Skip to content

Commit

Permalink
Pass transposition pitches of channels separately to synth to allow d…
Browse files Browse the repository at this point in the history
…ynamic change
  • Loading branch information
Danielku15 committed Dec 9, 2023
1 parent bde2503 commit c438985
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 28 deletions.
13 changes: 11 additions & 2 deletions src/AlphaTabApiBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,13 @@ export class AlphaTabApiBase<TSettings> {
// enable/disable player if needed
if (this.settings.player.enablePlayer) {
this.setupPlayer();
if (score) {
this.player?.applyTranspositionPitches(MidiFileGenerator.buildTranspositionPitches(score, this.settings));
}
} else {
this.destroyPlayer();
}

this.onSettingsUpdated();
}

Expand Down Expand Up @@ -331,8 +335,8 @@ export class AlphaTabApiBase<TSettings> {
this._cursorWrapper.width = result.totalWidth;
this._cursorWrapper.height = result.totalHeight;
}
if(result.width > 0 || result.height > 0) {

if (result.width > 0 || result.height > 0) {
this.uiFacade.beginAppendRenderResults(result);
}
} else {
Expand Down Expand Up @@ -607,10 +611,15 @@ export class AlphaTabApiBase<TSettings> {
let midiFile: MidiFile = new MidiFile();
let handler: AlphaSynthMidiFileHandler = new AlphaSynthMidiFileHandler(midiFile);
let generator: MidiFileGenerator = new MidiFileGenerator(this.score, this.settings, handler);

// we pass the transposition pitches separately to alphaSynth.
generator.applyTranspositionPitches = false;

generator.generate();
this._tickCache = generator.tickLookup;
this.onMidiLoad(midiFile);
this.player.loadMidiFile(midiFile);
this.player.applyTranspositionPitches(generator.transpositionPitches);
}

/**
Expand Down
42 changes: 36 additions & 6 deletions src/midi/MidiFileGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ export class MidiFileGenerator {
*/
public readonly tickLookup: MidiTickLookup = new MidiTickLookup();

/**
* Gets or sets whether transposition pitches should be applied to the individual midi events or not.
*/
public applyTranspositionPitches: boolean = true;


/**
* Gets the transposition pitches for the individual midi channels.
*/
public readonly transpositionPitches: Map<number, number> = new Map<number, number>();

/**
* Initializes a new instance of the {@link MidiFileGenerator} class.
* @param score The score for which the midi file should be generated.
Expand All @@ -87,6 +98,8 @@ export class MidiFileGenerator {
* Starts the generation of the midi file.
*/
public generate(): void {
this.transpositionPitches.clear();

// initialize tracks
for (const track of this._score.tracks) {
this.generateTrack(track);
Expand Down Expand Up @@ -141,7 +154,24 @@ export class MidiFileGenerator {
}
}

public static buildTranspositionPitches(score: Score, settings: Settings): Map<number, number> {
const transpositionPitches = new Map<number, number>();
for (const track of score.tracks) {
const transpositionPitch = track.index < settings.notation.transpositionPitches.length
? settings.notation.transpositionPitches[track.index]
: 0;
transpositionPitches.set(track.playbackInfo.primaryChannel, transpositionPitch);
transpositionPitches.set(track.playbackInfo.secondaryChannel, transpositionPitch);
}
return transpositionPitches;
}

private generateChannel(track: Track, channel: number, playbackInfo: PlaybackInformation): void {
const transpositionPitch = track.index < this._settings.notation.transpositionPitches.length
? this._settings.notation.transpositionPitches[track.index]
: 0;
this.transpositionPitches.set(channel, transpositionPitch);

let volume: number = MidiFileGenerator.toChannelShort(playbackInfo.volume);
let balance: number = MidiFileGenerator.toChannelShort(playbackInfo.balance);
this._handler.addControlChange(track.index, 0, channel, ControllerType.VolumeCoarse, volume);
Expand Down Expand Up @@ -411,7 +441,7 @@ export class MidiFileGenerator {
private generateNote(note: Note, beatStart: number, beatDuration: number, brushInfo: Int32Array): void {
const track: Track = note.beat.voice.bar.staff.track;
const staff: Staff = note.beat.voice.bar.staff;
let noteKey: number = note.realValue;
let noteKey: number = note.calculateRealValue(this.applyTranspositionPitches, true);
if (note.isPercussion) {
const articulation = PercussionMapper.getArticulation(note);
if (articulation) {
Expand Down Expand Up @@ -478,7 +508,7 @@ export class MidiFileGenerator {
this.generateWhammy(note.beat, noteStart, noteDuration, channel);
} else if (note.slideInType !== SlideInType.None || note.slideOutType !== SlideOutType.None) {
this.generateSlide(note, noteStart, noteDuration, noteKey, channel);
} else if (note.vibrato !== VibratoType.None || (note.isTieDestination && note.tieOrigin!.vibrato !== VibratoType.None)) {
} else if (note.vibrato !== VibratoType.None || (note.isTieDestination && note.tieOrigin!.vibrato !== VibratoType.None)) {
this.generateVibrato(note, noteStart, noteDuration, noteKey, channel);
}

Expand Down Expand Up @@ -669,8 +699,8 @@ export class MidiFileGenerator {
let phaseLength: number = 0;
let bendAmplitude: number = 0;
const vibratoType = note.vibrato !== VibratoType.None ? note.vibrato : (
note.isTieDestination ? note.tieOrigin!.vibrato :
VibratoType.Slight /* should never happen unless called wrongly */
note.isTieDestination ? note.tieOrigin!.vibrato :
VibratoType.Slight /* should never happen unless called wrongly */
);
switch (vibratoType) {
case VibratoType.Slight:
Expand All @@ -691,7 +721,7 @@ export class MidiFileGenerator {
}


public vibratoResolution:number = 16;
public vibratoResolution: number = 16;
private generateVibratorWithParams(
noteStart: number,
noteDuration: number,
Expand Down Expand Up @@ -790,7 +820,7 @@ export class MidiFileGenerator {
case SlideOutType.Shift:
playedBendPoints.push(new BendPoint(shiftSlideDurationOffset, 0));
// normal note values are in 1/2 tones, bends are in 1/4 tones
const dy = (note.slideTarget!.realValue - note.realValue) * 2;
const dy = (note.slideTarget!.calculateRealValue(this.applyTranspositionPitches, true) - note.calculateRealValue(this.applyTranspositionPitches, true)) * 2;
playedBendPoints.push(new BendPoint(BendPoint.MaxPosition, dy));
break;
case SlideOutType.OutDown:
Expand Down
51 changes: 34 additions & 17 deletions src/model/Note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,28 +504,45 @@ export class Note {
}

public get realValue(): number {
let realValue = this.realValueWithoutHarmonic;
if (this.isStringed) {
if (this.harmonicType === HarmonicType.Natural) {
realValue = this.harmonicPitch + this.stringTuning - this.beat.voice.bar.staff.transpositionPitch;
} else {
realValue += this.harmonicPitch;
}
}
return realValue;
return this.calculateRealValue(true, true);
}

public get realValueWithoutHarmonic(): number {
if (this.isPercussion) {
return this.percussionArticulation;
}
if (this.isStringed) {
return this.fret + this.stringTuning - this.beat.voice.bar.staff.transpositionPitch;
return this.calculateRealValue(true, false);
}

/**
* Calculates the real note value of this note as midi key respecting the given options.
* @param applyTranspositionPitch Whether or not to apply the transposition pitch of the current staff.
* @param applyHarmonic Whether or not to apply harmonic pitches to the note.
* @returns The calculated note value as midi key.
*/
public calculateRealValue(applyTranspositionPitch: boolean, applyHarmonic: boolean): number {
const transpositionPitch = applyTranspositionPitch ? this.beat.voice.bar.staff.transpositionPitch : 0;

if (applyHarmonic) {
let realValue = this.calculateRealValue(applyTranspositionPitch, false);
if (this.isStringed) {
if (this.harmonicType === HarmonicType.Natural) {
realValue = this.harmonicPitch + this.stringTuning - transpositionPitch;
} else {
realValue += this.harmonicPitch;
}
}
return realValue;
}
if (this.isPiano) {
return this.octave * 12 + this.tone - this.beat.voice.bar.staff.transpositionPitch;
else {
if (this.isPercussion) {
return this.percussionArticulation;
}
if (this.isStringed) {
return this.fret + this.stringTuning - transpositionPitch;
}
if (this.isPiano) {
return this.octave * 12 + this.tone - transpositionPitch;
}
return 0;
}
return 0;
}

public get harmonicPitch(): number {
Expand Down
7 changes: 5 additions & 2 deletions src/platform/javascript/AlphaSynthWebWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class AlphaSynthWebWorker {
private _player: AlphaSynth;
private _main: IWorkerScope;

public constructor(main: IWorkerScope, bufferTimeInMilliseconds:number) {
public constructor(main: IWorkerScope, bufferTimeInMilliseconds: number) {
this._main = main;
this._main.addEventListener('message', this.handleMessage.bind(this));

Expand Down Expand Up @@ -84,7 +84,7 @@ export class AlphaSynthWebWorker {
break;
case 'alphaSynth.setCountInVolume':
this._player.countInVolume = data.value;
break;
break;
case 'alphaSynth.setMidiEventsPlayedFilter':
this._player.midiEventsPlayedFilter = data.value;
break;
Expand Down Expand Up @@ -130,6 +130,9 @@ export class AlphaSynthWebWorker {
cmd: 'alphaSynth.destroyed'
});
break;
case 'alphaSynth.applyTranspositionPitches':
this._player.applyTranspositionPitches(new Map<number, number>(JSON.parse(data.transpositionPitches)));
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 @@ -312,6 +312,13 @@ export class AlphaSynthWebWorkerApi implements IAlphaSynth {
});
}

public applyTranspositionPitches(transpositionPitches: Map<number, number>): void {
this._synth.postMessage({
cmd: 'alphaSynth.applyTranspositionPitches',
transpositionPitches: JSON.stringify(Array.from(transpositionPitches.entries()))
});
}

public setChannelMute(channel: number, mute: boolean): void {
this._synth.postMessage({
cmd: 'alphaSynth.setChannelMute',
Expand Down
6 changes: 5 additions & 1 deletion src/synth/AlphaSynth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,10 @@ export class AlphaSynth implements IAlphaSynth {
}
}

public applyTranspositionPitches(transpositionPitches: Map<number, number>): void {
this._synthesizer.applyTranspositionPitches(transpositionPitches);
}

public setChannelMute(channel: number, mute: boolean): void {
this._synthesizer.channelSetMute(channel, mute);
}
Expand Down Expand Up @@ -418,7 +422,7 @@ export class AlphaSynth implements IAlphaSynth {
} else {
endTick = this._sequencer.currentEndTick;
}

if (this._tickPosition >= endTick && this._notPlayedSamples <= 0) {
this._notPlayedSamples = 0;
if (this._sequencer.isPlayingCountIn) {
Expand Down
6 changes: 6 additions & 0 deletions src/synth/IAlphaSynth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ export interface IAlphaSynth {
*/
loadMidiFile(midi: MidiFile): void;

/**
* Applies the given transposition pitches to be used during playback.
* @param transpositionPitches a map defining the transposition pitches for midi channel.
*/
applyTranspositionPitches(transpositionPitches: Map<number, number>): void;

/**
* Sets the mute state of a channel.
* @param channel The channel number
Expand Down
38 changes: 38 additions & 0 deletions src/synth/synthesis/TinySoundFont.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class TinySoundFont {
private _mutedChannels: Map<number, boolean> = new Map<number, boolean>();
private _soloChannels: Map<number, boolean> = new Map<number, boolean>();
private _isAnySolo: boolean = false;
private _transpositionPitches: Map<number, number> = new Map<number, number>();

public currentTempo: number = 0;
public timeSignatureNumerator: number = 0;
Expand Down Expand Up @@ -94,9 +95,34 @@ export class TinySoundFont {
public resetChannelStates(): void {
this._mutedChannels = new Map<number, boolean>();
this._soloChannels = new Map<number, boolean>();

this.applyTranspositionPitches(new Map<number, number>());
this._isAnySolo = false;
}

public applyTranspositionPitches(transpositionPitches: Map<number, number>): void {
// dynamically adjust actively playing voices to the new pitch they have.
// we are not updating the used preset and regions though.
const previousTransposePitches = this._transpositionPitches;
for (const voice of this._voices) {
if (voice.playingChannel >= 0 && voice.playingChannel !== 9 /*percussion*/) {
let pitchDifference = 0;

if(previousTransposePitches.has(voice.playingChannel)) {
pitchDifference -= previousTransposePitches.get(voice.playingChannel)!;
}

if(transpositionPitches.has(voice.playingChannel)) {
pitchDifference += transpositionPitches.get(voice.playingChannel)!;
}

voice.playingKey += pitchDifference;
}
}

this._transpositionPitches = transpositionPitches;
}

public dispatchEvent(synthEvent: SynthEvent): void {
this._midiEventQueue.enqueue(synthEvent);
}
Expand Down Expand Up @@ -581,6 +607,10 @@ export class TinySoundFont {
return;
}

if (this._transpositionPitches.has(channel)) {
key += this._transpositionPitches.get(channel)!;
}

this._channels.activeChannel = channel;
this.noteOn(this._channels.channelList[channel].presetIndex, key, vel);
}
Expand All @@ -591,6 +621,10 @@ export class TinySoundFont {
* @param key note value between 0 and 127 (60 being middle C)
*/
public channelNoteOff(channel: number, key: number): void {
if (this._transpositionPitches.has(channel)) {
key += this._transpositionPitches.get(channel)!;
}

const matches: Voice[] = [];
let matchFirst: Voice | null = null;
let matchLast: Voice | null = null;
Expand Down Expand Up @@ -804,6 +838,10 @@ export class TinySoundFont {
* @param pitchWheel pitch wheel position 0 to 16383 (default 8192 unpitched)
*/
public channelSetPerNotePitchWheel(channel: number, key: number, pitchWheel: number): void {
if (this._transpositionPitches.has(channel)) {
key += this._transpositionPitches.get(channel)!;
}

const c: Channel = this.channelInit(channel);
if (c.perNotePitchWheel.has(key) && c.perNotePitchWheel.get(key) === pitchWheel) {
return;
Expand Down

0 comments on commit c438985

Please sign in to comment.