autodetection: convert path to native separators before displaying it.
[Rockbox.git] / apps / codecs / spc.c
blob948a05edd8e0c7e53706d7acc29ba03c8fc044cd
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007-2008 Michael Sevakis (jhMikeS)
11 * Copyright (C) 2006-2007 Adam Gashlin (hcs)
12 * Copyright (C) 2004-2007 Shay Green (blargg)
13 * Copyright (C) 2002 Brad Martin
15 * All files in this archive are subject to the GNU General Public License.
16 * See the file COPYING in the source tree root for full license agreement.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
23 /* lovingly ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */
24 /* DSP Based on Brad Martin's OpenSPC DSP emulator */
25 /* tag reading from sexyspc by John Brawn (John_Brawn@yahoo.com) and others */
26 #include "codeclib.h"
27 #include "spc/spc_codec.h"
28 #include "spc/spc_profiler.h"
30 CODEC_HEADER
32 /**************** ID666 parsing ****************/
34 struct {
35 unsigned char isBinary;
36 char song[32];
37 char game[32];
38 char dumper[16];
39 char comments[32];
40 int day,month,year;
41 unsigned long length;
42 unsigned long fade;
43 char artist[32];
44 unsigned char muted;
45 unsigned char emulator;
46 } ID666;
48 static int LoadID666(unsigned char *buf) {
49 unsigned char *ib=buf;
50 int isbinary = 1;
51 int i;
53 memcpy(ID666.song,ib,32);
54 ID666.song[31]=0;
55 ib+=32;
57 memcpy(ID666.game,ib,32);
58 ID666.game[31]=0;
59 ib+=32;
61 memcpy(ID666.dumper,ib,16);
62 ID666.dumper[15]=0;
63 ib+=16;
65 memcpy(ID666.comments,ib,32);
66 ID666.comments[31]=0;
67 ib+=32;
69 /* Ok, now comes the fun part. */
71 /* Date check */
72 if(ib[2] == '/' && ib[5] == '/' )
73 isbinary = 0;
75 /* Reserved bytes check */
76 if(ib[0xD2 - 0x2E - 112] >= '0' &&
77 ib[0xD2 - 0x2E - 112] <= '9' &&
78 ib[0xD3 - 0x2E - 112] == 0x00)
79 isbinary = 0;
81 /* is length & fade only digits? */
82 for (i=0;i<8 && (
83 (ib[0xA9 - 0x2E - 112+i]>='0'&&ib[0xA9 - 0x2E - 112+i]<='9') ||
84 ib[0xA9 - 0x2E - 112+i]=='\0');
85 i++);
86 if (i==8) isbinary=0;
88 ID666.isBinary = isbinary;
90 if(isbinary) {
91 DEBUGF("binary tag detected\n");
92 ID666.year=*ib;
93 ib++;
94 ID666.year|=*ib<<8;
95 ib++;
96 ID666.month=*ib;
97 ib++;
98 ID666.day=*ib;
99 ib++;
101 ib+=7;
103 ID666.length=*ib;
104 ib++;
106 ID666.length|=*ib<<8;
107 ib++;
109 ID666.length|=*ib<<16;
110 ID666.length*=1000;
111 ib++;
113 ID666.fade=*ib;
114 ib++;
115 ID666.fade|=*ib<<8;
116 ib++;
117 ID666.fade|=*ib<<16;
118 ib++;
119 ID666.fade|=*ib<<24;
120 ib++;
122 memcpy(ID666.artist,ib,32);
123 ID666.artist[31]=0;
124 ib+=32;
126 ID666.muted=*ib;
127 ib++;
129 ID666.emulator=*ib;
130 ib++;
131 } else {
132 unsigned long tmp;
133 char buf[64];
135 DEBUGF("text tag detected\n");
137 memcpy(buf, ib, 2);
138 buf[2] = 0;
139 tmp = 0;
140 for (i=0;i<2 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0';
141 ID666.month = tmp;
142 ib+=3;
144 memcpy(buf, ib, 2);
145 buf[2] = 0;
146 tmp = 0;
147 for (i=0;i<2 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0';
148 ID666.day = tmp;
149 ib+=3;
151 memcpy(buf, ib, 4);
152 buf[4] = 0;
153 tmp = 0;
154 for (i=0;i<4 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0';
155 ID666.year = tmp;
156 ib+=5;
158 memcpy(buf, ib, 3);
159 buf[3] = 0;
160 tmp = 0;
161 for (i=0;i<3 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0';
162 ID666.length = tmp * 1000;
163 ib+=3;
165 memcpy(buf, ib, 5);
166 buf[5] = 0;
167 tmp = 0;
168 for (i=0;i<5 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0';
169 ID666.fade = tmp;
170 ib+=5;
172 memcpy(ID666.artist,ib,32);
173 ID666.artist[31]=0;
174 ib+=32;
176 /*I have no idea if this is right or not.*/
177 ID666.muted=*ib;
178 ib++;
180 memcpy(buf, ib, 1);
181 buf[1] = 0;
182 tmp = 0;
183 ib++;
185 return 1;
188 /**************** Codec ****************/
189 enum {SAMPLE_RATE = 32000};
190 static struct Spc_Emu spc_emu IDATA_ATTR CACHEALIGN_ATTR;
192 #if SPC_DUAL_CORE
193 /** Implementations for pipelined dual-core operation **/
194 static int spc_emu_thread_stack[DEFAULT_STACK_SIZE/sizeof(int)]
195 CACHEALIGN_ATTR;
197 static const unsigned char * const spc_emu_thread_name = "spc emu";
198 static struct thread_entry *emu_thread_p;
200 enum
202 SPC_EMU_AUDIO = 0,
203 SPC_EMU_LOAD,
204 SPC_EMU_QUIT,
207 struct spc_load
209 uint8_t *buf;
210 size_t size;
213 /* sample queue */
214 #define WAV_NUM_CHUNKS 2
215 #define WAV_CHUNK_MASK (WAV_NUM_CHUNKS-1)
216 struct sample_queue_chunk
218 long id;
219 union
221 intptr_t data;
222 int32_t audio[WAV_CHUNK_SIZE*2];
226 static struct
228 int head, tail;
229 struct semaphore emu_sem_head;
230 struct semaphore emu_sem_tail;
231 struct event emu_evt_reply;
232 intptr_t retval;
233 struct sample_queue_chunk wav_chunk[WAV_NUM_CHUNKS];
234 } sample_queue SHAREDBSS_ATTR;
236 static inline void samples_release_wrbuf(void)
238 sample_queue.tail++;
239 ci->semaphore_release(&sample_queue.emu_sem_head);
242 static inline struct sample_queue_chunk * samples_get_wrbuf(void)
244 ci->semaphore_wait(&sample_queue.emu_sem_tail);
245 return &sample_queue.wav_chunk[sample_queue.tail & WAV_CHUNK_MASK];
248 static inline void samples_release_rdbuf(void)
250 if (sample_queue.head != sample_queue.tail) {
251 sample_queue.head++;
254 ci->semaphore_release(&sample_queue.emu_sem_tail);
257 static inline int32_t * samples_get_rdbuf(void)
259 ci->semaphore_wait(&sample_queue.emu_sem_head);
261 if (ci->stop_codec || ci->new_track)
263 /* Told to stop. Buffer must be released. */
264 samples_release_rdbuf();
265 return NULL;
268 return sample_queue.wav_chunk[sample_queue.head & WAV_CHUNK_MASK].audio;
271 static intptr_t emu_thread_send_msg(long id, intptr_t data)
273 struct sample_queue_chunk *chunk;
274 /* Grab an audio output buffer */
275 ci->semaphore_wait(&sample_queue.emu_sem_head);
276 chunk = &sample_queue.wav_chunk[sample_queue.head & WAV_CHUNK_MASK];
277 /* Place a message in it instead of audio */
278 chunk->id = id;
279 chunk->data = data;
280 /* Release it to the emu thread */
281 samples_release_rdbuf();
283 if (id != SPC_EMU_QUIT) {
284 /* Wait for a response */
285 ci->event_wait(&sample_queue.emu_evt_reply, STATE_SIGNALED);
288 return sample_queue.retval;
291 /* thread function */
292 static bool emu_thread_process_msg(struct sample_queue_chunk *chunk)
294 long id = chunk->id;
295 bool ret = id != SPC_EMU_QUIT;
297 chunk->id = SPC_EMU_AUDIO; /* Reset chunk type to audio */
298 sample_queue.retval = 0;
300 if (id == SPC_EMU_LOAD)
302 struct spc_load *ld = (struct spc_load *)chunk->data;
303 invalidate_icache();
304 SPC_Init(&spc_emu);
305 sample_queue.retval = SPC_load_spc(&spc_emu, ld->buf, ld->size);
307 /* Empty the audio queue */
308 /* This is a dirty hack a timeout based wait would make unnescessary but
309 still safe because the other thread is known to be waiting for a reply
310 and is not using the objects. */
311 ci->semaphore_init(&sample_queue.emu_sem_tail, 2, 2);
312 ci->semaphore_init(&sample_queue.emu_sem_head, 2, 0);
313 sample_queue.head = sample_queue.tail = 0;
316 if (id != SPC_EMU_QUIT) {
317 ci->event_set_state(&sample_queue.emu_evt_reply, STATE_SIGNALED);
320 return ret;
323 static void spc_emu_thread(void)
325 CPU_Init(&spc_emu);
327 while (1) {
328 /* get a buffer for output */
329 struct sample_queue_chunk *chunk = samples_get_wrbuf();
331 if (chunk->id != SPC_EMU_AUDIO) {
332 /* This chunk doesn't contain audio but a command */
333 if (!emu_thread_process_msg(chunk))
334 break;
335 /* Have to re-get this pointer to keep semaphore counts correct */
336 continue;
339 ENTER_TIMER(render);
340 /* fill samples buffer */
341 if ( SPC_play(&spc_emu, WAV_CHUNK_SIZE*2, chunk->audio) )
342 assert( false );
343 EXIT_TIMER(render);
345 /* done so release it to output */
346 samples_release_wrbuf();
347 ci->yield();
351 static bool spc_emu_start(void)
353 emu_thread_p = ci->create_thread(spc_emu_thread, spc_emu_thread_stack,
354 sizeof(spc_emu_thread_stack), CREATE_THREAD_FROZEN,
355 spc_emu_thread_name IF_PRIO(, PRIORITY_PLAYBACK), COP);
357 if (emu_thread_p == NULL)
358 return false;
360 /* Initialize audio queue as full to prevent emu thread from trying to run the
361 emulator before loading something */
362 ci->event_init(&sample_queue.emu_evt_reply,
363 EVENT_AUTOMATIC | STATE_NONSIGNALED);
364 ci->semaphore_init(&sample_queue.emu_sem_tail, 2, 0);
365 ci->semaphore_init(&sample_queue.emu_sem_head, 2, 2);
366 sample_queue.head = 0;
367 sample_queue.tail = 2;
369 /* Start it running */
370 ci->thread_thaw(emu_thread_p);
371 return true;
374 /* load a new program on the emu thread */
375 static inline int load_spc_buffer(uint8_t *buf, size_t size)
377 struct spc_load ld = { buf, size };
378 flush_icache();
379 return emu_thread_send_msg(SPC_EMU_LOAD, (intptr_t)&ld);
382 static inline void spc_emu_quit(void)
384 if (emu_thread_p != NULL) {
385 emu_thread_send_msg(SPC_EMU_QUIT, 0);
386 /* Wait for emu thread to be killed */
387 ci->thread_wait(emu_thread_p);
388 invalidate_icache();
392 static inline bool spc_play_get_samples(int32_t **samples)
394 /* obtain filled samples buffer */
395 *samples = samples_get_rdbuf();
396 return *samples != NULL;
399 static inline void spc_play_send_samples(int32_t *samples)
401 ci->pcmbuf_insert(samples, samples+WAV_CHUNK_SIZE, WAV_CHUNK_SIZE);
402 /* done with chunk so release it to emu thread */
403 samples_release_rdbuf();
406 #else /* !SPC_DUAL_CORE */
407 /** Implementations for single-core operation **/
408 int32_t wav_chunk[WAV_CHUNK_SIZE*2] IBSS_ATTR;
410 /* load a new program into emu */
411 static inline int load_spc_buffer(uint8_t *buf, size_t size)
413 SPC_Init(&spc_emu);
414 return SPC_load_spc(&spc_emu, buf, size);
417 static inline bool spc_emu_start(void)
419 #ifdef CPU_COLDFIRE
420 /* signed integer mode with saturation */
421 coldfire_set_macsr(EMAC_SATURATE);
422 #endif
423 CPU_Init(&spc_emu);
424 return true;
427 static inline void spc_play_send_samples(int32_t *samples)
429 ci->pcmbuf_insert(samples, samples+WAV_CHUNK_SIZE, WAV_CHUNK_SIZE);
432 #define spc_emu_quit()
433 #define samples_release_rdbuf()
435 static inline bool spc_play_get_samples(int32_t **samples)
437 ENTER_TIMER(render);
438 /* fill samples buffer */
439 if ( SPC_play(&spc_emu,WAV_CHUNK_SIZE*2,wav_chunk) )
440 assert( false );
441 EXIT_TIMER(render);
442 *samples = wav_chunk;
443 return true;
445 #endif /* SPC_DUAL_CORE */
447 /* The main decoder loop */
448 static int play_track( void )
450 int sampleswritten=0;
452 unsigned long fadestartsample = ID666.length*(long long) SAMPLE_RATE/1000;
453 unsigned long fadeendsample = (ID666.length+ID666.fade)*(long long) SAMPLE_RATE/1000;
454 int fadedec = 0;
455 int fadevol = 0x7fffffffl;
457 if (fadeendsample>fadestartsample)
458 fadedec=0x7fffffffl/(fadeendsample-fadestartsample)+1;
460 ENTER_TIMER(total);
462 while ( 1 )
464 ci->yield();
465 if (ci->stop_codec || ci->new_track) {
466 break;
469 if (ci->seek_time) {
470 int curtime = sampleswritten*1000LL/SAMPLE_RATE;
471 DEBUGF("seek to %ld\ncurrently at %d\n",ci->seek_time,curtime);
472 if (ci->seek_time < curtime) {
473 DEBUGF("seek backwards = reset\n");
474 ci->seek_complete();
475 return 1;
477 ci->seek_complete();
480 int32_t *samples;
481 if (!spc_play_get_samples(&samples))
482 break;
484 sampleswritten += WAV_CHUNK_SIZE;
486 /* is track timed? */
487 if (ci->global_settings->repeat_mode!=REPEAT_ONE && ci->id3->length) {
488 unsigned long curtime = sampleswritten*1000LL/SAMPLE_RATE;
489 unsigned long lasttimesample = (sampleswritten-WAV_CHUNK_SIZE);
491 /* fade? */
492 if (curtime>ID666.length)
494 #ifdef CPU_COLDFIRE
495 /* Have to switch modes to do this */
496 long macsr = coldfire_get_macsr();
497 coldfire_set_macsr(EMAC_SATURATE | EMAC_FRACTIONAL | EMAC_ROUND);
498 #endif
499 int i;
500 for (i=0;i<WAV_CHUNK_SIZE;i++) {
501 if (lasttimesample+i>fadestartsample) {
502 if (fadevol>0) {
503 samples[i] = FRACMUL(samples[i], fadevol);
504 samples[i+WAV_CHUNK_SIZE] = FRACMUL(samples[i+WAV_CHUNK_SIZE], fadevol);
505 } else samples[i]=samples[i+WAV_CHUNK_SIZE]=0;
506 fadevol-=fadedec;
509 #ifdef CPU_COLDFIRE
510 coldfire_set_macsr(macsr);
511 #endif
513 /* end? */
514 if (lasttimesample>=fadeendsample)
516 samples_release_rdbuf();
517 break;
521 spc_play_send_samples(samples);
523 if (ci->global_settings->repeat_mode!=REPEAT_ONE)
524 ci->set_elapsed(sampleswritten*1000LL/SAMPLE_RATE);
525 else
526 ci->set_elapsed(0);
529 EXIT_TIMER(total);
530 return 0;
533 /* this is the codec entry point */
534 enum codec_status codec_main(void)
536 enum codec_status stat = CODEC_ERROR;
538 if (!spc_emu_start())
539 goto codec_quit;
543 DEBUGF("SPC: next_track\n");
544 if (codec_init()) {
545 goto codec_quit;
547 DEBUGF("SPC: after init\n");
549 ci->configure(DSP_SET_SAMPLE_DEPTH, 24);
550 ci->configure(DSP_SET_FREQUENCY, SAMPLE_RATE);
551 ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED);
553 /* wait for track info to load */
554 while (!*ci->taginfo_ready && !ci->stop_codec)
555 ci->yield();
557 codec_set_replaygain(ci->id3);
559 /* Read the entire file */
560 DEBUGF("SPC: request initial buffer\n");
561 ci->configure(CODEC_SET_FILEBUF_WATERMARK, ci->filesize);
563 ci->seek_buffer(0);
564 size_t buffersize;
565 uint8_t* buffer = ci->request_buffer(&buffersize, ci->filesize);
566 if (!buffer) {
567 goto codec_quit;
570 DEBUGF("SPC: read size = 0x%lx\n",(unsigned long)buffersize);
573 if (load_spc_buffer(buffer, buffersize)) {
574 DEBUGF("SPC load failure\n");
575 goto codec_quit;
578 LoadID666(buffer+0x2e);
580 if (ci->global_settings->repeat_mode!=REPEAT_ONE && ID666.length==0) {
581 ID666.length=3*60*1000; /* 3 minutes */
582 ID666.fade=5*1000; /* 5 seconds */
585 reset_profile_timers();
587 while ( play_track() );
589 print_timers(ci->id3->path);
591 while ( ci->request_next_track() );
593 stat = CODEC_OK;
595 codec_quit:
596 spc_emu_quit();
598 return stat;