tuning_fork.c 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <input/input.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <gui/canvas.h>
  7. #include <gui/elements.h>
  8. #include <gui/gui.h>
  9. #include <notification/notification.h>
  10. #include <notification/notification_messages.h>
  11. #include "tunings.h"
  12. #include "instruments.h"
  13. #define VOLUME_STEP 0.1f
  14. #define DEFAULT_VOLUME 1.0f
  15. #define QUEUE_SIZE 8
  16. #define SPEAKER_ACQUIRE_TIMEOUT 1000
  17. typedef enum {
  18. EventTypeTick,
  19. EventTypeKey,
  20. } EventType;
  21. typedef struct {
  22. EventType type;
  23. InputEvent input;
  24. } PluginEvent;
  25. enum Page {
  26. InstrumentsPage,
  27. VariationsPage,
  28. TuningsPage,
  29. NotesPage
  30. };
  31. typedef struct {
  32. FuriMutex* mutex;
  33. bool playing;
  34. enum Page page;
  35. float volume;
  36. int current_instrument_index;
  37. int current_variation_index;
  38. int current_tuning_index;
  39. int current_tuning_note_index;
  40. INSTRUMENT instrument;
  41. VARIATION variation;
  42. TUNING tuning;
  43. } TuningForkState;
  44. // Getters
  45. static INSTRUMENT current_instrument(TuningForkState* tuningForkState) {
  46. return tuningForkState->instrument;
  47. }
  48. static VARIATION current_variation(TuningForkState* tuningForkState) {
  49. return tuningForkState->variation;
  50. }
  51. static TUNING current_tuning(TuningForkState* tuningForkState) {
  52. return tuningForkState->tuning;
  53. }
  54. static NOTE current_tuning_note(TuningForkState* tuningForkState) {
  55. return current_tuning(tuningForkState).notes[tuningForkState->current_tuning_note_index];
  56. }
  57. static float current_tuning_note_freq(TuningForkState* tuningForkState) {
  58. return current_tuning_note(tuningForkState).frequency;
  59. }
  60. // String helper
  61. static void safe_string_copy(char* dest, const char* src, size_t size) {
  62. if(dest && src) {
  63. strncpy(dest, src, size - 1);
  64. dest[size - 1] = '\0';
  65. }
  66. }
  67. // Labels
  68. static void current_instrument_label(TuningForkState* tuningForkState, char* outCategoryLabel) {
  69. if(outCategoryLabel) {
  70. safe_string_copy(
  71. outCategoryLabel, current_instrument(tuningForkState).label, MAX_LABEL_LENGTH);
  72. }
  73. }
  74. static void current_variation_label(TuningForkState* tuningForkState, char* outCategoryLabel) {
  75. if(outCategoryLabel) {
  76. safe_string_copy(
  77. outCategoryLabel, current_variation(tuningForkState).label, MAX_LABEL_LENGTH);
  78. }
  79. }
  80. static void current_tuning_label(TuningForkState* tuningForkState, char* outTuningLabel) {
  81. if(outTuningLabel) {
  82. safe_string_copy(outTuningLabel, current_tuning(tuningForkState).label, MAX_LABEL_LENGTH);
  83. }
  84. }
  85. static void current_tuning_note_label(TuningForkState* tuningForkState, char* outNoteLabel) {
  86. if(outNoteLabel) {
  87. safe_string_copy(
  88. outNoteLabel, current_tuning_note(tuningForkState).label, MAX_LABEL_LENGTH);
  89. }
  90. }
  91. // Update references
  92. static void updateTuning(TuningForkState* tuning_fork_state) {
  93. tuning_fork_state->instrument = Instruments[tuning_fork_state->current_instrument_index];
  94. tuning_fork_state->variation =
  95. tuning_fork_state->instrument.variations[tuning_fork_state->current_variation_index];
  96. tuning_fork_state->tuning =
  97. tuning_fork_state->variation.tunings[tuning_fork_state->current_tuning_index];
  98. tuning_fork_state->current_tuning_note_index = 0;
  99. }
  100. // Instruments navigation
  101. static void next_instrument(TuningForkState* tuning_fork_state) {
  102. if(tuning_fork_state->current_instrument_index == INSTRUMENTS_COUNT - 1) {
  103. tuning_fork_state->current_instrument_index = 0;
  104. } else {
  105. tuning_fork_state->current_instrument_index += 1;
  106. }
  107. tuning_fork_state->current_variation_index = 0;
  108. tuning_fork_state->current_tuning_index = 0;
  109. updateTuning(tuning_fork_state);
  110. }
  111. static void prev_instrument(TuningForkState* tuning_fork_state) {
  112. if(tuning_fork_state->current_instrument_index == 0) {
  113. tuning_fork_state->current_instrument_index = INSTRUMENTS_COUNT - 1;
  114. } else {
  115. tuning_fork_state->current_instrument_index -= 1;
  116. }
  117. tuning_fork_state->current_variation_index = 0;
  118. tuning_fork_state->current_tuning_index = 0;
  119. updateTuning(tuning_fork_state);
  120. }
  121. // Variations navigation
  122. static void next_variation(TuningForkState* tuning_fork_state) {
  123. if(tuning_fork_state->current_variation_index ==
  124. tuning_fork_state->instrument.variations_count - 1) {
  125. tuning_fork_state->current_variation_index = 0;
  126. } else {
  127. tuning_fork_state->current_variation_index += 1;
  128. }
  129. updateTuning(tuning_fork_state);
  130. }
  131. static void prev_variation(TuningForkState* tuning_fork_state) {
  132. if(tuning_fork_state->current_variation_index == 0) {
  133. tuning_fork_state->current_variation_index =
  134. tuning_fork_state->instrument.variations_count - 1;
  135. } else {
  136. tuning_fork_state->current_variation_index -= 1;
  137. }
  138. updateTuning(tuning_fork_state);
  139. }
  140. // Tunings navigation
  141. static void next_tuning(TuningForkState* tuning_fork_state) {
  142. if(tuning_fork_state->current_tuning_index == tuning_fork_state->variation.tunings_count - 1) {
  143. tuning_fork_state->current_tuning_index = 0;
  144. } else {
  145. tuning_fork_state->current_tuning_index += 1;
  146. }
  147. updateTuning(tuning_fork_state);
  148. }
  149. static void prev_tuning(TuningForkState* tuning_fork_state) {
  150. if(tuning_fork_state->current_tuning_index == 0) {
  151. tuning_fork_state->current_tuning_index = tuning_fork_state->variation.tunings_count - 1;
  152. } else {
  153. tuning_fork_state->current_tuning_index -= 1;
  154. }
  155. updateTuning(tuning_fork_state);
  156. }
  157. // Notes navigation
  158. static void next_note(TuningForkState* tuning_fork_state) {
  159. if(tuning_fork_state->current_tuning_note_index ==
  160. current_tuning(tuning_fork_state).notes_count - 1) {
  161. tuning_fork_state->current_tuning_note_index = 0;
  162. } else {
  163. tuning_fork_state->current_tuning_note_index += 1;
  164. }
  165. }
  166. static void prev_note(TuningForkState* tuning_fork_state) {
  167. if(tuning_fork_state->current_tuning_note_index == 0) {
  168. tuning_fork_state->current_tuning_note_index =
  169. current_tuning(tuning_fork_state).notes_count - 1;
  170. } else {
  171. tuning_fork_state->current_tuning_note_index -= 1;
  172. }
  173. }
  174. // Volume adjustments
  175. static void increase_volume(TuningForkState* tuning_fork_state) {
  176. if(tuning_fork_state->volume <= (1.0f - VOLUME_STEP)) {
  177. tuning_fork_state->volume += VOLUME_STEP;
  178. }
  179. }
  180. static void decrease_volume(TuningForkState* tuning_fork_state) {
  181. if(tuning_fork_state->volume >= VOLUME_STEP) {
  182. tuning_fork_state->volume -= VOLUME_STEP;
  183. }
  184. }
  185. // Player
  186. static void play(TuningForkState* tuning_fork_state) {
  187. if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
  188. furi_hal_speaker_start(
  189. current_tuning_note_freq(tuning_fork_state), tuning_fork_state->volume);
  190. }
  191. }
  192. static void stop() {
  193. if(furi_hal_speaker_is_mine()) {
  194. furi_hal_speaker_stop();
  195. furi_hal_speaker_release();
  196. }
  197. }
  198. static void replay(TuningForkState* tuning_fork_state) {
  199. stop();
  200. play(tuning_fork_state);
  201. }
  202. // Renderer
  203. static void render_callback(Canvas* const canvas, void* ctx) {
  204. furi_assert(ctx);
  205. TuningForkState* tuning_fork_state = ctx;
  206. furi_mutex_acquire(tuning_fork_state->mutex, FuriWaitForever);
  207. FuriString* tempStr = furi_string_alloc();
  208. canvas_draw_frame(canvas, 0, 0, 128, 64);
  209. canvas_set_font(canvas, FontPrimary);
  210. if(tuning_fork_state->page == InstrumentsPage) {
  211. char instrumentLabel[MAX_LABEL_LENGTH];
  212. current_instrument_label(tuning_fork_state, instrumentLabel);
  213. furi_string_printf(tempStr, "< %s >", instrumentLabel);
  214. canvas_draw_str_aligned(
  215. canvas, 64, 28, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
  216. } else if(tuning_fork_state->page == VariationsPage) {
  217. char instrumentLabel[MAX_LABEL_LENGTH];
  218. char variationLabel[MAX_LABEL_LENGTH];
  219. current_instrument_label(tuning_fork_state, instrumentLabel);
  220. current_variation_label(tuning_fork_state, variationLabel);
  221. furi_string_printf(tempStr, "%s", instrumentLabel);
  222. canvas_draw_str_aligned(canvas, 4, 4, AlignLeft, AlignTop, furi_string_get_cstr(tempStr));
  223. furi_string_reset(tempStr);
  224. furi_string_printf(tempStr, "< %s >", variationLabel);
  225. canvas_draw_str_aligned(
  226. canvas, 64, 28, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
  227. } else if(tuning_fork_state->page == TuningsPage) {
  228. char instrumentLabel[MAX_LABEL_LENGTH];
  229. char variationLabel[MAX_LABEL_LENGTH];
  230. char tuningLabel[MAX_LABEL_LENGTH];
  231. current_instrument_label(tuning_fork_state, instrumentLabel);
  232. current_variation_label(tuning_fork_state, variationLabel);
  233. current_tuning_label(tuning_fork_state, tuningLabel);
  234. furi_string_printf(tempStr, "%s", instrumentLabel);
  235. canvas_draw_str_aligned(canvas, 4, 4, AlignLeft, AlignTop, furi_string_get_cstr(tempStr));
  236. furi_string_reset(tempStr);
  237. furi_string_printf(tempStr, "%s", variationLabel);
  238. canvas_draw_str_aligned(
  239. canvas, 124, 4, AlignRight, AlignTop, furi_string_get_cstr(tempStr));
  240. furi_string_reset(tempStr);
  241. furi_string_printf(tempStr, "< %s >", tuningLabel);
  242. canvas_draw_str_aligned(
  243. canvas, 64, 28, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
  244. } else {
  245. char instrumentLabel[MAX_LABEL_LENGTH];
  246. char variationLabel[MAX_LABEL_LENGTH];
  247. char tuningLabel[MAX_LABEL_LENGTH];
  248. current_instrument_label(tuning_fork_state, instrumentLabel);
  249. current_variation_label(tuning_fork_state, variationLabel);
  250. current_tuning_label(tuning_fork_state, tuningLabel);
  251. furi_string_printf(tempStr, "%s", instrumentLabel);
  252. canvas_draw_str_aligned(canvas, 4, 4, AlignLeft, AlignTop, furi_string_get_cstr(tempStr));
  253. furi_string_reset(tempStr);
  254. furi_string_printf(tempStr, "%s", variationLabel);
  255. canvas_draw_str_aligned(
  256. canvas, 124, 4, AlignRight, AlignTop, furi_string_get_cstr(tempStr));
  257. furi_string_reset(tempStr);
  258. furi_string_printf(tempStr, "%s", tuningLabel);
  259. canvas_draw_str_aligned(
  260. canvas, 64, 20, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
  261. furi_string_reset(tempStr);
  262. char tuningNoteLabel[MAX_LABEL_LENGTH];
  263. current_tuning_note_label(tuning_fork_state, tuningNoteLabel);
  264. furi_string_printf(tempStr, "< %s >", tuningNoteLabel);
  265. canvas_draw_str_aligned(
  266. canvas, 64, 32, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
  267. furi_string_reset(tempStr);
  268. }
  269. canvas_set_font(canvas, FontSecondary);
  270. elements_button_left(canvas, "Prev");
  271. elements_button_right(canvas, "Next");
  272. if(tuning_fork_state->page == NotesPage) {
  273. if(tuning_fork_state->playing) {
  274. elements_button_center(canvas, "Stop");
  275. } else {
  276. elements_button_center(canvas, "Play");
  277. }
  278. } else {
  279. elements_button_center(canvas, "Select");
  280. }
  281. if(tuning_fork_state->page == NotesPage) {
  282. elements_progress_bar(canvas, 8, 40, 112, tuning_fork_state->volume);
  283. }
  284. furi_string_free(tempStr);
  285. furi_mutex_release(tuning_fork_state->mutex);
  286. }
  287. static void input_callback(InputEvent* input_event, void* ctx) {
  288. furi_assert(ctx);
  289. FuriMessageQueue* event_queue = ctx;
  290. PluginEvent event = {.type = EventTypeKey, .input = *input_event};
  291. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  292. }
  293. static void tuning_fork_state_init(TuningForkState* const tuning_fork_state) {
  294. if(tuning_fork_state) {
  295. tuning_fork_state->playing = false;
  296. tuning_fork_state->page = InstrumentsPage;
  297. tuning_fork_state->volume = DEFAULT_VOLUME;
  298. tuning_fork_state->current_instrument_index = 0;
  299. tuning_fork_state->current_variation_index = 0;
  300. tuning_fork_state->current_tuning_index = 0;
  301. tuning_fork_state->current_tuning_note_index = 0;
  302. updateTuning(tuning_fork_state);
  303. }
  304. }
  305. int32_t tuning_fork_app() {
  306. FuriMessageQueue* event_queue = furi_message_queue_alloc(QUEUE_SIZE, sizeof(PluginEvent));
  307. if(!event_queue) {
  308. FURI_LOG_E("TuningFork", "Cannot create message queue\r\n");
  309. return 255;
  310. }
  311. TuningForkState* tuning_fork_state = malloc(sizeof(TuningForkState));
  312. if(!tuning_fork_state) {
  313. FURI_LOG_E("TuningFork", "Cannot allocate state\r\n");
  314. furi_message_queue_free(event_queue);
  315. return 255;
  316. }
  317. tuning_fork_state_init(tuning_fork_state);
  318. tuning_fork_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  319. if(!tuning_fork_state->mutex) {
  320. FURI_LOG_E("TuningFork", "cannot create mutex\r\n");
  321. free(tuning_fork_state);
  322. furi_message_queue_free(event_queue);
  323. return 255;
  324. }
  325. // Set system callbacks
  326. ViewPort* view_port = view_port_alloc();
  327. view_port_draw_callback_set(view_port, render_callback, tuning_fork_state);
  328. view_port_input_callback_set(view_port, input_callback, event_queue);
  329. Gui* gui = furi_record_open("gui");
  330. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  331. PluginEvent event;
  332. for(bool processing = true; processing;) {
  333. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  334. furi_mutex_acquire(tuning_fork_state->mutex, FuriWaitForever);
  335. if(event_status == FuriStatusOk) {
  336. if(event.type == EventTypeKey) {
  337. if(event.input.type == InputTypeShort) {
  338. // push events
  339. switch(event.input.key) {
  340. case InputKeyUp:
  341. if(tuning_fork_state->page == NotesPage) {
  342. increase_volume(tuning_fork_state);
  343. if(tuning_fork_state->playing) {
  344. replay(tuning_fork_state);
  345. }
  346. }
  347. break;
  348. case InputKeyDown:
  349. if(tuning_fork_state->page == NotesPage) {
  350. decrease_volume(tuning_fork_state);
  351. if(tuning_fork_state->playing) {
  352. replay(tuning_fork_state);
  353. }
  354. }
  355. break;
  356. case InputKeyRight:
  357. if(tuning_fork_state->page == InstrumentsPage) {
  358. next_instrument(tuning_fork_state);
  359. } else if(tuning_fork_state->page == VariationsPage) {
  360. next_variation(tuning_fork_state);
  361. } else if(tuning_fork_state->page == TuningsPage) {
  362. next_tuning(tuning_fork_state);
  363. } else {
  364. next_note(tuning_fork_state);
  365. if(tuning_fork_state->playing) {
  366. replay(tuning_fork_state);
  367. }
  368. }
  369. break;
  370. case InputKeyLeft:
  371. if(tuning_fork_state->page == InstrumentsPage) {
  372. prev_instrument(tuning_fork_state);
  373. } else if(tuning_fork_state->page == VariationsPage) {
  374. prev_variation(tuning_fork_state);
  375. } else if(tuning_fork_state->page == TuningsPage) {
  376. prev_tuning(tuning_fork_state);
  377. } else {
  378. prev_note(tuning_fork_state);
  379. if(tuning_fork_state->playing) {
  380. replay(tuning_fork_state);
  381. }
  382. }
  383. break;
  384. case InputKeyOk:
  385. if(tuning_fork_state->page == InstrumentsPage) {
  386. tuning_fork_state->page = VariationsPage;
  387. } else if(tuning_fork_state->page == VariationsPage) {
  388. tuning_fork_state->page = TuningsPage;
  389. } else if(tuning_fork_state->page == TuningsPage) {
  390. tuning_fork_state->page = NotesPage;
  391. } else {
  392. tuning_fork_state->playing = !tuning_fork_state->playing;
  393. if(tuning_fork_state->playing) {
  394. play(tuning_fork_state);
  395. } else {
  396. stop();
  397. }
  398. }
  399. break;
  400. case InputKeyBack:
  401. if(tuning_fork_state->page == InstrumentsPage) {
  402. processing = false;
  403. } else if(tuning_fork_state->page == VariationsPage) {
  404. tuning_fork_state->page = InstrumentsPage;
  405. } else if(tuning_fork_state->page == TuningsPage) {
  406. tuning_fork_state->page = VariationsPage;
  407. } else {
  408. tuning_fork_state->playing = false;
  409. tuning_fork_state->current_tuning_note_index = 0;
  410. stop();
  411. tuning_fork_state->page = TuningsPage;
  412. }
  413. break;
  414. default:
  415. break;
  416. }
  417. } else if(event.input.type == InputTypeLong) {
  418. // hold events
  419. switch(event.input.key) {
  420. case InputKeyUp:
  421. break;
  422. case InputKeyDown:
  423. break;
  424. case InputKeyRight:
  425. if(tuning_fork_state->page == InstrumentsPage) {
  426. next_instrument(tuning_fork_state);
  427. } else if(tuning_fork_state->page == VariationsPage) {
  428. next_variation(tuning_fork_state);
  429. } else if(tuning_fork_state->page == TuningsPage) {
  430. next_tuning(tuning_fork_state);
  431. } else {
  432. next_note(tuning_fork_state);
  433. if(tuning_fork_state->playing) {
  434. replay(tuning_fork_state);
  435. }
  436. }
  437. break;
  438. case InputKeyLeft:
  439. if(tuning_fork_state->page == InstrumentsPage) {
  440. prev_instrument(tuning_fork_state);
  441. } else if(tuning_fork_state->page == VariationsPage) {
  442. prev_variation(tuning_fork_state);
  443. } else if(tuning_fork_state->page == TuningsPage) {
  444. prev_tuning(tuning_fork_state);
  445. } else {
  446. prev_note(tuning_fork_state);
  447. if(tuning_fork_state->playing) {
  448. replay(tuning_fork_state);
  449. }
  450. }
  451. break;
  452. case InputKeyOk:
  453. break;
  454. case InputKeyBack:
  455. if(tuning_fork_state->page == InstrumentsPage) {
  456. processing = false;
  457. } else if(tuning_fork_state->page == VariationsPage) {
  458. tuning_fork_state->page = InstrumentsPage;
  459. } else if(tuning_fork_state->page == TuningsPage) {
  460. tuning_fork_state->page = VariationsPage;
  461. } else {
  462. tuning_fork_state->playing = false;
  463. stop();
  464. tuning_fork_state->page = TuningsPage;
  465. tuning_fork_state->current_tuning_note_index = 0;
  466. }
  467. break;
  468. default:
  469. break;
  470. }
  471. } else if(event.input.type == InputTypeRepeat) {
  472. // repeat events
  473. switch(event.input.key) {
  474. case InputKeyUp:
  475. break;
  476. case InputKeyDown:
  477. break;
  478. case InputKeyRight:
  479. if(tuning_fork_state->page == InstrumentsPage) {
  480. next_instrument(tuning_fork_state);
  481. } else if(tuning_fork_state->page == VariationsPage) {
  482. next_variation(tuning_fork_state);
  483. } else if(tuning_fork_state->page == TuningsPage) {
  484. next_tuning(tuning_fork_state);
  485. } else {
  486. next_note(tuning_fork_state);
  487. if(tuning_fork_state->playing) {
  488. replay(tuning_fork_state);
  489. }
  490. }
  491. break;
  492. case InputKeyLeft:
  493. if(tuning_fork_state->page == InstrumentsPage) {
  494. prev_instrument(tuning_fork_state);
  495. } else if(tuning_fork_state->page == VariationsPage) {
  496. prev_variation(tuning_fork_state);
  497. } else if(tuning_fork_state->page == TuningsPage) {
  498. prev_tuning(tuning_fork_state);
  499. } else {
  500. prev_note(tuning_fork_state);
  501. if(tuning_fork_state->playing) {
  502. replay(tuning_fork_state);
  503. }
  504. }
  505. break;
  506. case InputKeyOk:
  507. break;
  508. case InputKeyBack:
  509. if(tuning_fork_state->page == InstrumentsPage) {
  510. processing = false;
  511. } else if(tuning_fork_state->page == VariationsPage) {
  512. tuning_fork_state->page = InstrumentsPage;
  513. } else if(tuning_fork_state->page == TuningsPage) {
  514. tuning_fork_state->page = VariationsPage;
  515. } else {
  516. tuning_fork_state->playing = false;
  517. stop();
  518. tuning_fork_state->page = TuningsPage;
  519. tuning_fork_state->current_tuning_note_index = 0;
  520. }
  521. break;
  522. default:
  523. break;
  524. }
  525. }
  526. }
  527. }
  528. furi_mutex_release(tuning_fork_state->mutex);
  529. view_port_update(view_port);
  530. }
  531. view_port_enabled_set(view_port, false);
  532. gui_remove_view_port(gui, view_port);
  533. furi_record_close("gui");
  534. view_port_free(view_port);
  535. furi_message_queue_free(event_queue);
  536. furi_mutex_free(tuning_fork_state->mutex);
  537. furi_record_close(RECORD_NOTIFICATION);
  538. free(tuning_fork_state);
  539. return 0;
  540. }