diff --git a/.gitignore b/.gitignore
index 9b964d3..a7944aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,7 +20,6 @@ pnpm-debug.log*
lerna-debug.log*
# Editor directories and files
-.vscode/*
.idea
*.suo
*.ntvs*
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..78be5f0
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,6 @@
+{
+ "recommendations": [
+ "svelte.svelte-vscode",
+ "bradlc.vscode-tailwindcss"
+ ]
+}
\ No newline at end of file
diff --git a/src/lib/device/metronome/MetronomeBeats.svelte b/src/lib/device/metronome/MetronomeBeats.svelte
index 97b3a21..442c27e 100644
--- a/src/lib/device/metronome/MetronomeBeats.svelte
+++ b/src/lib/device/metronome/MetronomeBeats.svelte
@@ -1,26 +1,21 @@
{#each new Array(beatPerBar) as _, i}
{#if i === 0}
diff --git a/src/lib/device/metronome/MetronomeOptions.svelte b/src/lib/device/metronome/MetronomeOptions.svelte
index 6130b77..04ffb53 100644
--- a/src/lib/device/metronome/MetronomeOptions.svelte
+++ b/src/lib/device/metronome/MetronomeOptions.svelte
@@ -3,14 +3,19 @@
import { getMetronomeContext } from './context';
const metronome = getMetronomeContext();
- export let bpm = metronome.bpm;
- export let beatPerBar = metronome.beatPerBar;
+ export let bpm = metronome.timer.bpm;
+ export let beatPerBar = metronome.timer.beatPerBar;
+
+ metronome.timer.onTempoChanged((state) => {
+ bpm = state.bpm;
+ beatPerBar = state.beatPerBar;
+ });
let lastTapTimestamp = -1;
let tapIntervalStore: number[] = [];
- $: metronome.bpm = bpm;
- $: metronome.beatPerBar = beatPerBar;
+ $: metronome.timer.bpm = bpm;
+ $: metronome.timer.beatPerBar = beatPerBar;
diff --git a/src/lib/device/metronome/MetronomePlayButton.svelte b/src/lib/device/metronome/MetronomePlayButton.svelte
index dc7af10..6003405 100644
--- a/src/lib/device/metronome/MetronomePlayButton.svelte
+++ b/src/lib/device/metronome/MetronomePlayButton.svelte
@@ -1,17 +1,30 @@
diff --git a/src/routes/tools/render-sample/+layout.svelte b/src/routes/tools/render-sample/+layout.svelte
deleted file mode 100644
index f84bd4f..0000000
--- a/src/routes/tools/render-sample/+layout.svelte
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/routes/tools/render-sample/+page.svelte b/src/routes/tools/render-sample/+page.svelte
deleted file mode 100644
index 7f2fbe0..0000000
--- a/src/routes/tools/render-sample/+page.svelte
+++ /dev/null
@@ -1,152 +0,0 @@
-
-
-
diff --git a/src/routes/tools/render-sample/data.ts b/src/routes/tools/render-sample/data.ts
deleted file mode 100644
index 002816d..0000000
--- a/src/routes/tools/render-sample/data.ts
+++ /dev/null
@@ -1,325 +0,0 @@
-import type { Practice } from '$/lib/practice/types';
-import { TUNE } from '$/utils/music/pitch';
-
-const practices: Record = {
- // root: {
- // tempo: {
- // bpm: 120,
- // beatPerBar: 6,
- // signatureUnit: 8
- // },
- // guitar: {
- // tuning: TUNE.standard,
- // fretRange: {
- // start: 0,
- // end: 12,
- // visibility: 'end'
- // }
- // },
- // scores: [
- // {
- // notes: [
- // { position: { fret: 'open', line: 6 }, time: { start: 0, duration: 1 / 8 } },
- // { position: { fret: 7, line: 5 }, time: { start: 1 / 8, duration: 1 / 8 } },
- // { position: { fret: 2, line: 4 }, time: { start: 2 / 8, duration: 1 / 8 } },
- // { position: { fret: 9, line: 3 }, time: { start: 3 / 8, duration: 1 / 8 } },
- // { position: { fret: 5, line: 2 }, time: { start: 4 / 8, duration: 1 / 8 } },
- // { position: { fret: 'open', line: 1 }, time: { start: 5 / 8, duration: 1 / 8 } }
- // ],
- // boards: [
- // {
- // title: 'E',
- // fingers: [0, 1, 2, 3, 4, 5],
- // time: { start: 0 }
- // }
- // ]
- // },
- // {
- // notes: [
- // { position: { fret: 1, line: 6 }, time: { start: 0, duration: 1 / 8 } },
- // { position: { fret: 8, line: 5 }, time: { start: 1 / 8, duration: 1 / 8 } },
- // { position: { fret: 3, line: 4 }, time: { start: 2 / 8, duration: 1 / 8 } },
- // { position: { fret: 10, line: 3 }, time: { start: 3 / 8, duration: 1 / 8 } },
- // { position: { fret: 6, line: 2 }, time: { start: 4 / 8, duration: 1 / 8 } },
- // { position: { fret: 1, line: 1 }, time: { start: 5 / 8, duration: 1 / 8 } }
- // ],
- // boards: [
- // {
- // title: 'E',
- // fingers: [0, 1, 2, 3, 4, 5],
- // time: { start: 0, duration: 6 / 8 }
- // }
- // ]
- // }
- // ]
- // },
- 'major-scale': {
- tempo: {
- bpm: 120,
- beatPerBar: 4,
- signatureUnit: 4
- },
- guitar: {
- tuning: TUNE.standard
- },
- scores: [
- {
- positions: [
- { line: 6, fret: 8 },
- { line: 6, fret: 10 },
- { line: 5, fret: 7 },
- { line: 5, fret: 8 },
- { line: 5, fret: 10 },
- { line: 4, fret: 7 },
- { line: 4, fret: 9 },
- { line: 4, fret: 10 },
- { line: 3, fret: 7 },
- { line: 3, fret: 9 },
- { line: 3, fret: 10 },
- { line: 2, fret: 8 },
- { line: 2, fret: 10 },
- { line: 1, fret: 7 },
- { line: 1, fret: 8 },
- { line: 1, fret: 10 },
- { line: 6, fret: 7 }
- ],
- notes: [
- { position: 0, time: { start: 0, duration: 0.0625 } },
- { position: 1, time: { start: 0.0625, duration: 0.0625 } },
- { position: 2, time: { start: 0.125, duration: 0.0625 } },
- { position: 3, time: { start: 0.1875, duration: 0.0625 } },
- { position: 4, time: { start: 0.25, duration: 0.0625 } },
- { position: 5, time: { start: 0.3125, duration: 0.0625 } },
- { position: 6, time: { start: 0.375, duration: 0.0625 } },
- { position: 7, time: { start: 0.4375, duration: 0.0625 } },
- { position: 8, time: { start: 0.5, duration: 0.0625 } },
- { position: 9, time: { start: 0.5625, duration: 0.0625 } },
- { position: 10, time: { start: 0.625, duration: 0.0625 } },
- { position: 11, time: { start: 0.6875, duration: 0.0625 } },
- { position: 12, time: { start: 0.75, duration: 0.0625 } },
- { position: 13, time: { start: 0.8125, duration: 0.0625 } },
- { position: 14, time: { start: 0.875, duration: 0.0625 } },
- { position: 15, time: { start: 0.9375, duration: 0.0625 } },
- { position: 14, time: { start: 1, duration: 0.0625 } },
- { position: 13, time: { start: 1.0625, duration: 0.0625 } },
- { position: 12, time: { start: 1.125, duration: 0.0625 } },
- { position: 11, time: { start: 1.1875, duration: 0.0625 } },
- { position: 10, time: { start: 1.25, duration: 0.0625 } },
- { position: 9, time: { start: 1.3125, duration: 0.0625 } },
- { position: 8, time: { start: 1.375, duration: 0.0625 } },
- { position: 7, time: { start: 1.4375, duration: 0.0625 } },
- { position: 6, time: { start: 1.5, duration: 0.0625 } },
- { position: 5, time: { start: 1.5625, duration: 0.0625 } },
- { position: 4, time: { start: 1.625, duration: 0.0625 } },
- { position: 3, time: { start: 1.6875, duration: 0.0625 } },
- { position: 2, time: { start: 1.75, duration: 0.0625 } },
- { position: 1, time: { start: 1.8125, duration: 0.0625 } },
- { position: 0, time: { start: 1.875, duration: 0.0625 } },
- { position: 16, time: { start: 1.9375, duration: 0.0625 } },
- { position: 0, time: { start: 2, duration: 0.0625 } }
- ],
- boards: [
- {
- title: 'C line 1',
- fingers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
- time: { start: 0 }
- },
- {
- title: 'C line 2',
- fingers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
- time: { start: 15 / 16 }
- }
- ],
- fretRange: {
- start: 5,
- end: 11,
- visibility: 'all'
- }
- }
- ]
- },
- 'rhythm-test': {
- tempo: {
- bpm: 120,
- beatPerBar: 4,
- signatureUnit: 4
- },
- guitar: {
- tuning: TUNE.standard
- },
- scores: [
- {
- positions: [
- { line: 5, fret: 3 },
- { line: 6, fret: 3 },
- { line: 1, fret: 'open' },
- { line: 2, fret: 1 },
- { line: 3, fret: 'open' }
- ],
- notes: [
- { position: 0, time: { start: 0, duration: 1 / 4 } },
- { position: 2, time: { start: 1 / 4, duration: 1 / 4 } },
- { position: 3, time: { start: 1 / 4, duration: 1 / 4 } },
- { position: 4, time: { start: 1 / 4, duration: 1 / 4 } },
- { position: 1, time: { start: 2 / 4, duration: 1 / 4 } },
- { position: 2, time: { start: 3 / 4, duration: 1 / 4 } },
- { position: 3, time: { start: 3 / 4, duration: 1 / 4 } },
- { position: 4, time: { start: 3 / 4, duration: 1 / 4 } },
- { position: 0, time: { start: 4 / 4, duration: 1 / 4 } },
- { position: 2, time: { start: 5 / 4, duration: 1 / 4 } },
- { position: 3, time: { start: 5 / 4, duration: 1 / 4 } },
- { position: 4, time: { start: 5 / 4, duration: 1 / 4 } },
- { position: 1, time: { start: 6 / 4, duration: 1 / 4 } },
- { position: 2, time: { start: 7 / 4, duration: 1 / 4 } },
- { position: 3, time: { start: 7 / 4, duration: 1 / 4 } },
- { position: 4, time: { start: 7 / 4, duration: 1 / 4 } },
- { position: 0, time: { start: 8 / 4, duration: 1 / 4 } },
- { position: 2, time: { start: 9 / 4, duration: 1 / 4 } },
- { position: 3, time: { start: 9 / 4, duration: 1 / 4 } },
- { position: 4, time: { start: 9 / 4, duration: 1 / 4 } },
- { position: 1, time: { start: 10 / 4, duration: 1 / 4 } },
- { position: 2, time: { start: 11 / 4, duration: 1 / 4 } },
- { position: 3, time: { start: 11 / 4, duration: 1 / 4 } },
- { position: 4, time: { start: 11 / 4, duration: 1 / 4 } },
- { position: 0, time: { start: 12 / 4, duration: 1 / 4 } },
- { position: 2, time: { start: 13 / 4, duration: 1 / 4 } },
- { position: 3, time: { start: 13 / 4, duration: 1 / 4 } },
- { position: 4, time: { start: 13 / 4, duration: 1 / 4 } },
- { position: 1, time: { start: 14 / 4, duration: 1 / 4 } },
- { position: 2, time: { start: 15 / 4, duration: 1 / 4 } },
- { position: 3, time: { start: 15 / 4, duration: 1 / 4 } },
- { position: 4, time: { start: 15 / 4, duration: 1 / 4 } }
- ],
- boards: [
- {
- title: 'C line 1',
- fingers: [0, 2, 3, 4],
- time: { start: 0 }
- },
- {
- title: 'C line 1',
- fingers: [1, 2, 3, 4],
- time: { start: 1 / 2 }
- },
- {
- title: 'C line 1',
- fingers: [0, 2, 3, 4],
- time: { start: 2 / 2 }
- },
- {
- title: 'C line 1',
- fingers: [1, 2, 3, 4],
- time: { start: 3 / 2 }
- },
- {
- title: 'C line 1',
- fingers: [0, 2, 3, 4],
- time: { start: 4 / 2 }
- },
- {
- title: 'C line 1',
- fingers: [1, 2, 3, 4],
- time: { start: 5 / 2 }
- },
- {
- title: 'C line 1',
- fingers: [0, 2, 3, 4],
- time: { start: 6 / 2 }
- },
- {
- title: 'C line 1',
- fingers: [1, 2, 3, 4],
- time: { start: 7 / 2 }
- }
- ],
- fretRange: {
- start: 0,
- end: 12,
- visibility: 'all'
- }
- }
- ]
- }
-};
-
-export const practice: Practice = practices['rhythm-test'];
-
-export const items = [
- {
- title: 'E',
- fingers: [
- { position: { fret: 'open', line: 6 }, text: 'E' },
- { position: { fret: 7, line: 5 }, text: 'E' },
- { position: { fret: 2, line: 4 }, text: 'E' },
- { position: { fret: 9, line: 3 }, text: 'E' },
- { position: { fret: 5, line: 2 }, text: 'E' },
- { position: { fret: 'open', line: 1 }, text: 'E' }
- ]
- },
- {
- title: 'F',
- fingers: [
- { position: { fret: 1, line: 6 } },
- { position: { fret: 1, line: 1 } },
- { position: { fret: 3, line: 4 } },
- { position: { fret: 6, line: 2 } },
- { position: { fret: 8, line: 5 } },
- { position: { fret: 10, line: 3 } }
- ]
- },
- {
- title: 'G',
- fingers: [
- { position: { fret: 3, line: 6 } },
- { position: { fret: 5, line: 4 } },
- { position: { fret: 8, line: 2 } },
- { position: { fret: 10, line: 5 } },
- { position: { fret: 'open', line: 3 } },
- { position: { fret: 3, line: 1 } }
- ]
- },
- {
- title: 'A',
- fingers: [
- { position: { fret: 5, line: 6 } },
- { position: { fret: 7, line: 4 } },
- { position: { fret: 10, line: 2 } },
- { position: { fret: 'open', line: 5 } },
- { position: { fret: 2, line: 3 } },
- { position: { fret: 5, line: 1 } }
- ]
- },
- {
- title: 'B',
- fingers: [
- { position: { fret: 2, line: 5 } },
- { position: { fret: 4, line: 3 } },
- { position: { fret: 7, line: 1 } },
- { position: { fret: 7, line: 6 } },
- { position: { fret: 9, line: 4 } },
- { position: { fret: 'open', line: 2 } }
- ]
- },
- {
- title: 'C',
- fingers: [
- { position: { fret: 3, line: 5 } },
- { position: { fret: 5, line: 3 } },
- { position: { fret: 8, line: 1 } },
- { position: { fret: 8, line: 6 } },
- { position: { fret: 10, line: 4 } },
- { position: { fret: 1, line: 2 } }
- ]
- },
- {
- title: 'D',
- fingers: [
- { position: { fret: 'open', line: 4 } },
- { position: { fret: 2, line: 2 } },
- { position: { fret: 5, line: 5 } },
- { position: { fret: 7, line: 3 } },
- { position: { fret: 10, line: 1 } },
- { position: { fret: 10, line: 6 } }
- ]
- }
-];
diff --git a/src/utils/multimap.ts b/src/utils/multimap.ts
new file mode 100644
index 0000000..e50d4bb
--- /dev/null
+++ b/src/utils/multimap.ts
@@ -0,0 +1,60 @@
+export class MultiMap extends Map {
+ clear(): void {
+ super.clear();
+ }
+ delete(key: K): boolean {
+ return super.delete(key);
+ }
+ getAll(key: K): V[] | undefined {
+ return super.get(key);
+ }
+ getFirst(key: K): V | undefined {
+ const values = super.get(key);
+ return values ? values[0] : undefined;
+ }
+ has(key: K): boolean {
+ return super.has(key);
+ }
+ set(key: K, value: V): this {
+ let values = super.get(key);
+ if (values === undefined) {
+ values = [];
+ super.set(key, values);
+ }
+ values.push(value);
+ return this;
+ }
+ *[Symbol.iterator](): IterableIterator<[K, V]> {
+ for (const [k, values] of super[Symbol.iterator]()) {
+ for (const v of values) {
+ yield [k, v];
+ }
+ }
+ }
+ *keys(): IterableIterator {
+ for (const k of super.keys()) {
+ yield k;
+ }
+ }
+ *values(): IterableIterator {
+ for (const values of super.values()) {
+ for (const v of values) {
+ yield v;
+ }
+ }
+ }
+ *entries(): IterableIterator<[K, V[]]> {
+ for (const [k, values] of super[Symbol.iterator]()) {
+ for (const v of values) {
+ yield [k, v];
+ }
+ }
+ }
+ forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void {
+ super.forEach((k, values, map) => {
+ values.forEach((v: V) => {
+ callbackfn(v, k, map);
+ });
+ }, thisArg);
+ }
+}