Sound Engine crash Course

From Uzebox Wiki
Revision as of 19:19, 17 August 2011 by Three-Wolves (talk | contribs)
Jump to navigation Jump to search

If you are like most, composing music is not your cup of tea. However, you will indeed want to quickly put some sound FXs in your games. Here's a quick crash course on how to do just that.

The current engine works with the concept of "patches". Patches are a sequence of parameters that defines how your notes or sound effects evolve over time. There's already a couple of patches I've made for Megatris. I've regrouped them in an include file. Add this include to your program file:


   #include "data/patches.inc"


IF you are using the pre-beta3 kernel, look into it, you will see something like:


   //FX: "Echo Droplet"
   const char patch01[] PROGMEM ={
   0,
   0,PC_ENV_SPEED,-12,
   5,PC_NOTE_UP,12,
   5,PC_NOTE_DOWN,12,
   5,PC_NOTE_UP,12,
   5,PC_NOTE_DOWN,12,
   5,PC_NOTE_CUT,0,
   0,PATCH_END
   };


This is called a command stream. The first byte is the sound type. 0 is for wavetable sounds, 1 for noise channels sound, (from beta3 and onwards, this byte is removed, more on that later). The rest is a sequence of commands.

Commands are made of 3 bytes: the first one is a time delta in term of frames, (frames happen at 1/60 of a sec. to wait until this command is executed). The second byte is the command type, and the last (third) byte is the command value.

So in this example we have a wavetable sound (0), then when the sound is triggered (time zero), the volume envelope decay speed is set to -12. On each frame afterward -12 will automatically be subtracted from the sound's volume.

Then, after a wait of 5 frames, the sounds pitch is incremented by 12 semitones (one octave). Then wait again for 5 frames and the sound's pitch is decremented by an octave. And so on, until the last command, which must be a PATCH_END command (no value byte for this one). Have a look at the .h files for all the possible commands.

From Beta3 the patch system has been tweaked to support a PCM channel which allows you to play samples of arbitrary length. The sound type byte as been removed from the command stream and move into a special array of structs.

   const struct PatchStruct patches[] PROGMEM = {
   {0,NULL,patch00,0,0},
   {0,NULL,patch01,0,0},
   {0,NULL,patch02,0,0},
   {0,NULL,patch03,0,0},
   {1,NULL,patch04,0,0},
   ...
   };


Let's interpret one entry:


   {1,NULL,patch04,0,0} 


First parameter (1) is the sound type, in this case it is to be played on the noise channel (0=wave, 1=noise, 2=PCM). Second parameter (NULL) is a pointer to the PCM data if it were a PCM patch. Third parameter (patch04) is the patch's command stream pointer. Fourth (0) is the loop start position for PCM samples. Fifth (0) is the loop end position for PCM samples.

Soooo....now let play some sounds! Add this line to your main() function:


   InitMusicPlayer(patches);


And *then* you can trigger fxs anywhere in your code. If you look in patches.inc, patch 19 is the "t-spin" fx from megatris


   TriggerFx(19,0xff,true);


In this case: 19 is the patch number. 0xff is the volume. true is the 'retrig' attribute.

The 'retrig' attribute basically means that if another TriggerFx() call is made for the same patch *before* the previous one is finished playing, it will re-trigger it right away on the same channel instead of starting another simultaneous instance of the sound onto another channel (determined by the voice stealing algorithm).

Creating new patches is really trial and error. I suggest you make an empty project to create new patches. It will compile and flash faster.

That's it!