From c7fdf5245a3a20c99e59877e87316d03761d12c6 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 5 Jun 2022 23:37:24 +0200 Subject: [PATCH] more flexible pianoroll --- packages/tone/draw.mjs | 23 ++++++------ packages/tone/pianoroll.mjs | 72 ++++++++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/packages/tone/draw.mjs b/packages/tone/draw.mjs index d894c547..c0af49b2 100644 --- a/packages/tone/draw.mjs +++ b/packages/tone/draw.mjs @@ -20,7 +20,7 @@ export const getDrawContext = (id = 'test-canvas') => { return canvas.getContext('2d'); }; -Pattern.prototype.draw = function (callback, cycleSpan, lookaheadCycles = 1) { +Pattern.prototype.draw = function (callback, { from, to, onQuery }) { if (window.strudelAnimation) { cancelAnimationFrame(window.strudelAnimation); } @@ -29,19 +29,22 @@ Pattern.prototype.draw = function (callback, cycleSpan, lookaheadCycles = 1) { events = []; const animate = (time) => { const t = Tone.getTransport().seconds; - if (cycleSpan) { - const currentCycle = Math.floor(t / cycleSpan); + if (from !== undefined && to !== undefined) { + const currentCycle = Math.floor(t); if (cycle !== currentCycle) { cycle = currentCycle; - const begin = currentCycle * cycleSpan; - const end = (currentCycle + lookaheadCycles) * cycleSpan; - events = this._asNumber(true) // true = silent error - .query(new State(new TimeSpan(begin, end))) - .filter(Boolean) - .filter((event) => event.part.begin.equals(event.whole.begin)); + const begin = currentCycle + from; + const end = currentCycle + to; + setTimeout(() => { + events = this._asNumber(true) // true = silent error + .query(new State(new TimeSpan(begin, end))) + .filter(Boolean) + .filter((event) => event.part.begin.equals(event.whole.begin)); + onQuery?.(events); + }, 0); } } - callback(ctx, events, t, cycleSpan, time); + callback(ctx, events, t, time); window.strudelAnimation = requestAnimationFrame(animate); }; requestAnimationFrame(animate); diff --git a/packages/tone/pianoroll.mjs b/packages/tone/pianoroll.mjs index dfb5e5cb..210979ec 100644 --- a/packages/tone/pianoroll.mjs +++ b/packages/tone/pianoroll.mjs @@ -7,38 +7,84 @@ This program is free software: you can redistribute it and/or modify it under th import { Pattern } from '@strudel.cycles/core'; Pattern.prototype.pianoroll = function ({ - timeframe = 10, + from = -2, + to = 2, + overscan = 1, inactive = '#C9E597', active = '#FFCA28', - background = '#2A3236', - maxMidi = 90, - minMidi = 0, + // background = '#2A3236', + background = 'transparent', + maxMidi, + minMidi, + timeframe: timeFrameProp, } = {}) { - const w = window.innerWidth; - const h = window.innerHeight; - const midiRange = maxMidi - minMidi + 1; - const height = h / midiRange; + if (timeFrameProp) { + console.warn('timeframe is deprecated! use from/to instead'); + from = 0; + to = timeFrameProp; + } + const ctx = getDrawContext(); + const w = ctx.canvas.width; + const h = ctx.canvas.height; + let midiRange, height; + const autorange = minMidi === undefined || maxMidi === undefined; + if (autorange && (minMidi !== undefined || maxMidi !== undefined)) { + console.warn('pianoroll: minMidi and maxMidi must both be set to have an effect!'); + } + if (!autorange) { + midiRange = maxMidi - minMidi + 1; + height = h / midiRange; + } + const timeframe = to - from; + const t2x = (t) => Math.round(((t - from) / timeframe) * w); + const playheadX = t2x(0); this.draw( (ctx, events, t) => { ctx.fillStyle = background; ctx.clearRect(0, 0, w, h); ctx.fillRect(0, 0, w, h); - events.forEach((event) => { + const inFrame = (event) => event.whole.begin >= 0 && event.whole.begin <= t + to && event.whole.end >= t + from; + events.filter(inFrame).forEach((event) => { const isActive = event.whole.begin <= t && event.whole.end >= t; ctx.fillStyle = event.context?.color || inactive; ctx.strokeStyle = event.context?.color || active; ctx.globalAlpha = event.context.velocity ?? 1; - const x = Math.round((event.whole.begin / timeframe) * w); + ctx.beginPath(); + ctx.moveTo(playheadX, 0); + ctx.lineTo(playheadX, h); + ctx.stroke(); + const x = t2x(event.whole.begin); const width = Math.round(((event.whole.end - event.whole.begin) / timeframe) * w); - const y = Math.round(h - ((Number(event.value) - minMidi) / midiRange) * h); + const y = Math.round(h - ((Number(event.value) - minMidi + 1) / midiRange) * h); const offset = (t / timeframe) * w; const margin = 0; const coords = [x - offset + margin + 1, y + 1, width - 2, height - 2]; isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords); }); }, - timeframe, - 2, // lookaheadCycles + { + from: from - overscan, + to: to + overscan, + onQuery: (events) => { + if (autorange) { + const getValue = (e) => Number(e.value); + const { min, max } = events.reduce( + ({ min, max }, e) => { + const v = getValue(e); + return { + min: v < min ? v : min, + max: v > max ? v : max, + }; + }, + { min: Infinity, max: -Infinity }, + ); + minMidi = min; + maxMidi = max; + midiRange = maxMidi - minMidi + 1; + height = h / midiRange; + } + }, + }, ); return this; };