summaryrefslogtreecommitdiff
path: root/firmware/pcm_playback.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/pcm_playback.c')
-rw-r--r--firmware/pcm_playback.c119
1 files changed, 119 insertions, 0 deletions
diff --git a/firmware/pcm_playback.c b/firmware/pcm_playback.c
index e1afea949d..f5fc5e7391 100644
--- a/firmware/pcm_playback.c
+++ b/firmware/pcm_playback.c
@@ -207,3 +207,122 @@ void pcm_init(void)
pcm_set_frequency(44100);
}
+
+
+#define NUM_PCM_BUFFERS 16 /* Must be a power of 2 */
+#define NUM_PCM_BUFFERS_MASK (NUM_PCM_BUFFERS - 1)
+
+struct pcmbufdesc
+{
+ void *addr;
+ int size;
+ void (*callback)(void); /* Call this when the buffer has been played */
+} pcmbuffers[NUM_PCM_BUFFERS];
+
+int pcmbuf_read_index;
+int pcmbuf_write_index;
+int pcmbuf_unplayed_bytes;
+int pcmbuf_watermark;
+void (*pcmbuf_watermark_callback)(int bytes_left);
+
+int pcm_play_num_used_buffers(void)
+{
+ return (pcmbuf_write_index - pcmbuf_read_index) & NUM_PCM_BUFFERS_MASK;
+}
+
+void pcm_play_set_watermark(int numbytes, void (*callback)(int bytes_left))
+{
+ pcmbuf_watermark = numbytes;
+ pcmbuf_watermark_callback = callback;
+}
+
+bool pcm_play_add_chunk(void *addr, int size, void (*callback)(void))
+{
+ /* We don't use the last buffer, since we can't see the difference
+ between the full and empty condition */
+ if(pcm_play_num_used_buffers() < (NUM_PCM_BUFFERS - 1))
+ {
+ pcmbuffers[pcmbuf_write_index].addr = addr;
+ pcmbuffers[pcmbuf_write_index].size = size;
+ pcmbuffers[pcmbuf_write_index].callback = callback;
+ pcmbuf_write_index = (pcmbuf_write_index+1) & NUM_PCM_BUFFERS_MASK;
+ pcmbuf_unplayed_bytes += size;
+ return true;
+ }
+ else
+ return false;
+}
+
+void pcm_play_init(void)
+{
+ pcmbuf_read_index = 0;
+ pcmbuf_write_index = 0;
+ pcmbuf_unplayed_bytes = 0;
+ pcm_play_set_watermark(0x10000, NULL);
+}
+
+static int last_chunksize = 0;
+
+static void pcm_play_callback(unsigned char** start, long* size)
+{
+ struct pcmbufdesc *desc = &pcmbuffers[pcmbuf_read_index];
+ int sz;
+
+ pcmbuf_unplayed_bytes -= last_chunksize;
+
+ if(desc->size == 0)
+ {
+ /* The buffer is finished, call the callback function */
+ if(desc->callback)
+ desc->callback();
+
+ /* Advance to the next buffer */
+ pcmbuf_read_index = (pcmbuf_read_index + 1) & NUM_PCM_BUFFERS_MASK;
+ desc = &pcmbuffers[pcmbuf_read_index];
+ }
+
+ if(pcm_play_num_used_buffers())
+ {
+ /* Play max 64K at a time */
+ sz = MIN(desc->size, 32768);
+ *start = desc->addr;
+ *size = sz;
+
+ /* Update the buffer descriptor */
+ desc->size -= sz;
+ desc->addr += sz;
+
+ last_chunksize = sz;
+ }
+ else
+ {
+ /* No more buffers */
+ *size = 0;
+ }
+#if 0
+ if(pcmbuf_unplayed_bytes <= pcmbuf_watermark)
+ {
+ if(pcmbuf_watermark_callback)
+ {
+ pcmbuf_watermark_callback(pcmbuf_unplayed_bytes);
+ }
+ }
+#endif
+}
+
+void pcm_play_start(void)
+{
+ struct pcmbufdesc *desc = &pcmbuffers[pcmbuf_read_index];
+ int size;
+ char *start;
+
+ if(!pcm_is_playing())
+ {
+ size = MIN(desc->size, 32768);
+ start = desc->addr;
+ pcm_play_data(start, size, pcm_play_callback);
+ last_chunksize = size;
+ desc->size -= size;
+ desc->addr += size;
+ }
+}