Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Music player rework #1189

Merged
merged 14 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Music player: cli tool and new worker
  • Loading branch information
skotopes committed May 2, 2022
commit e74e331cddcefe9a0be793616913ae0d55dc5d18
5 changes: 5 additions & 0 deletions applications/applications.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ extern void crypto_on_system_start();
extern void ibutton_on_system_start();
extern void infrared_on_system_start();
extern void lfrfid_on_system_start();
extern void music_player_on_system_start();
extern void nfc_on_system_start();
extern void storage_on_system_start();
extern void subghz_on_system_start();
Expand Down Expand Up @@ -280,6 +281,10 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = {
infrared_on_system_start,
#endif

#ifdef APP_MUSIC_PLAYER
music_player_on_system_start,
#endif

#ifdef APP_NFC
nfc_on_system_start,
#endif
Expand Down
35 changes: 35 additions & 0 deletions applications/music_player/music_player_cli.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <furi.h>
#include <cli/cli.h>
#include "music_player_worker.h"

static void music_player_cli(Cli* cli, string_t args, void* context) {
MusicPlayerWorker* music_player_worker = music_player_worker_alloc();

do {
if(!music_player_worker_load(music_player_worker, string_get_cstr(args))) {
printf("Failed to open file %s", string_get_cstr(args));
break;
}

printf("Press CTRL+C to stop");
music_player_worker_start(music_player_worker);
while(!cli_cmd_interrupt_received(cli)) {
osDelay(50);
}
music_player_worker_stop(music_player_worker);
} while(0);

music_player_worker_free(music_player_worker);
}

void music_player_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open("cli");

cli_add_command(cli, "music_player", CliCommandFlagDefault, music_player_cli, NULL);

furi_record_close("cli");
#else
UNUSED(music_player_cli);
#endif
}
289 changes: 289 additions & 0 deletions applications/music_player/music_player_worker.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
#include "music_player_worker.h"

#include <furi_hal.h>
#include <furi.h>

#include <storage/storage.h>
#include <lib/flipper_format/flipper_format.h>

#include <m-array.h>

#define TAG "MusicPlayerWorker"

#define MUSIC_PLAYER_FILETYPE "Flipper Music Format"
#define MUSIC_PLAYER_VERSION 0

#define SEMITONE_PAUSE 0xFF

#define NOTE_A4 440.0f
#define NOTE_A4_SEMITONE (4.0f * 12.0f)
#define TWO_POW_TWELTH_ROOT 1.059463094359f

typedef struct {
uint8_t semitone;
uint8_t duration;
} NoteBlock;

ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);

struct MusicPlayerWorker {
NoteBlockArray_t notes;
FuriThread* thread;
uint32_t bpm;
uint32_t duration;
uint32_t octave;
bool should_work;
};

static int32_t music_player_worker_thread_callback(void* context) {
furi_assert(context);
MusicPlayerWorker* instance = context;

NoteBlockArray_it_t it;
NoteBlockArray_it(it, instance->notes);

while(instance->should_work) {
if(NoteBlockArray_end_p(it)) {
NoteBlockArray_it(it, instance->notes);
osDelay(10);
} else {
NoteBlock* note_block = NoteBlockArray_ref(it);

float note_from_a4 = (float)note_block->semitone - NOTE_A4_SEMITONE;
float frequency = NOTE_A4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
float duration =
60.0 * osKernelGetTickFreq() * 4 / instance->bpm / note_block->duration;

furi_hal_speaker_start(frequency, 1);
osDelay(duration);
furi_hal_speaker_stop();

NoteBlockArray_next(it);
}
}

return 0;
}

MusicPlayerWorker* music_player_worker_alloc() {
MusicPlayerWorker* instance = malloc(sizeof(MusicPlayerWorker));

NoteBlockArray_init(instance->notes);

instance->thread = furi_thread_alloc();
furi_thread_set_name(instance->thread, "MusicPlayerWorker");
furi_thread_set_stack_size(instance->thread, 1024);
furi_thread_set_context(instance->thread, instance);
furi_thread_set_callback(instance->thread, music_player_worker_thread_callback);

return instance;
}

void music_player_worker_free(MusicPlayerWorker* instance) {
furi_assert(instance);
furi_thread_free(instance->thread);
NoteBlockArray_reset(instance->notes);
free(instance);
}

static bool is_digit(const char c) {
return isdigit(c) != 0;
}

static bool is_letter(const char c) {
return islower(c) != 0 || isupper(c) != 0;
}

static bool is_space(const char c) {
return c == ' ' || c == '\t';
}

static size_t extract_number(const char* string, uint32_t* number) {
size_t ret = 0;
while(*string != '\0' && is_digit(*string)) {
*number *= 10;
*number += (*string - '0');
string++;
ret++;
}
return ret;
}

static size_t extract_char(const char* string, char* symbol) {
if(is_letter(*string)) {
*symbol = *string;
return 1;
} else {
return 0;
}
}

static size_t skip_till_comma(const char* string) {
size_t ret = 0;
while(*string != '\0' && *string != ',') {
string++;
ret++;
}
return ret;
}

static bool
music_player_worker_add_note(MusicPlayerWorker* instance, uint8_t semitone, uint8_t duration) {
NoteBlock note_block;
note_block.semitone = semitone;
note_block.duration = duration;

NoteBlockArray_push_back(instance->notes, note_block);

return true;
}

static uint8_t note_to_semitone(const char note) {
switch(note) {
case 'A':
return 0;
case 'B':
return 2;
case 'C':
return 3;
case 'D':
return 5;
case 'E':
return 7;
case 'F':
return 8;
case 'G':
return 10;
default:
return 0;
}
}

