Browse Source

Merge tuning_fork from https://github.com/besya/flipperzero-tuning-fork

# Conflicts:
#	tuning_fork/application.fam
#	tuning_fork/tuning_fork.c
#	tuning_fork/tunings.h
Willy-JL 9 months ago
parent
commit
24c7885092

+ 44 - 0
tuning_fork/.catalog/CHANGELOG.md

@@ -0,0 +1,44 @@
+## 2.1 (2025-03-09)
+  - New tunings
+    - Guitar 6 strings
+      - DADGAD
+      - C#G#C#F#G#C#
+      - CGCFGC
+    - Guitar 7 strings
+      - Standard A
+    - Bass 4 strings
+      - Drop C#
+      - Drop C
+  - UI improvements
+    - Display note names
+
+## 2.0 (2025-03-08)
+  - New structure
+    - Instruments
+    - Variations
+    - Tunings
+    - Notes
+  - New tunings
+    - Banjo 5 strings, Cigar Box 3 and 4 strings by @GrafOrlok
+  - UI improvements
+
+## 1.3 (2025-03-08)
+  - Sync updates from @xMasterX
+  - New tunings
+    - Ukulele by @portalsoup
+  - Bug fixes
+
+## 1.2
+  - Sync updates and latest API support
+
+## 1.1
+  - Various important fixes
+
+## 1.0.2 (2022-11-01)
+  - Latest API support
+
+## 1.0.1 (2022-10-18)
+  - Latest API support
+
+## 1.0 (2022-10-13)
+  - Initial release

+ 9 - 0
tuning_fork/.catalog/README.md

@@ -0,0 +1,9 @@
+# Tuning Fork
+
+Tuning fork for tuning musical instruments and more
+
+## Controls
+
+- Left/Right, Select, Back for navigation
+- Up/Down for volume control
+- Play/Stop for playing a note

BIN
tuning_fork/.catalog/screenshots/screenshot1.png


BIN
tuning_fork/.catalog/screenshots/screenshot2.png


BIN
tuning_fork/.catalog/screenshots/screenshot3.png


BIN
tuning_fork/.catalog/screenshots/screenshot4.png


+ 246 - 0
tuning_fork/.clang-format

@@ -0,0 +1,246 @@
+---
+Language:        Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: AlwaysBreak
+AlignArrayOfStructures: None
+AlignConsecutiveAssignments:
+  Enabled:         false
+  AcrossEmptyLines: false
+  AcrossComments:  false
+  AlignCompound:   false
+  AlignFunctionPointers: false
+  PadOperators:    true
+AlignConsecutiveBitFields:
+  Enabled:         true
+  AcrossEmptyLines: true
+  AcrossComments:  true
+  AlignCompound:   false
+  AlignFunctionPointers: false
+  PadOperators:    true
+AlignConsecutiveDeclarations:
+  Enabled:         false
+  AcrossEmptyLines: false
+  AcrossComments:  false
+  AlignCompound:   false
+  AlignFunctionPointers: false
+  PadOperators:    true
+AlignConsecutiveMacros:
+  Enabled:         true
+  AcrossEmptyLines: false
+  AcrossComments:  true
+  AlignCompound:   true
+  AlignFunctionPointers: false
+  PadOperators:    true
+AlignConsecutiveShortCaseStatements:
+  Enabled:         false
+  AcrossEmptyLines: false
+  AcrossComments:  false
+  AlignCaseColons: false
+AlignEscapedNewlines: Left
+AlignOperands:   Align
+AlignTrailingComments:
+  Kind:            Never
+  OverEmptyLines:  0
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowBreakBeforeNoexceptSpecifier: Never
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortCompoundRequirementOnASingleLine: true
+AllowShortEnumsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortLambdasOnASingleLine: All
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+  - __capability
+BinPackArguments: false
+BinPackParameters: false
+BitFieldColonSpacing: Both
+BraceWrapping:
+  AfterCaseLabel:  false
+  AfterClass:      false
+  AfterControlStatement: Never
+  AfterEnum:       false
+  AfterExternBlock: false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  BeforeCatch:     false
+  BeforeElse:      false
+  BeforeLambdaBody: false
+  BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakAdjacentStringLiterals: true
+BreakAfterAttributes: Leave
+BreakAfterJavaFieldAnnotations: false
+BreakArrays:     true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: Always
+BreakBeforeBraces: Attach
+BreakBeforeInlineASMColon: OnlyMultiline
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializers: BeforeComma
+BreakInheritanceList: BeforeColon
+BreakStringLiterals: false
+ColumnLimit:     99
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat:   false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: false
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+  - M_EACH
+IfMacros:
+  - KJ_IF_MAYBE
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseBlocks: false
+IndentCaseLabels: false
+IndentExternBlock: AfterExternBlock
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentRequiresClause: false
+IndentWidth:     4
+IndentWrappedFunctionNames: true
+InsertBraces:    false
+InsertNewlineAtEOF: true
+InsertTrailingCommas: None
+IntegerLiteralSeparator:
+  Binary:          0
+  BinaryMinDigits: 0
+  Decimal:         0
+  DecimalMinDigits: 0
+  Hex:             0
+  HexMinDigits:    0
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+KeepEmptyLinesAtEOF: false
+LambdaBodyIndentation: Signature
+LineEnding:      DeriveLF
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PackConstructorInitializers: BinPack
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakScopeResolution: 500
+PenaltyBreakString: 10
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 100
+PenaltyIndentedWhitespace: 0
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Left
+PPIndentWidth:   -1
+QualifierAlignment: Leave
+ReferenceAlignment: Pointer
+ReflowComments:  false
+RemoveBracesLLVM: false
+RemoveParentheses: Leave
+RemoveSemicolon: true
+RequiresClausePosition: OwnLine
+RequiresExpressionIndentation: OuterScope
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SkipMacroDefinitionBody: false
+SortIncludes:    Never
+SortJavaStaticImport: Before
+SortUsingDeclarations: Never
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeJsonColon: false
+SpaceBeforeParens: Never
+SpaceBeforeParensOptions:
+  AfterControlStatements: false
+  AfterForeachMacros: false
+  AfterFunctionDefinitionName: false
+  AfterFunctionDeclarationName: false
+  AfterIfMacros:   false
+  AfterOverloadedOperator: false
+  AfterPlacementOperator: true
+  AfterRequiresInClause: false
+  AfterRequiresInExpression: false
+  BeforeNonEmptyParentheses: false
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeSquareBrackets: false
+SpaceInEmptyBlock: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  Never
+SpacesInContainerLiterals: false
+SpacesInLineCommentPrefix:
+  Minimum:         1
+  Maximum:         -1
+SpacesInParens:  Never
+SpacesInParensOptions:
+  InCStyleCasts:   false
+  InConditionalStatements: false
+  InEmptyParentheses: false
+  Other:           false
+SpacesInSquareBrackets: false
+Standard:        c++20
+StatementAttributeLikeMacros:
+  - Q_EMIT
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        4
+UseTab:          Never
+VerilogBreakBetweenInstancePorts: true
+WhitespaceSensitiveMacros:
+  - STRINGIZE
+  - PP_STRINGIZE
+  - BOOST_PP_STRINGIZE
+  - NS_SWIFT_NAME
+  - CF_SWIFT_NAME
+...
+

