summaryrefslogtreecommitdiff
path: root/client/lib/midi.js
diff options
context:
space:
mode:
Diffstat (limited to 'client/lib/midi.js')
-rw-r--r--client/lib/midi.js142
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')
+}