Move frontend to packages/client/, server to packages/server/.
Root package.json uses npm workspaces to orchestrate both.
Structure:
reaktor/
packages/client/ (React + Vite + Tone.js frontend)
packages/server/ (static file server, future API)
dist/ (built output, shared)
docker-compose.yml (app + PostgreSQL for future backend)
- npm run dev → runs Vite dev server from client workspace
- npm run build → builds client, outputs to root dist/
- npm run start → runs server.js serving dist/
- Dockerfile updated for multi-stage monorepo build
- docker-compose.yml added with PostgreSQL service (ready for Phase 1)
- All imports and paths preserved, zero functionality change
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
329 lines
9.1 KiB
JavaScript
329 lines
9.1 KiB
JavaScript
/**
|
|
* moduleRegistry.js — Defines all available module types
|
|
* Each module type specifies: ports, params, icon, category, and audio factory
|
|
*/
|
|
|
|
export const PORT_TYPE = {
|
|
AUDIO: 'audio',
|
|
CONTROL: 'control',
|
|
TRIGGER: 'trigger',
|
|
};
|
|
|
|
// Module type definitions
|
|
const registry = {};
|
|
|
|
export function defineModule(type, def) {
|
|
registry[type] = { type, ...def };
|
|
}
|
|
|
|
export function getModuleDef(type) {
|
|
return registry[type] || null;
|
|
}
|
|
|
|
export function getAllModuleDefs() {
|
|
return Object.values(registry);
|
|
}
|
|
|
|
export function getModulesByCategory() {
|
|
const cats = {};
|
|
for (const def of Object.values(registry)) {
|
|
if (!cats[def.category]) cats[def.category] = [];
|
|
cats[def.category].push(def);
|
|
}
|
|
return cats;
|
|
}
|
|
|
|
// ==================== SOURCE ====================
|
|
|
|
defineModule('oscillator', {
|
|
name: 'Oscillator',
|
|
icon: '~',
|
|
category: 'Source',
|
|
inputs: [
|
|
{ name: 'freq', type: PORT_TYPE.AUDIO, label: 'Freq' },
|
|
{ name: 'detune', type: PORT_TYPE.CONTROL, label: 'Detune' },
|
|
],
|
|
outputs: [
|
|
{ name: 'out', type: PORT_TYPE.AUDIO, label: 'Out' },
|
|
],
|
|
params: {
|
|
waveform: { type: 'select', options: ['sine', 'square', 'sawtooth', 'triangle'], default: 'sawtooth', label: 'Wave' },
|
|
frequency: { type: 'knob', min: 20, max: 8000, default: 440, unit: 'Hz', label: 'Freq' },
|
|
detune: { type: 'knob', min: -1200, max: 1200, default: 0, unit: 'ct', label: 'Detune' },
|
|
},
|
|
});
|
|
|
|
defineModule('lfo', {
|
|
name: 'LFO',
|
|
icon: '∿',
|
|
category: 'Source',
|
|
inputs: [],
|
|
outputs: [
|
|
{ name: 'out', type: PORT_TYPE.CONTROL, label: 'Out' },
|
|
],
|
|
params: {
|
|
waveform: { type: 'select', options: ['sine', 'square', 'sawtooth', 'triangle'], default: 'sine', label: 'Wave' },
|
|
frequency: { type: 'knob', min: 0.01, max: 50, default: 2, unit: 'Hz', label: 'Rate' },
|
|
amplitude: { type: 'knob', min: 0, max: 1, default: 0.5, unit: '', label: 'Depth' },
|
|
},
|
|
});
|
|
|
|
defineModule('noise', {
|
|
name: 'Noise',
|
|
icon: '⣿',
|
|
category: 'Source',
|
|
inputs: [],
|
|
outputs: [
|
|
{ name: 'out', type: PORT_TYPE.AUDIO, label: 'Out' },
|
|
],
|
|
params: {
|
|
type: { type: 'select', options: ['white', 'pink', 'brown'], default: 'white', label: 'Type' },
|
|
},
|
|
});
|
|
|
|
// ==================== FILTER ====================
|
|
|
|
defineModule('filter', {
|
|
name: 'Filter',
|
|
icon: '▽',
|
|
category: 'Filter',
|
|
inputs: [
|
|
{ name: 'in', type: PORT_TYPE.AUDIO, label: 'In' },
|
|
{ name: 'cutoff', type: PORT_TYPE.CONTROL, label: 'Cutoff' },
|
|
],
|
|
outputs: [
|
|
{ name: 'out', type: PORT_TYPE.AUDIO, label: 'Out' },
|
|
],
|
|
params: {
|
|
type: { type: 'select', options: ['lowpass', 'highpass', 'bandpass', 'notch'], default: 'lowpass', label: 'Type' },
|
|
frequency: { type: 'knob', min: 20, max: 20000, default: 1000, unit: 'Hz', label: 'Cutoff' },
|
|
Q: { type: 'knob', min: 0.1, max: 20, default: 1, unit: '', label: 'Reso' },
|
|
},
|
|
});
|
|
|
|
// ==================== ENVELOPE ====================
|
|
|
|
defineModule('envelope', {
|
|
name: 'Envelope',
|
|
icon: '⏤╲',
|
|
category: 'Modulation',
|
|
inputs: [
|
|
{ name: 'gate', type: PORT_TYPE.TRIGGER, label: 'Gate' },
|
|
],
|
|
outputs: [
|
|
{ name: 'out', type: PORT_TYPE.CONTROL, label: 'Out' },
|
|
],
|
|
params: {
|
|
attack: { type: 'knob', min: 0.001, max: 4, default: 0.01, unit: 's', label: 'Attack' },
|
|
decay: { type: 'knob', min: 0.001, max: 4, default: 0.2, unit: 's', label: 'Decay' },
|
|
sustain: { type: 'knob', min: 0, max: 1, default: 0.5, unit: '', label: 'Sustain' },
|
|
release: { type: 'knob', min: 0.001, max: 8, default: 0.5, unit: 's', label: 'Release' },
|
|
},
|
|
});
|
|
|
|
// ==================== AMPLIFIER ====================
|
|
|
|
defineModule('vca', {
|
|
name: 'VCA',
|
|
icon: '△',
|
|
category: 'Utility',
|
|
inputs: [
|
|
{ name: 'in', type: PORT_TYPE.AUDIO, label: 'In' },
|
|
{ name: 'cv', type: PORT_TYPE.CONTROL, label: 'CV' },
|
|
],
|
|
outputs: [
|
|
{ name: 'out', type: PORT_TYPE.AUDIO, label: 'Out' },
|
|
],
|
|
params: {
|
|
gain: { type: 'knob', min: 0, max: 1, default: 0.8, unit: '', label: 'Gain' },
|
|
},
|
|
});
|
|
|
|
// ==================== EFFECTS ====================
|
|
|
|
defineModule('delay', {
|
|
name: 'Delay',
|
|
icon: '⟫',
|
|
category: 'Effect',
|
|
inputs: [
|
|
{ name: 'in', type: PORT_TYPE.AUDIO, label: 'In' },
|
|
],
|
|
outputs: [
|
|
{ name: 'out', type: PORT_TYPE.AUDIO, label: 'Out' },
|
|
],
|
|
params: {
|
|
delayTime: { type: 'knob', min: 0.01, max: 2, default: 0.3, unit: 's', label: 'Time' },
|
|
feedback: { type: 'knob', min: 0, max: 0.95, default: 0.4, unit: '', label: 'Feedbk' },
|
|
wet: { type: 'knob', min: 0, max: 1, default: 0.5, unit: '', label: 'Mix' },
|
|
},
|
|
});
|
|
|
|
defineModule('reverb', {
|
|
name: 'Reverb',
|
|
icon: '◌',
|
|
category: 'Effect',
|
|
inputs: [
|
|
{ name: 'in', type: PORT_TYPE.AUDIO, label: 'In' },
|
|
],
|
|
outputs: [
|
|
{ name: 'out', type: PORT_TYPE.AUDIO, label: 'Out' },
|
|
],
|
|
params: {
|
|
decay: { type: 'knob', min: 0.1, max: 15, default: 3, unit: 's', label: 'Decay' },
|
|
wet: { type: 'knob', min: 0, max: 1, default: 0.4, unit: '', label: 'Mix' },
|
|
},
|
|
});
|
|
|
|
defineModule('distortion', {
|
|
name: 'Distortion',
|
|
icon: '⚡',
|
|
category: 'Effect',
|
|
inputs: [
|
|
{ name: 'in', type: PORT_TYPE.AUDIO, label: 'In' },
|
|
],
|
|
outputs: [
|
|
{ name: 'out', type: PORT_TYPE.AUDIO, label: 'Out' },
|
|
],
|
|
params: {
|
|
distortion: { type: 'knob', min: 0, max: 1, default: 0.4, unit: '', label: 'Drive' },
|
|
wet: { type: 'knob', min: 0, max: 1, default: 0.5, unit: '', label: 'Mix' },
|
|
},
|
|
});
|
|
|
|
// ==================== MIXER ====================
|
|
|
|
defineModule('mixer', {
|
|
name: 'Mixer',
|
|
icon: '≡',
|
|
category: 'Utility',
|
|
inputs: [
|
|
{ name: 'in1', type: PORT_TYPE.AUDIO, label: 'In 1' },
|
|
{ name: 'in2', type: PORT_TYPE.AUDIO, label: 'In 2' },
|
|
{ name: 'in3', type: PORT_TYPE.AUDIO, label: 'In 3' },
|
|
{ name: 'in4', type: PORT_TYPE.AUDIO, label: 'In 4' },
|
|
],
|
|
outputs: [
|
|
{ name: 'out', type: PORT_TYPE.AUDIO, label: 'Out' },
|
|
],
|
|
params: {
|
|
gain1: { type: 'knob', min: 0, max: 1, default: 0.8, unit: '', label: 'Ch 1' },
|
|
gain2: { type: 'knob', min: 0, max: 1, default: 0.8, unit: '', label: 'Ch 2' },
|
|
gain3: { type: 'knob', min: 0, max: 1, default: 0.8, unit: '', label: 'Ch 3' },
|
|
gain4: { type: 'knob', min: 0, max: 1, default: 0.8, unit: '', label: 'Ch 4' },
|
|
},
|
|
});
|
|
|
|
// ==================== SCOPE ====================
|
|
|
|
defineModule('scope', {
|
|
name: 'Scope',
|
|
icon: '📊',
|
|
category: 'Utility',
|
|
inputs: [
|
|
{ name: 'in', type: PORT_TYPE.AUDIO, label: 'In' },
|
|
],
|
|
outputs: [],
|
|
params: {},
|
|
});
|
|
|
|
// ==================== CV TO GATE ====================
|
|
|
|
defineModule('cv2gate', {
|
|
name: 'CV→Gate',
|
|
icon: '⚡',
|
|
category: 'Utility',
|
|
inputs: [
|
|
{ name: 'in', type: PORT_TYPE.CONTROL, label: 'CV In' },
|
|
],
|
|
outputs: [
|
|
{ name: 'gate', type: PORT_TYPE.TRIGGER, label: 'Gate' },
|
|
],
|
|
params: {
|
|
threshold: { type: 'knob', min: 0, max: 1, default: 0.5, unit: '', label: 'Thresh' },
|
|
},
|
|
});
|
|
|
|
// ==================== OUTPUT ====================
|
|
|
|
defineModule('output', {
|
|
name: 'Output',
|
|
icon: '🔊',
|
|
category: 'Output',
|
|
inputs: [
|
|
{ name: 'left', type: PORT_TYPE.AUDIO, label: 'Left' },
|
|
{ name: 'right', type: PORT_TYPE.AUDIO, label: 'Right' },
|
|
],
|
|
outputs: [],
|
|
params: {
|
|
volume: { type: 'knob', min: -60, max: 6, default: -6, unit: 'dB', label: 'Volume' },
|
|
},
|
|
});
|
|
|
|
// ==================== KEYBOARD ====================
|
|
|
|
defineModule('keyboard', {
|
|
name: 'Keyboard',
|
|
icon: '🎹',
|
|
category: 'Source',
|
|
inputs: [],
|
|
outputs: [
|
|
{ name: 'freq', type: PORT_TYPE.AUDIO, label: 'Freq' },
|
|
{ name: 'gate', type: PORT_TYPE.TRIGGER, label: 'Gate' },
|
|
],
|
|
params: {
|
|
octave: { type: 'knob', min: 1, max: 8, default: 4, unit: '', label: 'Octave' },
|
|
},
|
|
});
|
|
|
|
// ==================== DRUM PAD ====================
|
|
|
|
defineModule('drumpad', {
|
|
name: 'Drum Pad',
|
|
icon: '🥁',
|
|
category: 'Source',
|
|
inputs: [],
|
|
outputs: [
|
|
{ name: 'freq', type: PORT_TYPE.AUDIO, label: 'Freq' },
|
|
{ name: 'gate', type: PORT_TYPE.TRIGGER, label: 'Gate' },
|
|
],
|
|
params: {},
|
|
});
|
|
|
|
// ==================== SEQUENCER ====================
|
|
|
|
defineModule('sequencer', {
|
|
name: 'Sequencer',
|
|
icon: '▦',
|
|
category: 'Source',
|
|
inputs: [],
|
|
outputs: [
|
|
{ name: 'freq', type: PORT_TYPE.AUDIO, label: 'Freq' },
|
|
{ name: 'gate', type: PORT_TYPE.TRIGGER, label: 'Gate' },
|
|
],
|
|
params: {
|
|
bpm: { type: 'knob', min: 40, max: 300, default: 140, unit: 'bpm', label: 'BPM' },
|
|
steps: { type: 'select', options: ['8', '16', '32'], default: '16', label: 'Steps' },
|
|
swing: { type: 'knob', min: 0, max: 0.5, default: 0, unit: '', label: 'Swing' },
|
|
},
|
|
// Custom data: step notes/gates stored in module.params._steps
|
|
});
|
|
|
|
// ==================== PIANO ROLL ====================
|
|
|
|
defineModule('pianoroll', {
|
|
name: 'Piano Roll',
|
|
icon: '🎼',
|
|
category: 'Source',
|
|
inputs: [],
|
|
outputs: [
|
|
{ name: 'freq', type: PORT_TYPE.AUDIO, label: 'Freq' },
|
|
{ name: 'gate', type: PORT_TYPE.TRIGGER, label: 'Gate' },
|
|
],
|
|
params: {
|
|
bpm: { type: 'knob', min: 40, max: 300, default: 140, unit: 'bpm', label: 'BPM' },
|
|
loop: { type: 'select', options: ['on', 'off'], default: 'on', label: 'Loop' },
|
|
bars: { type: 'select', options: ['1', '2', '4', '8'], default: '4', label: 'Bars' },
|
|
},
|
|
// Custom data: notes stored in module.params._notes = [{note, start, duration}, ...]
|
|
});
|