From 749662bf35f956e93dc6bb6ec1c53e6b1171b4b8 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Tue, 14 Aug 2018 21:31:46 +0200 Subject: record midiiiiii --- client/index.js | 173 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 114 insertions(+), 59 deletions(-) (limited to 'client/index.js') diff --git a/client/index.js b/client/index.js index 3e0e171..9db4e32 100644 --- a/client/index.js +++ b/client/index.js @@ -4,35 +4,40 @@ import Nexus from 'nexusui' import keys from './lib/keys' import kalimba from './lib/kalimba' import scales from './lib/scales' -import { mod, browser, requestAudioContext, ftom, mtof, tap } from './lib/util' +import { requestAudioContext, ftom, dataURItoBlob } from './lib/util' +import { saveAs } from 'file-saver/FileSaver' import * as data from './data' +const MidiWriter = require('midi-writer-js') + const DEFAULT_BPM = 60 const nx = window.nx = {} let midi +let exporting = false +let recording = false +let recorder = null + const note_values = [ - [8, '8 measures'], - [4, '4 measures'], - [2, '2 measures'], - [1, 'whole note'], - [1/2, 'half note'], - [1/3, 'third note'], - [1/4, 'quarter note'], - [1/5, 'fifth note'], - [1/6, 'sixth note'], - [1/8, 'eighth note'], - [1/10, 'tenth note'], - [1/12, 'twelfth note'], - [1/16, 'sixteenth note'], - [1/32, 'thirtysecond note'], + [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], ] - WebMidi.enable(midi_ready) - function midi_ready(err) { if (err) { console.error('webmidi failed to initialize') @@ -53,15 +58,17 @@ function midi_ready(err) { midi = midi || WebMidi.outputs[0] console.log(midi.name) } + let i = 0, datasets = {}, dataset = {}, bounds = {}, diff = [] let play_fn = play_sequence data.load().then(lists => { // nx.dataset.choices = Object.keys(lists) console.log(lists) datasets = lists - requestAudioContext(ready) pick_dataset('housing costs and income inequality') + requestAudioContext(ready) }) + function pick_dataset(key){ console.log('pick dataset:', key) i = 0 @@ -74,45 +81,49 @@ var behaviors = { interval: { name: 'Intervals', fn: play_interval_sequence }, } function pick_behavior(name){ - behaviors[name].fn() + play_fn = behaviors[name].fn } function play_next(){ - let note_time = 120000 / Tone.Transport.bpm.value * note_values[nx.timing.active][0] + let note_time = 120000 / Tone.Transport.bpm.value * note_values[nx.timing.active][0] * nx.duration.value setTimeout(play_next, note_time) - play_fn(note_time) -} -function play_sequence(){ - play_fn = (note_time) => { - 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; - play( norm(n, min, max) * nx.multiply.value, note_time * nx.duration.value) + let [new_i, notes] = play_fn(i, note_time) + i = new_i + if (recording) { + let timing = note_values[nx.timing.active][2] + if (timing.length) timing = timing[i % timing.length] + recorder.addEvent(new MidiWriter.NoteEvent({ pitch: notes, duration: 't' + timing })) } } -function play_interval_sequence(){ - play_fn = (note_time) => { - 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 - row.forEach(n => { - const note = row_root + norm(n - row_min, diff.min, diff.max) * nx.interval.value - play(note, note_time * nx.duration.value) - }) - i += 1 - } +function play_sequence(i, note_time) { + 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( norm(n, min, max) * nx.multiply.value, note_time) + return [i, [midi_note]] +} +function play_interval_sequence(i, note_time) { + 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_time) + }) + i += 1 + return [i, notes] } function norm(n, min, max){ @@ -156,16 +167,18 @@ function play(index, duration){ } cents *= 2 midi_note = Math.floor(midi_note) - if (midi) { - if (midi_note > 127) return - const note = Tone.Frequency(Math.floor(midi_note), "midi").toNote() + if ((midi || exporting) && midi_note > 127) return 0 + const note = Tone.Frequency(Math.floor(midi_note), "midi").toNote() + if (exporting || midi) { duration = duration || 60000 / Tone.Transport.bpm.value - midi.playNote(note, "all", { duration }) - // cents - // midi.sendPitchBend(cents, "all") + if (! exporting) { + midi.playNote(note, "all", { duration }) + // midi.sendPitchBend(cents, "all") + } } else { kalimba.play(freq) } + return note } function update_value_on_change(el, id, is_int, fn) { @@ -195,7 +208,7 @@ function update_radio_value_on_change(el, id, values, fn) { el.update = update } function build_options(el, lists, fn) { - Object.keys(lists).forEach( (key, i) => { + Object.keys(lists).forEach(key => { const list = lists[key] const option = document.createElement('option') option.innerHTML = list.name @@ -268,9 +281,51 @@ function ready () { }) update_value_on_change(nx.interval, '#interval', true) + const export_midi_button = document.querySelector('#export_midi') + export_midi_button.addEventListener('click', export_pattern_as_midi) + + const record_midi_button = document.querySelector('#record_midi') + record_midi_button.addEventListener('click', () => { + if (recording) { + record_midi_button.innerHTML = 'Record MIDI' + document.body.classList.remove('recording') + recording = false + const writer = new MidiWriter.Writer([recorder]) + const blob = dataURItoBlob(writer.dataUri()) + saveAs(blob, 'Recording - ' + dataset.name + '.mid') + } else { + record_midi_button.innerHTML = 'Save Recording' + document.body.classList.add('recording') + recording = true + recorder = new MidiWriter.Track() + recorder.setTempo(nx.tempo.value) + } + }) + document.querySelector('.loading').classList.remove('loading') play_next() } +function export_pattern_as_midi(){ + 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[nx.timing.active][2] + exporting = true + let midi_track = new MidiWriter.Track() + midi_track.setTempo(nx.tempo.value) + for (let i = 0, len = count; i < len; i++) { + notes = play_fn(i)[1] + if (timing.length) note_time = 't' + timing[i % timing.length] + else note_time = timing + midi_track.addEvent(new MidiWriter.NoteEvent({ pitch: notes, duration: note_time })) + } + const writer = new MidiWriter.Writer([midi_track]) + const blob = dataURItoBlob(writer.dataUri()) + saveAs(blob, 'Recording - ' + dataset.name + '.mid') + exporting = false +} keys.listen(index => { nx.offset.value = index -- cgit v1.2.3-70-g09d2