+ 13 - 0
tuning_fork/.editorconfig

@@ -0,0 +1,13 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+
+[*.{cpp,h,c,py,sh}]
+indent_style = space
+indent_size = 4
+
+[{Makefile,*.mk}]
+indent_size = tab

+ 40 - 0
tuning_fork/.github/workflows/build.yml

@@ -0,0 +1,40 @@
+name: "FAP: Build for multiple SDK sources"
+# This will build your app for dev and release channels on GitHub.
+# It will also build your app every day to make sure it's up to date with the latest SDK changes.
+# See https://github.com/marketplace/actions/build-flipper-application-package-fap for more information
+
+on:
+  push:
+    branches:
+     - main
+  pull_request:
+  schedule:
+    # do a build every day
+    - cron: "1 1 * * *"
+
+jobs:
+  ufbt-build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        include:
+          - name: dev channel
+            sdk-channel: dev
+          - name: release channel
+            sdk-channel: release
+          # You can add unofficial channels here. See ufbt action docs for more info.
+    name: 'ufbt: Build for ${{ matrix.name }}'
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+      - name: Build with ufbt
+        uses: flipperdevices/flipperzero-ufbt-action@v0.1
+        id: build-app
+        with:
+          sdk-channel: ${{ matrix.sdk-channel }}
+      - name: Upload app artifacts
+        uses: actions/upload-artifact@v4
+        with:
+          # See ufbt action docs for other output variables
+          name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }}
+          path: ${{ steps.build-app.outputs.fap-artifacts }}

+ 11 - 0
tuning_fork/.gitignore

