Dsaudio

From ProjectWiki
Revision as of 18:10, 21 February 2008 by Eris (Talk | contribs)
Jump to: navigation, search

Stuff I'd like to see in the libnds audio implementation

Contents

Project Status: Preliminary design

Introduction

The current libnds audio implementation lacks well most everything. There are only three audio related functions.

This page is for 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 (without them modifying arm7 code preferably).

Objective of the project overall is to make useful an extensible high quality developer friendly audio library available for DS homebrew... Because, like others, need or otherwise would like it for my game!!!!! :p

Feel free to comment or discuss concerns or give feedback such as "I'll never use this bit and don't see why anyone else would either", or "I really need this useful feature that can be useful to others.", or anything that comes to mind.

What is here so far is based on discussions on irc, various libnds and hardware docs on the web like these.

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
  • 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
  • Not suxy :p

Todo

Pretty much everything! But more specifically:

  • Create some type of generalized overview of the complete system
  • Document/define structures, and types for lib specific return values
  • Otherwise improve documentation in general
  • Tho a lot of it is self evident, still need to work out the arm7 side enough to assist in defining an efficient scheme for communication (utilizing the newly added FIFO functionality) between the arm7 and arm9.

Functions

So far functions present the arm9 side of things a game/app dev would see.

Initialization and Global Settings

mmSoundInit

void mmSoundInit(void);

Initalize audio system.
Self explanitory, reqired once to initalize audio system.
might need a reinit that will free() dynamics mem allocs?

mmSoundInitAdvanced

bool mmSoundInitAdvanced(u8 maxsamples, u8 maxpatches) {
  sampletable=malloc(maxsamples*sizeof(sampleentry));
  patchtable=malloc(maxpatches*size(patchentry));
}

This function is 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.

Loading From Ram

mmSampleLoad

u16 mmSampleLoad(void *start, int len, void *loopstart, int rate, char level, char pan, u8 flags);

mmSampleSimpleLoad

u16 mmSampleSimpleLoad(void *start, int len);

Loading From File

mmSampleLoadFile

u16 mmSampleLoadFile(char *file, int rate, char level, char pan, u8 flags);

Load sample from filesystem in raw mode.

mmSampleSimpleLoadFile

u16 mmSampleSimpleLoadFile(char *file);

Load sample from filesystem in raw mode using default values.

mmSampleLoadWavFile

u16 mmSampleLoadWavFile(char *file, char level, char pan, u8 flags);

riff header is uber ez to parse so why not support .wav format?? Load sample from file in wav format.

mmSampleSimpleLoadWavFile

u16 mmSampleSimpleLoadWavFile(char *file);

Load sample from file in wav format using default values.

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 hardware channel to retrig 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);
 }
};

References/External Links

Hardware references and other DS audio libraries
Maxmod library It would be nice if this was merged or at least compatible with eKids's project.
gbatek sound
neimod
#dsdev@irc.blitzed.org

Example Header

Mostly useless outdated header example can be found here. Original notes related to implementing an audio library written in .c syntax. This wiki page represents a more refined version of these header notes.

Personal tools
irssi scripts
eggdrop scripts