From b9942c512bbcda13fa87b910f02062f01d77b713 Mon Sep 17 00:00:00 2001 From: rai <96561881+r4ai@users.noreply.github.com> Date: Sat, 1 Apr 2023 21:02:03 +0900 Subject: [PATCH] Feature: synth preset customization (#14) * refactor: move sequenceOption type and defaultValue * add config feature * refactor ui * fix * feat: save synth config * update app version --- .prettierrc.yaml | 2 +- .vscode/settings.json | 3 + src-tauri/tauri.conf.json | 6 +- src/App.svelte | 8 +- src/lib/Sequence.svelte | 50 +++--- src/lib/config_panel/ConfigPanel.svelte | 216 ++++++++++++++++++------ src/lib/config_panel/Input.svelte | 3 +- src/lib/config_panel/Slider.svelte | 9 +- src/lib/constants.ts | 40 ++++- src/lib/settings.ts | 2 +- src/lib/stores.ts | 42 ++++- src/tests/Sequence.test.ts | 13 +- 12 files changed, 299 insertions(+), 95 deletions(-) diff --git a/.prettierrc.yaml b/.prettierrc.yaml index e29cc0a..15b390a 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -12,7 +12,7 @@ plugins: - "prettier-plugin-organize-imports" - "prettier-plugin-tailwindcss" pluginSearchDirs: - - "." + - "./src/" overrides: - files: "*.svelte" options: diff --git a/.vscode/settings.json b/.vscode/settings.json index 1cb24d2..b5a69bb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,6 +19,7 @@ "vs-code-prettier-eslint.prettierLast": false, // set as "true" to run 'prettier' last not first "prettier.documentSelectors": ["**/*.svelte"], "prettier.configPath": ".prettierrc.yaml", + "prettier.ignorePath": ".prettierignore", "[svelte]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, @@ -35,9 +36,11 @@ "cSpell.words": [ "ACRN", "APPCONFIG", + "fatsine", "headphonejames", "imgs", "Neuromodulation", + "testid", "Unsubscriber" ], "markdownlint.config": { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 590abe1..c85df66 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "tinnitus-reducer-ACRN", - "version": "0.1.3" + "version": "0.2.0" }, "tauri": { "allowlist": { @@ -68,7 +68,9 @@ "resizable": true, "title": "tinnitus reducer ACRN", "width": 800, - "height": 600 + "minWidth": 450, + "height": 600, + "minHeight": 200 } ] } diff --git a/src/App.svelte b/src/App.svelte index 3d52c31..5edb2d9 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -12,6 +12,9 @@ theme, pan, clientWidth, + loopRepeat, + restLength, + duration, } from "./lib/stores"; import FrequencyController from "./lib/FrequencyController.svelte"; import PlayController from "./lib/PlayController.svelte"; @@ -27,7 +30,7 @@ import Navbar from "./lib/nav/Navbar.svelte"; import ConfigPanel from "./lib/config_panel/ConfigPanel.svelte"; import { isTauri } from "./lib/utils"; - import type { SettingsScheme } from "./lib/constants"; + import type { SettingsScheme } from "./lib/stores"; let unsubscribeStores: Unsubscriber[] | undefined = undefined; let unsubscribeLazySave: Unsubscriber | undefined = undefined; @@ -68,6 +71,9 @@ $bpm = [settings.bpm]; $pan = [settings.pan]; $theme = settings.theme; + $loopRepeat = [settings.loopRepeat]; + $restLength = [settings.restLength]; + $duration = [settings.duration]; } // * Setup Tone.js diff --git a/src/lib/Sequence.svelte b/src/lib/Sequence.svelte index e352c81..0a3dadd 100644 --- a/src/lib/Sequence.svelte +++ b/src/lib/Sequence.svelte @@ -83,18 +83,6 @@ return synth; } - export type SequenceOption = { - loopRepeat: number; - restLength: number; - duration: Tone.Unit.Time; - }; - - export const defaultSequenceOption: SequenceOption = { - loopRepeat: 3, - restLength: 2, - duration: "4n", - }; - /** * Create a sequence of frequencies and start it. * TODO: Refactor the code inside the Tone.Sequence @@ -105,7 +93,7 @@ export function createSequence( synth: PolySynth | Synth, frequencies: number[], - { loopRepeat, restLength, duration } = defaultSequenceOption + { loopRepeat, restLength, duration } = DEFAULT_SEQUENCE_OPTION ): Sequence { let currentFrequencies = shuffledFrequencies(frequencies); @@ -139,7 +127,7 @@ oldSeq: Sequence | undefined, synth: PolySynth | Synth | undefined, frequencies: number[], - option = defaultSequenceOption + option = DEFAULT_SEQUENCE_OPTION ): Sequence | undefined { if (synth) { oldSeq?.dispose(); @@ -155,13 +143,27 @@ + -
- - 540} - suffix="dB" - sliderProps={{ - pips: true, - pipstep: 20, - all: "label", - }} - /> - - match(value) - .with(0, () => "Center") - .with(-1, () => "Left") - .with(1, () => "Right") - .otherwise(() => value.toString()), - }} - /> -
+
+
+

