import Tone from 'tone' import WebMidi from 'webmidi' import scales from './scales' import { ftom, norm, dataURItoBlob } from './util' import kalimba from './kalimba' import { saveAs } from 'file-saver/FileSaver' import { nx } from './ui' let midiDevice let sendPitchBend = false export const MidiWriter = require('midi-writer-js') export const note_values = [ [8, '8 measures', 8 * 512], [4, '4 measures', 4 * 512], [2, '2 measures', 2 * 512], [1, 'whole note', 512], [1/2, 'half note', 256], [1/3, 'third note', [170, 170, 171]], [1/4, 'quarter note', 128], [1/5, 'fifth note', [51,51,51,51,52]], [1/6, 'sixth note', [85, 85, 86, 85, 85, 86]], [1/8, 'eighth note', 64], [1/10, 'tenth note', [25,26,26,25,26,25,26,26,25,26]], [1/12, 'twelfth note', [21,21,22, 21,21,22, 21,21,22, 21,21,22]], [1/16, 'sixteenth note', 32], [1/32, 'thirtysecond note', 16], ] export function midi_init() { WebMidi.enable(midi_ready) function midi_ready(err) { if (err) { console.error('webmidi failed to initialize') return } if (!WebMidi.outputs.length) { console.error('no MIDI output found') return } console.log(WebMidi.inputs) console.log(WebMidi.outputs) if (WebMidi.outputs.length > 1) { const filtered = WebMidi.outputs.filter(output => output.name.match(/prodipe/i)) if (filtered.length) { // midiDevice = filtered[0] } } // midiDevice = midiDevice || WebMidi.outputs[0] // console.log(midiDevice.name) } } /* play a single note */ export function play_note(index, duration, channel="all", exporting=false, defer=0){ // console.log(index) const scale = scales.current() const freq = scale.index(index + Math.round(nx.offset.value), nx.octave.value) let midi_note = ftom(freq) let cents = midi_note % 1 if (cents > 0.5) { midi_note += 1 cents -= 1 } cents *= 2 midi_note = Math.floor(midi_note) if ((midiDevice || exporting) && midi_note > 127) return 0 const note = Tone.Frequency(Math.floor(midi_note), "midi").toNote() const defer_time = 30000 / Tone.Transport.bpm.value * defer / 128 if (exporting) { return note } console.log('defer', defer, defer_time) if (midiDevice) { duration = duration || 60000 / Tone.Transport.bpm.value if (! exporting) { if (defer) { setTimeout(() => { play_midi_note(note, cents, channel, duration) }, defer) } else { play_midi_note(note, cents, channel, duration) } } } else if (defer) { setTimeout(() => { kalimba.play(freq) }, defer_time) } else { kalimba.play(freq) } return note } export function play_midi_note(note, cents, channel, duration) { midiDevice.playNote(note, channel, { duration }) if (sendPitchBend) { midiDevice.sendPitchBend(cents, channel) } } /* play the next note in sequence */ export function play_sequence(i, bounds, diff, note_time, channel="all", exporting) { const { rows, min, max } = bounds const count = rows.length * rows[0].length if (i >= count) i = 0 const y = Math.floor(i / rows[0].length) const x = i % rows[0].length // if (!x) console.log(y) const n = rows[y][x] i += 1 if (i >= count) i = 0 const midi_note = play_note( norm(n, min, max) * nx.multiply.value, note_time, channel, exporting) return [i, [midi_note], [128]] } // [next_i, notes, timings, pedal_note] /* play the next row as an interval */ export function play_interval_sequence(i, bounds, diff, note_time, channel="all", exporting) { const { rows, min, max } = bounds const count = rows.length if (i >= count) i = 0 const y = i % count const row = rows[y] if (! row) { i = 0; return } const row_min = Math.min.apply(Math, row) // const row_max = Math.max.apply(Math, row) const row_f0 = norm(row_min, min, max) const row_root = row_f0 * nx.multiply.value const notes = row.map(n => { const note = row_root + norm(n - row_min, diff.min, diff.max) * nx.interval.value play_note(note, note_time, channel, exporting) }) i += 1 return [i, notes] } /* generate a 1-track midi file by calling the play function repeatedly */ export function export_pattern_as_midi(dataset, bounds, diff, tempo, timingIndex, play_fn, max_i) { // const behavior = document.querySelector('#behavior').value const { rows } = bounds // let count = behavior === 'sequence' ? rows[0].length * rows.length : rows.length max_i = max_i || rows[0].length let notes, timings let note_time // let timing = note_values[timingIndex][2] let pedal_note, pedal_track, next_i let wait = 0 let midi_track = new MidiWriter.Track() midi_track.setTempo(tempo) if (dataset.pedal) { pedal_track = new MidiWriter.Track() pedal_track.setTempo(tempo) } for (let i = 0, len = max_i; i < len; i++) { [next_i, notes, timings, pedal_note] = play_fn(i, bounds, diff, note_time, "all", true) // if (timing.length) { // note_time = timing[i % timing.length] // } else { // note_time = timing // } // midi_track.addEvent(new MidiWriter.NoteEvent({ pitch: notes, duration: 't' + note_time })) // console.log(i, notes, timings) if (!notes.length) wait += 128 for (let j = 0; j < notes.length; j++) { console.log(i, j, notes[j], timings[j], wait, pedal_note) let e = { pitch: notes[j], duration: 't' + timings[j], velocity: 50, } if (wait) { e.wait = 't' + wait } midi_track.addEvent(new MidiWriter.NoteEvent(e)) wait = 0 } if (dataset.pedal) { pedal_track.addEvent(new MidiWriter.NoteEvent({ pitch: pedal_note, duration: 't128', velocity: 25, })) } } let tracks = dataset.pedal ? [midi_track, pedal_track] : [midi_track] const writer = new MidiWriter.Writer(tracks) const blob = dataURItoBlob(writer.dataUri()) saveAs(blob, 'Recording - ' + dataset.name + '.mid') }