mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-05-12 11:43:16 -04:00
get_more() can disable audio when there's nothing more to genreate but that can get called (and "fail") _before_ we set the flag that audio is turned on. If this occurs, we will incorrecly set the "audio on" flag after the failure which will prevent the mixer from being re-started. (We're at the mercy of thread scheduling) Change-Id: I4f6c50a71c7ad685ff45f775e7e4b1c61b8b7777
480 lines
11 KiB
C
480 lines
11 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Port of xrick, a Rick Dangerous clone, to Rockbox.
|
|
* See http://www.bigorno.net/xrick/
|
|
*
|
|
* Copyright (C) 2008-2014 Pierluigi Vicinanza
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "xrick/config.h"
|
|
|
|
#ifdef ENABLE_SOUND
|
|
|
|
#include "xrick/system/system.h"
|
|
|
|
#include "xrick/game.h"
|
|
#include "xrick/debug.h"
|
|
#include "xrick/system/syssnd_rockbox.h"
|
|
|
|
#include "plugin.h"
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
const U8 syssnd_period = 20;
|
|
|
|
/*
|
|
* Local variables
|
|
*/
|
|
enum
|
|
{
|
|
SYSSND_MIX_CHANNELS = 5,
|
|
SYSSND_MIX_SAMPLES = 1024, /* try changing this value if sound mixing is too slow or choppy */
|
|
SYSSND_SOURCE_SAMPLES = SYSSND_MIX_SAMPLES / 2
|
|
};
|
|
|
|
/* channels to be mixed */
|
|
static channel_t channels[SYSSND_MIX_CHANNELS];
|
|
/* buffer used to mix sounds sent to pcm playback, stores 16b stereo 44Khz audio samples */
|
|
enum { AUDIO_BUFFER_COUNT = 4 };
|
|
typedef struct
|
|
{
|
|
U32 data[SYSSND_MIX_SAMPLES];
|
|
size_t length; /* in 8 bit mono samples */
|
|
} mix_buffer_t;
|
|
static mix_buffer_t mixBuffers[AUDIO_BUFFER_COUNT];
|
|
static size_t writeIndex;
|
|
static size_t readIndex;
|
|
static size_t fillCount;
|
|
static bool isAudioPlaying;
|
|
static bool isAudioInitialised = false;
|
|
|
|
/*
|
|
* Prototypes
|
|
*/
|
|
static void endChannel(size_t c);
|
|
static void get_more(const void **start, size_t *size);
|
|
|
|
/*
|
|
* Deactivate channel
|
|
*/
|
|
static void endChannel(size_t c)
|
|
{
|
|
channels[c].loop = 0;
|
|
channels[c].sound = NULL;
|
|
}
|
|
|
|
/*
|
|
* Audio callback
|
|
*/
|
|
static void get_more(const void **start, size_t *size)
|
|
{
|
|
if (fillCount > 0)
|
|
{
|
|
/* Store output data address and size. */
|
|
*start = mixBuffers[readIndex].data;
|
|
*size = mixBuffers[readIndex].length * 8;
|
|
|
|
/* Free this part of output buffer. */
|
|
mixBuffers[readIndex].length = 0;
|
|
|
|
/* Advance to the next part of output buffer. */
|
|
readIndex = (readIndex + 1) & (AUDIO_BUFFER_COUNT - 1);
|
|
fillCount--;
|
|
}
|
|
else
|
|
{
|
|
/* Nothing to play. */
|
|
isAudioPlaying = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Mix audio samples and fill playback buffer
|
|
*/
|
|
void syssnd_update(void)
|
|
{
|
|
if (!isAudioInitialised)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
size_t c;
|
|
size_t sampleOffset;
|
|
size_t maxSampleCount;
|
|
bool isFirstSound;
|
|
U8 *sourceBuf, *sourceBufEnd;
|
|
U32 *destBuf;
|
|
|
|
/* Cancel if whole buffer filled. */
|
|
if (fillCount >= (AUDIO_BUFFER_COUNT - 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
maxSampleCount = 0;
|
|
|
|
sampleOffset = mixBuffers[writeIndex].length;
|
|
destBuf = mixBuffers[writeIndex].data + sampleOffset * 2;
|
|
|
|
isFirstSound = true;
|
|
for (c = 0; c < SYSSND_MIX_CHANNELS ; ++c)
|
|
{
|
|
U32 * mixBuffer;
|
|
size_t sampleCount;
|
|
channel_t * channel = &channels[c];
|
|
|
|
if (!channel->sound /* no sound to play on this channel */
|
|
|| (channel->loop == 0)) /* channel is inactive */
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (isFirstSound)
|
|
{
|
|
/* clear mixing buffer */
|
|
rb->memset(destBuf, 0, (SYSSND_MIX_SAMPLES - (sampleOffset * 2)) * sizeof(U32));
|
|
isFirstSound = false;
|
|
}
|
|
|
|
sampleCount = MIN(SYSSND_SOURCE_SAMPLES - sampleOffset, channel->len);
|
|
if (maxSampleCount < sampleCount)
|
|
{
|
|
maxSampleCount = sampleCount;
|
|
}
|
|
|
|
/* mix sound samples */
|
|
mixBuffer = destBuf;
|
|
sourceBuf = channel->buf;
|
|
sourceBufEnd = channel->buf + sampleCount;
|
|
while (sourceBuf < sourceBufEnd)
|
|
{
|
|
/* Convert from unsigned 8 bit mono 22khz to signed 16 bit stereo 44khz */
|
|
const int sourceSample = *sourceBuf++;
|
|
int monoSample = (sourceSample - 0x80) << 8;
|
|
U32 stereoSample = *mixBuffer;
|
|
monoSample += (S32)(stereoSample) >> 16;
|
|
if (monoSample >= 0x8000)
|
|
{
|
|
monoSample = 0x7FFF;
|
|
}
|
|
else if (monoSample < -0x8000)
|
|
{
|
|
monoSample = -0x8000;
|
|
}
|
|
stereoSample = (U16)monoSample | ((U16)monoSample << 16);
|
|
*mixBuffer++ = stereoSample;
|
|
*mixBuffer++ = stereoSample;
|
|
}
|
|
channel->buf = sourceBufEnd;
|
|
|
|
channel->len -= sampleCount;
|
|
if (channel->len == 0) /* ending ? */
|
|
{
|
|
if (channel->loop > 0)
|
|
{
|
|
channel->loop--;
|
|
}
|
|
if (channel->loop)
|
|
{
|
|
/* just loop */
|
|
IFDEBUG_AUDIO2(sys_printf("xrick/audio: channel %d - loop\n", c););
|
|
channel->buf = channel->sound->buf;
|
|
channel->len = channel->sound->len;
|
|
}
|
|
else
|
|
{
|
|
/* end for real */
|
|
IFDEBUG_AUDIO2(sys_printf("xrick/audio: channel %d - end\n", c););
|
|
endChannel(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (maxSampleCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
mixBuffers[writeIndex].length += maxSampleCount;
|
|
|
|
/* Advance one part of audio buffer. */
|
|
writeIndex = (writeIndex + 1) & (AUDIO_BUFFER_COUNT - 1);
|
|
fillCount++;
|
|
|
|
if (!isAudioPlaying && fillCount > 0)
|
|
{
|
|
isAudioPlaying = true;
|
|
rb->mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, get_more, NULL, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialise audio
|
|
*/
|
|
bool syssnd_init(void)
|
|
{
|
|
if (isAudioInitialised)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
IFDEBUG_AUDIO(sys_printf("xrick/audio: start\n"););
|
|
|
|
rb->audio_stop();
|
|
rb->talk_disable(true);
|
|
|
|
/* Stop playback to reconfigure audio settings and acquire audio buffer */
|
|
rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK);
|
|
|
|
#if INPUT_SRC_CAPS != 0
|
|
/* Select playback */
|
|
rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
|
|
rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
|
|
#endif
|
|
|
|
rb->mixer_set_frequency(HW_FREQ_44);
|
|
|
|
rb->memset(channels, 0, sizeof(channels));
|
|
rb->memset(mixBuffers, 0, sizeof(mixBuffers));
|
|
|
|
writeIndex = 0;
|
|
readIndex = 0;
|
|
fillCount = 0;
|
|
isAudioPlaying = false;
|
|
|
|
isAudioInitialised = true;
|
|
IFDEBUG_AUDIO(sys_printf("xrick/audio: ready\n"););
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Shutdown
|
|
*/
|
|
void syssnd_shutdown(void)
|
|
{
|
|
if (!isAudioInitialised)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Stop playback. */
|
|
rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK);
|
|
|
|
/* Reset playing status. */
|
|
isAudioPlaying = false;
|
|
|
|
/* Restore default sampling rate. */
|
|
rb->mixer_set_frequency(HW_SAMPR_DEFAULT);
|
|
|
|
rb->talk_disable(false);
|
|
|
|
isAudioInitialised = false;
|
|
IFDEBUG_AUDIO(sys_printf("xrick/audio: stop\n"););
|
|
}
|
|
|
|
/*
|
|
* Play a sound
|
|
*
|
|
* loop: number of times the sound should be played, -1 to loop forever
|
|
*
|
|
* NOTE if sound is already playing, simply reset it (i.e. can not have
|
|
* twice the same sound playing -- tends to become noisy when too many
|
|
* bad guys die at the same time).
|
|
*/
|
|
void syssnd_play(sound_t *sound, S8 loop)
|
|
{
|
|
size_t c;
|
|
|
|
if (!isAudioInitialised || !sound)
|
|
{
|
|
return;
|
|
}
|
|
|
|
c = 0;
|
|
while (channels[c].sound != sound &&
|
|
channels[c].loop != 0 &&
|
|
c < SYSSND_MIX_CHANNELS)
|
|
{
|
|
c++;
|
|
}
|
|
if (c >= SYSSND_MIX_CHANNELS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!sound->buf)
|
|
{
|
|
syssnd_load(sound);
|
|
if (!sound->buf)
|
|
{
|
|
sys_error("(audio) can not load %s", sound->name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
IFDEBUG_AUDIO(
|
|
if (channels[c].sound == sound)
|
|
{
|
|
sys_printf("xrick/audio: already playing %s on channel %d - resetting\n",
|
|
sound->name, c);
|
|
}
|
|
else
|
|
{
|
|
sys_printf("xrick/audio: playing %s on channel %d\n", sound->name, c);
|
|
}
|
|
);
|
|
|
|
channels[c].loop = loop;
|
|
channels[c].sound = sound;
|
|
channels[c].buf = sound->buf;
|
|
channels[c].len = sound->len;
|
|
}
|
|
|
|
/*
|
|
* Pause all sounds
|
|
*/
|
|
void syssnd_pauseAll(bool pause)
|
|
{
|
|
if (!isAudioInitialised)
|
|
{
|
|
return;
|
|
}
|
|
|
|
rb->mixer_channel_play_pause(PCM_MIXER_CHAN_PLAYBACK, !pause);
|
|
}
|
|
|
|
/*
|
|
* Stop a sound
|
|
*/
|
|
void syssnd_stop(sound_t *sound)
|
|
{
|
|
size_t c;
|
|
|
|
if (!isAudioInitialised || !sound)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (c = 0; c < SYSSND_MIX_CHANNELS; c++)
|
|
{
|
|
if (channels[c].sound == sound)
|
|
{
|
|
endChannel(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Stops all channels.
|
|
*/
|
|
void syssnd_stopAll(void)
|
|
{
|
|
size_t c;
|
|
|
|
if (!isAudioInitialised)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (c = 0; c < SYSSND_MIX_CHANNELS; c++)
|
|
{
|
|
if (channels[c].sound)
|
|
{
|
|
endChannel(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Load a sound.
|
|
*/
|
|
void syssnd_load(sound_t *sound)
|
|
{
|
|
int bytesRead;
|
|
file_t fp;
|
|
bool success;
|
|
|
|
if (!isAudioInitialised || !sound)
|
|
{
|
|
return;
|
|
}
|
|
|
|
success = false;
|
|
do
|
|
{
|
|
sound->buf = sysmem_push(sound->len);
|
|
if (!sound->buf)
|
|
{
|
|
sys_error("(audio) not enough memory for \"%s\", %d bytes needed", sound->name, sound->len);
|
|
break;
|
|
}
|
|
|
|
fp = sysfile_open(sound->name);
|
|
if (!fp)
|
|
{
|
|
sys_error("(audio) unable to open \"%s\"", sound->name);
|
|
break;
|
|
}
|
|
|
|
sysfile_seek(fp, sizeof(wave_header_t), SEEK_SET); /* skip WAVE header */
|
|
|
|
bytesRead = sysfile_read(fp, sound->buf, sound->len, 1);
|
|
sysfile_close(fp);
|
|
if (bytesRead != 1)
|
|
{
|
|
sys_error("(audio) unable to read from \"%s\"", sound->name);
|
|
break;
|
|
}
|
|
|
|
success = true;
|
|
} while (false);
|
|
|
|
if (!success)
|
|
{
|
|
sysmem_pop(sound->buf);
|
|
sound->buf = NULL;
|
|
sound->len = 0;
|
|
return;
|
|
}
|
|
|
|
IFDEBUG_AUDIO(sys_printf("xrick/audio: successfully loaded \"%s\"\n", sound->name););
|
|
}
|
|
|
|
/*
|
|
* Unload a sound.
|
|
*/
|
|
void syssnd_unload(sound_t *sound)
|
|
{
|
|
if (!isAudioInitialised || !sound || !sound->buf)
|
|
{
|
|
return;
|
|
}
|
|
|
|
sysmem_pop(sound->buf);
|
|
sound->buf = NULL;
|
|
sound->len = 0;
|
|
}
|
|
|
|
#endif /* ENABLE_SOUND */
|
|
|
|
/* eof */
|