Browse Source

Fixed zerocopy dac module for drum sequencer

Daniele Lacamera 4 years ago
parent
commit
9a6c8e5caa
14 changed files with 459 additions and 92 deletions
  1. 2 0
      Makefile
  2. 5 0
      README.md
  3. 124 45
      dac.c
  4. 1 0
      dac.h
  5. 102 0
      drums.c
  6. 9 8
      led.c
  7. 8 3
      main.c
  8. 21 0
      make_drumkit.sh
  9. 1 1
      sound.c
  10. 1 1
      target.ld
  11. 96 19
      timer.c
  12. 28 0
      timer.h
  13. 3 1
      ui.c
  14. 58 14
      ui_drone.c

+ 2 - 0
Makefile

@@ -13,6 +13,8 @@ UMXFLAGS:=-Ilib/unicore-mx/include/ -DSTM32F4
 LIBS+=$(UMX)
 
 
+OBJS+=drumkit_0.o  drumkit_1.o  drumkit_2.o  drumkit_3.o  drumkit_4.o  drumkit_5.o  drumkit_6.o  drums.o
+
 LSCRIPT:=target.ld
 
 OBJCOPY:=$(CROSS_COMPILE)objcopy

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
+# Waveblender
+
+waveblender is a DIY synth drone.
+
+Schematics+instructions coming soon.

+ 124 - 45
dac.c

@@ -11,80 +11,130 @@
 #include <unicore-mx/stm32/gpio.h>
 #include <unicore-mx/stm32/rcc.h>
 #include <unicore-mx/stm32/timer.h>
+#include <stdlib.h>
 #include "pot.h"
+#include "timer.h"
+#include "system.h"
 
-#define DAC_BUFSIZ 512
+#define DAC_BUFSIZ (512)
+#define DAC_MEMSIZ (4 * DAC_BUFSIZ)
 extern const unsigned char raw_au[];
+extern volatile uint32_t jiffies;
 const unsigned int raw_au_len;
 
-static volatile int dac_written;
 static int dac_transfer_size;
-static int dac_chunk_size;
-static uint8_t dac_outb[DAC_BUFSIZ];
+
+static uint8_t dac_memory[DAC_MEMSIZ];
+static volatile uint32_t dac_written;
+
+
+static volatile int direct = 0;
+static volatile int dac_busy = 0;
+
+int dac_is_busy(void)
+{
+    return dac_busy;
+}
 
 static void dac_xmit(void)
 {
     uint32_t size = DAC_BUFSIZ;
-    if ((dac_transfer_size - dac_written ) < size)
-        size = dac_transfer_size - dac_written;
-    dac_chunk_size = size;
+    if ((dac_transfer_size == 0) || (dac_written >= dac_transfer_size)) {
+        dac_written = 0;
+        dac_transfer_size = 0;
+        memset(dac_memory, 0, DAC_MEMSIZ);
+    } else {
+        if ((dac_transfer_size - dac_written) < size)
+            size = dac_transfer_size - dac_written;
+        if (size == 0) {
+            dac_transfer_size = 0;
+            dac_written = 0;
+            memset(dac_memory, 0, DAC_MEMSIZ);
+        }
+    }
+    if (dac_transfer_size == 0) {
+        dma_disable_stream(DMA1, DMA_STREAM5);
+        dac_trigger_disable(CHANNEL_1);
+        dac_dma_disable(CHANNEL_1);
+        return;
+    }
+
+    dma_set_number_of_data(DMA1, DMA_STREAM5, size);
+    dma_set_memory_address(DMA1, DMA_STREAM5, (uint32_t)(dac_memory + dac_written));
+    direct = 0;
 
     /* Start DMA transfer of waveform */
     dac_trigger_enable(CHANNEL_1);
     dac_set_trigger_source(DAC_CR_TSEL1_T2);
     dac_dma_enable(CHANNEL_1);
-    dma_set_memory_address(DMA1, DMA_STREAM5, (uint32_t) (dac_outb + dac_written));
-    dma_set_number_of_data(DMA1, DMA_STREAM5, size);
     dma_enable_transfer_complete_interrupt(DMA1, DMA_STREAM5);
     dma_channel_select(DMA1, DMA_STREAM5, DMA_SxCR_CHSEL_7);
     dma_enable_stream(DMA1, DMA_STREAM5);
 }
 
