import * as mm from '@magenta/music' ; import { observable } from 'mobx' ; import logging , { Events } from './logging' ; import { Note } from './note' ; import { editor } from './index' ; import { range } from 'lodash' ; import * as Tone from 'tone' ; import { trim , getMagentaNoteSequence } from './magenta-utils' ; import { DEFAULT_BPM , MIN_PITCH , MAX_PITCH , NOTE_VELOCITY , SOUNDFONT_URL , TOTAL_SIXTEENTHS , } from './constants' ; export class CallbackObject extends mm . BasePlayerCallback { constructor ( private player ) { super ( ) ; } run ( note , t ) { const { pitch , quantizedStartStep } = note ; editor . setNotePlaying ( pitch , quantizedStartStep + this . player . loopStart ) ; } stop ( ) { if ( this . player . shouldLoop ) { this . player . loop ( ) ; } else { this . player . stop ( ) ; } } } class Player { playerCallbackObject = new CallbackObject ( this ) ; @ observable isPlayerLoaded = false ; @ observable isPlaying = false ; @ observable shouldLoop = true ; @ observable loopStart = 0 ; @ observable loopEnd = TOTAL_SIXTEENTHS ; private player = new mm . SoundFontPlayer ( SOUNDFONT_URL , Tone . master , undefined , undefined , this . playerCallbackObject ) ; @ observable bpm = DEFAULT_BPM ; constructor ( ) { this . loadPlayer ( ) ; } async loadPlayer ( ) { const allNotes = range ( MIN_PITCH , MAX_PITCH + 1 ) . map ( pitch => ( { pitch , velocity : NOTE_VELOCITY , } ) ) ; const allNotesSeq = { notes : allNotes } ; await this . player . loadSamples ( allNotesSeq ) ; this . isPlayerLoaded = true ; } togglePlay ( ) { if ( editor . allNotes . length === 0 ) return ; if ( this . isPlaying ) { this . stop ( ) ; } else { this . start ( ) ; } } playNote ( note ) { if ( this . isPlayerLoaded ) { this . playNoteDown ( note ) ; const timePerSixteenth = ( ( ( 1 / this . bpm ) * 60 ) / 4 ) * 1000 ; const delay = note . duration * timePerSixteenth ; setTimeout ( ( ) => this . playNoteUp ( note ) , delay ) ; } } playNoteDown ( note ) { if ( this . isPlayerLoaded ) { editor . setActiveNoteValue ( note . pitch ) ; this . player . playNoteDown ( note . magentaNote ) ; } } playNoteUp ( note ) { if ( this . isPlayerLoaded ) { editor . setActiveNoteValue ( null ) ; this . player . playNoteUp ( note . magentaNote ) ; } } start ( ) { logging . logEvent ( Events . PLAY ) ; this . _start ( ) ; } private _start ( ) { if ( this . isPlayerLoaded ) { if ( editor . allNotes . length === 0 ) { return ; } const sequence = getMagentaNoteSequence ( editor . unmutedNotes , this . bpm , editor . totalSixteenths ) ; const loopSequence = trim ( sequence , this . loopStart , this . loopEnd , true ) ; loopSequence . notes = loopSequence . notes . filter ( note => { return note . quantizedStartStep !== note . quantizedEndStep ; } ) ; this . player . start ( loopSequence ) ; this . isPlaying = true ; } } loop ( ) { editor . clearPlayingNotes ( ) ; this . start ( ) ; } stop ( ) { logging . logEvent ( Events . STOP ) ; this . _stop ( ) ; } private _stop ( ) { this . player . stop ( ) ; this . isPlaying = false ; editor . clearPlayingNotes ( ) ; } restart ( ) { this . _stop ( ) ; this . _start ( ) ; } } export default new Player ( ) ;