From 9a6c8e5caabbd7195d014b953bc6af86b40df1eb Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Sat, 11 Apr 2020 15:28:44 +0200 Subject: [PATCH] Fixed zerocopy dac module for drum sequencer --- Makefile | 2 + README.md | 5 ++ dac.c | 169 +++++++++++++++++++++++++++++++++++------------- dac.h | 1 + drums.c | 102 +++++++++++++++++++++++++++++ led.c | 17 ++--- main.c | 11 +++- make_drumkit.sh | 21 ++++++ sound.c | 2 +- target.ld | 2 +- timer.c | 115 ++++++++++++++++++++++++++------ timer.h | 28 ++++++++ ui.c | 4 +- ui_drone.c | 72 +++++++++++++++++---- 14 files changed, 459 insertions(+), 92 deletions(-) create mode 100644 README.md create mode 100644 drums.c create mode 100755 make_drumkit.sh create mode 100644 timer.h diff --git a/Makefile b/Makefile index 55fd751..27e951f 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e4e0a7 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Waveblender + +waveblender is a DIY synth drone. + +Schematics+instructions coming soon. diff --git a/dac.c b/dac.c index 86cbfe2..b7bb9f5 100644 --- a/dac.c +++ b/dac.c @@ -11,80 +11,130 @@ #include #include #include +#include #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; } diff --git a/dac.h b/dac.h index 7758220..d0cc8c9 100644 --- a/dac.h +++ b/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) diff --git a/drums.c b/drums.c new file mode 100644 index 0000000..3551485 --- /dev/null +++ b/drums.c @@ -0,0 +1,102 @@ + + +#include +#include "system.h" +#include "display.h" +#include "systick.h" +#include "button.h" +#include +#include +#include +#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; +} diff --git a/led.c b/led.c index 20d0889..9d3cce5 100644 --- a/led.c +++ b/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) diff --git a/main.c b/main.c index b688c66..253f03c 100644 --- a/main.c +++ b/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 diff --git a/make_drumkit.sh b/make_drumkit.sh new file mode 100755 index 0000000..3fbb183 --- /dev/null +++ b/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 diff --git a/sound.c b/sound.c index afcbb83..d0b282e 100644 --- a/sound.c +++ b/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, diff --git a/target.ld b/target.ld index 3f5868d..ee7fcde 100644 --- a/target.ld +++ b/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 } diff --git a/timer.c b/timer.c index bf78d97..d389a2a 100644 --- a/timer.c +++ b/timer.c @@ -1,37 +1,95 @@ #include #include "system.h" +#include "led.h" +#include "timer.h" +#include -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; } +static int beat = 1; +int timer_get_beat(void) +{ + return beat; +} + +void timer_set_beat(int b) +{ + beat = b; +} + +void timer_poll(void) +{ + if (beat_callback && (pending_cb > 0)) { + pending_cb--; + beat_callback(beat); + beat++; + } +} + void isr_tim4(void) { TIM4_SR &= ~TIM_SR_UIF; tim4_ticks++; -} - -void gettime(uint32_t *seconds, uint32_t *microseconds) -{ - *microseconds = ((TIM4_CNT * TMR4_INIT_PSC) / 84) + (tim4_ticks & 0x07) * 125000; - *seconds = (tim4_ticks >> 3); + if (beat_callback) { + pending_cb++; + } else { + led_beat((beat % 8) + 1); + } } diff --git a/timer.h b/timer.h new file mode 100644 index 0000000..fd32ee4 --- /dev/null +++ b/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 diff --git a/ui.c b/ui.c index 425236e..6bde3ce 100644 --- a/ui.c +++ b/ui.c @@ -14,6 +14,8 @@ #include #include #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; } diff --git a/ui_drone.c b/ui_drone.c index f8a9d89..000a8e3 100644 --- a/ui_drone.c +++ b/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 },