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);