mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-22 05:05:26 -04:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c9e4b16460 | |||
| 6350dfa31f | |||
| fe691adb28 | |||
| 187307c6bc |
@@ -127,6 +127,12 @@ registerWidget('_scope', (id, options = {}, pat) => {
|
|||||||
return pat.tag(id).scope({ ...options, ctx, id });
|
return pat.tag(id).scope({ ...options, ctx, id });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerWidget('_claviature', (id, options = {}, pat) => {
|
||||||
|
options = { height: 75, width: 640, ...options };
|
||||||
|
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||||
|
return pat.tag(id).claviature({ ...options, ctx, id });
|
||||||
|
});
|
||||||
|
|
||||||
registerWidget('_pitchwheel', (id, options = {}, pat) => {
|
registerWidget('_pitchwheel', (id, options = {}, pat) => {
|
||||||
let _size = options.size || 200;
|
let _size = options.size || 200;
|
||||||
options = { width: _size, height: _size, ...options, size: _size / 5 };
|
options = { width: _size, height: _size, ...options, size: _size / 5 };
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { Pattern, noteToMidi } from '@strudel/core';
|
||||||
|
|
||||||
|
const blackPattern = [0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0];
|
||||||
|
|
||||||
|
export const tokenizeNote = (note) => {
|
||||||
|
if (typeof note !== 'string') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bs]*)([0-9])?$/)?.slice(1) || [];
|
||||||
|
if (!pc) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [pc, acc, oct ? Number(oct) : undefined];
|
||||||
|
};
|
||||||
|
const accs = { '#': 1, b: -1, s: 1 };
|
||||||
|
const toMidi = (note) => {
|
||||||
|
if (typeof note === 'number') {
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
const [pc, acc, oct] = tokenizeNote(note);
|
||||||
|
if (!pc) {
|
||||||
|
throw new Error('not a note: "' + note + '"');
|
||||||
|
}
|
||||||
|
const chroma = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }[pc.toLowerCase()];
|
||||||
|
const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0;
|
||||||
|
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMidiKeys = (range, offset) => {
|
||||||
|
const white /* : number[] */ = [];
|
||||||
|
const black /* : number[] */ = [];
|
||||||
|
const to = noteToMidi(range[1]);
|
||||||
|
for (let i = offset; i <= to; i++) {
|
||||||
|
//
|
||||||
|
(blackPattern[i % 12] ? black : white).push(i);
|
||||||
|
}
|
||||||
|
return [white, black];
|
||||||
|
};
|
||||||
|
|
||||||
|
const whiteWidth = (midi, topWidth) => (midi % 12 > 4 ? 7 / 4 : 5 / 3) * topWidth;
|
||||||
|
|
||||||
|
const whiteX = (midi, offset, topWidth) =>
|
||||||
|
Array.from({ length: midi - offset }, (_, i) => i + offset).reduce(
|
||||||
|
(sum, m) => (!blackPattern[m % 12] ? sum + whiteWidth(m, topWidth) : sum),
|
||||||
|
0,
|
||||||
|
); // TODO: calculate mathematically
|
||||||
|
|
||||||
|
/* const blackX = (index, offset, topWidth) => {
|
||||||
|
const cDiff = 12 - (offset % 12);
|
||||||
|
console.log('cDiff', cDiff);
|
||||||
|
const cOffset = whiteX(cDiff + offset);
|
||||||
|
const blackOffset = cOffset + cDiff * topWidth;
|
||||||
|
return (index - offset) * topWidth + blackOffset;
|
||||||
|
}; */
|
||||||
|
|
||||||
|
const parseNote = (note) => (typeof note === 'number' ? note : toMidi(note));
|
||||||
|
|
||||||
|
export function claviature(haps, options) {
|
||||||
|
const {
|
||||||
|
ctx = getDrawContext(),
|
||||||
|
range = ['C1', 'D3'],
|
||||||
|
scaleX = 1,
|
||||||
|
scaleY = 1,
|
||||||
|
palette = [getTheme().foreground, getTheme().background],
|
||||||
|
strokeWidth = 0,
|
||||||
|
stroke = getTheme().foreground,
|
||||||
|
upperWidth = 14,
|
||||||
|
upperHeight = 100,
|
||||||
|
lowerHeight = 45,
|
||||||
|
} = options || {};
|
||||||
|
const offset = parseNote(range[0]);
|
||||||
|
const colorizedMidi = haps.map((hap) => ({
|
||||||
|
keys: [parseNote(hap.value.note)],
|
||||||
|
color: hap.value.color || getTheme().selection,
|
||||||
|
}));
|
||||||
|
/* const to = parseNote(range[1]);
|
||||||
|
const totalKeys = to - offset + 1; */
|
||||||
|
/* const width = totalKeys * topWidth + topWidth + strokeWidth * 2;
|
||||||
|
const height = whiteHeight; */
|
||||||
|
|
||||||
|
const topWidth = upperWidth * scaleX;
|
||||||
|
const [white, black] = getMidiKeys(range, offset);
|
||||||
|
|
||||||
|
const whiteHeight = (upperHeight + lowerHeight) * scaleY;
|
||||||
|
const blackHeight = upperHeight * scaleY;
|
||||||
|
|
||||||
|
const cDiff = 12 - (offset % 12);
|
||||||
|
const cOffset = whiteX(cDiff + offset);
|
||||||
|
const blackOffset = cOffset;
|
||||||
|
|
||||||
|
const blackX = (midi) => (midi - offset) * topWidth + blackOffset;
|
||||||
|
|
||||||
|
const getColor = (midi) => colorizedMidi.find(({ keys }) => keys.includes(midi))?.color;
|
||||||
|
ctx.clearRect(0, 0, ctx.canvas.width * 2, ctx.canvas.height * 2);
|
||||||
|
ctx.strokeStyle = 'white';
|
||||||
|
ctx.strokeWidth = strokeWidth;
|
||||||
|
white.forEach((midi) => {
|
||||||
|
ctx.fillStyle = getColor(midi) ?? palette[1];
|
||||||
|
const x = whiteX(midi, offset, topWidth);
|
||||||
|
const width = whiteWidth(midi, topWidth);
|
||||||
|
ctx.fillRect(x, 0, width, whiteHeight);
|
||||||
|
ctx.strokeRect(x, 0, width, whiteHeight);
|
||||||
|
});
|
||||||
|
black.forEach((midi) => {
|
||||||
|
ctx.fillStyle = getColor(midi) ?? palette[0];
|
||||||
|
const x = blackX(midi, offset, topWidth);
|
||||||
|
//ctx.strokeRect(x, 0, topWidth, blackHeight);
|
||||||
|
ctx.fillRect(x, 0, topWidth, blackHeight);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern.prototype.claviature = function (options) {
|
||||||
|
return this.draw((haps) => claviature(haps, options), { id: options.id });
|
||||||
|
};
|
||||||
@@ -3,4 +3,5 @@ export * from './color.mjs';
|
|||||||
export * from './draw.mjs';
|
export * from './draw.mjs';
|
||||||
export * from './pianoroll.mjs';
|
export * from './pianoroll.mjs';
|
||||||
export * from './spiral.mjs';
|
export * from './spiral.mjs';
|
||||||
|
export * from './claviature.mjs';
|
||||||
export * from './pitchwheel.mjs';
|
export * from './pitchwheel.mjs';
|
||||||
|
|||||||
Reference in New Issue
Block a user