feat: add sequencer, piano roll modules with pre-composed chiptune melody
Add step sequencer (16-step with note/gate editing) and piano roll (canvas-based MIDI editor with draw/erase tools). Includes a Megaman-style melody in C minor. Chiptune preset now uses piano roll instead of keyboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -146,7 +146,6 @@ function createNode(mod) {
|
||||
};
|
||||
}
|
||||
case 'keyboard': {
|
||||
// Keyboard outputs frequency as a Signal and gate as a Signal
|
||||
const freqSig = new Tone.Signal(440);
|
||||
const gateSig = new Tone.Signal(0);
|
||||
return {
|
||||
@@ -158,6 +157,37 @@ function createNode(mod) {
|
||||
dispose: () => { freqSig.dispose(); gateSig.dispose(); },
|
||||
};
|
||||
}
|
||||
case 'sequencer': {
|
||||
const freqSig = new Tone.Signal(440);
|
||||
const gateSig = new Tone.Signal(0);
|
||||
// Sequencer loop managed externally by SequencerWidget
|
||||
return {
|
||||
node: null,
|
||||
inputs: {},
|
||||
outputs: { freq: freqSig, gate: gateSig },
|
||||
_freqSig: freqSig,
|
||||
_gateSig: gateSig,
|
||||
_seq: null, // Tone.Sequence set by widget
|
||||
dispose: () => {
|
||||
freqSig.dispose(); gateSig.dispose();
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'pianoroll': {
|
||||
const freqSig = new Tone.Signal(440);
|
||||
const gateSig = new Tone.Signal(0);
|
||||
return {
|
||||
node: null,
|
||||
inputs: {},
|
||||
outputs: { freq: freqSig, gate: gateSig },
|
||||
_freqSig: freqSig,
|
||||
_gateSig: gateSig,
|
||||
_part: null, // Tone.Part set by widget
|
||||
dispose: () => {
|
||||
freqSig.dispose(); gateSig.dispose();
|
||||
},
|
||||
};
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -280,11 +310,31 @@ export function updateParam(moduleId, paramName, value) {
|
||||
if (paramName === 'volume') entry.node.gain.value = Tone.dbToGain(value);
|
||||
break;
|
||||
case 'keyboard':
|
||||
if (paramName === 'octave') { /* stored in state only */ }
|
||||
case 'sequencer':
|
||||
case 'pianoroll':
|
||||
// All params stored in state, managed by widgets
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function setSequencerSignals(moduleId, freq, gate) {
|
||||
const entry = audioNodes[moduleId];
|
||||
if (!entry) return;
|
||||
if (entry._freqSig) entry._freqSig.value = freq;
|
||||
if (entry._gateSig) entry._gateSig.value = gate ? 1 : 0;
|
||||
|
||||
// Trigger connected envelopes
|
||||
for (const conn of state.connections) {
|
||||
if (conn.from.moduleId === moduleId && conn.from.port === 'gate') {
|
||||
const envEntry = audioNodes[conn.to.moduleId];
|
||||
if (envEntry && envEntry.node instanceof Tone.Envelope) {
|
||||
if (gate) envEntry.node.triggerAttack();
|
||||
else envEntry.node.triggerRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function triggerKeyboard(moduleId, freq, gate) {
|
||||
const entry = audioNodes[moduleId];
|
||||
if (!entry) return;
|
||||
|
||||
Reference in New Issue
Block a user