summaryrefslogtreecommitdiff
path: root/client/index.js
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2018-10-07 03:16:22 +0200
committerJules Laplace <julescarbon@gmail.com>2018-10-07 03:16:22 +0200
commit5496464966ff34c848538d726819ed91119da1f2 (patch)
treef6fed075b6df3fb249f69118f25b743317ac1ac2 /client/index.js
parent852ed2e007deac47292d3e83a374070683c29894 (diff)
grep script
Diffstat (limited to 'client/index.js')
-rw-r--r--client/index.js384
1 files changed, 179 insertions, 205 deletions
diff --git a/client/index.js b/client/index.js
index e6f0716..af1886a 100644
--- a/client/index.js
+++ b/client/index.js
@@ -1,92 +1,188 @@
import Tone from 'tone'
-import WebMidi from 'webmidi'
import Nexus from 'nexusui'
+import { saveAs } from 'file-saver/FileSaver'
+
import keys from './lib/keys'
-import kalimba from './lib/kalimba'
import scales from './lib/scales'
-import { requestAudioContext, ftom, dataURItoBlob } from './lib/util'
-import { saveAs } from 'file-saver/FileSaver'
+import {
+ midi_init,
+ play_note,
+ play_sequence,
+ play_interval_sequence,
+ note_values,
+ MidiWriter,
+} 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
+} from './lib/ui'
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
+let recording = false
+let sendPitchBend = false
-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],
-]
+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) {
- // midi = filtered[0]
- }
- }
- // midi = midi || WebMidi.outputs[0]
- // console.log(midi.name)
-}
+/* initialization */
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
- pick_dataset('housing costs and income inequality')
+ datasets = lists.map(list => {
+ list.shift()
+ switch(list.name) {
+ // case 'gun_violence':
+ // return {
+ // ...list,
+ // lines: list.lines.map(line => {
+ // // 0 incident_id
+ // // 1 date
+ // // 2 state
+ // // 3 city_or_county
+ // // 4 address
+ // // 5 n_killed
+ // // 6 n_injured
+ // })
+ // }
+ // break
+ case 'gun_violence':
+ return gun_violence_melody(list)
+ case 'mass_shootings':
+ return {
+ ...list,
+ lines: list.lines.map(line => {
+ // 0 case name
+ // 1 location
+ // 2 date
+ // 3 summary
+ // 4 fatalities
+ // 5 injured
+ // 6 total_victims
+ // 7 location
+ // 8 age_of_shooter
+ // 9 prior_signs_mental_health_issues
+ // 10 mental_health_details
+ // 11 weapons_obtained_legally
+ // 12 where_obtained
+ // 13 weapon_type
+ // 14 weapon_details
+ // 15 race
+ // 16 gender
+ // 17 sources
+ // 18 mental_health_sources
+ // 19 sources_additional_age
+ // 20 latitude
+ // 21 longitude
+ // 22 type (Spree / Mass)
+ // 23 year
+ })
+ }
+ break
+ }
+ })
+ pick_dataset('mass shootings')
requestAudioContext(ready)
})
-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
+/*
+ 479363,
+ 2013-01-19,New Mexico,Albuquerque,2806 Long Lane,
+ 5,0,
+ http://www.gunviolencearchive.org/incident/479363,
+ http://hinterlandgazette.com/2013/01/pastor-greg-griego-identified-victims-killed-nehemiah-griego-albuquerque-nm-shooting.html,
+ False,1,
+
+ gun_stolen,
+ 0::Unknown||1::Unknown,
+
+ gun_type,
+ 0::22 LR||1::223 Rem [AR-15],
+
+ incident_characteristics,
+ "Shot - Dead (murder, accidental, suicide)
+ ||Mass Shooting (4+ victims injured or killed excluding the subject/suspect/perpetrator, one location)
+ ||Domestic Violence",
+
+ latitude, location_description, longitude,
+ 34.9791,,-106.716,
+
+ n_guns_involved, notes,
+ 2,,
+
+ participant_age,
+ 0::51||1::40||2::9||3::5||4::2||5::15,
+
+ participant_age_group,
+ 0::Adult 18+||1::Adult 18+||2::Child 0-11||3::Child 0-11||4::Child 0-11||5::Teen 12-17,
+
+ participant_gender,
+ 0::Male||1::Female||2::Male||3::Female||4::Female||5::Male,
+
+ participant_name,
+ 0::Greg Griego||1::Sara Griego||2::Zephania Griego||3::Jael Griego||4::Angelina Griego||5::Nehemiah Griego,
+
+ participant_relationship,
+ 5::Family,
+
+ participant_status,
+ "0::Killed||1::Killed||2::Killed||3::Killed||4::Killed||5::Unharmed, Arrested",
+
+ participant_type,
+ 0::Victim||1::Victim||2::Victim||3::Victim||4::Victim||5::Subject-Suspect,
+
+ http://www.cbsnews.com/news/nehemiah-gringo-case-memorial-service-planned-for-family-allegedly-slain-by-new-mexico-teen/||
+ http://www.thewire.com/national/2013/01/teenager-reportedly-used-ar-15-kill-five-new-mexico/61199/||
+ http://bigstory.ap.org/article/officials-nm-teen-gunman-kills-5-inside-home||
+ http://www.huffingtonpost.com/2013/01/21/nehemiah-griego-teen-shoots-parents-3-children_n_2519359.html||
+ http://murderpedia.org/male.G/g/griego-nehemiah.htm||
+ http://hinterlandgazette.com/2013/01/pastor-greg-griego-identified-victims-killed-nehemiah-griego-albuquerque-nm-shooting.html,
+ 10,14
+*/
+
+function gun_violence_melody(list){
+ let melody = []
+ let lookup = {}
+ let last = Date.now()
+ let last_y = 2018
+ let last_m = 3
+ list.lines.forEach(line => {
+ let [
+ incident_id, date, state, city_or_county, address, n_killed, n_injured,
+ incident_url, source_url, incident_url_fields_missing, congressional_district,
+ gun_stolen, gun_type, incident_characteristics, latitude, location_description, longitude,
+ n_guns_involved, notes,
+ participant_age, participant_age_group, participant_gender,
+ participant_name, participant_relationship, participant_status,
+ participant_type,
+ sources,
+ state_house_district, state_senate_district
+ ] = line
+ let [ y, m, d ] = date.split('-')
+ })
+ return {
+ ...list,
+ lines: melody,
+ }
}
+
+/* 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, note_time)
+ let [new_i, notes] = play_fn(i, bounds, note_time)
i = new_i
if (recording) {
let timing = note_values[nx.timing.active][2]
@@ -94,131 +190,26 @@ function play_next(){
recorder.addEvent(new MidiWriter.NoteEvent({ pitch: notes, duration: 't' + timing }))
}
}
-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){
- return (n - min) / (max - min)
-}
-function get_diff_bounds(rows){
- const diffs = rows.map(row => {
- const row_min = Math.min.apply(Math, row)
- const row_max = Math.max.apply(Math, row)
- return row_max - row_min
- })
- const min = Math.min.apply(Math, diffs)
- const max = Math.max.apply(Math, diffs)
- return { min, max }
-}
-function get_bounds(dataset){
- let rows = dataset.lines
- rows.forEach(row => row.shift())
- rows = rows.map(a => a.map(n => parseFloat(n)))
- const max = rows.reduce((a,b) => {
- return b.reduce((z,bb) => {
- return Math.max(z, bb)
- }, a)
- }, -Infinity)
- const min = rows.reduce((a,b) => {
- return b.reduce((z,bb) => {
- return Math.min(z, bb)
- }, a)
- }, Infinity)
- return { rows, max, min }
-}
-function play(index, duration){
- // 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 ((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
- if (! exporting) {
- midi.playNote(note, "all", { duration })
- midi.sendPitchBend(cents, "all")
- }
- } else {
- kalimba.play(freq)
- }
- return note
-}
+/* bind selects */
-function update_value_on_change(el, id, is_int, fn) {
- const label = document.querySelector(id + ' + .val')
- const update = v => {
- label.innerHTML = is_int ? parseInt(v) : v.toFixed(2)
- fn && fn(v)
- }
- el.on('change', update)
- update(el.value)
- el.update = update
+function pick_dataset(key){
+ console.log('pick dataset:', key)
+ i = 0
+ dataset = datasets[key]
+ bounds = get_bounds(dataset)
+ diff = get_diff_bounds(bounds.rows)
}
-function update_radio_value_on_change(el, id, values, fn) {
- let old_v = el.active
- const label = document.querySelector(id + ' + .val')
- const update = v => {
- if (v === -1) {
- v = el.active = old_v
- } else {
- old_v = v
- }
- label.innerHTML = values[v][1]
- fn && fn(v)
- }
- el.on('change', update)
- update(el.active)
- el.update = update
+var behaviors = {
+ sequence: { name: 'Sequence', fn: play_sequence },
+ interval: { name: 'Intervals', fn: play_interval_sequence },
}
-function build_options(el, lists, fn) {
- Object.keys(lists).forEach(key => {
- const list = lists[key]
- const option = document.createElement('option')
- option.innerHTML = list.name
- option.value = key
- el.appendChild(option)
- })
- el.addEventListener('input', function(e){
- fn(e.target.value)
- })
+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)
@@ -282,7 +273,9 @@ 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)
+ export_midi_button.addEventListener('click', () => {
+ export_pattern_as_midi(dataset.name, bounds, nx.tempo.value, nx.timing.active, play_fn)
+ })
const record_midi_button = document.querySelector('#record_midi')
record_midi_button.addEventListener('click', () => {
@@ -305,27 +298,8 @@ function ready () {
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 = 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 - ' + dataset.name + '.mid')
- exporting = false
-}
+
+/* keys */
keys.listen(index => {
nx.offset.value = index