forked from len0rd/rockbox
original xrick code by 'BigOrno' at: http://www.bigorno.net/xrick/ Rockbox port, plus bugfixes at: https://github.com/pierluigi-vicinanza/xrick Further changes: * Additonal fixes from g#3026 * Port to modern plugin API * Add Pluginlib keymap fallback * Support all >1bpp screens * Fix build warnings in miniz * Better error message when resources are missing Change-Id: Id83928bc2539901b0221692f65cbca41389c58e7
483 lines
11 KiB
C
483 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)
|
|
{
|
|
rb->pcm_play_data(&get_more, NULL, NULL, 0);
|
|
isAudioPlaying = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialise audio
|
|
*/
|
|
bool syssnd_init(void)
|
|
{
|
|
if (isAudioInitialised)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
IFDEBUG_AUDIO(sys_printf("xrick/audio: start\n"););
|
|
|
|
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->pcm_set_frequency(HW_FREQ_44);
|
|
rb->pcm_apply_settings();
|
|
|
|
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->pcm_play_stop();
|
|
|
|
/* Reset playing status. */
|
|
isAudioPlaying = false;
|
|
|
|
/* Restore default sampling rate. */
|
|
rb->pcm_set_frequency(HW_SAMPR_DEFAULT);
|
|
rb->pcm_apply_settings();
|
|
|
|
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->pcm_play_lock();
|
|
rb->mixer_channel_play_pause(PCM_MIXER_CHAN_PLAYBACK, !pause);
|
|
rb->pcm_play_unlock();
|
|
}
|
|
|
|
/*
|
|
* 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 */
|