Dsaudio
Stuff I'd like to see in the libnds audio implementation
Contents |
Introduction
The current libnds audio implementation lacks well most everything. There are only three audio related functions. So was considering what would be the best way to implement full featured audio functionality that makes full use of the DS's wonderful audio hardware and what features would best serve everyone's needs without adding unnecessary bloat to support rarely used functions while hopefully not preventing a developer from adding these features later.
What is here so far is based on discussions in #dsdev, various libnds and hardware docs on the web, and many years of audio/dsp, embedded programming, and music making experiences. If overlapping anything else already in development, it is not the intention to step on anyone's toes. Objective is simply to make the best audio library available for the DS developer without need to change things later... And then i may complete my Touhou fan game!!!!! \^_^/ ~eris - 2008/02/15
Design Considerations
- efficently using fifo, memory, and cpu balanced against ease of use and flexibility
- making full use of the DS's audio hardware, including PSG
- adding most common features without causing bloat
- no need for end user to modify arm7 code AT ALL
Features
- Sample playback
- Load samples from file in raw and wave formats
- Dynamic or static allocation of the DS's 16 audio channels.
- Basic audio synthesizer using either the PSG/Noise generators or audio samples
- Not suxy :p
Functions
Initialization and Misc
mmSoundInit
void mmSoundInit(void); //Initalize audio system (might need a reinit that will free() dynamics mem allocs?)
Self explanitory, reqired once to initalize audio system.
mmSoundInitAdvanced
bool mmSoundInitAdvanced(u8 maxsamples, u8 maxpatches) { sampletable=malloc(maxsamples*sizeof(sampleentry)); patchtable=malloc(maxpatches*size(patchentry)); }
Just a thought, but you could dynamically alloc ram for arm7's sample tables and if implemented synth tables allowing user to only allocate what they need. ie:
- maxsamples - the absolute most samples the program will use.
- maxpatches - the most synthesizer patches that will be used.
mmSoundSetMasterLevel
void mmSoundSetMasterLevel(u8 level);
Set Master audio level (default to full)
mmSoundSpkrEnable
void mmSoundSpkrEnable(bool enable);
Enables/disables speakers (should be enabled by default in init)
Audio Sample Loading
all 'Load' commands return mmSample handle or -1 if error most sample loadinging returning u16 value to support up to 65536 samples, however may just use u8 for 256 samples to reduce ram usage. (should be plenty and 65536 is overkill!!)
The following argument apply to this section: rate - sample rate in hertz. For instance 22050, 44100, 9600 are typical values level - volume 0-127 pan - pan 0-127 flags - various sample flags (see table) start - pointer to start of sample len - sample length in bytes
mmSampleDefaults
void mmSampleDefaults(int rate, char level, char pan, u8 flags);
set default values used by all subsequently 'SimpleLoad'ed samples.
mmSampleSimpleLoad
u16 mmSampleSimpleLoad(void *start, int len);
mmSampleLoad
u16 mmSampleLoad(void *start, int len, void *loopstart, int rate, char level, char pan, u8 flags);
mmSampleSimpleLoadFile
u16 mmSampleSimpleLoadFile(char *file);
mmSampleLoadFile
u16 mmSampleLoadFile(char *file, int rate, char level, char pan, u8 flags);
//load sample from filesystem (in raw or wav (or mp3 would be really nice) format)
mmSampleSimpleLoadWavFile
u16 mmSampleSimpleLoadWavFile(char *file);
mmSampleLoadWavFile
u16 mmSampleLoadWavFile(char *file, char level, char pan, u8 flags);
riff header is uber ez to parse so why not support .wav format??
mmSampleUnload
void mmSampleUnload(u16 mmSample);
stops playing and removes sample from sampletable, and if loaded from file (see below) will free() malloc'd ram..
Audio Sample Playback
mmSamplePlay
char mmSamplePlay(u16 mmSample);
returns channel mmChan or -1 if no channels available (if stereo sample high nibble contains right channel # and low nibble contains left channel number)
mmSampleStop
void mmSampleStop(u16 mmSample);
stops sample on all channels currently using it
mmSampleSetRate
void mmSampleSetRate(u16 mmSample, int rate);
mmSampleSetLevel
void mmSampleSetLevel(u16 mmSample, char level);
mmSampleSetPan
void mmSampleSetPan(u16 mmSample, char pan);
mmSampleSetFlags
void mmSampleSetFlags(u16 mmSample, char flags);
ORs Flags? or should just set them??
mmSampleClearFlags
void mmSampleClearFlags(u16 mmSample, char flags);
if go with ORing</source>
Channel Specific functions
if mmSamplePlay() reuses the channel 'Set' values are lost mmChan must be 0-15, if stereo sample (and high nibble is used) user is required to mask off upper bits
mmChanStop
void mmChanStop(char mmChan);
stops sample based on the channel
mmChanSetRate
void mmChanSetRate(char mmChan, int rate);
sets sample rate
mmChanSetLevel
void mmChanSetLevel(char mmChan, u8 level);
sets vol of a specified channel
mmChanSetPan
void mmChanSetPan(char mmChan, char pan);
sets pan of specified channel
mmChanSetFlags
void mmChanSetFlags(char mmChan,u8 flags);
sets flags (for instance STICKY, not all flags valid for chans)
mmChanSamplePlay
void mmChanSamplePlay(char chan, int mmSample);
Prolly normally not used, but if u did want to use a specific channel for a specific reason, here ya go..
mmChanGetStatus
u8 mmChanGetStatus(char chan);
returns channel busy flag and mebbe other things?
PSG/NOISE Related Functions
these may be unnecessary if the synth option is implemented...
mmChanPsgNote
void mmChanPsgNote(char mmChan, u8 note);
Plays specified diatonic note for instance 0=LowestC 12=next octave's C etc etc 0 tru 127 sorta like midi
mmChanPsgNote
void mmChanPsgNote(char mmChan, u8 note, u8 level, u8 pan,u8 duty);
same as above but sets all at once
mmChanPsgNoteDetune =
void mmChanPsgNoteDetune(char mmChan, int amount);
Detunes note by specified amount (for pitch bends)
mmChanGet
char mmChanGet(u8 type);
returns a free channel than can be used for psg or whatever (or -1 if none) type specify to get any free channel, or psg channel, or noise channel using defines below... MAY need to at the same time flag this channel as being in use to prevent arm7 from using it before arm9 gets a chance.. tho only should happen if interrupts or multiprocess?! In that case change name to mmChanAlloc
#define MMGET_ANY 0x0 #define MMGET_PSG 0x1 #define MMGET_NOISE 0x2
Synthesizer Functions
now would be the time to add synth functions (using the psg/noise and mostly arm7 cpu) such as: AD(SR) envelope generators for volume. A few (4 works for me) LFOs that can be assigned to volume, dutycycle, pitch, or pan of each 'patch'.
mmSynthPatchCreatePsg
u8 mmSynthPatchCreatePsg(u16 atime, u16 dtime, u8 slevel, u16 rtime, u8 envamount, u16 glidetime, u8 duty, u8 pan, u8 vollfo, u8 dutylfo, u8 pitchlfo, u8 panlfo);
Pass this info as a struct?? would save memory that way :) 4 lfo params can be combined into one byte if only 4 lfo are available
mmSynthPatchCreateNoise
u8 mmSynthPatchCreateNoise(u16 atime, u16 dtime, u8 slevel, u16 rtime, u8 envamount, u8 vollfo, u8 dutylfo, u8 panlfo);
mmSynthPatchCreateSample
u8 mmSynthPatchCreateSample(u8 mmSample, u16 atime, u16 dtime, u8 slevel, u16 rtime, u8 envamount, u16 glidetime, u8 pan, u8 vollfo, u8 pitchlfo, u8 panlfo);
4 LFO that can be used by a patch to modify various patch params
mmSynthLfo
void mmSynthLfo(u8 lfo, u8 lfotype, u16 freq, u8 amount);
lfo = 0 thru 3 indicating which lfo to adjust. freq = fixed decimal frequency, 8 bits for decimal. amount is the max level , type is either triangle, square (mebbe sawtooth ramp up, ramp down for bomp bomp bomp or womp womp womp effects :)
mmSynthNoteOn
u8 mmSynthNoteOn(u8 patch, u8 note);
returns channel used...
mmSynthNoteOnVel
u8 mmSynthNoteOnVel(u8 patch, u8 note, u8 velocity);
same as above but with velocity (volume) param
mmSynthNoteOff
void mmSynthNoteOff(u8 patch, u8 note);
stops playing note based on patch/note (note u could also use the mmSynthNoteOn()'s return value with mmChanStop() for doing same thing with less overhead); Add mmSynthNoteBend() or just use the mmChanPsgNoteDetune ??? would be less cpu ... dunno
Defines, Globals, and Structures
Usage Examples
Simplest sample playback scenario:
mmSoundInit(); //Initalize sound lib and set default params (22050khz, PCM8, no loop, mono) mmSamplePlay(mmSampleSimpleLoad((void *)mysample_raw, mysample_len));
Okai, mmSoundInit(); must be called at least once for everything else so im not gonna bother putting in further examples
Playing a 44100khz 16bit sample and stopping it after a little bit...
u16 testsample=mmSampleLoad((void *)mysample_raw, mysample_len, NULL, 44100, 127, 64, MMFLAGS_PCM16); mmSamplePlay(testsample); msleep(20000); mmSampleStop(testsample);
Playing a sample from filesystem using the default rate/vol/pan/flags:
u16 testsample=mmSampleSimpleLoadFile("/pathto/mysample.raw"); mmSamplePlay(testsample);
Reusing the same channel for same sample
u16 testsample=mmSampleSimpleLoad((void*)mysample_raw,mysample_len); u8 samplechan=mmSamplePlay(testsample); doSomethingForABit(); mmChanSamplePlay(samplechan,testsample); //reuse same channel
Alternate way to do sorta same thing, in this case make a imaginary spaceship's laser firing noise:
u16 shipshootsample=mmSampleSimpleLoad((void*)mysample_raw,mysample_len); u8 shipshootchannel=mmChanGet(MMGET_ANY); //Get an unused sample channel mmChanSetFlags(MMFLAGS_STICKY); //optional, used to indicate this channel shouldnt be dynamically allocated to other sounds while(1) { if(shipgotshot) { mmChanSamplePlay(shipshootchannel,shipshootsample); } };