music_beeper_worker.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. #include "music_beeper_worker.h"
  2. #include <furi_hal.h>
  3. #include <furi.h>
  4. #include <storage/storage.h>
  5. #include <lib/flipper_format/flipper_format.h>
  6. #include <m-array.h>
  7. #define TAG "MusicBeeperWorker"
  8. #define MUSIC_BEEPER_FILETYPE "Flipper Music Format"
  9. #define MUSIC_BEEPER_VERSION 0
  10. #define SEMITONE_PAUSE 0xFF
  11. #define NOTE_C4 261.63f
  12. #define NOTE_C4_SEMITONE (4.0f * 12.0f)
  13. #define TWO_POW_TWELTH_ROOT 1.059463094359f
  14. typedef struct {
  15. uint8_t semitone;
  16. uint8_t duration;
  17. uint8_t dots;
  18. } NoteBlock;
  19. ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);
  20. struct MusicBeeperWorker {
  21. FuriThread* thread;
  22. bool should_work;
  23. MusicBeeperWorkerCallback callback;
  24. void* callback_context;
  25. float volume;
  26. uint32_t bpm;
  27. uint32_t duration;
  28. uint32_t octave;
  29. NoteBlockArray_t notes;
  30. };
  31. static int32_t music_beeper_worker_thread_callback(void* context) {
  32. furi_assert(context);
  33. MusicBeeperWorker* instance = context;
  34. NoteBlockArray_it_t it;
  35. NoteBlockArray_it(it, instance->notes);
  36. if(furi_hal_speaker_acquire(1000)) {
  37. while(instance->should_work) {
  38. if(NoteBlockArray_end_p(it)) {
  39. NoteBlockArray_it(it, instance->notes);
  40. furi_delay_ms(10);
  41. } else {
  42. NoteBlock* note_block = NoteBlockArray_ref(it);
  43. float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
  44. float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
  45. float duration = 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm /
  46. note_block->duration;
  47. uint32_t dots = note_block->dots;
  48. while(dots > 0) {
  49. duration += duration / 2;
  50. dots--;
  51. }
  52. uint32_t next_tick = furi_get_tick() + duration;
  53. float volume = instance->volume;
  54. if(instance->callback) {
  55. instance->callback(
  56. note_block->semitone,
  57. note_block->dots,
  58. note_block->duration,
  59. 0.0,
  60. instance->callback_context);
  61. }
  62. furi_hal_speaker_stop();
  63. furi_hal_speaker_start(frequency, volume);
  64. while(instance->should_work && furi_get_tick() < next_tick) {
  65. volume *= 1;
  66. furi_hal_speaker_set_volume(volume);
  67. furi_delay_ms(2);
  68. }
  69. NoteBlockArray_next(it);
  70. }
  71. }
  72. furi_hal_speaker_stop();
  73. furi_hal_speaker_release();
  74. } else {
  75. FURI_LOG_E(TAG, "Speaker system is busy with another process.");
  76. }
  77. return 0;
  78. }
  79. MusicBeeperWorker* music_beeper_worker_alloc() {
  80. MusicBeeperWorker* instance = malloc(sizeof(MusicBeeperWorker));
  81. NoteBlockArray_init(instance->notes);
  82. instance->thread = furi_thread_alloc();
  83. furi_thread_set_name(instance->thread, "MusicBeeperWorker");
  84. furi_thread_set_stack_size(instance->thread, 1024);
  85. furi_thread_set_context(instance->thread, instance);
  86. furi_thread_set_callback(instance->thread, music_beeper_worker_thread_callback);
  87. instance->volume = 1.0f;
  88. return instance;
  89. }
  90. void music_beeper_worker_clear(MusicBeeperWorker* instance) {
  91. NoteBlockArray_reset(instance->notes);
  92. }
  93. void music_beeper_worker_free(MusicBeeperWorker* instance) {
  94. furi_assert(instance);
  95. furi_thread_free(instance->thread);
  96. NoteBlockArray_clear(instance->notes);
  97. free(instance);
  98. }
  99. static bool is_digit(const char c) {
  100. return isdigit(c) != 0;
  101. }
  102. static bool is_letter(const char c) {
  103. return islower(c) != 0 || isupper(c) != 0;
  104. }
  105. static bool is_space(const char c) {
  106. return c == ' ' || c == '\t';
  107. }
  108. static size_t extract_number(const char* string, uint32_t* number) {
  109. size_t ret = 0;
  110. *number = 0;
  111. while(is_digit(*string)) {
  112. *number *= 10;
  113. *number += (*string - '0');
  114. string++;
  115. ret++;
  116. }
  117. return ret;
  118. }
  119. static size_t extract_dots(const char* string, uint32_t* number) {
  120. size_t ret = 0;
  121. *number = 0;
  122. while(*string == '.') {
  123. *number += 1;
  124. string++;
  125. ret++;
  126. }
  127. return ret;
  128. }
  129. static size_t extract_char(const char* string, char* symbol) {
  130. if(is_letter(*string)) {
  131. *symbol = *string;
  132. return 1;
  133. } else {
  134. return 0;
  135. }
  136. }
  137. static size_t extract_sharp(const char* string, char* symbol) {
  138. if(*string == '#' || *string == '_') {
  139. *symbol = '#';
  140. return 1;
  141. } else {
  142. return 0;
  143. }
  144. }
  145. static size_t skip_till(const char* string, const char symbol) {
  146. size_t ret = 0;
  147. while(*string != '\0' && *string != symbol) {
  148. string++;
  149. ret++;
  150. }
  151. if(*string != symbol) {
  152. ret = 0;
  153. }
  154. return ret;
  155. }
  156. static bool music_beeper_worker_add_note(
  157. MusicBeeperWorker* instance,
  158. uint8_t semitone,
  159. uint8_t duration,
  160. uint8_t dots) {
  161. NoteBlock note_block;
  162. note_block.semitone = semitone;
  163. note_block.duration = duration;
  164. note_block.dots = dots;
  165. NoteBlockArray_push_back(instance->notes, note_block);
  166. return true;
  167. }
  168. static int8_t note_to_semitone(const char note) {
  169. switch(note) {
  170. case 'C':
  171. return 0;
  172. // C#
  173. case 'D':
  174. return 2;
  175. // D#
  176. case 'E':
  177. return 4;
  178. case 'F':
  179. return 5;
  180. // F#
  181. case 'G':
  182. return 7;
  183. // G#
  184. case 'A':
  185. return 9;
  186. // A#
  187. case 'B':
  188. return 11;
  189. default:
  190. return 0;
  191. }
  192. }
  193. static bool music_beeper_worker_parse_notes(MusicBeeperWorker* instance, const char* string) {
  194. const char* cursor = string;
  195. bool result = true;
  196. while(*cursor != '\0') {
  197. if(!is_space(*cursor)) {
  198. uint32_t duration = 0;
  199. char note_char = '\0';
  200. char sharp_char = '\0';
  201. uint32_t octave = 0;
  202. uint32_t dots = 0;
  203. // Parsing
  204. cursor += extract_number(cursor, &duration);
  205. cursor += extract_char(cursor, &note_char);
  206. cursor += extract_sharp(cursor, &sharp_char);
  207. cursor += extract_number(cursor, &octave);
  208. cursor += extract_dots(cursor, &dots);
  209. // Post processing
  210. note_char = toupper(note_char);
  211. if(!duration) {
  212. duration = instance->duration;
  213. }
  214. if(!octave) {
  215. octave = instance->octave;
  216. }
  217. // Validation
  218. bool is_valid = true;
  219. is_valid &= (duration >= 1 && duration <= 128);
  220. is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P');
  221. is_valid &= (sharp_char == '#' || sharp_char == '\0');
  222. is_valid &= (octave <= 16);
  223. is_valid &= (dots <= 16);
  224. if(!is_valid) {
  225. FURI_LOG_E(
  226. TAG,
  227. "Invalid note: %lu%c%c%lu.%lu",
  228. duration,
  229. note_char == '\0' ? '_' : note_char,
  230. sharp_char == '\0' ? '_' : sharp_char,
  231. octave,
  232. dots);
  233. result = false;
  234. break;
  235. }
  236. // Note to semitones
  237. uint8_t semitone = 0;
  238. if(note_char == 'P') {
  239. semitone = SEMITONE_PAUSE;
  240. } else {
  241. semitone += octave * 12;
  242. semitone += note_to_semitone(note_char);
  243. semitone += sharp_char == '#' ? 1 : 0;
  244. }
  245. if(music_beeper_worker_add_note(instance, semitone, duration, dots)) {
  246. FURI_LOG_D(
  247. TAG,
  248. "Added note: %c%c%lu.%lu = %u %lu",
  249. note_char == '\0' ? '_' : note_char,
  250. sharp_char == '\0' ? '_' : sharp_char,
  251. octave,
  252. dots,
  253. semitone,
  254. duration);
  255. } else {
  256. FURI_LOG_E(
  257. TAG,
  258. "Invalid note: %c%c%lu.%lu = %u %lu",
  259. note_char == '\0' ? '_' : note_char,
  260. sharp_char == '\0' ? '_' : sharp_char,
  261. octave,
  262. dots,
  263. semitone,
  264. duration);
  265. }
  266. cursor += skip_till(cursor, ',');
  267. }
  268. if(*cursor != '\0') cursor++;
  269. }
  270. return result;
  271. }
  272. bool music_beeper_worker_load(MusicBeeperWorker* instance, const char* file_path) {
  273. furi_assert(instance);
  274. furi_assert(file_path);
  275. bool ret = false;
  276. if(strcasestr(file_path, ".fmf")) {
  277. ret = music_beeper_worker_load_fmf_from_file(instance, file_path);
  278. } else {
  279. ret = music_beeper_worker_load_rtttl_from_file(instance, file_path);
  280. }
  281. return ret;
  282. }
  283. bool music_beeper_worker_load_fmf_from_file(MusicBeeperWorker* instance, const char* file_path) {
  284. furi_assert(instance);
  285. furi_assert(file_path);
  286. bool result = false;
  287. FuriString* temp_str;
  288. temp_str = furi_string_alloc();
  289. Storage* storage = furi_record_open(RECORD_STORAGE);
  290. FlipperFormat* file = flipper_format_file_alloc(storage);
  291. do {
  292. if(!flipper_format_file_open_existing(file, file_path)) break;
  293. uint32_t version = 0;
  294. if(!flipper_format_read_header(file, temp_str, &version)) break;
  295. if(furi_string_cmp_str(temp_str, MUSIC_BEEPER_FILETYPE) ||
  296. (version != MUSIC_BEEPER_VERSION)) {
  297. FURI_LOG_E(TAG, "Incorrect file format or version");
  298. break;
  299. }
  300. if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) {
  301. FURI_LOG_E(TAG, "BPM is missing");
  302. break;
  303. }
  304. if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) {
  305. FURI_LOG_E(TAG, "Duration is missing");
  306. break;
  307. }
  308. if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) {
  309. FURI_LOG_E(TAG, "Octave is missing");
  310. break;
  311. }
  312. if(!flipper_format_read_string(file, "Notes", temp_str)) {
  313. FURI_LOG_E(TAG, "Notes is missing");
  314. break;
  315. }
  316. if(!music_beeper_worker_parse_notes(instance, furi_string_get_cstr(temp_str))) {
  317. break;
  318. }
  319. result = true;
  320. } while(false);
  321. furi_record_close(RECORD_STORAGE);
  322. flipper_format_free(file);
  323. furi_string_free(temp_str);
  324. return result;
  325. }
  326. bool music_beeper_worker_load_rtttl_from_file(MusicBeeperWorker* instance, const char* file_path) {
  327. furi_assert(instance);
  328. furi_assert(file_path);
  329. bool result = false;
  330. FuriString* content;
  331. content = furi_string_alloc();
  332. Storage* storage = furi_record_open(RECORD_STORAGE);
  333. File* file = storage_file_alloc(storage);
  334. do {
  335. if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
  336. FURI_LOG_E(TAG, "Unable to open file");
  337. break;
  338. };
  339. uint16_t ret = 0;
  340. do {
  341. uint8_t buffer[65] = {0};
  342. ret = storage_file_read(file, buffer, sizeof(buffer) - 1);
  343. for(size_t i = 0; i < ret; i++) {
  344. furi_string_push_back(content, buffer[i]);
  345. }
  346. } while(ret > 0);
  347. furi_string_trim(content);
  348. if(!furi_string_size(content)) {
  349. FURI_LOG_E(TAG, "Empty file");
  350. break;
  351. }
  352. if(!music_beeper_worker_load_rtttl_from_string(instance, furi_string_get_cstr(content))) {
  353. FURI_LOG_E(TAG, "Invalid file content");
  354. break;
  355. }
  356. result = true;
  357. } while(0);
  358. storage_file_free(file);
  359. furi_record_close(RECORD_STORAGE);
  360. furi_string_free(content);
  361. return result;
  362. }
  363. bool music_beeper_worker_load_rtttl_from_string(MusicBeeperWorker* instance, const char* string) {
  364. furi_assert(instance);
  365. const char* cursor = string;
  366. // Skip name
  367. cursor += skip_till(cursor, ':');
  368. if(*cursor != ':') {
  369. return false;
  370. }
  371. // Duration
  372. cursor += skip_till(cursor, '=');
  373. if(*cursor != '=') {
  374. return false;
  375. }
  376. cursor++;
  377. cursor += extract_number(cursor, &instance->duration);
  378. // Octave
  379. cursor += skip_till(cursor, '=');
  380. if(*cursor != '=') {
  381. return false;
  382. }
  383. cursor++;
  384. cursor += extract_number(cursor, &instance->octave);
  385. // BPM
  386. cursor += skip_till(cursor, '=');
  387. if(*cursor != '=') {
  388. return false;
  389. }
  390. cursor++;
  391. cursor += extract_number(cursor, &instance->bpm);
  392. // Notes
  393. cursor += skip_till(cursor, ':');
  394. if(*cursor != ':') {
  395. return false;
  396. }
  397. cursor++;
  398. if(!music_beeper_worker_parse_notes(instance, cursor)) {
  399. return false;
  400. }
  401. return true;
  402. }
  403. void music_beeper_worker_set_callback(
  404. MusicBeeperWorker* instance,
  405. MusicBeeperWorkerCallback callback,
  406. void* context) {
  407. furi_assert(instance);
  408. instance->callback = callback;
  409. instance->callback_context = context;
  410. }
  411. void music_beeper_worker_set_volume(MusicBeeperWorker* instance, float volume) {
  412. furi_assert(instance);
  413. instance->volume = volume;
  414. }
  415. void music_beeper_worker_start(MusicBeeperWorker* instance) {
  416. furi_assert(instance);
  417. furi_assert(instance->should_work == false);
  418. instance->should_work = true;
  419. furi_thread_start(instance->thread);
  420. }
  421. void music_beeper_worker_stop(MusicBeeperWorker* instance) {
  422. furi_assert(instance);
  423. furi_assert(instance->should_work == true);
  424. instance->should_work = false;
  425. furi_thread_join(instance->thread);
  426. }