[In this reprinted #altdevblogaday-opinion piece, indie developer Mike Slone examines a dynamic music system he recently built, explaining how it works and offering helpful pointers on how to leverage the BASS API for such a system.] I find it easiest to write about something I've done recently, so I'm going to share about the dynamic music system I made for Chickens 4 Cash on Monday. For the sake of keeping things simple, I'm going to explain this as though we're accessing the API directly, despite that it's actually wrapped in my own code. Fat Chance: A friend of mine, off whom I often bounce my game ideas, suggested putting together a dynamic music system for Chickens 4 Cash. Initially, I imagined it being the way it had been at Volition, with all kinds of awesome orchestral cues with smooth, dynamic transitions. With that in mind, my initial reaction was that the amount of work required was prohibitive, given the scope the game. New Inspiration: I had been working on music for a boss , and it became apparent, not only that the stages of the boss (because every good shmup boss has stages) each needed to have their own musical cue, but also that the level could potentially continue on indefinitely. So I got to work on a new plan for the system. Keeping it Simple With the notion that I needed to keep it simple, and some exploration into how my audio API worked, the Slonersoft dynamic music system became as follows…
Music is split into 'sets' which contain a series of loops which can be played back end-to-end.
Each set has one contiguous audio file, within which we will jump around to make the loops work.
This played best with the API, which supports seamlessly jumping around within a playing stream.
My initial plan was to have separate files for each loop, but they didn't line up seamlessly in playback.
One set is loaded & played at a time.
The Loop Data:
A name & name hash for the loop.
A start & end time (floating point seconds in the XML, QWORD of bytes in code)
The Set Data:
A name & name hash for the set.
The filename for the audio.
A list of loops.
A handle for the playing audio channel (the stream handle in the case of BASS streams).
An index of the default loop that should play if the queue ends or if the set is asked to play with no loop index parameter.
Using the API Getting the Times in Bytes: You'll need the start and end times of your loops in terms of bytes. To convert from time in seconds to time in bytes, you'll use the function:
QWORD BASS_ChannelSeconds2Bytes(DWORD handle, double pos);
…passing in the channel/stream handle and the time in seconds. Getting Notified: To get notified that you have reached the end of the current loop, you'll use the function BASS_ChannelSetSync function and pass it a callback of type SYNC_PROC *. To sync to a specific time, you'll call it like so:
HSYNC loopSync = BASS_ChannelSetSync(channelHandle, BASS_SYNC_POS| BASS_SYNC_MIXTIME, timeInBytes, syncCallback, 0);
If you want to sync to the end of the stream (which you'll want to do for the last loop in the set), you call it like so:
HSYNC loopSync = BASS_ChannelSetSync(channelHandle, BASS_SYNC_END| BASS_SYNC_MIXTIME, 0, syncCallback, 0);
This function will pass back a value of type HSYNC, which you should store so that before you change it to something else, you can clear it with:
No tags.