import React , { useState , useEffect , useRef } from 'react' ; import equal from 'fast-deep-equal' ; import { SongContext } from './Song' ; import Tone from '../lib/tone' ; import buildSequencerStep , { SequencerStep } from '../lib/buildSequencerStep' ; import { usePrevious } from '../lib/hooks' ; import { MidiNote } from '../types/midi-notes' ; export interface StepNoteType { name : MidiNote ; duration ? : number | string ; velocity ? : number ; } export type StepType = StepNoteType | StepNoteType [ ] | MidiNote | MidiNote [ ] | null ; export interface TrackProps { steps ? : StepType [ ] ; volume ? : number ; pan ? : number ; mute ? : boolean ; solo ? : boolean ; subdivision ? : string ; effects ? : React . ReactNode [ ] ; children : React . ReactNode ; onStepPlay ? : ( stepNotes : StepNoteType [ ] , index : number ) => void ; } export interface TrackConsumerProps extends TrackProps { isPlaying : boolean ; } export const TrackContext = React . createContext ( { volume : 0 , pan : 0 , mute : false , solo : false , effectsChain : null , onInstrumentsUpdate : null , onAddToEffectsChain : null , onRemoveFromEffectsChain : null } ) ; const TrackConsumer = ( { isPlaying , steps = [ ] , volume = 0 , pan = 0 , mute , solo , subdivision = '4n' , effects = [ ] , children , onStepPlay , } ) => { const [ effectsChain , setEffectsChain ] = useState ( [ ] ) ; const [ instruments , setInstruments ] = useState ( [ ] ) ; const sequencer = useRef < { start : Function ; stop : Function ; remove : Function ; add : Function ; dispose : Function ; removeAll : Function ; } > ( ) ; const instrumentsRef = useRef ( instruments ) ; useEffect ( ( ) => { instrumentsRef . current = instruments ; } , [ instruments ] ) ; const sequencerSteps = steps . map ( buildSequencerStep ) ; const prevSequencerSteps = usePrevious ( sequencerSteps ) ; useEffect ( ( ) => { if ( isPlaying ) { sequencer . current = new Tone . Sequence ( ( _ , step ) => { step . notes . forEach ( ( note ) => { instrumentsRef . current . forEach ( ( instrument ) => { instrument . triggerAttackRelease ( note . name , note . duration || 0.5 , undefined , note . velocity ) ; } ) ; } ) ; if ( typeof onStepPlay === 'function' ) { onStepPlay ( step . notes , step . index ) ; } } , sequencerSteps , subdivision ) ; sequencer . current ?. start ( 0 ) ; } else { if ( sequencer . current ) { sequencer . current . stop ( ) ; } } } , [ isPlaying ] ) ; useEffect ( ( ) => { if ( sequencer . current ) { if ( prevSequencerSteps ?. length === sequencerSteps . length ) { sequencerSteps . forEach ( ( step , i ) => { const isEqual = equal ( sequencerSteps [ i ] . notes , prevSequencerSteps && prevSequencerSteps [ i ] ? prevSequencerSteps [ i ] . notes : [ ] ) ; if ( ! isEqual ) { sequencer . current ?. remove ( i ) ; sequencer . current ?. add ( i , step ) ; } } ) ; } else { sequencer . current . removeAll ( ) ; sequencerSteps . forEach ( ( step , i ) => { sequencer . current . add ( i , step ) ; } ) ; } } } , [ JSON . stringify ( sequencerSteps ) ] ) ; useEffect ( ( ) => { return function cleanup ( ) { if ( sequencer . current ) { sequencer . current . dispose ( ) ; } } ; } , [ ] ) ; const handleAddToEffectsChain = ( effect ) => { setEffectsChain ( ( prevEffectsChain ) => { return [ effect , ... prevEffectsChain ] ; } ) ; } ; const handleRemoveFromEffectsChain = ( effect ) => { setEffectsChain ( ( prevEffectsChain ) => { return prevEffectsChain . filter ( ( e ) => e . id !== effect . id ) ; } ) ; } ; const handleInstrumentsUpdate = ( newInstruments ) => { setInstruments ( newInstruments ) ; } ; return ( < TrackContext . Provider value = { { effectsChain , pan , volume , mute , solo , onInstrumentsUpdate : handleInstrumentsUpdate , onAddToEffectsChain : handleAddToEffectsChain , onRemoveFromEffectsChain : handleRemoveFromEffectsChain } } > { children } { effects } < /TrackContext.Provider>); } ; const Track = ( props ) => { const { isPlaying } = React . useContext ( SongContext ) ; if ( typeof window === 'undefined' ) { return null ; } return < TrackConsumer isPlaying = { isPlaying } { ... props } />; } ; export default Track ;