diff options
Diffstat (limited to 'client/lib/midi.js')
| -rw-r--r-- | client/lib/midi.js | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/client/lib/midi.js b/client/lib/midi.js new file mode 100644 index 0000000..05fd708 --- /dev/null +++ b/client/lib/midi.js @@ -0,0 +1,142 @@ +import Tone from 'tone' +import WebMidi from 'webmidi' +import scales from './scales' +import { ftom } from './util' +import kalimba from './kalimba' + +let midiDevice + +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){ + // 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() + if (exporting || midiDevice) { + duration = duration || 60000 / Tone.Transport.bpm.value + if (! exporting) { + midiDevice.playNote(note, channel, { duration }) + if (sendPitchBend) { + midiDevice.sendPitchBend(cents, channel) + } + } + } else { + kalimba.play(freq) + } + return note +} + +/* play the next note in sequence */ + +function play_sequence(i, bounds, note_time, channel="all") { + 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]] +} + +/* play the next row as an interval */ + +function play_interval_sequence(i, bounds, note_time, channel="all") { + 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 */ + +function export_pattern_as_midi(datasetName, bounds, tempo, timingIndex, play_fn) { + const behavior = document.querySelector('#behavior').value + const { rows } = bounds + let count = behavior === 'sequence' ? rows[0].length * rows.length : rows.length + let notes + let note_time + let timing = note_values[timingIndex][2] + let midi_track = new MidiWriter.Track() + midi_track.setTempo(tempo) + for (let i = 0, len = count; i < len; i++) { + notes = play_fn(i, bounds, exporting = true)[1] + 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 })) + } + const writer = new MidiWriter.Writer([midi_track]) + const blob = dataURItoBlob(writer.dataUri()) + saveAs(blob, 'Recording - ' + datasetName + '.mid') +} |
