Skip to content

Commit aeac847

Browse files
committed
feat: add 10 new instruments with distinct sounds and per-clip presets
- Add 10 new synth presets: Organ, Clavinet, FM Bass, Pluck Bass, FM Lead, Pulse Lead, Choir Pad, Glass Pad, Pluck, Bell - Use diverse synthesis engines (FMSynth, AMSynth, MonoSynth with filter envelopes, partial harmonics, pulse oscillators) for genuinely distinct timbres - Add instrumentPreset to Clip type so each clip can have its own instrument sound independent of other clips on the same track - Update playout and offline-renderer to check clip.instrumentPreset first, falling back to track.instrumentPreset - Add category-matched demo note patterns for all new instruments - Fix addTrack() to assign default instrumentPreset based on track color Closes #9
1 parent 20336af commit aeac847

7 files changed

Lines changed: 414 additions & 10 deletions

File tree

components/compose/TrackList.tsx

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ function getDemoNotesForInstrument(instrumentId: string): Array<{ pitch: number;
7373
// Piano/Keys - C major chord progression
7474
case 'electric-piano':
7575
case 'bright-piano':
76+
case 'organ':
77+
case 'clavinet':
7678
return [
7779
// C major chord (beat 0)
7880
{ pitch: 60, startBeat: 0, duration: 2, velocity: 100 },
@@ -95,6 +97,8 @@ function getDemoNotesForInstrument(instrumentId: string): Array<{ pitch: number;
9597
// Bass - simple bass line
9698
case 'sub-bass':
9799
case 'synth-bass':
100+
case 'fm-bass':
101+
case 'pluck-bass':
98102
return [
99103
{ pitch: 36, startBeat: 0, duration: 1, velocity: 110 },
100104
{ pitch: 36, startBeat: 1.5, duration: 0.5, velocity: 90 },
@@ -109,6 +113,8 @@ function getDemoNotesForInstrument(instrumentId: string): Array<{ pitch: number;
109113
// Lead - melody line
110114
case 'saw-lead':
111115
case 'square-lead':
116+
case 'fm-lead':
117+
case 'pulse-lead':
112118
return [
113119
{ pitch: 72, startBeat: 0, duration: 0.5, velocity: 100 },
114120
{ pitch: 74, startBeat: 0.5, duration: 0.5, velocity: 95 },
@@ -123,13 +129,44 @@ function getDemoNotesForInstrument(instrumentId: string): Array<{ pitch: number;
123129
// Pads - long sustained chords
124130
case 'warm-pad':
125131
case 'string-pad':
132+
case 'choir-pad':
133+
case 'glass-pad':
126134
return [
127135
// C major sustained
128136
{ pitch: 60, startBeat: 0, duration: 8, velocity: 80 },
129137
{ pitch: 64, startBeat: 0, duration: 8, velocity: 75 },
130138
{ pitch: 67, startBeat: 0, duration: 8, velocity: 70 },
131139
];
132140

141+
// Pluck - staccato arpeggio to showcase short decay
142+
case 'pluck-synth':
143+
return [
144+
{ pitch: 60, startBeat: 0, duration: 0.25, velocity: 100 },
145+
{ pitch: 64, startBeat: 0.5, duration: 0.25, velocity: 90 },
146+
{ pitch: 67, startBeat: 1, duration: 0.25, velocity: 100 },
147+
{ pitch: 72, startBeat: 1.5, duration: 0.25, velocity: 95 },
148+
{ pitch: 76, startBeat: 2, duration: 0.25, velocity: 100 },
149+
{ pitch: 72, startBeat: 2.5, duration: 0.25, velocity: 90 },
150+
{ pitch: 67, startBeat: 3, duration: 0.25, velocity: 95 },
151+
{ pitch: 64, startBeat: 3.5, duration: 0.25, velocity: 90 },
152+
{ pitch: 60, startBeat: 4, duration: 0.25, velocity: 100 },
153+
{ pitch: 55, startBeat: 4.5, duration: 0.25, velocity: 85 },
154+
{ pitch: 60, startBeat: 5, duration: 0.25, velocity: 95 },
155+
{ pitch: 64, startBeat: 5.5, duration: 0.25, velocity: 90 },
156+
{ pitch: 67, startBeat: 6, duration: 0.25, velocity: 100 },
157+
{ pitch: 72, startBeat: 6.5, duration: 0.25, velocity: 95 },
158+
{ pitch: 76, startBeat: 7, duration: 0.5, velocity: 100 },
159+
];
160+
161+
// Bell - spaced hits to let the long decay ring
162+
case 'bell-synth':
163+
return [
164+
{ pitch: 84, startBeat: 0, duration: 0.5, velocity: 90 },
165+
{ pitch: 79, startBeat: 2, duration: 0.5, velocity: 85 },
166+
{ pitch: 76, startBeat: 4, duration: 0.5, velocity: 80 },
167+
{ pitch: 72, startBeat: 6, duration: 0.5, velocity: 85 },
168+
];
169+
133170
// Default synth - simple arpeggio
134171
case 'basic-synth':
135172
default:
@@ -849,6 +886,7 @@ interface TrackLaneProps {
849886
function TrackLane({ track, index, pixelsPerBeat, beatsPerBar, isSelected, onSelect }: TrackLaneProps) {
850887
const project = useProjectStore((s) => s.project);
851888
const addClip = useProjectStore((s) => s.addClip);
889+
const updateClip = useProjectStore((s) => s.updateClip);
852890
const addNote = useProjectStore((s) => s.addNote);
853891
const updateTrack = useProjectStore((s) => s.updateTrack);
854892
const addTrackEffect = useProjectStore((s) => s.addTrackEffect);
@@ -951,15 +989,22 @@ function TrackLane({ track, index, pixelsPerBeat, beatsPerBar, isSelected, onSel
951989
});
952990

953991
} else if (data.type === 'instrument') {
954-
// Update track's instrument preset
955-
updateTrack(track.id, { instrumentPreset: data.data.id });
956-
957992
// Create MIDI clip for instrument
958993
const clipType = track.color === 'drums' ? 'drum' : 'midi';
959994
const clip = addClip(track.id, clipType, bar, 2); // 2 bar default
960995

961-
// Add demo notes so user hears something immediately
996+
// Store instrument preset on the clip (not the track)
997+
// so each clip can have its own instrument sound
962998
const instrumentId = data.data.id as string;
999+
updateClip(clip.id, {
1000+
instrumentPreset: instrumentId,
1001+
name: data.data.name || clip.name,
1002+
});
1003+
1004+
// Also update track preset (used as fallback for new clips without preset)
1005+
updateTrack(track.id, { instrumentPreset: instrumentId });
1006+
1007+
// Add demo notes so user hears something immediately
9631008
const demoNotes = getDemoNotesForInstrument(instrumentId);
9641009
demoNotes.forEach(note => addNote(clip.id, note));
9651010

@@ -1008,7 +1053,7 @@ function TrackLane({ track, index, pixelsPerBeat, beatsPerBar, isSelected, onSel
10081053
} catch (err) {
10091054
console.error('[TrackLane] Failed to parse drop data:', err);
10101055
}
1011-
}, [track.id, track.color, pixelsPerBeat, beatsPerBar, addClip, addNote, selectClip, openEditor, updateTrack, addTrackEffect]);
1056+
}, [track.id, track.color, pixelsPerBeat, beatsPerBar, addClip, updateClip, addNote, selectClip, openEditor, updateTrack, addTrackEffect]);
10121057

10131058
if (!project) return null;
10141059

lib/audio/offline-renderer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,10 @@ async function scheduleMidiClipForOffline(
354354
): Promise<void> {
355355
if (!clip.notes?.length) return;
356356

357-
const synth = createSynthForTrack(track);
357+
// Prefer clip-level instrument preset, fall back to track
358+
const synth = clip.instrumentPreset
359+
? createSynthFromPreset(clip.instrumentPreset)
360+
: createSynthForTrack(track);
358361
synth.connect(destination);
359362

360363
// Wait for synth to be ready (important for Sampler which loads async)

lib/audio/playout.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,10 @@ class PlayoutManager {
391391
return;
392392
}
393393

394-
// Create synth based on track type
395-
const synth = this.createSynthForTrack(track);
394+
// Create synth: prefer clip-level instrument, fall back to track
395+
const synth = clip.instrumentPreset
396+
? createSynthFromPreset(clip.instrumentPreset)
397+
: this.createSynthForTrack(track);
396398
synth.connect(destination);
397399

398400
// Wait for synth to be ready (important for Sampler which loads async)

0 commit comments

Comments
 (0)