Ver código fonte

Add Playback Scene + Tone Properties

[Problem]
The playback button doesn't do anything at the moment.

[Solution]
Added the playback scene which displays a waveform based on
the properties of the tone. Properties of the tone are stored
in the application context and then passed to the view model
that displays the animation.

The view for the animation is simply a custom view that has
a callback called to draw to its canvas continuously. The waveform
is drawn with lines in order to ensure it is drawn properly connected.
Can currently display sine waves and square waves.

[Testing]
Ran on device and confirmed working as expected.
Gerald McAlister 2 anos atrás
pai
commit
e6be51ff07

+ 1 - 0
README.MD

@@ -11,6 +11,7 @@ This project depends on [uFBT](https://github.com/flipperdevices/flipperzero-ufb
 The application is structured such that each scene can be self contained, with the sharing of data between scenes done via the app context. All of the scenes can be found with their corresponding source and header files in the `src/scenes` directory. The scenes do as follows:
 
 - **Starting Scene**: The scene where the application starts. This has the main menu options that users see when they start the app.
+- **Playback Scene**: The scene where the sound is played. Animates a waveform with the *approximate* shape of the sound being played.
 - **Settings Scene**: The scene where users can configure the tone's properties.
 
 Note as well that the app context file is generic, and designed in such as way that it should not need to be updated for things specific to the application. This allows for an easier time to allow scenes to self manage, insteaed of having somewhere else that centrally manages everything.

+ 4 - 0
src/app_context.c

@@ -60,6 +60,7 @@ AppContextStatus initializeAppContext(
 
 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;
@@ -77,6 +78,9 @@ AppContextStatus freeAppContextViews(struct AppContext_t** context) {
             case MENU:
                 menu_free(view->viewData);
                 break;
+            case VIEW:
+                view_free(view->viewData);
+                break;
             case POPUP:
                 popup_free(view->viewData);
                 break;

+ 2 - 0
src/app_context.h

@@ -9,6 +9,7 @@
 
 typedef enum {
     MENU,
+    VIEW,
     POPUP,
 } ViewType;
 
@@ -31,6 +32,7 @@ struct AppContext_t {
     ViewDispatcher* view_dispatcher;
     struct View_t** activeViews;
     int activeViewsCount;
+    void* additionalData;
 };
 
 /// @brief Creates an app context with the desired scene handlers.

+ 83 - 0
src/scenes/playback_scene.c

@@ -0,0 +1,83 @@
+#include "playback_scene.h"
+#include "../app_context.h"
+#include "../tone_gen.h"
+
+#define SINE_WAVE(x, toneModelData)                                                    \
+    (toneDataModel->amplitude *                                                        \
+         sin((x + toneDataModel->animationOffset) * 50 * toneDataModel->period) * 20 + \
+     (64 / 2))
+
+#define SQUARE_WAVE(x, toneModelData)                                                            \
+    (toneDataModel->amplitude *                                                                  \
+         (sin((x + toneDataModel->animationOffset) * 50 * toneDataModel->period) > 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->amplitude = ((struct ToneData_t*)app->additionalData)->amplitude;
+    toneDataModel->period = ((struct ToneData_t*)app->additionalData)->period;
+    toneDataModel->waveType = ((struct ToneData_t*)app->additionalData)->waveType;
+
+    // Set the currently active view
+    FURI_LOG_I(TAG, "setting active view");
+    view_dispatcher_switch_to_view(app->view_dispatcher, ToneGenAppView_PlaybackView);
+}
+
+// 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);
+}

+ 11 - 0
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

+ 3 - 4
src/scenes/starting_scene.c

@@ -19,8 +19,7 @@ void menu_callback_starting_scene(void* context, uint32_t index) {
     struct AppContext_t* app = context;
     switch(index) {
     case ToneGenAppMenuSelection_Play:
-        FURI_LOG_I(TAG, "selection one");
-        // scene_manager_handle_custom_event(app->scene_manager, ToneGenAppEvent_StartPlayback);
+        scene_manager_handle_custom_event(app->scene_manager, ToneGenAppMenuSelection_Play);
         break;
     case ToneGenAppMenuSelection_Adjust:
         scene_manager_handle_custom_event(app->scene_manager, ToneGenAppMenuSelection_Adjust);
@@ -68,8 +67,8 @@ bool scene_on_event_starting_scene(void* context, SceneManagerEvent event) {
     case SceneManagerEventTypeCustom:
         switch(event.event) {
         case ToneGenAppMenuSelection_Play:
-            // scene_manager_next_scene(app->scene_manager, ToneGenAppScene_Playback);
-            // consumed = true;
+            scene_manager_next_scene(app->scene_manager, ToneGenAppScene_Playback);
+            consumed = true;
             break;
         case ToneGenAppMenuSelection_Adjust:
             scene_manager_next_scene(app->scene_manager, ToneGenAppScene_Settings);

+ 34 - 0
src/tone_gen.c

@@ -1,3 +1,4 @@
+#include <gui/canvas.h>
 #include <gui/modules/menu.h>
 
 /* generated by fbt from .png files in images folder */
@@ -7,23 +8,27 @@
 #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,
 };
 
@@ -35,23 +40,46 @@ const SceneManagerHandlers scene_event_handlers = {
 
 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* playbackView = malloc(sizeof(struct View_t));
+    playbackView->viewData = view_alloc();
+    playbackView->viewId = ToneGenAppView_PlaybackView;
+    playbackView->type = VIEW;
+
     // Add views to the app context for management later
+    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, playbackView);
+    if(result != APP_CONTEXT_OK) {
+        FURI_LOG_E(TAG, "There was a problem adding the view %d!", playbackView->viewId);
+        return -1;
+    }
+
     // Add views to the view dispatcher for usage later
+    FURI_LOG_I(TAG, "Adding views to view dispatcher");
     view_dispatcher_add_view(
         (*appContext)->view_dispatcher,
         sharedMenuView->viewId,
         menu_get_view(sharedMenuView->viewData));
+    FURI_LOG_I(TAG, "Adding next view to app context");
+    view_dispatcher_add_view(
+        (*appContext)->view_dispatcher, playbackView->viewId, playbackView->viewData);
+    FURI_LOG_I(TAG, "Done making views");
+
+    // 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;
 }
 
@@ -65,6 +93,12 @@ int32_t tone_gen_app(void* p) {
         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)->amplitude = 1;
+        ((struct ToneData_t*)appContext->additionalData)->period = 1;
+        ((struct ToneData_t*)appContext->additionalData)->waveType = SINE;
+
         result = setupViews(&appContext);
         if(result == 0) {
             // set the scene and launch the main loop

+ 11 - 0
src/tone_gen.h

@@ -10,6 +10,7 @@
 // ids for all scenes used by the app
 typedef enum {
     ToneGenAppScene_Starting,
+    ToneGenAppScene_Playback,
     ToneGenAppScene_Settings,
     ToneGenAppScene_count
 } ToneGenAppScene;
@@ -17,8 +18,18 @@ typedef enum {
 // ids for the 2 types of view used by the app
 typedef enum {
     ToneGenAppView_SharedMenu,
+    ToneGenAppView_PlaybackView,
     ToneGenAppView_Popup,
     ToneGenAppView_count
 } ToneGenAppView;
 
+typedef enum { SQUARE, SINE } ToneWaveType;
+
+struct ToneData_t {
+    int animationOffset;
+    int amplitude;
+    int period;
+    ToneWaveType waveType;
+};
+
 #endif