Просмотр исходного кода

simple music player app (#240)

* split power-cli dependence
* music_player application
* add player to release mode
* fix stupid error
DrZlo13 5 лет назад
Родитель
Сommit
3d6af91dd1

+ 9 - 0
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
 };

+ 12 - 0
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

+ 444 - 0
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);
+    }
+}

+ 3 - 2
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);