Published
Edited
Sep 26, 2021
Insert cell
md`# Piano transcription tools`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof playbackrate = slider({title: 'Playback Rate', value: 1, min: 0.01, max: 1})
Insert cell
viewof n_bins = select({value: 8192, options: [...Array(5)].map((_, i) => 2**(11 + i))})
Insert cell
mutable peaks_hist = [[]]
Insert cell
mutable peaks = []
Insert cell
viewof autoloop = {
const el = select({title: 'Auto-loop', options: [ 'yes', 'no'], value: 'yes'})
return el
}
Insert cell
graph_freq_anon = {
const canvasCtx = canvas.getContext('2d');
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
// driven by data
data
function draw() {

//Draw gray background
canvasCtx.fillStyle = 'rgb(110, 110, 110)';
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);

//Draw spectrum
const barWidth = (width / 88);
let posX = 0;
const max_fft_height = -80
canvasCtx.fillStyle = 'rgb(255, 110, 110)';
canvasCtx.fillRect(0, canvas.height - ((sensitivity + 90) / 250 * canvas.height * 3), canvas.width, 2)
const key_height = 80
for (let i = 1; i <= 88; i++) {
let f = (27.5*2**((i-1)/12))
let name = key_name(f)
let color = name && name.includes('#') ? 'black' : 'white'
canvasCtx.fillStyle = color == 'white' ? 'rgb(255, 255, 255)' : 'rgb(0, 0, 0)'
canvasCtx.fillRect((i-1) * barWidth, 0, barWidth, key_height)
// key dividers
canvasCtx.fillStyle = 'rgb(110, 110, 110)'
canvasCtx.fillRect((i-1) * barWidth, 0, 1, key_height)
}
for (let i = 0; i < analyser.frequencyBinCount; i++) {
let f = i * (context.sampleRate /2) / analyser.frequencyBinCount
let name = key_name(f)
let color = name && name.includes('#') ? 'black' : 'white'
const barHeight = (data[i] == 0 ? 0 : data[i] + 90) * 7;
// if (!mutable peaks.includes(f) && mutable peaks.map(key_name).includes(name)) {
// continue;
// }
if (!mutable peaks.includes(f)) {

canvasCtx.fillStyle = 'rgb(255, 0, 255)'
canvasCtx.fillRect(key_n(f) * barWidth, canvas.height-barHeight, barWidth, barHeight)
continue;
}
canvasCtx.fillStyle = 'rgb(255, 50, 50)';
// red bar
canvasCtx.fillRect(key_n(f) * barWidth, canvas.height - barHeight, barWidth, barHeight);
// red key
canvasCtx.fillRect(key_n(f) * barWidth, 0, barWidth, key_height)
//posX += barWidth + 1;
}
};

draw();
}
Insert cell
mutable file = null
Insert cell
mutable jump_size = 1
Insert cell
mutable start = 0
Insert cell
filter = {
const f1 = context.createBiquadFilter()
f1.type = 'lowpass'
f1.frequency.setValueAtTime(100, context.currentTime)
const f2 = context.createBiquadFilter()
f1.type = 'lowshelf'
f2.frequency.setValueAtTime(240, context.currentTime)
f2.gain.setValueAtTime(40, context.currentTime)
return f1.connect(f2)
}
Insert cell
mutable on = false
Insert cell
pause = () => {
mutable on = false
mutable looping = false
}
Insert cell
play = () => {
mutable on = true
mutable looping = true
if (a.currentTime >= end) a.currentTime = start
}
Insert cell
handle_on_change_anon = {
on ? (context.resume(), a.play()) : a.pause()
}
Insert cell
mutable end = 5
Insert cell
mutable key_pressed = true
Insert cell
{
a.playbackRate = playbackrate
}
Insert cell
a = {
filter
const el = html`<audio loop preload="metadata" controls>
<source src="${url}" type="audio/ogg">
Your browser does not support the audio element.
</audio>`
el.crossOrigin = 'anonymous'
await new Promise(resolve => el.onloadedmetadata = resolve)
return el
}
Insert cell
context = new AudioContext()
Insert cell
analyser = {
output
const ret = context.createAnalyser()
output.connect(ret)
ret.fftSize = n_bins//4096//2048
return ret
}
Insert cell
data = {
const data = new Float32Array(analyser.frequencyBinCount)
while(true) {
if (!mutable on) {
yield data
continue
}
analyser.getFloatFrequencyData(data)
let data_max = data.reduce((acc, p) => p > acc ? p : acc, -9999999)
let all_peaks = [...data].map((p, i) => data[i-1] < p && data[i+1] < p ? p : undefined).filter(p=>p)
mutable default_sensitivity = data_max - Math.abs(data_max) *.2
mutable peaks = [...data].map((p, i) => p > sensitivity && data[i-1] < p && data[i+1] < p ? i * (context.sampleRate /2) / analyser.frequencyBinCount : undefined).filter(p => p)
if (JSON.stringify(mutable peaks.map(key_name)) != JSON.stringify(mutable peaks_hist[0].map(key_name)) && mutable on)
mutable peaks_hist = [mutable peaks.map(key_name), ...mutable peaks_hist].slice(0, 10)
yield data
}
}
Insert cell
mutable default_sensitivity = -41
Insert cell
mutable temp = 0
Insert cell
key_name(1119)
Insert cell
key_n(440)
Insert cell
key_n = f => Math.round(49 + 12 *Math.log2(f/440))
Insert cell
freq = n => 2**((n - 49)/12) * 440
Insert cell
freq(3)
Insert cell
names = ["A", "A#/Bb", "B", "C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab"]
Insert cell
[...Array(88)].map((_, i) => names[i % names.length] + (Math.floor(i / names.length) + 1).toString())
Insert cell
key_name = f => {
let n = Math.round(49 + 12 * Math.log2(f/440))
const names = ["A", "A#/Bb", "B", "C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab"]
const all_names = [...Array(88)].map((_, i) => names[i % names.length] + (Math.floor(i / names.length) + 1).toString())
return all_names[n - 2]
}
Insert cell
2048 * (context.sampleRate /2) / analyser.frequencyBinCount
Insert cell
source = context.createMediaElementSource(a)
Insert cell
output = {
source.disconnect()
if (filter_treble === 'yes') {
const ret = source.connect(filter);
filter.connect(context.destination);
return ret
} else {
source.connect(context.destination)
return source
}
}
Insert cell
play_on_page_refresh = a.play() // keep playing on page refresh
Insert cell
style = html`<style>
input[type=range], .range-slider {
width: -webkit-fill-available
}
</style>`
Insert cell
mutable looping = true
Insert cell
handle_autoloop_anon = { // handle auto-looping
while (true) {
if (a.currentTime < start || a.currentTime > end + .2) {
a.currentTime = start
mutable on = true
}
if (a.currentTime > end && !a.paused) {
a.pause()
mutable looping = autoloop == 'yes'
setTimeout(() => {
console.log('looping')
if (mutable looping && mutable on && a.currentTime >= end) {
a.currentTime = start
a.play()
mutable looping = false
}
}, ms_between_loops)
}
yield
}
}
Insert cell
url = file ? 'data:audio/mp3;' + (await Files.url(file)).split(';')[1] : FileAttachment("海が見える街 (Jazz Version)-palWMnxi4GU.mp3").url()//'https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3'
Insert cell
import {slider, button, select, file as file_input} from "@jashkenas/inputs"
Insert cell
import {rangeSlider} from '@mootari/range-slider'
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more