Matthew 1 год назад
Родитель
Сommit
8c7d75571b

+ 21 - 0
tone_gen/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Gerald McAlister
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 17 - 0
tone_gen/application.fam

@@ -0,0 +1,17 @@
+# For details & more options, see documentation/AppManifests.md in firmware repo
+
+App(
+    appid="tone_gen",  # Must be unique
+    name="Tone Generator",  # Displayed in menus
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="tone_gen_app",
+    stack_size=2 * 1024,
+    fap_category="Tools",
+    fap_version="1.0",
+    fap_icon="tone_gen.png",  # 10x10 1-bit PNG
+    fap_description="A simple app to generate sound tones.",
+    fap_author="Gerald McAlister",
+    fap_weburl="https://github.com/GEMISIS/tone_gen",
+    fap_icon_assets="images",  # Image assets to compile for this application
+    sources=["src/*.c", "src/scenes/*.c", "src/system/*.c", "src/utils/*.c"],
+)

+ 0 - 0
tone_gen/images/.gitkeep


BIN
tone_gen/images/play_button.gif


BIN
tone_gen/images/play_button/frame_01.png


BIN
tone_gen/images/play_button/frame_02.png


BIN
tone_gen/images/play_button/frame_03.png


BIN
tone_gen/images/play_button/frame_04.png


+ 1 - 0
tone_gen/images/play_button/frame_rate

@@ -0,0 +1 @@
+2

BIN
tone_gen/images/settings_button.gif


BIN
tone_gen/images/settings_button/frame_01.png


BIN
tone_gen/images/settings_button/frame_02.png


+ 1 - 0
tone_gen/images/settings_button/frame_rate

@@ -0,0 +1 @@
+2

+ 141 - 0
tone_gen/src/app_context.c

