| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912 |
- #include <furi.h>
- #include <furi_hal.h>
- #include <gui/gui.h>
- #include <gui/view.h>
- #include <gui/view_dispatcher.h>
- #include <gui/modules/submenu.h>
- #include <gui/modules/text_input.h>
- #include <gui/modules/widget.h>
- #include <gui/modules/variable_item_list.h>
- #include <dialogs/dialogs.h>
- #include <storage/storage.h>
- #include <flipper_format.h>
- #include "fmf_to_sub_icons.h"
- #define TAG "FMF to SUB"
- #define FMF_FILE_EXTENSION ".fmf"
- #define FMF_LOAD_PATH \
- EXT_PATH("apps_data") \
- "/" \
- "music_player"
- // Our application menu.
- typedef enum {
- Fmf2SubSubmenuIndexConfigure,
- Fmf2SubSubmenuIndexConvert,
- Fmf2SubSubmenuIndexAbout,
- } Fmf2SubSubmenuIndex;
- // Each view is a screen we show the user.
- typedef enum {
- Fmf2SubViewSubmenu, // The menu when the app starts
- Fmf2SubViewTextInput, // Input for configuring text settings
- Fmf2SubViewConfigure, // The configuration screen
- Fmf2SubViewConvert, // The main screen
- Fmf2SubViewAbout, // The about screen with directions, link to social channel, etc.
- } Fmf2SubView;
- typedef enum {
- Fmf2SubEventIdRedrawScreen = 0, // Custom event to redraw the screen
- Fmf2SubEventIdOkPressed = 1, // Custom event to process OK button getting pressed down
- Fmf2SubEventIdLoadFile = 2, // Custom event to load the file
- Fmf2SubEventIdCreateSub = 3, // Custom event to create the sub file
- } Fmf2SubEventId;
- typedef struct {
- ViewDispatcher* view_dispatcher; // Switches between our views
- DialogsApp* dialogs; // Shows dialogs like file browser
- Submenu* submenu; // The application menu
- TextInput* text_input; // The text input screen
- VariableItemList* variable_item_list_config; // The configuration screen
- VariableItem* variable_item_button; // The button on FlipBoard
- View* view_convert; // The main screen
- Widget* widget_about; // The about screen
- FuriString* file_path; // The path to the file
- char* temp_buffer; // Temporary buffer for text input
- uint32_t temp_buffer_size; // Size of temporary buffer
- FuriTimer* timer; // Timer for redrawing the screen
- } Fmf2SubApp;
- typedef enum {
- Fmf2SubStateIdle,
- Fmf2SubStateLoading,
- Fmf2SubStateConverting,
- Fmf2SubStateConverted,
- Fmf2SubStateError,
- } Fmf2SubState;
- typedef struct {
- uint32_t bpm;
- uint32_t duration;
- uint32_t octave;
- FuriString* notes;
- } Fmf2SubData;
- typedef struct {
- uint8_t setting_frequency_index; // The frequency
- uint8_t setting_modulation_index; // The modulation
- uint8_t setting_button_index; // The button on FlipBoard
- Fmf2SubState state; // The state of the application
- Fmf2SubData data; // The data from the file
- } Fmf2SubConvertModel;
- /**
- * @brief Callback for exiting the application.
- * @details This function is called when user press back button. We return VIEW_NONE to
- * indicate that we want to exit the application.
- * @param _context The context - unused
- * @return next view id (VIEW_NONE)
- */
- static uint32_t fmf2sub_navigation_exit_callback(void* _context) {
- UNUSED(_context);
- return VIEW_NONE;
- }
- /**
- * @brief Callback for returning to submenu.
- * @details This function is called when user press back button. We return ViewSubmenu to
- * indicate that we want to navigate to the submenu.
- * @param _context The context - unused
- * @return next view id (ViewSubmenu)
- */
- static uint32_t fmf2sub_navigation_submenu_callback(void* _context) {
- UNUSED(_context);
- return Fmf2SubViewSubmenu;
- }
- /**
- * @brief Handle submenu item selection.
- * @details This function is called when user selects an item from the submenu.
- * @param context The context - Fmf2SubApp object.
- * @param index The Fmf2SubSubmenuIndex item that was clicked.
- */
- static void fmf2sub_submenu_callback(void* context, uint32_t index) {
- Fmf2SubApp* app = (Fmf2SubApp*)context;
- switch(index) {
- case Fmf2SubSubmenuIndexConfigure:
- view_dispatcher_switch_to_view(app->view_dispatcher, Fmf2SubViewConfigure);
- break;
- case Fmf2SubSubmenuIndexConvert:
- view_dispatcher_switch_to_view(app->view_dispatcher, Fmf2SubViewConvert);
- break;
- case Fmf2SubSubmenuIndexAbout:
- view_dispatcher_switch_to_view(app->view_dispatcher, Fmf2SubViewAbout);
- break;
- default:
- break;
- }
- }
- /**
- * Frequency settings and values.
- */
- static const char* setting_frequency_label = "Frequency";
- static char* setting_frequency_values[] = {
- "300000000", "302757000", "303875000", "303900000", "304250000", "307000000", "307500000",
- "307800000", "309000000", "310000000", "312000000", "312100000", "313000000", "313850000",
- "314000000", "314350000", "314980000", "315000000", "318000000", "330000000", "345000000",
- "348000000", "387000000", "390000000", "418000000", "430000000", "431000000", "431500000",
- "433075000", "433220000", "433420000", "433657070", "433889000", "433920000", "434075000",
- "434176948", "434390000", "434420000", "434775000", "438900000", "440175000", "464000000",
- "779000000", "868350000", "868400000", "868800000", "868950000", "906400000", "915000000",
- "925000000", "928000000"};
- static char* setting_frequency_names[] = {
- "300.00", "302.75", "303.88", "303.90", "304.25", "307.00", "307.50", "307.80", "309.00",
- "310.00", "312.00", "312.10", "313.00", "313.85", "314.00", "314.35", "314.98", "315.00",
- "318.00", "330.00", "345.00", "348.00", //
- "387.00", "390.00", "418.00", "430.00", "431.00", "431.50", "433.07", "433.22", "433.42",
- "433.66", "433.89", "433.92", "434.07", "434.18", "434.39", "434.42", "434.78", "438.90",
- "440.18", "464.00", //
- "779.00", "868.35", "868.40", "868.80", "868.95", "906.40", "915.00", "925.00", "928.00"};
- static void fmf2sub_setting_frequency_change(VariableItem* item) {
- Fmf2SubApp* app = variable_item_get_context(item);
- uint8_t index = variable_item_get_current_value_index(item);
- variable_item_set_current_value_text(item, setting_frequency_names[index]);
- Fmf2SubConvertModel* model = view_get_model(app->view_convert);
- model->setting_frequency_index = index;
- }
- /**
- * Modulation
- */
- static const char* setting_modulation_label = "Modulation";
- static char* setting_modulation_values[] = {
- "FuriHalSubGhzPresetOok270Async",
- "FuriHalSubGhzPresetOok650Async",
- "FuriHalSubGhzPreset2FSKDev238Async",
- "FuriHalSubGhzPreset2FSKDev476Async"};
- static char* setting_modulation_names[] = {"AM270", "AM650", "FM238", "FM476"};
- static void fmf2sub_setting_modulation_change(VariableItem* item) {
- Fmf2SubApp* app = variable_item_get_context(item);
- uint8_t index = variable_item_get_current_value_index(item);
- variable_item_set_current_value_text(item, setting_modulation_names[index]);
- Fmf2SubConvertModel* model = view_get_model(app->view_convert);
- model->setting_modulation_index = index;
- }
- /**
- * Flipboard button
- */
- static const char* setting_button_label = "FlipButtons";
- static char* setting_button_values[] = {
- "Flip1.sub",
- "Flip2.sub",
- "Flip3.sub",
- "Flip4.sub",
- "Flip5.sub",
- "Flip6.sub",
- "Flip7.sub",
- "Flip8.sub",
- "Flip9.sub",
- "Flip10.sub",
- "Flip11.sub",
- "Flip12.sub",
- "Flip13.sub",
- "Flip14.sub",
- "Flip15.sub"};
- static char* setting_button_names[] = {
- "1",
- "2",
- "1+2",
- "3",
- "1+3",
- "2+3",
- "1+2+3",
- "4",
- "1+4",
- "2+4",
- "1+2+4",
- "3+4",
- "1+3+4",
- "2+3+4",
- "All"};
- static void fmf2sub_setting_button_change(VariableItem* item) {
- Fmf2SubApp* app = variable_item_get_context(item);
- uint8_t index = variable_item_get_current_value_index(item);
- variable_item_set_current_value_text(item, setting_button_names[index]);
- Fmf2SubConvertModel* model = view_get_model(app->view_convert);
- model->setting_button_index = index;
- }
- /**
- * @brief Callback for drawing the convert screen.
- * @details This function is called when the screen needs to be redrawn, like when the model gets updated.
- * @param canvas The canvas to draw on.
- * @param model The model - MyModel object.
- */
- static void fmf2sub_view_convert_draw_callback(Canvas* canvas, void* model) {
- Fmf2SubConvertModel* my_model = (Fmf2SubConvertModel*)model;
- canvas_set_font(canvas, FontSecondary);
- canvas_draw_str(canvas, 1, 10, "Press OK to select Flipper");
- canvas_draw_str(canvas, 1, 20, "Music File (.FMF) to convert");
- canvas_draw_str(canvas, 1, 30, "to Sub-GHz format (.SUB).");
- canvas_draw_str(canvas, 10, 40, "FlipBoard buttons:");
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str(canvas, 90, 40, setting_button_names[my_model->setting_button_index]);
- if(my_model->data.notes && my_model->state == Fmf2SubStateConverting) {
- canvas_draw_str(canvas, 1, 50, furi_string_get_cstr(my_model->data.notes));
- } else {
- canvas_draw_str(canvas, 40, 50, setting_button_values[my_model->setting_button_index]);
- }
- canvas_set_font(canvas, FontPrimary);
- if(my_model->state == Fmf2SubStateLoading) {
- canvas_draw_str(canvas, 1, 60, "Loading...");
- } else if(my_model->state == Fmf2SubStateError) {
- canvas_draw_str(canvas, 1, 60, "Error!");
- } else if(my_model->state == Fmf2SubStateConverting) {
- canvas_draw_str(canvas, 1, 60, "Converting...");
- } else if(my_model->state == Fmf2SubStateConverted) {
- canvas_draw_str(canvas, 1, 60, "Saved in Sub-GHz folder");
- } else {
- canvas_draw_str(canvas, 1, 60, "Press OK to choose file");
- }
- }
- /**
- * @brief Callback when the user starts the convert screen.
- * @details This function is called when the user enters the convert screen.
- * @param context The context - Fmf2SubApp object.
- */
- static void fmf2sub_view_convert_enter_callback(void* context) {
- UNUSED(context);
- }
- /**
- * @brief Callback when the user exits the convert screen.
- * @details This function is called when the user exits the convert screen.
- * @param context The context - Fmf2SubApp object.
- */
- static void fmf2sub_view_convert_exit_callback(void* context) {
- Fmf2SubApp* app = (Fmf2SubApp*)context;
- with_view_model(
- app->view_convert,
- Fmf2SubConvertModel * model,
- {
- model->state = Fmf2SubStateIdle;
- if(model->data.notes) {
- furi_string_free(model->data.notes);
- model->data.notes = NULL;
- }
- },
- false);
- }
- static uint32_t
- fmf2sub_extract_param(FuriString* song_settings, char param, uint32_t default_value) {
- uint16_t value = 0;
- char param_equal[3] = {param, '=', 0};
- size_t index = furi_string_search_str(song_settings, param_equal);
- if(index != FURI_STRING_FAILURE) {
- index += 2;
- do {
- char ch = furi_string_get_char(song_settings, index++);
- if(ch < '0' || ch > '9') {
- break;
- }
- value *= 10;
- value += ch - '0';
- } while(true);
- } else {
- value = default_value;
- }
- return value;
- }
- static void fmf2sub_load_txt_file(Fmf2SubApp* app, Fmf2SubConvertModel* model) {
- UNUSED(app);
- UNUSED(model);
- bool error = false;
- Storage* storage = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(storage);
- do {
- if(storage_file_open(
- file, furi_string_get_cstr(app->file_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
- char ch;
- while(storage_file_read(file, &ch, 1) && !storage_file_eof(file)) {
- if(ch == ':') {
- break;
- }
- }
- if(storage_file_eof(file)) {
- FURI_LOG_E(TAG, "Failed to find first delimiter.");
- error = true;
- break;
- }
- FuriString* song_settings = furi_string_alloc();
- while(storage_file_read(file, &ch, 1) && !storage_file_eof(file)) {
- if(ch == ':') {
- break;
- }
- furi_string_push_back(song_settings, ch);
- }
- model->data.duration = fmf2sub_extract_param(song_settings, 'd', 4);
- model->data.octave = fmf2sub_extract_param(song_settings, 'o', 5);
- model->data.bpm = fmf2sub_extract_param(song_settings, 'b', 120);
- furi_string_free(song_settings);
- if(storage_file_eof(file)) {
- FURI_LOG_E(TAG, "Failed to find second delimiter.");
- error = true;
- break;
- }
- model->data.notes = furi_string_alloc();
- while(storage_file_read(file, &ch, 1) && !storage_file_eof(file)) {
- furi_string_push_back(model->data.notes, ch);
- }
- }
- } while(false);
- storage_file_close(file);
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
- if(error) {
- model->state = Fmf2SubStateError;
- } else {
- view_dispatcher_send_custom_event(app->view_dispatcher, Fmf2SubEventIdCreateSub);
- }
- }
- static void fmf2sub_load_fmf_file(Fmf2SubApp* app, Fmf2SubConvertModel* model) {
- UNUSED(model);
- FlipperFormat* ff;
- Storage* storage = furi_record_open(RECORD_STORAGE);
- FuriString* buf = furi_string_alloc();
- bool error = false;
- if(model->data.notes) {
- furi_string_free(model->data.notes);
- }
- model->data.notes = NULL;
- ff = flipper_format_buffered_file_alloc(storage);
- do {
- uint32_t format_version;
- if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(app->file_path))) {
- FURI_LOG_E(TAG, "Failed to open file: %s", furi_string_get_cstr(app->file_path));
- error = true;
- break;
- }
- if(!flipper_format_read_header(ff, buf, &format_version)) {
- FURI_LOG_E(TAG, "Failed to read settings header.");
- error = true;
- break;
- }
- flipper_format_read_uint32(ff, "BPM", &(model->data.bpm), 120);
- flipper_format_read_uint32(ff, "Duration", &(model->data.duration), 4);
- flipper_format_read_uint32(ff, "Octave", &(model->data.octave), 5);
- model->data.notes = furi_string_alloc();
- if(!flipper_format_read_string(ff, "Notes", model->data.notes)) {
- FURI_LOG_E(TAG, "Failed to read notes.");
- furi_string_free(model->data.notes);
- model->data.notes = NULL;
- error = true;
- }
- } while(false);
- flipper_format_buffered_file_close(ff);
- flipper_format_free(ff);
- furi_record_close(RECORD_STORAGE);
- furi_string_free(buf);
- if(error) {
- fmf2sub_load_txt_file(app, model);
- } else {
- view_dispatcher_send_custom_event(app->view_dispatcher, Fmf2SubEventIdCreateSub);
- }
- }
- static void fmf2sub_file_write(File* file, const char* str) {
- storage_file_write(file, str, strlen(str));
- }
- void fmf2sub_save_sub_file(Fmf2SubApp* app, Fmf2SubConvertModel* model) {
- UNUSED(app);
- Storage* storage = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(storage);
- FuriString* file_path = furi_string_alloc();
- furi_string_cat_printf(
- file_path,
- "%s/%s",
- EXT_PATH("/subghz/"),
- setting_button_values[model->setting_button_index]);
- if(storage_file_open(file, furi_string_get_cstr(file_path), FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
- storage_file_truncate(file);
- FuriString* tmp_string = furi_string_alloc();
- fmf2sub_file_write(file, "Filetype: Flipper SubGhz RAW File\n");
- fmf2sub_file_write(file, "Version: 1\n");
- fmf2sub_file_write(file, "Frequency: ");
- fmf2sub_file_write(file, setting_frequency_values[model->setting_frequency_index]);
- fmf2sub_file_write(file, "\nPreset: ");
- fmf2sub_file_write(file, setting_modulation_values[model->setting_modulation_index]);
- fmf2sub_file_write(file, "\nProtocol: RAW");
- // process the notes
- FuriString* notes = model->data.notes;
- int16_t duration = -1;
- int16_t octave = -1;
- bool dot = false;
- for(size_t i = 0; i < furi_string_size(notes); i++) {
- char ch = furi_string_get_char(notes, i);
- if(ch == ' ' || ch == ',') {
- // skip spaces and commas.
- continue;
- }
- // is is a duration?
- while(ch >= '0' && ch <= '9') {
- if(duration == -1) {
- duration = 0;
- }
- duration *= 10;
- duration += ch - '0';
- ch = furi_string_get_char(notes, ++i);
- }
- if(duration == -1) {
- duration = model->data.duration;
- }
- // it should be note.
- if(ch < 'A' && ch > 'G' && ch < 'a' && ch > 'g' && ch != 'P' && ch != 'p') {
- FURI_LOG_D(TAG, "Invalid note: %c", ch);
- // invalid note
- continue;
- }
- bool sharp = furi_string_get_char(notes, i + 1) == '#';
- // convert to frequency (octave 2)
- float frequency = 0;
- ch = toupper(ch);
- if(ch == 'P') {
- frequency = 6.0;
- } else if(ch == 'C') {
- frequency = !sharp ? 130.0 : 138.6;
- } else if(ch == 'D') {
- frequency = !sharp ? 146.8 : 155.6;
- } else if(ch == 'E') {
- frequency = 164.8;
- } else if(ch == 'F') {
- frequency = !sharp ? 174.6 : 185.0;
- } else if(ch == 'G') {
- frequency = !sharp ? 196.0 : 207.7;
- } else if(ch == 'A') {
- frequency = !sharp ? 220.0 : 233.1;
- } else if(ch == 'B') {
- frequency = 246.9;
- } else {
- FURI_LOG_D(TAG, "Invalid note: %c, %d", ch, (int)ch);
- // invalid note
- continue;
- }
- if(sharp) {
- i++;
- }
- ch = furi_string_get_char(notes, ++i);
- if(ch == '.') {
- dot = true; // 50% longer
- ch = furi_string_get_char(notes, ++i);
- }
- while(ch >= '0' && ch <= '9') {
- if(octave == -1) {
- octave = 0;
- }
- octave *= 10;
- octave += ch - '0';
- ch = furi_string_get_char(notes, ++i);
- }
- if(octave == -1) {
- octave = model->data.octave;
- }
- if(ch == '.') {
- dot = true; // 50% longer
- }
- if(octave < 2) {
- frequency /= 2.0;
- } else {
- for(int i = 2; i < octave; i++) {
- frequency *= 2.0;
- }
- }
- uint32_t pulse = (1000000 / frequency) / 2;
- // 4/4 timing, duration of quarter note (4) is 500ms.
- float count = (1000000.0 / (pulse * 2.0)) * 2.0 / duration;
- count *= 120.0f;
- count /= model->data.bpm;
- if(dot) {
- count += (count / 2.0f);
- }
- if(count < 1.0f) {
- count = 1.0;
- }
- if(duration <= 1) {
- count *= 0.98; // whole note.
- } else if(duration <= 2) {
- count *= 0.95; // half note.
- } else if(duration <= 4) {
- count *= 0.90; // quarter note.
- } else {
- count *= 0.90;
- }
- float duration_tone = ((uint32_t)count) * pulse * 2;
- float duration_beat =
- (1000000.0 * 2.0 / duration * 120.0 / model->data.bpm) * (dot ? 1.5 : 1.0);
- float duration_rem = (duration_beat - duration_tone) / 2.0f;
- uint32_t rem_counter = 1;
- while(duration_rem > 20000.0f) {
- rem_counter *= 2;
- duration_rem /= 2.0;
- }
- FURI_LOG_D(
- TAG,
- "octave: %d, duration: %d, freq: %f, dot: %c, pulse: %ld, count: %f, bpm: %ld",
- octave,
- duration,
- (double)frequency,
- dot ? 'Y' : 'N',
- pulse,
- (double)count,
- model->data.bpm);
- FURI_LOG_D(
- TAG,
- "beat: %f tone: %f rem-us: %f rem-cnt: %ld",
- (double)duration_beat,
- (double)duration_tone,
- (double)duration_rem,
- rem_counter);
- fmf2sub_file_write(file, "\nRAW_Data:");
- furi_string_printf(tmp_string, " %ld %ld", pulse, -pulse);
- for(uint32_t i = 0; i < (uint32_t)count; i++) {
- if(i % 256 == 255) {
- fmf2sub_file_write(file, "\nRAW_Data:");
- }
- fmf2sub_file_write(file, furi_string_get_cstr(tmp_string));
- }
- fmf2sub_file_write(file, "\nRAW_Data:");
- furi_string_printf(
- tmp_string, " %ld -%ld", (uint32_t)duration_rem, (uint32_t)duration_rem);
- for(uint32_t i = 0; i < rem_counter; i++) {
- fmf2sub_file_write(file, furi_string_get_cstr(tmp_string));
- }
- octave = -1;
- duration = -1;
- dot = false;
- }
- furi_string_free(tmp_string);
- // write file
- model->state = Fmf2SubStateConverted;
- } else {
- FURI_LOG_D(TAG, "Failed to create file: %s", furi_string_get_cstr(file_path));
- model->state = Fmf2SubStateError;
- }
- storage_file_close(file);
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
- furi_string_free(file_path);
- }
- /**
- * @brief Callback for custom events.
- * @details This function is called when a custom event is sent to the view dispatcher.
- * @param event The event id - Fmf2SubEventId value.
- * @param context The context - Fmf2SubApp object.
- */
- static bool fmf2sub_view_convert_custom_event_callback(uint32_t event, void* context) {
- Fmf2SubApp* app = (Fmf2SubApp*)context;
- switch(event) {
- case Fmf2SubEventIdRedrawScreen:
- // Redraw screen by passing true to last parameter of with_view_model.
- {
- bool redraw = true;
- with_view_model(
- app->view_convert, Fmf2SubConvertModel * _model, { UNUSED(_model); }, redraw);
- return true;
- }
- case Fmf2SubEventIdOkPressed: {
- with_view_model(
- app->view_convert,
- Fmf2SubConvertModel * model,
- { model->state = Fmf2SubStateIdle; },
- false);
- DialogsFileBrowserOptions browser_options;
- dialog_file_browser_set_basic_options(&browser_options, "", &I_fmf_10x10);
- browser_options.hide_dot_files = true;
- browser_options.hide_ext = false;
- browser_options.base_path = FMF_LOAD_PATH;
- furi_string_set(app->file_path, browser_options.base_path);
- if(dialog_file_browser_show(
- app->dialogs, app->file_path, app->file_path, &browser_options)) {
- view_dispatcher_send_custom_event(app->view_dispatcher, Fmf2SubEventIdLoadFile);
- }
- return true;
- }
- case Fmf2SubEventIdLoadFile: {
- with_view_model(
- app->view_convert,
- Fmf2SubConvertModel * model,
- {
- model->state = Fmf2SubStateLoading;
- fmf2sub_load_fmf_file(app, model);
- },
- true);
- return true;
- }
- case Fmf2SubEventIdCreateSub: {
- with_view_model(
- app->view_convert,
- Fmf2SubConvertModel * model,
- {
- model->state = Fmf2SubStateConverting;
- FURI_LOG_D(TAG, "Loaded file: %s", furi_string_get_cstr(app->file_path));
- FURI_LOG_D(TAG, "BPM: %ld", model->data.bpm);
- FURI_LOG_D(TAG, "Duration: %ld", model->data.duration);
- FURI_LOG_D(TAG, "Octave: %ld", model->data.octave);
- FURI_LOG_D(TAG, "Notes: %s", furi_string_get_cstr(model->data.notes));
- fmf2sub_save_sub_file(app, model);
- },
- true);
- return true;
- }
- default:
- return false;
- }
- }
- /**
- * @brief Callback for convert screen input.
- * @details This function is called when the user presses a button while on the convert screen.
- * @param event The event - InputEvent object.
- * @param context The context - Fmf2SubApp object.
- * @return true if the event was handled, false otherwise.
- */
- static bool fmf2sub_view_convert_input_callback(InputEvent* event, void* context) {
- Fmf2SubApp* app = (Fmf2SubApp*)context;
- UNUSED(app);
- if(event->type == InputTypeShort) {
- if(event->key == InputKeyLeft) {
- bool redraw = true;
- with_view_model(
- app->view_convert,
- Fmf2SubConvertModel * model,
- {
- if(model->setting_button_index > 0) {
- model->setting_button_index--;
- variable_item_set_current_value_text(
- app->variable_item_button,
- setting_button_names[model->setting_button_index]);
- variable_item_set_current_value_index(
- app->variable_item_button, model->setting_button_index);
- }
- },
- redraw);
- } else if(event->key == InputKeyRight) {
- bool redraw = true;
- with_view_model(
- app->view_convert,
- Fmf2SubConvertModel * model,
- {
- if(model->setting_button_index + 1 <
- (uint8_t)COUNT_OF(setting_button_values)) {
- model->setting_button_index++;
- variable_item_set_current_value_text(
- app->variable_item_button,
- setting_button_names[model->setting_button_index]);
- variable_item_set_current_value_index(
- app->variable_item_button, model->setting_button_index);
- }
- },
- redraw);
- }
- } else if(event->type == InputTypePress) {
- if(event->key == InputKeyOk) {
- view_dispatcher_send_custom_event(app->view_dispatcher, Fmf2SubEventIdOkPressed);
- return true;
- }
- }
- return false;
- }
- /**
- * @brief Allocate the fmf2sub application.
- * @details This function allocates the fmf2sub application resources.
- * @return Fmf2SubApp object.
- */
- static Fmf2SubApp* fmf2sub_app_alloc() {
- Fmf2SubApp* app = (Fmf2SubApp*)malloc(sizeof(Fmf2SubApp));
- Gui* gui = furi_record_open(RECORD_GUI);
- app->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
- view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
- app->submenu = submenu_alloc();
- submenu_add_item(
- app->submenu, "Configure", Fmf2SubSubmenuIndexConfigure, fmf2sub_submenu_callback, app);
- submenu_add_item(
- app->submenu, "Convert", Fmf2SubSubmenuIndexConvert, fmf2sub_submenu_callback, app);
- submenu_add_item(
- app->submenu, "About", Fmf2SubSubmenuIndexAbout, fmf2sub_submenu_callback, app);
- view_set_previous_callback(submenu_get_view(app->submenu), fmf2sub_navigation_exit_callback);
- view_dispatcher_add_view(
- app->view_dispatcher, Fmf2SubViewSubmenu, submenu_get_view(app->submenu));
- view_dispatcher_switch_to_view(app->view_dispatcher, Fmf2SubViewSubmenu);
- app->text_input = text_input_alloc();
- view_dispatcher_add_view(
- app->view_dispatcher, Fmf2SubViewTextInput, text_input_get_view(app->text_input));
- app->temp_buffer_size = 32;
- app->temp_buffer = (char*)malloc(app->temp_buffer_size);
- app->variable_item_list_config = variable_item_list_alloc();
- variable_item_list_reset(app->variable_item_list_config);
- //variable_item_list_set_header(app->variable_item_list_config, "Flipboard Signal Config");
- VariableItem* item = variable_item_list_add(
- app->variable_item_list_config,
- setting_frequency_label,
- COUNT_OF(setting_frequency_values),
- fmf2sub_setting_frequency_change,
- app);
- uint8_t setting_frequency_index = 33;
- variable_item_set_current_value_index(item, setting_frequency_index);
- variable_item_set_current_value_text(item, setting_frequency_names[setting_frequency_index]);
- item = variable_item_list_add(
- app->variable_item_list_config,
- setting_modulation_label,
- COUNT_OF(setting_modulation_values),
- fmf2sub_setting_modulation_change,
- app);
- uint8_t setting_modulation_index = 1;
- variable_item_set_current_value_index(item, setting_modulation_index);
- variable_item_set_current_value_text(item, setting_modulation_names[setting_modulation_index]);
- app->variable_item_button = variable_item_list_add(
- app->variable_item_list_config,
- setting_button_label,
- COUNT_OF(setting_button_values),
- fmf2sub_setting_button_change,
- app);
- uint8_t setting_button_index = 0;
- variable_item_set_current_value_index(app->variable_item_button, setting_button_index);
- variable_item_set_current_value_text(
- app->variable_item_button, setting_button_names[setting_button_index]);
- view_set_previous_callback(
- variable_item_list_get_view(app->variable_item_list_config),
- fmf2sub_navigation_submenu_callback);
- view_dispatcher_add_view(
- app->view_dispatcher,
- Fmf2SubViewConfigure,
- variable_item_list_get_view(app->variable_item_list_config));
- app->view_convert = view_alloc();
- view_set_draw_callback(app->view_convert, fmf2sub_view_convert_draw_callback);
- view_set_input_callback(app->view_convert, fmf2sub_view_convert_input_callback);
- view_set_previous_callback(app->view_convert, fmf2sub_navigation_submenu_callback);
- view_set_enter_callback(app->view_convert, fmf2sub_view_convert_enter_callback);
- view_set_exit_callback(app->view_convert, fmf2sub_view_convert_exit_callback);
- view_set_context(app->view_convert, app);
- view_set_custom_callback(app->view_convert, fmf2sub_view_convert_custom_event_callback);
- view_allocate_model(app->view_convert, ViewModelTypeLockFree, sizeof(Fmf2SubConvertModel));
- Fmf2SubConvertModel* model = view_get_model(app->view_convert);
- model->setting_frequency_index = setting_frequency_index;
- model->setting_modulation_index = setting_modulation_index;
- model->setting_button_index = setting_button_index;
- view_dispatcher_add_view(app->view_dispatcher, Fmf2SubViewConvert, app->view_convert);
- app->widget_about = widget_alloc();
- widget_add_text_scroll_element(
- app->widget_about,
- 0,
- 0,
- 128,
- 64,
- "Music to Sub-GHz v1.2!\n\n"
- "Converts music files (.FMF)\n"
- "or (.TXT) to Sub-GHz format\n"
- "(.SUB) Files. Flip#.sub is\n"
- "written to the SD Card's\n"
- "subghz folder. Another\n"
- "Flipper Zero with sound\n"
- "turned on doing a Read RAW\n"
- "in the Sub-GHz app can\n"
- "listen to the music!\n"
- "Use Flipboard Signal app to\n"
- "send signals or use the\n"
- "Sub-GHz app. Enjoy!\n\n"
- "author: @codeallnight\nhttps://discord.com/invite/NsjCvqwPAd\nhttps://youtube.com/@MrDerekJamison");
- view_set_previous_callback(
- widget_get_view(app->widget_about), fmf2sub_navigation_submenu_callback);
- view_dispatcher_add_view(
- app->view_dispatcher, Fmf2SubViewAbout, widget_get_view(app->widget_about));
- app->file_path = furi_string_alloc();
- app->dialogs = furi_record_open(RECORD_DIALOGS);
- return app;
- }
- /**
- * @brief Free the fmf2sub application.
- * @details This function frees the fmf2sub application resources.
- * @param app The fmf2sub application object.
- */
- static void fmf2sub_app_free(Fmf2SubApp* app) {
- view_dispatcher_remove_view(app->view_dispatcher, Fmf2SubViewTextInput);
- text_input_free(app->text_input);
- free(app->temp_buffer);
- view_dispatcher_remove_view(app->view_dispatcher, Fmf2SubViewAbout);
- widget_free(app->widget_about);
- view_dispatcher_remove_view(app->view_dispatcher, Fmf2SubViewConvert);
- view_free(app->view_convert);
- view_dispatcher_remove_view(app->view_dispatcher, Fmf2SubViewConfigure);
- variable_item_list_free(app->variable_item_list_config);
- view_dispatcher_remove_view(app->view_dispatcher, Fmf2SubViewSubmenu);
- submenu_free(app->submenu);
- view_dispatcher_free(app->view_dispatcher);
- furi_record_close(RECORD_GUI);
- furi_string_free(app->file_path);
- furi_record_close(RECORD_DIALOGS);
- free(app);
- }
- /**
- * @brief Main function for fmf2sub application.
- * @details This function is the entry point for the fmf2sub application. It should be defined in
- * application.fam as the entry_point setting.
- * @param _p Input parameter - unused
- * @return 0 - Success
- */
- int32_t fmf_to_sub_app(void* _p) {
- UNUSED(_p);
- Fmf2SubApp* app = fmf2sub_app_alloc();
- view_dispatcher_run(app->view_dispatcher);
- fmf2sub_app_free(app);
- return 0;
- }
|