import * as Tone from 'tone'; import { LTTB } from 'downsample'; import { XYDataPoint } from 'downsample/dist/types'; import { AudiografOptions } from './audiograf-options'; export class AudiografService { synth = new Tone.Synth().toMaster(); constructor() {} onPlaySound(data: number[], options: AudiografOptions) { this.onPlaySoundInternal(this.sampleData(data, options.samples), options); } private sampleData(data: number[], samples: number): number[] { if (data.length < samples) { return data; } const source = data.map((element, index) => { x: index, y: element }); const downsampled = LTTB(source, samples); return downsampled.map(d => d.y); } private onPlaySoundInternal(data: number[], options: AudiografOptions) { Tone.Transport.stop(); const notes = this.getNotes(data, options); const melody = new Tone.Sequence(this.setPlay.bind(this), notes, '8n'); melody.loop = 0; melody.start(0); Tone.Transport.start(); } private setPlay(time, note) { this.synth.triggerAttackRelease(note, '4n', time); } private getNotes(data: number[], options: AudiografOptions): string[] { const values = data.concat().sort((a, b) => a - b); const baseline = options.baseline ? options.baseline : this.getMedian(values); const min = options.min ? options.min : Math.min(...values); const max = options.max ? options.max : Math.max(...values); const normalization = Math.max(max - baseline, baseline - min); console.log(data); const notes = data.map(value => this.getNote(value, normalization, baseline)); console.log(notes); return notes; } getNote(value: number, range: number, median: number): string { const bucketSize = range / 12; const normalizedValue = value - median; const normalizedNote = Math.round(normalizedValue / bucketSize); switch (normalizedNote) { case -12: return 'C3'; case -11: return 'C#3'; case -10: return 'D3'; case -9: return 'D#3'; case -8: return 'E3'; case -7: return 'F3'; case -6: return 'F#3'; case -5: return 'G3'; case -4: return 'G#3'; case -3: return 'A3'; case -2: return 'A#3'; case -1: return 'B3'; case 0: return 'C4'; case 1: return 'C#4'; case 2: return 'D4'; case 3: return 'D#4'; case 4: return 'E4'; case 5: return 'F4'; case 6: return 'F#4'; case 7: return 'G4'; case 8: return 'G#4'; case 9: return 'A4'; case 10: return 'A#4'; case 11: return 'B4'; case 12: return 'C5'; default: return 'C6'; } } getMedian(values: number[]): number { const lowMiddle = Math.floor((values.length - 1) / 2); const highMiddle = Math.ceil((values.length - 1) / 2); return (values[lowMiddle] + values[highMiddle]) / 2; } }