diff options
Diffstat (limited to 'apps/codecs/spc.c')
-rw-r--r-- | apps/codecs/spc.c | 309 |
1 files changed, 276 insertions, 33 deletions
diff --git a/apps/codecs/spc.c b/apps/codecs/spc.c index f2890cd4a4..ae3026354d 100644 --- a/apps/codecs/spc.c +++ b/apps/codecs/spc.c | |||
@@ -185,12 +185,253 @@ static int LoadID666(unsigned char *buf) { | |||
185 | } | 185 | } |
186 | 186 | ||
187 | /**************** Codec ****************/ | 187 | /**************** Codec ****************/ |
188 | enum {SAMPLE_RATE = 32000}; | ||
189 | static struct Spc_Emu spc_emu IDATA_ATTR CACHEALIGN_ATTR; | ||
188 | 190 | ||
189 | static int32_t samples[WAV_CHUNK_SIZE*2] IBSS_ATTR; | 191 | #if SPC_DUAL_CORE |
192 | /** Implementations for pipelined dual-core operation **/ | ||
193 | static int spc_emu_thread_stack[DEFAULT_STACK_SIZE/sizeof(int)] | ||
194 | CACHEALIGN_ATTR; | ||
190 | 195 | ||
191 | static struct Spc_Emu spc_emu IDATA_ATTR; | 196 | static const unsigned char * const spc_emu_thread_name = "spc emu"; |
197 | static struct thread_entry *emu_thread_p; | ||
192 | 198 | ||
193 | enum {SAMPLE_RATE = 32000}; | 199 | enum |
200 | { | ||
201 | SPC_EMU_AUDIO = 0, | ||
202 | SPC_EMU_LOAD, | ||
203 | SPC_EMU_QUIT, | ||
204 | }; | ||
205 | |||
206 | struct spc_load | ||
207 | { | ||
208 | uint8_t *buf; | ||
209 | size_t size; | ||
210 | }; | ||
211 | |||
212 | /* sample queue */ | ||
213 | #define WAV_NUM_CHUNKS 2 | ||
214 | #define WAV_CHUNK_MASK (WAV_NUM_CHUNKS-1) | ||
215 | struct sample_queue_chunk | ||
216 | { | ||
217 | long id; | ||
218 | union | ||
219 | { | ||
220 | intptr_t data; | ||
221 | int32_t audio[WAV_CHUNK_SIZE*2]; | ||
222 | }; | ||
223 | }; | ||
224 | |||
225 | static struct | ||
226 | { | ||
227 | int head, tail; | ||
228 | struct semaphore emu_sem_head; | ||
229 | struct semaphore emu_sem_tail; | ||
230 | struct event emu_evt_reply; | ||
231 | intptr_t retval; | ||
232 | struct sample_queue_chunk wav_chunk[WAV_NUM_CHUNKS]; | ||
233 | } sample_queue NOCACHEBSS_ATTR; | ||
234 | |||
235 | static inline void samples_release_wrbuf(void) | ||
236 | { | ||
237 | sample_queue.tail++; | ||
238 | ci->semaphore_release(&sample_queue.emu_sem_head); | ||
239 | } | ||
240 | |||
241 | static inline struct sample_queue_chunk * samples_get_wrbuf(void) | ||
242 | { | ||
243 | ci->semaphore_wait(&sample_queue.emu_sem_tail); | ||
244 | return &sample_queue.wav_chunk[sample_queue.tail & WAV_CHUNK_MASK]; | ||
245 | } | ||
246 | |||
247 | static inline void samples_release_rdbuf(void) | ||
248 | { | ||
249 | if (sample_queue.head != sample_queue.tail) { | ||
250 | sample_queue.head++; | ||
251 | } | ||
252 | |||
253 | ci->semaphore_release(&sample_queue.emu_sem_tail); | ||
254 | } | ||
255 | |||
256 | static inline int32_t * samples_get_rdbuf(void) | ||
257 | { | ||
258 | ci->semaphore_wait(&sample_queue.emu_sem_head); | ||
259 | |||
260 | if (ci->stop_codec || ci->new_track) | ||
261 | { | ||
262 | /* Told to stop. Buffer must be released. */ | ||
263 | samples_release_rdbuf(); | ||
264 | return NULL; | ||
265 | } | ||
266 | |||
267 | return sample_queue.wav_chunk[sample_queue.head & WAV_CHUNK_MASK].audio; | ||
268 | } | ||
269 | |||
270 | static intptr_t emu_thread_send_msg(long id, intptr_t data) | ||
271 | { | ||
272 | struct sample_queue_chunk *chunk; | ||
273 | /* Grab an audio output buffer */ | ||
274 | ci->semaphore_wait(&sample_queue.emu_sem_head); | ||
275 | chunk = &sample_queue.wav_chunk[sample_queue.head & WAV_CHUNK_MASK]; | ||
276 | /* Place a message in it instead of audio */ | ||
277 | chunk->id = id; | ||
278 | chunk->data = data; | ||
279 | /* Release it to the emu thread */ | ||
280 | samples_release_rdbuf(); | ||
281 | /* Wait for a response */ | ||
282 | ci->event_wait(&sample_queue.emu_evt_reply, STATE_SIGNALED); | ||
283 | return sample_queue.retval; | ||
284 | } | ||
285 | |||
286 | /* thread function */ | ||
287 | static bool emu_thread_process_msg(struct sample_queue_chunk *chunk) | ||
288 | { | ||
289 | long id = chunk->id; | ||
290 | bool ret = id != SPC_EMU_QUIT; | ||
291 | |||
292 | chunk->id = SPC_EMU_AUDIO; /* Reset chunk type to audio */ | ||
293 | sample_queue.retval = 0; | ||
294 | |||
295 | if (id == SPC_EMU_LOAD) | ||
296 | { | ||
297 | struct spc_load *ld = (struct spc_load *)chunk->data; | ||
298 | invalidate_icache(); | ||
299 | SPC_Init(&spc_emu); | ||
300 | sample_queue.retval = SPC_load_spc(&spc_emu, ld->buf, ld->size); | ||
301 | } | ||
302 | |||
303 | /* Empty the audio queue */ | ||
304 | /* This is a dirty hack a timeout based wait would make unnescessary but | ||
305 | still safe because the other thread is known to be waiting for a reply | ||
306 | and is not using the objects. */ | ||
307 | ci->semaphore_init(&sample_queue.emu_sem_tail, 2, 2); | ||
308 | ci->semaphore_init(&sample_queue.emu_sem_head, 2, 0); | ||
309 | sample_queue.head = sample_queue.tail = 0; | ||
310 | ci->event_set_state(&sample_queue.emu_evt_reply, STATE_SIGNALED); | ||
311 | |||
312 | return ret; | ||
313 | } | ||
314 | |||
315 | static void spc_emu_thread(void) | ||
316 | { | ||
317 | CPU_Init(&spc_emu); | ||
318 | |||
319 | while (1) { | ||
320 | /* get a buffer for output */ | ||
321 | struct sample_queue_chunk *chunk = samples_get_wrbuf(); | ||
322 | |||
323 | if (chunk->id != SPC_EMU_AUDIO) { | ||
324 | /* This chunk doesn't contain audio but a command */ | ||
325 | if (!emu_thread_process_msg(chunk)) | ||
326 | break; | ||
327 | /* Have to re-get this pointer to keep semaphore counts correct */ | ||
328 | continue; | ||
329 | } | ||
330 | |||
331 | ENTER_TIMER(render); | ||
332 | /* fill samples buffer */ | ||
333 | if ( SPC_play(&spc_emu, WAV_CHUNK_SIZE*2, chunk->audio) ) | ||
334 | assert( false ); | ||
335 | EXIT_TIMER(render); | ||
336 | |||
337 | /* done so release it to output */ | ||
338 | samples_release_wrbuf(); | ||
339 | ci->yield(); | ||
340 | } | ||
341 | } | ||
342 | |||
343 | static bool spc_emu_start(void) | ||
344 | { | ||
345 | emu_thread_p = ci->create_thread(spc_emu_thread, spc_emu_thread_stack, | ||
346 | sizeof(spc_emu_thread_stack), CREATE_THREAD_FROZEN, | ||
347 | spc_emu_thread_name IF_PRIO(, PRIORITY_PLAYBACK), COP); | ||
348 | |||
349 | if (emu_thread_p == NULL) | ||
350 | return false; | ||
351 | |||
352 | /* Initialize audio queue as full to prevent emu thread from trying to run the | ||
353 | emulator before loading something */ | ||
354 | ci->event_init(&sample_queue.emu_evt_reply, | ||
355 | EVENT_AUTOMATIC | STATE_NONSIGNALED); | ||
356 | ci->semaphore_init(&sample_queue.emu_sem_tail, 2, 0); | ||
357 | ci->semaphore_init(&sample_queue.emu_sem_head, 2, 2); | ||
358 | sample_queue.head = 0; | ||
359 | sample_queue.tail = 2; | ||
360 | |||
361 | /* Start it running */ | ||
362 | ci->thread_thaw(emu_thread_p); | ||
363 | return true; | ||
364 | } | ||
365 | |||
366 | /* load a new program on the emu thread */ | ||
367 | static inline int load_spc_buffer(uint8_t *buf, size_t size) | ||
368 | { | ||
369 | struct spc_load ld = { buf, size }; | ||
370 | flush_icache(); | ||
371 | return emu_thread_send_msg(SPC_EMU_LOAD, (intptr_t)&ld); | ||
372 | } | ||
373 | |||
374 | static inline void spc_emu_quit(void) | ||
375 | { | ||
376 | emu_thread_send_msg(SPC_EMU_QUIT, 0); | ||
377 | /* Wait for emu thread to be killed */ | ||
378 | ci->thread_wait(emu_thread_p); | ||
379 | } | ||
380 | |||
381 | static inline bool spc_play_get_samples(int32_t **samples) | ||
382 | { | ||
383 | /* obtain filled samples buffer */ | ||
384 | *samples = samples_get_rdbuf(); | ||
385 | return *samples != NULL; | ||
386 | } | ||
387 | |||
388 | static inline void spc_play_send_samples(int32_t *samples) | ||
389 | { | ||
390 | ci->pcmbuf_insert(samples, samples+WAV_CHUNK_SIZE, WAV_CHUNK_SIZE); | ||
391 | /* done with chunk so release it to emu thread */ | ||
392 | samples_release_rdbuf(); | ||
393 | } | ||
394 | |||
395 | #else /* !SPC_DUAL_CORE */ | ||
396 | /** Implementations for single-core operation **/ | ||
397 | int32_t wav_chunk[WAV_CHUNK_SIZE*2] IBSS_ATTR; | ||
398 | |||
399 | /* load a new program into emu */ | ||
400 | static inline int load_spc_buffer(uint8_t *buf, size_t size) | ||
401 | { | ||
402 | SPC_Init(&spc_emu); | ||
403 | return SPC_load_spc(&spc_emu, buf, size); | ||
404 | } | ||
405 | |||
406 | static inline bool spc_emu_start(void) | ||
407 | { | ||
408 | #ifdef CPU_COLDFIRE | ||
409 | /* signed integer mode with saturation */ | ||
410 | coldfire_set_macsr(EMAC_SATURATE); | ||
411 | #endif | ||
412 | CPU_Init(&spc_emu); | ||
413 | return true; | ||
414 | } | ||
415 | |||
416 | static inline void spc_play_send_samples(int32_t *samples) | ||
417 | { | ||
418 | ci->pcmbuf_insert(samples, samples+WAV_CHUNK_SIZE, WAV_CHUNK_SIZE); | ||
419 | } | ||
420 | |||
421 | #define spc_emu_quit() | ||
422 | #define samples_release_rdbuf() | ||
423 | |||
424 | static inline bool spc_play_get_samples(int32_t **samples) | ||
425 | { | ||
426 | ENTER_TIMER(render); | ||
427 | /* fill samples buffer */ | ||
428 | if ( SPC_play(&spc_emu,WAV_CHUNK_SIZE*2,wav_chunk) ) | ||
429 | assert( false ); | ||
430 | EXIT_TIMER(render); | ||
431 | *samples = wav_chunk; | ||
432 | return true; | ||
433 | } | ||
434 | #endif /* SPC_DUAL_CORE */ | ||
194 | 435 | ||
195 | /* The main decoder loop */ | 436 | /* The main decoder loop */ |
196 | static int play_track( void ) | 437 | static int play_track( void ) |
@@ -206,7 +447,7 @@ static int play_track( void ) | |||
206 | fadedec=0x7fffffffl/(fadeendsample-fadestartsample)+1; | 447 | fadedec=0x7fffffffl/(fadeendsample-fadestartsample)+1; |
207 | 448 | ||
208 | ENTER_TIMER(total); | 449 | ENTER_TIMER(total); |
209 | 450 | ||
210 | while ( 1 ) | 451 | while ( 1 ) |
211 | { | 452 | { |
212 | ci->yield(); | 453 | ci->yield(); |
@@ -224,14 +465,12 @@ static int play_track( void ) | |||
224 | } | 465 | } |
225 | ci->seek_complete(); | 466 | ci->seek_complete(); |
226 | } | 467 | } |
227 | 468 | ||
228 | ENTER_TIMER(render); | 469 | int32_t *samples; |
229 | /* fill samples buffer */ | 470 | if (!spc_play_get_samples(&samples)) |
230 | if ( SPC_play(&spc_emu,WAV_CHUNK_SIZE*2,samples) ) | 471 | break; |
231 | assert( false ); | 472 | |
232 | EXIT_TIMER(render); | 473 | sampleswritten += WAV_CHUNK_SIZE; |
233 | |||
234 | sampleswritten+=WAV_CHUNK_SIZE; | ||
235 | 474 | ||
236 | /* is track timed? */ | 475 | /* is track timed? */ |
237 | if (ci->global_settings->repeat_mode!=REPEAT_ONE && ci->id3->length) { | 476 | if (ci->global_settings->repeat_mode!=REPEAT_ONE && ci->id3->length) { |
@@ -241,11 +480,11 @@ static int play_track( void ) | |||
241 | /* fade? */ | 480 | /* fade? */ |
242 | if (curtime>ID666.length) | 481 | if (curtime>ID666.length) |
243 | { | 482 | { |
244 | #ifdef CPU_COLDFIRE | 483 | #ifdef CPU_COLDFIRE |
245 | /* Have to switch modes to do this */ | 484 | /* Have to switch modes to do this */ |
246 | long macsr = coldfire_get_macsr(); | 485 | long macsr = coldfire_get_macsr(); |
247 | coldfire_set_macsr(EMAC_SATURATE | EMAC_FRACTIONAL | EMAC_ROUND); | 486 | coldfire_set_macsr(EMAC_SATURATE | EMAC_FRACTIONAL | EMAC_ROUND); |
248 | #endif | 487 | #endif |
249 | int i; | 488 | int i; |
250 | for (i=0;i<WAV_CHUNK_SIZE;i++) { | 489 | for (i=0;i<WAV_CHUNK_SIZE;i++) { |
251 | if (lasttimesample+i>fadestartsample) { | 490 | if (lasttimesample+i>fadestartsample) { |
@@ -256,42 +495,43 @@ static int play_track( void ) | |||
256 | fadevol-=fadedec; | 495 | fadevol-=fadedec; |
257 | } | 496 | } |
258 | } | 497 | } |
259 | #ifdef CPU_COLDFIRE | 498 | #ifdef CPU_COLDFIRE |
260 | coldfire_set_macsr(macsr); | 499 | coldfire_set_macsr(macsr); |
261 | #endif | 500 | #endif |
262 | } | 501 | } |
263 | /* end? */ | 502 | /* end? */ |
264 | if (lasttimesample>=fadeendsample) | 503 | if (lasttimesample>=fadeendsample) |
504 | { | ||
505 | samples_release_rdbuf(); | ||
265 | break; | 506 | break; |
507 | } | ||
266 | } | 508 | } |
267 | 509 | ||
268 | ci->pcmbuf_insert(samples, samples+WAV_CHUNK_SIZE, WAV_CHUNK_SIZE); | 510 | spc_play_send_samples(samples); |
269 | 511 | ||
270 | if (ci->global_settings->repeat_mode!=REPEAT_ONE) | 512 | if (ci->global_settings->repeat_mode!=REPEAT_ONE) |
271 | ci->set_elapsed(sampleswritten*1000LL/SAMPLE_RATE); | 513 | ci->set_elapsed(sampleswritten*1000LL/SAMPLE_RATE); |
272 | else | 514 | else |
273 | ci->set_elapsed(0); | 515 | ci->set_elapsed(0); |
274 | } | 516 | } |
275 | 517 | ||
276 | EXIT_TIMER(total); | 518 | EXIT_TIMER(total); |
277 | |||
278 | return 0; | 519 | return 0; |
279 | } | 520 | } |
280 | 521 | ||
281 | /* this is the codec entry point */ | 522 | /* this is the codec entry point */ |
282 | enum codec_status codec_main(void) | 523 | enum codec_status codec_main(void) |
283 | { | 524 | { |
284 | #ifdef CPU_COLDFIRE | 525 | enum codec_status stat = CODEC_ERROR; |
285 | /* signed integer mode with saturation */ | 526 | |
286 | coldfire_set_macsr(EMAC_SATURATE); | 527 | if (!spc_emu_start()) |
287 | #endif | 528 | goto codec_quit; |
288 | CPU_Init(&spc_emu); | ||
289 | 529 | ||
290 | do | 530 | do |
291 | { | 531 | { |
292 | DEBUGF("SPC: next_track\n"); | 532 | DEBUGF("SPC: next_track\n"); |
293 | if (codec_init()) { | 533 | if (codec_init()) { |
294 | return CODEC_ERROR; | 534 | goto codec_quit; |
295 | } | 535 | } |
296 | DEBUGF("SPC: after init\n"); | 536 | DEBUGF("SPC: after init\n"); |
297 | 537 | ||
@@ -301,7 +541,7 @@ enum codec_status codec_main(void) | |||
301 | 541 | ||
302 | /* wait for track info to load */ | 542 | /* wait for track info to load */ |
303 | while (!*ci->taginfo_ready && !ci->stop_codec) | 543 | while (!*ci->taginfo_ready && !ci->stop_codec) |
304 | ci->sleep(1); | 544 | ci->yield(); |
305 | 545 | ||
306 | codec_set_replaygain(ci->id3); | 546 | codec_set_replaygain(ci->id3); |
307 | 547 | ||
@@ -313,20 +553,19 @@ enum codec_status codec_main(void) | |||
313 | size_t buffersize; | 553 | size_t buffersize; |
314 | uint8_t* buffer = ci->request_buffer(&buffersize, ci->filesize); | 554 | uint8_t* buffer = ci->request_buffer(&buffersize, ci->filesize); |
315 | if (!buffer) { | 555 | if (!buffer) { |
316 | return CODEC_ERROR; | 556 | goto codec_quit; |
317 | } | 557 | } |
318 | 558 | ||
319 | DEBUGF("SPC: read size = 0x%lx\n",(unsigned long)buffersize); | 559 | DEBUGF("SPC: read size = 0x%lx\n",(unsigned long)buffersize); |
320 | do | 560 | do |
321 | { | 561 | { |
322 | SPC_Init(&spc_emu); | 562 | if (load_spc_buffer(buffer, buffersize)) { |
323 | if (SPC_load_spc(&spc_emu,buffer,buffersize)) { | ||
324 | DEBUGF("SPC load failure\n"); | 563 | DEBUGF("SPC load failure\n"); |
325 | return CODEC_ERROR; | 564 | goto codec_quit; |
326 | } | 565 | } |
327 | 566 | ||
328 | LoadID666(buffer+0x2e); | 567 | LoadID666(buffer+0x2e); |
329 | 568 | ||
330 | if (ci->global_settings->repeat_mode!=REPEAT_ONE && ID666.length==0) { | 569 | if (ci->global_settings->repeat_mode!=REPEAT_ONE && ID666.length==0) { |
331 | ID666.length=3*60*1000; /* 3 minutes */ | 570 | ID666.length=3*60*1000; /* 3 minutes */ |
332 | ID666.fade=5*1000; /* 5 seconds */ | 571 | ID666.fade=5*1000; /* 5 seconds */ |
@@ -340,12 +579,16 @@ enum codec_status codec_main(void) | |||
340 | 579 | ||
341 | reset_profile_timers(); | 580 | reset_profile_timers(); |
342 | } | 581 | } |
343 | |||
344 | while ( play_track() ); | 582 | while ( play_track() ); |
345 | 583 | ||
346 | print_timers(ci->id3->path); | 584 | print_timers(ci->id3->path); |
347 | } | 585 | } |
348 | while ( ci->request_next_track() ); | 586 | while ( ci->request_next_track() ); |
587 | |||
588 | stat = CODEC_OK; | ||
589 | |||
590 | codec_quit: | ||
591 | spc_emu_quit(); | ||
349 | 592 | ||
350 | return CODEC_OK; | 593 | return stat; |
351 | } | 594 | } |