Configuration

+
+
540 * 2 ? "flex-row" : "flex-col"} gap-4`}> +
+ + 540} + suffix="dB" + sliderProps={{ + pips: true, + pipstep: 20, + all: "label", + }} + /> + + match(value) + .with(0, () => "Center") + .with(-1, () => "Left") + .with(1, () => "Right") + .otherwise(() => value.toString()), + }} + /> +
+
+ e === MAX_DURATION)} + minValue={subdivisions.findIndex(e => e === MIN_DURATION)} + step={1} + isVertical={$clientWidth > 540} + sliderProps={{ + pips: true, + pipstep: 1, + all: "label", + formatter: value => subdivisions[value], + }} + /> + + +
+
+
diff --git a/src/lib/config_panel/Input.svelte b/src/lib/config_panel/Input.svelte index c9f1faf..4c13517 100644 --- a/src/lib/config_panel/Input.svelte +++ b/src/lib/config_panel/Input.svelte @@ -1,10 +1,11 @@
diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 881b055..9343f73 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,3 +1,5 @@ +import type { Duration, SequenceOption, SettingsScheme } from "./stores"; + /* Used for internal purposes */ export const INITIAL_THEME = "dark"; @@ -23,18 +25,35 @@ export const INITIAL_PAN = 0; // means center export const MAX_PAN = 1; // means right export const MIN_PAN = -1; // means left -export const LOOP_REPEAT = 4; -export const REST_LENGTH = 4; +/** + * The number of times the sequence will repeat. + */ +export const DEFAULT_LOOP_REPEAT = 3; +export const MIN_LOOP_REPEAT = 1; +export const MAX_LOOP_REPEAT = 10; + +/** + * The number of rests in the sequence. + */ +export const DEFAULT_REST_LENGTH = 2 * 4; +export const MIN_REST_LENGTH = 0; +export const MAX_REST_LENGTH = 16 * 4; + +/** + * The duration of the note in the sequence. + */ +export const DEFAULT_DURATION: Duration = "4n"; // 4n = 1/4 +export const MIN_DURATION: Duration = "128n"; // 16n = 1/16 +export const MAX_DURATION: Duration = "1n"; // 1n = 1/1 + +export const DEFAULT_SEQUENCE_OPTION: SequenceOption = { + loopRepeat: DEFAULT_LOOP_REPEAT, + restLength: DEFAULT_REST_LENGTH, + duration: DEFAULT_DURATION, +}; /* settings */ export const LOCAL_STORAGE_SETTINGS_KEY = "settings"; -export type SettingsScheme = { - theme: "dark" | "light"; - frequency: number; - bpm: number; - volume: number; - pan: number; -}; export const SETTINGS_FILE_NAME = "user-settings"; export const DEFAULT_SETTINGS: SettingsScheme = { theme: INITIAL_THEME, @@ -42,4 +61,7 @@ export const DEFAULT_SETTINGS: SettingsScheme = { bpm: INITIAL_BPM, volume: INITIAL_VOLUME, pan: INITIAL_PAN, + loopRepeat: DEFAULT_LOOP_REPEAT, + restLength: DEFAULT_REST_LENGTH, + duration: DEFAULT_DURATION, }; diff --git a/src/lib/settings.ts b/src/lib/settings.ts index abcc30d..288ca9d 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -3,13 +3,13 @@ import { LOCAL_STORAGE_SETTINGS_KEY, SAVE_DELAY_TIME, SETTINGS_FILE_NAME, - type SettingsScheme, } from "@/lib/constants"; import { get, writable, type Writable } from "svelte/store"; import { getAll } from "tauri-settings"; import { STATUS } from "tauri-settings/dist/fs/ensure-settings-file"; import { saveSettings as saveAll } from "tauri-settings/dist/fs/load-save"; import { match } from "ts-pattern"; +import type { SettingsScheme } from "./stores"; import { isTauri } from "./utils"; export const timer = writable(0); // 0 ~ SAVE_DELAY_TIME diff --git a/src/lib/stores.ts b/src/lib/stores.ts index a94f03a..b6a7cf7 100644 --- a/src/lib/stores.ts +++ b/src/lib/stores.ts @@ -1,5 +1,9 @@ import { writable, type Writable } from "svelte/store"; +import type { Unit } from "tone"; import { + DEFAULT_DURATION, + DEFAULT_LOOP_REPEAT, + DEFAULT_REST_LENGTH, INITIAL_BPM, INITIAL_FREQUENCY, INITIAL_PAN, @@ -9,15 +13,42 @@ import { updateCache } from "./settings"; export type Mode = "TONE" | "ACRN"; export type Theme = "light" | "dark"; +export type SequenceOption = { + loopRepeat: number; + restLength: number; + duration: Unit.Time; +}; +export type Duration = + | "1n" + | "2n" + | "4n" + | "8n" + | "16n" + | "32n" + | "64n" + | "128n" + | "256n"; +export type SettingsScheme = { + theme: "dark" | "light"; + frequency: number; + bpm: number; + volume: number; + pan: number; + loopRepeat: number; + restLength: number; + duration: Duration; +}; export const frequency = writable([INITIAL_FREQUENCY]); // 0kHz to 15kHz export const volume = writable([INITIAL_VOLUME]); // 0 ~ 100 export const bpm = writable([INITIAL_BPM]); // According to the paper, the cycle repetition rate was 1.5 Hz. (T = 0.66 s) export const pan = writable([INITIAL_PAN]); // -1 ~ 1 +export const loopRepeat = writable([DEFAULT_LOOP_REPEAT]); +export const restLength = writable([DEFAULT_REST_LENGTH]); +export const duration: Writable = writable([DEFAULT_DURATION]); export const mode: Writable = writable("TONE"); export const isPlaying = writable(false); export const theme: Writable = writable("light"); - export const clientWidth = writable(0); /** @@ -41,6 +72,15 @@ export function subscribeStores() { pan.subscribe(value => { updateCache({ pan: value[0] }); }), + loopRepeat.subscribe(value => { + updateCache({ loopRepeat: value[0] }); + }), + restLength.subscribe(value => { + updateCache({ restLength: value[0] }); + }), + duration.subscribe(value => { + updateCache({ duration: value[0] }); + }), ]; return unsubscribe; } diff --git a/src/tests/Sequence.test.ts b/src/tests/Sequence.test.ts index d2db6d8..792a046 100644 --- a/src/tests/Sequence.test.ts +++ b/src/tests/Sequence.test.ts @@ -1,3 +1,4 @@ +import { DEFAULT_SEQUENCE_OPTION } from "@/lib/constants"; import "@testing-library/jest-dom"; import type { PolySynth } from "tone"; import * as Tone from "tone"; @@ -6,7 +7,6 @@ import { createPanner } from "../lib/Oscillator.svelte"; import { createSequence, createSynth, - defaultSequenceOption, generateAcrnSheet, generateFrequencies, shuffledFrequencies, @@ -47,9 +47,12 @@ describe("generateFrequencies", () => { describe("shuffledFrequencies", () => { test("returns shuffled array", () => { const input = [1, 2, 3, 4, 5, 6]; - const result = shuffledFrequencies(input); - while (result === input) { - shuffledFrequencies(input); + let result; + for (let i = 0; i < 100; i++) { + result = shuffledFrequencies(input); + if (result === input) { + break; + } } expect(result).not.toEqual(input); }, 50); @@ -190,7 +193,7 @@ describe("createSequence", () => { test("default option", async () => { const frequencies = generateFrequencies(8000); - const { loopRepeat, restLength } = defaultSequenceOption; + const { loopRepeat, restLength } = DEFAULT_SEQUENCE_OPTION; const seq = createSequence(synthMock, frequencies); expect(seq.events).toEqual(generateAcrnSheet(loopRepeat, restLength)); expect(seq.loop).toBe(true);