/** * * 修改自: * https://github.com/zya/wa-metro/blob/master/lib/wa-metro.js * */ import runtime, * as RunTimeUtils from '/src/pages/detail/runtime' import state from '/src/pages/detail/state' type ICallback = (time?: number, step?: number, scheduler_interval?: number, context?: AudioContext) => void type IOptions = { steps: number tempo: number denominator: number numerator: number limit: number onStop?: () => void onTick?: (tick?: number) => void } export const ac = (window.AudioContext || (window as any).webkitAudioContext || (window as any).mozAudioContext || (window as any).msAudioContext) class Metro { audio: HTMLAudioElement = new Audio()//runtime.audiosInstance.audio.cloneNode(true) context = new ac() callback: ICallback = () => {} look_ahead = 1.0 _step = 1 _scheduler_interval = 20 _next_event_time = 0.0 _first = true _is_running = false timer: number = -1 tiems: number = 0 tick: number = 0 options: IOptions = { steps: 4, tempo: 90, denominator: 4, numerator: 4, limit: 0, onStop: () => {}, onTick: () => {} } constructor(options?: IOptions) { this.options = { ...this.options, ...options } } refreshTick = () => { RunTimeUtils.refreshTick(this.audio.currentTime) if (this.audio.currentTime >= 4.8) { this.stop() } } start = (options?: IOptions) => { // state.showTick = true this.audio.play() this.audio.addEventListener('timeupdate', this.refreshTick) // if (this._is_running) { // console.log('already started'); // return; // } // this._step = 1 this.options = { ...this.options, ...options } // this._is_running = true; // this.timer = setInterval(() => { // this._scheduler() // }, 25) // this._worker.postMessage('start'); }; pause = () => { this._is_running = false; // this._worker.postMessage('stop'); this.stop() }; stop = (dontCall?: boolean) => { // state.showTick = false this.audio.pause() this.audio.removeEventListener('timeupdate', this.refreshTick) if (this.audio.currentTime >= 4.8) { this.options.onStop && this.options.onStop() } runtime.osmd.cursor.hide() this.audio.currentTime = 0 this.refreshTick() // this._first = true; // this._step = 1; // this._is_running = false; // clearInterval(this.timer) // this.timer = -1 // this.tiems = 0 // this.tick = 0 // this.context = new ac() // if (dontCall !== true) { // setTimeout(() => { // this._is_running = false; // this.options.onStop && this.options.onStop() // this.context.close() // }, 500) // } else { // // this.context.close() // } // this._worker.postMessage('stop'); }; scheduleNote = (beatNumber: number, time: number) => { // create an oscillator var osc = this.context.createOscillator(); osc.connect( this.context.destination ); // if (beatNumber % 16 === 0) // beat 0 == low pitch // osc.frequency.value = 880.0; // osc.type = 'sine'; // osc.detune.value = -300; if (beatNumber === 1) { osc.frequency.value = 880.0; } else { osc.frequency.value = 440.0; } osc.start( time ); osc.stop( time + 0.05 ); // osc.onended = () => { // console.log(time) // this.tick ++ // this.options.onTick && this.options.onTick(this.tick); // // if (this.options.limit > 0 && this.tick === this.options.limit * this.options.numerator) { // // this.options.onStop && this.options.onStop() // // } // } } _scheduler = () => { var self = this; if (this._step === 1 && this._first) { this._next_event_time = this.context.currentTime; } while (this._next_event_time < this.context.currentTime + this.look_ahead && this._is_running) { if (this.options.limit > 0 && this.tiems >= this.options.limit) { this.stop() break } if (this._step === this.options.numerator) { this.tiems ++ } this.options.onTick && this.options.onTick(this._step); // var event_time_from_scheduled = self._next_event_time - self.context.currentTime; this.scheduleNote(this._step, self._next_event_time) this._next(); } }; _next = () => { this._step++; if (this._first) { this._next_event_time = this.context.currentTime; this._first = false; } // if (this._step > this.options.numerator) { // this._step = 1; // } this._next_event_time += ((60.0 / this.options.tempo) * 4) / this.options.denominator; }; } export default Metro