| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504 |
- #include "doom_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_C4 261.63f
- #define NOTE_C4_SEMITONE (4.0f * 12.0f)
- #define TWO_POW_TWELTH_ROOT 1.059463094359f
- typedef struct {
- uint8_t semitone;
- uint8_t duration;
- uint8_t dots;
- } NoteBlock;
- ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);
- struct MusicPlayerWorker {
- FuriThread* thread;
- bool should_work;
- MusicPlayerWorkerCallback callback;
- void* callback_context;
- float volume;
- uint32_t bpm;
- uint32_t duration;
- uint32_t octave;
- NoteBlockArray_t notes;
- };
- 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);
- if(furi_hal_speaker_acquire(1000)) {
- while(instance->should_work) {
- if(NoteBlockArray_end_p(it)) {
- NoteBlockArray_it(it, instance->notes);
- furi_delay_ms(10);
- } else {
- NoteBlock* note_block = NoteBlockArray_ref(it);
- float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
- float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
- float duration = 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm /
- note_block->duration;
- uint32_t dots = note_block->dots;
- while(dots > 0) {
- duration += duration / 2;
- dots--;
- }
- uint32_t next_tick = furi_get_tick() + duration;
- float volume = instance->volume;
- if(instance->callback) {
- instance->callback(
- note_block->semitone,
- note_block->dots,
- note_block->duration,
- 0.0,
- instance->callback_context);
- }
- furi_hal_speaker_stop();
- furi_hal_speaker_start(frequency, volume);
- while(instance->should_work && furi_get_tick() < next_tick) {
- volume *= 0.9945679;
- furi_hal_speaker_set_volume(volume);
- furi_delay_ms(2);
- }
- NoteBlockArray_next(it);
- }
- }
- furi_hal_speaker_stop();
- furi_hal_speaker_release();
- } else {
- FURI_LOG_E(TAG, "Speaker system is busy with another process.");
- }
- 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);
- instance->volume = 1.0f;
- return instance;
- }
- void music_player_worker_free(MusicPlayerWorker* instance) {
- furi_assert(instance);
- furi_thread_free(instance->thread);
- NoteBlockArray_clear(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(is_digit(*string)) {
- *number *= 10;
- *number += (*string - '0');
- string++;
- ret++;
- }
- return ret;
- }
- static size_t extract_dots(const char* string, uint32_t* number) {
- size_t ret = 0;
- while(*string == '.') {
- *number += 1;
- 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 extract_sharp(const char* string, char* symbol) {
- if(*string == '#' || *string == '_') {
- *symbol = '#';
- return 1;
- } else {
- return 0;
- }
- }
- static size_t skip_till(const char* string, const char symbol) {
- size_t ret = 0;
- while(*string != '\0' && *string != symbol) {
- string++;
- ret++;
- }
- if(*string != symbol) {
- ret = 0;
- }
- return ret;
- }
- static bool music_player_worker_add_note(
- MusicPlayerWorker* instance,
- uint8_t semitone,
- uint8_t duration,
- uint8_t dots) {
- NoteBlock note_block;
- note_block.semitone = semitone;
- note_block.duration = duration;
- note_block.dots = dots;
- NoteBlockArray_push_back(instance->notes, note_block);
- return true;
- }
- static int8_t note_to_semitone(const char note) {
- switch(note) {
- case 'C':
- return 0;
- // C#
- case 'D':
- return 2;
- // D#
- case 'E':
- return 4;
- case 'F':
- return 5;
- // F#
- case 'G':
- return 7;
- // G#
- case 'A':
- return 9;
- // A#
- case 'B':
- return 11;
- default:
- return 0;
- }
- }
- static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const char* string) {
- const char* cursor = string;
- 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;
- uint32_t dots = 0;
- // Parsing
- cursor += extract_number(cursor, &duration);
- cursor += extract_char(cursor, ¬e_char);
- cursor += extract_sharp(cursor, &sharp_char);
- cursor += extract_number(cursor, &octave);
- cursor += extract_dots(cursor, &dots);
- // 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 <= 16);
- is_valid &= (dots <= 16);
- if(!is_valid) {
- FURI_LOG_E(
- TAG,
- "Invalid note: %lu%c%c%lu.%lu",
- duration,
- note_char == '\0' ? '_' : note_char,
- sharp_char == '\0' ? '_' : sharp_char,
- octave,
- dots);
- 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, dots)) {
- FURI_LOG_D(
- TAG,
- "Added note: %c%c%lu.%lu = %u %lu",
- note_char == '\0' ? '_' : note_char,
- sharp_char == '\0' ? '_' : sharp_char,
- octave,
- dots,
- semitone,
- duration);
- } else {
- FURI_LOG_E(
- TAG,
- "Invalid note: %c%c%lu.%lu = %u %lu",
- note_char == '\0' ? '_' : note_char,
- sharp_char == '\0' ? '_' : sharp_char,
- octave,
- dots,
- semitone,
- duration);
- }
- cursor += skip_till(cursor, ',');
- }
- if(*cursor != '\0') cursor++;
- }
- return result;
- }
- bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path) {
- furi_assert(instance);
- furi_assert(file_path);
- bool ret = false;
- if(strcasestr(file_path, ".fmf")) {
- ret = music_player_worker_load_fmf_from_file(instance, file_path);
- } else {
- ret = music_player_worker_load_rtttl_from_file(instance, file_path);
- }
- return ret;
- }
- bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path) {
- furi_assert(instance);
- furi_assert(file_path);
- bool result = false;
- FuriString* temp_str;
- temp_str = furi_string_alloc();
- Storage* storage = furi_record_open(RECORD_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(furi_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, furi_string_get_cstr(temp_str))) {
- break;
- }
- result = true;
- } while(false);
- furi_record_close(RECORD_STORAGE);
- flipper_format_free(file);
- furi_string_free(temp_str);
- return result;
- }
- bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path) {
- furi_assert(instance);
- furi_assert(file_path);
- bool result = false;
- FuriString* content;
- content = furi_string_alloc();
- Storage* storage = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(storage);
- do {
- if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
- FURI_LOG_E(TAG, "Unable to open file");
- break;
- };
- uint16_t ret = 0;
- do {
- uint8_t buffer[65] = {0};
- ret = storage_file_read(file, buffer, sizeof(buffer) - 1);
- for(size_t i = 0; i < ret; i++) {
- furi_string_push_back(content, buffer[i]);
- }
- } while(ret > 0);
- furi_string_trim(content);
- if(!furi_string_size(content)) {
- FURI_LOG_E(TAG, "Empty file");
- break;
- }
- if(!music_player_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) {
- FURI_LOG_E(TAG, "Invalid file content");
- break;
- }
- result = true;
- } while(0);
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
- furi_string_free(content);
- return result;
- }
- bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string) {
- furi_assert(instance);
- const char* cursor = string;
- // Skip name
- cursor += skip_till(cursor, ':');
- if(*cursor != ':') {
- return false;
- }
- // Duration
- cursor += skip_till(cursor, '=');
- if(*cursor != '=') {
- return false;
- }
- cursor++;
- cursor += extract_number(cursor, &instance->duration);
- // Octave
- cursor += skip_till(cursor, '=');
- if(*cursor != '=') {
- return false;
- }
- cursor++;
- cursor += extract_number(cursor, &instance->octave);
- // BPM
- cursor += skip_till(cursor, '=');
- if(*cursor != '=') {
- return false;
- }
- cursor++;
- cursor += extract_number(cursor, &instance->bpm);
- // Notes
- cursor += skip_till(cursor, ':');
- if(*cursor != ':') {
- return false;
- }
- cursor++;
- if(!music_player_worker_parse_notes(instance, cursor)) {
- return false;
- }
- return true;
- }
- void music_player_worker_set_callback(
- MusicPlayerWorker* instance,
- MusicPlayerWorkerCallback callback,
- void* context) {
- furi_assert(instance);
- instance->callback = callback;
- instance->callback_context = context;
- }
- void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume) {
- furi_assert(instance);
- instance->volume = volume;
- }
- 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);
- }
|