@@ -0,0 +1,141 @@
+#include <gui/modules/menu.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/variable_item_list.h>
+
+#include "tone_gen.h"
+#include "app_context.h"
+
+/** custom event handler - passes the event to the scene manager */
+bool viewDispatcherCustomCallback(void* context, uint32_t custom_event) {
+    furi_assert(context);
+    struct AppContext_t* appContext = context;
+    return scene_manager_handle_custom_event(appContext->scene_manager, custom_event);
+}
+
+/** navigation event handler - passes the event to the scene manager */
+bool viewDispatcherNavigationCallback(void* context) {
+    furi_assert(context);
+    struct AppContext_t* appContext = context;
+    return scene_manager_handle_back_event(appContext->scene_manager);
+}
+
+AppContextStatus initializeAppContext(
+    struct AppContext_t** context,
+    int viewsCount,
+    const SceneManagerHandlers* sceneManagerHandlers) {
+    FURI_LOG_I(TAG, "Allocating memory for app context");
+
+    *context = malloc(sizeof(struct AppContext_t));
+    if(*context == NULL) {
+        FURI_LOG_E(TAG, "Failed to allocate memory for app context");
+        return APP_CONTEXT_CANT_ALLOCATE;
+    }
+
+    // Allocate our scene manager with the handlers provided
+    FURI_LOG_I(TAG, "Setting up the scene manager");
+    (*context)->scene_manager = scene_manager_alloc(sceneManagerHandlers, *context);
+
+    // Now setup our view dispatchers
+    FURI_LOG_I(TAG, "Setting up the view dispatcher");
+    (*context)->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue((*context)->view_dispatcher);
+
+    FURI_LOG_I(TAG, "Setting view dispatcher callbacks");
+    view_dispatcher_set_event_callback_context((*context)->view_dispatcher, (*context));
+    FURI_LOG_I(TAG, "Setting view dispatcher custom event callbacks");
+    view_dispatcher_set_custom_event_callback(
+        (*context)->view_dispatcher, viewDispatcherCustomCallback);
+    FURI_LOG_I(TAG, "Setting view dispatcher navigation event callbacks");
+    view_dispatcher_set_navigation_event_callback(
+        (*context)->view_dispatcher, viewDispatcherNavigationCallback);
+    FURI_LOG_I(TAG, "Setting view dispatcher callbacks done");
+
+    (*context)->activeViews = malloc(sizeof(struct View_t) * viewsCount);
+    if((*context)->activeViews == NULL) {
+        return APP_CONTEXT_CANT_ALLOCATE;
+    }
+    (*context)->activeViewsCount = viewsCount;
+
+    return APP_CONTEXT_OK;
+}
+
+AppContextStatus addViewToAppContext(struct AppContext_t** context, struct View_t* view) {
+    if(view->viewId > (*context)->activeViewsCount || view->viewId < 0) {
+        FURI_LOG_I(TAG, "Not enough views!");
+        return APP_CONTEXT_NOT_ENOUGH_VIEWS;
+    }
+    (*context)->activeViews[view->viewId] = view;
+    switch(view->type) {
+    case MENU:
+        view_dispatcher_add_view(
+            (*context)->view_dispatcher, view->viewId, menu_get_view(view->viewData));
+        break;
+    case SUBMENU:
+        view_dispatcher_add_view(
+            (*context)->view_dispatcher, view->viewId, submenu_get_view(view->viewData));
+        break;
+    case VIEW:
+        view_dispatcher_add_view((*context)->view_dispatcher, view->viewId, view->viewData);
+        break;
+    case VARIABLE_ITEM_LIST:
+        view_dispatcher_add_view(
+            (*context)->view_dispatcher,
+            view->viewId,
+            variable_item_list_get_view(view->viewData));
+        break;
+    case POPUP:
+        view_dispatcher_add_view(
+            (*context)->view_dispatcher, view->viewId, popup_get_view(view->viewData));
+        break;
+    }
+    return APP_CONTEXT_OK;
+}
+
+AppContextStatus freeAppContextViews(struct AppContext_t** context) {
+    FURI_LOG_I(TAG, "Freeing views");
+    for(int i = 0; i < (*context)->activeViewsCount; i++) {
+        struct View_t* view = (*context)->activeViews[i];
+        if(view != NULL) {
+            view_dispatcher_remove_view((*context)->view_dispatcher, view->viewId);
+
+            switch(view->type) {
+            case MENU:
+                menu_free(view->viewData);
+                break;
+            case SUBMENU:
+                submenu_free(view->viewData);
+                break;
+            case VIEW:
+                view_free(view->viewData);
+                break;
+            case VARIABLE_ITEM_LIST:
+                variable_item_list_free(view->viewData);
+                break;
+            case POPUP:
+                popup_free(view->viewData);
+                break;
+            }
+            free(view);
+        }
+    }
+    FURI_LOG_I(TAG, "Removing all views from list");
+    free((*context)->activeViews);
+    (*context)->activeViewsCount = 0;
+    return APP_CONTEXT_OK;
+}
+
+AppContextStatus freeAppContext(struct AppContext_t** context) {
+    FURI_LOG_I(TAG, "Ensuring views are free'd");
+    AppContextStatus result = freeAppContextViews(context);
+    if(result != APP_CONTEXT_OK) {
+        return APP_CONTEXT_CANT_FREE_VIEWS;
+    }
+    FURI_LOG_I(TAG, "Freeing the scene");
+    scene_manager_free((*context)->scene_manager);
+    FURI_LOG_I(TAG, "Freeing the view dispatcher");
+    view_dispatcher_free((*context)->view_dispatcher);
+    FURI_LOG_I(TAG, "Freeing the app context");
+    free((*context));
+    return APP_CONTEXT_OK;
+}

+ 62 - 0
tone_gen/src/app_context.h

@@ -0,0 +1,62 @@
+#ifndef _APP_CONTEXT_H_
+
+#define _APP_CONTEXT_H_
+
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+
+#include "utils/linked_list.h"
+
+typedef enum {
+    MENU,
+    SUBMENU,
+    VIEW,
+    VARIABLE_ITEM_LIST,
+    POPUP,
+} ViewType;
+
+struct View_t {
+    ViewType type;
+    int viewId;
+    void* viewData;
+};
+
+typedef enum {
+    APP_CONTEXT_OK = 0,
+    APP_CONTEXT_CANT_ALLOCATE = -1,
+    APP_CONTEXT_CANT_FREE_VIEWS = -2,
+    APP_CONTEXT_NOT_ENOUGH_VIEWS = -3,
+    APP_CONTEXT_UNKNOWN_ERROR = -4,
+} AppContextStatus;
+
+struct AppContext_t {
+    SceneManager* scene_manager;
+    ViewDispatcher* view_dispatcher;
+    struct View_t** activeViews;
+    int activeViewsCount;
+    void* additionalData;
+};
+
+/// @brief Creates an app context with the desired scene handlers.
+/// @param context The app context to populate. Will be passed through to the supplied scene handlers.
+/// @param viewsCount The number of views that to be added to this scene.
+/// @param sceneManagerHandlers The scene handlers to use for each scene in your app.
+/// @return Returns APP_CONTEXT_OK on success, APP_CONTEXT_CANT_ALLOCATE if there is an error.
+AppContextStatus initializeAppContext(
+    struct AppContext_t** context,
+    int viewsCount,
+    const SceneManagerHandlers* sceneManagerHandlers);
+
+/// @brief Adds a view to the given app context.
+/// @param context The app context to add the view to.
+/// @param view The view to add to the app context.
+/// @return Returns APP_CONTEXT_OK on success, APP_CONTEXT_NOT_ENOUGH_VIEWS if the ID of
+//  the view exceeds the number of available views in the app context.
+AppContextStatus addViewToAppContext(struct AppContext_t** context, struct View_t* view);
+
+/// @brief Frees the app context entirely, cleaning it up from usage.
+/// @param context The app context to clean up.
+/// @return Returns APP_CONTEXT_OK on success. Should not error.
+AppContextStatus freeAppContext(struct AppContext_t** context);
+
+#endif

