Skip to content

Instantly share code, notes, and snippets.

@bewest
Last active February 20, 2020 20:10
Show Gist options
  • Save bewest/2c5699535f0e4bf1704f57ee0869e3a1 to your computer and use it in GitHub Desktop.
Save bewest/2c5699535f0e4bf1704f57ee0869e3a1 to your computer and use it in GitHub Desktop.
glucose sonification

Glucose Sonification

demo

code

Sonification is the use of non-speech audio to convey information or perceptualize data.[1] Auditory perception has advantages in temporal, spatial, amplitude, and frequency resolution that open possibilities as an alternative or complement to visualization techniques.

This crude version maps every glucose value to a pitch, and plays each glucose value as a 16th note at it's given pitch. The overal meter is 3/2 at 320 beats per minute. This allows 48 hours of glucose trends to be perceived in about 30 seconds. The highs and lows are clearly recognizable. When data is unavailable, a click is used to mark the passage of time instead of a pitch.

Next steps would include sound design on the tones generated. In addition, it would be interesting to map the tessitura of the listener to the euglycemic range, and add guiding reference tones. Providing guiding reference tones allows the distance in pitch between a glucose value and the reference tone allows the listener to determine the precice glucose value through intervalic analysis. For example, a glucose value that sounds in unison with a guiding tone set at 120 would be 120. A glucose value sounding an octave above the guiding tone would be 160, while an octave below would be 80. Guiding tones at the target range boundaries would inform most listeners as to whether glucose values are in target range and to what degree. Mapping the target range to the listener's vocal tessitura would allow the listener to easily identify healthy values by vocal reproduction.

> api = convert( )
{ [Function: api] invert: [Function: invert], freq: [Function: freq] }
> api(120)
220.00000000000006
> api.invert(440)
160
> api.invert(440/2)
120
> api.invert(440/2/2)
80
> api(80)
110.00000000000003
> api(120)
220.00000000000006
> api(160)
440.0000000000001

I allowed this to run every 5 minutes automatically in my apartment and started to get an automatic passive sense of my glucose without ever looking at any displays.

window.Nightscout.client.init(function loaded() {
/*
Many thanks to @ps2, Pete Schwamb
* https://gist.github.com/ps2/314145bb91fa720bba59cf58f7e9cad2
2**((numOctaves/(maxBG-minBG))*(bg-minBG) + Math.log2(minFreq))
*/
function convert (opts) {
opts = opts || { };
var octaves = opts.octaves || 9;
var maxBG = opts.maxBG || 400;
var minBG = opts.minBG || 40;
var minFreq = opts.minFreq || 55;
var x = minBG
, y = minFreq
, z = octaves/(maxBG - minBG)
;
function freq (bg) {
return Math.pow(2, (z* (bg - x ) ) + Math.log2(y)) ;
// return Math.pow(2, (z* (bg + x ) ) + Math.log2(y)) ;
}
function invert (freq) {
return ((Math.log2(freq) - Math.log2(y)) / z ) + x;
}
function api (glucose) {
return freq(glucose);
}
api.invert = invert;
api.freq = freq;
return api;
}
function createLoop (synth, sgvs) {
function callback (time, note) {
console.log(time, note);
synth.triggerAttackRelease(note, '16n', time);
}
var seq = new Tone.Sequence(callback, sgvs, '16n');
seq.loop = false;
return seq;
}
function glucose (sgv) {
if (sgv) {
return parseInt(sgv.mgdl || sgv.sgv || sgv.glucose || 35);
}
return 20;
}
$(document).ready(function ( ) {
console.log('OK');
var converter = convert( );
var synth = new Tone.PolySynth(16, Tone.MonoSynth);
// default volume always makes my ears bleed
synth.chain(new Tone.Volume(-26), Tone.Master);
// synth.toMaster();
Tone.Transport.timeSignature = [ 3, 2 ];
Tone.Transport.bpm.value = 320;
// function play_next (time) {
// var sgv = sgvs.shift( );
// console.log(sgv);
// if (!sgv) {
// loop.stop( );
// }
// if (sgv) {
// var freq = converter.freq(sgv.mgdl || 30);
// synth.triggerAttackRelease(parseInt(sgv.mgdl || sgv.sgv || sgv.glucose || 39) * 4, '8n', time);
// }
// }
// var loop = new Tone.Loop(play_next, '4n');
var loop;
function play_data ( ) {
var sgvs = Nightscout.client.sbx.data.sgvs.slice( ).map(glucose).map(converter.freq);
console.log('last two hours', sgvs.length);
var new_loop = createLoop(synth, sgvs);
if (loop) {
loop.stop( );
loop.dispose( );
loop = null;
}
loop = new_loop;
Nightscout.client.radio.loop = loop;
loop.start( );
}
Nightscout.client.radio = {
converter: converter
, synth: synth
, loop: loop
};
Nightscout.client.socket.on('dataUpdate', function ( ) {
play_data( );
});
$('#again').on('click', function ( ) {
play_data( );
});
Tone.Transport.start( );
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment