UNPKG

smoosic

Version:

<sub>[Github site](https://github.com/Smoosic/smoosic) | [source documentation](https://smoosic.github.io/Smoosic/release/docs/modules.html) | [change notes](https://aarondavidnewman.github.io/Smoosic/changes.html) | [application](https://smoosic.github.i

155 lines (150 loc) 5.82 kB
// [Smoosic](https://github.com/AaronDavidNewman/Smoosic) // Copyright (c) Aaron David Newman 2021. import { SmoMusic } from '../data/music'; import { TickMappable } from '../data/measure'; import { Pitch, IsPitchLetter, TickAccidental } from '../data/common'; import { SmoNote } from '../data/note'; /** * create a map note durations at each index into the voice, including the accidentals at each duration. * return format: * ``` tickmap = { totalDuration: 16384, durationMap:[2048,4096,..], // A running total per tick deltaMap:[2048,2048...], a map of deltas ``` * @category SmoTransform */ export class TickMap { keySignature: string; voice: number; notes: SmoNote[] = []; index: number = 0; startIndex: number = 0; endIndex: number = 0; // duration is the accumulated duraition over all the notes totalDuration: number = 0; // delta is the tick contribution of this note delta: number = 0; // the absolute tick start location of notes[x] durationMap: number[] = []; // the relative duration if each tick slot deltaMap: number[] = []; // An array of active accidentals for each tick index accidentalMap: Record<string, TickAccidental>[] = []; // a map of active accidentals, indexed by duration index durationAccidentalMap: Record<string | number, Record<string, TickAccidental>> = {}; constructor(measure: TickMappable, voiceIndex: number) { this.keySignature = measure.keySignature; this.voice = voiceIndex; if (measure.voices.length <= this.voice) { console.warn('tickmap for invalid voice'); return; } this.notes = measure.voices[this.voice].notes; this.endIndex = this.notes.length; this.createMap(); } // ### _getAccidentalsForKey // Update `map` with the correct accidental based on the key signature. _getAccidentalsForKey(map: Record<string, TickAccidental>) { const keys = SmoMusic.getScaleTonesForKey(this.keySignature); const keyKeys = Object.keys(keys); keyKeys.forEach((keyKey) => { const vexKey = keys[keyKey]; if (vexKey.length > 1 && (vexKey[1] === 'b' || vexKey[1] === '#')) { if (IsPitchLetter(vexKey[0])) { const pitch = { letter: vexKey[0], accidental: vexKey[1], octave: 4 }; map[vexKey[0]] = { duration: 0, pitch }; } } }); } // ### updateAccidentalMap // Keep a running tally of the accidentals for this voice // based on the key and previous accidentals. updateAccidentalMap(note: SmoNote) { let i = 0; let sigObj: Record<string, TickAccidental> = {}; const newObj: Record<string, TickAccidental> = {}; if (this.index === 0) { this._getAccidentalsForKey(newObj); sigObj = newObj; } else { sigObj = this.accidentalMap[this.index - 1]; } for (i = 0; i < note.pitches.length; ++i) { if (note.noteType !== 'n') { continue; } const pitch: Pitch = note.pitches[i]; const pitchOctave = pitch.letter.toLowerCase() + '-' + pitch.octave; const sigLetter: string = pitchOctave + pitch.accidental; const sigKey = SmoMusic.getKeySignatureKey(pitch.letter, this.keySignature); if (sigObj && sigObj[pitchOctave]) { const curObj = sigObj[pitchOctave]; const currentVal = curObj.pitch.letter.toLowerCase() + '-' + curObj.pitch.octave + curObj.pitch.accidental; if (sigLetter !== currentVal) { newObj[pitchOctave] = { pitch, duration: this.duration }; } } else { if (sigLetter !== sigKey) { newObj[pitchOctave] = { pitch, duration: this.duration }; } } } this.accidentalMap.push(newObj); // Mark the accidental with the start of this note. this.durationAccidentalMap[this.durationMap[this.index]] = newObj; } // ### getActiveAccidental // return the active accidental for the given note getActiveAccidental(pitch: Pitch, iteratorIndex: number, keySignature: string) { let defaultAccidental: string = SmoMusic.getKeySignatureKey(pitch.letter, keySignature); let i = 0; let j = 0; defaultAccidental = defaultAccidental.length > 1 ? defaultAccidental[1] : 'n'; if (iteratorIndex === 0) { return defaultAccidental; } // Back up the accidental map until we have a match, or until we run out for (i = iteratorIndex; i > 0; --i) { const map: Record<string, TickAccidental> = this.accidentalMap[i - 1]; const mapKeys = Object.keys(map); for (j = 0; j < mapKeys.length; ++j) { const mapKey: string = mapKeys[j]; // The letter name + accidental in the map const mapPitch: Pitch = map[mapKey].pitch; const mapAcc = mapPitch.accidental ? mapPitch.accidental : 'n'; // if the letters match and the accidental... if (mapPitch.letter.toLowerCase() === pitch.letter) { return mapAcc; } } } return defaultAccidental; } get duration() { return this.totalDuration; } createMap() { for (this.index = this.startIndex; this.index < this.endIndex; ++this.index) { const note = this.notes[this.index]; // save the starting point, tickwise this.durationMap.push(this.totalDuration); // the number of ticks for this note this.delta = (note.ticks.numerator / note.ticks.denominator) + note.ticks.remainder; this.deltaMap.push(this.delta); // update the tick count for the whole array/measure this.totalDuration += this.delta; this.updateAccidentalMap(note); } } }