import Tone from 'tone' import keys from './lib/keys' import kalimba from './lib/kalimba' import colundi from './lib/colundi' import { mod, browser, requestAudioContext } from './lib/util' import { Grid, Views, HexFactory } from 'honeycomb-grid' const gridSize = browser.isMobile ? 25 : 50 const hexagon = polygon(6, gridSize) const hertz_el = document.querySelector('#hertz') const grid = Grid({ size: gridSize, }) const Hex = HexFactory({ size: gridSize, template: function(hex) { return ` ` } }) const container = document.querySelector('#container') const origin = { x: container.offsetWidth / 2 - hexagon.width / 2, y: container.offsetHeight / 2 - hexagon.height / 2, } const view = Views.DOM({ container: container, origin: origin, }) let last = null, dragging = false let hexes, els = [], order = [] requestAudioContext(init) function init () { if (browser.isMobile) { document.addEventListener('touchstart', touch(down)) document.addEventListener('touchmove', touch(move, true)) document.addEventListener('touchend', touch(up)) } else { document.addEventListener('mousedown', down) document.addEventListener('mousemove', move) document.addEventListener('mouseup', up) } document.querySelector('#frequency').addEventListener('click', sortByFrequency) document.querySelector('#color').addEventListener('click', sortByColor) document.querySelector('#brightness').addEventListener('click', sortByBrightness) build(colundi.list) keys.listen(function(index){ index += 20 const freq = colundi.index(index) kalimba.play(freq) }) } function sortByBrightness () { els.sort((a,b) => a.lum>b.lum?-1:a.lum==b.lum?0:1).map(resort) } function sortByColor () { els.sort((a,b) => a.hue a.freq>b.freq?-1:a.freq==b.freq?0:1).map(resort) } function resort (tuple,i) { const position = order[i] hexes[position.id] = tuple tuple.el.style.top = position.top tuple.el.style.left = position.left } function build (list) { container.innerHTML = '' let perimeter = [ Hex(0, 0, 0) ] hexes = {} for (let i = 0, len = list.length; i < len;) { const hex = perimeter.shift() const id = hexToString(hex) if (hexes[id]) continue let pair = list[len - i - 1] const neighbors = [0,1,2,3,4,5].map((j) => Hex.neighbor(hex, j)) perimeter = perimeter.concat(neighbors) view.renderHexes([ hex ]) const el = container.lastChild el.querySelector('polygon').setAttribute('fill', pair.color) const hsl = rgbStringToHSL(pair.color) const tuple = { el: el, freq: pair.freq, hue: (hsl[0]+0.2) % 1, lum: hsl[2] } hexes[id] = tuple els.push(tuple) order.push({ id: id, top: el.style.top, left: el.style.left }) i++ } } function down (e){ dragging = true const hex = grid.pointToHex({ x: e.pageX - origin.x - hexagon.width/2, y: e.pageY - origin.y - hexagon.height/2 }) const pair = find(hex) if (! pair) return pair.el.style.opacity = 0.5 hertz_el.innerHTML = pair.freq kalimba.play( pair.freq ) last = pair } function move (e){ if (! dragging) return const hex = grid.pointToHex({ x: e.pageX - origin.x - hexagon.width/2, y: e.pageY - origin.y - hexagon.height/2 }) const pair = find(hex) if (last === pair) return if (last) last.el.style.opacity = 1 if (! pair) return pair.el.style.opacity = 0.5 hertz_el.innerHTML = pair.freq kalimba.play( pair.freq ) last = pair } function up (e){ if (last) last.el.style.opacity = 1 last = null hertz_el.innerHTML = '' dragging = false } function find(hex){ const id = hexToString(hex) if (id in hexes) return hexes[id] return null } function touch(fn, shouldPreventDefault){ return (e) => { shouldPreventDefault && e.preventDefault() fn(e.touches[0]) } } function polygon(n, side){ const points = [] const xs = [], ys = [] let min_x = Infinity, max_x = -Infinity let min_y = Infinity, max_y = -Infinity let x, y for (let i = 0; i < n; i++) { x = side * Math.cos(Math.PI/n * (1 + 2*i)) y = side * Math.sin(Math.PI/n * (1 + 2*i)) min_x = Math.min(min_x, x) max_x = Math.max(max_x, x) min_y = Math.min(min_y, y) max_y = Math.max(max_y, y) xs.push(x) ys.push(y) } for (let i = 0; i < n; i++) { points.push(xs[i] - min_x, ys[i] - min_y) } return { points: points, pointString: points.map(p => p.toFixed(3)).join(' '), width: max_x - min_x, height: max_y - min_y } } function hexToString(hex) { return [hex.x, hex.y, hex.z].join('_') } function rgbStringToHSL(s) { const RGB = s.replace('rgb(','').replace(')','').split(', ').map((a) => parseInt(a)) var R = RGB[0] var G = RGB[1] var B = RGB[2] var var_R = ( R / 255 ) //RGB from 0 to 255 var var_G = ( G / 255 ) var var_B = ( B / 255 ) var var_Min = Math.min( var_R, var_G, var_B ) //Min. value of RGB var var_Max = Math.max( var_R, var_G, var_B ) //Max. value of RGB var del_Max = var_Max - var_Min //Delta RGB value var H, S var L = ( var_Max + var_Min ) / 2 if ( del_Max == 0 ) //This is a gray, no chroma... { H = 0 //HSL results from 0 to 1 S = 0 } else //Chromatic data... { if ( L < 0.5 ) S = del_Max / ( var_Max + var_Min ) else S = del_Max / ( 2 - var_Max - var_Min ) var del_R = ( ( ( var_Max - var_R ) / 6 ) + ( del_Max / 2 ) ) / del_Max var del_G = ( ( ( var_Max - var_G ) / 6 ) + ( del_Max / 2 ) ) / del_Max var del_B = ( ( ( var_Max - var_B ) / 6 ) + ( del_Max / 2 ) ) / del_Max if ( var_R == var_Max ) H = del_B - del_G else if ( var_G == var_Max ) H = ( 1 / 3 ) + del_R - del_B else if ( var_B == var_Max ) H = ( 2 / 3 ) + del_G - del_R if ( H < 0 ) H += 1 if ( H > 1 ) H -= 1 } return [H,S,L] }