@@ -9,17 +9,17 @@ import useStore from '../store';
9
9
export const BASE_PITCH = 21 ;
10
10
const NOTE_ON = 0x90 ;
11
11
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
12
14
13
15
const PlayPauseButton : React . FC = ( ) => {
14
16
const { bpm, outputChannel, grids, selectedOutput } = useStore ( ) ;
15
17
const [ isPlaying , setIsPlaying ] = useState ( false ) ;
16
18
const currentBeat = useRef < number > ( 0 ) ;
17
19
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
22
21
const isScheduling = useRef < boolean > ( false ) ; // Flag to stop scheduling when playback is paused
22
+ const intervalDurationSeconds = ( 60 / bpm ) ; // Time per beat in seconds
23
23
24
24
useKeydown ( 'Space' , ( ) => {
25
25
setIsPlaying ( prev => ! prev ) ;
@@ -28,28 +28,35 @@ const PlayPauseButton: React.FC = () => {
28
28
}
29
29
} ) ;
30
30
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
35
37
38
+ console . log ( `Playing note: ${ pitch } , velocity: ${ velocity } , time: ${ noteOnTime } ` ) ;
36
39
selectedOutput . send ( [ NOTE_ON + outputChannel , BASE_PITCH + pitch , velocity ] , noteOnTime ) ;
40
+
41
+ console . log ( `Stopping note: ${ pitch } , time: ${ noteOffTime } ` ) ;
37
42
selectedOutput . send ( [ NOTE_OFF + outputChannel , BASE_PITCH + pitch , 0 ] , noteOffTime ) ;
38
- } , [ intervalDuration , outputChannel , selectedOutput ] ) ;
43
+ } , [ intervalDurationSeconds , outputChannel , selectedOutput ] ) ;
39
44
40
45
const scheduleNotes = useCallback ( ( ) => {
41
46
if ( ! selectedOutput || ! audioContextRef . current ) return ;
42
47
48
+ const currentTime = audioContextRef . current . currentTime ; // Get current audio context time
49
+
43
50
// Schedule notes ahead of time for all grids
44
- while ( nextNoteTime . current < audioContextRef . current . currentTime + scheduleAheadTime ) {
51
+ while ( nextNoteTimeInSeconds . current < currentTime + SCHEDULE_AHEAD_SECONDS ) {
45
52
grids . forEach ( ( grid ) => {
46
53
const currentBeatIndex = currentBeat . current % ( grid . numColumns || 1 ) ;
47
54
const beat = grid . beats [ currentBeatIndex ] ;
48
55
49
56
if ( beat ) {
50
57
// Schedule notes for the current beat
51
58
Object . values ( beat . notes ) . forEach ( note => {
52
- playNoteNow ( note . pitch , note . velocity , nextNoteTime . current ) ;
59
+ playNoteNow ( note . pitch , note . velocity ) ;
53
60
} ) ;
54
61
}
55
62
} ) ;
@@ -58,23 +65,23 @@ const PlayPauseButton: React.FC = () => {
58
65
59
66
// Move to the next beat
60
67
currentBeat . current += 1 ;
61
- nextNoteTime . current += intervalDuration ; // Schedule next beat
68
+ nextNoteTimeInSeconds . current += intervalDurationSeconds ; // Schedule next beat
62
69
}
63
- } , [ selectedOutput , grids , intervalDuration , playNoteNow ] ) ;
70
+ } , [ selectedOutput , grids , intervalDurationSeconds , playNoteNow ] ) ;
64
71
65
72
const scheduler = useCallback ( ( ) => {
66
73
if ( ! isPlaying || ! audioContextRef . current || ! isScheduling . current ) return ;
67
74
68
75
scheduleNotes ( ) ;
69
- setTimeout ( scheduler , lookahead ) ;
76
+ setTimeout ( scheduler , LOOK_AHEAD_MS ) ;
70
77
} , [ isPlaying , scheduleNotes ] ) ;
71
78
72
79
const startPlayback = useCallback ( ( ) => {
73
80
if ( ! audioContextRef . current ) {
74
81
audioContextRef . current = new window . AudioContext ( ) ;
75
82
}
76
83
77
- nextNoteTime . current = audioContextRef . current . currentTime ; // Initialize next note time
84
+ nextNoteTimeInSeconds . current = audioContextRef . current . currentTime ; // Initialize next note time
78
85
// Start scheduling
79
86
isScheduling . current = true ;
80
87
scheduler ( ) ;
@@ -105,7 +112,7 @@ const PlayPauseButton: React.FC = () => {
105
112
useEffect ( ( ) => {
106
113
const handlePlayNote = ( event : CustomEvent < PlayNoteNowDetail > ) => {
107
114
const { pitch, velocity } = event . detail ;
108
- playNoteNow ( pitch , velocity , audioContextRef . current ?. currentTime || 0 ) ;
115
+ playNoteNow ( pitch , velocity ) ;
109
116
} ;
110
117
111
118
window . addEventListener ( PLAY_NOTE_NOW_EVENT_NAME , handlePlayNote as EventListener ) ;
0 commit comments