Skip to content

Commit e803cf1

Browse files
committed
Note off times
1 parent d288330 commit e803cf1

File tree

1 file changed

+23
-16
lines changed

1 file changed

+23
-16
lines changed

src/components/PlayMIDI.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ import useStore from '../store';
99
export const BASE_PITCH = 21;
1010
const NOTE_ON = 0x90;
1111
const NOTE_OFF = 0x80;
12+
const LOOK_AHEAD_MS = 25; // How often to check (ms)
13+
const SCHEDULE_AHEAD_SECONDS = 0.1; // Schedule notes ahead by 100ms
1214

1315
const PlayPauseButton: React.FC = () => {
1416
const { bpm, outputChannel, grids, selectedOutput } = useStore();
1517
const [isPlaying, setIsPlaying] = useState(false);
1618
const currentBeat = useRef<number>(0);
1719
const audioContextRef = useRef<AudioContext | null>(null);
18-
const nextNoteTime = useRef<number>(0); // Time for next note in seconds
19-
const lookahead = 25; // How often to check (ms)
20-
const scheduleAheadTime = 0.1; // Schedule notes ahead by 100ms
21-
const intervalDuration = (60 / bpm); // Time per beat (in seconds)
20+
const nextNoteTimeInSeconds = useRef<number>(0); // Time for next note in seconds
2221
const isScheduling = useRef<boolean>(false); // Flag to stop scheduling when playback is paused
22+
const intervalDurationSeconds = (60 / bpm); // Time per beat in seconds
2323

2424
useKeydown('Space', () => {
2525
setIsPlaying(prev => !prev);
@@ -28,28 +28,35 @@ const PlayPauseButton: React.FC = () => {
2828
}
2929
});
3030

31-
const playNoteNow = useCallback((pitch: number, velocity = 75, time: number) => {
32-
if (!selectedOutput) return;
33-
const noteOnTime = time * 1000; // Convert seconds to ms
34-
const noteOffTime = noteOnTime + (intervalDuration * 1000); // Note length is the interval
31+
const playNoteNow = useCallback((pitch: number, velocity = 75) => {
32+
if (!selectedOutput || !audioContextRef.current) return;
33+
34+
const currentTime = audioContextRef.current.currentTime; // Current time in seconds
35+
const noteOnTime = currentTime; // Now
36+
const noteOffTime = currentTime + intervalDurationSeconds; // After the duration
3537

38+
console.log(`Playing note: ${pitch}, velocity: ${velocity}, time: ${noteOnTime}`);
3639
selectedOutput.send([NOTE_ON + outputChannel, BASE_PITCH + pitch, velocity], noteOnTime);
40+
41+
console.log(`Stopping note: ${pitch}, time: ${noteOffTime}`);
3742
selectedOutput.send([NOTE_OFF + outputChannel, BASE_PITCH + pitch, 0], noteOffTime);
38-
}, [intervalDuration, outputChannel, selectedOutput]);
43+
}, [intervalDurationSeconds, outputChannel, selectedOutput]);
3944

4045
const scheduleNotes = useCallback(() => {
4146
if (!selectedOutput || !audioContextRef.current) return;
4247

48+
const currentTime = audioContextRef.current.currentTime; // Get current audio context time
49+
4350
// Schedule notes ahead of time for all grids
44-
while (nextNoteTime.current < audioContextRef.current.currentTime + scheduleAheadTime) {
51+
while (nextNoteTimeInSeconds.current < currentTime + SCHEDULE_AHEAD_SECONDS) {
4552
grids.forEach((grid) => {
4653
const currentBeatIndex = currentBeat.current % (grid.numColumns || 1);
4754
const beat = grid.beats[currentBeatIndex];
4855

4956
if (beat) {
5057
// Schedule notes for the current beat
5158
Object.values(beat.notes).forEach(note => {
52-
playNoteNow(note.pitch, note.velocity, nextNoteTime.current);
59+
playNoteNow(note.pitch, note.velocity);
5360
});
5461
}
5562
});
@@ -58,23 +65,23 @@ const PlayPauseButton: React.FC = () => {
5865

5966
// Move to the next beat
6067
currentBeat.current += 1;
61-
nextNoteTime.current += intervalDuration; // Schedule next beat
68+
nextNoteTimeInSeconds.current += intervalDurationSeconds; // Schedule next beat
6269
}
63-
}, [selectedOutput, grids, intervalDuration, playNoteNow]);
70+
}, [selectedOutput, grids, intervalDurationSeconds, playNoteNow]);
6471

6572
const scheduler = useCallback(() => {
6673
if (!isPlaying || !audioContextRef.current || !isScheduling.current) return;
6774

6875
scheduleNotes();
69-
setTimeout(scheduler, lookahead);
76+
setTimeout(scheduler, LOOK_AHEAD_MS);
7077
}, [isPlaying, scheduleNotes]);
7178

7279
const startPlayback = useCallback(() => {
7380
if (!audioContextRef.current) {
7481
audioContextRef.current = new window.AudioContext();
7582
}
7683

77-
nextNoteTime.current = audioContextRef.current.currentTime; // Initialize next note time
84+
nextNoteTimeInSeconds.current = audioContextRef.current.currentTime; // Initialize next note time
7885
// Start scheduling
7986
isScheduling.current = true;
8087
scheduler();
@@ -105,7 +112,7 @@ const PlayPauseButton: React.FC = () => {
105112
useEffect(() => {
106113
const handlePlayNote = (event: CustomEvent<PlayNoteNowDetail>) => {
107114
const { pitch, velocity } = event.detail;
108-
playNoteNow(pitch, velocity, audioContextRef.current?.currentTime || 0);
115+
playNoteNow(pitch, velocity);
109116
};
110117

111118
window.addEventListener(PLAY_NOTE_NOW_EVENT_NAME, handlePlayNote as EventListener);

0 commit comments

Comments
 (0)