usbloadergx/source/libwiigui/gui_sound.cpp
ardi@ist-einmalig.de a0182d0c4c * GuiSound stuff rewritten it makes modular
* ogg decoder added (old oggplayer removed)
  * mp3 decoder added (mp3's cane use as backgroundsounds)
  * WAVE decoder added (but only uncompressed WAVE's)
  * AIFF decoder added (only uncrompressed)
  * BNS decoder added
    all soundformats can use as backgroundsounds
    dimoks GameSound class removed it is replaced with the new GuiSound stuff

* Many small fixes and other changes
2009-11-09 23:03:13 +00:00

441 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/****************************************************************************
* libwiigui
*
* Tantric 2009
*
* gui_sound.cpp
*
* decoder modification by ardi 2009
*
* GUI class definitions
***************************************************************************/
#include "gui.h"
#include <unistd.h>
#include "gecko.h"
#include "gui_sound_decoder.h"
#define BUFFER_SIZE 8192
/***************************************************************
*
* D E C O D E R L I S T
*
*
***************************************************************/
GuiSoundDecoder::DecoderListEntry *GuiSoundDecoder::DecoderList = NULL;
GuiSoundDecoder::DecoderListEntry &GuiSoundDecoder::RegisterDecoder(DecoderListEntry &Decoder, GuiSoundDecoderCreate fnc)
{
if(Decoder.fnc != fnc)
{
Decoder.fnc = fnc;
Decoder.next = DecoderList;
DecoderList = &Decoder;
}
return Decoder;
}
GuiSoundDecoder *GuiSoundDecoder::GetDecoder(const u8 * snd, u32 len, bool snd_is_allocated)
{
for(DecoderListEntry *de = DecoderList; de; de=de->next)
{
GuiSoundDecoder *d = NULL;
try{ d = de->fnc(snd, len, snd_is_allocated); }
catch(const char *error){
gprintf("%s", error); }
catch(...){}
if(d) return d;
}
return NULL;
}
/***************************************************************
*
* D E C O D E R T H R E A D
*
*
***************************************************************/
static GuiSound *GuiSoundPlayer[16] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
static lwp_t GuiSoundDecoderThreadHandle = LWP_THREAD_NULL;
static bool GuiSoundDecoderThreadRunning = false;
static bool GuiSoundDecoderDataRquested = false;
void *GuiSoundDecoderThread(void *args)
{
GuiSoundDecoderThreadRunning = true;
do
{
if(GuiSoundDecoderDataRquested)
{
GuiSoundDecoderDataRquested = false;
GuiSound **players = GuiSoundPlayer;
for( int i = 0; i < 16; ++i , ++players)
{
GuiSound *player = *players;
if(player)
player->DecoderCallback();
}
}
if(!GuiSoundDecoderDataRquested)
usleep(50);
} while(GuiSoundDecoderThreadRunning);
return 0;
}
/***************************************************************
*
* A S N D C A L L B A C K
*
*
***************************************************************/
void GuiSoundPlayerCallback(s32 Voice)
{
if(Voice >= 0 && Voice < 16 && GuiSoundPlayer[Voice])
{
GuiSoundPlayer[Voice]->PlayerCallback();
GuiSoundDecoderDataRquested = true;
}
}
/***************************************************************
*
* R A W - D E C O D E R
* Decoder for Raw-PCM-Datas (16bit Stereo 48kHz)
*
***************************************************************/
class GuiSoundDecoderRAW : public GuiSoundDecoder
{
protected:
GuiSoundDecoderRAW(const u8 * snd, u32 len, bool snd_is_allocated)
{
pcm_start = snd;
is_allocated = snd_is_allocated;
pcm_end = pcm_start+len;
pos = pcm_start;
is_running = false;
}
public:
~GuiSoundDecoderRAW()
{
while(is_running) usleep(50);
if(is_allocated) delete [] pcm_start;
}
static GuiSoundDecoder *Create(const u8 * snd, u32 len, bool snd_is_allocated)
{
try { return new GuiSoundDecoderRAW(snd, len, snd_is_allocated); }
catch(...) {}
return NULL;
}
s32 GetFormat()
{
return VOICE_STEREO_16BIT;
}
s32 GetSampleRate()
{
return 48000;
}
/* Read reads data from stream to buffer
return: >0 = readed bytes;
0 = EOF;
<0 = Error;
*/
int Read(u8 * buffer, int buffer_size)
{
if(pos >= pcm_end)
return 0; // EOF
is_running = true;
if(pos + buffer_size > pcm_end)
buffer_size = pcm_end-pos;
memcpy(buffer, pos, buffer_size);
pos += buffer_size;
is_running = false;
return buffer_size;
}
int Rewind()
{
pos = pcm_start;
return 0;
}
private:
const u8 *pcm_start;
const u8 *pcm_end;
bool is_allocated;
const u8 *pos;
bool is_running;
};
/***************************************************************
*
* G u i S o u n d
*
*
***************************************************************/
#define GuiSoundBufferReady 0x01
#define GuiSoundBufferEOF 0x02
#define GuiSoundFinish 0x04
static int GuiSoundCount = 0;
/**
* Constructor for the GuiSound class.
*/
GuiSound::GuiSound(const u8 *s, int l, int v/*=100*/, bool r/*=true*/, bool a/*=false*/)
{
if(GuiSoundCount++ == 0 || GuiSoundDecoderThreadHandle == LWP_THREAD_NULL)
{
LWP_CreateThread(&GuiSoundDecoderThreadHandle,GuiSoundDecoderThread,NULL,NULL,32*1024,80);
}
voice = -1;
play_buffer[0] = (u8*)memalign(32, BUFFER_SIZE*3); // tripple-buffer first is played
play_buffer[1] = play_buffer[0] + BUFFER_SIZE; // second is waiting
play_buffer[2] = play_buffer[1] + BUFFER_SIZE; // third is decoding
buffer_nr = 0; // current playbuffer
buffer_pos = 0; // current idx to write in buffer
buffer_ready = false;
buffer_eof = false;
loop = false; // play looped
volume = v; // volume
decoder = NULL;
if(play_buffer[0]) // playbuffer ok
Load(s, l, r, a);
}
bool GuiSound::Load(const u8 *s, int l, bool r/*=false*/, bool a/*=false*/)
{
Stop();
if(!play_buffer[0]) return false;
GuiSoundDecoder *newDecoder = GuiSoundDecoder::GetDecoder(s, l, a);
if(!newDecoder && r) newDecoder = GuiSoundDecoderRAW::Create(s, l, a);
if(newDecoder)
{
delete decoder;
decoder = newDecoder;
return true;
}
else if(a)
delete [] s;
return false;
}
GuiSound::GuiSound(const char *p, int v/*=100*/)
{
if(GuiSoundCount++ == 0 || GuiSoundDecoderThreadHandle == LWP_THREAD_NULL)
{
LWP_CreateThread(&GuiSoundDecoderThreadHandle,GuiSoundDecoderThread,NULL,NULL,32*1024,80);
}
voice = -1;
play_buffer[0] = (u8*)memalign(32, BUFFER_SIZE*3); // tripple-buffer first is played
play_buffer[1] = play_buffer[0] + BUFFER_SIZE; // second is waiting
play_buffer[2] = play_buffer[1] + BUFFER_SIZE; // third is decoding
buffer_nr = 0; // current playbuffer
buffer_pos = 0; // current idx to write in buffer
buffer_ready = false;
buffer_eof = false;
loop = false; // play looped
volume = v; // volume
decoder = NULL;
if(play_buffer[0]) // playbuffer ok
Load(p);
}
bool GuiSound::Load(const char *p)
{
Stop(); // stop playing
if(!play_buffer[0]) return false;
bool ret = false;
voice = -2; // -2 marks loading from file
u32 filesize = 0;
u8 *buffer = NULL;
size_t result;
FILE * pFile = fopen (p, "rb");
if(pFile)
{
// get file size:
fseek (pFile , 0 , SEEK_END);
filesize = ftell (pFile);
fseek (pFile , 0 , SEEK_SET);
// allocate memory to contain the whole file:
buffer = new(std::nothrow) u8[filesize];
if (buffer)
{
// copy the file into the buffer:
result = fread (buffer, 1, filesize, pFile);
if (result == filesize)
ret= Load(buffer, filesize, false, true);
else
delete [] buffer;
}
fclose (pFile);
}
return ret;
}
/**
* Destructor for the GuiSound class.
*/
GuiSound::~GuiSound()
{
if(!loop) while(voice >= 0) usleep(50);
Stop();
if(--GuiSoundCount == 0 && GuiSoundDecoderThreadHandle != LWP_THREAD_NULL)
{
GuiSoundDecoderThreadRunning = false;
LWP_JoinThread(GuiSoundDecoderThreadHandle,NULL);
GuiSoundDecoderThreadHandle = LWP_THREAD_NULL;
}
delete decoder;
free(play_buffer[0]);
}
void GuiSound::Play()
{
Stop(); // stop playing if it played
if(!play_buffer[0]) return;
if(!decoder) return; // no decoder or no play_buffer -> no playing
// initialize the buffer
buffer_nr = 0; // allways starts with buffer 0
buffer_pos = 0; // reset position
buffer_ready = false;
buffer_eof = false;
decoder->Rewind(); // play from begin
DecoderCallback(); // fill first buffer;
if(!buffer_ready || buffer_eof) // if first buffer not ready -> no play
return;
voice = ASND_GetFirstUnusedVoice();
if(voice >= 0)
{
s32 vol = (255*volume)/100;
s32 format = decoder->GetFormat();
s32 samplerate = decoder->GetSampleRate();
s32 first_pos = buffer_pos;
// switch to next buffer
buffer_nr = 1;
buffer_pos = 0;
buffer_ready = false;
buffer_eof = false;
DecoderCallback(); // fill second buffer;
GuiSoundPlayer[voice] = this; // activate Callbacks for this voice
// Play the voice
ASND_SetVoice(voice, format, samplerate, 0, play_buffer[0], first_pos, vol, vol, GuiSoundPlayerCallback);
}
}
/*
int GuiSound::PlayOggFile(char * path)
{
if(Load(path))
Play();
return 1;
}
*/
void GuiSound::Stop()
{
if(voice < 0) return ;
GuiSoundPlayer[voice] = NULL; // disable Callbacks
SND_StopVoice(voice);
voice = -1;
}
void GuiSound::Pause()
{
if(voice < 0) return ;
ASND_PauseVoice(voice, 1);
}
void GuiSound::Resume()
{
if(voice < 0) return ;
ASND_PauseVoice(voice, 0);
}
bool GuiSound::IsPlaying()
{
return voice >= 0;
}
void GuiSound::SetVolume(int vol)
{
volume = vol;
if(voice < 0) return ;
int newvol = 255*(volume/100.0);
ASND_ChangeVolumeVoice(voice, newvol, newvol);
}
void GuiSound::SetLoop(bool l)
{
loop = l;
}
void GuiSound::DecoderCallback()
{
if(buffer_ready || buffer_eof) // if buffer ready or EOF -> nothing
return;
bool error=false;
while(buffer_pos < BUFFER_SIZE)
{
int ret = decoder->Read(&play_buffer[buffer_nr][buffer_pos], BUFFER_SIZE-buffer_pos);
if(ret > 0)
buffer_pos += ret; // ok -> fill the buffer more
else if(ret == 0) // EOF from decoder
{
if(loop)
decoder->Rewind(); // if loop -> rewind and fill the buffer more
else if(buffer_pos)
break; // has data in buffer -> play the buffer
else
buffer_eof = true; // no data in buffer -> return EOF
return;
}
else if(ret < 0) // an ERROR
{
if(buffer_pos)
break; // has data in buffer -> play the buffer
else if(loop)
{
if(!error) // if no prev error
{
decoder->Rewind(); // if loop -> rewind
error = true; // set error-state
continue; // and fill the buffer more
}
buffer_eof = true; // has prev error -> error in first block -> return EOF
return;
}
else
{
buffer_eof = true; // no loop -> return EOF
return;
}
}
error = false; // clear error-state
}
buffer_ready = true;
}
void GuiSound::PlayerCallback()
{
if(buffer_eof) // if EOF
{
if(ASND_TestPointer(voice, play_buffer[(buffer_nr+2)%3])==0) // test prev. Buffer
Stop();
}
else if(buffer_ready) // if buffer ready
{
if(ASND_AddVoice(voice, play_buffer[buffer_nr], buffer_pos)==SND_OK) // add buffer
{
// next buffer
buffer_nr = (buffer_nr+1)%3;
buffer_pos = 0;
buffer_ready= false;
buffer_eof = false;
}
}
}