summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/pcmbuf.c92
-rw-r--r--apps/pcmbuf.h10
-rw-r--r--apps/playback.c561
-rw-r--r--apps/playback.h2
-rw-r--r--apps/talk.c3
-rw-r--r--firmware/SOURCES4
-rw-r--r--firmware/common/memswap128.c44
-rw-r--r--firmware/include/memory.h11
-rw-r--r--firmware/target/arm/memswap128-arm.S44
-rw-r--r--firmware/target/coldfire/memswap128-coldfire.S50
10 files changed, 579 insertions, 242 deletions
diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c
index 568cc7f49e..89f9e27798 100644
--- a/apps/pcmbuf.c
+++ b/apps/pcmbuf.c
@@ -58,7 +58,10 @@ struct pcmbufdesc
58 void (*callback)(void); 58 void (*callback)(void);
59}; 59};
60 60
61#define PCMBUF_DESCS(bufsize) ((bufsize) / PCMBUF_MINAVG_CHUNK) 61#define PCMBUF_DESCS(bufsize) \
62 ((bufsize) / PCMBUF_MINAVG_CHUNK)
63#define PCMBUF_DESCS_SIZE(bufsize) \
64 (PCMBUF_DESCS(bufsize)*sizeof(struct pcmbufdesc))
62 65
63/* Size of the PCM buffer. */ 66/* Size of the PCM buffer. */
64static size_t pcmbuf_size IDATA_ATTR = 0; 67static size_t pcmbuf_size IDATA_ATTR = 0;
@@ -76,6 +79,7 @@ static void (*position_callback)(size_t size) IDATA_ATTR;
76 79
77/* Crossfade related state */ 80/* Crossfade related state */
78static bool crossfade_enabled; 81static bool crossfade_enabled;
82static bool crossfade_enabled_pending;
79static bool crossfade_mixmode; 83static bool crossfade_mixmode;
80static bool crossfade_active IDATA_ATTR; 84static bool crossfade_active IDATA_ATTR;
81static bool crossfade_init IDATA_ATTR; 85static bool crossfade_init IDATA_ATTR;
@@ -187,9 +191,13 @@ void pcmbuf_set_position_callback(void (*callback)(size_t size))
187 position_callback = callback; 191 position_callback = callback;
188} 192}
189 193
190static void pcmbuf_set_watermark_bytes(size_t numbytes) 194static void pcmbuf_set_watermark_bytes(void)
191{ 195{
192 pcmbuf_watermark = numbytes; 196 pcmbuf_watermark = (crossfade_enabled && pcmbuf_size) ?
197 /* If crossfading, try to keep the buffer full other than 1 second */
198 (pcmbuf_size - (NATIVE_FREQUENCY * 4 * 1)) :
199 /* Otherwise, just keep it above 2 second */
200 PCMBUF_WATERMARK;
193} 201}
194 202
195/* This is really just part of pcmbuf_flush_fillpos, but is easier to keep 203/* This is really just part of pcmbuf_flush_fillpos, but is easier to keep
@@ -413,30 +421,60 @@ static void pcmbuf_init_pcmbuffers(void) {
413 } 421 }
414} 422}
415 423
416bool pcmbuf_is_same_size(size_t bufsize) 424static size_t pcmbuf_get_next_required_pcmbuf_size(void)
417{ 425{
418 /* keep calculations synced with pcmbuf_init */ 426#if MEM > 1
419 bufsize += PCMBUF_MIX_CHUNK * 2 + 427 size_t seconds = 1;
420 PCMBUF_DESCS(bufsize)*sizeof(struct pcmbufdesc); 428
421 return bufsize == (size_t)(pcmbuf_bufend - audiobuffer); 429 if (crossfade_enabled_pending)
430 seconds += global_settings.crossfade_fade_out_delay
431 + global_settings.crossfade_fade_out_duration;
432
433 /* Buffer has to be at least 2s long. */
434 seconds += 2;
435 logf("pcmbuf len: %ld", seconds);
436 return seconds * (NATIVE_FREQUENCY*4);
437#else
438 return NATIVE_FREQUENCY*2;
439#endif
440}
441
442static char *pcmbuf_calc_audiobuffer_ptr(size_t bufsize)
443{
444 return pcmbuf_bufend - (bufsize + PCMBUF_MIX_CHUNK * 2 +
445 PCMBUF_DESCS_SIZE(bufsize));
446}
447
448bool pcmbuf_is_same_size(void)
449{
450 if (audiobuffer == NULL)
451 return true; /* Not set up yet even once so always */
452
453 size_t bufsize = pcmbuf_get_next_required_pcmbuf_size();
454 return pcmbuf_calc_audiobuffer_ptr(bufsize) == audiobuffer;
422} 455}
423 456
424/* Initialize the pcmbuffer the structure looks like this: 457/* Initialize the pcmbuffer the structure looks like this:
425 * ...|---------PCMBUF---------|FADEBUF|VOICEBUF|DESCS|... */ 458 * ...|---------PCMBUF---------|FADEBUF|VOICEBUF|DESCS|... */
426size_t pcmbuf_init(size_t bufsize, char *bufend) 459size_t pcmbuf_init(unsigned char *bufend)
427{ 460{
428 pcmbuf_size = bufsize;
429 pcmbuf_bufend = bufend; 461 pcmbuf_bufend = bufend;
430 pcmbuf_descsize = pcmbuf_descs()*sizeof(struct pcmbufdesc); 462 pcmbuf_size = pcmbuf_get_next_required_pcmbuf_size();
431 audiobuffer = pcmbuf_bufend - (pcmbuf_size + PCMBUF_MIX_CHUNK * 2 463 audiobuffer = pcmbuf_calc_audiobuffer_ptr(pcmbuf_size);
432 + pcmbuf_descsize);
433 fadebuf = &audiobuffer[pcmbuf_size]; 464 fadebuf = &audiobuffer[pcmbuf_size];
434 voicebuf = &fadebuf[PCMBUF_MIX_CHUNK]; 465 voicebuf = &fadebuf[PCMBUF_MIX_CHUNK];
435 pcmbuf_write = (struct pcmbufdesc *)&voicebuf[PCMBUF_MIX_CHUNK]; 466 pcmbuf_write = (struct pcmbufdesc *)&voicebuf[PCMBUF_MIX_CHUNK];
467
468 pcmbuf_descsize = PCMBUF_DESCS_SIZE(pcmbuf_size);
436 pcmbuf_init_pcmbuffers(); 469 pcmbuf_init_pcmbuffers();
470
437 position_callback = NULL; 471 position_callback = NULL;
438 pcmbuf_event_handler = NULL; 472 pcmbuf_event_handler = NULL;
473
474 pcmbuf_crossfade_enable_finished();
475
439 pcmbuf_play_stop(); 476 pcmbuf_play_stop();
477
440 return pcmbuf_bufend - audiobuffer; 478 return pcmbuf_bufend - audiobuffer;
441} 479}
442 480
@@ -445,6 +483,14 @@ size_t pcmbuf_get_bufsize(void)
445 return pcmbuf_size; 483 return pcmbuf_size;
446} 484}
447 485
486#ifdef ROCKBOX_HAS_LOGF
487unsigned char * pcmbuf_get_meminfo(size_t *length)
488{
489 *length = pcmbuf_bufend - audiobuffer;
490 return audiobuffer;
491}
492#endif
493
448void pcmbuf_pause(bool pause) { 494void pcmbuf_pause(bool pause) {
449#ifdef PCMBUF_MUTING 495#ifdef PCMBUF_MUTING
450 if (pause) 496 if (pause)
@@ -1036,15 +1082,18 @@ void pcmbuf_mix_voice(int count)
1036 1082
1037void pcmbuf_crossfade_enable(bool on_off) 1083void pcmbuf_crossfade_enable(bool on_off)
1038{ 1084{
1039 crossfade_enabled = on_off; 1085#if MEM > 1
1086 /* Next setting to be used, not applied now */
1087 crossfade_enabled_pending = on_off;
1088#endif
1089 (void)on_off;
1090}
1040 1091
1041 if (crossfade_enabled) { 1092void pcmbuf_crossfade_enable_finished(void)
1042 /* If crossfading, try to keep the buffer full other than 1 second */ 1093{
1043 pcmbuf_set_watermark_bytes(pcmbuf_size - (NATIVE_FREQUENCY * 4 * 1)); 1094 /* Copy the pending setting over now */
1044 } else { 1095 crossfade_enabled = crossfade_enabled_pending;
1045 /* Otherwise, just keep it above 2 second */ 1096 pcmbuf_set_watermark_bytes();
1046 pcmbuf_set_watermark_bytes(PCMBUF_WATERMARK);
1047 }
1048} 1097}
1049 1098
1050bool pcmbuf_is_crossfade_enabled(void) 1099bool pcmbuf_is_crossfade_enabled(void)
@@ -1054,4 +1103,3 @@ bool pcmbuf_is_crossfade_enabled(void)
1054 1103
1055 return crossfade_enabled; 1104 return crossfade_enabled;
1056} 1105}
1057
diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h
index 5c35ecc291..bddc7bb93d 100644
--- a/apps/pcmbuf.h
+++ b/apps/pcmbuf.h
@@ -37,10 +37,14 @@
37 for mixing (crossfade or voice) */ 37 for mixing (crossfade or voice) */
38 38
39/* Returns true if the buffer needs to change size */ 39/* Returns true if the buffer needs to change size */
40bool pcmbuf_is_same_size(size_t bufsize); 40bool pcmbuf_is_same_size(void);
41size_t pcmbuf_init(size_t bufsize, char *bufend); 41size_t pcmbuf_init(unsigned char *bufend);
42/* Size in bytes used by the pcmbuffer */ 42/* Size in bytes used by the pcmbuffer */
43size_t pcmbuf_get_bufsize(void); 43size_t pcmbuf_get_bufsize(void);
44#ifdef ROCKBOX_HAS_LOGF
45/* just used for logging for now */
46unsigned char * pcmbuf_get_meminfo(size_t *length);
47#endif
44size_t get_pcmbuf_descsize(void); 48size_t get_pcmbuf_descsize(void);
45 49
46void pcmbuf_pause(bool pause); 50void pcmbuf_pause(bool pause);
@@ -68,7 +72,7 @@ void* pcmbuf_request_buffer(int *count);
68void* pcmbuf_request_voice_buffer(int *count, bool mix); 72void* pcmbuf_request_voice_buffer(int *count, bool mix);
69bool pcmbuf_is_crossfade_enabled(void); 73bool pcmbuf_is_crossfade_enabled(void);
70void pcmbuf_crossfade_enable(bool on_off); 74void pcmbuf_crossfade_enable(bool on_off);
71 75void pcmbuf_crossfade_enable_finished(void);
72int pcmbuf_usage(void); 76int pcmbuf_usage(void);
73int pcmbuf_mix_free(void); 77int pcmbuf_mix_free(void);
74void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude); 78void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude);
diff --git a/apps/playback.c b/apps/playback.c
index 9676a107d0..ef525a5776 100644
--- a/apps/playback.c
+++ b/apps/playback.c
@@ -34,6 +34,8 @@
34#include "system.h" 34#include "system.h"
35#include "thread.h" 35#include "thread.h"
36#include "file.h" 36#include "file.h"
37#include "panic.h"
38#include "memory.h"
37#include "lcd.h" 39#include "lcd.h"
38#include "font.h" 40#include "font.h"
39#include "button.h" 41#include "button.h"
@@ -167,14 +169,14 @@ enum {
167 169
168/* As defined in plugin.lds */ 170/* As defined in plugin.lds */
169#if defined(CPU_PP) 171#if defined(CPU_PP)
170#define CODEC_IRAM_ORIGIN 0x4000c000 172#define CODEC_IRAM_ORIGIN ((unsigned char *)0x4000c000)
171#define CODEC_IRAM_SIZE 0xc000 173#define CODEC_IRAM_SIZE ((size_t)0xc000)
172#elif defined(IAUDIO_X5) || defined(IAUDIO_M5) 174#elif defined(IAUDIO_X5) || defined(IAUDIO_M5)
173#define CODEC_IRAM_ORIGIN 0x10010000 175#define CODEC_IRAM_ORIGIN ((unsigned char *)0x10010000)
174#define CODEC_IRAM_SIZE 0x10000 176#define CODEC_IRAM_SIZE ((size_t)0x10000)
175#else 177#else
176#define CODEC_IRAM_ORIGIN 0x1000c000 178#define CODEC_IRAM_ORIGIN ((unsigned char *)0x1000c000)
177#define CODEC_IRAM_SIZE 0xc000 179#define CODEC_IRAM_SIZE ((size_t)0xc000)
178#endif 180#endif
179 181
180#ifndef IBSS_ATTR_VOICE_STACK 182#ifndef IBSS_ATTR_VOICE_STACK
@@ -205,7 +207,7 @@ static volatile size_t buf_widx IDATA_ATTR = 0; /* Buffer write position (A/C-)
205 207
206/* Possible arrangements of the buffer */ 208/* Possible arrangements of the buffer */
207#define BUFFER_STATE_TRASHED -1 /* trashed; must be reset */ 209#define BUFFER_STATE_TRASHED -1 /* trashed; must be reset */
208#define BUFFER_STATE_NORMAL 0 /* voice+audio OR audio-only */ 210#define BUFFER_STATE_INITIALIZED 0 /* voice+audio OR audio-only */
209#define BUFFER_STATE_VOICED_ONLY 1 /* voice-only */ 211#define BUFFER_STATE_VOICED_ONLY 1 /* voice-only */
210static int buffer_state = BUFFER_STATE_TRASHED; /* Buffer state */ 212static int buffer_state = BUFFER_STATE_TRASHED; /* Buffer state */
211 213
@@ -282,7 +284,7 @@ static const char audio_thread_name[] = "audio";
282static void audio_thread(void); 284static void audio_thread(void);
283static void audio_initiate_track_change(long direction); 285static void audio_initiate_track_change(long direction);
284static bool audio_have_tracks(void); 286static bool audio_have_tracks(void);
285static void audio_reset_buffer(size_t pcmbufsize); 287static void audio_reset_buffer(void);
286 288
287/* Codec thread */ 289/* Codec thread */
288extern struct codec_api ci; 290extern struct codec_api ci;
@@ -315,10 +317,16 @@ static unsigned char sim_iram[CODEC_IRAM_SIZE];
315#define CODEC_IRAM_ORIGIN sim_iram 317#define CODEC_IRAM_ORIGIN sim_iram
316#endif 318#endif
317 319
318/* Pointer to IRAM buffers for normal/voice codecs */ 320/* iram_buf and dram_buf are either both NULL or both non-NULL */
319static unsigned char *iram_buf[2] = { NULL, NULL }; 321/* Pointer to IRAM buffer for codec swapping */
320/* Pointer to DRAM buffers for normal/voice codecs */ 322static unsigned char *iram_buf = NULL;
321static unsigned char *dram_buf[2] = { NULL, NULL }; 323/* Pointer to DRAM buffer for codec swapping */
324static unsigned char *dram_buf = NULL;
325/* Parity of swap_codec calls - needed because one codec swapping itself in
326 automatically swaps in the other and the swap when unlocking should not
327 happen if the parity is even.
328 */
329static bool swap_codec_parity = false; /* true=odd, false=even */
322/* Mutex to control which codec (normal/voice) is running */ 330/* Mutex to control which codec (normal/voice) is running */
323static struct mutex mutex_codecthread NOCACHEBSS_ATTR; 331static struct mutex mutex_codecthread NOCACHEBSS_ATTR;
324 332
@@ -342,6 +350,7 @@ struct voice_info {
342 char *buf; 350 char *buf;
343}; 351};
344static void voice_thread(void); 352static void voice_thread(void);
353static void voice_stop(void);
345 354
346#endif /* PLAYBACK_VOICE */ 355#endif /* PLAYBACK_VOICE */
347 356
@@ -404,7 +413,7 @@ void mpeg_id3_options(bool _v1first)
404static void wait_for_voice_swap_in(void) 413static void wait_for_voice_swap_in(void)
405{ 414{
406#ifdef PLAYBACK_VOICE 415#ifdef PLAYBACK_VOICE
407 if (NULL == iram_buf[CODEC_IDX_VOICE]) 416 if (NULL == iram_buf)
408 return; 417 return;
409 418
410 while (current_codec != CODEC_IDX_VOICE) 419 while (current_codec != CODEC_IDX_VOICE)
@@ -426,7 +435,8 @@ unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size)
426 435
427 if (buffer_size == NULL) 436 if (buffer_size == NULL)
428 { 437 {
429 /* Special case for talk_init to use */ 438 /* Special case for talk_init to use since it already knows it's
439 trashed */
430 buffer_state = BUFFER_STATE_TRASHED; 440 buffer_state = BUFFER_STATE_TRASHED;
431 return NULL; 441 return NULL;
432 } 442 }
@@ -434,8 +444,15 @@ unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size)
434 if (talk_buf || buffer_state == BUFFER_STATE_TRASHED 444 if (talk_buf || buffer_state == BUFFER_STATE_TRASHED
435 || !talk_voice_required()) 445 || !talk_voice_required())
436 { 446 {
437 logf("get buffer: talk_buf"); 447 logf("get buffer: talk, audio");
438 /* ok to use everything from audiobuf to audiobufend */ 448 /* Ok to use everything from audiobuf to audiobufend - voice is loaded,
449 the talk buffer is not needed because voice isn't being used, or
450 could be BUFFER_STATE_TRASHED already. If state is
451 BUFFER_STATE_VOICED_ONLY, no problem as long as memory isn't written
452 without the caller knowing what's going on. Changing certain settings
453 may move it to a worse condition but the memory in use by something
454 else will remain undisturbed.
455 */
439 if (buffer_state != BUFFER_STATE_TRASHED) 456 if (buffer_state != BUFFER_STATE_TRASHED)
440 { 457 {
441 talk_buffer_steal(); 458 talk_buffer_steal();
@@ -447,10 +464,14 @@ unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size)
447 } 464 }
448 else 465 else
449 { 466 {
450 /* skip talk buffer and move pcm buffer to end */ 467 /* Safe to just return this if already BUFFER_STATE_VOICED_ONLY or
451 logf("get buffer: voice"); 468 still BUFFER_STATE_INITIALIZED */
469 /* Skip talk buffer and move pcm buffer to end to maximize available
470 contiguous memory - no audio running means voice will not need the
471 swap space */
472 logf("get buffer: audio");
452 buf = audiobuf + talk_get_bufsize(); 473 buf = audiobuf + talk_get_bufsize();
453 end = audiobufend - pcmbuf_init(pcmbuf_get_bufsize(), audiobufend); 474 end = audiobufend - pcmbuf_init(audiobufend);
454 buffer_state = BUFFER_STATE_VOICED_ONLY; 475 buffer_state = BUFFER_STATE_VOICED_ONLY;
455 } 476 }
456 477
@@ -466,18 +487,22 @@ void audio_iram_steal(void)
466 audio_stop(); 487 audio_stop();
467 488
468#ifdef PLAYBACK_VOICE 489#ifdef PLAYBACK_VOICE
469 if (NULL != iram_buf[CODEC_IDX_VOICE]) 490 if (NULL != iram_buf)
470 { 491 {
471 /* Can't already be stolen */ 492 /* Can't already be stolen */
472 if (voice_iram_stolen) 493 if (voice_iram_stolen)
473 return; 494 return;
474 495
496 /* Must wait for voice to be current again if it is swapped which
497 would cause the caller's buffer to get clobbered when voice locks
498 and runs - we'll wait for it to lock and yield again then make sure
499 the ride has come to a complete stop */
475 wait_for_voice_swap_in(); 500 wait_for_voice_swap_in();
476 voice_stop(); 501 voice_stop();
477 502
478 /* Save voice IRAM - safe to do here since state is known */ 503 /* Save voice IRAM but just memcpy - safe to do here since voice
479 memcpy(iram_buf[CODEC_IDX_VOICE], (void *)CODEC_IRAM_ORIGIN, 504 is current and no audio codec is loaded */
480 CODEC_IRAM_SIZE); 505 memcpy(iram_buf, CODEC_IRAM_ORIGIN, CODEC_IRAM_SIZE);
481 voice_iram_stolen = true; 506 voice_iram_stolen = true;
482 } 507 }
483 else 508 else
@@ -492,21 +517,23 @@ void audio_iram_steal(void)
492#ifdef HAVE_RECORDING 517#ifdef HAVE_RECORDING
493unsigned char *audio_get_recording_buffer(size_t *buffer_size) 518unsigned char *audio_get_recording_buffer(size_t *buffer_size)
494{ 519{
495 /* don't allow overwrite of voice swap area or we'll trash the 520 /* Don't allow overwrite of voice swap area or we'll trash the
496 swapped-out voice codec but can use whole thing if none */ 521 swapped-out voice codec but can use whole thing if none */
497 unsigned char *end; 522 unsigned char *end;
498 523
524 /* Stop audio and voice. Wait for voice to swap in and be clear
525 of pending events to ensure trouble-free operation of encoders */
499 audio_stop(); 526 audio_stop();
500 wait_for_voice_swap_in(); 527 wait_for_voice_swap_in();
501 voice_stop(); 528 voice_stop();
502 talk_buffer_steal(); 529 talk_buffer_steal();
503 530
504#ifdef PLAYBACK_VOICE 531#ifdef PLAYBACK_VOICE
505#ifdef IRAM_STEAL 532 /* If no dram_buf, swap space not used and recording gets more
506 end = dram_buf[CODEC_IDX_VOICE]; 533 memory. Codec swap areas will remain unaffected by the next init
507#else 534 since they're allocated at the end of the buffer and their sizes
508 end = iram_buf[CODEC_IDX_VOICE]; 535 don't change between calls */
509#endif /* IRAM_STEAL */ 536 end = dram_buf;
510 if (NULL == end) 537 if (NULL == end)
511#endif /* PLAYBACK_VOICE */ 538#endif /* PLAYBACK_VOICE */
512 end = audiobufend; 539 end = audiobufend;
@@ -781,101 +808,51 @@ void audio_set_buffer_margin(int setting)
781{ 808{
782 static const int lookup[] = {5, 15, 30, 60, 120, 180, 300, 600}; 809 static const int lookup[] = {5, 15, 30, 60, 120, 180, 300, 600};
783 buffer_margin = lookup[setting]; 810 buffer_margin = lookup[setting];
784 logf("buffer margin: %ds", buffer_margin); 811 logf("buffer margin: %ld", buffer_margin);
785 set_filebuf_watermark(buffer_margin); 812 set_filebuf_watermark(buffer_margin);
786} 813}
787 814
788/* Set crossfade & PCM buffer length. */ 815/* Take nescessary steps to enable or disable the crossfade setting */
789void audio_set_crossfade(int enable) 816void audio_set_crossfade(int enable)
790{ 817{
818 size_t offset;
819 bool was_playing;
791 size_t size; 820 size_t size;
792 bool was_playing = (playing && audio_is_initialized);
793 size_t offset = 0;
794#if MEM > 1
795 int seconds = 1;
796#endif
797 821
798 if (!filebuf) 822 /* Tell it the next setting to use */
799 return; /* Audio buffers not yet set up */ 823 pcmbuf_crossfade_enable(enable);
800 824
801#if MEM > 1 825 /* Return if size hasn't changed or this is too early to determine
802 if (enable) 826 which in the second case there's no way we could be playing
803 seconds = global_settings.crossfade_fade_out_delay 827 anything at all */
804 + global_settings.crossfade_fade_out_duration; 828 if (pcmbuf_is_same_size())
805 829 {
806 /* Buffer has to be at least 2s long. */ 830 /* This function is a copout and just syncs some variables -
807 seconds += 2; 831 to be removed at a later date */
808 logf("buf len: %d", seconds); 832 pcmbuf_crossfade_enable_finished();
809 size = seconds * (NATIVE_FREQUENCY*4); 833 return;
810#else 834 }
811 enable = 0;
812 size = NATIVE_FREQUENCY*2;
813#endif
814 if (buffer_state == BUFFER_STATE_NORMAL && pcmbuf_is_same_size(size))
815 return ;
816 835
836 offset = 0;
837 was_playing = playing;
838
839 /* Playback has to be stopped before changing the buffer size */
817 if (was_playing) 840 if (was_playing)
818 { 841 {
819 /* Store the track resume position */ 842 /* Store the track resume position */
820 offset = CUR_TI->id3.offset; 843 offset = CUR_TI->id3.offset;
821 844 gui_syncsplash(0, str(LANG_RESTARTING_PLAYBACK));
822 /* Playback has to be stopped before changing the buffer size. */
823 gui_syncsplash(0, (char *)str(LANG_RESTARTING_PLAYBACK));
824 audio_stop();
825 } 845 }
826 846
827 voice_stop(); 847 /* Blast it - audio buffer will have to be setup again next time
828 848 something plays */
829 /* Re-initialize audio system. */ 849 audio_get_buffer(true, &size);
830 audio_reset_buffer(size);
831 pcmbuf_crossfade_enable(enable);
832 logf("abuf:%dB", pcmbuf_get_bufsize());
833 logf("fbuf:%dB", filebuflen);
834
835 voice_init();
836 850
837 /* Restart playback. */ 851 /* Restart playback if audio was running previously */
838 if (was_playing) 852 if (was_playing)
839 audio_play(offset); 853 audio_play(offset);
840} 854}
841 855
842void voice_init(void)
843{
844#ifdef PLAYBACK_VOICE
845 if (voice_thread_p || !filebuf || voice_codec_loaded ||
846 !talk_voice_required())
847 return;
848
849 logf("Starting voice codec");
850 queue_init(&voice_queue, true);
851 voice_thread_p = create_thread(voice_thread, voice_stack,
852 sizeof(voice_stack), voice_thread_name
853 IF_PRIO(, PRIORITY_PLAYBACK) IF_COP(, CPU, false));
854
855 while (!voice_codec_loaded)
856 yield();
857#endif
858} /* voice_init */
859
860void voice_stop(void)
861{
862#ifdef PLAYBACK_VOICE
863 /* Messages should not be posted to voice codec queue unless it is the
864 current codec or deadlocks happen. */
865 if (current_codec != CODEC_IDX_VOICE)
866 return;
867
868 LOGFQUEUE("mp3 > voice Q_VOICE_STOP");
869 queue_post(&voice_queue, Q_VOICE_STOP, 0);
870 while (voice_is_playing || !queue_empty(&voice_queue))
871 yield();
872 if (!playing)
873 pcmbuf_play_stop();
874#endif
875} /* voice_stop */
876
877
878
879/* --- Routines called from multiple threads --- */ 856/* --- Routines called from multiple threads --- */
880static void set_current_codec(int codec_idx) 857static void set_current_codec(int codec_idx)
881{ 858{
@@ -886,35 +863,61 @@ static void set_current_codec(int codec_idx)
886#ifdef PLAYBACK_VOICE 863#ifdef PLAYBACK_VOICE
887static void swap_codec(void) 864static void swap_codec(void)
888{ 865{
889 int my_codec = current_codec; 866 int my_codec;
890 867
891 logf("swapping out codec:%d", my_codec); 868 /* Swap nothing if no swap buffers exist */
892 869 if (dram_buf == NULL)
893 /* Save our current IRAM and DRAM */
894#ifdef IRAM_STEAL
895 if (voice_iram_stolen)
896 { 870 {
897 logf("swap: iram restore"); 871 logf("swap: no swap buffers");
898 voice_iram_stolen = false; 872 return;
899 /* Don't swap trashed data into buffer - _should_ always be the case
900 if voice_iram_stolen is true since the voice has been swapped in
901 before hand */
902 if (my_codec == CODEC_IDX_VOICE)
903 goto skip_iram_swap;
904 } 873 }
874
875 my_codec = current_codec;
876
877 logf("swapping out codec: %d", my_codec);
878
879 /* Invert this when a codec thread enters and leaves */
880 swap_codec_parity = !swap_codec_parity;
881
882 /* If this is true, an odd number of calls has occurred and there's
883 no codec thread waiting to swap us out when it locks and runs. This
884 occurs when playback is stopped or when just starting playback and
885 the audio thread is loading a codec; parities should always be even
886 on entry when a thread calls this during playback */
887 if (swap_codec_parity)
888 {
889 /* Save our current IRAM and DRAM */
890#ifdef IRAM_STEAL
891 if (voice_iram_stolen)
892 {
893 logf("swap: iram restore");
894 voice_iram_stolen = false;
895 /* Don't swap trashed data into buffer as the voice IRAM will
896 already be swapped out - should _always_ be the case if
897 voice_iram_stolen is true since the voice has been swapped
898 in beforehand */
899 if (my_codec == CODEC_IDX_VOICE)
900 {
901 logf("voice iram already swapped");
902 goto skip_iram_swap;
903 }
904 }
905#endif 905#endif
906 906
907 memcpy(iram_buf[my_codec], (unsigned char *)CODEC_IRAM_ORIGIN, 907 memswap128(iram_buf, CODEC_IRAM_ORIGIN, CODEC_IRAM_SIZE);
908 CODEC_IRAM_SIZE);
909 908
910#ifdef IRAM_STEAL 909#ifdef IRAM_STEAL
911skip_iram_swap: 910 skip_iram_swap:
912#endif 911#endif
913 912
914 memcpy(dram_buf[my_codec], codecbuf, CODEC_SIZE); 913 memswap128(dram_buf, codecbuf, CODEC_SIZE);
914 /* No cache invalidation needed; it will be done in codec_load_ram
915 or we won't be here otherwise */
916 }
915 917
916 /* Release my semaphore */ 918 /* Release my semaphore */
917 mutex_unlock(&mutex_codecthread); 919 mutex_unlock(&mutex_codecthread);
920 logf("unlocked: %d", my_codec);
918 921
919 /* Loop until the other codec has locked and run */ 922 /* Loop until the other codec has locked and run */
920 do { 923 do {
@@ -923,28 +926,57 @@ skip_iram_swap:
923 } while (my_codec == current_codec); 926 } while (my_codec == current_codec);
924 927
925 /* Wait for other codec to unlock */ 928 /* Wait for other codec to unlock */
929 /* FIXME: We need some sort of timed boost cancellation here or the CPU
930 doesn't unboost during playback when the voice codec goes back to
931 waiting - recall that mutex_lock calls block_thread which is an
932 indefinite wait that doesn't cancel the thread's CPU boost */
926 mutex_lock(&mutex_codecthread); 933 mutex_lock(&mutex_codecthread);
927 934
928 /* Take control */ 935 /* Take control */
936 logf("waiting for lock: %d", my_codec);
929 set_current_codec(my_codec); 937 set_current_codec(my_codec);
930 938
931 /* Reload our IRAM and DRAM */ 939 /* Reload our IRAM and DRAM */
932 memcpy((unsigned char *)CODEC_IRAM_ORIGIN, iram_buf[my_codec], 940 memswap128(iram_buf, CODEC_IRAM_ORIGIN, CODEC_IRAM_SIZE);
933 CODEC_IRAM_SIZE); 941 memswap128(dram_buf, codecbuf, CODEC_SIZE);
934 invalidate_icache(); 942 invalidate_icache();
935 memcpy(codecbuf, dram_buf[my_codec], CODEC_SIZE);
936 943
937 logf("resuming codec:%d", my_codec); 944 /* Flip parity again */
945 swap_codec_parity = !swap_codec_parity;
946
947 logf("resuming codec: %d", my_codec);
938} 948}
949
950/* This function is meant to be used by the buffer stealing functions to
951 ensure the codec is no longer active and so voice will be swapped-in
952 before it is called */
953static void voice_stop(void)
954{
955#ifdef PLAYBACK_VOICE
956 /* Must have a voice codec loaded or we'll hang forever here */
957 if (!voice_codec_loaded)
958 return;
959
960 LOGFQUEUE("mp3 > voice Q_VOICE_STOP");
961 queue_post(&voice_queue, Q_VOICE_STOP, 0);
962
963 /* Loop until voice empties it's queue, stops and picks up on the new
964 track; the voice thread must be stopped and waiting for messages
965 outside the codec */
966 while (voice_is_playing || !queue_empty(&voice_queue) ||
967 ci_voice.new_track)
968 yield();
969
970 if (!playing)
971 pcmbuf_play_stop();
939#endif 972#endif
973} /* voice_stop */
974#endif /* PLAYBACK_VOICE */
940 975
941static void set_filebuf_watermark(int seconds) 976static void set_filebuf_watermark(int seconds)
942{ 977{
943 size_t bytes; 978 size_t bytes;
944 979
945 if (current_codec == CODEC_IDX_VOICE)
946 return;
947
948 if (!filebuf) 980 if (!filebuf)
949 return; /* Audio buffers not yet set up */ 981 return; /* Audio buffers not yet set up */
950 982
@@ -1058,6 +1090,14 @@ static void voice_set_offset_callback(size_t value)
1058 (void)value; 1090 (void)value;
1059} 1091}
1060 1092
1093static void voice_configure_callback(int setting, intptr_t value)
1094{
1095 if (!dsp_configure(setting, value))
1096 {
1097 logf("Illegal key:%d", setting);
1098 }
1099}
1100
1061static size_t voice_filebuf_callback(void *ptr, size_t size) 1101static size_t voice_filebuf_callback(void *ptr, size_t size)
1062{ 1102{
1063 (void)ptr; 1103 (void)ptr;
@@ -1066,6 +1106,34 @@ static size_t voice_filebuf_callback(void *ptr, size_t size)
1066 return 0; 1106 return 0;
1067} 1107}
1068 1108
1109/* Handle Q_VOICE_STOP and part of SYS_USB_CONNECTED */
1110static bool voice_on_voice_stop(bool aborting, size_t *realsize)
1111{
1112 if (aborting && !playing && pcm_is_playing())
1113 {
1114 /* Aborting: Slight hack - flush PCM buffer if
1115 only being used for voice */
1116 pcmbuf_play_stop();
1117 }
1118
1119 if (voice_is_playing)
1120 {
1121 /* Clear the current buffer */
1122 voice_is_playing = false;
1123 voice_getmore = NULL;
1124 voice_remaining = 0;
1125 voicebuf = NULL;
1126
1127 /* Force the codec to think it's changing tracks */
1128 ci_voice.new_track = 1;
1129
1130 *realsize = 0;
1131 return true; /* Yes, change tracks */
1132 }
1133
1134 return false;
1135}
1136
1069static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize) 1137static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize)
1070{ 1138{
1071 struct event ev; 1139 struct event ev;
@@ -1079,15 +1147,21 @@ static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize)
1079 while (1) 1147 while (1)
1080 { 1148 {
1081 if (voice_is_playing || playing) 1149 if (voice_is_playing || playing)
1150 {
1082 queue_wait_w_tmo(&voice_queue, &ev, 0); 1151 queue_wait_w_tmo(&voice_queue, &ev, 0);
1152 if (!voice_is_playing && ev.id == SYS_TIMEOUT)
1153 ev.id = Q_AUDIO_PLAY;
1154 }
1083 else 1155 else
1156 {
1084 /* We must use queue_wait_w_tmo() because queue_wait() doesn't 1157 /* We must use queue_wait_w_tmo() because queue_wait() doesn't
1085 unboost the CPU */ 1158 unboost the CPU */
1086 queue_wait_w_tmo(&voice_queue, &ev, INT_MAX); 1159 /* FIXME: when long timeouts work correctly max out the the timeout
1087 if (!voice_is_playing) 1160 (we'll still need the timeout guard here) or an infinite timeout
1088 { 1161 can unboost, use that */
1089 if (ev.id == SYS_TIMEOUT) 1162 do
1090 ev.id = Q_AUDIO_PLAY; 1163 queue_wait_w_tmo(&voice_queue, &ev, HZ*5);
1164 while (ev.id == SYS_TIMEOUT); /* Fake infinite wait */
1091 } 1165 }
1092 1166
1093 switch (ev.id) { 1167 switch (ev.id) {
@@ -1110,35 +1184,27 @@ static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize)
1110 1184
1111 case Q_VOICE_STOP: 1185 case Q_VOICE_STOP:
1112 LOGFQUEUE("voice < Q_VOICE_STOP"); 1186 LOGFQUEUE("voice < Q_VOICE_STOP");
1113 if (ev.data == 1 && !playing && pcm_is_playing()) 1187 if (voice_on_voice_stop(ev.data, realsize))
1114 {
1115 /* Aborting: Slight hack - flush PCM buffer if
1116 only being used for voice */
1117 pcmbuf_play_stop();
1118 }
1119 if (voice_is_playing)
1120 {
1121 /* Clear the current buffer */
1122 voice_is_playing = false;
1123 voice_getmore = NULL;
1124 voice_remaining = 0;
1125 voicebuf = NULL;
1126
1127 /* Force the codec to think it's changing tracks */
1128 ci_voice.new_track = 1;
1129 *realsize = 0;
1130 return NULL; 1188 return NULL;
1131 } 1189 break;
1132 else
1133 break;
1134 1190
1135 case SYS_USB_CONNECTED: 1191 case SYS_USB_CONNECTED:
1192 {
1136 LOGFQUEUE("voice < SYS_USB_CONNECTED"); 1193 LOGFQUEUE("voice < SYS_USB_CONNECTED");
1137 usb_acknowledge(SYS_USB_CONNECTED_ACK); 1194 bool change_tracks = voice_on_voice_stop(ev.data, realsize);
1195 /* Voice is obviously current so let us swap ourselves away if
1196 playing so audio may stop itself - audio_codec_loaded can
1197 only be true in this case if we're here even if the codec
1198 is only about to load */
1138 if (audio_codec_loaded) 1199 if (audio_codec_loaded)
1139 swap_codec(); 1200 swap_codec();
1201 /* Playback should be finished by now - ack and wait */
1202 usb_acknowledge(SYS_USB_CONNECTED_ACK);
1140 usb_wait_for_disconnect(&voice_queue); 1203 usb_wait_for_disconnect(&voice_queue);
1204 if (change_tracks)
1205 return NULL;
1141 break; 1206 break;
1207 }
1142 1208
1143 case Q_VOICE_PLAY: 1209 case Q_VOICE_PLAY:
1144 LOGFQUEUE("voice < Q_VOICE_PLAY"); 1210 LOGFQUEUE("voice < Q_VOICE_PLAY");
@@ -1149,17 +1215,17 @@ static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize)
1149#ifdef IRAM_STEAL 1215#ifdef IRAM_STEAL
1150 if (voice_iram_stolen) 1216 if (voice_iram_stolen)
1151 { 1217 {
1218 /* Voice is the first to run again and is currently
1219 loaded */
1152 logf("voice: iram restore"); 1220 logf("voice: iram restore");
1153 memcpy((void*)CODEC_IRAM_ORIGIN, 1221 memcpy(CODEC_IRAM_ORIGIN, iram_buf, CODEC_IRAM_SIZE);
1154 iram_buf[CODEC_IDX_VOICE],
1155 CODEC_IRAM_SIZE);
1156 voice_iram_stolen = false; 1222 voice_iram_stolen = false;
1157 } 1223 }
1158#endif 1224#endif
1159 /* must reset the buffer before any playback 1225 /* Must reset the buffer before any playback begins if
1160 begins if needed */ 1226 needed */
1161 if (buffer_state == BUFFER_STATE_TRASHED) 1227 if (buffer_state == BUFFER_STATE_TRASHED)
1162 audio_reset_buffer(pcmbuf_get_bufsize()); 1228 audio_reset_buffer();
1163 1229
1164 voice_is_playing = true; 1230 voice_is_playing = true;
1165 trigger_cpu_boost(); 1231 trigger_cpu_boost();
@@ -1168,7 +1234,7 @@ static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize)
1168 voicebuf = voice_data->buf; 1234 voicebuf = voice_data->buf;
1169 voice_getmore = voice_data->callback; 1235 voice_getmore = voice_data->callback;
1170 } 1236 }
1171 goto voice_play_clip; 1237 goto voice_play_clip; /* To exit both switch and while */
1172 1238
1173 case SYS_TIMEOUT: 1239 case SYS_TIMEOUT:
1174 LOGFQUEUE_SYS_TIMEOUT("voice < SYS_TIMEOUT"); 1240 LOGFQUEUE_SYS_TIMEOUT("voice < SYS_TIMEOUT");
@@ -1254,11 +1320,14 @@ static void voice_thread(void)
1254 voice_remaining = 0; 1320 voice_remaining = 0;
1255 voice_getmore = NULL; 1321 voice_getmore = NULL;
1256 1322
1323 /* FIXME: If we being starting the voice thread without reboot, the
1324 voice_queue could be full of old stuff and we must flush it. */
1257 codec_load_file(get_codec_filename(AFMT_MPA_L3), &ci_voice); 1325 codec_load_file(get_codec_filename(AFMT_MPA_L3), &ci_voice);
1258 1326
1259 logf("Voice codec finished"); 1327 logf("Voice codec finished");
1260 voice_codec_loaded = false; 1328 voice_codec_loaded = false;
1261 mutex_unlock(&mutex_codecthread); 1329 mutex_unlock(&mutex_codecthread);
1330 voice_thread_p = NULL;
1262 remove_thread(NULL); 1331 remove_thread(NULL);
1263} /* voice_thread */ 1332} /* voice_thread */
1264 1333
@@ -1898,7 +1967,8 @@ static void codec_thread(void)
1898 LOGFQUEUE("codec < Q_CODEC_LOAD_DISK"); 1967 LOGFQUEUE("codec < Q_CODEC_LOAD_DISK");
1899 audio_codec_loaded = true; 1968 audio_codec_loaded = true;
1900#ifdef PLAYBACK_VOICE 1969#ifdef PLAYBACK_VOICE
1901 /* Don't sent messages to voice codec if it's not current */ 1970 /* Don't sent messages to voice codec if it's already swapped
1971 out or it will never get this */
1902 if (voice_codec_loaded && current_codec == CODEC_IDX_VOICE) 1972 if (voice_codec_loaded && current_codec == CODEC_IDX_VOICE)
1903 { 1973 {
1904 LOGFQUEUE("codec > voice Q_AUDIO_PLAY"); 1974 LOGFQUEUE("codec > voice Q_AUDIO_PLAY");
@@ -2346,7 +2416,7 @@ strip_ape_tag:
2346 /* Skip APE tag */ 2416 /* Skip APE tag */
2347 if (FILEBUFUSED > len) 2417 if (FILEBUFUSED > len)
2348 { 2418 {
2349 logf("Skipping APE tag (%dB)", len); 2419 logf("Skipping APE tag (%ldB)", len);
2350 buf_widx = RINGBUF_SUB(buf_widx, len); 2420 buf_widx = RINGBUF_SUB(buf_widx, len);
2351 tracks[track_widx].available -= len; 2421 tracks[track_widx].available -= len;
2352 tracks[track_widx].filesize -= len; 2422 tracks[track_widx].filesize -= len;
@@ -2388,7 +2458,7 @@ static bool audio_read_file(size_t minimum)
2388 2458
2389 if (rc < 0) 2459 if (rc < 0)
2390 { 2460 {
2391 logf("File ended %dB early", tracks[track_widx].filerem); 2461 logf("File ended %ldB early", tracks[track_widx].filerem);
2392 tracks[track_widx].filesize -= tracks[track_widx].filerem; 2462 tracks[track_widx].filesize -= tracks[track_widx].filerem;
2393 tracks[track_widx].filerem = 0; 2463 tracks[track_widx].filerem = 0;
2394 break; 2464 break;
@@ -2408,7 +2478,7 @@ static bool audio_read_file(size_t minimum)
2408 2478
2409 if ((unsigned)rc > tracks[track_widx].filerem) 2479 if ((unsigned)rc > tracks[track_widx].filerem)
2410 { 2480 {
2411 logf("Bad: rc-filerem=%d, fixing", rc-tracks[track_widx].filerem); 2481 logf("Bad: rc-filerem=%ld, fixing", rc-tracks[track_widx].filerem);
2412 tracks[track_widx].filesize += rc - tracks[track_widx].filerem; 2482 tracks[track_widx].filesize += rc - tracks[track_widx].filerem;
2413 tracks[track_widx].filerem = rc; 2483 tracks[track_widx].filerem = rc;
2414 } 2484 }
@@ -2440,7 +2510,7 @@ static bool audio_read_file(size_t minimum)
2440 2510
2441 if (tracks[track_widx].filerem == 0) 2511 if (tracks[track_widx].filerem == 0)
2442 { 2512 {
2443 logf("Finished buf:%dB", tracks[track_widx].filesize); 2513 logf("Finished buf:%ldB", tracks[track_widx].filesize);
2444 close(current_fd); 2514 close(current_fd);
2445 current_fd = -1; 2515 current_fd = -1;
2446 audio_strip_tags(); 2516 audio_strip_tags();
@@ -2453,7 +2523,7 @@ static bool audio_read_file(size_t minimum)
2453 } 2523 }
2454 else 2524 else
2455 { 2525 {
2456 logf("%s buf:%dB", ret_val?"Quick":"Partially", 2526 logf("%s buf:%ldB", ret_val?"Quick":"Partially",
2457 tracks[track_widx].filesize - tracks[track_widx].filerem); 2527 tracks[track_widx].filesize - tracks[track_widx].filerem);
2458 return ret_val; 2528 return ret_val;
2459 } 2529 }
@@ -2548,7 +2618,7 @@ static bool audio_loadcodec(bool start_play)
2548 tracks[track_widx].has_codec = true; 2618 tracks[track_widx].has_codec = true;
2549 2619
2550 close(fd); 2620 close(fd);
2551 logf("Done: %dB", size); 2621 logf("Done: %ldB", size);
2552 2622
2553 return true; 2623 return true;
2554} 2624}
@@ -2901,9 +2971,11 @@ static void audio_fill_file_buffer(
2901 bool had_next_track = audio_next_track() != NULL; 2971 bool had_next_track = audio_next_track() != NULL;
2902 bool continue_buffering; 2972 bool continue_buffering;
2903 2973
2904 /* must reset the buffer before use if trashed */ 2974 /* Must reset the buffer before use if trashed or voice only - voice
2905 if (buffer_state != BUFFER_STATE_NORMAL) 2975 file size shouldn't have changed so we can go straight from
2906 audio_reset_buffer(pcmbuf_get_bufsize()); 2976 BUFFER_STATE_VOICED_ONLY to BUFFER_STATE_INITIALIZED */
2977 if (buffer_state != BUFFER_STATE_INITIALIZED)
2978 audio_reset_buffer();
2907 2979
2908 if (!audio_initialize_buffer_fill(!start_play)) 2980 if (!audio_initialize_buffer_fill(!start_play))
2909 return ; 2981 return ;
@@ -3342,16 +3414,13 @@ static void audio_initiate_dir_change(long direction)
3342} 3414}
3343 3415
3344/* 3416/*
3345 * Layout audio buffer as follows: 3417 * Layout audio buffer as follows - iram buffer depends on target:
3346 * [|TALK]|MALLOC|FILE|GUARD|PCM|AUDIOCODEC|[VOICECODEC|] 3418 * [|SWAP:iram][|TALK]|MALLOC|FILE|GUARD|PCM|[SWAP:dram[|iram]|]
3347 */ 3419 */
3348static void audio_reset_buffer(size_t pcmbufsize) 3420static void audio_reset_buffer(void)
3349{ 3421{
3350 /* see audio_get_recording_buffer if this is modified */ 3422 /* see audio_get_recording_buffer if this is modified */
3351 size_t offset;
3352
3353 logf("audio_reset_buffer"); 3423 logf("audio_reset_buffer");
3354 logf(" size:%08X", pcmbufsize);
3355 3424
3356 /* If the setup of anything allocated before the file buffer is 3425 /* If the setup of anything allocated before the file buffer is
3357 changed, do check the adjustments after the buffer_alloc call 3426 changed, do check the adjustments after the buffer_alloc call
@@ -3362,19 +3431,34 @@ static void audio_reset_buffer(size_t pcmbufsize)
3362 /* Align the malloc buf to line size. Especially important to cf 3431 /* Align the malloc buf to line size. Especially important to cf
3363 targets that do line reads/writes. */ 3432 targets that do line reads/writes. */
3364 malloc_buf = (unsigned char *)(((uintptr_t)malloc_buf + 15) & ~15); 3433 malloc_buf = (unsigned char *)(((uintptr_t)malloc_buf + 15) & ~15);
3365 filebuf = malloc_buf + MALLOC_BUFSIZE; 3434 filebuf = malloc_buf + MALLOC_BUFSIZE; /* filebuf line align implied */
3366 filebuflen = audiobufend - filebuf; 3435 filebuflen = audiobufend - filebuf;
3367 3436
3368 /* Allow for codec(s) at end of audio buffer */ 3437 /* Allow for codec swap space at end of audio buffer */
3369 if (talk_voice_required()) 3438 if (talk_voice_required())
3370 { 3439 {
3440 /* Layout of swap buffer:
3441 * #ifdef IRAM_STEAL (dedicated iram_buf):
3442 * |iram_buf|...audiobuf...|dram_buf|audiobufend
3443 * #else:
3444 * audiobuf...|dram_buf|iram_buf|audiobufend
3445 */
3371#ifdef PLAYBACK_VOICE 3446#ifdef PLAYBACK_VOICE
3447 /* Check for an absolutely nasty situation which should never,
3448 ever happen - frankly should just panic */
3449 if (voice_codec_loaded && current_codec != CODEC_IDX_VOICE)
3450 {
3451 logf("buffer reset with voice swapped");
3452 }
3453 /* line align length which line aligns the calculations below since
3454 all sizes are also at least line aligned - needed for memswap128 */
3455 filebuflen &= ~15;
3372#ifdef IRAM_STEAL 3456#ifdef IRAM_STEAL
3373 filebuflen -= CODEC_IRAM_SIZE + 2*CODEC_SIZE; 3457 filebuflen -= CODEC_SIZE;
3374#else 3458#else
3375 filebuflen -= 2*(CODEC_IRAM_SIZE + CODEC_SIZE); 3459 filebuflen -= CODEC_SIZE + CODEC_IRAM_SIZE;
3376#endif 3460#endif
3377 /* Allow 2 codecs at end of audio buffer */ 3461 /* Allocate buffers for swapping voice <=> audio */
3378 /* If using IRAM for plugins voice IRAM swap buffer must be dedicated 3462 /* If using IRAM for plugins voice IRAM swap buffer must be dedicated
3379 and out of the way of buffer usage or else a call to audio_get_buffer 3463 and out of the way of buffer usage or else a call to audio_get_buffer
3380 and subsequent buffer use might trash the swap space. A plugin 3464 and subsequent buffer use might trash the swap space. A plugin
@@ -3383,56 +3467,77 @@ static void audio_reset_buffer(size_t pcmbufsize)
3383 has been obtained already or never allowing use of the voice IRAM 3467 has been obtained already or never allowing use of the voice IRAM
3384 buffer within the audio buffer. Using buffer_alloc basically 3468 buffer within the audio buffer. Using buffer_alloc basically
3385 implements the second in a more convenient way. */ 3469 implements the second in a more convenient way. */
3386 iram_buf[CODEC_IDX_AUDIO] = filebuf + filebuflen; 3470 dram_buf = filebuf + filebuflen;
3387 dram_buf[CODEC_IDX_AUDIO] = iram_buf[CODEC_IDX_AUDIO] + CODEC_IRAM_SIZE;
3388 3471
3389#ifdef IRAM_STEAL 3472#ifdef IRAM_STEAL
3390 /* Allocate voice IRAM swap buffer once */ 3473 /* Allocate voice IRAM swap buffer once */
3391 if (iram_buf[CODEC_IDX_VOICE] == NULL) 3474 if (iram_buf == NULL)
3392 { 3475 {
3393 iram_buf[CODEC_IDX_VOICE] = buffer_alloc(CODEC_IRAM_SIZE); 3476 iram_buf = buffer_alloc(CODEC_IRAM_SIZE);
3394 /* buffer_alloc moves audiobuf; this is safe because only the end 3477 /* buffer_alloc moves audiobuf; this is safe because only the end
3395 * has been touched so far in this function and the address of 3478 * has been touched so far in this function and the address of
3396 * filebuf + filebuflen is not changed */ 3479 * filebuf + filebuflen is not changed */
3397 malloc_buf += CODEC_IRAM_SIZE; 3480 malloc_buf += CODEC_IRAM_SIZE;
3398 filebuf += CODEC_IRAM_SIZE; 3481 filebuf += CODEC_IRAM_SIZE;
3399 filebuflen -= CODEC_IRAM_SIZE; 3482 filebuflen -= CODEC_IRAM_SIZE;
3400 } 3483 }
3401 dram_buf[CODEC_IDX_VOICE] = dram_buf[CODEC_IDX_AUDIO] + CODEC_SIZE;
3402#else 3484#else
3403 iram_buf[CODEC_IDX_VOICE] = dram_buf[CODEC_IDX_AUDIO] + CODEC_SIZE; 3485 /* Allocate iram_buf after dram_buf */
3404 dram_buf[CODEC_IDX_VOICE] = iram_buf[CODEC_IDX_VOICE] + CODEC_IRAM_SIZE; 3486 iram_buf = dram_buf + CODEC_SIZE;
3405#endif /* IRAM_STEAL */ 3487#endif /* IRAM_STEAL */
3406
3407#endif /* PLAYBACK_VOICE */ 3488#endif /* PLAYBACK_VOICE */
3408 } 3489 }
3409 else 3490 else
3410 { 3491 {
3411#ifdef PLAYBACK_VOICE 3492#ifdef PLAYBACK_VOICE
3412 /* Allow for 1 codec at end of audio buffer */ 3493 /* No swap buffers needed */
3413 filebuflen -= CODEC_IRAM_SIZE + CODEC_SIZE; 3494 iram_buf = NULL;
3414 3495 dram_buf = NULL;
3415 iram_buf[CODEC_IDX_AUDIO] = filebuf + filebuflen;
3416 dram_buf[CODEC_IDX_AUDIO] = iram_buf[CODEC_IDX_AUDIO] + CODEC_IRAM_SIZE;
3417 iram_buf[CODEC_IDX_VOICE] = NULL;
3418 dram_buf[CODEC_IDX_VOICE] = NULL;
3419#endif 3496#endif
3420 } 3497 }
3421 3498
3422 filebuflen -= pcmbuf_init(pcmbufsize, filebuf + filebuflen) + GUARD_BUFSIZE; 3499 /* Subtract whatever the pcm buffer says it used plus the guard buffer */
3500 filebuflen -= pcmbuf_init(filebuf + filebuflen) + GUARD_BUFSIZE;
3423 3501
3424 /* Ensure that file buffer is aligned */ 3502 /* Make sure filebuflen is a longword multiple after adjustment - filebuf
3425 offset = -(size_t)filebuf & 3; 3503 will already be line aligned */
3426 filebuf += offset;
3427 filebuflen -= offset;
3428 filebuflen &= ~3; 3504 filebuflen &= ~3;
3429 3505
3506 /* Set the high watermark as 75% full...or 25% empty :) */
3430#if MEM > 8 3507#if MEM > 8
3431 high_watermark = (3*filebuflen)/4; 3508 high_watermark = 3*filebuflen / 4;
3432#endif 3509#endif
3433 3510
3434 /* Clear any references to the file buffer */ 3511 /* Clear any references to the file buffer */
3435 buffer_state = BUFFER_STATE_NORMAL; 3512 buffer_state = BUFFER_STATE_INITIALIZED;
3513
3514#ifdef ROCKBOX_HAS_LOGF
3515 /* Make sure everything adds up - yes, some info is a bit redundant but
3516 aids viewing and the sumation of certain variables should add up to
3517 the location of others. */
3518 {
3519 size_t pcmbufsize;
3520 unsigned char * pcmbuf = pcmbuf_get_meminfo(&pcmbufsize);
3521 logf("mabuf: %08X", (unsigned)malloc_buf);
3522 logf("mabufe: %08X", (unsigned)(malloc_buf + MALLOC_BUFSIZE));
3523 logf("fbuf: %08X", (unsigned)filebuf);
3524 logf("fbufe: %08X", (unsigned)(filebuf + filebuflen));
3525 logf("gbuf: %08X", (unsigned)(filebuf + filebuflen));
3526 logf("gbufe: %08X", (unsigned)(filebuf + filebuflen + GUARD_BUFSIZE));
3527 logf("pcmb: %08X", (unsigned)pcmbuf);
3528 logf("pcmbe: %08X", (unsigned)(pcmbuf + pcmbufsize));
3529 if (dram_buf)
3530 {
3531 logf("dramb: %08X", (unsigned)dram_buf);
3532 logf("drambe: %08X", (unsigned)(dram_buf + CODEC_SIZE));
3533 }
3534 if (iram_buf)
3535 {
3536 logf("iramb: %08X", (unsigned)iram_buf);
3537 logf("irambe: %08X", (unsigned)(iram_buf + CODEC_IRAM_SIZE));
3538 }
3539 }
3540#endif
3436} 3541}
3437 3542
3438#if MEM > 8 3543#if MEM > 8
@@ -3454,6 +3559,16 @@ static void audio_thread(void)
3454#ifdef PLAYBACK_VOICE 3559#ifdef PLAYBACK_VOICE
3455 /* Unlock mutex that init stage locks before creating this thread */ 3560 /* Unlock mutex that init stage locks before creating this thread */
3456 mutex_unlock(&mutex_codecthread); 3561 mutex_unlock(&mutex_codecthread);
3562
3563 /* Buffers must be set up by now - should panic - really */
3564 if (buffer_state != BUFFER_STATE_INITIALIZED)
3565 {
3566 logf("audio_thread start: no buffer");
3567 }
3568
3569 /* Have to wait for voice to load up or else the codec swap will be
3570 invalid when an audio codec is loaded */
3571 wait_for_voice_swap_in();
3457#endif 3572#endif
3458 3573
3459 while (1) 3574 while (1)
@@ -3604,12 +3719,14 @@ void audio_init(void)
3604 static struct mp3entry id3_voice; 3719 static struct mp3entry id3_voice;
3605#endif 3720#endif
3606 3721
3607 logf("audio: %s", audio_is_initialized ?
3608 "initializing" : "already initialized");
3609
3610 /* Can never do this twice */ 3722 /* Can never do this twice */
3611 if (audio_is_initialized) 3723 if (audio_is_initialized)
3724 {
3725 logf("audio: already initialized");
3612 return; 3726 return;
3727 }
3728
3729 logf("audio: initializing");
3613 3730
3614 /* Initialize queues before giving control elsewhere in case it likes 3731 /* Initialize queues before giving control elsewhere in case it likes
3615 to send messages. Thread creation will be delayed however so nothing 3732 to send messages. Thread creation will be delayed however so nothing
@@ -3620,6 +3737,7 @@ void audio_init(void)
3620 hardware is initialized - audio thread unlocks it after final init 3737 hardware is initialized - audio thread unlocks it after final init
3621 stage */ 3738 stage */
3622 mutex_lock(&mutex_codecthread); 3739 mutex_lock(&mutex_codecthread);
3740 queue_init(&voice_queue, true);
3623#endif 3741#endif
3624 queue_init(&audio_queue, true); 3742 queue_init(&audio_queue, true);
3625 queue_enable_queue_send(&audio_queue, &audio_queue_sender_list); 3743 queue_enable_queue_send(&audio_queue, &audio_queue_sender_list);
@@ -3663,6 +3781,7 @@ void audio_init(void)
3663 ci_voice.seek_complete = voice_do_nothing; 3781 ci_voice.seek_complete = voice_do_nothing;
3664 ci_voice.set_elapsed = voice_set_elapsed_callback; 3782 ci_voice.set_elapsed = voice_set_elapsed_callback;
3665 ci_voice.set_offset = voice_set_offset_callback; 3783 ci_voice.set_offset = voice_set_offset_callback;
3784 ci_voice.configure = voice_configure_callback;
3666 ci_voice.discard_codec = voice_do_nothing; 3785 ci_voice.discard_codec = voice_do_nothing;
3667 ci_voice.taginfo_ready = &voicetagtrue; 3786 ci_voice.taginfo_ready = &voicetagtrue;
3668 ci_voice.id3 = &id3_voice; 3787 ci_voice.id3 = &id3_voice;
@@ -3671,12 +3790,14 @@ void audio_init(void)
3671#endif 3790#endif
3672 3791
3673 /* initialize the buffer */ 3792 /* initialize the buffer */
3674 filebuf = audiobuf; /* must be non-NULL for audio_set_crossfade */ 3793 filebuf = audiobuf;
3675 3794
3676 /* audio_reset_buffer must to know the size of voice buffer so init 3795 /* audio_reset_buffer must to know the size of voice buffer so init
3677 voice first */ 3796 talk first */
3678 talk_init(); 3797 talk_init();
3679 3798
3799 /* Create the threads late now that we shouldn't be yielding again before
3800 returning */
3680 codec_thread_p = create_thread( 3801 codec_thread_p = create_thread(
3681 codec_thread, codec_stack, sizeof(codec_stack), 3802 codec_thread, codec_stack, sizeof(codec_stack),
3682 codec_thread_name IF_PRIO(, PRIORITY_PLAYBACK) 3803 codec_thread_name IF_PRIO(, PRIORITY_PLAYBACK)
@@ -3686,8 +3807,24 @@ void audio_init(void)
3686 audio_thread_name IF_PRIO(, PRIORITY_BUFFERING) 3807 audio_thread_name IF_PRIO(, PRIORITY_BUFFERING)
3687 IF_COP(, CPU, false)); 3808 IF_COP(, CPU, false));
3688 3809
3689 audio_set_crossfade(global_settings.crossfade); 3810#ifdef PLAYBACK_VOICE
3811 /* TODO: Change this around when various speech codecs can be used */
3812 if (talk_voice_required())
3813 {
3814 logf("Starting voice codec");
3815 create_thread(voice_thread, voice_stack,
3816 sizeof(voice_stack), voice_thread_name
3817 IF_PRIO(, PRIORITY_PLAYBACK) IF_COP(, CPU, false));
3818 }
3819#endif
3820
3821 /* Set crossfade setting for next buffer init which should be about... */
3822 pcmbuf_crossfade_enable(global_settings.crossfade);
3823
3824 /* ...now! Set up the buffers */
3825 audio_reset_buffer();
3690 3826
3827 /* Probably safe to say */
3691 audio_is_initialized = true; 3828 audio_is_initialized = true;
3692 3829
3693 sound_settings_apply(); 3830 sound_settings_apply();
diff --git a/apps/playback.h b/apps/playback.h
index 82179f1ee2..cf7547ec26 100644
--- a/apps/playback.h
+++ b/apps/playback.h
@@ -64,8 +64,6 @@ void audio_set_track_buffer_event(void (*handler)(struct mp3entry *id3,
64 bool last_track)); 64 bool last_track));
65void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3, 65void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3,
66 bool last_track)); 66 bool last_track));
67void voice_init(void);
68void voice_stop(void);
69 67
70#if CONFIG_CODEC == SWCODEC /* This #ifdef is better here than gui/gwps.c */ 68#if CONFIG_CODEC == SWCODEC /* This #ifdef is better here than gui/gwps.c */
71extern void audio_next_dir(void); 69extern void audio_next_dir(void);
diff --git a/apps/talk.c b/apps/talk.c
index f975ca2028..04e37394b6 100644
--- a/apps/talk.c
+++ b/apps/talk.c
@@ -528,9 +528,6 @@ void talk_init(void)
528 if (has_voicefile) 528 if (has_voicefile)
529 { 529 {
530 voicefile_size = filesize(filehandle); 530 voicefile_size = filesize(filehandle);
531#if CONFIG_CODEC == SWCODEC
532 voice_init();
533#endif
534 close(filehandle); /* close again, this was just to detect presence */ 531 close(filehandle); /* close again, this was just to detect presence */
535 filehandle = -1; 532 filehandle = -1;
536 } 533 }
diff --git a/firmware/SOURCES b/firmware/SOURCES
index 549e4af286..974326570a 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -248,6 +248,8 @@ target/coldfire/crt0.S
248target/coldfire/memcpy-coldfire.S 248target/coldfire/memcpy-coldfire.S
249target/coldfire/memmove-coldfire.S 249target/coldfire/memmove-coldfire.S
250target/coldfire/memset-coldfire.S 250target/coldfire/memset-coldfire.S
251target/coldfire/memswap128-coldfire.S
252common/memswap128.c
251#if defined(HAVE_LCD_COLOR) \ 253#if defined(HAVE_LCD_COLOR) \
252 || defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_PIXELFORMAT == VERTICAL_INTERLEAVED) 254 || defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_PIXELFORMAT == VERTICAL_INTERLEAVED)
253target/coldfire/memset16-coldfire.S 255target/coldfire/memset16-coldfire.S
@@ -269,6 +271,7 @@ common/strlen.c
269#ifndef SIMULATOR 271#ifndef SIMULATOR
270target/arm/memset-arm.S 272target/arm/memset-arm.S
271target/arm/memset16-arm.S 273target/arm/memset16-arm.S
274target/arm/memswap128-arm.S
272#if CONFIG_I2C == I2C_PP5020 || CONFIG_I2C == I2C_PP5002 275#if CONFIG_I2C == I2C_PP5020 || CONFIG_I2C == I2C_PP5002
273target/arm/i2c-pp.c 276target/arm/i2c-pp.c
274#elif CONFIG_I2C == I2C_PNX0101 277#elif CONFIG_I2C == I2C_PNX0101
@@ -295,6 +298,7 @@ common/memcpy.c
295common/memmove.c 298common/memmove.c
296common/memset.c 299common/memset.c
297common/memset16.c 300common/memset16.c
301common/memswap128.c
298common/strlen.c 302common/strlen.c
299#ifndef SIMULATOR 303#ifndef SIMULATOR
300crt0.S 304crt0.S
diff --git a/firmware/common/memswap128.c b/firmware/common/memswap128.c
new file mode 100644
index 0000000000..af1fe157b6
--- /dev/null
+++ b/firmware/common/memswap128.c
@@ -0,0 +1,44 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007 by Michael Sevakis
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19#include "config.h"
20#include <string.h>
21#include <inttypes.h>
22
23void memswap128(void *a, void *b, size_t len)
24{
25 for (len >>= 4; len > 0; len--, a += 16, b += 16)
26 {
27 int32_t a0 = *((int32_t *)a + 0);
28 int32_t a1 = *((int32_t *)a + 1);
29 int32_t a2 = *((int32_t *)a + 2);
30 int32_t a3 = *((int32_t *)a + 3);
31 int32_t b0 = *((int32_t *)b + 0);
32 int32_t b1 = *((int32_t *)b + 1);
33 int32_t b2 = *((int32_t *)b + 2);
34 int32_t b3 = *((int32_t *)b + 3);
35 *((int32_t *)b + 0) = a0;
36 *((int32_t *)b + 1) = a1;
37 *((int32_t *)b + 2) = a2;
38 *((int32_t *)b + 3) = a3;
39 *((int32_t *)a + 0) = b0;
40 *((int32_t *)a + 1) = b1;
41 *((int32_t *)a + 2) = b2;
42 *((int32_t *)a + 3) = b3;
43 }
44}
diff --git a/firmware/include/memory.h b/firmware/include/memory.h
index 559c6ed96a..75bcb98df7 100644
--- a/firmware/include/memory.h
+++ b/firmware/include/memory.h
@@ -24,4 +24,15 @@
24 24
25void memset16(void *dst, int val, size_t len); 25void memset16(void *dst, int val, size_t len);
26 26
27/**
28 * memswap128
29 *
30 * Exchanges the contents of two buffers.
31 * For maximum efficiency, this function performs no aligning of addresses
32 * and buf1, buf2 and len should be 16 byte (128 bit) aligned. Not being at
33 * least longword aligned will fail on some architechtures. Any len mod 16
34 * at the end is not swapped.
35 */
36void memswap128(void *buf1, void *buf2, size_t len);
37
27#endif /* _MEMORY_H_ */ 38#endif /* _MEMORY_H_ */
diff --git a/firmware/target/arm/memswap128-arm.S b/firmware/target/arm/memswap128-arm.S
new file mode 100644
index 0000000000..f5276ef353
--- /dev/null
+++ b/firmware/target/arm/memswap128-arm.S
@@ -0,0 +1,44 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007 by Michael Sevakis
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20/****************************************************************************
21 * void memswap128(void *buf1, void *buf2, size_t len)
22 */
23 .section .icode, "ax", %progbits
24 .align 2
25 .global memswap128
26 .type memswap128, %function
27memswap128:
28 @ r0 = buf1
29 @ r1 = buf2
30 @ r2 = len
31 movs r2, r2, lsr #4 @ bytes => lines, len == 0?
32 moveq pc, lr @ not at least a line? leave
33 stmdb sp!, { r4-r10, lr } @ save registers and return address
34.loop: @
35 ldmia r0, { r3-r6 } @ read four longwords from buf1
36 ldmia r1, { r7-r10 } @ read four longwords from buf2
37 stmia r0!, { r7-r10 } @ write buf2 data to buf1, buf1 += 16
38 stmia r1!, { r3-r6 } @ write buf1 data to buf2, buf2 += 16
39 subs r2, r2, #1 @ len -= 1, len > 0 ?
40 bhi .loop @ yes? keep exchanging
41 ldmia sp!, { r4-r10, pc } @ restore registers and return
42.end:
43 .size memswap128, .end-memswap128
44
diff --git a/firmware/target/coldfire/memswap128-coldfire.S b/firmware/target/coldfire/memswap128-coldfire.S
new file mode 100644
index 0000000000..2509fd0e45
--- /dev/null
+++ b/firmware/target/coldfire/memswap128-coldfire.S
@@ -0,0 +1,50 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007 by Michael Sevakis
11 *
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20/****************************************************************************
21 * void memswap128(void *buf1, void *buf2, size_t len)
22 */
23 .section .icode, "ax", @progbits
24 .align 2
25 .global memswap128
26 .type memswap128, @function
27memswap128:
28 lea.l -28(%sp), %sp | save registers
29 movem.l %d2-%d7/%a2, (%sp) |
30 movem.l 32(%sp), %a0-%a2 | %a0 = buf1
31 | %a1 = buf2
32 | %a2 = len
33 lea.l -15(%a0, %a2.l), %a2 | %a2 = end address - 15
34 cmp.l %a0, %a2 | end address <= buf1?
35 bls.b .no_lines | not at least a line? leave
36.loop: |
37 movem.l (%a0), %d0-%d3 | read four longwords from buf1
38 movem.l (%a1), %d4-%d7 | read four longwords from buf2
39 movem.l %d4-%d7, (%a0) | write buf2 data to buf1
40 movem.l %d0-%d3, (%a1) | write buf1 data to buf2
41 lea.l 16(%a0), %a0 | buf1 += 16
42 lea.l 16(%a1), %a1 | buf2 += 16
43 cmp.l %a0, %a2 | %a0 < %d0?
44 bhi.b .loop | yes? keep exchanging
45.no_lines: |
46 movem.l (%sp), %d2-%d7/%a2 | restore registers
47 lea.l 28(%sp), %sp |
48 rts |
49.end:
50 .size memswap128, .end-memswap128