Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ public void Pause()
DispatchOnWorkerThread(() => { Player.Pause(); });
}

public void PlayOneTimeMidiFile(MidiFile midiFile)
{
DispatchOnWorkerThread(() => { Player.PlayOneTimeMidiFile(midiFile); });
}

public void PlayPause()
{
DispatchOnWorkerThread(() => { Player.PlayPause(); });
Expand Down
42 changes: 40 additions & 2 deletions src/AlphaTabApiBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { Settings } from '@src/Settings';
import { Logger } from '@src/Logger';
import { ModelUtils } from '@src/model/ModelUtils';
import { AlphaTabError, AlphaTabErrorType } from '@src/AlphaTabError';
import { Note } from './model/Note';

class SelectionInfo {
public beat: Beat;
Expand Down Expand Up @@ -637,6 +638,43 @@ export class AlphaTabApiBase<TSettings> {
this.player.stop();
}

/**
* Triggers the play of the given beat. This will stop the any other current ongoing playback.
* @param beat the single beat to play
*/
public playBeat(beat: Beat): void {
if (!this.player) {
return;
}

// we generate a new midi file containing only the beat
let midiFile: MidiFile = new MidiFile();
let handler: AlphaSynthMidiFileHandler = new AlphaSynthMidiFileHandler(midiFile);
let generator: MidiFileGenerator = new MidiFileGenerator(beat.voice.bar.staff.track.score, this.settings, handler);
generator.generateSingleBeat(beat);

this.player.playOneTimeMidiFile(midiFile);
}

/**
* Triggers the play of the given note. This will stop the any other current ongoing playback.
* @param beat the single note to play
*/
public playNote(note: Note): void {
if (!this.player) {
return;
}

// we generate a new midi file containing only the beat
let midiFile: MidiFile = new MidiFile();
let handler: AlphaSynthMidiFileHandler = new AlphaSynthMidiFileHandler(midiFile);
let generator: MidiFileGenerator = new MidiFileGenerator(note.beat.voice.bar.staff.track.score, this.settings, handler);
generator.generateSingleNote(note);

this.player.playOneTimeMidiFile(midiFile);
}


private _cursorWrapper: IContainer | null = null;
private _barCursor: IContainer | null = null;
private _beatCursor: IContainer | null = null;
Expand Down Expand Up @@ -969,7 +1007,7 @@ export class AlphaTabApiBase<TSettings> {
if (!this._tickCache) {
return;
}
if(range) {
if (range) {
const startBeat = this._tickCache.findBeat(this.tracks, range.startTick);
const endBeat = this._tickCache.findBeat(this.tracks, range.endTick);
if (startBeat && endBeat) {
Expand All @@ -980,7 +1018,7 @@ export class AlphaTabApiBase<TSettings> {
} else {
this.cursorSelectRange(null, null);
}

}

private setupClickHandling(): void {
Expand Down
67 changes: 67 additions & 0 deletions src/midi/MidiFileGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1265,4 +1265,71 @@ export class MidiFileGenerator {
break;
}
}

public prepareSingleBeat(beat: Beat) {
// collect tempo and program at given beat
let tempo = -1;
let program = -1;

// traverse to previous beats until we maybe hit the automations needed
let currentBeat: Beat | null = beat;
while (currentBeat && (tempo === -1 || program === -1)) {
for (const automation of beat.automations) {
switch (automation.type) {
case AutomationType.Instrument:
program = automation.value;
break;
case AutomationType.Tempo:
tempo = automation.value;
break;
}
}
currentBeat = currentBeat.previousBeat;
}

const track = beat.voice.bar.staff.track;
const masterBar = beat.voice.bar.masterBar;
if (tempo === -1) {
tempo = masterBar.score.tempo;
}

if (program === -1) {
program = track.playbackInfo.program;
}

const volume = track.playbackInfo.volume;

// setup channel
this.generateTrack(track);
this._handler.addTimeSignature(0, masterBar.timeSignatureNumerator, masterBar.timeSignatureDenominator);
this._handler.addTempo(0, tempo);


let volumeCoarse: number = MidiFileGenerator.toChannelShort(volume);
this._handler.addControlChange(
0,
0,
track.playbackInfo.primaryChannel,
ControllerType.VolumeCoarse,
volumeCoarse
);
this._handler.addControlChange(
0,
0,
track.playbackInfo.secondaryChannel,
ControllerType.VolumeCoarse,
volumeCoarse
);
}

public generateSingleBeat(beat: Beat) {
this.prepareSingleBeat(beat);

this.generateBeat(beat, -beat.playbackStart /* to bring it to 0*/, beat.voice.bar);
}

public generateSingleNote(note: Note) {
this.prepareSingleBeat(note.beat);
this.generateNote(note, -note.beat.playbackStart, note.beat.playbackDuration, new Int32Array(note.beat.voice.bar.staff.tuning.length));
}
}
3 changes: 3 additions & 0 deletions src/platform/javascript/AlphaSynthWebWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ export class AlphaSynthWebWorker {
case 'alphaSynth.stop':
this._player.stop();
break;
case 'alphaSynth.playOneTimeMidiFile':
this._player.playOneTimeMidiFile(JsonConverter.jsObjectToMidiFile(data.midi));
break;
case 'alphaSynth.loadSoundFontBytes':
this._player.loadSoundFont(data.data, data.append);
break;
Expand Down
7 changes: 7 additions & 0 deletions src/platform/javascript/AlphaSynthWebWorkerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,13 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth {
});
}

public playOneTimeMidiFile(midi: MidiFile): void {
this._synth.postMessage({
cmd: 'alphaSynth.playOneTimeMidiFile',
midi: JsonConverter.midiFileToJsObject(midi)
});
}

public loadSoundFont(data: Uint8Array, append: boolean): void {
this._synth.postMessage({
cmd: 'alphaSynth.loadSoundFontBytes',
Expand Down
55 changes: 40 additions & 15 deletions src/synth/AlphaSynth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export class AlphaSynth implements IAlphaSynth {
}

public play(): boolean {
if (this.state === PlayerState.Playing || !this._isMidiLoaded) {
if (this.state !== PlayerState.Paused || !this._isMidiLoaded) {
return false;
}
this.output.activate();
Expand Down Expand Up @@ -206,7 +206,7 @@ export class AlphaSynth implements IAlphaSynth {
}

public playPause(): void {
if (this.state === PlayerState.Playing || !this._isMidiLoaded) {
if (this.state !== PlayerState.Paused || !this._isMidiLoaded) {
this.pause();
} else {
this.play();
Expand All @@ -227,6 +227,22 @@ export class AlphaSynth implements IAlphaSynth {
new PlayerStateChangedEventArgs(this.state, true)
);
}

public playOneTimeMidiFile(midi: MidiFile): void {
// pause current playback.
this.pause();

this._sequencer.loadOneTimeMidi(midi);

this._sequencer.stop();
this._synthesizer.noteOffAll(true);
this.tickPosition = 0;

(this.stateChanged as EventEmitterOfT<PlayerStateChangedEventArgs>).trigger(
new PlayerStateChangedEventArgs(this.state, false)
);
this.output.play();
}

public resetSoundFonts(): void {
this.stop();
Expand Down Expand Up @@ -304,7 +320,6 @@ export class AlphaSynth implements IAlphaSynth {
private onSamplesPlayed(sampleCount: number): void {
let playedMillis: number = (sampleCount / this._synthesizer.outSampleRate) * 1000;
this.updateTimePosition(this._timePosition + playedMillis);

this.checkForFinish();
}

Expand All @@ -320,12 +335,19 @@ export class AlphaSynth implements IAlphaSynth {

if (this._tickPosition >= endTick) {
Logger.debug('AlphaSynth', 'Finished playback');
(this.finished as EventEmitter).trigger();

if (this.isLooping) {
this.tickPosition = startTick;
if(this._sequencer.isPlayingOneTimeMidi) {
this._sequencer.resetOneTimeMidi();
this.state = PlayerState.Paused;
this.output.pause();
this._synthesizer.noteOffAll(false);
} else {
this.stop();
(this.finished as EventEmitter).trigger();

if (this.isLooping) {
this.tickPosition = startTick;
} else {
this.stop();
}
}
}
}
Expand All @@ -336,13 +358,16 @@ export class AlphaSynth implements IAlphaSynth {
const currentTick: number = (this._tickPosition = this._sequencer.timePositionToTickPosition(currentTime));
const endTime: number = this._sequencer.endTime;
const endTick: number = this._sequencer.endTick;
Logger.debug(
'AlphaSynth',
`Position changed: (time: ${currentTime}/${endTime}, tick: ${currentTick}/${endTick}, Active Voices: ${this._synthesizer.activeVoiceCount}`
);
(this.positionChanged as EventEmitterOfT<PositionChangedEventArgs>).trigger(
new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick)
);

if(!this._sequencer.isPlayingOneTimeMidi) {
Logger.debug(
'AlphaSynth',
`Position changed: (time: ${currentTime}/${endTime}, tick: ${currentTick}/${endTick}, Active Voices: ${this._synthesizer.activeVoiceCount}`
);
(this.positionChanged as EventEmitterOfT<PositionChangedEventArgs>).trigger(
new PositionChangedEventArgs(currentTime, endTime, currentTick, endTick)
);
}
}

readonly ready: IEventEmitter = new EventEmitter();
Expand Down
6 changes: 6 additions & 0 deletions src/synth/IAlphaSynth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ export interface IAlphaSynth {
* Stopps the playback
*/
stop(): void;

/**
* Stops any ongoing playback and plays the given midi file instead.
* @param midi The midi file to play
*/
playOneTimeMidiFile(midi: MidiFile): void;

/**
* Loads a soundfont from the given data
Expand Down
Loading