static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const char* cursor) {
bool result = true;

while(*cursor != '\0') {
if(!is_space(*cursor)) {
uint32_t duration = 0;
char note_char = '\0';
char sharp_char = '\0';
uint32_t octave = 0;

// Parsing
cursor += extract_number(cursor, &duration);
cursor += extract_char(cursor, &note_char);
cursor += extract_char(cursor, &sharp_char);
cursor += extract_number(cursor, &octave);

// Post processing
note_char = toupper(note_char);
if(!duration) {
duration = instance->duration;
}
if(!octave) {
octave = instance->octave;
}

// Validation
bool is_valid = true;
is_valid &= (duration >= 1 && duration <= 128);
is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P');
is_valid &= (sharp_char == '#' || sharp_char == '\0');
is_valid &= (octave >= 0 && octave <= 16);
if(!is_valid) {
FURI_LOG_D(TAG, "Invalid note definition");
result = false;
break;
}

// Note to semitones
uint8_t semitone = 0;
if(note_char == 'P') {
semitone = SEMITONE_PAUSE;
} else {
semitone += octave * 12;
semitone += note_to_semitone(note_char);
semitone += sharp_char == '#' ? 1 : 0;
}

if(music_player_worker_add_note(instance, semitone, duration)) {
FURI_LOG_D(TAG, "Added note: %u %u", semitone, duration);
} else {
FURI_LOG_E(TAG, "Invalid note: %u %u", semitone, duration);
}

cursor += skip_till_comma(cursor);
}
cursor++;
}

return result;
}

bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path) {
furi_assert(instance);
furi_assert(file_path);

bool result = false;
string_t temp_str;
string_init(temp_str);

Storage* storage = furi_record_open("storage");
FlipperFormat* file = flipper_format_file_alloc(storage);

do {
if(!flipper_format_file_open_existing(file, file_path)) break;

uint32_t version = 0;
if(!flipper_format_read_header(file, temp_str, &version)) break;
if(string_cmp_str(temp_str, MUSIC_PLAYER_FILETYPE) || (version != MUSIC_PLAYER_VERSION)) {
FURI_LOG_E(TAG, "Incorrect file format or version");
break;
}

if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) {
FURI_LOG_E(TAG, "BPM is missing");
break;
}
if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) {
FURI_LOG_E(TAG, "Duration is missing");
break;
}
if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) {
FURI_LOG_E(TAG, "Octave is missing");
break;
}
if(!flipper_format_read_string(file, "Notes", temp_str)) {
FURI_LOG_E(TAG, "Notes is missing");
break;
}

if(!music_player_worker_parse_notes(instance, string_get_cstr(temp_str))) {
break;
}

result = true;
} while(false);

furi_record_close("storage");
flipper_format_free(file);
string_clear(temp_str);

return result;
}

void music_player_worker_start(MusicPlayerWorker* instance) {
furi_assert(instance);
furi_assert(instance->should_work == false);

instance->should_work = true;
furi_thread_start(instance->thread);
}

void music_player_worker_stop(MusicPlayerWorker* instance) {
furi_assert(instance);
furi_assert(instance->should_work == true);

instance->should_work = false;
furi_thread_join(instance->thread);
}
15 changes: 15 additions & 0 deletions applications/music_player/music_player_worker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <stdbool.h>

typedef struct MusicPlayerWorker MusicPlayerWorker;

MusicPlayerWorker* music_player_worker_alloc();

void music_player_worker_free(MusicPlayerWorker* instance);

bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path);

void music_player_worker_start(MusicPlayerWorker* instance);

void music_player_worker_stop(MusicPlayerWorker* instance);
4 changes: 3 additions & 1 deletion assets/resources/Manifest
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
V:0
T:1651076680
T:1651524332
D:badusb
D:dolphin
D:infrared
D:music_player
D:nfc
D:subghz
D:u2f
Expand Down Expand Up @@ -223,6 +224,7 @@ F:f267f0654781049ca323b11bb4375519:581:dolphin/L3_Lab_research_128x54/frame_9.bm
F:41106c0cbc5144f151b2b2d3daaa0527:727:dolphin/L3_Lab_research_128x54/meta.txt
D:infrared/assets
F:d895fda2f48c6cc4c55e8a398ff52e43:74300:infrared/assets/tv.ir
F:a157a80f5a668700403d870c23b9567d:470:music_player/Marble_Machine.fmf
D:nfc/assets
F:c6826a621d081d68309e4be424d3d974:4715:nfc/assets/aid.nfc
F:86efbebdf41bb6bf15cc51ef88f069d5:2565:nfc/assets/country_code.nfc
Expand Down
6 changes: 6 additions & 0 deletions assets/resources/music_player/Marble_Machine.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Filetype: Flipper Music Format
Version: 0
BPM: 130
Duration: 8
Octave: 5
Notes: E6, P, E, B, 4P, E, A, G, A, E, B, P, G, A, D6, 4P, D, B, 4P, D, A, G, A, D, F#, P, G, A, D6, 4P, F#, B, 4P, F#, D6, C6, B, F#, A, P, G, F#, E, P, C, E, B, B4, C, D, D6, C6, B, F#, A, P, G, A, E6, 4P, E, B, 4P, E, A, G, A, E, B, P, G, A, D6, 4P, D, B, 4P, D, A, G, A, D, F#, P, G, A, D6, 4P, F#, B, 4P, F#, D6, C6, B, F#, A, P, G, F#, E, P, C, E, B, B4, C, D, D6, C6, B, F#, A, P, G, A, E6