Public
Edited
Sep 21, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
instrument = xylo
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
channelDataToAudioEl(data, sampleRate)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
if (renderBtn === 0) return;
const notes = 16;
const noteOffsets = [0.5];
// const tempoChanges = d3.range(-0.06, 0.07, 0.02);
const tempoChanges = [0.01];
// const wrongNoteIndices = d3.range(1, notes - 2, 2);
const wrongNoteIndices = [3];
const noteDeviations = d3.range(-0.05, 0.06, 0.02);

const imageWidth = 1000;
const imageHeight = 100;
const maxTimeDrawn = notes * d3.max(noteOffsets) + 5;

const zip = new jszip.default();

const count =
noteOffsets.length *
tempoChanges.length *
wrongNoteIndices.length *
noteDeviations.length;
console.log(`rendering ${count} samples`);
let current = 1;

for (const noteOffset of noteOffsets) {
for (const tempoChange of tempoChanges) {
for (const wrongNoteIndex of wrongNoteIndices) {
for (const noteDeviation of noteDeviations) {
if (current % 50 === 0 || current === count)
console.log(`rendering ${current} / ${count}`);
current++;
const name = `n-${notes}_no-${noteOffset.toFixed(
3
)}_tc-${tempoChange.toFixed(
3
)}_wni-${wrongNoteIndex}_nd-${noteDeviation.toFixed(3)}`;
// pattern
const pattern = generatePattern(
notes,
noteOffset,
tempoChange,
wrongNoteIndex,
noteDeviation
);
// render audio
const data = simulate(instrument, pattern);
const buffer = new AudioBuffer({ sampleRate, length: data.length });
buffer.copyToChannel(data, 0);
const blob = new Blob([audioBufferToWav.default(buffer)], {
type: "audio/wav"
});
zip.file(`${name}.wav`, blob);
// render tick image
// use fixed seconds/pixel
const canvas = document.createElement("canvas");
canvas.width = imageWidth;
canvas.height = imageHeight;
const context = canvas.getContext("2d");
context.fillStyle = "white";
context.fillRect(0, 0, imageWidth, imageHeight);
context.fillStyle = "black";
const scaleX = d3
.scaleLinear()
.domain([0, maxTimeDrawn])
.range([10, imageWidth - 10]);
for (const note of pattern) {
context.fillRect(scaleX(note), 0, 2, imageHeight);
}
const imgBlob = await new Promise((resolve) =>
canvas.toBlob(resolve)
);
zip.file(`${name}_ticks.png`, imgBlob);
// render waveform
context.fillStyle = "white";
context.fillRect(0, 0, imageWidth, imageHeight);
context.strokeStyle = "black";
const max = d3.max(data.map((d) => Math.abs(d)));
const scaleY = d3
.scaleLinear()
.domain([max, -max])
.range([0, imageHeight]);
const line = d3
.line()
.x((d, i) => scaleX(i / sampleRate))
.y((d) => scaleY(d))
.context(context);
context.beginPath();
line(data);
context.stroke();
const imgBlob2 = await new Promise((resolve) =>
canvas.toBlob(resolve)
);
zip.file(`${name}_wave.png`, imgBlob2);
}
}
}
}

// download
zip.generateAsync({ type: "blob" }).then((content) => {
// see FileSaver.js
fileSaver.default(content, "example.zip");
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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