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]
}