-int dac_write(const void *buf, unsigned int len)
-{
-    if (dac_written < dac_transfer_size) {
-        if ((len + dac_transfer_size) > DAC_BUFSIZ)
-            return 0;
-    }
-    if (dac_written >= dac_transfer_size) {
-        dac_written = 0;
-        dac_transfer_size = 0;
-    }
-    memcpy(dac_outb + dac_transfer_size, buf, len);
-    dac_transfer_size += len;
-    dac_xmit();
-    return dac_written;
-}
+
 
 int dac_space(void)
 {
-    if (dac_written >= dac_transfer_size) {
-        dac_written = 0;
-        dac_transfer_size = 0;
-    }
-    return DAC_BUFSIZ - dac_transfer_size;
+    return DAC_MEMSIZ - dac_transfer_size;
 }
 
 
+
+
 /* IRQ Handler */
 void dma1_stream5_isr(void)
 {
     if (dma_get_interrupt_flag(DMA1, DMA_STREAM5, DMA_TCIF)) {
-        if (dac_written < dac_transfer_size)
-            dac_written += dac_chunk_size;
-
         dma_clear_interrupt_flags(DMA1, DMA_STREAM5, DMA_TCIF);
         dma_disable_stream(DMA1, DMA_STREAM5);
         dac_trigger_disable(CHANNEL_1);
         dac_dma_disable(CHANNEL_1);
-        if (dac_written >= dac_transfer_size) {
-            return;
-        } else {
+        dac_busy = 0;
+        if (!direct) {
+            if (dac_written < dac_transfer_size) {
+                dac_written += DAC_BUFSIZ;
+            } else {
+                dac_written = 0;
+                dac_transfer_size = 0;
+                return;
+            }
             dac_xmit();
         }
     }
 }
 
+void dac_stop(void)
+{
+    dma_clear_interrupt_flags(DMA1, DMA_STREAM5, DMA_TCIF);
+    dma_disable_stream(DMA1, DMA_STREAM5);
+    dac_trigger_disable(CHANNEL_1);
+    dac_dma_disable(CHANNEL_1);
+    dac_busy = 0;
+}
+
+
+void dac_play_direct(uint8_t *mem, uint32_t size)
+{
+    dma_clear_interrupt_flags(DMA1, DMA_STREAM5, DMA_TCIF);
+    dma_disable_stream(DMA1, DMA_STREAM5);
+    dac_trigger_disable(CHANNEL_1);
+    dac_dma_disable(CHANNEL_1);
+    dma_set_number_of_data(DMA1, DMA_STREAM5, size);
+    dma_set_memory_address(DMA1, DMA_STREAM5, (uint32_t)mem);
+
+    direct = 1;
+    dac_busy = 1;
+
+    /* Start DMA transfer of waveform */
+    dac_trigger_enable(CHANNEL_1);
+    dac_set_trigger_source(DAC_CR_TSEL1_T2);
+    dac_dma_enable(CHANNEL_1);
+    dma_enable_transfer_complete_interrupt(DMA1, DMA_STREAM5);
+    dma_channel_select(DMA1, DMA_STREAM5, DMA_SxCR_CHSEL_7);
+    dma_enable_stream(DMA1, DMA_STREAM5);
+}
+
 
 /* Initialization functions */
 
@@ -147,28 +197,57 @@ static void dac_hw_init(data_channel c)
     dac_enable(c);
 }
 
+void dac_reset(void)
+{
+    dac_written = 0;
+    dac_transfer_size = 0;
+}
+
 void dac_play(const uint8_t *buf, int len)
 {
     int i = 0;
     int space;
-    while(i < len) {
-        space = dac_space();
-        if (space > 0) {
-            if (space > (len - i))
-                space = len - i;
-            dac_write(buf + i, space);
-            i += space;
+    int w = 0;
+    dac_written = 0;
+    dac_transfer_size = 0;
+    while (len > 0) {
+        space = DAC_MEMSIZ;
+        if (space > len) {
+            space = len;
+        }
+        if (dac_space() == 0) {
+            WFI();
+            continue;
         }
+        memcpy(dac_memory, buf + w, space);
+        dac_transfer_size = space;
+        dac_xmit();
+        len -= space;
+        w += space;
     }
-
 }
 
+extern unsigned char drumkit_0_au[];
+extern unsigned char drumkit_1_au[];
+extern unsigned char drumkit_2_au[];
+extern unsigned int drumkit_0_au_len;
+extern unsigned int drumkit_1_au_len;
+extern unsigned int drumkit_2_au_len;
+
+
 int dac_init(void)
 {
     int i;
+    uint32_t now;
     dac_hw_init(CHANNEL_1);
     dac_dma_setup();
-    pot_set_master(200);
-    dac_play(raw_au, raw_au_len);
+    pot_set_master(100);
+
+//    dac_play(drumkit_0_au, drumkit_0_au_len);
+//    dac_play(raw_au, raw_au_len);
+    dac_play_direct(drumkit_2_au, drumkit_2_au_len);
+    while(dac_is_busy())
+        WFI();
+
     return 0;
 }

+ 1 - 0
dac.h

@@ -8,6 +8,7 @@
 void dac_init(void);
 int dac_write(const void *buf, unsigned int len);
 int dac_play(const void *buf, unsigned int len);
+void dac_play_direct(uint8_t *mem, uint32_t size);
 
 #define pot_get_master() pot_get(2)
 #define pot_set_master(x) pot_set(2,x)

+ 102 - 0
drums.c

@@ -0,0 +1,102 @@
+
+
+#include <stdint.h>
+#include "system.h"
+#include "display.h"
+#include "systick.h"
+#include "button.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "ui.h"
+#include "timer.h"
+#include "led.h"
+
+
+#define MAX_PATTERN_LEN 64
+
+#define DRUMS_KICK      (1 << 0)
+#define DRUMS_HIHAT     (1 << 1)
+#define DRUMS_SNARE     (1 << 2)
+#define DRUMS_TOM1      (1 << 3)
+#define DRUMS_TOM2      (1 << 4)
+#define DRUMS_CYM       (1 << 6)
+#define DRUMS_CLAP      (1 << 7)
+
+
+#define TRACKS 8
+
+static uint32_t pattern[MAX_PATTERN_LEN] = { DRUMS_KICK, DRUMS_HIHAT, DRUMS_SNARE, DRUMS_HIHAT, DRUMS_KICK, DRUMS_KICK, DRUMS_SNARE, DRUMS_HIHAT };
+//static uint32_t pattern[MAX_PATTERN_LEN] = { DRUMS_KICK, DRUMS_KICK, DRUMS_KICK, DRUMS_KICK, DRUMS_KICK, DRUMS_KICK, DRUMS_SNARE, DRUMS_HIHAT };
+static uint32_t pattern_len = 8;
+static uint32_t pattern_pos = 0;
+static unsigned char *dsample[TRACKS] = {};
+static unsigned int dsample_len[TRACKS] = {};
+
+
+
+void beat_cb(uint32_t b)
+{
+    uint32_t key = pattern[pattern_pos];
+    int i;
+    for (i = 0; i < TRACKS; i++) {
+        if ((key & (1 << i)) == (1 << i)) {
+            if (dsample[i]) {
+                while(dac_is_busy())
+                    dac_stop();
+                dac_play_direct(dsample[i], dsample_len[i]);
+                break;
+            }
+        }
+    }
+    pattern_pos++;
+    if (pattern_pos >= pattern_len) {
+        pattern_pos = 0;
+    }
+    led_beat((pattern_pos  % 8) + 1);
+}
+
+void drums_init(void)
+{
+    dsample[0] = drumkit_0_au;
+    dsample[1] = drumkit_1_au;
+    dsample[2] = drumkit_2_au;
+    dsample[3] = drumkit_3_au;
+    dsample[4] = drumkit_4_au;
+    dsample[5] = drumkit_5_au;
+    dsample[6] = drumkit_6_au;
+
+    dsample_len[0] = drumkit_0_au_len;
+    dsample_len[1] = drumkit_1_au_len;
+    dsample_len[2] = drumkit_2_au_len;
+    dsample_len[3] = drumkit_3_au_len;
+    dsample_len[4] = drumkit_4_au_len;
+    dsample_len[5] = drumkit_5_au_len;
+    dsample_len[6] = drumkit_6_au_len;
+}
+
+void drums_start(void)
+{
+    timer_set_beat_callback(beat_cb);
+}
+
+void drums_stop(void)
+{
+    timer_clear_beat_callback();
+}
+
+void drums_set_pattern_len(int l)
+{
+    pattern_len = l;
+    timer_set_beat(1);
+}
+
+void drums_set(uint32_t track, int pos)
+{
+    pattern[pos] |= track;
+}
+
+void drums_clear(uint32_t track, int pos)
+{
+    pattern[pos] &= ~track;
+}

+ 9 - 8
led.c

@@ -43,14 +43,15 @@
 
 
 struct pin Leds[9] = {
-    { GPIOE, 1 << 0 },
-    { GPIOE, 1 << 10 },
-    { GPIOB, 1 << 2 },
-    { GPIOE, 1 << 8 },
-    { GPIOE, 1 << 9 },
-    { GPIOE, 1 << 1 },
-    { GPIOB, 1 << 8 },
-    { GPIOB, 1 << 9 },
+    { GPIOE, LD0 },
+    { GPIOE, LD1 },
+    { GPIOB, LD2 },
+    { GPIOE, LD3 },
+    { GPIOE, LD4 },
+    { GPIOE, LD5 },
+    { GPIOE, LD6 },
+    { GPIOB, LD7 },
+    { GPIOB, LD8 },
 };
 
 void led_setup(void)

+ 8 - 3
main.c

@@ -39,7 +39,9 @@ static void bootlevel_0(void)
     printf("Buttons initialized.\r\n");
 }
 
-extern void timer_init(void);
+extern int timer_init(uint32_t bpm);
+extern int timer_start();
+extern int timer_stop();
 
 static void bootlevel_1(void)
 {
@@ -64,8 +66,9 @@ static void bootlevel_1(void)
     printf("Displaying splash screen...\r\n");
     ui_init();
     printf("UI initialized.\r\n");
-    //djhero_init();
-    timer_init();
+    timer_init(60);
+    drums_init();
+    drums_start();
 
     printf("System up and running.\r\n\n\n");
 }
@@ -126,6 +129,8 @@ int main(void) {
         led_on(LED);
         poll_time = jiffies;
 
+        timer_poll();
+
         if (!keepalive_callback)
             ui_keepalive(hb_len);
         else

+ 21 - 0
make_drumkit.sh

@@ -0,0 +1,21 @@
+#!/bin/bash
+
+
+## Usage example:
+## ./make_drumkit.sh kick1.wav hihat1.wav snare3.wav tom1.wav tom2.wav cowbell.wav clap.wav 
+
+IDX=0
+
+for i in $@; do
+    echo $i
+    sox $i -r 22050 -t u8 drumkit_$IDX.au || exit 1
+    #sox $i -t u8 drumkit_$IDX.au
+
+    xxd -i drumkit_$IDX.au drumkit_$IDX.c
+#    xxd -i drumkit_$IDX.au drumkit_tmp_$IDX.c
+#    cat drumkit_tmp_$IDX.c | sed -e "s/unsigned /const unsigned /g" >drumkit_$IDX.c
+#    rm drumkit_$IDX.au
+    IDX=`expr $IDX + 1`
+done
+
+#rm -f drumkit_tmp_* drumkit_*.au

+ 1 - 1
sound.c

@@ -1,4 +1,4 @@
-const unsigned char raw_au[] = {
+unsigned char raw_au[] = {
   0x57, 0x5a, 0x58, 0x59, 0x58, 0x58, 0x57, 0x58, 0x57, 0x57, 0x57, 0x56,
   0x55, 0x55, 0x56, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, 0x54, 0x53, 0x54,
   0x54, 0x53, 0x53, 0x54, 0x53, 0x53, 0x53, 0x53, 0x52, 0x53, 0x53, 0x52,

+ 1 - 1
target.ld

@@ -1,7 +1,7 @@
 MEMORY
 {
     FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 256K - 0x100
-    RAM (rw) : ORIGIN = 0x20001000, LENGTH = 60K
+    RAM (rw) : ORIGIN = 0x20001000, LENGTH = 252K
     RAM_S (rw) : ORIGIN = 0x20000000, LENGTH = 4K
 }
 

+ 96 - 19
timer.c

@@ -1,37 +1,95 @@
 #include <stdint.h>
 #include "system.h"
+#include "led.h"
+#include "timer.h"
+#include <stdlib.h>
 
-static uint32_t master_clock = 0;
+#define S_PER_MINUTE (60)
 
-/* Timer 4: Use val 60000 with PSC 1400 for 1s tick (84 Mhz) */
-/* Timer 4: Use val 52500 with PSC 200 for 1/8 s tick (84 Mhz) */
+extern uint32_t cpu_freq;
+static uint32_t sys_bpm = 75;
 
-#define TMR4_INIT_VAL 52500
-#define TMR4_INIT_PSC 200
+void (*beat_callback)(uint32_t b) = NULL;
 
-void timer_init(void)
+void timer_set_beat_callback(void (*b_cb)(uint32_t))
+{
+    beat_callback = b_cb;
+}
+
+void timer_clear_beat_callback(void)
+{
+    timer_set_beat_callback(NULL);
+} 
+
+
+uint32_t timer_get_bpm(void)
+{
+    return sys_bpm;
+}
+
+int timer_set_bpm(uint32_t bpm)
 {
     uint32_t val = 0;
     uint32_t psc = 1;
     uint32_t err = 0;
+    uint32_t reg = 0;
+    uint32_t clock = (cpu_freq / (4 * bpm)) * (S_PER_MINUTE);;
+
+    while (psc < 65535) {
+        val = clock / psc;
+        err = clock % psc;
+        if ((val < 65535) && (err < (psc / 2))) {
+            val--;
+            break;
+        }
+        val = 0;
+        psc++;
+    }
+    if (val == 0)
+        return -1;
+    TIM4_CR1    = 0;
+    __asm__ volatile ("dmb");
+    TIM4_PSC    = psc;
+    TIM4_ARR    = val;
+    TIM4_CNT    = val - 1;
+    sys_bpm = bpm;
+    return 0;
+}
+
+int timer_start(void)
+{
+    TIM4_CR1    |= TIM_CR1_CLOCK_ENABLE;
+    TIM4_DIER   |= TIM_DIER_UIE;
+}
+
+int timer_stop(void)
+{
+    TIM4_CR1    |= TIM_CR1_CLOCK_ENABLE;
+    TIM4_DIER   |= TIM_DIER_UIE;
+}
 
+static void timer_irq_setup(void)
+{
     nvic_irq_enable(NVIC_TIM4_IRQN);
     nvic_irq_setprio(NVIC_TIM4_IRQN, 0);
     APB1_CLOCK_RST |= TIM4_APB1_CLOCK_ER_VAL;
     __asm__ volatile ("dmb");
     APB1_CLOCK_RST &= ~TIM4_APB1_CLOCK_ER_VAL;
     APB1_CLOCK_ER |= TIM4_APB1_CLOCK_ER_VAL;
+}
 
-    TIM4_CR1    = 0;
-    __asm__ volatile ("dmb");
-    TIM4_PSC    = TMR4_INIT_PSC;
-    TIM4_ARR    = TMR4_INIT_VAL;
-    TIM4_CR1    |= TIM_CR1_CLOCK_ENABLE;
-    TIM4_DIER   |= TIM_DIER_UIE;
-    __asm__ volatile ("dmb");
+
+int timer_init(uint32_t bpm)
+{
+    timer_stop();
+    timer_irq_setup();
+    timer_set_bpm(bpm);
+    timer_start();
+    return 0;
 }
 
 static volatile uint32_t tim4_ticks = 0;
+static volatile int pending_cb = 0;
 void isr_tim1(void)
 {
     TIM1_SR &= ~TIM_SR_UIF;
@@ -42,16 +100,35 @@ void isr_tim3(void)
     TIM3_SR &= ~TIM_SR_UIF;
 }
 
-void isr_tim4(void)
+static int beat = 1;
+int timer_get_beat(void)
 {
-    TIM4_SR &= ~TIM_SR_UIF;
-    tim4_ticks++;
+    return beat;
+}
+
+void timer_set_beat(int b)
+{
+    beat = b;
 }
 
-void gettime(uint32_t *seconds, uint32_t *microseconds)
+void timer_poll(void)
 {
-    *microseconds = ((TIM4_CNT * TMR4_INIT_PSC) / 84) + (tim4_ticks & 0x07) * 125000;
-    *seconds = (tim4_ticks >> 3);
+    if (beat_callback && (pending_cb > 0)) {
+        pending_cb--;
+        beat_callback(beat);
+        beat++;
+    }
+}
+
+void isr_tim4(void)
+{
+    TIM4_SR &= ~TIM_SR_UIF;
+    tim4_ticks++;
+    if (beat_callback) {
+        pending_cb++;
+    } else {
+        led_beat((beat % 8) + 1);
+    }
 }
 
 

+ 28 - 0
timer.h

@@ -0,0 +1,28 @@
+#ifndef TIMER_H_INCLUDED
+#define TIMER_H_INCLUDED
+
+uint32_t timer_get_bpm(void);
+int timer_set_bpm(uint32_t bpm);
+int timer_start(void);
+int timer_stop(void);
+int timer_init(uint32_t bpm);
+void timer_set_beat_callback(void (*b_cb)(uint32_t));
+void timer_clear_beat_callback(void);
+
+extern unsigned char drumkit_0_au[],
+       drumkit_1_au[],
+       drumkit_2_au[],
+       drumkit_3_au[],
+       drumkit_4_au[],
+       drumkit_5_au[],
+       drumkit_6_au[];
+
+extern unsigned int drumkit_0_au_len,
+       drumkit_1_au_len,
+       drumkit_2_au_len,
+       drumkit_3_au_len,
+       drumkit_4_au_len,
+       drumkit_5_au_len,
+       drumkit_6_au_len;
+
+#endif

+ 3 - 1
ui.c

@@ -14,6 +14,8 @@
 #include <string.h>
 #include <stdio.h>
 #include "ui.h"
+#include "timer.h"
+#include "led.h"
 
 
 /* Uncomment for device initialization */
@@ -170,7 +172,7 @@ void ui_init(void)
     now = jiffies;
     for (i = 1; i < 9; i++) {
         led_beat(i);
-        while((jiffies - now < 100))
+        while((jiffies - now < 10))
             WFI();
         now = jiffies;
     }

+ 58 - 14
ui_drone.c

@@ -49,16 +49,20 @@ static void display_drone(int line, uint16_t val)
     display_text(line, txt);
 }
 
-static void display_volume(void)
+static void display_thousand(uint32_t val)
 {
     char txt[4] = "000";
-    int master_vol = pot_get_master();
-    txt[0] = '0' + master_vol / 100;
-    txt[1] = '0' + (master_vol % 100) / 10;
-    txt[2] = '0' + (master_vol % 10);
+    txt[0] = '0' + val / 100;
+    txt[1] = '0' + (val % 100) / 10;
+    txt[2] = '0' + (val % 10);
     display_text(6, txt);
 }
 
+static void display_volume(void)
+{
+    display_thousand(pot_get_master());
+}
+
 void ui_drone_xy_poll(void)
 {
     uint16_t x = 0, y = 0;
@@ -157,7 +161,7 @@ static void ui_mastervol_input(uint8_t press, int hold)
 {
     int master_vol = pot_get_master();
     if (press == 'U')
-        if (master_vol < 1000)
+        if (master_vol < 100)
             pot_set_master(master_vol + 5);
 
     if (press == 'D') {
@@ -182,6 +186,45 @@ static void ui_mastervol(const void *arg)
     display_volume();
 }
 
+static void ui_bpm_input(uint8_t press, int hold)
+{
+    uint32_t sys_bpm = timer_get_bpm();
+    if (press == 'U') {
+        if (sys_bpm < 300) {
+            timer_stop();
+            timer_set_bpm(sys_bpm + 1);
+            led_beat(1);
+            timer_set_beat(1);
+            timer_start();
+        }
+    }
+
+    if (press == 'D') {
+        if (sys_bpm > 60) {
+            timer_stop();
+            timer_set_bpm(sys_bpm - 1);
+            led_beat(1);
+            timer_set_beat(1);
+            timer_start();
+        }
+    }
+    if (press == '+') {
+        display_clear(NULL);
+        clear_input_callback();
+        ui_display_menu(&MainMenu);
+        return;
+    }
+    display_text(5, "    BPM     ");
+    display_thousand(timer_get_bpm());
+}
+
+static void ui_bpm(const void *arg)
+{
+    set_input_callback(ui_bpm_input);
+    display_clear(NULL);
+    display_text(5, "    BPM     ");
+    display_thousand(timer_get_bpm());
+}
 
 static void ui_bytebeat_input(uint8_t press, int hold) {
     if (press == '+') {
@@ -194,19 +237,20 @@ static void ui_bytebeat_input(uint8_t press, int hold) {
 
 #define PLAY_SIZE 512
 
+
 static uint8_t bb_buffer[PLAY_SIZE];
 
 void ui_bytebeat_keepalive(void)
 {
     static uint32_t t = 0;
     int r = 0;
-    if (dac_space() >= PLAY_SIZE) {
-        for(;;t++) { 
-            bb_buffer[r++] = (t<<1)|(t>>4)*(((t>>12)|(t>>13)|(t>>6) | ((t>>2)|(t>>4))|(t<<1)|(t<<12)|((t<<5)&~(t>>22))));
-            if (r >= PLAY_SIZE) {
-                dac_write(bb_buffer, r);
-                break;
-            }
+    for(r = 0;;t++) { 
+        while (dac_is_busy())
+            WFI();
+        bb_buffer[r++] = (t<<1)|(t>>4)*(((t>>12)|(t>>13)|(t>>6) | ((t>>2)|(t>>4))|(t<<1)|(t<<12)|((t<<5)&~(t>>22))));
+        if (r >= PLAY_SIZE) {
+            dac_play_direct(bb_buffer, r);
+            break;
         }
     }
 }
@@ -254,7 +298,7 @@ const struct display_menu MainMenu = {
     .entry_n = 5,
     .entry = {
         { "Master Volume  ", ui_mastervol, NULL },
-        { "Settings       ", ui_submenu, &SettingsMenu },
+        { "BPM"          , ui_bpm, NULL},
         { "Pattern        ", ui_submenu, &PatternMenu },
         { "Drone          ", ui_submenu, &DroneMenu},
         { "Bytebeat       ", ui_bytebeat },