UNPKG

blob-opera-midi

Version:
174 lines (124 loc) 3.88 kB
const Alea = require(`alea`) const _ = require(`lodash`) const VOWELS = _.range(4) const CONSONANTS = _.range(5, 29) const DEFAULT_VOWEL_DURATION = 0.20000000298023224 const DEFAULT_CONSONANT_DURATION = 0.10000000149011612 class MidiToBlob { constructor (player) { this.player = player } getCurrentPhoneme ({ tick }, collection) { const prng = new Alea(tick) const tickValue = Math.round(prng() * 1000) return collection[tickValue % collection.length] } getMaxTicks () { const allEvents = this.player.getEvents() return _.last(_.maxBy(allEvents, (events) => _.last(events).tick)).tick } getNoteEvents () { return this.player.getEvents() .map(track => _.uniqBy(track.filter(event => [`Note on`, `Note off`].includes(event.name)), `tick`)) .filter(track => !_.isEmpty(track)) } getSongTime () { return this.player.getSongTime() } getTrackTimelines () { return this.getNoteEvents() .map((track, index) => { return { title: `Track ${index}`, x: track.map(event => event.tick), y: track.map(event => event.noteNumber), style: { line: _.times(3, () => Math.random() * 255) } } }) } getMidiPitch ({ noteNumber }, freePitch) { if (freePitch) { return noteNumber } if (noteNumber < 48) { return noteNumber + 12 } if (noteNumber < 70) { return noteNumber } return noteNumber - 12 } convert (trackAssignments = [0, 1, 2, 3], christmas = true, random = false, freePitch = false) { const noteEvents = this.getNoteEvents() const maxTicks = this.getMaxTicks() const songTime = this.getSongTime() const parts = trackAssignments.map((trackIndex) => { const track = noteEvents[trackIndex] return track.reduce((memo, event, index, allEvents) => { const nextEvent = _.get(allEvents, index + 1) const offset = random ? (Math.random() * 0.025 * _.sample([1, -1])) : 0 const offsetTimeSeconds = (event.tick / maxTicks) * songTime + offset const timeSeconds = event.tick > 0 ? offsetTimeSeconds : Math.abs(offsetTimeSeconds) const midiPitch = this.getMidiPitch(event, freePitch) if (!event.velocity || event.name === `Note off`) { if (nextEvent && nextEvent.delta > 800) { memo.push({ midiPitch, timeSeconds: timeSeconds + 0.5, librettoChunk: { vowel: { name: 4, duration: DEFAULT_VOWEL_DURATION }, suffix: [{ name: this.getCurrentPhoneme(event, CONSONANTS), duration: DEFAULT_CONSONANT_DURATION }] }, controlled: false }) } return memo } let duration = DEFAULT_VOWEL_DURATION if (nextEvent) { duration = Math.min(((nextEvent.delta / maxTicks) * songTime) / 2, DEFAULT_VOWEL_DURATION) } memo.push({ timeSeconds, midiPitch, librettoChunk: { vowel: { name: this.getCurrentPhoneme(event, VOWELS), duration: duration }, suffix: [{ name: this.getCurrentPhoneme(event, CONSONANTS), duration: DEFAULT_CONSONANT_DURATION }] }, controlled: true }) return memo }, []) }) .map((notes) => { return { notes, startSuffix: [ { name: this.getCurrentPhoneme({ tick: 0 }, CONSONANTS), duration: DEFAULT_CONSONANT_DURATION } ] } }) return { theme: christmas ? 1 : 0, parts } } } module.exports = MidiToBlob