mirror of
https://github.com/dborth/snes9xgx.git
synced 2024-12-26 19:21:50 +01:00
547 lines
11 KiB
C
547 lines
11 KiB
C
/*
|
|
Copyright (c) 2008 Francisco Muñoz 'Hermes' <www.elotrolado.net>
|
|
All rights reserved.
|
|
|
|
Proper (standard) vorbis usage by Tantric, 2009
|
|
Threading modifications/corrections by Tantric, 2009
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
- Redistributions of source code must retain the above copyright notice, this
|
|
list of conditions and the following disclaimer.
|
|
- Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
- The names of the contributors may not be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef NO_SOUND
|
|
|
|
#include <asndlib.h>
|
|
#include <tremor/ivorbiscodec.h>
|
|
#include <tremor/ivorbisfile.h>
|
|
#include <gccore.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
#include "oggplayer.h"
|
|
|
|
/* functions to read the Ogg file from memory */
|
|
|
|
static struct
|
|
{
|
|
char *mem;
|
|
int size;
|
|
int pos;
|
|
} file[4];
|
|
|
|
static int f_read(void * punt, int bytes, int blocks, int *f)
|
|
{
|
|
int b;
|
|
int c = 0;
|
|
int d;
|
|
|
|
if (bytes * blocks <= 0)
|
|
return 0;
|
|
|
|
blocks *= bytes;
|
|
|
|
while (blocks > 0)
|
|
{
|
|
b = blocks;
|
|
if (b > 4096)
|
|
b = 4096;
|
|
|
|
d = (*f) - 0x666;
|
|
if((unsigned)(d) <= (0x669 - 0x666))
|
|
{
|
|
if (file[d].size == 0)
|
|
return -1;
|
|
if ((file[d].pos + b) > file[d].size)
|
|
b = file[d].size - file[d].pos;
|
|
if (b > 0)
|
|
{
|
|
memcpy(punt, file[d].mem + file[d].pos, b);
|
|
file[d].pos += b;
|
|
}
|
|
}
|
|
else
|
|
b = read(*f, ((char *) punt) + c, b);
|
|
|
|
if (b <= 0)
|
|
{
|
|
return c / bytes;
|
|
}
|
|
c += b;
|
|
blocks -= b;
|
|
}
|
|
return c / bytes;
|
|
}
|
|
|
|
static int f_seek(int *f, ogg_int64_t offset, int mode)
|
|
{
|
|
if(f==NULL) return(-1);
|
|
|
|
int k;
|
|
mode &= 3;
|
|
|
|
int d = (*f) - 0x666;
|
|
if((unsigned)(d) <= (0x669 - 0x666))
|
|
{
|
|
k = 0;
|
|
|
|
if (file[d].size == 0)
|
|
return -1;
|
|
|
|
if (mode == 0)
|
|
{
|
|
if ((offset) >= file[d].size)
|
|
{
|
|
file[d].pos = file[d].size;
|
|
k = -1;
|
|
}
|
|
else if ((offset) < 0)
|
|
{
|
|
file[d].pos = 0;
|
|
k = -1;
|
|
}
|
|
else
|
|
file[d].pos = offset;
|
|
}
|
|
else if (mode == 1)
|
|
{
|
|
if ((file[d].pos + offset) >= file[d].size)
|
|
{
|
|
file[d].pos = file[d].size;
|
|
k = -1;
|
|
}
|
|
else if ((file[d].pos + offset) < 0)
|
|
{
|
|
file[d].pos = 0;
|
|
k = -1;
|
|
}
|
|
else
|
|
file[d].pos += offset;
|
|
}
|
|
else if (mode == 2)
|
|
{
|
|
|
|
if ((file[d].size + offset) >= file[d].size)
|
|
{
|
|
file[d].pos = file[d].size;
|
|
k = -1;
|
|
}
|
|
else if ((file[d].size + offset) < 0)
|
|
{
|
|
file[d].pos = 0;
|
|
k = -1;
|
|
}
|
|
else
|
|
file[d].pos = file[d].size + offset;
|
|
}
|
|
|
|
}
|
|
else
|
|
k = lseek(*f, (int) offset, mode);
|
|
|
|
if (k < 0)
|
|
k = -1;
|
|
else
|
|
k = 0;
|
|
return k;
|
|
}
|
|
|
|
static int f_close(int *f)
|
|
{
|
|
int d = (*f) - 0x666;
|
|
if((unsigned)(d) <= (0x669 - 0x666))
|
|
{
|
|
file[d].size = 0;
|
|
file[d].pos = 0;
|
|
if (file[d].mem)
|
|
{
|
|
file[d].mem = (void *) 0;
|
|
}
|
|
return 0;
|
|
}
|
|
else
|
|
return close(*f);
|
|
return 0;
|
|
}
|
|
|
|
static long f_tell(int *f)
|
|
{
|
|
int k;
|
|
|
|
int d = (*f) - 0x666;
|
|
if((unsigned)(d) <= (0x669 - 0x666))
|
|
{
|
|
k = file[d].pos;
|
|
}
|
|
else
|
|
k = lseek(*f, 0, 1);
|
|
|
|
return (long) k;
|
|
}
|
|
|
|
static int mem_open(char * ogg, int size)
|
|
{
|
|
static int one = 1;
|
|
int n;
|
|
if (one)
|
|
{
|
|
one = 0;
|
|
|
|
file[0].size = 0;
|
|
file[1].size = 0;
|
|
file[2].size = 0;
|
|
file[3].size = 0;
|
|
file[0].mem = ogg;
|
|
file[0].size = size;
|
|
file[0].pos = 0;
|
|
return (0x666);
|
|
}
|
|
|
|
for (n = 0; n < 4; n++)
|
|
{
|
|
if (file[n].size == 0)
|
|
{
|
|
file[n].mem = ogg;
|
|
file[n].size = size;
|
|
file[n].pos = 0;
|
|
return (0x666 + n);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int mem_close(int fd)
|
|
{
|
|
if((unsigned)((fd) - 0x666) <= (0x669 - 0x666)) // it is a memory file descriptor?
|
|
{
|
|
fd -= 0x666;
|
|
file[fd].size = 0;
|
|
return 0;
|
|
}
|
|
else
|
|
return f_close(&fd);
|
|
}
|
|
|
|
static ov_callbacks callbacks = {
|
|
(size_t (*)(void *, size_t, size_t, void *)) f_read,
|
|
(int (*)(void *, ogg_int64_t, int)) f_seek,
|
|
(int (*)(void *)) f_close,
|
|
(long (*)(void *)) f_tell
|
|
};
|
|
|
|
/* OGG control */
|
|
|
|
#define READ_SAMPLES 4096 // samples that it must read before to send
|
|
#define MAX_PCMOUT 4096 // minimum size to read ogg samples
|
|
typedef struct
|
|
{
|
|
OggVorbis_File vf;
|
|
vorbis_info *vi;
|
|
int current_section;
|
|
|
|
// OGG file operation
|
|
int fd;
|
|
int mode;
|
|
int eof;
|
|
int flag;
|
|
int volume;
|
|
int seek_time;
|
|
|
|
/* OGG buffer control */
|
|
short pcmout[2][READ_SAMPLES + MAX_PCMOUT * 2]; /* take 4k out of the data segment, not the stack */
|
|
int pcmout_pos;
|
|
int pcm_indx;
|
|
|
|
} private_data_ogg;
|
|
|
|
static private_data_ogg private_ogg;
|
|
|
|
// OGG thread control
|
|
|
|
#define STACKSIZE 8192
|
|
|
|
static u8 oggplayer_stack[STACKSIZE];
|
|
static lwpq_t oggplayer_queue = LWP_TQUEUE_NULL;
|
|
static lwp_t h_oggplayer = LWP_THREAD_NULL;
|
|
static int ogg_thread_running = 0;
|
|
|
|
static void ogg_add_callback(int voice)
|
|
{
|
|
if (!ogg_thread_running)
|
|
{
|
|
ASND_StopVoice(0);
|
|
return;
|
|
}
|
|
|
|
if (private_ogg.flag & 128)
|
|
return; // Ogg is paused
|
|
|
|
if (private_ogg.pcm_indx >= READ_SAMPLES)
|
|
{
|
|
if (ASND_AddVoice(0,
|
|
(void *) private_ogg.pcmout[private_ogg.pcmout_pos],
|
|
private_ogg.pcm_indx << 1) == 0)
|
|
{
|
|
private_ogg.pcmout_pos ^= 1;
|
|
private_ogg.pcm_indx = 0;
|
|
private_ogg.flag = 0;
|
|
LWP_ThreadSignal(oggplayer_queue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (private_ogg.flag & 64)
|
|
{
|
|
private_ogg.flag &= ~64;
|
|
LWP_ThreadSignal(oggplayer_queue);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void * ogg_player_thread(private_data_ogg * priv)
|
|
{
|
|
int first_time = 1;
|
|
long ret;
|
|
|
|
//init
|
|
LWP_InitQueue(&oggplayer_queue);
|
|
|
|
priv[0].vi = ov_info(&priv[0].vf, -1);
|
|
|
|
ASND_Pause(0);
|
|
|
|
priv[0].pcm_indx = 0;
|
|
priv[0].pcmout_pos = 0;
|
|
priv[0].eof = 0;
|
|
priv[0].flag = 0;
|
|
priv[0].current_section = 0;
|
|
|
|
ogg_thread_running = 1;
|
|
|
|
while (!priv[0].eof && ogg_thread_running)
|
|
{
|
|
if (priv[0].flag)
|
|
LWP_ThreadSleep(oggplayer_queue); // wait only when i have samples to send
|
|
|
|
if (priv[0].flag == 0) // wait to all samples are sent
|
|
{
|
|
if (ASND_TestPointer(0, priv[0].pcmout[priv[0].pcmout_pos])
|
|
&& ASND_StatusVoice(0) != SND_UNUSED)
|
|
{
|
|
priv[0].flag |= 64;
|
|
continue;
|
|
}
|
|
if (priv[0].pcm_indx < READ_SAMPLES)
|
|
{
|
|
priv[0].flag = 3;
|
|
|
|
if (priv[0].seek_time >= 0)
|
|
{
|
|
ov_time_seek(&priv[0].vf, priv[0].seek_time);
|
|
priv[0].seek_time = -1;
|
|
}
|
|
|
|
ret = ov_read(
|
|
&priv[0].vf,
|
|
(void *) &priv[0].pcmout[priv[0].pcmout_pos][priv[0].pcm_indx],
|
|
MAX_PCMOUT,/*0,2,1,*/&priv[0].current_section);
|
|
priv[0].flag &= 192;
|
|
if (ret == 0)
|
|
{
|
|
/* EOF */
|
|
if (priv[0].mode & 1)
|
|
ov_time_seek(&priv[0].vf, 0); // repeat
|
|
else
|
|
priv[0].eof = 1; // stops
|
|
}
|
|
else if (ret < 0)
|
|
{
|
|
/* error in the stream. Not a problem, just reporting it in
|
|
case we (the app) cares. In this case, we don't. */
|
|
if (ret != OV_HOLE)
|
|
{
|
|
if (priv[0].mode & 1)
|
|
ov_time_seek(&priv[0].vf, 0); // repeat
|
|
else
|
|
priv[0].eof = 1; // stops
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* we don't bother dealing with sample rate changes, etc, but
|
|
you'll have to*/
|
|
priv[0].pcm_indx += ret >> 1; //get 16 bits samples
|
|
}
|
|
}
|
|
else
|
|
priv[0].flag = 1;
|
|
}
|
|
|
|
if (priv[0].flag == 1)
|
|
{
|
|
if (ASND_StatusVoice(0) == SND_UNUSED || first_time)
|
|
{
|
|
first_time = 0;
|
|
if (priv[0].vi->channels == 2)
|
|
{
|
|
ASND_SetVoice(0, VOICE_STEREO_16BIT, priv[0].vi->rate, 0,
|
|
(void *) priv[0].pcmout[priv[0].pcmout_pos],
|
|
priv[0].pcm_indx << 1, priv[0].volume,
|
|
priv[0].volume, ogg_add_callback);
|
|
priv[0].pcmout_pos ^= 1;
|
|
priv[0].pcm_indx = 0;
|
|
priv[0].flag = 0;
|
|
}
|
|
else
|
|
{
|
|
ASND_SetVoice(0, VOICE_MONO_16BIT, priv[0].vi->rate, 0,
|
|
(void *) priv[0].pcmout[priv[0].pcmout_pos],
|
|
priv[0].pcm_indx << 1, priv[0].volume,
|
|
priv[0].volume, ogg_add_callback);
|
|
priv[0].pcmout_pos ^= 1;
|
|
priv[0].pcm_indx = 0;
|
|
priv[0].flag = 0;
|
|
}
|
|
}
|
|
}
|
|
usleep(100);
|
|
}
|
|
ov_clear(&priv[0].vf);
|
|
priv[0].fd = -1;
|
|
priv[0].pcm_indx = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void StopOgg()
|
|
{
|
|
ASND_StopVoice(0);
|
|
ogg_thread_running = 0;
|
|
|
|
if(h_oggplayer != LWP_THREAD_NULL)
|
|
{
|
|
if(oggplayer_queue != LWP_TQUEUE_NULL)
|
|
LWP_ThreadSignal(oggplayer_queue);
|
|
LWP_JoinThread(h_oggplayer, NULL);
|
|
h_oggplayer = LWP_THREAD_NULL;
|
|
}
|
|
if(oggplayer_queue != LWP_TQUEUE_NULL)
|
|
{
|
|
LWP_CloseQueue(oggplayer_queue);
|
|
oggplayer_queue = LWP_TQUEUE_NULL;
|
|
}
|
|
}
|
|
|
|
int PlayOgg(const void *buffer, s32 len, int time_pos, int mode)
|
|
{
|
|
StopOgg();
|
|
|
|
private_ogg.fd = mem_open((char *)buffer, len);
|
|
|
|
if (private_ogg.fd < 0)
|
|
{
|
|
private_ogg.fd = -1;
|
|
return -1;
|
|
}
|
|
|
|
private_ogg.mode = mode;
|
|
private_ogg.eof = 0;
|
|
private_ogg.volume = 127;
|
|
private_ogg.flag = 0;
|
|
private_ogg.seek_time = -1;
|
|
|
|
if (time_pos > 0)
|
|
private_ogg.seek_time = time_pos;
|
|
|
|
if (ov_open_callbacks((void *) &private_ogg.fd, &private_ogg.vf, NULL, 0, callbacks) < 0)
|
|
{
|
|
mem_close(private_ogg.fd); // mem_close() can too close files from devices
|
|
private_ogg.fd = -1;
|
|
ogg_thread_running = 0;
|
|
return -1;
|
|
}
|
|
|
|
if (LWP_CreateThread(&h_oggplayer, (void *) ogg_player_thread,
|
|
&private_ogg, oggplayer_stack, STACKSIZE, 80) == -1)
|
|
{
|
|
ogg_thread_running = 0;
|
|
ov_clear(&private_ogg.vf);
|
|
private_ogg.fd = -1;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void PauseOgg(int pause)
|
|
{
|
|
if (pause)
|
|
{
|
|
private_ogg.flag |= 128;
|
|
}
|
|
else
|
|
{
|
|
if (private_ogg.flag & 128)
|
|
{
|
|
private_ogg.flag |= 64;
|
|
private_ogg.flag &= ~128;
|
|
if (ogg_thread_running > 0)
|
|
{
|
|
LWP_ThreadSignal(oggplayer_queue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int StatusOgg()
|
|
{
|
|
if (ogg_thread_running == 0)
|
|
return -1; // Error
|
|
else if (private_ogg.eof)
|
|
return 255; // EOF
|
|
else if (private_ogg.flag & 128)
|
|
return 2; // paused
|
|
return 1; // running
|
|
}
|
|
|
|
void SetVolumeOgg(int volume)
|
|
{
|
|
private_ogg.volume = volume;
|
|
ASND_ChangeVolumeVoice(0, volume, volume);
|
|
}
|
|
|
|
s32 GetTimeOgg()
|
|
{
|
|
int ret;
|
|
if (ogg_thread_running == 0 || private_ogg.fd < 0)
|
|
return -1;
|
|
ret = ((s32) ov_time_tell(&private_ogg.vf));
|
|
|
|
return ret;
|
|
}
|
|
|
|
void SetTimeOgg(s32 time_pos)
|
|
{
|
|
if (time_pos >= 0)
|
|
private_ogg.seek_time = time_pos;
|
|
}
|
|
|
|
#endif
|