Skip to content

Commit

Permalink
feat: testing practice score scheduling feature
Browse files Browse the repository at this point in the history
  • Loading branch information
threedalpeng committed Feb 18, 2024
1 parent f97c4e8 commit 579d82c
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 0 deletions.
23 changes: 23 additions & 0 deletions src/routes/tools/render-sample/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts" context="module">
export interface Board {
title: string;
fingers: FingerInfo[];
}
</script>

<script lang="ts">
import MetronomeProvider from '$/lib/device/metronome/MetronomeProvider.svelte';
import type { FingerInfo } from '$/lib/guitar/finger-board/FingerBoard.svelte';
import RandomBoxProvider from '$/lib/practice/RandomBox/RandomBoxProvider.svelte';
import { practice } from './data';
</script>

<MetronomeProvider
bpm={practice.tempo.bpm}
beatPerBar={practice.tempo.beatPerBar}
signatureUnit={practice.tempo.signatureUnit}
>
<RandomBoxProvider items={practice.scores}>
<slot />
</RandomBoxProvider>
</MetronomeProvider>
173 changes: 173 additions & 0 deletions src/routes/tools/render-sample/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<script lang="ts">
import MetronomeBeats from '$/lib/device/metronome/MetronomeBeats.svelte';
import MetronomeOptions from '$/lib/device/metronome/MetronomeOptions.svelte';
import { getMetronomeContext } from '$/lib/device/metronome/context';
import type { OnBarCallback, OnOptionChangeCallback } from '$/lib/device/metronome/metronome';
import FingerBoard, {
type FingerInfo,
type FingerPosition
} from '$/lib/guitar/finger-board/FingerBoard.svelte';
import RandomBoxOptions from '$/lib/practice/RandomBox/RandomBoxOptions.svelte';
import { getRandomBoxContext } from '$/lib/practice/RandomBox/context';
import { type AudioTickCallback, type TickCallback } from '$/lib/timer/tick';
import { getPitchFromFingerPosition } from '$/utils/music/pitch';
import { Play, Stop } from '@steeze-ui/heroicons';
import { Icon } from '@steeze-ui/svelte-icon';
import { onDestroy, onMount } from 'svelte';
import { practice } from './data';
const metronome = getMetronomeContext();
const randomBox = getRandomBoxContext<(typeof practice.scores)[number]>();
const timer = metronome.timer;
let score = replaceScore();
let isRunning: boolean = metronome.isRunning;
let testNum = 0;
function replaceScore() {
let score = randomBox.open();
const schedule = () => {
scheduleScore(score);
timer.removeTick(schedule);
};
timer.onTick(schedule);
return score;
}
let currentBoard: (typeof score.boards)[number];
let currentActiveFingers = new Set<number>();
$: fingers = (currentBoard?.fingers ?? []).map((finger) => {
return {
position: score.notes[finger].position,
style: { color: currentActiveFingers.has(finger) ? 'red' : undefined }
};
}) as FingerInfo[];
function scheduleScore(score: (typeof practice.scores)[number]) {
/** Now scheduleing */
// 1. board replacement
score.boards.map((board) => {
const notes = board.fingers.map((finger) => {
const note = score.notes[finger];
const pitch = getPitchFromFingerPosition(
note.position as FingerPosition,
practice.guitar.tuning
);
return { ...note, id: finger, pitch };
});
onTimeAfter(
board.time,
() => {
currentBoard = board;
// 2. active note(finger)
notes.forEach((note) => {
onTimeAfter(
note.time,
() => {
currentActiveFingers.add(note.id);
currentActiveFingers = currentActiveFingers;
return () => {
currentActiveFingers.delete(note.id);
currentActiveFingers = currentActiveFingers;
};
},
({ audioCtx }) => {}
);
});
},
({ audioCtx }) => {}
);
});
}
function onTimeAfter(
time: { start: number; duration?: number },
cb: TickCallback,
audioCb: AudioTickCallback
) {
const start = timer.convert(time.start, 'note', 'second');
const duration = time.duration ? timer.convert(time.duration, 'note', 'second') : -1;
let currentTime = metronome.timer.currentTime;
let cleanup: TickCallback | null = null;
const onStart: TickCallback = (state) => {
if (currentTime + start <= state.time) {
cleanup = cb(state) as TickCallback;
timer.removeTick(onStart);
}
};
const onEnd: TickCallback = (state) => {
if (currentTime + start + duration <= state.time) {
if (cleanup) {
cleanup(state);
}
timer.removeTick(onEnd);
}
};
const onAudioTick: AudioTickCallback = (state) => {
if (currentTime + start >= state.time) {
audioCb(state);
timer.removeAudioTick(onAudioTick);
}
};
metronome.timer.onTick(onStart);
if (duration > 0) {
metronome.timer.onTick(onEnd);
}
metronome.timer.onAudioTick(onAudioTick);
}
onMount(() => {
metronome.onBar(onMetronomeBar);
metronome.onOptionChange(onMetronomeOptionChange);
timer.onTick(onTimerTick);
});
onDestroy(() => {
metronome.removeBar(onMetronomeBar);
metronome.removeOptionChange(onMetronomeOptionChange);
timer.removeTick(onTimerTick);
});
const onMetronomeBar: OnBarCallback = () => {
score = replaceScore();
};
const onMetronomeOptionChange: OnOptionChangeCallback = ({ bpm }) => {
// timer.tickIntervalMs = calcTickIntervalMs(bpm);
};
const onTimerTick: TickCallback = ({ tickPassed }) => {
testNum = Math.floor(timer.convert(tickPassed, 'tick', 'beat'));
};
</script>

<div class="h-full w-screen">
<div class="relative h-full">
<div class="absolute right-0 z-10 flex h-[60px] w-screen flex-row items-center justify-between">
<div class="ml-4 flex h-full flex-row items-start justify-between gap-4">
<MetronomeOptions />
<RandomBoxOptions />
</div>
<button
class="mr-8 flex aspect-square h-3/4 items-center justify-center rounded-full bg-indigo-900 p-0 focus:outline-none"
on:click={() => {
metronome.toggle();
isRunning = metronome.isRunning;
}}
>
{#if isRunning}
<Icon class="m-0 h-[20px] w-[20px] p-0 text-indigo-100" src={Stop} theme="solid" />
{:else}
<Icon class="m-0 h-[20px] w-[20px] p-0 text-indigo-100" src={Play} theme="solid" />
{/if}
</button>
</div>
<div class="relative flex h-full flex-col items-center justify-center">
<div class="relative" style="left: {0}em;">{testNum}</div>
<FingerBoard readonly {fingers}></FingerBoard>
<MetronomeBeats />
</div>
</div>
</div>
128 changes: 128 additions & 0 deletions src/routes/tools/render-sample/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { TUNE } from '$/utils/music/pitch';

export const practice = {
tempo: {
bpm: 120,
beatPerBar: 6,
signatureUnit: 8
},
guitar: {
tuning: TUNE.standard
},
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 }
}
]
}
]
};

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 } }
]
}
];

0 comments on commit 579d82c

Please sign in to comment.