0,0 → 1,1488 |
/* |
SDL_mixer: An audio mixer library based on the SDL library |
Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org> |
|
This software is provided 'as-is', without any express or implied |
warranty. In no event will the authors be held liable for any damages |
arising from the use of this software. |
|
Permission is granted to anyone to use this software for any purpose, |
including commercial applications, and to alter it and redistribute it |
freely, subject to the following restrictions: |
|
1. The origin of this software must not be misrepresented; you must not |
claim that you wrote the original software. If you use this software |
in a product, an acknowledgment in the product documentation would be |
appreciated but is not required. |
2. Altered source versions must be plainly marked as such, and must not be |
misrepresented as being the original software. |
3. This notice may not be removed or altered from any source distribution. |
*/ |
|
/* $Id$ */ |
|
#include <stdio.h> |
#include <stdlib.h> |
#include <string.h> |
|
#include "SDL_mutex.h" |
#include "SDL_endian.h" |
#include "SDL_timer.h" |
|
#include "SDL_mixer.h" |
#include "load_aiff.h" |
#include "load_voc.h" |
#include "load_ogg.h" |
#include "load_flac.h" |
#include "dynamic_flac.h" |
#include "dynamic_mod.h" |
#include "dynamic_mp3.h" |
#include "dynamic_ogg.h" |
|
#define __MIX_INTERNAL_EFFECT__ |
#include "effects_internal.h" |
|
/* Magic numbers for various audio file formats */ |
#define RIFF 0x46464952 /* "RIFF" */ |
#define WAVE 0x45564157 /* "WAVE" */ |
#define FORM 0x4d524f46 /* "FORM" */ |
#define OGGS 0x5367674f /* "OggS" */ |
#define CREA 0x61657243 /* "Crea" */ |
#define FLAC 0x43614C66 /* "fLaC" */ |
|
static int audio_opened = 0; |
static SDL_AudioSpec mixer; |
|
typedef struct _Mix_effectinfo |
{ |
Mix_EffectFunc_t callback; |
Mix_EffectDone_t done_callback; |
void *udata; |
struct _Mix_effectinfo *next; |
} effect_info; |
|
static struct _Mix_Channel { |
Mix_Chunk *chunk; |
int playing; |
int paused; |
Uint8 *samples; |
int volume; |
int looping; |
int tag; |
Uint32 expire; |
Uint32 start_time; |
Mix_Fading fading; |
int fade_volume; |
int fade_volume_reset; |
Uint32 fade_length; |
Uint32 ticks_fade; |
effect_info *effects; |
} *mix_channel = NULL; |
|
static effect_info *posteffects = NULL; |
|
static int num_channels; |
static int reserved_channels = 0; |
|
|
/* Support for hooking into the mixer callback system */ |
static void (*mix_postmix)(void *udata, Uint8 *stream, int len) = NULL; |
static void *mix_postmix_data = NULL; |
|
/* rcg07062001 callback to alert when channels are done playing. */ |
static void (*channel_done_callback)(int channel) = NULL; |
|
/* Music function declarations */ |
extern int open_music(SDL_AudioSpec *mixer); |
extern void close_music(void); |
|
/* Support for user defined music functions, plus the default one */ |
extern int volatile music_active; |
extern void music_mixer(void *udata, Uint8 *stream, int len); |
static void (*mix_music)(void *udata, Uint8 *stream, int len) = music_mixer; |
static void *music_data = NULL; |
|
/* rcg06042009 report available decoders at runtime. */ |
static const char **chunk_decoders = NULL; |
static int num_decoders = 0; |
|
/* Semicolon-separated SoundFont paths */ |
#ifdef MID_MUSIC |
extern char* soundfont_paths; |
#endif |
|
int Mix_GetNumChunkDecoders(void) |
{ |
return(num_decoders); |
} |
|
const char *Mix_GetChunkDecoder(int index) |
{ |
if ((index < 0) || (index >= num_decoders)) { |
return NULL; |
} |
return(chunk_decoders[index]); |
} |
|
static void add_chunk_decoder(const char *decoder) |
{ |
void *ptr = SDL_realloc(chunk_decoders, (num_decoders + 1) * sizeof (const char **)); |
if (ptr == NULL) { |
return; /* oh well, go on without it. */ |
} |
chunk_decoders = (const char **) ptr; |
chunk_decoders[num_decoders++] = decoder; |
} |
|
/* rcg06192001 get linked library's version. */ |
const SDL_version *Mix_Linked_Version(void) |
{ |
static SDL_version linked_version; |
SDL_MIXER_VERSION(&linked_version); |
return(&linked_version); |
} |
|
static int initialized = 0; |
|
int Mix_Init(int flags) |
{ |
int result = 0; |
|
if (flags & MIX_INIT_FLUIDSYNTH) { |
#ifdef USE_FLUIDSYNTH_MIDI |
if ((initialized & MIX_INIT_FLUIDSYNTH) || Mix_InitFluidSynth() == 0) { |
result |= MIX_INIT_FLUIDSYNTH; |
} |
#else |
Mix_SetError("Mixer not built with FluidSynth support"); |
#endif |
} |
if (flags & MIX_INIT_FLAC) { |
#ifdef FLAC_MUSIC |
if ((initialized & MIX_INIT_FLAC) || Mix_InitFLAC() == 0) { |
result |= MIX_INIT_FLAC; |
} |
#else |
Mix_SetError("Mixer not built with FLAC support"); |
#endif |
} |
if (flags & MIX_INIT_MOD) { |
#ifdef MOD_MUSIC |
if ((initialized & MIX_INIT_MOD) || Mix_InitMOD() == 0) { |
result |= MIX_INIT_MOD; |
} |
#else |
Mix_SetError("Mixer not built with MOD support"); |
#endif |
} |
if (flags & MIX_INIT_MP3) { |
#ifdef MP3_MUSIC |
if ((initialized & MIX_INIT_MP3) || Mix_InitMP3() == 0) { |
result |= MIX_INIT_MP3; |
} |
#else |
Mix_SetError("Mixer not built with MP3 support"); |
#endif |
} |
if (flags & MIX_INIT_OGG) { |
#ifdef OGG_MUSIC |
if ((initialized & MIX_INIT_OGG) || Mix_InitOgg() == 0) { |
result |= MIX_INIT_OGG; |
} |
#else |
Mix_SetError("Mixer not built with Ogg Vorbis support"); |
#endif |
} |
initialized |= result; |
|
return (result); |
} |
|
void Mix_Quit() |
{ |
#ifdef USE_FLUIDSYNTH_MIDI |
if (initialized & MIX_INIT_FLUIDSYNTH) { |
Mix_QuitFluidSynth(); |
} |
#endif |
#ifdef FLAC_MUSIC |
if (initialized & MIX_INIT_FLAC) { |
Mix_QuitFLAC(); |
} |
#endif |
#ifdef MOD_MUSIC |
if (initialized & MIX_INIT_MOD) { |
Mix_QuitMOD(); |
} |
#endif |
#ifdef MP3_MUSIC |
if (initialized & MIX_INIT_MP3) { |
Mix_QuitMP3(); |
} |
#endif |
#ifdef OGG_MUSIC |
if (initialized & MIX_INIT_OGG) { |
Mix_QuitOgg(); |
} |
#endif |
#ifdef MID_MUSIC |
if (soundfont_paths) { |
SDL_free(soundfont_paths); |
} |
#endif |
initialized = 0; |
} |
|
static int _Mix_remove_all_effects(int channel, effect_info **e); |
|
/* |
* rcg06122001 Cleanup effect callbacks. |
* MAKE SURE SDL_LockAudio() is called before this (or you're in the |
* audio callback). |
*/ |
static void _Mix_channel_done_playing(int channel) |
{ |
if (channel_done_callback) { |
channel_done_callback(channel); |
} |
|
/* |
* Call internal function directly, to avoid locking audio from |
* inside audio callback. |
*/ |
_Mix_remove_all_effects(channel, &mix_channel[channel].effects); |
} |
|
|
static void *Mix_DoEffects(int chan, void *snd, int len) |
{ |
int posteffect = (chan == MIX_CHANNEL_POST); |
effect_info *e = ((posteffect) ? posteffects : mix_channel[chan].effects); |
void *buf = snd; |
|
if (e != NULL) { /* are there any registered effects? */ |
/* if this is the postmix, we can just overwrite the original. */ |
if (!posteffect) { |
buf = SDL_malloc(len); |
if (buf == NULL) { |
return(snd); |
} |
memcpy(buf, snd, len); |
} |
|
for (; e != NULL; e = e->next) { |
if (e->callback != NULL) { |
e->callback(chan, buf, len, e->udata); |
} |
} |
} |
|
/* be sure to SDL_free() the return value if != snd ... */ |
return(buf); |
} |
|
|
/* Mixing function */ |
static void mix_channels(void *udata, Uint8 *stream, int len) |
{ |
Uint8 *mix_input; |
int i, mixable, volume = SDL_MIX_MAXVOLUME; |
Uint32 sdl_ticks; |
|
#if SDL_VERSION_ATLEAST(1, 3, 0) |
/* Need to initialize the stream in SDL 1.3+ */ |
memset(stream, mixer.silence, len); |
#endif |
|
/* Mix the music (must be done before the channels are added) */ |
if ( music_active || (mix_music != music_mixer) ) { |
mix_music(music_data, stream, len); |
} |
|
/* Mix any playing channels... */ |
sdl_ticks = SDL_GetTicks(); |
for ( i=0; i<num_channels; ++i ) { |
if( ! mix_channel[i].paused ) { |
if ( mix_channel[i].expire > 0 && mix_channel[i].expire < sdl_ticks ) { |
/* Expiration delay for that channel is reached */ |
mix_channel[i].playing = 0; |
mix_channel[i].looping = 0; |
mix_channel[i].fading = MIX_NO_FADING; |
mix_channel[i].expire = 0; |
_Mix_channel_done_playing(i); |
} else if ( mix_channel[i].fading != MIX_NO_FADING ) { |
Uint32 ticks = sdl_ticks - mix_channel[i].ticks_fade; |
if( ticks > mix_channel[i].fade_length ) { |
Mix_Volume(i, mix_channel[i].fade_volume_reset); /* Restore the volume */ |
if( mix_channel[i].fading == MIX_FADING_OUT ) { |
mix_channel[i].playing = 0; |
mix_channel[i].looping = 0; |
mix_channel[i].expire = 0; |
_Mix_channel_done_playing(i); |
} |
mix_channel[i].fading = MIX_NO_FADING; |
} else { |
if( mix_channel[i].fading == MIX_FADING_OUT ) { |
Mix_Volume(i, (mix_channel[i].fade_volume * (mix_channel[i].fade_length-ticks)) |
/ mix_channel[i].fade_length ); |
} else { |
Mix_Volume(i, (mix_channel[i].fade_volume * ticks) / mix_channel[i].fade_length ); |
} |
} |
} |
if ( mix_channel[i].playing > 0 ) { |
int index = 0; |
int remaining = len; |
while (mix_channel[i].playing > 0 && index < len) { |
remaining = len - index; |
volume = (mix_channel[i].volume*mix_channel[i].chunk->volume) / MIX_MAX_VOLUME; |
mixable = mix_channel[i].playing; |
if ( mixable > remaining ) { |
mixable = remaining; |
} |
|
mix_input = Mix_DoEffects(i, mix_channel[i].samples, mixable); |
SDL_MixAudio(stream+index,mix_input,mixable,volume); |
if (mix_input != mix_channel[i].samples) |
SDL_free(mix_input); |
|
mix_channel[i].samples += mixable; |
mix_channel[i].playing -= mixable; |
index += mixable; |
|
/* rcg06072001 Alert app if channel is done playing. */ |
if (!mix_channel[i].playing && !mix_channel[i].looping) { |
_Mix_channel_done_playing(i); |
} |
} |
|
/* If looping the sample and we are at its end, make sure |
we will still return a full buffer */ |
while ( mix_channel[i].looping && index < len ) { |
int alen = mix_channel[i].chunk->alen; |
remaining = len - index; |
if (remaining > alen) { |
remaining = alen; |
} |
|
mix_input = Mix_DoEffects(i, mix_channel[i].chunk->abuf, remaining); |
SDL_MixAudio(stream+index, mix_input, remaining, volume); |
if (mix_input != mix_channel[i].chunk->abuf) |
SDL_free(mix_input); |
|
--mix_channel[i].looping; |
mix_channel[i].samples = mix_channel[i].chunk->abuf + remaining; |
mix_channel[i].playing = mix_channel[i].chunk->alen - remaining; |
index += remaining; |
} |
if ( ! mix_channel[i].playing && mix_channel[i].looping ) { |
--mix_channel[i].looping; |
mix_channel[i].samples = mix_channel[i].chunk->abuf; |
mix_channel[i].playing = mix_channel[i].chunk->alen; |
} |
} |
} |
} |
|
/* rcg06122001 run posteffects... */ |
Mix_DoEffects(MIX_CHANNEL_POST, stream, len); |
|
if ( mix_postmix ) { |
mix_postmix(mix_postmix_data, stream, len); |
} |
} |
|
#if 0 |
static void PrintFormat(char *title, SDL_AudioSpec *fmt) |
{ |
printf("%s: %d bit %s audio (%s) at %u Hz\n", title, (fmt->format&0xFF), |
(fmt->format&0x8000) ? "signed" : "unsigned", |
(fmt->channels > 2) ? "surround" : |
(fmt->channels > 1) ? "stereo" : "mono", fmt->freq); |
} |
#endif |
|
|
/* Open the mixer with a certain desired audio format */ |
int Mix_OpenAudio(int frequency, Uint16 format, int nchannels, int chunksize) |
{ |
int i; |
SDL_AudioSpec desired; |
|
/* If the mixer is already opened, increment open count */ |
if ( audio_opened ) { |
if ( format == mixer.format && nchannels == mixer.channels ) { |
++audio_opened; |
return(0); |
} |
while ( audio_opened ) { |
Mix_CloseAudio(); |
} |
} |
|
/* Set the desired format and frequency */ |
desired.freq = frequency; |
desired.format = format; |
desired.channels = nchannels; |
desired.samples = chunksize; |
desired.callback = mix_channels; |
desired.userdata = NULL; |
|
/* Accept nearly any audio format */ |
if ( SDL_OpenAudio(&desired, &mixer) < 0 ) { |
return(-1); |
} |
#if 0 |
PrintFormat("Audio device", &mixer); |
#endif |
|
/* Initialize the music players */ |
if ( open_music(&mixer) < 0 ) { |
SDL_CloseAudio(); |
return(-1); |
} |
|
num_channels = MIX_CHANNELS; |
mix_channel = (struct _Mix_Channel *) SDL_malloc(num_channels * sizeof(struct _Mix_Channel)); |
|
/* Clear out the audio channels */ |
for ( i=0; i<num_channels; ++i ) { |
mix_channel[i].chunk = NULL; |
mix_channel[i].playing = 0; |
mix_channel[i].looping = 0; |
mix_channel[i].volume = SDL_MIX_MAXVOLUME; |
mix_channel[i].fade_volume = SDL_MIX_MAXVOLUME; |
mix_channel[i].fade_volume_reset = SDL_MIX_MAXVOLUME; |
mix_channel[i].fading = MIX_NO_FADING; |
mix_channel[i].tag = -1; |
mix_channel[i].expire = 0; |
mix_channel[i].effects = NULL; |
mix_channel[i].paused = 0; |
} |
Mix_VolumeMusic(SDL_MIX_MAXVOLUME); |
|
_Mix_InitEffects(); |
|
/* This list is (currently) decided at build time. */ |
add_chunk_decoder("WAVE"); |
add_chunk_decoder("AIFF"); |
add_chunk_decoder("VOC"); |
#ifdef OGG_MUSIC |
add_chunk_decoder("OGG"); |
#endif |
#ifdef FLAC_MUSIC |
add_chunk_decoder("FLAC"); |
#endif |
|
audio_opened = 1; |
SDL_PauseAudio(0); |
return(0); |
} |
|
/* Dynamically change the number of channels managed by the mixer. |
If decreasing the number of channels, the upper channels are |
stopped. |
*/ |
int Mix_AllocateChannels(int numchans) |
{ |
if ( numchans<0 || numchans==num_channels ) |
return(num_channels); |
|
if ( numchans < num_channels ) { |
/* Stop the affected channels */ |
int i; |
for(i=numchans; i < num_channels; i++) { |
Mix_UnregisterAllEffects(i); |
Mix_HaltChannel(i); |
} |
} |
SDL_LockAudio(); |
mix_channel = (struct _Mix_Channel *) SDL_realloc(mix_channel, numchans * sizeof(struct _Mix_Channel)); |
if ( numchans > num_channels ) { |
/* Initialize the new channels */ |
int i; |
for(i=num_channels; i < numchans; i++) { |
mix_channel[i].chunk = NULL; |
mix_channel[i].playing = 0; |
mix_channel[i].looping = 0; |
mix_channel[i].volume = SDL_MIX_MAXVOLUME; |
mix_channel[i].fade_volume = SDL_MIX_MAXVOLUME; |
mix_channel[i].fade_volume_reset = SDL_MIX_MAXVOLUME; |
mix_channel[i].fading = MIX_NO_FADING; |
mix_channel[i].tag = -1; |
mix_channel[i].expire = 0; |
mix_channel[i].effects = NULL; |
mix_channel[i].paused = 0; |
} |
} |
num_channels = numchans; |
SDL_UnlockAudio(); |
return(num_channels); |
} |
|
/* Return the actual mixer parameters */ |
int Mix_QuerySpec(int *frequency, Uint16 *format, int *channels) |
{ |
if ( audio_opened ) { |
if ( frequency ) { |
*frequency = mixer.freq; |
} |
if ( format ) { |
*format = mixer.format; |
} |
if ( channels ) { |
*channels = mixer.channels; |
} |
} |
return(audio_opened); |
} |
|
|
/* |
* !!! FIXME: Ideally, we want a Mix_LoadSample_RW(), which will handle the |
* generic setup, then call the correct file format loader. |
*/ |
|
/* Load a wave file */ |
Mix_Chunk *Mix_LoadWAV_RW(SDL_RWops *src, int freesrc) |
{ |
Uint32 magic; |
Mix_Chunk *chunk; |
SDL_AudioSpec wavespec, *loaded; |
SDL_AudioCVT wavecvt; |
int samplesize; |
|
/* rcg06012001 Make sure src is valid */ |
if ( ! src ) { |
SDL_SetError("Mix_LoadWAV_RW with NULL src"); |
return(NULL); |
} |
|
/* Make sure audio has been opened */ |
if ( ! audio_opened ) { |
SDL_SetError("Audio device hasn't been opened"); |
if ( freesrc && src ) { |
SDL_RWclose(src); |
} |
return(NULL); |
} |
|
/* Allocate the chunk memory */ |
chunk = (Mix_Chunk *)SDL_malloc(sizeof(Mix_Chunk)); |
if ( chunk == NULL ) { |
SDL_SetError("Out of memory"); |
if ( freesrc ) { |
SDL_RWclose(src); |
} |
return(NULL); |
} |
|
/* Find out what kind of audio file this is */ |
magic = SDL_ReadLE32(src); |
/* Seek backwards for compatibility with older loaders */ |
SDL_RWseek(src, -(int)sizeof(Uint32), SEEK_CUR); |
|
switch (magic) { |
case WAVE: |
case RIFF: |
loaded = SDL_LoadWAV_RW(src, freesrc, &wavespec, |
(Uint8 **)&chunk->abuf, &chunk->alen); |
break; |
case FORM: |
loaded = Mix_LoadAIFF_RW(src, freesrc, &wavespec, |
(Uint8 **)&chunk->abuf, &chunk->alen); |
break; |
#ifdef OGG_MUSIC |
case OGGS: |
loaded = Mix_LoadOGG_RW(src, freesrc, &wavespec, |
(Uint8 **)&chunk->abuf, &chunk->alen); |
break; |
#endif |
#ifdef FLAC_MUSIC |
case FLAC: |
loaded = Mix_LoadFLAC_RW(src, freesrc, &wavespec, |
(Uint8 **)&chunk->abuf, &chunk->alen); |
break; |
#endif |
case CREA: |
loaded = Mix_LoadVOC_RW(src, freesrc, &wavespec, |
(Uint8 **)&chunk->abuf, &chunk->alen); |
break; |
default: |
SDL_SetError("Unrecognized sound file type"); |
return(0); |
} |
if ( !loaded ) { |
SDL_free(chunk); |
if ( freesrc ) { |
SDL_RWclose(src); |
} |
return(NULL); |
} |
|
#if 0 |
PrintFormat("Audio device", &mixer); |
PrintFormat("-- Wave file", &wavespec); |
#endif |
|
/* Build the audio converter and create conversion buffers */ |
if ( wavespec.format != mixer.format || |
wavespec.channels != mixer.channels || |
wavespec.freq != mixer.freq ) { |
if ( SDL_BuildAudioCVT(&wavecvt, |
wavespec.format, wavespec.channels, wavespec.freq, |
mixer.format, mixer.channels, mixer.freq) < 0 ) { |
SDL_free(chunk->abuf); |
SDL_free(chunk); |
return(NULL); |
} |
samplesize = ((wavespec.format & 0xFF)/8)*wavespec.channels; |
wavecvt.len = chunk->alen & ~(samplesize-1); |
wavecvt.buf = (Uint8 *)SDL_calloc(1, wavecvt.len*wavecvt.len_mult); |
if ( wavecvt.buf == NULL ) { |
SDL_SetError("Out of memory"); |
SDL_free(chunk->abuf); |
SDL_free(chunk); |
return(NULL); |
} |
memcpy(wavecvt.buf, chunk->abuf, chunk->alen); |
SDL_free(chunk->abuf); |
|
/* Run the audio converter */ |
if ( SDL_ConvertAudio(&wavecvt) < 0 ) { |
SDL_free(wavecvt.buf); |
SDL_free(chunk); |
return(NULL); |
} |
|
chunk->abuf = wavecvt.buf; |
chunk->alen = wavecvt.len_cvt; |
} |
|
chunk->allocated = 1; |
chunk->volume = MIX_MAX_VOLUME; |
|
return(chunk); |
} |
|
/* Load a wave file of the mixer format from a memory buffer */ |
Mix_Chunk *Mix_QuickLoad_WAV(Uint8 *mem) |
{ |
Mix_Chunk *chunk; |
Uint8 magic[4]; |
|
/* Make sure audio has been opened */ |
if ( ! audio_opened ) { |
SDL_SetError("Audio device hasn't been opened"); |
return(NULL); |
} |
|
/* Allocate the chunk memory */ |
chunk = (Mix_Chunk *)SDL_calloc(1,sizeof(Mix_Chunk)); |
if ( chunk == NULL ) { |
SDL_SetError("Out of memory"); |
return(NULL); |
} |
|
/* Essentially just skip to the audio data (no error checking - fast) */ |
chunk->allocated = 0; |
mem += 12; /* WAV header */ |
do { |
memcpy(magic, mem, 4); |
mem += 4; |
chunk->alen = ((mem[3]<<24)|(mem[2]<<16)|(mem[1]<<8)|(mem[0])); |
mem += 4; |
chunk->abuf = mem; |
mem += chunk->alen; |
} while ( memcmp(magic, "data", 4) != 0 ); |
chunk->volume = MIX_MAX_VOLUME; |
|
return(chunk); |
} |
|
/* Load raw audio data of the mixer format from a memory buffer */ |
Mix_Chunk *Mix_QuickLoad_RAW(Uint8 *mem, Uint32 len) |
{ |
Mix_Chunk *chunk; |
|
/* Make sure audio has been opened */ |
if ( ! audio_opened ) { |
SDL_SetError("Audio device hasn't been opened"); |
return(NULL); |
} |
|
/* Allocate the chunk memory */ |
chunk = (Mix_Chunk *)SDL_malloc(sizeof(Mix_Chunk)); |
if ( chunk == NULL ) { |
SDL_SetError("Out of memory"); |
return(NULL); |
} |
|
/* Essentially just point at the audio data (no error checking - fast) */ |
chunk->allocated = 0; |
chunk->alen = len; |
chunk->abuf = mem; |
chunk->volume = MIX_MAX_VOLUME; |
|
return(chunk); |
} |
|
/* Free an audio chunk previously loaded */ |
void Mix_FreeChunk(Mix_Chunk *chunk) |
{ |
int i; |
|
/* Caution -- if the chunk is playing, the mixer will crash */ |
if ( chunk ) { |
/* Guarantee that this chunk isn't playing */ |
SDL_LockAudio(); |
if ( mix_channel ) { |
for ( i=0; i<num_channels; ++i ) { |
if ( chunk == mix_channel[i].chunk ) { |
mix_channel[i].playing = 0; |
mix_channel[i].looping = 0; |
} |
} |
} |
SDL_UnlockAudio(); |
/* Actually free the chunk */ |
if ( chunk->allocated ) { |
SDL_free(chunk->abuf); |
} |
SDL_free(chunk); |
} |
} |
|
/* Set a function that is called after all mixing is performed. |
This can be used to provide real-time visual display of the audio stream |
or add a custom mixer filter for the stream data. |
*/ |
void Mix_SetPostMix(void (*mix_func) |
(void *udata, Uint8 *stream, int len), void *arg) |
{ |
SDL_LockAudio(); |
mix_postmix_data = arg; |
mix_postmix = mix_func; |
SDL_UnlockAudio(); |
} |
|
/* Add your own music player or mixer function. |
If 'mix_func' is NULL, the default music player is re-enabled. |
*/ |
void Mix_HookMusic(void (*mix_func)(void *udata, Uint8 *stream, int len), |
void *arg) |
{ |
SDL_LockAudio(); |
if ( mix_func != NULL ) { |
music_data = arg; |
mix_music = mix_func; |
} else { |
music_data = NULL; |
mix_music = music_mixer; |
} |
SDL_UnlockAudio(); |
} |
|
void *Mix_GetMusicHookData(void) |
{ |
return(music_data); |
} |
|
void Mix_ChannelFinished(void (*channel_finished)(int channel)) |
{ |
SDL_LockAudio(); |
channel_done_callback = channel_finished; |
SDL_UnlockAudio(); |
} |
|
|
/* Reserve the first channels (0 -> n-1) for the application, i.e. don't allocate |
them dynamically to the next sample if requested with a -1 value below. |
Returns the number of reserved channels. |
*/ |
int Mix_ReserveChannels(int num) |
{ |
if (num > num_channels) |
num = num_channels; |
reserved_channels = num; |
return num; |
} |
|
static int checkchunkintegral(Mix_Chunk *chunk) |
{ |
int frame_width = 1; |
|
if ((mixer.format & 0xFF) == 16) frame_width = 2; |
frame_width *= mixer.channels; |
while (chunk->alen % frame_width) chunk->alen--; |
return chunk->alen; |
} |
|
/* Play an audio chunk on a specific channel. |
If the specified channel is -1, play on the first free channel. |
'ticks' is the number of milliseconds at most to play the sample, or -1 |
if there is no limit. |
Returns which channel was used to play the sound. |
*/ |
int Mix_PlayChannelTimed(int which, Mix_Chunk *chunk, int loops, int ticks) |
{ |
int i; |
|
/* Don't play null pointers :-) */ |
if ( chunk == NULL ) { |
Mix_SetError("Tried to play a NULL chunk"); |
return(-1); |
} |
if ( !checkchunkintegral(chunk)) { |
Mix_SetError("Tried to play a chunk with a bad frame"); |
return(-1); |
} |
|
/* Lock the mixer while modifying the playing channels */ |
SDL_LockAudio(); |
{ |
/* If which is -1, play on the first free channel */ |
if ( which == -1 ) { |
for ( i=reserved_channels; i<num_channels; ++i ) { |
if ( mix_channel[i].playing <= 0 ) |
break; |
} |
if ( i == num_channels ) { |
Mix_SetError("No free channels available"); |
which = -1; |
} else { |
which = i; |
} |
} |
|
/* Queue up the audio data for this channel */ |
if ( which >= 0 && which < num_channels ) { |
Uint32 sdl_ticks = SDL_GetTicks(); |
if (Mix_Playing(which)) |
_Mix_channel_done_playing(which); |
mix_channel[which].samples = chunk->abuf; |
mix_channel[which].playing = chunk->alen; |
mix_channel[which].looping = loops; |
mix_channel[which].chunk = chunk; |
mix_channel[which].paused = 0; |
mix_channel[which].fading = MIX_NO_FADING; |
mix_channel[which].start_time = sdl_ticks; |
mix_channel[which].expire = (ticks>0) ? (sdl_ticks + ticks) : 0; |
} |
} |
SDL_UnlockAudio(); |
|
/* Return the channel on which the sound is being played */ |
return(which); |
} |
|
/* Change the expiration delay for a channel */ |
int Mix_ExpireChannel(int which, int ticks) |
{ |
int status = 0; |
|
if ( which == -1 ) { |
int i; |
for ( i=0; i < num_channels; ++ i ) { |
status += Mix_ExpireChannel(i, ticks); |
} |
} else if ( which < num_channels ) { |
SDL_LockAudio(); |
mix_channel[which].expire = (ticks>0) ? (SDL_GetTicks() + ticks) : 0; |
SDL_UnlockAudio(); |
++ status; |
} |
return(status); |
} |
|
/* Fade in a sound on a channel, over ms milliseconds */ |
int Mix_FadeInChannelTimed(int which, Mix_Chunk *chunk, int loops, int ms, int ticks) |
{ |
int i; |
|
/* Don't play null pointers :-) */ |
if ( chunk == NULL ) { |
return(-1); |
} |
if ( !checkchunkintegral(chunk)) { |
Mix_SetError("Tried to play a chunk with a bad frame"); |
return(-1); |
} |
|
/* Lock the mixer while modifying the playing channels */ |
SDL_LockAudio(); |
{ |
/* If which is -1, play on the first free channel */ |
if ( which == -1 ) { |
for ( i=reserved_channels; i<num_channels; ++i ) { |
if ( mix_channel[i].playing <= 0 ) |
break; |
} |
if ( i == num_channels ) { |
which = -1; |
} else { |
which = i; |
} |
} |
|
/* Queue up the audio data for this channel */ |
if ( which >= 0 && which < num_channels ) { |
Uint32 sdl_ticks = SDL_GetTicks(); |
if (Mix_Playing(which)) |
_Mix_channel_done_playing(which); |
mix_channel[which].samples = chunk->abuf; |
mix_channel[which].playing = chunk->alen; |
mix_channel[which].looping = loops; |
mix_channel[which].chunk = chunk; |
mix_channel[which].paused = 0; |
mix_channel[which].fading = MIX_FADING_IN; |
mix_channel[which].fade_volume = mix_channel[which].volume; |
mix_channel[which].fade_volume_reset = mix_channel[which].volume; |
mix_channel[which].volume = 0; |
mix_channel[which].fade_length = (Uint32)ms; |
mix_channel[which].start_time = mix_channel[which].ticks_fade = sdl_ticks; |
mix_channel[which].expire = (ticks > 0) ? (sdl_ticks+ticks) : 0; |
} |
} |
SDL_UnlockAudio(); |
|
/* Return the channel on which the sound is being played */ |
return(which); |
} |
|
/* Set volume of a particular channel */ |
int Mix_Volume(int which, int volume) |
{ |
int i; |
int prev_volume = 0; |
|
if ( which == -1 ) { |
for ( i=0; i<num_channels; ++i ) { |
prev_volume += Mix_Volume(i, volume); |
} |
prev_volume /= num_channels; |
} else if ( which < num_channels ) { |
prev_volume = mix_channel[which].volume; |
if ( volume >= 0 ) { |
if ( volume > SDL_MIX_MAXVOLUME ) { |
volume = SDL_MIX_MAXVOLUME; |
} |
mix_channel[which].volume = volume; |
} |
} |
return(prev_volume); |
} |
/* Set volume of a particular chunk */ |
int Mix_VolumeChunk(Mix_Chunk *chunk, int volume) |
{ |
int prev_volume; |
|
prev_volume = chunk->volume; |
if ( volume >= 0 ) { |
if ( volume > MIX_MAX_VOLUME ) { |
volume = MIX_MAX_VOLUME; |
} |
chunk->volume = volume; |
} |
return(prev_volume); |
} |
|
/* Halt playing of a particular channel */ |
int Mix_HaltChannel(int which) |
{ |
int i; |
|
if ( which == -1 ) { |
for ( i=0; i<num_channels; ++i ) { |
Mix_HaltChannel(i); |
} |
} else if ( which < num_channels ) { |
SDL_LockAudio(); |
if (mix_channel[which].playing) { |
_Mix_channel_done_playing(which); |
mix_channel[which].playing = 0; |
mix_channel[which].looping = 0; |
} |
mix_channel[which].expire = 0; |
if(mix_channel[which].fading != MIX_NO_FADING) /* Restore volume */ |
mix_channel[which].volume = mix_channel[which].fade_volume_reset; |
mix_channel[which].fading = MIX_NO_FADING; |
SDL_UnlockAudio(); |
} |
return(0); |
} |
|
/* Halt playing of a particular group of channels */ |
int Mix_HaltGroup(int tag) |
{ |
int i; |
|
for ( i=0; i<num_channels; ++i ) { |
if( mix_channel[i].tag == tag ) { |
Mix_HaltChannel(i); |
} |
} |
return(0); |
} |
|
/* Fade out a channel and then stop it automatically */ |
int Mix_FadeOutChannel(int which, int ms) |
{ |
int status; |
|
status = 0; |
if ( audio_opened ) { |
if ( which == -1 ) { |
int i; |
|
for ( i=0; i<num_channels; ++i ) { |
status += Mix_FadeOutChannel(i, ms); |
} |
} else if ( which < num_channels ) { |
SDL_LockAudio(); |
if ( mix_channel[which].playing && |
(mix_channel[which].volume > 0) && |
(mix_channel[which].fading != MIX_FADING_OUT) ) { |
mix_channel[which].fade_volume = mix_channel[which].volume; |
mix_channel[which].fading = MIX_FADING_OUT; |
mix_channel[which].fade_length = ms; |
mix_channel[which].ticks_fade = SDL_GetTicks(); |
|
/* only change fade_volume_reset if we're not fading. */ |
if (mix_channel[which].fading == MIX_NO_FADING) { |
mix_channel[which].fade_volume_reset = mix_channel[which].volume; |
} |
++status; |
} |
SDL_UnlockAudio(); |
} |
} |
return(status); |
} |
|
/* Halt playing of a particular group of channels */ |
int Mix_FadeOutGroup(int tag, int ms) |
{ |
int i; |
int status = 0; |
for ( i=0; i<num_channels; ++i ) { |
if( mix_channel[i].tag == tag ) { |
status += Mix_FadeOutChannel(i,ms); |
} |
} |
return(status); |
} |
|
Mix_Fading Mix_FadingChannel(int which) |
{ |
if ( which < 0 || which >= num_channels ) { |
return MIX_NO_FADING; |
} |
return mix_channel[which].fading; |
} |
|
/* Check the status of a specific channel. |
If the specified mix_channel is -1, check all mix channels. |
*/ |
int Mix_Playing(int which) |
{ |
int status; |
|
status = 0; |
if ( which == -1 ) { |
int i; |
|
for ( i=0; i<num_channels; ++i ) { |
if ((mix_channel[i].playing > 0) || |
(mix_channel[i].looping > 0)) |
{ |
++status; |
} |
} |
} else if ( which < num_channels ) { |
if ( (mix_channel[which].playing > 0) || |
(mix_channel[which].looping > 0) ) |
{ |
++status; |
} |
} |
return(status); |
} |
|
/* rcg06072001 Get the chunk associated with a channel. */ |
Mix_Chunk *Mix_GetChunk(int channel) |
{ |
Mix_Chunk *retval = NULL; |
|
if ((channel >= 0) && (channel < num_channels)) { |
retval = mix_channel[channel].chunk; |
} |
|
return(retval); |
} |
|
/* Close the mixer, halting all playing audio */ |
void Mix_CloseAudio(void) |
{ |
int i; |
|
if ( audio_opened ) { |
if ( audio_opened == 1 ) { |
for (i = 0; i < num_channels; i++) { |
Mix_UnregisterAllEffects(i); |
} |
Mix_UnregisterAllEffects(MIX_CHANNEL_POST); |
close_music(); |
Mix_HaltChannel(-1); |
_Mix_DeinitEffects(); |
SDL_CloseAudio(); |
SDL_free(mix_channel); |
mix_channel = NULL; |
|
/* rcg06042009 report available decoders at runtime. */ |
SDL_free(chunk_decoders); |
chunk_decoders = NULL; |
num_decoders = 0; |
} |
--audio_opened; |
} |
} |
|
/* Pause a particular channel (or all) */ |
void Mix_Pause(int which) |
{ |
Uint32 sdl_ticks = SDL_GetTicks(); |
if ( which == -1 ) { |
int i; |
|
for ( i=0; i<num_channels; ++i ) { |
if ( mix_channel[i].playing > 0 ) { |
mix_channel[i].paused = sdl_ticks; |
} |
} |
} else if ( which < num_channels ) { |
if ( mix_channel[which].playing > 0 ) { |
mix_channel[which].paused = sdl_ticks; |
} |
} |
} |
|
/* Resume a paused channel */ |
void Mix_Resume(int which) |
{ |
Uint32 sdl_ticks = SDL_GetTicks(); |
|
SDL_LockAudio(); |
if ( which == -1 ) { |
int i; |
|
for ( i=0; i<num_channels; ++i ) { |
if ( mix_channel[i].playing > 0 ) { |
if(mix_channel[i].expire > 0) |
mix_channel[i].expire += sdl_ticks - mix_channel[i].paused; |
mix_channel[i].paused = 0; |
} |
} |
} else if ( which < num_channels ) { |
if ( mix_channel[which].playing > 0 ) { |
if(mix_channel[which].expire > 0) |
mix_channel[which].expire += sdl_ticks - mix_channel[which].paused; |
mix_channel[which].paused = 0; |
} |
} |
SDL_UnlockAudio(); |
} |
|
int Mix_Paused(int which) |
{ |
if ( which < 0 ) { |
int status = 0; |
int i; |
for( i=0; i < num_channels; ++i ) { |
if ( mix_channel[i].paused ) { |
++ status; |
} |
} |
return(status); |
} else if ( which < num_channels ) { |
return(mix_channel[which].paused != 0); |
} else { |
return(0); |
} |
} |
|
/* Change the group of a channel */ |
int Mix_GroupChannel(int which, int tag) |
{ |
if ( which < 0 || which > num_channels ) |
return(0); |
|
SDL_LockAudio(); |
mix_channel[which].tag = tag; |
SDL_UnlockAudio(); |
return(1); |
} |
|
/* Assign several consecutive channels to a group */ |
int Mix_GroupChannels(int from, int to, int tag) |
{ |
int status = 0; |
for( ; from <= to; ++ from ) { |
status += Mix_GroupChannel(from, tag); |
} |
return(status); |
} |
|
/* Finds the first available channel in a group of channels */ |
int Mix_GroupAvailable(int tag) |
{ |
int i; |
for( i=0; i < num_channels; i ++ ) { |
if ( ((tag == -1) || (tag == mix_channel[i].tag)) && |
(mix_channel[i].playing <= 0) ) |
return i; |
} |
return(-1); |
} |
|
int Mix_GroupCount(int tag) |
{ |
int count = 0; |
int i; |
for( i=0; i < num_channels; i ++ ) { |
if ( mix_channel[i].tag==tag || tag==-1 ) |
++ count; |
} |
return(count); |
} |
|
/* Finds the "oldest" sample playing in a group of channels */ |
int Mix_GroupOldest(int tag) |
{ |
int chan = -1; |
Uint32 mintime = SDL_GetTicks(); |
int i; |
for( i=0; i < num_channels; i ++ ) { |
if ( (mix_channel[i].tag==tag || tag==-1) && mix_channel[i].playing > 0 |
&& mix_channel[i].start_time <= mintime ) { |
mintime = mix_channel[i].start_time; |
chan = i; |
} |
} |
return(chan); |
} |
|
/* Finds the "most recent" (i.e. last) sample playing in a group of channels */ |
int Mix_GroupNewer(int tag) |
{ |
int chan = -1; |
Uint32 maxtime = 0; |
int i; |
for( i=0; i < num_channels; i ++ ) { |
if ( (mix_channel[i].tag==tag || tag==-1) && mix_channel[i].playing > 0 |
&& mix_channel[i].start_time >= maxtime ) { |
maxtime = mix_channel[i].start_time; |
chan = i; |
} |
} |
return(chan); |
} |
|
|
|
/* |
* rcg06122001 The special effects exportable API. |
* Please see effect_*.c for internally-implemented effects, such |
* as Mix_SetPanning(). |
*/ |
|
/* MAKE SURE you hold the audio lock (SDL_LockAudio()) before calling this! */ |
static int _Mix_register_effect(effect_info **e, Mix_EffectFunc_t f, |
Mix_EffectDone_t d, void *arg) |
{ |
effect_info *new_e; |
|
if (!e) { |
Mix_SetError("Internal error"); |
return(0); |
} |
|
if (f == NULL) { |
Mix_SetError("NULL effect callback"); |
return(0); |
} |
|
new_e = SDL_malloc(sizeof (effect_info)); |
if (new_e == NULL) { |
Mix_SetError("Out of memory"); |
return(0); |
} |
|
new_e->callback = f; |
new_e->done_callback = d; |
new_e->udata = arg; |
new_e->next = NULL; |
|
/* add new effect to end of linked list... */ |
if (*e == NULL) { |
*e = new_e; |
} else { |
effect_info *cur = *e; |
while (1) { |
if (cur->next == NULL) { |
cur->next = new_e; |
break; |
} |
cur = cur->next; |
} |
} |
|
return(1); |
} |
|
|
/* MAKE SURE you hold the audio lock (SDL_LockAudio()) before calling this! */ |
static int _Mix_remove_effect(int channel, effect_info **e, Mix_EffectFunc_t f) |
{ |
effect_info *cur; |
effect_info *prev = NULL; |
effect_info *next = NULL; |
|
if (!e) { |
Mix_SetError("Internal error"); |
return(0); |
} |
|
for (cur = *e; cur != NULL; cur = cur->next) { |
if (cur->callback == f) { |
next = cur->next; |
if (cur->done_callback != NULL) { |
cur->done_callback(channel, cur->udata); |
} |
SDL_free(cur); |
|
if (prev == NULL) { /* removing first item of list? */ |
*e = next; |
} else { |
prev->next = next; |
} |
return(1); |
} |
prev = cur; |
} |
|
Mix_SetError("No such effect registered"); |
return(0); |
} |
|
|
/* MAKE SURE you hold the audio lock (SDL_LockAudio()) before calling this! */ |
static int _Mix_remove_all_effects(int channel, effect_info **e) |
{ |
effect_info *cur; |
effect_info *next; |
|
if (!e) { |
Mix_SetError("Internal error"); |
return(0); |
} |
|
for (cur = *e; cur != NULL; cur = next) { |
next = cur->next; |
if (cur->done_callback != NULL) { |
cur->done_callback(channel, cur->udata); |
} |
SDL_free(cur); |
} |
*e = NULL; |
|
return(1); |
} |
|
|
/* MAKE SURE you hold the audio lock (SDL_LockAudio()) before calling this! */ |
int _Mix_RegisterEffect_locked(int channel, Mix_EffectFunc_t f, |
Mix_EffectDone_t d, void *arg) |
{ |
effect_info **e = NULL; |
|
if (channel == MIX_CHANNEL_POST) { |
e = &posteffects; |
} else { |
if ((channel < 0) || (channel >= num_channels)) { |
Mix_SetError("Invalid channel number"); |
return(0); |
} |
e = &mix_channel[channel].effects; |
} |
|
return _Mix_register_effect(e, f, d, arg); |
} |
|
int Mix_RegisterEffect(int channel, Mix_EffectFunc_t f, |
Mix_EffectDone_t d, void *arg) |
{ |
int retval; |
SDL_LockAudio(); |
retval = _Mix_RegisterEffect_locked(channel, f, d, arg); |
SDL_UnlockAudio(); |
return retval; |
} |
|
|
/* MAKE SURE you hold the audio lock (SDL_LockAudio()) before calling this! */ |
int _Mix_UnregisterEffect_locked(int channel, Mix_EffectFunc_t f) |
{ |
effect_info **e = NULL; |
|
if (channel == MIX_CHANNEL_POST) { |
e = &posteffects; |
} else { |
if ((channel < 0) || (channel >= num_channels)) { |
Mix_SetError("Invalid channel number"); |
return(0); |
} |
e = &mix_channel[channel].effects; |
} |
|
return _Mix_remove_effect(channel, e, f); |
} |
|
int Mix_UnregisterEffect(int channel, Mix_EffectFunc_t f) |
{ |
int retval; |
SDL_LockAudio(); |
retval = _Mix_UnregisterEffect_locked(channel, f); |
SDL_UnlockAudio(); |
return(retval); |
} |
|
/* MAKE SURE you hold the audio lock (SDL_LockAudio()) before calling this! */ |
int _Mix_UnregisterAllEffects_locked(int channel) |
{ |
effect_info **e = NULL; |
|
if (channel == MIX_CHANNEL_POST) { |
e = &posteffects; |
} else { |
if ((channel < 0) || (channel >= num_channels)) { |
Mix_SetError("Invalid channel number"); |
return(0); |
} |
e = &mix_channel[channel].effects; |
} |
|
return _Mix_remove_all_effects(channel, e); |
} |
|
int Mix_UnregisterAllEffects(int channel) |
{ |
int retval; |
SDL_LockAudio(); |
retval = _Mix_UnregisterAllEffects_locked(channel); |
SDL_UnlockAudio(); |
return(retval); |
} |
|
/* end of mixer.c ... */ |
|