Ihar Biaspalau 3 éve
szülő
commit
10e3ee0751
8 módosított fájl, 782 hozzáadás és 0 törlés
  1. 25 0
      README.md
  2. 14 0
      application.fam
  3. BIN
      img/screenshot_1.png
  4. BIN
      img/screenshot_2.png
  5. 160 0
      notes.h
  6. 395 0
      tuning_fork.c
  7. BIN
      tuning_fork_icon.png
  8. 188 0
      tunings.h

+ 25 - 0
README.md

@@ -0,0 +1,25 @@
+# Tuning Fork
+
+Inspired by [Metronome](https://github.com/panki27/Metronome)
+
+A tuning fork for the [Flipper Zero](https://flipperzero.one/) device.
+Allows to play different types of notes, including guitar tuning.
+
+![screenshot](img/screenshot_1.png)
+
+![screenshot](img/screenshot_2.png)
+
+## Features
+
+- Guitar Standard (6 strings)
+- Guitar Drop D (6 strings)
+- Guitar D (6 strings)
+- Guitar Drop C (6 strings)
+- Tuning forks (440, 432, etc.)
+- Scientific pitch (..., 256, 512, 1024, ...)
+
+## Compiling
+
+```
+./fbt firmware_tuning_fork
+```

+ 14 - 0
application.fam

@@ -0,0 +1,14 @@
+App(
+    appid="tuning_fork",
+    name="Tuning Fork",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="tuning_fork_app",
+    cdefines=["APP_TUNING_FORM"],
+    requires=[
+        "gui",
+    ],
+    fap_icon="tuning_fork_icon.png",
+    fap_category="Music",
+    stack_size=2 * 1024,
+    order=20,
+)

BIN
img/screenshot_1.png


BIN
img/screenshot_2.png


+ 160 - 0
notes.h

@@ -0,0 +1,160 @@
+#ifndef NOTES
+#define NOTES
+
+
+#define C0 16.35f
+#define Cs0 17.32f
+#define Db0 17.32f
+#define D0 18.35f
+#define Ds0 19.45f
+#define Eb0 19.45f
+#define E0 20.60f
+#define F0 21.83f
+#define Fs0 23.12f
+#define Gb0 23.12f
+#define G0 24.50f
+#define Gs0 25.96f
+#define Ab0 25.96f
+#define A0 27.50f
+#define As0 29.14f
+#define Bb0 29.14f
+#define B0 30.868f
+#define C1 32.70f
+#define Cs1 34.65f
+#define Db1 34.65f
+#define D1 36.71f
+#define Ds1 38.89f
+#define Eb1 38.89f
+#define E1 41.203f
+#define F1 43.65f
+#define Fs1 46.25f
+#define Gb1 46.25f
+#define G1 49.00f
+#define Gs1 51.91f
+#define Ab1 51.91f
+#define A1 55.00f
+#define As1 58.27f
+#define Bb1 58.27f
+#define B1 61.74f
+#define C2 65.41f
+#define Cs2 69.30f
+#define Db2 69.30f
+#define D2 73.416f
+#define Ds2 77.78f
+#define Eb2 77.78f
+#define E2 82.41f
+#define F2 87.31f
+#define Fs2 92.50f
+#define Gb2 92.50f
+#define G2 97.999f
+#define Gs2 103.83f
+#define Ab2 103.83f
+#define A2 110.00f
+#define As2 116.54f
+#define Bb2 116.54f
+#define B2 123.47f
+#define C3 130.813f
+#define Cs3 138.59f
+#define Db3 138.59f
+#define D3 146.83f
+#define Ds3 155.56f
+#define Eb3 155.56f
+#define E3 164.81f
+#define F3 174.61f
+#define Fs3 185.00f
+#define Gb3 185.00f
+#define G3 196.00f
+#define Gs3 207.65f
+#define Ab3 207.65f
+#define A3 220.00f
+#define As3 233.08f
+#define Bb3 233.08f
+#define B3 246.94f
+#define C4 261.63f
+#define Cs4 277.18f
+#define Db4 277.18f
+#define D4 293.66f
+#define Ds4 311.13f
+#define Eb4 311.13f
+#define E4 329.63f
+#define F4 349.23f
+#define Fs4 369.99f
+#define Gb4 369.99f
+#define G4 392.00f
+#define Gs4 415.30f
+#define Ab4 415.30f
+#define A4 440.00f
+#define As4 466.16f
+#define Bb4 466.16f
+#define B4 493.88f
+#define C5 523.25f
+#define Cs5 554.37f
+#define Db5 554.37f
+#define D5 587.33f
+#define Ds5 622.25f
+#define Eb5 622.25f
+#define E5 659.25f
+#define F5 698.46f
+#define Fs5 739.99f
+#define Gb5 739.99f
+#define G5 783.99f
+#define Gs5 830.61f
+#define Ab5 830.61f
+#define A5 880.00f
+#define As5 932.33f
+#define Bb5 932.33f
+#define B5 987.77f
+#define C6 1046.50f
+#define Cs6 1108.73f
+#define Db6 1108.73f
+#define D6 1174.66f
+#define Ds6 1244.51f
+#define Eb6 1244.51f
+#define E6 1318.51f
+#define F6 1396.91f
+#define Fs6 1479.98f
+#define Gb6 1479.98f
+#define G6 1567.98f
+#define Gs6 1661.22f
+#define Ab6 1661.22f
+#define A6 1760.00f
+#define As6 1864.66f
+#define Bb6 1864.66f
+#define B6 1975.53f
+#define C7 2093.00f
+#define Cs7 2217.46f
+#define Db7 2217.46f
+#define D7 2349.32f
+#define Ds7 2489.02f
+#define Eb7 2489.02f
+#define E7 2637.02f
+#define F7 2793.83f
+#define Fs7 2959.96f
+#define Gb7 2959.96f
+#define G7 3135.96f
+#define Gs7 3322.44f
+#define Ab7 3322.44f
+#define A7 3520.00f
+#define As7 3729.31f
+#define Bb7 3729.31f
+#define B7 3951.07f
+#define C8 4186.01f
+#define Cs8 4434.92f
+#define Db8 4434.92f
+#define D8 4698.63f
+#define Ds8 4978.03f
+#define Eb8 4978.03f
+#define E8 5274.04f
+#define F8 5587.65f
+#define Fs8 5919.91f
+#define Gb8 5919.91f
+#define G8 6271.93f
+#define Gs8 6644.88f
+#define Ab8 6644.88f
+#define A8 7040.00f
+#define As8 7458.62f
+#define Bb8 7458.62f
+#define B8 7902.13f
+
+#endif //NOTES
+

+ 395 - 0
tuning_fork.c

@@ -0,0 +1,395 @@
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <m-string.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <gui/gui.h>
+#include <gui/elements.h>
+#include <gui/canvas.h>
+
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+
+#include "notes.h"
+#include "tunings.h"
+
+typedef enum {
+  EventTypeTick,
+  EventTypeKey,
+} EventType;
+
+typedef struct {
+  EventType type;
+  InputEvent input;
+} PluginEvent;
+
+enum Page {
+  Tunings,
+  Notes
+};
+
+typedef struct {
+  bool playing;
+  enum Page page;
+  int current_tuning_note_index;
+  int current_tuning_index;
+  float volume;
+  TUNING tuning;
+} TuningForkState;
+
+static TUNING current_tuning(TuningForkState* tuningForkState) {
+  return tuningForkState->tuning;
+}
+
+static NOTE current_tuning_note(TuningForkState* tuningForkState) {
+  return current_tuning(tuningForkState).notes[tuningForkState->current_tuning_note_index];
+}
+
+static float current_tuning_note_freq(TuningForkState* tuningForkState) {
+  return current_tuning_note(tuningForkState).frequency;
+}
+
+static void current_tuning_note_label(TuningForkState* tuningForkState, char* outNoteLabel) {
+  for(int i=0; i < 20; ++i){
+    outNoteLabel[i] = current_tuning_note(tuningForkState).label[i];
+  }
+}
+
+static void current_tuning_label(TuningForkState* tuningForkState, char* outTuningLabel) {
+  for(int i=0; i < 20; ++i){
+    outTuningLabel[i] = current_tuning(tuningForkState).label[i];
+  }
+}
+
+static void updateTuning(TuningForkState* tuning_fork_state) {
+  tuning_fork_state->tuning = TuningList[tuning_fork_state->current_tuning_index];
+  tuning_fork_state->current_tuning_note_index = 0;
+}
+
+static void next_tuning(TuningForkState* tuning_fork_state) {
+  if (tuning_fork_state->current_tuning_index == TUNINGS_COUNT - 1) {
+    tuning_fork_state->current_tuning_index = 0;
+  } else {
+    tuning_fork_state->current_tuning_index += 1;
+  }
+  updateTuning(tuning_fork_state);
+}
+
+static void prev_tuning(TuningForkState* tuning_fork_state) {
+  if (tuning_fork_state->current_tuning_index - 1 < 0) {
+    tuning_fork_state->current_tuning_index = TUNINGS_COUNT - 1;
+  } else {
+    tuning_fork_state->current_tuning_index -= 1;
+  }
+  updateTuning(tuning_fork_state);
+}
+
+static void next_note(TuningForkState* tuning_fork_state) {
+  if (tuning_fork_state->current_tuning_note_index == current_tuning(tuning_fork_state).notes_length - 1) {
+    tuning_fork_state->current_tuning_note_index = 0;
+  } else {
+    tuning_fork_state->current_tuning_note_index += 1;
+  }
+}
+
+static void prev_note(TuningForkState* tuning_fork_state) {
+  if (tuning_fork_state->current_tuning_note_index == 0) {
+    tuning_fork_state->current_tuning_note_index = current_tuning(tuning_fork_state).notes_length - 1;
+  } else {
+    tuning_fork_state->current_tuning_note_index -= 1;
+  }
+}
+
+static void increase_volume(TuningForkState* tuning_fork_state) {
+  if (tuning_fork_state->volume < 1.0f) {
+    tuning_fork_state->volume += 0.1f;
+  }
+}
+
+static void decrease_volume(TuningForkState* tuning_fork_state) {
+  if (tuning_fork_state->volume > 0.0f) {
+    tuning_fork_state->volume -= 0.1f;
+  }
+}
+
+static void play(TuningForkState* tuning_fork_state) {
+  furi_hal_speaker_start(current_tuning_note_freq(tuning_fork_state), tuning_fork_state->volume);
+}
+
+static void stop() {
+  furi_hal_speaker_stop();
+}
+
+static void replay(TuningForkState* tuning_fork_state) {
+  stop();
+  play(tuning_fork_state);
+}
+
+static void render_callback(Canvas* const canvas, void* ctx) {
+  TuningForkState* tuning_fork_state = acquire_mutex((ValueMutex*)ctx, 25);
+  if(tuning_fork_state == NULL) {
+    return;
+  }
+
+  string_t tempStr;
+  string_init(tempStr);
+
+  canvas_draw_frame(canvas, 0, 0, 128, 64);
+
+  canvas_set_font(canvas, FontPrimary);
+
+  if (tuning_fork_state->page == Tunings) {
+    char tuningLabel[20];
+    current_tuning_label(tuning_fork_state, tuningLabel);
+    string_printf(tempStr, "< %s >", tuningLabel);
+    canvas_draw_str_aligned(canvas, 64, 28, AlignCenter, AlignCenter, string_get_cstr(tempStr));
+    string_reset(tempStr);
+  } else {
+    char tuningLabel[20];
+    current_tuning_label(tuning_fork_state, tuningLabel);
+    string_printf(tempStr, "%s", tuningLabel);
+    canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, string_get_cstr(tempStr));
+    string_reset(tempStr);
+
+    char tuningNoteLabel[20];
+    current_tuning_note_label(tuning_fork_state, tuningNoteLabel);
+    string_printf(tempStr, "< %s >", tuningNoteLabel);
+    canvas_draw_str_aligned(canvas, 64, 24, AlignCenter, AlignCenter, string_get_cstr(tempStr));
+    string_reset(tempStr);
+  }
+
+  canvas_set_font(canvas, FontSecondary);
+  elements_button_left(canvas, "Prev");
+  elements_button_right(canvas, "Next");
+
+  if (tuning_fork_state->page == Notes) {
+    if (tuning_fork_state->playing) {
+      elements_button_center(canvas, "Stop ");
+    } else {
+      elements_button_center(canvas, "Play");
+    }
+  } else {
+    elements_button_center(canvas, "Select");
+  }
+  if (tuning_fork_state->page == Notes) {
+    elements_progress_bar(canvas, 8, 36, 112, tuning_fork_state->volume);
+  }
+
+  string_clear(tempStr);
+  release_mutex((ValueMutex*)ctx, tuning_fork_state);
+}
+
+static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+  furi_assert(event_queue);
+
+  PluginEvent event = {.type = EventTypeKey, .input = *input_event};
+  furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+static void tuning_fork_state_init(TuningForkState* const tuning_fork_state) {
+  tuning_fork_state->playing = false;
+  tuning_fork_state->page = Tunings;
+  tuning_fork_state->volume = 1.0f;
+  tuning_fork_state->tuning = GuitarStandard6;
+  tuning_fork_state->current_tuning_index = 2;
+  tuning_fork_state->current_tuning_note_index = 0;
+}
+
+int32_t tuning_fork_app() {
+  FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
+
+  TuningForkState* tuning_fork_state = malloc(sizeof(TuningForkState));
+  tuning_fork_state_init(tuning_fork_state);
+
+  ValueMutex state_mutex;
+  if(!init_mutex(&state_mutex, tuning_fork_state, sizeof(TuningForkState))) {
+    FURI_LOG_E("TuningFork", "cannot create mutex\r\n");
+    free(tuning_fork_state);
+    return 255;
+  }
+
+  // Set system callbacks
+  ViewPort* view_port = view_port_alloc();
+  view_port_draw_callback_set(view_port, render_callback, &state_mutex);
+  view_port_input_callback_set(view_port, input_callback, event_queue);
+
+  Gui* gui = furi_record_open("gui");
+  gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+  PluginEvent event;
+  for(bool processing = true; processing;) {
+    FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+
+    TuningForkState* tuning_fork_state = (TuningForkState*)acquire_mutex_block(&state_mutex);
+
+    if(event_status == FuriStatusOk) {
+      if(event.type == EventTypeKey) {
+        if(event.input.type == InputTypeShort) {
+          // push events
+          switch(event.input.key) {
+            case InputKeyUp:
+              if (tuning_fork_state->page == Notes) {
+                increase_volume(tuning_fork_state);
+                if (tuning_fork_state->playing) {
+                  replay(tuning_fork_state);
+                }
+              }
+              break;
+            case InputKeyDown:
+              if (tuning_fork_state->page == Notes) {
+                decrease_volume(tuning_fork_state);
+                if (tuning_fork_state->playing) {
+                  replay(tuning_fork_state);
+                }
+              }
+              break;
+            case InputKeyRight:
+              if (tuning_fork_state->page == Tunings) {
+                next_tuning(tuning_fork_state);
+              } else {
+                next_note(tuning_fork_state);
+                if (tuning_fork_state->playing) {
+                  replay(tuning_fork_state);
+                }
+              }
+              break;
+            case InputKeyLeft:
+              if (tuning_fork_state->page == Tunings) {
+                prev_tuning(tuning_fork_state);
+              } else {
+                prev_note(tuning_fork_state);
+                if (tuning_fork_state->playing) {
+                  replay(tuning_fork_state);
+                }
+              }
+              break;
+            case InputKeyOk:
+              if (tuning_fork_state->page == Tunings) {
+                tuning_fork_state->page = Notes;
+              } else {
+                tuning_fork_state->playing = !tuning_fork_state->playing;
+                if (tuning_fork_state->playing) {
+                  play(tuning_fork_state);
+                } else {
+                  stop();
+                }
+              }
+              break;
+            case InputKeyBack:
+              if (tuning_fork_state->page == Tunings) {
+                processing = false;
+              } else {
+                tuning_fork_state->playing = false;
+                tuning_fork_state->current_tuning_note_index = 0;
+                stop();
+                tuning_fork_state->page = Tunings;
+              }
+              break;
+          }
+        } else if (event.input.type == InputTypeLong) {
+          // hold events
+          switch(event.input.key) {
+            case InputKeyUp:
+              break;
+            case InputKeyDown:
+              break;
+            case InputKeyRight:
+              if (tuning_fork_state->page == Tunings) {
+                next_tuning(tuning_fork_state);
+              } else {
+                next_note(tuning_fork_state);
+                if (tuning_fork_state->playing) {
+                  replay(tuning_fork_state);
+                }
+              }
+
+              break;
+            case InputKeyLeft:
+              if (tuning_fork_state->page == Tunings) {
+                prev_tuning(tuning_fork_state);
+              } else {
+                prev_note(tuning_fork_state);
+                if (tuning_fork_state->playing) {
+                  replay(tuning_fork_state);
+                }
+              }
+
+              break;
+            case InputKeyOk:
+              break;
+            case InputKeyBack:
+              if (tuning_fork_state->page == Tunings) {
+                processing = false;
+              } else {
+                tuning_fork_state->playing = false;
+                stop();
+                tuning_fork_state->page = Tunings;
+                tuning_fork_state->current_tuning_note_index = 0;
+              }
+              break;
+          }
+        } else if (event.input.type == InputTypeRepeat) {
+          // repeat events
+          switch(event.input.key) {
+            case InputKeyUp:
+              break;
+            case InputKeyDown:
+              break;
+            case InputKeyRight:
+              if (tuning_fork_state->page == Tunings) {
+                next_tuning(tuning_fork_state);
+              } else {
+                next_note(tuning_fork_state);
+                if (tuning_fork_state->playing) {
+                  replay(tuning_fork_state);
+                }
+              }
+
+              break;
+            case InputKeyLeft:
+              if (tuning_fork_state->page == Tunings) {
+                prev_tuning(tuning_fork_state);
+              } else {
+                prev_note(tuning_fork_state);
+                if (tuning_fork_state->playing) {
+                  replay(tuning_fork_state);
+                }
+              }
+
+              break;
+            case InputKeyOk:
+              break;
+            case InputKeyBack:
+              if (tuning_fork_state->page == Tunings) {
+                processing = false;
+              } else {
+                tuning_fork_state->playing = false;
+                stop();
+                tuning_fork_state->page = Tunings;
+                tuning_fork_state->current_tuning_note_index = 0;
+              }
+              break;
+          }
+        }
+      }
+    } else {
+      FURI_LOG_D("TuningFork", "FuriMessageQueue: event timeout");
+    }
+
+    view_port_update(view_port);
+    release_mutex(&state_mutex, tuning_fork_state);
+  }
+
+  view_port_enabled_set(view_port, false);
+  gui_remove_view_port(gui, view_port);
+  furi_record_close("gui");
+  view_port_free(view_port);
+  furi_message_queue_free(event_queue);
+  delete_mutex(&state_mutex);
+  furi_record_close(RECORD_NOTIFICATION);
+  free(tuning_fork_state);
+
+  return 0;
+}

BIN
tuning_fork_icon.png


+ 188 - 0
tunings.h

@@ -0,0 +1,188 @@
+#include "notes.h"
+
+#ifndef TUNINGS
+#define TUNINGS
+
+typedef struct {
+  char label[20];
+  float frequency;
+} NOTE;
+
+typedef struct {
+  char label[20];
+  int notes_length;
+  NOTE notes[20];
+} TUNING;
+
+const TUNING TuningForks = {
+  "Tuning forks", 6, {
+    { "Common A4 (440)", 440.00f},
+    { "Sarti's A4 (436)", 436.00f},
+    { "1858 A4 (435)", 435.00f},
+    { "Verdi's A4 (432)", 432.00f},
+    { "1750-1820 A4 (423.5)", 423.50f},
+    { "Verdi's C4 (256.00)", 256.00f},
+  }
+};
+
+const TUNING ScientificPitch = {
+  "Scientific pitch", 12, {
+    { "C0 (16Hz)", 16.0f},
+    { "C1 (32Hz)", 32.0f},
+    { "C2 (64Hz)", 64.0f},
+    { "C3 (128Hz)", 128.0f},
+    { "C4 (256Hz)", 256.0f},
+    { "C5 (512Hz)", 512.0f},
+    { "C6 (1024Hz)", 1024.0f},
+    { "C7 (2048Hz)", 2048.0f},
+    { "C8 (4096Hz)", 4096.0f},
+    { "C9 (8192Hz)", 8192.0f},
+    { "C10 (16384Hz)", 16384.0f},
+    { "C11 (32768Hz)", 32768.0f}
+  }
+};
+
+const TUNING GuitarStandard6 = {
+  "Guitar Standard 6", 6, {
+    {"String 1", E4},
+    {"String 2", B3},
+    {"String 3", G3},
+    {"String 4", D3},
+    {"String 5", A2},
+    {"String 6", E2}
+  }
+};
+
+const TUNING GuitarDropD6 = {
+  "Guitar Drop D 6", 6, {
+    {"String 1", E4},
+    {"String 2", B3},
+    {"String 3", G3},
+    {"String 4", D3},
+    {"String 5", A2},
+    {"String 6", D2}
+  }
+};
+
+const TUNING GuitarD6 = {
+  "Guitar D 6", 6, {
+    {"String 1", D4},
+    {"String 2", A3},
+    {"String 3", F3},
+    {"String 4", C3},
+    {"String 5", G2},
+    {"String 6", D2}
+  }
+};
+
+const TUNING GuitarDropC6 = {
+  "Guitar Drop C 6", 6, {
+    {"String 1", D4},
+    {"String 2", A3},
+    {"String 3", F3},
+    {"String 4", C3},
+    {"String 5", G2},
+    {"String 6", C2}
+  }
+};
+
+const TUNING GuitarStandard7 = {
+  "Guitar Standard 7", 7, {
+    {"String 1", E4},
+    {"String 2", B3},
+    {"String 3", G3},
+    {"String 4", D3},
+    {"String 5", A2},
+    {"String 6", E2},
+    {"String 7", B1}
+  }
+};
+
+const TUNING BassStandard4 = {
+  "Bass Standard 4", 4, {
+    {"String 1", G2},
+    {"String 2", D2},
+    {"String 3", A1},
+    {"String 4", E1}
+  }
+};
+
+const TUNING BassStandardTenor4 = {
+  "Bass Stand Tenor 4", 4, {
+    {"String 1", C3},
+    {"String 2", G2},
+    {"String 3", D2},
+    {"String 4", A1}
+  }
+};
+
+const TUNING BassStandard5 = {
+  "Bass Standard 5", 5, {
+    {"String 1", G2},
+    {"String 2", D2},
+    {"String 3", A1},
+    {"String 4", E1},
+    {"String 5", B0}
+  }
+};
+
+const TUNING BassStandardTenor5 = {
+  "Bass Stand Tenor 5", 5, {
+    {"String 1", C3},
+    {"String 2", G2},
+    {"String 3", D2},
+    {"String 4", A1},
+    {"String 5", E1}
+  }
+};
+
+const TUNING BassDropD4 = {
+  "Bass Drop D 4", 4, {
+    {"String 1", G2},
+    {"String 2", D2},
+    {"String 3", A1},
+    {"String 4", D1}
+  }
+};
+
+const TUNING BassD4 = {
+  "Bass D 4", 4, {
+    {"String 1", F2},
+    {"String 2", C2},
+    {"String 3", G1},
+    {"String 4", D1}
+  }
+};
+
+const TUNING BassDropA5 = {
+  "Bass Drop A 5", 5, {
+    {"String 1", G2},
+    {"String 2", D2},
+    {"String 3", A1},
+    {"String 4", E1},
+    {"String 5", A0}
+  }
+};
+
+#define TUNINGS_COUNT 14
+
+TUNING TuningList[TUNINGS_COUNT] = {
+  ScientificPitch,
+  TuningForks,
+
+  GuitarStandard6,
+  GuitarDropD6,
+  GuitarD6,
+  GuitarDropC6,
+  GuitarStandard7,
+
+  BassStandard4,
+  BassStandardTenor4,
+  BassStandard5,
+  BassStandardTenor5,
+  BassDropD4,
+  BassD4,
+  BassDropA5
+};
+
+#endif //TUNINGS