/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2006-2007 Adam Gashlin (hcs) * Copyright (C) 2004-2007 Shay Green (blargg) * Copyright (C) 2002 Brad Martin * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ /* lovingly ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ /* DSP Based on Brad Martin's OpenSPC DSP emulator */ /* tag reading from sexyspc by John Brawn (John_Brawn@yahoo.com) and others */ #include "codeclib.h" #include "spc/spc_codec.h" #include "spc/spc_profiler.h" CODEC_HEADER /**************** ID666 parsing ****************/ struct { unsigned char isBinary; char song[32]; char game[32]; char dumper[16]; char comments[32]; int day,month,year; unsigned long length; unsigned long fade; char artist[32]; unsigned char muted; unsigned char emulator; } ID666; static int LoadID666(unsigned char *buf) { unsigned char *ib=buf; int isbinary = 1; int i; memcpy(ID666.song,ib,32); ID666.song[31]=0; ib+=32; memcpy(ID666.game,ib,32); ID666.game[31]=0; ib+=32; memcpy(ID666.dumper,ib,16); ID666.dumper[15]=0; ib+=16; memcpy(ID666.comments,ib,32); ID666.comments[31]=0; ib+=32; /* Ok, now comes the fun part. */ /* Date check */ if(ib[2] == '/' && ib[5] == '/' ) isbinary = 0; /* Reserved bytes check */ if(ib[0xD2 - 0x2E - 112] >= '0' && ib[0xD2 - 0x2E - 112] <= '9' && ib[0xD3 - 0x2E - 112] == 0x00) isbinary = 0; /* is length & fade only digits? */ for (i=0;i<8 && ( (ib[0xA9 - 0x2E - 112+i]>='0'&&ib[0xA9 - 0x2E - 112+i]<='9') || ib[0xA9 - 0x2E - 112+i]=='\0'); i++); if (i==8) isbinary=0; ID666.isBinary = isbinary; if(isbinary) { DEBUGF("binary tag detected\n"); ID666.year=*ib; ib++; ID666.year|=*ib<<8; ib++; ID666.month=*ib; ib++; ID666.day=*ib; ib++; ib+=7; ID666.length=*ib; ib++; ID666.length|=*ib<<8; ib++; ID666.length|=*ib<<16; ID666.length*=1000; ib++; ID666.fade=*ib; ib++; ID666.fade|=*ib<<8; ib++; ID666.fade|=*ib<<16; ib++; ID666.fade|=*ib<<24; ib++; memcpy(ID666.artist,ib,32); ID666.artist[31]=0; ib+=32; ID666.muted=*ib; ib++; ID666.emulator=*ib; ib++; } else { unsigned long tmp; char buf[64]; DEBUGF("text tag detected\n"); memcpy(buf, ib, 2); buf[2] = 0; tmp = 0; for (i=0;i<2 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0'; ID666.month = tmp; ib+=3; memcpy(buf, ib, 2); buf[2] = 0; tmp = 0; for (i=0;i<2 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0'; ID666.day = tmp; ib+=3; memcpy(buf, ib, 4); buf[4] = 0; tmp = 0; for (i=0;i<4 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0'; ID666.year = tmp; ib+=5; memcpy(buf, ib, 3); buf[3] = 0; tmp = 0; for (i=0;i<3 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0'; ID666.length = tmp * 1000; ib+=3; memcpy(buf, ib, 5); buf[5] = 0; tmp = 0; for (i=0;i<5 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0'; ID666.fade = tmp; ib+=5; memcpy(ID666.artist,ib,32); ID666.artist[31]=0; ib+=32; /*I have no idea if this is right or not.*/ ID666.muted=*ib; ib++; memcpy(buf, ib, 1); buf[1] = 0; tmp = 0; ib++; } return 1; } /**************** Codec ****************/ enum {SAMPLE_RATE = 32000}; static struct Spc_Emu spc_emu IDATA_ATTR CACHEALIGN_ATTR; #if SPC_DUAL_CORE /** Implementations for pipelined dual-core operation **/ static int spc_emu_thread_stack[DEFAULT_STACK_SIZE/sizeof(int)] CACHEALIGN_ATTR; static const unsigned char * const spc_emu_thread_name = "spc emu"; static struct thread_entry *emu_thread_p; enum { SPC_EMU_AUDIO = 0, SPC_EMU_LOAD, SPC_EMU_QUIT, }; struct spc_load { uint8_t *buf; size_t size; }; /* sample queue */ #define WAV_NUM_CHUNKS 2 #define WAV_CHUNK_MASK (WAV_NUM_CHUNKS-1) struct sample_queue_chunk { long id; union { intptr_t data; int32_t audio[WAV_CHUNK_SIZE*2]; }; }; static struct { int head, tail; struct semaphore emu_sem_head; struct semaphore emu_sem_tail; struct event emu_evt_reply; intptr_t retval; struct sample_queue_chunk wav_chunk[WAV_NUM_CHUNKS]; } sample_queue NOCACHEBSS_ATTR; static inline void samples_release_wrbuf(void) { sample_queue.tail++; ci->semaphore_release(&sample_queue.emu_sem_head); } static inline struct sample_queue_chunk * samples_get_wrbuf(void) { ci->semaphore_wait(&sample_queue.emu_sem_tail); return &sample_queue.wav_chunk[sample_queue.tail & WAV_CHUNK_MASK]; } static inline void samples_release_rdbuf(void) { if (sample_queue.head != sample_queue.tail) { sample_queue.head++; } ci->semaphore_release(&sample_queue.emu_sem_tail); } static inline int32_t * samples_get_rdbuf(void) { ci->semaphore_wait(&sample_queue.emu_sem_head); if (ci->stop_codec || ci->new_track) { /* Told to stop. Buffer must be released. */ samples_release_rdbuf(); return NULL; } return sample_queue.wav_chunk[sample_queue.head & WAV_CHUNK_MASK].audio; } static intptr_t emu_thread_send_msg(long id, intptr_t data) { struct sample_queue_chunk *chunk; /* Grab an audio output buffer */ ci->semaphore_wait(&sample_queue.emu_sem_head); chunk = &sample_queue.wav_chunk[sample_queue.head & WAV_CHUNK_MASK]; /* Place a message in it instead of audio */ chunk->id = id; chunk->data = data; /* Release it to the emu thread */ samples_release_rdbuf(); /* Wait for a response */ ci->event_wait(&sample_queue.emu_evt_reply, STATE_SIGNALED); return sample_queue.retval; } /* thread function */ static bool emu_thread_process_msg(struct sample_queue_chunk *chunk) { long id = chunk->id; bool ret = id != SPC_EMU_QUIT; chunk->id = SPC_EMU_AUDIO; /* Reset chunk type to audio */ sample_queue.retval = 0; if (id == SPC_EMU_LOAD) { struct spc_load *ld = (struct spc_load *)chunk->data; invalidate_icache(); SPC_Init(&spc_emu); sample_queue.retval = SPC_load_spc(&spc_emu, ld->buf, ld->size); } /* Empty the audio queue */ /* This is a dirty hack a timeout based wait would make unnescessary but still safe because the other thread is known to be waiting for a reply and is not using the objects. */ ci->semaphore_init(&sample_queue.emu_sem_tail, 2, 2); ci->semaphore_init(&sample_queue.emu_sem_head, 2, 0); sample_queue.head = sample_queue.tail = 0; ci->event_set_state(&sample_queue.emu_evt_reply, STATE_SIGNALED); return ret; } static void spc_emu_thread(void) { CPU_Init(&spc_emu); while (1) { /* get a buffer for output */ struct sample_queue_chunk *chunk = samples_get_wrbuf(); if (chunk->id != SPC_EMU_AUDIO) { /* This chunk doesn't contain audio but a command */ if (!emu_thread_process_msg(chunk)) break; /* Have to re-get this pointer to keep semaphore counts correct */ continue; } ENTER_TIMER(render); /* fill samples buffer */ if ( SPC_play(&spc_emu, WAV_CHUNK_SIZE*2, chunk->audio) ) assert( false ); EXIT_TIMER(render); /* done so release it to output */ samples_release_wrbuf(); ci->yield(); } } static bool spc_emu_start(void) { emu_thread_p = ci->create_thread(spc_emu_thread, spc_emu_thread_stack, sizeof(spc_emu_thread_stack), CREATE_THREAD_FROZEN, spc_emu_thread_name IF_PRIO(, PRIORITY_PLAYBACK), COP); if (emu_thread_p == NULL) return false; /* Initialize audio queue as full to prevent emu thread from trying to run the emulator before loading something */ ci->event_init(&sample_queue.emu_evt_reply, EVENT_AUTOMATIC | STATE_NONSIGNALED); ci->semaphore_init(&sample_queue.emu_sem_tail, 2, 0); ci->semaphore_init(&sample_queue.emu_sem_head, 2, 2); sample_queue.head = 0; sample_queue.tail = 2; /* Start it running */ ci->thread_thaw(emu_thread_p); return true; } /* load a new program on the emu thread */ static inline int load_spc_buffer(uint8_t *buf, size_t size) { struct spc_load ld = { buf, size }; flush_icache(); return emu_thread_send_msg(SPC_EMU_LOAD, (intptr_t)&ld); } static inline void spc_emu_quit(void) { emu_thread_send_msg(SPC_EMU_QUIT, 0); /* Wait for emu thread to be killed */ ci->thread_wait(emu_thread_p); } static inline bool spc_play_get_samples(int32_t **samples) { /* obtain filled samples buffer */ *samples = samples_get_rdbuf(); return *samples != NULL; } static inline void spc_play_send_samples(int32_t *samples) { ci->pcmbuf_insert(samples, samples+WAV_CHUNK_SIZE, WAV_CHUNK_SIZE); /* done with chunk so release it to emu thread */ samples_release_rdbuf(); } #else /* !SPC_DUAL_CORE */ /** Implementations for single-core operation **/ int32_t wav_chunk[WAV_CHUNK_SIZE*2] IBSS_ATTR; /* load a new program into emu */ static inline int load_spc_buffer(uint8_t *buf, size_t size) { SPC_Init(&spc_emu); return SPC_load_spc(&spc_emu, buf, size); } static inline bool spc_emu_start(void) { #ifdef CPU_COLDFIRE /* signed integer mode with saturation */ coldfire_set_macsr(EMAC_SATURATE); #endif CPU_Init(&spc_emu); return true; } static inline void spc_play_send_samples(int32_t *samples) { ci->pcmbuf_insert(samples, samples+WAV_CHUNK_SIZE, WAV_CHUNK_SIZE); } #define spc_emu_quit() #define samples_release_rdbuf() static inline bool spc_play_get_samples(int32_t **samples) { ENTER_TIMER(render); /* fill samples buffer */ if ( SPC_play(&spc_emu,WAV_CHUNK_SIZE*2,wav_chunk) ) assert( false ); EXIT_TIMER(render); *samples = wav_chunk; return true; } #endif /* SPC_DUAL_CORE */ /* The main decoder loop */ static int play_track( void ) { int sampleswritten=0; unsigned long fadestartsample = ID666.length*(long long) SAMPLE_RATE/1000; unsigned long fadeendsample = (ID666.length+ID666.fade)*(long long) SAMPLE_RATE/1000; int fadedec = 0; int fadevol = 0x7fffffffl; if (fadeendsample>fadestartsample) fadedec=0x7fffffffl/(fadeendsample-fadestartsample)+1; ENTER_TIMER(total); while ( 1 ) { ci->yield(); if (ci->stop_codec || ci->new_track) { break; } if (ci->seek_time) { int curtime = sampleswritten*1000LL/SAMPLE_RATE; DEBUGF("seek to %ld\ncurrently at %d\n",ci->seek_time,curtime); if (ci->seek_time < curtime) { DEBUGF("seek backwards = reset\n"); ci->seek_complete(); return 1; } ci->seek_complete(); } int32_t *samples; if (!spc_play_get_samples(&samples)) break; sampleswritten += WAV_CHUNK_SIZE; /* is track timed? */ if (ci->global_settings->repeat_mode!=REPEAT_ONE && ci->id3->length) { unsigned long curtime = sampleswritten*1000LL/SAMPLE_RATE; unsigned long lasttimesample = (sampleswritten-WAV_CHUNK_SIZE); /* fade? */ if (curtime>ID666.length) { #ifdef CPU_COLDFIRE /* Have to switch modes to do this */ long macsr = coldfire_get_macsr(); coldfire_set_macsr(EMAC_SATURATE | EMAC_FRACTIONAL | EMAC_ROUND); #endif int i; for (i=0;ifadestartsample) { if (fadevol>0) { samples[i] = FRACMUL(samples[i], fadevol); samples[i+WAV_CHUNK_SIZE] = FRACMUL(samples[i+WAV_CHUNK_SIZE], fadevol); } else samples[i]=samples[i+WAV_CHUNK_SIZE]=0; fadevol-=fadedec; } } #ifdef CPU_COLDFIRE coldfire_set_macsr(macsr); #endif } /* end? */ if (lasttimesample>=fadeendsample) { samples_release_rdbuf(); break; } } spc_play_send_samples(samples); if (ci->global_settings->repeat_mode!=REPEAT_ONE) ci->set_elapsed(sampleswritten*1000LL/SAMPLE_RATE); else ci->set_elapsed(0); } EXIT_TIMER(total); return 0; } /* this is the codec entry point */ enum codec_status codec_main(void) { enum codec_status stat = CODEC_ERROR; if (!spc_emu_start()) goto codec_quit; do { DEBUGF("SPC: next_track\n"); if (codec_init()) { goto codec_quit; } DEBUGF("SPC: after init\n"); ci->configure(DSP_SET_SAMPLE_DEPTH, 24); ci->configure(DSP_SET_FREQUENCY, SAMPLE_RATE); ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); /* wait for track info to load */ while (!*ci->taginfo_ready && !ci->stop_codec) ci->yield(); codec_set_replaygain(ci->id3); /* Read the entire file */ DEBUGF("SPC: request initial buffer\n"); ci->configure(CODEC_SET_FILEBUF_WATERMARK, ci->filesize); ci->seek_buffer(0); size_t buffersize; uint8_t* buffer = ci->request_buffer(&buffersize, ci->filesize); if (!buffer) { goto codec_quit; } DEBUGF("SPC: read size = 0x%lx\n",(unsigned long)buffersize); do { if (load_spc_buffer(buffer, buffersize)) { DEBUGF("SPC load failure\n"); goto codec_quit; } LoadID666(buffer+0x2e); if (ci->global_settings->repeat_mode!=REPEAT_ONE && ID666.length==0) { ID666.length=3*60*1000; /* 3 minutes */ ID666.fade=5*1000; /* 5 seconds */ } ci->id3->length = ID666.length+ID666.fade; ci->id3->title = ID666.song; ci->id3->album = ID666.game; ci->id3->artist = ID666.artist; ci->id3->year = ID666.year; ci->id3->comment = ID666.comments; reset_profile_timers(); } while ( play_track() ); print_timers(ci->id3->path); } while ( ci->request_next_track() ); stat = CODEC_OK; codec_quit: spc_emu_quit(); return stat; }