refactor: restructure to monorepo with npm workspaces (Phase 0)
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>
This commit is contained in:
328
packages/client/src/engine/moduleRegistry.js
Normal file
328
packages/client/src/engine/moduleRegistry.js
Normal file
@@ -0,0 +1,328 @@
|
||||
/**
|
||||
* 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}, ...]
|
||||
});
|
||||
Reference in New Issue
Block a user