From 3d6af91dd1f266f34df9749fbc7e96a857aa1186 Mon Sep 17 00:00:00 2001 From: DrZlo13 Date: Mon, 16 Nov 2020 20:12:05 +0300 Subject: [PATCH] simple music player app (#240) * split power-cli dependence * music_player application * add player to release mode * fix stupid error --- applications/applications.h | 9 + applications/applications.mk | 12 + applications/music-player/music-player.c | 444 +++++++++++++++++++++++ applications/power/power.c | 5 +- 4 files changed, 468 insertions(+), 2 deletions(-) create mode 100644 applications/music-player/music-player.c diff --git a/applications/applications.h b/applications/applications.h index 1af3f23f..ff337ca5 100644 --- a/applications/applications.h +++ b/applications/applications.h @@ -36,6 +36,7 @@ void sd_card_test(void* p); void application_vibro(void* p); void app_gpio_test(void* p); void cli_task(void* p); +void music_player(void* p); const FlipperStartupApp FLIPPER_STARTUP[] = { #ifdef APP_DISPLAY @@ -121,6 +122,10 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { {.app = sd_card_test, .name = "sd_card_test", .libs = {1, FURI_LIB{"gui_task"}}}, #endif +#ifdef APP_MUSIC_PLAYER + {.app = music_player, .name = "music player", .libs = {1, FURI_LIB{"gui_task"}}}, +#endif + #ifdef APP_GPIO_DEMO { .app = app_gpio_test, @@ -169,4 +174,8 @@ const FlipperStartupApp FLIPPER_APPS[] = { #ifdef BUILD_GPIO_DEMO {.app = app_gpio_test, .name = "gpio test", .libs = {1, FURI_LIB{"gui_task"}}}, #endif + +#ifdef BUILD_MUSIC_PLAYER + {.app = music_player, .name = "music player", .libs = {1, FURI_LIB{"gui_task"}}}, +#endif }; \ No newline at end of file diff --git a/applications/applications.mk b/applications/applications.mk index 3aae9d92..ba21c519 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -23,6 +23,7 @@ BUILD_SPEAKER_DEMO = 1 BUILD_VIBRO_DEMO = 1 BUILD_SD_TEST = 1 BUILD_GPIO_DEMO = 1 +BUILD_MUSIC_PLAYER = 1 endif APP_NFC ?= 0 @@ -246,6 +247,17 @@ CFLAGS += -DBUILD_GPIO_DEMO C_SOURCES += $(wildcard $(APP_DIR)/gpio-tester/*.c) endif +APP_MUSIC_PLAYER ?= 0 +ifeq ($(APP_MUSIC_PLAYER), 1) +CFLAGS += -DAPP_MUSIC_PLAYER +BUILD_MUSIC_PLAYER = 1 +endif +BUILD_MUSIC_PLAYER ?= 0 +ifeq ($(BUILD_MUSIC_PLAYER), 1) +CFLAGS += -DBUILD_MUSIC_PLAYER +C_SOURCES += $(wildcard $(APP_DIR)/music-player/*.c) +endif + # device drivers APP_GUI ?= 0 diff --git a/applications/music-player/music-player.c b/applications/music-player/music-player.c new file mode 100644 index 00000000..09e3ae47 --- /dev/null +++ b/applications/music-player/music-player.c @@ -0,0 +1,444 @@ +#include "flipper_v2.h" + +// TODO float note freq +typedef enum { + // Delay + N = 0, + // Octave 4 + B4 = 494, + // Octave 5 + C5 = 523, + D5 = 587, + E5 = 659, + F_5 = 740, + G5 = 784, + A5 = 880, + B5 = 988, + // Octave 6 + C6 = 1046, + D6 = 1175, + E6 = 1319, +} MelodyEventNote; + +typedef enum { + L1 = 1, + L2 = 2, + L4 = 4, + L8 = 8, + L16 = 16, + L32 = 32, + L64 = 64, + L128 = 128, +} MelodyEventLength; + +typedef struct { + MelodyEventNote note; + MelodyEventLength length; +} MelodyEventRecord; + +typedef struct { + const MelodyEventRecord* record; + int8_t loop_count; +} SongPattern; + +const MelodyEventRecord melody_start[] = { + {E6, L8}, {N, L8}, {E5, L8}, {B5, L8}, {N, L4}, {E5, L8}, {A5, L8}, {G5, L8}, {A5, L8}, + {E5, L8}, {B5, L8}, {N, L8}, {G5, L8}, {A5, L8}, {D6, L8}, {N, L4}, {D5, L8}, {B5, L8}, + {N, L4}, {D5, L8}, {A5, L8}, {G5, L8}, {A5, L8}, {D5, L8}, {F_5, L8}, {N, L8}, {G5, L8}, + {A5, L8}, {D6, L8}, {N, L4}, {F_5, L8}, {B5, L8}, {N, L4}, {F_5, L8}, {D6, L8}, {C6, L8}, + {B5, L8}, {F_5, L8}, {A5, L8}, {N, L8}, {G5, L8}, {F_5, L8}, {E5, L8}, {N, L8}, {C5, L8}, + {E5, L8}, {B5, L8}, {B4, L8}, {C5, L8}, {D5, L8}, {D6, L8}, {C6, L8}, {B5, L8}, {F_5, L8}, + {A5, L8}, {N, L8}, {G5, L8}, {A5, L8}, {E6, L8}}; + +const MelodyEventRecord melody_loop[] = { + {N, L4}, {E5, L8}, {B5, L8}, {N, L4}, {E5, L8}, {A5, L8}, {G5, L8}, {A5, L8}, {E5, L8}, + {B5, L8}, {N, L8}, {G5, L8}, {A5, L8}, {D6, L8}, {N, L4}, {D5, L8}, {B5, L8}, {N, L4}, + {D5, L8}, {A5, L8}, {G5, L8}, {A5, L8}, {D5, L8}, {F_5, L8}, {N, L8}, {G5, L8}, {A5, L8}, + {D6, L8}, {N, L4}, {F_5, L8}, {B5, L8}, {N, L4}, {F_5, L8}, {D6, L8}, {C6, L8}, {B5, L8}, + {F_5, L8}, {A5, L8}, {N, L8}, {G5, L8}, {F_5, L8}, {E5, L8}, {N, L8}, {C5, L8}, {E5, L8}, + {B5, L8}, {B4, L8}, {C5, L8}, {D5, L8}, {D6, L8}, {C6, L8}, {B5, L8}, {F_5, L8}, {A5, L8}, + {N, L8}, {G5, L8}, {A5, L8}, {E6, L8}}; + +const MelodyEventRecord melody_chords_1bar[] = { + {E6, L8}, {N, L8}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, + {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, + {B4, L128}, {E5, L128}, {B5, L8}, {N, L4}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, + {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, + {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {A5, L8}}; + +const SongPattern song[] = {{melody_start, 1}, {melody_loop, -1}}; + +typedef enum { + EventTypeTick, + EventTypeKey, + EventTypeNote, + // add your events type +} MusicDemoEventType; + +typedef struct { + union { + InputEvent input; + const MelodyEventRecord* note_record; + } value; + MusicDemoEventType type; +} MusicDemoEvent; + +typedef struct { + ValueMutex* state_mutex; + osMessageQueueId_t event_queue; + +} MusicDemoContext; + +#define note_stack_size 4 +typedef struct { + // describe state here + const MelodyEventRecord* note_record; + const MelodyEventRecord* note_stack[note_stack_size]; + uint8_t volume_id; + uint8_t volume_id_max; +} State; + +float volumes[] = {0, 0.02, 0.05, 0.1, 0.5}; + +bool is_white_note(const MelodyEventRecord* note_record, uint8_t id) { + if(note_record == NULL) return false; + + switch(note_record->note) { + case C5: + case C6: + if(id == 0) return true; + break; + case D5: + case D6: + if(id == 1) return true; + break; + case E5: + case E6: + if(id == 2) return true; + break; + case G5: + if(id == 4) return true; + break; + case A5: + if(id == 5) return true; + break; + case B4: + case B5: + if(id == 6) return true; + break; + default: + break; + } + + return false; +} + +bool is_black_note(const MelodyEventRecord* note_record, uint8_t id) { + if(note_record == NULL) return false; + + switch(note_record->note) { + case F_5: + if(id == 3) return true; + break; + default: + break; + } + + return false; +} + +const char* get_note_name(const MelodyEventRecord* note_record) { + if(note_record == NULL) return ""; + + switch(note_record->note) { + case N: + return "---"; + break; + case B4: + return "B4-"; + break; + case C5: + return "C5-"; + break; + case D5: + return "D5-"; + break; + case E5: + return "E5-"; + break; + case F_5: + return "F#5"; + break; + case G5: + return "G5-"; + break; + case A5: + return "A5-"; + break; + case B5: + return "B5-"; + break; + case C6: + return "C6-"; + break; + case D6: + return "D6-"; + break; + case E6: + return "E6-"; + break; + default: + return "UNK"; + break; + } +} +const char* get_note_len_name(const MelodyEventRecord* note_record) { + if(note_record == NULL) return ""; + + switch(note_record->length) { + case L1: + return "1-"; + break; + case L2: + return "2-"; + break; + case L4: + return "4-"; + break; + case L8: + return "8-"; + break; + case L16: + return "16"; + break; + case L32: + return "32"; + break; + case L64: + return "64"; + break; + case L128: + return "1+"; + break; + default: + return "--"; + break; + } +} + +static void render_callback(CanvasApi* canvas, void* ctx) { + State* state = (State*)acquire_mutex((ValueMutex*)ctx, 25); + + canvas->clear(canvas); + canvas->set_color(canvas, ColorBlack); + canvas->set_font(canvas, FontPrimary); + canvas->draw_str(canvas, 0, 12, "MusicPlayer"); + + uint8_t x_pos = 0; + uint8_t y_pos = 24; + const uint8_t white_w = 10; + const uint8_t white_h = 40; + + const int8_t black_x = 6; + const int8_t black_y = -5; + const uint8_t black_w = 8; + const uint8_t black_h = 32; + + // white keys + for(size_t i = 0; i < 7; i++) { + if(is_white_note(state->note_record, i)) { + canvas->draw_box(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h); + } else { + canvas->draw_frame(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h); + } + } + + // black keys + for(size_t i = 0; i < 7; i++) { + if(i != 2 && i != 6) { + canvas->set_color(canvas, ColorWhite); + canvas->draw_box( + canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); + canvas->set_color(canvas, ColorBlack); + if(is_black_note(state->note_record, i)) { + canvas->draw_box( + canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); + } else { + canvas->draw_frame( + canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); + } + } + } + + // volume widget + x_pos = 124; + y_pos = 0; + const uint8_t volume_h = (64 / (state->volume_id_max - 1)) * state->volume_id; + canvas->draw_frame(canvas, x_pos, y_pos, 4, 64); + canvas->draw_box(canvas, x_pos, y_pos + (64 - volume_h), 4, volume_h); + + // note stack widget + x_pos = 73; + y_pos = 0; + canvas->set_color(canvas, ColorBlack); + canvas->set_font(canvas, FontPrimary); + canvas->draw_frame(canvas, x_pos, y_pos, 49, 64); + canvas->draw_line(canvas, x_pos + 28, 0, x_pos + 28, 64); + + for(uint8_t i = 0; i < note_stack_size; i++) { + if(i == 0) { + canvas->draw_box(canvas, x_pos, y_pos + 48, 49, 16); + canvas->set_color(canvas, ColorWhite); + } else { + canvas->set_color(canvas, ColorBlack); + } + canvas->draw_str(canvas, x_pos + 4, 64 - 16 * i - 3, get_note_name(state->note_stack[i])); + canvas->draw_str( + canvas, x_pos + 31, 64 - 16 * i - 3, get_note_len_name(state->note_stack[i])); + canvas->draw_line(canvas, x_pos, 64 - 16 * i, x_pos + 48, 64 - 16 * i); + } + + release_mutex((ValueMutex*)ctx, state); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + osMessageQueueId_t event_queue = (QueueHandle_t)ctx; + + MusicDemoEvent event; + event.type = EventTypeKey; + event.value.input = *input_event; + osMessageQueuePut(event_queue, &event, 0, 0); +} + +void process_note( + const MelodyEventRecord* note_record, + float bar_length_ms, + MusicDemoContext* context) { + MusicDemoEvent event; + // send note event + event.type = EventTypeNote; + event.value.note_record = note_record; + osMessageQueuePut(context->event_queue, &event, 0, 0); + + // read volume + State* state = (State*)acquire_mutex(context->state_mutex, 25); + float volume = volumes[state->volume_id]; + release_mutex(context->state_mutex, state); + + // play note + float note_delay = bar_length_ms / (float)note_record->length; + if(note_record->note != N) { + hal_pwm_set(volume, note_record->note, &SPEAKER_TIM, SPEAKER_CH); + } + delay(note_delay); + hal_pwm_stop(&SPEAKER_TIM, SPEAKER_CH); +} + +void music_player_thread(void* p) { + MusicDemoContext* context = (MusicDemoContext*)p; + + const float bpm = 130.0f; + // 4/4 + const float bar_length_ms = (60.0f * 1000.0f / bpm) * 4; + const uint16_t melody_start_events_count = sizeof(melody_start) / sizeof(melody_start[0]); + const uint16_t melody_loop_events_count = sizeof(melody_loop) / sizeof(melody_loop[0]); + + for(size_t i = 0; i < melody_start_events_count; i++) { + process_note(&melody_start[i], bar_length_ms, context); + } + + while(1) { + for(size_t i = 0; i < melody_loop_events_count; i++) { + process_note(&melody_loop[i], bar_length_ms, context); + } + } +} + +void music_player(void* p) { + osMessageQueueId_t event_queue = osMessageQueueNew(1, sizeof(MusicDemoEvent), NULL); + + State _state; + _state.note_record = NULL; + for(size_t i = 0; i < note_stack_size; i++) { + _state.note_stack[i] = NULL; + } + _state.volume_id = 1; + _state.volume_id_max = sizeof(volumes) / sizeof(volumes[0]); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, &_state, sizeof(State))) { + printf("cannot create mutex\n"); + furiac_exit(NULL); + } + + Widget* widget = widget_alloc(); + widget_draw_callback_set(widget, render_callback, &state_mutex); + widget_input_callback_set(widget, input_callback, event_queue); + + // Open GUI and register widget + GuiApi* gui = (GuiApi*)furi_open("gui"); + if(gui == NULL) { + printf("gui is not available\n"); + furiac_exit(NULL); + } + gui->add_widget(gui, widget, GuiLayerFullscreen); + + // open input record + PubSub* input_events_record = furi_open("input_events"); + // prepare "do nothing" event + InputEvent input_event = {InputRight, true}; + + // start player thread + // TODO change to fuirac_start + osThreadAttr_t player_attr = {.name = "music_player_thread", .stack_size = 512}; + MusicDemoContext context = {.state_mutex = &state_mutex, .event_queue = event_queue}; + osThreadId_t player = osThreadNew(music_player_thread, &context, &player_attr); + + MusicDemoEvent event; + while(1) { + osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100); + + State* state = (State*)acquire_mutex_block(&state_mutex); + + if(event_status == osOK) { + if(event.type == EventTypeKey) { + // press events + if(event.value.input.state && event.value.input.input == InputBack) { + } + + if(event.value.input.state && event.value.input.input == InputUp) { + if(state->volume_id < state->volume_id_max - 1) state->volume_id++; + } + + if(event.value.input.state && event.value.input.input == InputDown) { + if(state->volume_id > 0) state->volume_id--; + } + + if(event.value.input.state && event.value.input.input == InputLeft) { + } + + if(event.value.input.state && event.value.input.input == InputRight) { + } + + if(event.value.input.input == InputOk) { + } + + } else if(event.type == EventTypeNote) { + // send "do nothing" event to prevent display backlight off + notify_pubsub(input_events_record, &input_event); + + state->note_record = event.value.note_record; + + for(size_t i = note_stack_size - 1; i > 0; i--) { + state->note_stack[i] = state->note_stack[i - 1]; + } + state->note_stack[0] = state->note_record; + } + } else { + // event timeout + } + + widget_update(widget); + release_mutex(&state_mutex, state); + } +} diff --git a/applications/power/power.c b/applications/power/power.c index c8c0d5d6..bf4789f9 100644 --- a/applications/power/power.c +++ b/applications/power/power.c @@ -57,7 +57,6 @@ Power* power_alloc() { furi_check(power->menu_vm); power->cli = furi_open("cli"); - furi_check(power->cli); power->menu = menu_item_alloc_menu("Power", NULL); menu_item_subitem_add( @@ -97,7 +96,9 @@ void power_task(void* p) { (void)p; Power* power = power_alloc(); - cli_add_command(power->cli, "poweroff", power_cli_poweroff, power); + if(power->cli) { + cli_add_command(power->cli, "poweroff", power_cli_poweroff, power); + } FuriRecordSubscriber* gui_record = furi_open_deprecated("gui", false, false, NULL, NULL, NULL); assert(gui_record);