diff options
Diffstat (limited to 'client')
| -rw-r--r-- | client/data.js | 5 | ||||
| -rw-r--r-- | client/index.js | 85 | ||||
| -rw-r--r-- | client/lib/midi.js | 13 | ||||
| -rw-r--r-- | client/lib/ui.js | 2 | ||||
| -rw-r--r-- | client/lib/util.js | 46 |
5 files changed, 53 insertions, 98 deletions
diff --git a/client/data.js b/client/data.js index 733d3bf..1ddd664 100644 --- a/client/data.js +++ b/client/data.js @@ -1,6 +1,7 @@ const files = [ // "gun_violence", "mass_shootings", + "gun_violence_by_month", ] const parse = require('csv-parse') @@ -9,7 +10,7 @@ const dataPromises = files.map(name => { return rows.text() }).then(text => { return new Promise((resolve, reject) => { - parse(text, {}, (err, lines) => resolve(lines)) + parse(text, {}, (_, lines) => resolve(lines)) }) }).then(lines => { console.log(name, lines) @@ -24,7 +25,7 @@ const dataPromises = files.map(name => { const allPromises = Promise.all(dataPromises).then(data => { return data.reduce((a,b) => { console.log(b) - a[b.name.replace(/-/g, '_')] = b + a[b.name] = b return a }, {}) }) diff --git a/client/index.js b/client/index.js index 65b15d3..74c9252 100644 --- a/client/index.js +++ b/client/index.js @@ -9,8 +9,10 @@ import { play_note, play_sequence, play_interval_sequence, + export_pattern_as_midi, note_values, MidiWriter, + transpose, } from './lib/midi' import { requestAudioContext, ftom, norm, dataURItoBlob, @@ -19,18 +21,16 @@ import { import { update_value_on_change, update_radio_value_on_change, - build_options + build_options, + nx } from './lib/ui' import * as data from './data' const DEFAULT_BPM = 60 -const nx = window.nx = {} - let recorder = null let recording = false -let sendPitchBend = false midi_init() @@ -39,81 +39,20 @@ midi_init() let i = 0, datasets = {}, dataset = {}, bounds = {}, diff = [] let play_fn = play_sequence data.load().then(lists => { - console.log(lists) - datasets = lists.map(list => { - list.shift() - switch(list.name) { - 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) + // pick_dataset('mass shootings') + // requestAudioContext(ready) + console.log(lists) + transpose(lists.gun_violence_by_month.lines) }) -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, bounds, 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] @@ -141,7 +80,7 @@ function pick_behavior(name){ /* build and bind the UI */ -function ready () { +function ready() { scales.build_options(document.querySelector('#scale')) build_options(document.querySelector('#dataset'), datasets, pick_dataset) build_options(document.querySelector('#behavior'), behaviors, pick_behavior) @@ -205,7 +144,7 @@ function ready () { const export_midi_button = document.querySelector('#export_midi') export_midi_button.addEventListener('click', () => { - export_pattern_as_midi(dataset.name, bounds, nx.tempo.value, nx.timing.active, play_fn) + export_pattern_as_midi(dataset.name, bounds, diff, nx.tempo.value, nx.timing.active, play_fn) }) const record_midi_button = document.querySelector('#record_midi') diff --git a/client/lib/midi.js b/client/lib/midi.js index 05fd708..f2e0295 100644 --- a/client/lib/midi.js +++ b/client/lib/midi.js @@ -1,10 +1,13 @@ import Tone from 'tone' import WebMidi from 'webmidi' import scales from './scales' -import { ftom } from './util' +import { ftom, norm } from './util' import kalimba from './kalimba' +import { nx } from './ui' + let midiDevice +let sendPitchBend = false export const MidiWriter = require('midi-writer-js') @@ -81,7 +84,7 @@ export function play_note(index, duration, channel="all", exporting=false){ /* play the next note in sequence */ -function play_sequence(i, bounds, note_time, channel="all") { +export function play_sequence(i, bounds, diff, note_time, channel="all", exporting) { const { rows, min, max } = bounds const count = rows.length * rows[0].length if (i >= count) i = 0 @@ -97,7 +100,7 @@ function play_sequence(i, bounds, note_time, channel="all") { /* play the next row as an interval */ -function play_interval_sequence(i, bounds, note_time, channel="all") { +export function play_interval_sequence(i, bounds, diff, note_time, channel="all", exporting) { const { rows, min, max } = bounds const count = rows.length if (i >= count) i = 0 @@ -118,7 +121,7 @@ function play_interval_sequence(i, bounds, note_time, channel="all") { /* generate a 1-track midi file by calling the play function repeatedly */ -function export_pattern_as_midi(datasetName, bounds, tempo, timingIndex, play_fn) { +export function export_pattern_as_midi(datasetName, bounds, diff, tempo, timingIndex, play_fn) { const behavior = document.querySelector('#behavior').value const { rows } = bounds let count = behavior === 'sequence' ? rows[0].length * rows.length : rows.length @@ -128,7 +131,7 @@ function export_pattern_as_midi(datasetName, bounds, tempo, timingIndex, play_fn 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] + notes = play_fn(i, bounds, note_time, "all", true)[1] if (timing.length) { note_time = timing[i % timing.length] } else { diff --git a/client/lib/ui.js b/client/lib/ui.js index f344f0e..b0909cd 100644 --- a/client/lib/ui.js +++ b/client/lib/ui.js @@ -1,3 +1,5 @@ +export const nx = window.nx = {} + /* ui - update an int/float value */ export function update_value_on_change(el, id, is_int, fn) { diff --git a/client/lib/util.js b/client/lib/util.js index 0685b9d..f33146f 100644 --- a/client/lib/util.js +++ b/client/lib/util.js @@ -1,21 +1,21 @@ import Tone from 'tone' import StartAudioContext from './startAudioContext' -const isIphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) -const isIpad = (navigator.userAgent.match(/iPad/i)) -const isAndroid = (navigator.userAgent.match(/Android/i)) -const isMobile = isIphone || isIpad || isAndroid -const isDesktop = ! isMobile +export const isIphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) +export const isIpad = (navigator.userAgent.match(/iPad/i)) +export const isAndroid = (navigator.userAgent.match(/Android/i)) +export const isMobile = isIphone || isIpad || isAndroid +export const isDesktop = ! isMobile document.body.classList.add(isMobile ? 'mobile' : 'desktop') -const browser = { isIphone, isIpad, isMobile, isDesktop } +export const browser = { isIphone, isIpad, isMobile, isDesktop } -function choice (a){ return a[ Math.floor(Math.random() * a.length) ] } -function mod(n,m){ return n-(m * Math.floor(n/m)) } -function norm(n, min, max){ return (n - min) / (max - min) } +export function choice (a){ return a[ Math.floor(Math.random() * a.length) ] } +export function mod(n,m){ return n-(m * Math.floor(n/m)) } +export function norm(n, min, max){ return (n - min) / (max - min) } -function requestAudioContext (fn) { +export function requestAudioContext (fn) { if (isMobile) { const container = document.createElement('div') const button = document.createElement('div') @@ -55,7 +55,7 @@ function requestAudioContext (fn) { } } -function dataURItoBlob(dataURI) { +export function dataURItoBlob(dataURI) { // convert base64 to raw binary data held in a string // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this var byteString = atob(dataURI.split(',')[1]); @@ -79,14 +79,14 @@ function dataURItoBlob(dataURI) { return blob; } -function ftom(f) { +export function ftom(f) { // return (Math.log(f) - Math.log(261.626)) / Math.log(2) + 4.0 return 69 + 12 * Math.log2(f / 440) } -function mtof(m) { +export function mtof(m) { return 440 * Math.pow(2, (m - 69) / 12) } -function tap (fn) { +export function tap (fn) { return (e) => { if (browser.isMobile) fn() else if (e.press) fn() @@ -95,7 +95,7 @@ function tap (fn) { /* get minimum and maximum variance from row-to-row */ -function get_diff_bounds(rows){ +export 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) @@ -108,7 +108,7 @@ function get_diff_bounds(rows){ /* get minimum and maximum values from a dataset */ -function get_bounds(dataset){ +export function get_bounds(dataset){ let rows = dataset.lines // rows.forEach(row => row.shift()) rows = rows.map(a => a.map(n => parseFloat(n))) @@ -125,7 +125,17 @@ function get_bounds(dataset){ return { rows, max, min } } +/* transpose a 2D array */ - -export { choice, mod, norm, browser, get_bounds, get_diff_bounds, requestAudioContext, ftom, mtof, tap, dataURItoBlob } +export function transpose(a) { + let i_len = a.length, j_len = a[0].length + let T = new Array(i_len) + for (let i = 0; i < i_len; i++) { + T[i] = new Array(j_len) + for (var j = 0; j < j_len; j++) { + T[i][j] = a[j][i] + } + } + return T +} |