@@ -0,0 +1,11 @@
+dist/*
+.vscode
+.clangd
+.env
+.ufbt
+.cache
+.nova/*
+!.nova/Tasks/
+!.nova/Tasks/Dev.json
+.DS_Store
+compile_commands.json

+ 17 - 0
tuning_fork/.nova/Tasks/Dev.json

@@ -0,0 +1,17 @@
+{
+  "actions" : {
+    "build" : {
+      "enabled" : true,
+      "script" : "ufbt -c && ufbt && rm -rf .vscode"
+    },
+    "clean" : {
+      "enabled" : true,
+      "script" : "ufbt -c"
+    },
+    "run" : {
+      "enabled" : true,
+      "script" : "ufbt launch"
+    }
+  },
+  "openLogOnRun" : "fail"
+}

+ 74 - 21
tuning_fork/README.md

@@ -1,30 +1,83 @@
 # Tuning Fork
 
-Inspired by [Metronome](https://github.com/panki27/Metronome)
+Tuning fork for tuning musical instruments and more
 
-A tuning fork for the [Flipper Zero](https://flipperzero.one/) device.
-Allows to play different notes in different pitches.
+![tuning_fork](img/tuning_fork.gif)
 
-![screenshot](img/tuning_fork.gif)
+## Tunings
+ - Guitar
+    - 6 strings: Standard, Drop D, DADGAD (Dsus4), Standard D, C#G#C#F#G#C# (C#sus4), Drop C, CGCFGC
+    - 7 strings: Standard, Drop A, Standard A
+ - Bass
+    - 4 strings: Standard, Tenor, Drop D, Standard D, Drop C#, Drop C
+    - 5 strings: Standard, Tenor, Drop A
+ - Ukulele
+    - 4 strings: Standard
+ - Banjo
+    - 5 strings: Standard
+ - Cigar Box
+    - 3 strings: Open G, Open D, Open A
+    - 4 strings: Open G
+ - Tuning Forks: Common A4 (440Hz), Sarti's A4 (436Hz), 1858 A4 (435Hz), 1750-1820 A4 (423.5Hz), Verdi's C4 (256Hz)
+  - Scientific Pitch: C0 (16Hz), C1 (32Hz), C2 (64Hz), C3 (128Hz), C4 (256Hz), C5 (512Hz), C6 (1024Hz), C7 (2048Hz), C8 (4096Hz), C9 (8192Hz), C10 (16384Hz), C11 (32768Hz)
 
-## Features
-- Tuning forks (440Hz, 432Hz, etc.)
-- Scientific pitch (..., 256Hz, 512Hz, 1024Hz, ...)
-- Guitar Standard (6 strings)
-- Guitar Drop D (6 strings)
-- Guitar D (6 strings)
-- Guitar Drop C (6 strings)
-- Guitar Standard (7 strings)
-- Bass Standard (4 strings)
-- Bass Standard Tenor (4 strings)
-- Bass Standard (5 strings)
-- Bass Standard Tenor (5 strings)
-- Bass Drop D (4 strings)
-- Bass D (4 strings)
-- Bass Drop A (5 strings)
+## Development
 
-## Compiling
+### Install ufbt
+> Linux & macOS: `python3 -m pip install --upgrade ufbt`
+> Windows: `py -m pip install --upgrade ufbt`
 
+### Clone repo
 ```
-./fbt firmware_tuning_fork
+git clone https://github.com/besya/flipperzero-tuning-fork.git
 ```
+### Navigate to project folder
+```
+cd flipperzero-tuning-fork
+```
+### Prepare VSCode environment
+```
+ufbt vscode_dist
+```
+### Build app
+```
+ufbt
+```
+> This command creates dist/tuning_fork.fap
+
+### Launch app
+```
+ufbt launch
+```
+> This command deploys app to Flipper Zero and launches an application
+
+### Nova
+For Nova there is a helpful script to setup dev env.
+
+1. Install C-Dragon extension
+2. Run `./nova.sh`
+3. Update C-Dragon settings to use Custom clangd and set Language server Path to one produced by `./nova.sh` command
+4. Restart Nova and enjoy
+
+## Contibuting
+
+1. Fork repo
+1. Clone
+1. Create branch
+1. Make changes
+1. Commit changes and push to branch
+1. Open fork page and click Contribute
+1. Create a pull-request from your branch to this repo's main branch
+
+## Acknowledgements
+
+Special thanks to:
+- [@xMasterX](https://github.com/xMasterX) for distributing and support
+- [@RogueMaster](https://github.com/RogueMaster) for distribution and support
+- [@Willy-JL](https://github.com/@Willy-JL) for fixes
+- [@panki27](https://github.com/@panki27) for inspiration
+- The Flipper Zero community for support and feedback
+
+## License
+
+This project is licensed under the GNU GPL v3 - see the [LICENSE](LICENSE) file for details.

+ 4 - 4
tuning_fork/application.fam

@@ -3,7 +3,7 @@ App(
     name="Tuning Fork",
     apptype=FlipperAppType.EXTERNAL,
     entry_point="tuning_fork_app",
-    cdefines=["APP_TUNING_FORM"],
+    cdefines=["APP_TUNING_FORK"],
     requires=[
         "gui",
     ],
@@ -11,8 +11,8 @@ App(
     fap_category="Media",
     stack_size=2 * 1024,
     order=20,
-    fap_author="@besya & (Fixes by @Willy-JL)",
+    fap_author="@besya",
     fap_weburl="https://github.com/besya/flipperzero-tuning-fork",
-    fap_version="1.2",
-    fap_description="Tuning fork for tuning musical instruments",
+    fap_version="2.1",
+    fap_description="Tuning fork for tuning musical instruments and more",
 )

+ 2 - 0
tuning_fork/constants.h

@@ -0,0 +1,2 @@
+#define MAX_LABEL_LENGTH     20
+#define MAX_NOTES_PER_TUNING 20

BIN
tuning_fork/img/screenshot_1.png


BIN
tuning_fork/img/screenshot_2.png


BIN
tuning_fork/img/tuning_fork.gif


+ 50 - 0
tuning_fork/instruments.h

@@ -0,0 +1,50 @@
+#include "tunings.h"
+
+const TUNING Guitar6Tunings[] = {
+    Guitar6Standard,
+    Guitar6DropD,
+    Guitar6DADGAD,
+    Guitar6D,
+    Guitar6CGCFGCSharp,
+    Guitar6DropC,
+    Guitar6CGCFGC};
+
+const TUNING Guitar7Tunings[] = {Guitar7Standard, Guitar7DropA, Guitar7A};
+const VARIATION GuitarVariations[] = {
+    {"6 strings", (TUNING*)Guitar6Tunings, 7},
+    {"7 strings", (TUNING*)Guitar7Tunings, 3}};
+
+const TUNING Bass4Tunings[] =
+    {Bass4Standard, Bass4Tenor, Bass4DropD, Bass4D, Bass4DropCSharp, Bass4DropC};
+const TUNING Bass5Tunings[] = {Bass5Standard, Bass5Tenor, Bass5DropA};
+const VARIATION BassVariations[] = {
+    {"4 strings", (TUNING*)Bass4Tunings, 6},
+    {"5 strings", (TUNING*)Bass5Tunings, 3}};
+
+const TUNING Ukulele4Tunings[] = {Ukulele4Standard};
+const VARIATION UkuleleVariations[] = {{"4 strings", (TUNING*)Ukulele4Tunings, 1}};
+
+const TUNING Banjo5Tunings[] = {Banjo5Standard};
+const VARIATION BanjoVariations[] = {{"5 strings", (TUNING*)Banjo5Tunings, 1}};
+
+const TUNING CigarBox3Tunings[] = {CigarBox3OpenG, CigarBox3OpenD, CigarBox3OpenA};
+const TUNING CigarBox4Tunings[] = {CigarBox4OpenG};
+const VARIATION CigarBoxVariations[] = {
+    {"3 strings", (TUNING*)CigarBox3Tunings, 3},
+    {"4 strings", (TUNING*)CigarBox4Tunings, 1}};
+
+const TUNING ForkTunings[] = {ForkCommon, ForkSarti, ForkMid19Century, Fork18Century, ForkVerdi};
+const TUNING OtherTunings[] = {ScientificPitch};
+const VARIATION MiscellaneousVariations[] = {
+    {"Forks", (TUNING*)ForkTunings, 5},
+    {"Other", (TUNING*)OtherTunings, 1}};
+
+const INSTRUMENT Instruments[] = {
+    {"Guitar", (VARIATION*)GuitarVariations, 2},
+    {"Bass", (VARIATION*)BassVariations, 2},
+    {"Ukulele", (VARIATION*)UkuleleVariations, 1},
+    {"Banjo", (VARIATION*)BanjoVariations, 1},
+    {"Cigar Box", (VARIATION*)CigarBoxVariations, 2},
+    {"Miscellaneous", (VARIATION*)MiscellaneousVariations, 2}};
+
+#define INSTRUMENTS_COUNT 6

+ 57 - 0
tuning_fork/nova.sh

@@ -0,0 +1,57 @@
+#!/bin/bash
+
+if [[ "$OSTYPE" != "darwin"* ]]; then
+    echo "Unsupported OS"
+    exit 1
+fi
+
+PROJECT_PATH="$(pwd)"
+VSCODE_PATH="${PROJECT_PATH}/.vscode"
+CONFIG="compile_commands.json"
+
+VSCODE_CONFIG_PATH="${VSCODE_PATH}/${CONFIG}"
+PROJECT_CONFIG_PATH="${PROJECT_PATH}/${CONFIG}"
+TMP_PATH="${PROJECT_PATH}/tmp.json"
+TOOLCHAIN_PATH="${HOME}/.ufbt/toolchain/arm64-darwin"
+CLANGD_PATH="${TOOLCHAIN_PATH}/bin/clangd"
+
+FLAGS_TO_ADD=" --target=arm-none-eabi -Wno-unused-include-directive --sysroot=${TOOLCHAIN_PATH}/arm-none-eabi"
+FLAGS_TO_REMOVE="-mword-relocations"
+FLAGS_TO_REMOVE_ARRAY=$(echo "$FLAGS_TO_REMOVE" | jq -R 'split(" ")')
+
+echo "Installing ufbt..."
+python3 -m pip install --upgrade ufbt
+
+echo "Updating ufbt..."
+ufbt update
+
+echo "Generate compile_commands.json"
+ufbt cdb
+
+echo "Move compile_commands.json to project root"
+mv "${VSCODE_CONFIG_PATH}" "${PROJECT_CONFIG_PATH}"
+
+echo "Add extra flags to compile_commands.json"
+jq --arg flags "${FLAGS_TO_ADD}" 'map(.command += $flags)' "${PROJECT_CONFIG_PATH}" > "${TMP_PATH}" && mv "${TMP_PATH}" "${PROJECT_CONFIG_PATH}"
+
+echo "Remove conflicting flags from compile_commands.json"
+jq --argjson flags "${FLAGS_TO_REMOVE_ARRAY}" '
+  map(
+    .command = (
+      (.command | split(" "))
+      | map(select(. as $word | $flags | index($word) | not))
+      | join(" ")
+    )
+  )
+' "${PROJECT_CONFIG_PATH}" > "${TMP_PATH}" && mv "${TMP_PATH}" "${PROJECT_CONFIG_PATH}"
+
+echo "Remove .vscode dir"
+rm -rf "${VSCODE_PATH}"
+
+echo "Remove .clangd file"
+rm -rf "${PROJECT_PATH}/.clangd"
+
+printf "Done\n\n"
+
+echo "Update Language Server path in C-Dragon extension settings to:"
+echo "${CLANGD_PATH}"

+ 270 - 58
tuning_fork/tuning_fork.c

@@ -1,18 +1,23 @@
 #include <furi.h>
 #include <furi_hal.h>
 #include <input/input.h>
-#include <string.h>
 #include <stdlib.h>
+#include <string.h>
 
-#include <gui/gui.h>
-#include <gui/elements.h>
 #include <gui/canvas.h>
+#include <gui/elements.h>
+#include <gui/gui.h>
 
 #include <notification/notification.h>
 #include <notification/notification_messages.h>
 
-#include "notes.h"
 #include "tunings.h"
+#include "instruments.h"
+
+#define VOLUME_STEP             0.1f
+#define DEFAULT_VOLUME          1.0f
+#define QUEUE_SIZE              8
+#define SPEAKER_ACQUIRE_TIMEOUT 1000
 
 typedef enum {
     EventTypeTick,
@@ -25,20 +30,37 @@ typedef struct {
 } PluginEvent;
 
 enum Page {
-    Tunings,
-    Notes
+    InstrumentsPage,
+    VariationsPage,
+    TuningsPage,
+    NotesPage
 };
 
 typedef struct {
     FuriMutex* mutex;
     bool playing;
     enum Page page;
-    int current_tuning_note_index;
-    int current_tuning_index;
     float volume;
+
+    int current_instrument_index;
+    int current_variation_index;
+    int current_tuning_index;
+    int current_tuning_note_index;
+
+    INSTRUMENT instrument;
+    VARIATION variation;
     TUNING tuning;
 } TuningForkState;
 
+// Getters
+static INSTRUMENT current_instrument(TuningForkState* tuningForkState) {
+    return tuningForkState->instrument;
+}
+
+static VARIATION current_variation(TuningForkState* tuningForkState) {
+    return tuningForkState->variation;
+}
+
 static TUNING current_tuning(TuningForkState* tuningForkState) {
     return tuningForkState->tuning;
 }
@@ -51,25 +73,99 @@ 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];
+// String helper
+static void safe_string_copy(char* dest, const char* src, size_t size) {
+    if(dest && src) {
+        strncpy(dest, src, size - 1);
+        dest[size - 1] = '\0';
+    }
+}
+
+// Labels
+static void current_instrument_label(TuningForkState* tuningForkState, char* outCategoryLabel) {
+    if(outCategoryLabel) {
+        safe_string_copy(
+            outCategoryLabel, current_instrument(tuningForkState).label, MAX_LABEL_LENGTH);
+    }
+}
+
+static void current_variation_label(TuningForkState* tuningForkState, char* outCategoryLabel) {
+    if(outCategoryLabel) {
+        safe_string_copy(
+            outCategoryLabel, current_variation(tuningForkState).label, MAX_LABEL_LENGTH);
     }
 }
 
 static void current_tuning_label(TuningForkState* tuningForkState, char* outTuningLabel) {
-    for(int i = 0; i < 20; ++i) {
-        outTuningLabel[i] = current_tuning(tuningForkState).label[i];
+    if(outTuningLabel) {
+        safe_string_copy(outTuningLabel, current_tuning(tuningForkState).label, MAX_LABEL_LENGTH);
+    }
+}
+
+static void current_tuning_note_label(TuningForkState* tuningForkState, char* outNoteLabel) {
+    if(outNoteLabel) {
+        safe_string_copy(
+            outNoteLabel, current_tuning_note(tuningForkState).label, MAX_LABEL_LENGTH);
     }
 }
 
+// Update references
 static void updateTuning(TuningForkState* tuning_fork_state) {
-    tuning_fork_state->tuning = TuningList[tuning_fork_state->current_tuning_index];
+    tuning_fork_state->instrument = Instruments[tuning_fork_state->current_instrument_index];
+    tuning_fork_state->variation =
+        tuning_fork_state->instrument.variations[tuning_fork_state->current_variation_index];
+    tuning_fork_state->tuning =
+        tuning_fork_state->variation.tunings[tuning_fork_state->current_tuning_index];
     tuning_fork_state->current_tuning_note_index = 0;
 }
 
+// Instruments navigation
+static void next_instrument(TuningForkState* tuning_fork_state) {
+    if(tuning_fork_state->current_instrument_index == INSTRUMENTS_COUNT - 1) {
+        tuning_fork_state->current_instrument_index = 0;
+    } else {
+        tuning_fork_state->current_instrument_index += 1;
+    }
+    tuning_fork_state->current_variation_index = 0;
+    tuning_fork_state->current_tuning_index = 0;
+    updateTuning(tuning_fork_state);
+}
+
+static void prev_instrument(TuningForkState* tuning_fork_state) {
+    if(tuning_fork_state->current_instrument_index == 0) {
+        tuning_fork_state->current_instrument_index = INSTRUMENTS_COUNT - 1;
+    } else {
+        tuning_fork_state->current_instrument_index -= 1;
+    }
+    tuning_fork_state->current_variation_index = 0;
+    tuning_fork_state->current_tuning_index = 0;
+    updateTuning(tuning_fork_state);
+}
+
+// Variations navigation
+static void next_variation(TuningForkState* tuning_fork_state) {
+    if(tuning_fork_state->current_variation_index ==
+       tuning_fork_state->instrument.variations_count - 1) {
+        tuning_fork_state->current_variation_index = 0;
+    } else {
+        tuning_fork_state->current_variation_index += 1;
+    }
+    updateTuning(tuning_fork_state);
+}
+
+static void prev_variation(TuningForkState* tuning_fork_state) {
+    if(tuning_fork_state->current_variation_index == 0) {
+        tuning_fork_state->current_variation_index =
+            tuning_fork_state->instrument.variations_count - 1;
+    } else {
+        tuning_fork_state->current_variation_index -= 1;
+    }
+    updateTuning(tuning_fork_state);
+}
+
+// Tunings navigation
 static void next_tuning(TuningForkState* tuning_fork_state) {
-    if(tuning_fork_state->current_tuning_index == TUNINGS_COUNT - 1) {
+    if(tuning_fork_state->current_tuning_index == tuning_fork_state->variation.tunings_count - 1) {
         tuning_fork_state->current_tuning_index = 0;
     } else {
         tuning_fork_state->current_tuning_index += 1;
@@ -78,17 +174,18 @@ static void next_tuning(TuningForkState* 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;
+    if(tuning_fork_state->current_tuning_index == 0) {
+        tuning_fork_state->current_tuning_index = tuning_fork_state->variation.tunings_count - 1;
     } else {
         tuning_fork_state->current_tuning_index -= 1;
     }
     updateTuning(tuning_fork_state);
 }
 
+// Notes navigation
 static void next_note(TuningForkState* tuning_fork_state) {
     if(tuning_fork_state->current_tuning_note_index ==
-       current_tuning(tuning_fork_state).notes_length - 1) {
+       current_tuning(tuning_fork_state).notes_count - 1) {
         tuning_fork_state->current_tuning_note_index = 0;
     } else {
         tuning_fork_state->current_tuning_note_index += 1;
@@ -98,24 +195,26 @@ static void next_note(TuningForkState* tuning_fork_state) {
 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;
+            current_tuning(tuning_fork_state).notes_count - 1;
     } else {
         tuning_fork_state->current_tuning_note_index -= 1;
     }
 }
 
+// Volume adjustments
 static void increase_volume(TuningForkState* tuning_fork_state) {
-    if(tuning_fork_state->volume < 1.0f) {
-        tuning_fork_state->volume += 0.1f;
+    if(tuning_fork_state->volume <= (1.0f - VOLUME_STEP)) {
+        tuning_fork_state->volume += VOLUME_STEP;
     }
 }
 
 static void decrease_volume(TuningForkState* tuning_fork_state) {
-    if(tuning_fork_state->volume > 0.0f) {
-        tuning_fork_state->volume -= 0.1f;
+    if(tuning_fork_state->volume >= VOLUME_STEP) {
+        tuning_fork_state->volume -= VOLUME_STEP;
     }
 }
 
+// Player
 static void play(TuningForkState* tuning_fork_state) {
     if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) {
         furi_hal_speaker_start(
@@ -135,6 +234,7 @@ static void replay(TuningForkState* tuning_fork_state) {
     play(tuning_fork_state);
 }
 
+// Renderer
 static void render_callback(Canvas* const canvas, void* ctx) {
     furi_assert(ctx);
     TuningForkState* tuning_fork_state = ctx;
@@ -143,29 +243,85 @@ static void render_callback(Canvas* const canvas, void* ctx) {
     FuriString* tempStr = furi_string_alloc();
 
     canvas_draw_frame(canvas, 0, 0, 128, 64);
-
     canvas_set_font(canvas, FontPrimary);
 
-    if(tuning_fork_state->page == Tunings) {
-        char tuningLabel[20];
+    if(tuning_fork_state->page == InstrumentsPage) {
+        char instrumentLabel[MAX_LABEL_LENGTH];
+        current_instrument_label(tuning_fork_state, instrumentLabel);
+        furi_string_printf(tempStr, "< %s >", instrumentLabel);
+        canvas_draw_str_aligned(
+            canvas, 64, 28, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
+    } else if(tuning_fork_state->page == VariationsPage) {
+        char instrumentLabel[MAX_LABEL_LENGTH];
+        char variationLabel[MAX_LABEL_LENGTH];
+
+        current_instrument_label(tuning_fork_state, instrumentLabel);
+        current_variation_label(tuning_fork_state, variationLabel);
+
+        furi_string_printf(tempStr, "%s", instrumentLabel);
+        canvas_draw_str_aligned(canvas, 4, 4, AlignLeft, AlignTop, furi_string_get_cstr(tempStr));
+
+        furi_string_reset(tempStr);
+
+        furi_string_printf(tempStr, "< %s >", variationLabel);
+        canvas_draw_str_aligned(
+            canvas, 64, 28, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
+
+    } else if(tuning_fork_state->page == TuningsPage) {
+        char instrumentLabel[MAX_LABEL_LENGTH];
+        char variationLabel[MAX_LABEL_LENGTH];
+        char tuningLabel[MAX_LABEL_LENGTH];
+
+        current_instrument_label(tuning_fork_state, instrumentLabel);
+        current_variation_label(tuning_fork_state, variationLabel);
         current_tuning_label(tuning_fork_state, tuningLabel);
+
+        furi_string_printf(tempStr, "%s", instrumentLabel);
+        canvas_draw_str_aligned(canvas, 4, 4, AlignLeft, AlignTop, furi_string_get_cstr(tempStr));
+
+        furi_string_reset(tempStr);
+
+        furi_string_printf(tempStr, "%s", variationLabel);
+        canvas_draw_str_aligned(
+            canvas, 124, 4, AlignRight, AlignTop, furi_string_get_cstr(tempStr));
+
+        furi_string_reset(tempStr);
+
         furi_string_printf(tempStr, "< %s >", tuningLabel);
         canvas_draw_str_aligned(
             canvas, 64, 28, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
-        furi_string_reset(tempStr);
+
     } else {
-        char tuningLabel[20];
+        char instrumentLabel[MAX_LABEL_LENGTH];
+        char variationLabel[MAX_LABEL_LENGTH];
+        char tuningLabel[MAX_LABEL_LENGTH];
+
+        current_instrument_label(tuning_fork_state, instrumentLabel);
+        current_variation_label(tuning_fork_state, variationLabel);
         current_tuning_label(tuning_fork_state, tuningLabel);
+
+        furi_string_printf(tempStr, "%s", instrumentLabel);
+        canvas_draw_str_aligned(canvas, 4, 4, AlignLeft, AlignTop, furi_string_get_cstr(tempStr));
+
+        furi_string_reset(tempStr);
+
+        furi_string_printf(tempStr, "%s", variationLabel);
+        canvas_draw_str_aligned(
+            canvas, 124, 4, AlignRight, AlignTop, furi_string_get_cstr(tempStr));
+
+        furi_string_reset(tempStr);
+
         furi_string_printf(tempStr, "%s", tuningLabel);
         canvas_draw_str_aligned(
-            canvas, 64, 8, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
+            canvas, 64, 20, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
+
         furi_string_reset(tempStr);
 
-        char tuningNoteLabel[20];
+        char tuningNoteLabel[MAX_LABEL_LENGTH];
         current_tuning_note_label(tuning_fork_state, tuningNoteLabel);
         furi_string_printf(tempStr, "< %s >", tuningNoteLabel);
         canvas_draw_str_aligned(
-            canvas, 64, 24, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
+            canvas, 64, 32, AlignCenter, AlignCenter, furi_string_get_cstr(tempStr));
         furi_string_reset(tempStr);
     }
 
@@ -173,17 +329,18 @@ static void render_callback(Canvas* const canvas, void* ctx) {
     elements_button_left(canvas, "Prev");
     elements_button_right(canvas, "Next");
 
-    if(tuning_fork_state->page == Notes) {
+    if(tuning_fork_state->page == NotesPage) {
         if(tuning_fork_state->playing) {
-            elements_button_center(canvas, "Stop ");
+            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);
+
+    if(tuning_fork_state->page == NotesPage) {
+        elements_progress_bar(canvas, 8, 40, 112, tuning_fork_state->volume);
     }
 
     furi_string_free(tempStr);
@@ -199,24 +356,39 @@ static void input_callback(InputEvent* input_event, void* ctx) {
 }
 
 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;
+    if(tuning_fork_state) {
+        tuning_fork_state->playing = false;
+        tuning_fork_state->page = InstrumentsPage;
+        tuning_fork_state->volume = DEFAULT_VOLUME;
+        tuning_fork_state->current_instrument_index = 0;
+        tuning_fork_state->current_variation_index = 0;
+        tuning_fork_state->current_tuning_index = 0;
+        tuning_fork_state->current_tuning_note_index = 0;
+        updateTuning(tuning_fork_state);
+    }
 }
 
 int32_t tuning_fork_app() {
-    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(QUEUE_SIZE, sizeof(PluginEvent));
+    if(!event_queue) {
+        FURI_LOG_E("TuningFork", "Cannot create message queue\r\n");
+        return 255;
+    }
 
     TuningForkState* tuning_fork_state = malloc(sizeof(TuningForkState));
+    if(!tuning_fork_state) {
+        FURI_LOG_E("TuningFork", "Cannot allocate state\r\n");
+        furi_message_queue_free(event_queue);
+        return 255;
+    }
+
     tuning_fork_state_init(tuning_fork_state);
 
     tuning_fork_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
     if(!tuning_fork_state->mutex) {
         FURI_LOG_E("TuningFork", "cannot create mutex\r\n");
         free(tuning_fork_state);
+        furi_message_queue_free(event_queue);
         return 255;
     }
 
@@ -240,7 +412,7 @@ int32_t tuning_fork_app() {
                     // push events
                     switch(event.input.key) {
                     case InputKeyUp:
-                        if(tuning_fork_state->page == Notes) {
+                        if(tuning_fork_state->page == NotesPage) {
                             increase_volume(tuning_fork_state);
                             if(tuning_fork_state->playing) {
                                 replay(tuning_fork_state);
@@ -248,7 +420,7 @@ int32_t tuning_fork_app() {
                         }
                         break;
                     case InputKeyDown:
-                        if(tuning_fork_state->page == Notes) {
+                        if(tuning_fork_state->page == NotesPage) {
                             decrease_volume(tuning_fork_state);
                             if(tuning_fork_state->playing) {
                                 replay(tuning_fork_state);
@@ -256,7 +428,11 @@ int32_t tuning_fork_app() {
                         }
                         break;
                     case InputKeyRight:
-                        if(tuning_fork_state->page == Tunings) {
+                        if(tuning_fork_state->page == InstrumentsPage) {
+                            next_instrument(tuning_fork_state);
+                        } else if(tuning_fork_state->page == VariationsPage) {
+                            next_variation(tuning_fork_state);
+                        } else if(tuning_fork_state->page == TuningsPage) {
                             next_tuning(tuning_fork_state);
                         } else {
                             next_note(tuning_fork_state);
@@ -266,7 +442,11 @@ int32_t tuning_fork_app() {
                         }
                         break;
                     case InputKeyLeft:
-                        if(tuning_fork_state->page == Tunings) {
+                        if(tuning_fork_state->page == InstrumentsPage) {
+                            prev_instrument(tuning_fork_state);
+                        } else if(tuning_fork_state->page == VariationsPage) {
+                            prev_variation(tuning_fork_state);
+                        } else if(tuning_fork_state->page == TuningsPage) {
                             prev_tuning(tuning_fork_state);
                         } else {
                             prev_note(tuning_fork_state);
@@ -276,8 +456,12 @@ int32_t tuning_fork_app() {
                         }
                         break;
                     case InputKeyOk:
-                        if(tuning_fork_state->page == Tunings) {
-                            tuning_fork_state->page = Notes;
+                        if(tuning_fork_state->page == InstrumentsPage) {
+                            tuning_fork_state->page = VariationsPage;
+                        } else if(tuning_fork_state->page == VariationsPage) {
+                            tuning_fork_state->page = TuningsPage;
+                        } else if(tuning_fork_state->page == TuningsPage) {
+                            tuning_fork_state->page = NotesPage;
                         } else {
                             tuning_fork_state->playing = !tuning_fork_state->playing;
                             if(tuning_fork_state->playing) {
@@ -288,13 +472,17 @@ int32_t tuning_fork_app() {
                         }
                         break;
                     case InputKeyBack:
-                        if(tuning_fork_state->page == Tunings) {
+                        if(tuning_fork_state->page == InstrumentsPage) {
                             processing = false;
+                        } else if(tuning_fork_state->page == VariationsPage) {
+                            tuning_fork_state->page = InstrumentsPage;
+                        } else if(tuning_fork_state->page == TuningsPage) {
+                            tuning_fork_state->page = VariationsPage;
                         } else {
                             tuning_fork_state->playing = false;
                             tuning_fork_state->current_tuning_note_index = 0;
                             stop();
-                            tuning_fork_state->page = Tunings;
+                            tuning_fork_state->page = TuningsPage;
                         }
                         break;
                     default:
@@ -308,7 +496,11 @@ int32_t tuning_fork_app() {
                     case InputKeyDown:
                         break;
                     case InputKeyRight:
-                        if(tuning_fork_state->page == Tunings) {
+                        if(tuning_fork_state->page == InstrumentsPage) {
+                            next_instrument(tuning_fork_state);
+                        } else if(tuning_fork_state->page == VariationsPage) {
+                            next_variation(tuning_fork_state);
+                        } else if(tuning_fork_state->page == TuningsPage) {
                             next_tuning(tuning_fork_state);
                         } else {
                             next_note(tuning_fork_state);
@@ -319,7 +511,11 @@ int32_t tuning_fork_app() {
 
                         break;
                     case InputKeyLeft:
-                        if(tuning_fork_state->page == Tunings) {
+                        if(tuning_fork_state->page == InstrumentsPage) {
+                            prev_instrument(tuning_fork_state);
+                        } else if(tuning_fork_state->page == VariationsPage) {
+                            prev_variation(tuning_fork_state);
+                        } else if(tuning_fork_state->page == TuningsPage) {
                             prev_tuning(tuning_fork_state);
                         } else {
                             prev_note(tuning_fork_state);
@@ -332,12 +528,16 @@ int32_t tuning_fork_app() {
                     case InputKeyOk:
                         break;
                     case InputKeyBack:
-                        if(tuning_fork_state->page == Tunings) {
+                        if(tuning_fork_state->page == InstrumentsPage) {
                             processing = false;
+                        } else if(tuning_fork_state->page == VariationsPage) {
+                            tuning_fork_state->page = InstrumentsPage;
+                        } else if(tuning_fork_state->page == TuningsPage) {
+                            tuning_fork_state->page = VariationsPage;
                         } else {
                             tuning_fork_state->playing = false;
                             stop();
-                            tuning_fork_state->page = Tunings;
+                            tuning_fork_state->page = TuningsPage;
                             tuning_fork_state->current_tuning_note_index = 0;
                         }
                         break;
@@ -352,7 +552,11 @@ int32_t tuning_fork_app() {
                     case InputKeyDown:
                         break;
                     case InputKeyRight:
-                        if(tuning_fork_state->page == Tunings) {
+                        if(tuning_fork_state->page == InstrumentsPage) {
+                            next_instrument(tuning_fork_state);
+                        } else if(tuning_fork_state->page == VariationsPage) {
+                            next_variation(tuning_fork_state);
+                        } else if(tuning_fork_state->page == TuningsPage) {
                             next_tuning(tuning_fork_state);
                         } else {
                             next_note(tuning_fork_state);
@@ -363,7 +567,11 @@ int32_t tuning_fork_app() {
 
                         break;
                     case InputKeyLeft:
-                        if(tuning_fork_state->page == Tunings) {
+                        if(tuning_fork_state->page == InstrumentsPage) {
+                            prev_instrument(tuning_fork_state);
+                        } else if(tuning_fork_state->page == VariationsPage) {
+                            prev_variation(tuning_fork_state);
+                        } else if(tuning_fork_state->page == TuningsPage) {
                             prev_tuning(tuning_fork_state);
                         } else {
                             prev_note(tuning_fork_state);
@@ -376,12 +584,16 @@ int32_t tuning_fork_app() {
                     case InputKeyOk:
                         break;
                     case InputKeyBack:
-                        if(tuning_fork_state->page == Tunings) {
+                        if(tuning_fork_state->page == InstrumentsPage) {
                             processing = false;
+                        } else if(tuning_fork_state->page == VariationsPage) {
+                            tuning_fork_state->page = InstrumentsPage;
+                        } else if(tuning_fork_state->page == TuningsPage) {
+                            tuning_fork_state->page = VariationsPage;
                         } else {
                             tuning_fork_state->playing = false;
                             stop();
-                            tuning_fork_state->page = Tunings;
+                            tuning_fork_state->page = TuningsPage;
                             tuning_fork_state->current_tuning_note_index = 0;
                         }
                         break;

+ 230 - 143
tuning_fork/tunings.h

@@ -1,34 +1,232 @@
-#include "notes.h"
+#ifndef TUNINGS_H
+#define TUNINGS_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},
-    }};
+#include <stdint.h>
+#include "constants.h"
+#include "types.h"
+#include "notes.h"
 
+// GUITAR
+// 6 strings
+const TUNING Guitar6Standard = {
+    "Standard",
+    {{"String 1 (E4)", E4},
+     {"String 2 (B3)", B3},
+     {"String 3 (G3)", G3},
+     {"String 4 (D3)", D3},
+     {"String 5 (A2)", A2},
+     {"String 6 (E2)", E2}},
+    6};
+
+const TUNING Guitar6DropD = {
+    "Drop D",
+    {{"String 1 (E4)", E4},
+     {"String 2 (B3)", B3},
+     {"String 3 (G3)", G3},
+     {"String 4 (D3)", D3},
+     {"String 5 (A2)", A2},
+     {"String 6 (D2)", D2}},
+    6};
+
+const TUNING Guitar6DADGAD = {
+    "DADGAD (Dsus4)",
+    {{"String 1 (D4)", D4},
+     {"String 2 (A3)", A3},
+     {"String 3 (G3)", G3},
+     {"String 4 (D3)", D3},
+     {"String 5 (A2)", A2},
+     {"String 6 (D2)", D2}},
+    6};
+
+const TUNING Guitar6D = {
+    "Standard D",
+    {{"String 1 (D4)", D4},
+     {"String 2 (A3)", A3},
+     {"String 3 (F3)", F3},
+     {"String 4 (C3)", C3},
+     {"String 5 (G2)", G2},
+     {"String 6 (D2)", D2}},
+    6};
+
+const TUNING Guitar6CGCFGCSharp = {
+    "C#G#C#F#G#C#(C#sus4)",
+    {{"String 1 (C#4)", Cs4},
+     {"String 2 (G#3)", Gs3},
+     {"String 3 (F#3)", Fs3},
+     {"String 4 (C#3)", Cs3},
+     {"String 5 (G#2)", Gs2},
+     {"String 6 (C#2)", Cs2}},
+    6};
+
+const TUNING Guitar6DropC = {
+    "Drop C",
+    {{"String 1 (D4)", D4},
+     {"String 2 (A3)", A3},
+     {"String 3 (F3)", F3},
+     {"String 4 (C3)", C3},
+     {"String 5 (G2)", G2},
+     {"String 6 (C2)", C2}},
+    6};
+
+const TUNING Guitar6CGCFGC = {
+    "CGCFGC (Csus4)",
+    {{"String 1 (C4)", C4},
+     {"String 2 (G3)", G3},
+     {"String 3 (F3)", F3},
+     {"String 4 (C3)", C3},
+     {"String 5 (G2)", G2},
+     {"String 6 (C2)", C2}},
+    6};
+
+// 7 strings
+const TUNING Guitar7Standard = {
+    "Standard",
+    {{"String 1 (E4)", E4},
+     {"String 2 (B3)", B3},
+     {"String 3 (G3)", G3},
+     {"String 4 (D3)", D3},
+     {"String 5 (A2)", A2},
+     {"String 6 (E2)", E2},
+     {"String 7 (B1)", B1}},
+    7};
+
+const TUNING Guitar7DropA = {
+    "Drop A",
+    {{"String 1 (E4)", E4},
+     {"String 2 (B3)", B3},
+     {"String 3 (G3)", G3},
+     {"String 4 (D3)", D3},
+     {"String 5 (A2)", A2},
+     {"String 6 (E2)", E2},
+     {"String 7 (A1)", A1}},
+    7};
+
+const TUNING Guitar7A = {
+    "Standard A",
+    {{"String 1 (D4)", D4},
+     {"String 2 (A3)", A3},
+     {"String 3 (F3)", F3},
+     {"String 4 (C3)", C3},
+     {"String 5 (G2)", G2},
+     {"String 6 (D2)", D2},
+     {"String 7 (A1)", A1}},
+    7};
+
+// BASS
+// 4 strings
+const TUNING Bass4Standard = {
+    "Standard",
+    {{"String 1 (G2)", G2}, {"String 2 (D2)", D2}, {"String 3 (A1)", A1}, {"String 4 (E1)", E1}},
+    4};
+
+const TUNING Bass4Tenor = {
+    "Tenor",
+    {{"String 1 (C3)", C3}, {"String 2 (G2)", G2}, {"String 3 (D2)", D2}, {"String 4 (A1)", A1}},
+    4};
+
+const TUNING Bass4DropD = {
+    "Drop D",
+    {{"String 1 (G2)", G2}, {"String 2 (D2)", D2}, {"String 3 (A1)", A1}, {"String 4 (D1)", D1}},
+    4};
+
+const TUNING Bass4D = {
+    "Standard D",
+    {{"String 1 (F2)", F2}, {"String 2 (C2)", C2}, {"String 3 (G1)", G1}, {"String 4 (D1)", D1}},
+    4};
+
+const TUNING Bass4DropCSharp = {
+    "Drop C#",
+    {{"String 1 (F#2)", Fs2},
+     {"String 2 (C#2)", Cs2},
+     {"String 3 (G#1)", Gs1},
+     {"String 4 (C#1)", Cs1}},
+    4};
+
+const TUNING Bass4DropC = {
+    "Drop C",
+    {{"String 1 (F2)", F2}, {"String 2 (C2)", C2}, {"String 3 (G1)", G1}, {"String 4 (C1)", C1}},
+    4};
+
+// 5 strings
+const TUNING Bass5Standard = {
+    "Standard",
+    {{"String 1 (G2)", G2},
+     {"String 2 (D2)", D2},
+     {"String 3 (A1)", A1},
+     {"String 4 (E1)", E1},
+     {"String 5 (B0)", B0}},
+    5};
+
+const TUNING Bass5Tenor = {
+    "Tenor",
+    {{"String 1 (C3)", C3},
+     {"String 2 (G2)", G2},
+     {"String 3 (D2)", D2},
+     {"String 4 (A1)", A1},
+     {"String 5 (E1)", E1}},
+    5};
+
+const TUNING Bass5DropA = {
+    "Drop A",
+    {{"String 1 (G2)", G2},
+     {"String 2 (D2)", D2},
+     {"String 3 (A1)", A1},
+     {"String 4 (E1)", E1},
+     {"String 5 (A0)", A0}},
+    5};
+
+// UKULELE
+// 4 strings
+const TUNING Ukulele4Standard = {
+    "Standard",
+    {{"String 1 (A4)", A4}, {"String 2 (E4)", E4}, {"String 3 (C4)", C4}, {"String 4 (G4)", G4}},
+    4};
+
+// BANJO
+// 5 strings
+const TUNING Banjo5Standard = {
+    "Standard",
+    {{"String 1 (D4)", D4},
+     {"String 2 (B3)", B3},
+     {"String 3 (G3)", G3},
+     {"String 4 (D3)", D3},
+     {"String 5 (G4)", G4}},
+    5};
+
+// CIGAR BOX
+// 3 strings
+const TUNING CigarBox3OpenG = {
+    "Open G",
+    {{"String 1 (G3)", G3}, {"String 2 (D3)", D3}, {"String 3 (G2)", G2}},
+    3};
+
+const TUNING CigarBox3OpenD = {
+    "Open D",
+    {{"String 1 (Fs4)", Fs4}, {"String 2 (A3)", A3}, {"String 3 (D3)", D3}},
+    3};
+
+const TUNING CigarBox3OpenA = {
+    "Open A",
+    {{"String 1 (A4)", A4}, {"String 2 (E4)", E4}, {"String 3 (A3)", A3}},
+    3};
+
+// 4 strings
+const TUNING CigarBox4OpenG = {
+    "Open G",
+    {{"String 1 (D4)", D4}, {"String 2 (B3)", B3}, {"String 3 (G3)", G3}, {"String 4 (D3)", D3}},
+    4};
+
+// MISCELLANEOUS
+// Fork Tunings
+const TUNING ForkCommon = {"Common", {{"A4 (440Hz)", 440.00f}}, 1};
+const TUNING ForkSarti = {"Sarti's", {{"A4 (436Hz)", 436.00f}}, 1};
+const TUNING ForkMid19Century = {"1858", {{"A4 (435Hz)", 435.00f}}, 1};
+const TUNING Fork18Century = {"1750-1820", {{"A4 (423.5Hz)", 423.50f}}, 1};
+const TUNING ForkVerdi = {"Verdi's", {{"C4 (256Hz)", 256.00f}}, 1};
+
+// Other Tunings
 const TUNING ScientificPitch = {
-    "Scientific pitch",
-    12,
+    "Scientific Pitch",
     {{"C0 (16Hz)", 16.0f},
      {"C1 (32Hz)", 32.0f},
      {"C2 (64Hz)", 64.0f},
@@ -40,118 +238,7 @@ const TUNING ScientificPitch = {
      {"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}}};
-
-const TUNING UkuleleStandard4 = {
-    "Ukulele Standard 4",
-    4,
-    {{"String 1", A4}, {"String 2", E4}, {"String 3", C4}, {"String 4", G4}}};
-
-#define TUNINGS_COUNT 15
-
-TUNING TuningList[TUNINGS_COUNT] = {
-    ScientificPitch,
-    TuningForks,
-
-    GuitarStandard6,
-    GuitarDropD6,
-    GuitarD6,
-    GuitarDropC6,
-    GuitarStandard7,
-
-    BassStandard4,
-    BassStandardTenor4,
-    BassStandard5,
-    BassStandardTenor5,
-    BassDropD4,
-    BassD4,
-    BassDropA5,
-    UkuleleStandard4};
-
-#endif //TUNINGS
+     {"C11 (32768Hz)", 32768.0f}},
+    12};
+
+#endif //TUNINGS_H

+ 25 - 0
tuning_fork/types.h

@@ -0,0 +1,25 @@
+#include <stdint.h>
+#include "constants.h"
+
+typedef struct {
+    char label[MAX_LABEL_LENGTH];
+    float frequency;
+} NOTE;
+
+typedef struct {
+    char label[MAX_LABEL_LENGTH];
+    NOTE notes[MAX_NOTES_PER_TUNING];
+    uint8_t notes_count;
+} TUNING;
+
+typedef struct {
+    char label[MAX_LABEL_LENGTH];
+    TUNING* tunings;
+    uint8_t tunings_count;
+} VARIATION;
+
+typedef struct {
+    char label[MAX_LABEL_LENGTH];
+    VARIATION* variations;
+    uint8_t variations_count;
+} INSTRUMENT;