Skip to content

Commit

Permalink
feat: improved note play
Browse files Browse the repository at this point in the history
  • Loading branch information
threedalpeng committed Feb 18, 2024
1 parent a550f9e commit f263008
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 134 deletions.
42 changes: 25 additions & 17 deletions src/lib/guitar/finger-board/FingerBoard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
style?: Partial<FingerStyle>;
text?: string;
}
export interface FretRangeOption {
start: number;
end: number;
visibility: 'none' | 'all' | 'start' | 'end';
}
</script>

<script lang="ts">
Expand All @@ -50,18 +55,21 @@
setFingerBoardContext();
export let readonly: boolean = false;
export let fretRange: {
start: number;
end: number;
visibility: 'none' | 'all' | 'start' | 'end';
} = {
export let fretRange: Partial<FretRangeOption> = {
start: 0,
end: 12,
visibility: 'start'
};
let range = Object.assign(
{
start: 0,
end: 12,
visibility: 'end'
},
fretRange
);
const fretNumberPadding = 0.3;
$: fretRangeGap = fretRange.end - fretRange.start;
$: fretRangeGap = range.end - range.start;
$: fretRangeWidth = FRET_GAP * (fretRangeGap + fretNumberPadding * 2);
export let fingers: FingerInfo[] = [];
Expand All @@ -80,7 +88,7 @@
width={FRET_START * 2 + FRET_GAP * FRET_MAX}
height={STRING_START * 2 + STRING_GAP * 5}
sourceArea={{
x: getXFromFretNumber(fretRange.start) - FRET_WIDTH - 4,
x: getXFromFretNumber(range.start) - FRET_WIDTH - 4,
width: fretRangeWidth
}}
destArea={{ x: FRET_START }}
Expand Down Expand Up @@ -119,8 +127,8 @@
{/if}
{/each}
{#if !readonly}
{#each Array(fretRange.end - fretRange.start) as _, i}
{@const fretNum = i + 1 + fretRange.start}
{#each Array(range.end - range.start) as _, i}
{@const fretNum = i + 1 + range.start}
{@const leftX = getXFromFretNumber(fretNum - 1)}
{#each Array(6) as _, j}
{@const lineNum = j + 1}
Expand Down Expand Up @@ -171,25 +179,25 @@
y={getYFromStringNumber(nonFinger.position.line)}
/>
{/each}
{#if fretRange.visibility === 'all' || fretRange.visibility === 'start'}
{#if range.visibility === 'all' || range.visibility === 'start'}
<Text
fontSize="20px"
fontFamily="FinaleJazz"
textAlign="center"
textBaseline="bottom"
text={`${fretRange.start}`}
x={FRET_START + fretNumberPadding * FRET_GAP}
y={STRING_START - (fretRange.start === 0 ? 3 : 0)}
text={`${range.start}`}
x={FRET_START + fretNumberPadding * FRET_GAP - 4}
y={STRING_START - (range.start === 0 ? 3 : 0)}
/>
{/if}
{#if fretRange.visibility === 'all' || fretRange.visibility === 'end'}
{#if range.visibility === 'all' || range.visibility === 'end'}
<Text
fontSize="20px"
fontFamily="FinaleJazz"
textAlign="center"
textBaseline="bottom"
text={`${fretRange.end}`}
x={FRET_START + fretRangeWidth - fretNumberPadding * FRET_GAP}
text={`${range.end}`}
x={FRET_START + fretRangeWidth - fretNumberPadding * FRET_GAP - 4}
y={STRING_START}
/>
{/if}
Expand Down
39 changes: 39 additions & 0 deletions src/lib/timer/tick.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { WithCleanup } from '$/utils/types';
import TimerWorker from './timer-worker?worker';

export interface TickState {
Expand Down Expand Up @@ -246,4 +247,42 @@ export class TempoTimer extends AudioClockTimer {
return ticks * this.tickIntervalMs;
}
}

onTimeAfter(
time: { start: number; duration?: number },
cb: WithCleanup<TickCallback>,
audioCb: AudioTickCallback
) {
const start = this.convert(time.start, 'note', 'second');
const duration = time.duration ? this.convert(time.duration, 'note', 'second') : -1;

let currentTime = this.currentTime;
let cleanup: TickCallback | null = null;
const onStart: TickCallback = (state) => {
if (currentTime + start <= state.time) {
cleanup = cb(state) ?? null;
this.removeTick(onStart);
}
};
const onEnd: TickCallback = (state) => {
if (currentTime + start + duration <= state.time) {
if (cleanup) {
cleanup(state);
}
this.removeTick(onEnd);
}
};
const onAudioTick: AudioTickCallback = (state) => {
if (currentTime + start >= state.time) {
audioCb(state);
this.removeAudioTick(onAudioTick);
}
};

this.onTick(onStart);
if (duration > 0) {
this.onTick(onEnd);
}
this.onAudioTick(onAudioTick);
}
}
113 changes: 39 additions & 74 deletions src/routes/tools/render-sample/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@
} 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 type { PracticeBoard, PracticeScore } from '$/lib/practice/types';
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';
Expand All @@ -22,7 +20,6 @@
const timer = metronome.timer;
let score = replaceScore();
let isRunning: boolean = metronome.isRunning;
let testNum = 0;
function replaceScore() {
let score = randomBox.open();
Expand All @@ -34,113 +31,81 @@
return score;
}
let currentBoard: (typeof score.boards)[number];
let currentBoard: PracticeBoard = score.boards[0];
let currentActiveFingers = new Set<number>();
let nextNotes: number[] = [];
$: fingers = (currentBoard?.fingers ?? []).map((finger) => {
const order = nextNotes.findIndex((f) => f === finger);
return {
position: score.notes[finger].position,
style: { color: currentActiveFingers.has(finger) ? 'red' : undefined }
position: score.positions[finger],
style: {
color: currentActiveFingers.has(finger) ? 'red' : undefined
// scale: currentActiveFingers.has(finger) ? 1 : order >= 0 ? (4 - order) / 4 : 0.5
}
};
}) as FingerInfo[];
// $: console.log(nextNotes);
function scheduleScore(score: (typeof practice.scores)[number]) {
function scheduleScore(score: PracticeScore) {
/** 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(
timer.onTimeAfter(
board.time,
() => {
currentActiveFingers.clear();
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);
// 2. notes
const notes = score.notes.map((note) => {
const pitch = getPitchFromFingerPosition(
score.positions[note.position] as FingerPosition,
practice.guitar.tuning
);
return { ...note, pitch };
});
for (let i = 0; i < notes.length; i++) {
const note = notes[i];
const nextThreeFingers = notes.slice(i + 1, i + 4).map((n) => n.position);
timer.onTimeAfter(
note.time,
() => {
currentActiveFingers.add(note.position);
currentActiveFingers = currentActiveFingers;
nextNotes = nextThreeFingers;
return () => {
currentActiveFingers.delete(note.position);
currentActiveFingers = currentActiveFingers;
};
},
({ audioCtx }) => {
// play audio with pitch
}
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();
// 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">
Expand Down
Loading

0 comments on commit f263008

Please sign in to comment.