+ 92 - 0
tone_gen/src/scenes/playback_scene.c

@@ -0,0 +1,92 @@
+#include <furi_hal.h>
+#include <notification/notification_messages.h>
+
+#include "playback_scene.h"
+#include "../app_context.h"
+#include "../tone_gen.h"
+#include "../system/audio_helper.h"
+
+#define SINE_WAVE(x, toneModelData) \
+    (sin((x + toneDataModel->animationOffset) * 50) * 20 + (64 / 2))
+
+#define SQUARE_WAVE(x, toneModelData) \
+    ((sin((x + toneDataModel->animationOffset) * 50) > 0 ? 1 : -1) * 20 + (64 / 2))
+
+// Renders the waveform
+static void playback_view_draw_callback(Canvas* canvas, void* model) {
+    UNUSED(model);
+
+    struct ToneData_t* toneDataModel = (struct ToneData_t*)model;
+    for(int x = 1; x < 128; x++) {
+        int x1 = x - 1;
+        int x2 = x;
+        int y1 = 0;
+        int y2 = 0;
+        switch(toneDataModel->waveType) {
+        case SINE:
+            y1 = SINE_WAVE(x1, toneDataModel);
+            y2 = SINE_WAVE(x2, toneDataModel);
+            break;
+        case SQUARE:
+            y1 = SQUARE_WAVE(x1, toneDataModel);
+            y2 = SQUARE_WAVE(x2, toneDataModel);
+            break;
+        default:
+            y1 = 64 / 2;
+            y2 = 64 / 2;
+            break;
+        }
+        // Draw lines to connect the pieces of the wave.
+        canvas_draw_line(canvas, x1, y1, x2, y2);
+    }
+    if(toneDataModel->animationOffset < 128) {
+        toneDataModel->animationOffset += 2;
+    } else {
+        toneDataModel->animationOffset = 0;
+    }
+}
+
+// Sets up the waveform to be displayed
+void scene_on_enter_playback_scene(void* context) {
+    FURI_LOG_I(TAG, "scene_on_enter_playback_scene");
+    struct AppContext_t* app = (struct AppContext_t*)context;
+    struct View_t* playbackView = app->activeViews[ToneGenAppView_PlaybackView];
+
+    // Configure the custom view
+    view_set_draw_callback(playbackView->viewData, playback_view_draw_callback);
+    view_set_context(playbackView->viewData, context);
+
+    FURI_LOG_I(TAG, "setting view model");
+    struct ToneData_t* toneDataModel = (struct ToneData_t*)view_get_model(playbackView->viewData);
+    toneDataModel->waveType = ((struct ToneData_t*)app->additionalData)->waveType;
+    toneDataModel->frequency = ((struct ToneData_t*)app->additionalData)->frequency;
+    toneDataModel->volume = ((struct ToneData_t*)app->additionalData)->volume;
+
+    // Set the currently active view
+    FURI_LOG_I(TAG, "setting active view");
+    view_dispatcher_switch_to_view(app->view_dispatcher, ToneGenAppView_PlaybackView);
+    NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
+    notification_message(notifications, &sequence_set_only_blue_255);
+    if(initializeSpeaker()) {
+        FURI_LOG_I(TAG, "Starting sound");
+        startSound(toneDataModel);
+    }
+}
+
+// Not actively used in this instance.
+bool scene_on_event_playback_scene(void* context, SceneManagerEvent event) {
+    FURI_LOG_I(TAG, "scene_on_event_playback_scene");
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+// Not actively used in this instance.
+void scene_on_exit_playback_scene(void* context) {
+    FURI_LOG_I(TAG, "scene_on_exit_playback_scene");
+    UNUSED(context);
+    stopSound();
+    deinitializeSpeaker();
+    NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
+    notification_message(notifications, &sequence_reset_rgb);
+}

+ 11 - 0
tone_gen/src/scenes/playback_scene.h

@@ -0,0 +1,11 @@
+#ifndef _PLAYBACK_SCENE_H_
+
+#define _PLAYBACK_SCENE_H_
+
+#include <gui/scene_manager.h>
+
+void scene_on_enter_playback_scene(void* context);
+bool scene_on_event_playback_scene(void* context, SceneManagerEvent event);
+void scene_on_exit_playback_scene(void* context);
+
+#endif

+ 119 - 0
tone_gen/src/scenes/settings_scene.c

@@ -0,0 +1,119 @@
+#include <gui/modules/variable_item_list.h>
+
+#include "settings_scene.h"
+#include "../app_context.h"
+#include "../tone_gen.h"
+#include "../utils/linked_list.h"
+
+// Not actively used in this instance.
+void menu_callback_settings_scene(void* context, uint32_t index) {
+    UNUSED(context);
+    UNUSED(index);
+}
+
+static uint8_t wave_option_values[] = {SINE, SQUARE};
+static char* wave_option_names[] = {"Sine", "Square"};
+static void wave_type_option_change(VariableItem* item) {
+    struct AppContext_t* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, wave_option_names[index]);
+    ((struct ToneData_t*)app->additionalData)->waveType = index;
+}
+
+// Since the max number of options for variable item lists is
+// the size of an 8-bit integer, we need to limit the max
+// number of steps. In this case, we limit it to 241 total
+// steps available, incrementing in steps of 10.
+#define MIN_FREQ 100
+#define MAX_FREQ 2500
+#define FREQ_STEPS 10
+#define INDEX_TO_FREQ(index) (uint16_t)((index * FREQ_STEPS) + MIN_FREQ)
+#define FREQ_TO_INDEX(freq) (uint8_t)((freq - MIN_FREQ) / FREQ_STEPS)
+char* frequencyStr;
+static void frequency_option_change(VariableItem* item) {
+    struct AppContext_t* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    ((struct ToneData_t*)app->additionalData)->frequency = INDEX_TO_FREQ(index);
+    snprintf(frequencyStr, 8, "%dhz", ((struct ToneData_t*)app->additionalData)->frequency);
+    variable_item_set_current_value_text(item, frequencyStr);
+}
+
+char* volumeStr;
+static void volume_option_change(VariableItem* item) {
+    struct AppContext_t* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    ((struct ToneData_t*)app->additionalData)->volume = ((float)(index)) / 10.0f;
+    snprintf(volumeStr, 5, "%d", (index * 10));
+    variable_item_set_current_value_text(item, volumeStr);
+}
+
+/** resets the menu, gives it content, callbacks and selection enums */
+void scene_on_enter_settings_scene(void* context) {
+    FURI_LOG_I(TAG, "scene_on_enter_settings_scene");
+    struct AppContext_t* app = (struct AppContext_t*)context;
+
+    // Setup our menu
+    FURI_LOG_D(TAG, "Adding view menu");
+    struct View_t* variableItemListView = app->activeViews[ToneGenAppView_VariableItemList];
+
+    // Set the currently active view
+    variable_item_list_reset(variableItemListView->viewData);
+
+    FURI_LOG_D(TAG, "Adding options for settings");
+    // Wave type setting
+    VariableItem* item = variable_item_list_add(
+        variableItemListView->viewData,
+        "Wave Display",
+        COUNT_OF(wave_option_values),
+        wave_type_option_change,
+        app);
+    variable_item_set_current_value_index(
+        item, ((struct ToneData_t*)app->additionalData)->waveType);
+    variable_item_set_current_value_text(
+        item, wave_option_names[((struct ToneData_t*)app->additionalData)->waveType]);
+
+    // Frequency setting
+    item = variable_item_list_add(
+        variableItemListView->viewData,
+        "Frequency",
+        FREQ_TO_INDEX(MAX_FREQ) + 1,
+        frequency_option_change,
+        app);
+    variable_item_set_current_value_index(
+        item, FREQ_TO_INDEX(((struct ToneData_t*)app->additionalData)->frequency));
+
+    frequencyStr = calloc(8, sizeof(char));
+    snprintf(frequencyStr, 8, "%dhz", ((struct ToneData_t*)app->additionalData)->frequency);
+    variable_item_set_current_value_text(item, frequencyStr);
+
+    // Volume setting
+    item = variable_item_list_add(
+        variableItemListView->viewData, "Volume", 11, volume_option_change, app);
+    variable_item_set_current_value_index(
+        item, (uint8_t)(((struct ToneData_t*)app->additionalData)->volume * 10.0f));
+
+    volumeStr = calloc(5, sizeof(char));
+    snprintf(
+        volumeStr,
+        5,
+        "%d",
+        ((uint8_t)(((struct ToneData_t*)app->additionalData)->volume * 100.0f)));
+    variable_item_set_current_value_text(item, volumeStr);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, ToneGenAppView_VariableItemList);
+}
+
+// Not actively used in this instance.
+bool scene_on_event_settings_scene(void* context, SceneManagerEvent event) {
+    FURI_LOG_I(TAG, "scene_on_event_settings_scene");
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void scene_on_exit_settings_scene(void* context) {
+    FURI_LOG_I(TAG, "scene_on_exit_settings_scene");
+    UNUSED(context);
+    free(frequencyStr);
+    free(volumeStr);
+}

+ 11 - 0
tone_gen/src/scenes/settings_scene.h

@@ -0,0 +1,11 @@
+#ifndef _SETTINGS_SCENE_H_
+
+#define _SETTINGS_SCENE_H_
+
+#include <gui/scene_manager.h>
+
+void scene_on_enter_settings_scene(void* context);
+bool scene_on_event_settings_scene(void* context, SceneManagerEvent event);
+void scene_on_exit_settings_scene(void* context);
+
+#endif

+ 92 - 0
tone_gen/src/scenes/starting_scene.c

@@ -0,0 +1,92 @@
+#include <gui/modules/menu.h>
+#include <gui/modules/popup.h>
+
+#include "starting_scene.h"
+#include "../app_context.h"
+#include "../tone_gen.h"
+#include "../utils/linked_list.h"
+
+// Icons to include
+#include "tone_gen_icons.h"
+
+/** indices for menu items */
+typedef enum {
+    ToneGenAppMenuSelection_Play,
+    ToneGenAppMenuSelection_Adjust
+} ToneGenAppMenuSelection;
+
+/** main menu callback - sends a custom event to the scene manager based on the menu selection */
+void menu_callback_starting_scene(void* context, uint32_t index) {
+    FURI_LOG_I(TAG, "menu_callback_starting_scene");
+    UNUSED(context);
+    struct AppContext_t* app = context;
+    switch(index) {
+    case ToneGenAppMenuSelection_Play:
+        scene_manager_handle_custom_event(app->scene_manager, ToneGenAppMenuSelection_Play);
+        break;
+    case ToneGenAppMenuSelection_Adjust:
+        scene_manager_handle_custom_event(app->scene_manager, ToneGenAppMenuSelection_Adjust);
+        break;
+    }
+}
+
+/** resets the menu, gives it content, callbacks and selection enums */
+void scene_on_enter_starting_scene(void* context) {
+    FURI_LOG_I(TAG, "scene_on_enter_starting_scene");
+    struct AppContext_t* app = (struct AppContext_t*)context;
+    struct View_t* menuView = app->activeViews[ToneGenAppView_SharedMenu];
+
+    // Set the currently active view
+    menu_reset(menuView->viewData);
+
+    // NB. icons are specified as NULL below, because:
+    // * icons are _always_ animated by the menu
+    // * the icons provided (&I_one, &I_two) are generated by the build process
+    // * these icons do not have a framerate (resulting in a division by zero)
+    menu_add_item(
+        menuView->viewData,
+        "Play Tone",
+        &A_play_button,
+        ToneGenAppMenuSelection_Play,
+        menu_callback_starting_scene,
+        app);
+    menu_add_item(
+        menuView->viewData,
+        "Adjust Tone",
+        &A_settings_button,
+        ToneGenAppMenuSelection_Adjust,
+        menu_callback_starting_scene,
+        app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, ToneGenAppView_SharedMenu);
+}
+
+/** main menu event handler - switches scene based on the event */
+bool scene_on_event_starting_scene(void* context, SceneManagerEvent event) {
+    FURI_LOG_I(TAG, "scene_on_event_starting_scene");
+    UNUSED(context);
+    struct AppContext_t* app = context;
+    bool consumed = false;
+    switch(event.type) {
+    case SceneManagerEventTypeCustom:
+        switch(event.event) {
+        case ToneGenAppMenuSelection_Play:
+            scene_manager_next_scene(app->scene_manager, ToneGenAppScene_Playback);
+            consumed = true;
+            break;
+        case ToneGenAppMenuSelection_Adjust:
+            scene_manager_next_scene(app->scene_manager, ToneGenAppScene_Settings);
+            consumed = true;
+            break;
+        }
+        break;
+    default: // eg. SceneManagerEventTypeBack, SceneManagerEventTypeTick
+        consumed = false;
+        break;
+    }
+    return consumed;
+}
+
+void scene_on_exit_starting_scene(void* context) {
+    FURI_LOG_I(TAG, "scene_on_exit_starting_scene");
+    UNUSED(context);
+}

+ 11 - 0
tone_gen/src/scenes/starting_scene.h

@@ -0,0 +1,11 @@
+#ifndef _STARTING_SCENE_H_
+
+#define _STARTING_SCENE_H_
+
+#include <gui/scene_manager.h>
+
+void scene_on_enter_starting_scene(void* context);
+bool scene_on_event_starting_scene(void* context, SceneManagerEvent event);
+void scene_on_exit_starting_scene(void* context);
+
+#endif

+ 28 - 0
tone_gen/src/system/audio_helper.c

@@ -0,0 +1,28 @@
+#include "audio_helper.h"
+
+bool initializeSpeaker() {
+    return furi_hal_speaker_acquire(SPEAKER_TIMEOUT);
+}
+
+bool startSound(struct ToneData_t* toneData) {
+    bool hasSpeaker = furi_hal_speaker_is_mine();
+    if(!hasSpeaker) {
+        hasSpeaker = furi_hal_speaker_acquire(SPEAKER_TIMEOUT);
+    }
+
+    if(hasSpeaker) {
+        FURI_LOG_I(TAG, "Sound is beginning to play");
+        furi_hal_speaker_start(toneData->frequency, toneData->volume);
+    } else {
+        FURI_LOG_E(TAG, "Error acquiring speaker!");
+    }
+    return hasSpeaker;
+}
+
+void stopSound() {
+    furi_hal_speaker_stop();
+}
+
+void deinitializeSpeaker() {
+    furi_hal_speaker_release();
+}

+ 16 - 0
tone_gen/src/system/audio_helper.h

@@ -0,0 +1,16 @@
+#ifndef _AUDIO_HELPER_H_
+
+#define _AUDIO_HELPER_H_
+
+#include <furi_hal.h>
+
+#include "../tone_gen.h"
+
+#define SPEAKER_TIMEOUT 10
+
+bool initializeSpeaker();
+bool startSound(struct ToneData_t* toneData);
+void stopSound();
+void deinitializeSpeaker();
+
+#endif

+ 140 - 0
tone_gen/src/tone_gen.c

@@ -0,0 +1,140 @@
+#include <gui/canvas.h>
+#include <gui/modules/menu.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/variable_item_list.h>
+
+/* generated by fbt from .png files in images folder */
+#include <tone_gen_icons.h>
+
+#include "app_context.h"
+#include "tone_gen.h"
+
+#include "scenes/starting_scene.h"
+#include "scenes/playback_scene.h"
+#include "scenes/settings_scene.h"
+
+/** collection of all scene on_enter handlers - in the same order as their enum */
+void (*const scene_on_enter_handlers[])(void*) = {
+    scene_on_enter_starting_scene,
+    scene_on_enter_playback_scene,
+    scene_on_enter_settings_scene,
+};
+
+/** collection of all scene on event handlers - in the same order as their enum */
+bool (*const scene_on_event_handlers[])(void*, SceneManagerEvent) = {
+    scene_on_event_starting_scene,
+    scene_on_event_playback_scene,
+    scene_on_event_settings_scene,
+};
+
+/** collection of all scene on exit handlers - in the same order as their enum */
+void (*const scene_on_exit_handlers[])(void*) = {
+    scene_on_exit_starting_scene,
+    scene_on_exit_playback_scene,
+    scene_on_exit_settings_scene,
+};
+
+const SceneManagerHandlers scene_event_handlers = {
+    .on_enter_handlers = scene_on_enter_handlers,
+    .on_event_handlers = scene_on_event_handlers,
+    .on_exit_handlers = scene_on_exit_handlers,
+    .scene_num = ToneGenAppScene_count};
+
+int setupViews(struct AppContext_t** appContext) {
+    // Create views
+    FURI_LOG_I(TAG, "Creating views");
+    struct View_t* sharedMenuView = malloc(sizeof(struct View_t));
+    sharedMenuView->viewData = menu_alloc();
+    sharedMenuView->viewId = ToneGenAppView_SharedMenu;
+    sharedMenuView->type = MENU;
+
+    struct View_t* submenuView = malloc(sizeof(struct View_t));
+    submenuView->viewData = submenu_alloc();
+    submenuView->viewId = ToneGenAppView_Submenu;
+    submenuView->type = SUBMENU;
+
+    struct View_t* playbackView = malloc(sizeof(struct View_t));
+    playbackView->viewData = view_alloc();
+    playbackView->viewId = ToneGenAppView_PlaybackView;
+    playbackView->type = VIEW;
+
+    FURI_LOG_I(TAG, "creating var-item-list view");
+    struct View_t* variableItemListView = malloc(sizeof(struct View_t));
+    FURI_LOG_I(TAG, "allocating view data");
+    variableItemListView->viewData = variable_item_list_alloc();
+    FURI_LOG_I(TAG, "setting view id");
+    variableItemListView->viewId = ToneGenAppView_VariableItemList;
+    FURI_LOG_I(TAG, "setting view type");
+    variableItemListView->type = VARIABLE_ITEM_LIST;
+    FURI_LOG_I(TAG, "moving on");
+
+    // Add views to the app context to be managed there
+    FURI_LOG_I(TAG, "Adding views to app context");
+    AppContextStatus result = addViewToAppContext(appContext, sharedMenuView);
+    if(result != APP_CONTEXT_OK) {
+        FURI_LOG_E(TAG, "There was a problem adding the view %d!", sharedMenuView->viewId);
+        return -1;
+    }
+
+    result = addViewToAppContext(appContext, submenuView);
+    if(result != APP_CONTEXT_OK) {
+        FURI_LOG_E(TAG, "There was a problem adding the view %d!", submenuView->viewId);
+        return -1;
+    }
+
+    result = addViewToAppContext(appContext, playbackView);
+    if(result != APP_CONTEXT_OK) {
+        FURI_LOG_E(TAG, "There was a problem adding the view %d!", playbackView->viewId);
+        return -1;
+    }
+
+    FURI_LOG_I(TAG, "Adding variable item list view");
+    result = addViewToAppContext(appContext, variableItemListView);
+    if(result != APP_CONTEXT_OK) {
+        FURI_LOG_E(TAG, "There was a problem adding the view %d!", variableItemListView->viewId);
+        return -1;
+    }
+
+    // On the playback view, ensure we only allocate for the model once
+    FURI_LOG_I(TAG, "allocating view model for playback");
+    view_allocate_model(playbackView->viewData, ViewModelTypeLockFree, sizeof(struct ToneData_t));
+
+    return 0;
+}
+
+int32_t tone_gen_app(void* p) {
+    UNUSED(p);
+
+    FURI_LOG_I(TAG, "Tone gen app starting...");
+
+    struct AppContext_t* appContext;
+    AppContextStatus result =
+        initializeAppContext(&appContext, ToneGenAppView_count, &scene_event_handlers);
+
+    if(result == APP_CONTEXT_OK) {
+        appContext->additionalData = malloc(sizeof(struct ToneData_t));
+        ((struct ToneData_t*)appContext->additionalData)->animationOffset = 0;
+        ((struct ToneData_t*)appContext->additionalData)->frequency = 440;
+        ((struct ToneData_t*)appContext->additionalData)->waveType = SINE;
+        ((struct ToneData_t*)appContext->additionalData)->volume = 1.0f;
+
+        result = setupViews(&appContext);
+        if(result == 0) {
+            // set the scene and launch the main loop
+            FURI_LOG_D(TAG, "Setting the scene");
+            Gui* gui = furi_record_open(RECORD_GUI);
+            view_dispatcher_attach_to_gui(
+                appContext->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+            scene_manager_next_scene(appContext->scene_manager, ToneGenAppScene_Starting);
+            FURI_LOG_D(TAG, "Starting the view dispatcher");
+            view_dispatcher_run(appContext->view_dispatcher);
+        }
+
+        // free all memory
+        FURI_LOG_D(TAG, "Ending the app");
+        furi_record_close(RECORD_GUI);
+        freeAppContext(&appContext);
+        return 0;
+    }
+    return -1;
+}

+ 36 - 0
tone_gen/src/tone_gen.h

@@ -0,0 +1,36 @@
+#ifndef _TONE_GEN_H_
+
+#define _TONE_GEN_H_
+
+#define TAG "tone-gen"
+
+#include <furi.h>
+
+// ids for all scenes used by the app
+typedef enum {
+    ToneGenAppScene_Starting,
+    ToneGenAppScene_Playback,
+    ToneGenAppScene_Settings,
+    ToneGenAppScene_count
+} ToneGenAppScene;
+
+// ids for the 2 types of view used by the app
+typedef enum {
+    ToneGenAppView_SharedMenu,
+    ToneGenAppView_Submenu,
+    ToneGenAppView_VariableItemList,
+    ToneGenAppView_PlaybackView,
+    ToneGenAppView_Popup,
+    ToneGenAppView_count
+} ToneGenAppView;
+
+typedef enum { SINE, SQUARE } ToneWaveType;
+
+struct ToneData_t {
+    int animationOffset;
+    ToneWaveType waveType;
+    uint16_t frequency;
+    float volume;
+};
+
+#endif

+ 71 - 0
tone_gen/src/utils/linked_list.c

@@ -0,0 +1,71 @@
+#include <stdlib.h>
+
+#include "linked_list.h"
+
+int getLength(const struct ListNode_t* root) {
+    int count = 0;
+    while(root) {
+        root = root->next;
+        count += 1;
+    }
+
+    return count;
+}
+
+LinkedListStatus
+    createNode(struct ListNode_t** emptyNode, struct ListNode_t* previousNode, void* data) {
+    *emptyNode = malloc(sizeof(struct ListNode_t));
+    if(*emptyNode == 0) {
+        return LIST_CANT_ALLOCATE;
+    }
+    (*emptyNode)->data = data;
+    (*emptyNode)->previous = previousNode;
+    return LIST_OK;
+}
+
+LinkedListStatus addNode(struct ListNode_t** root, void* data) {
+    // If there is no root node, add that first
+    if(*root == 0) {
+        return createNode(root, 0, data);
+    }
+
+    // Iterate until we find an empty node
+    struct ListNode_t* base = *root;
+    while(base->next) {
+        base = base->next;
+    }
+    return createNode(&base->next, base, data);
+}
+
+LinkedListStatus removeNode(struct ListNode_t** root, const void* data) {
+    if(*root == 0) {
+        return LIST_NO_NODE;
+    }
+
+    struct ListNode_t* base = *root;
+    while(base->data != data && base->next) {
+        base = base->next;
+    }
+
+    // Delete node if data is matching
+    if(base->data == data) {
+        (base->previous)->next = base->next;
+        (base->next)->previous = base->previous;
+        free(base);
+        return LIST_OK;
+    }
+    return LIST_NO_NODE;
+}
+
+LinkedListStatus removeAllNodes(struct ListNode_t** root) {
+    struct ListNode_t* base = *root;
+    struct ListNode_t* temp;
+    while(base) {
+        temp = base;
+        base = base->next;
+        free(temp);
+    }
+    (*root) = NULL;
+
+    return LIST_OK;
+}

+ 23 - 0
tone_gen/src/utils/linked_list.h

@@ -0,0 +1,23 @@
+#ifndef _LINKED_LIST_H_
+
+#define _LINKED_LIST_H_
+
+typedef enum {
+    LIST_OK = 0,
+    LIST_CANT_ALLOCATE = -1,
+    LIST_NO_NODE = -2,
+} LinkedListStatus;
+
+struct ListNode_t {
+    struct ListNode_t* previous;
+    struct ListNode_t* next;
+
+    void* data;
+};
+
+int getLength(const struct ListNode_t* root);
+LinkedListStatus addNode(struct ListNode_t** root, void* data);
+LinkedListStatus removeNode(struct ListNode_t** root, const void* data);
+LinkedListStatus removeAllNodes(struct ListNode_t** root);
+
+#endif

BIN
tone_gen/tone_gen.png