import Tone from 'tone' import Nexus from 'nexusui' import { saveAs } from 'file-saver/FileSaver' import keys from './lib/keys' import scales from './lib/scales' import { midi_init, play_note, play_sequence, play_interval_sequence, export_pattern_as_midi, note_values, MidiWriter, transpose, } from './lib/midi' import { requestAudioContext, ftom, norm, dataURItoBlob, get_bounds, get_diff_bounds, } from './lib/util' import { update_value_on_change, update_radio_value_on_change, build_options, nx } from './lib/ui' import * as data from './data' const DEFAULT_BPM = 60 let recorder = null let recording = false midi_init() /* initialization */ let i = 0, datasets = {}, dataset = {}, bounds = {}, diff = [] let play_fn = play_sequence data.load().then(lists => { // pick_dataset('mass shootings') // requestAudioContext(ready) console.log(lists) transpose(lists.gun_violence_by_month.lines) }) // /* play next note according to sonification */ function play_next(){ let note_time = 120000 / Tone.Transport.bpm.value * note_values[nx.timing.active][0] * nx.duration.value setTimeout(play_next, note_time) let [new_i, notes] = play_fn(i, bounds, diff, 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 })) } } /* bind selects */ function pick_dataset(key){ console.log('pick dataset:', key) i = 0 dataset = datasets[key] bounds = get_bounds(dataset) diff = get_diff_bounds(bounds.rows) } var behaviors = { sequence: { name: 'Sequence', fn: play_sequence }, interval: { name: 'Intervals', fn: play_interval_sequence }, } function pick_behavior(name){ play_fn = behaviors[name].fn } /* build and bind the UI */ function ready() { scales.build_options(document.querySelector('#scale')) build_options(document.querySelector('#dataset'), datasets, pick_dataset) build_options(document.querySelector('#behavior'), behaviors, pick_behavior) // nx.colorize('#f4d142') Tone.Transport.bpm.value = DEFAULT_BPM nx.tempo = new Nexus.Dial('#tempo', { min: 10, max: 300, step: 1, value: DEFAULT_BPM, }) update_value_on_change(nx.tempo, '#tempo', true, v => Tone.Transport.bpm.value = v) nx.timing = new Nexus.RadioButton('#timing', { size: [400,25], numberOfButtons: note_values.length, active: 6, }) update_radio_value_on_change(nx.timing, '#timing', note_values) nx.duration = new Nexus.Dial('#duration', { min: 0, max: 2, step: 0.01, value: 0.8, }) update_value_on_change(nx.duration, '#duration', false) nx.offset = new Nexus.Dial('#offset', { min: -24, max: 24, step: 1, value: 0, }) update_value_on_change(nx.offset, '#offset', true) nx.octave = new Nexus.Dial('#octave', { min: -4, max: 4, step: 1, value: 0, }) update_value_on_change(nx.octave, '#octave', true) nx.multiply = new Nexus.Dial('#multiply', { min: -64, max: 64, step: 1, value: 7, }) update_value_on_change(nx.multiply, '#multiply', true) nx.interval = new Nexus.Dial('#interval', { min: -64, max: 64, step: 1, value: 10, }) 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(dataset.name, bounds, diff, nx.tempo.value, nx.timing.active, play_fn) }) 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() } /* keys */ keys.listen(index => { nx.offset.value = index nx.offset.update(index) })