Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/dialogs/InstrumentPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export function InstrumentPicker() {
<button
onClick={() => {
const track = addTrackAtSelectedSlot('keyboard', 'pianoRoll');
updateTrack(track.id, { displayName: 'Quick Sampler', synthPreset: 'sampler' });
updateTrack(track.id, { displayName: 'Quick Sampler' });
setTrackSampler(track.id, { rootNote: 60 });
close();
setOpenPianoRoll(track.id);
Expand Down
12 changes: 11 additions & 1 deletion src/components/midi/VirtualKeyboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useProjectStore } from '../../store/projectStore';
import { useTransportStore } from '../../store/transportStore';
import { useUIStore } from '../../store/uiStore';
import type { Track } from '../../types/project';
import { getTrackInstrumentSelectValue, resolveTrackInstrument } from '../../utils/trackInstrument';

const WHITE_KEY_BINDINGS = [
{ code: 'KeyA', semitone: 0, label: 'A' },
Expand Down Expand Up @@ -158,7 +159,16 @@ export function VirtualKeyboard() {
let clipStartTime = 0;

if (targetTrack) {
synthEngine.ensureTrackSynth(targetTrack.id, targetTrack.synthPreset ?? 'piano');
const trackInstrument = resolveTrackInstrument(targetTrack);
const instrumentSelectValue = getTrackInstrumentSelectValue(targetTrack);
synthEngine.ensureTrackSynth(
targetTrack.id,
trackInstrument ?? (
instrumentSelectValue === 'fm' || instrumentSelectValue === 'sampler'
? 'piano'
: instrumentSelectValue
),
);
synthEngine.noteOn(targetTrack.id, pitch, nextVelocity);
captureService.noteOn(targetTrack.id, pitch, velocityNormalized, startTime);

Expand Down
51 changes: 47 additions & 4 deletions src/components/pianoroll/PianoRoll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import { useProjectStore } from '../../store/projectStore';
import { useUIStore } from '../../store/uiStore';
import type { PianoRollGrid, SamplerConfig } from '../../types/project';
import { CHORD_SHAPES, DEFAULT_CHORD_SHAPE_ABBR, getChordShapeByAbbr } from '../../utils/chords';
import {
createDefaultFmInstrument,
createDefaultSubtractiveInstrument,
getTrackInstrumentSelectValue,
resolveTrackInstrument,
type TrackInstrumentSelectValue,
} from '../../utils/trackInstrument';
import { QuickSamplerEditor } from './QuickSamplerEditor';
import { SynthInstrumentEditor } from './SynthInstrumentEditor';
import { GeneratePatternDialog } from './GeneratePatternDialog';
import { PianoRollCanvas } from './PianoRollCanvas';
import { PianoRollEmptyState } from './PianoRollEmptyState';
Expand All @@ -30,7 +38,7 @@ export function PianoRoll() {
const [samplerDropActive, setSamplerDropActive] = useState(false);

const project = useProjectStore((s) => s.project);
const updateTrack = useProjectStore((s) => s.updateTrack);
const setTrackInstrument = useProjectStore((s) => s.setTrackInstrument);
const setTrackSampler = useProjectStore((s) => s.setTrackSampler);
const clearTrackSampler = useProjectStore((s) => s.clearTrackSampler);
const updateSamplerConfig = useProjectStore((s) => s.updateSamplerConfig);
Expand Down Expand Up @@ -106,6 +114,15 @@ export function PianoRoll() {
return track.clips.find((candidate) => candidate.midiData) ?? null;
}, [openClipId, track]);

const trackInstrument = useMemo(
() => (track ? resolveTrackInstrument(track) : null),
[track],
);
const instrumentSelectValue = useMemo<TrackInstrumentSelectValue>(
() => (track ? getTrackInstrumentSelectValue(track) : 'piano'),
[track],
);

const ghostNotes = useMemo(() => {
if (!showGhostNotes || !project || !track) return [];
const notes: Array<{ pitch: number; startBeat: number; durationBeats: number; color: string }> = [];
Expand Down Expand Up @@ -312,8 +329,26 @@ export function PianoRoll() {

<select
aria-label="Track synth preset"
value={track.synthPreset ?? 'piano'}
onChange={(e) => updateTrack(track.id, { synthPreset: e.target.value as typeof track.synthPreset })}
value={instrumentSelectValue}
onChange={(e) => {
const nextValue = e.target.value as TrackInstrumentSelectValue;
if (nextValue === 'sampler') {
setTrackSampler(track.id, {
audioKey: track.sampler?.audioKey,
sampleName: track.sampler?.sampleName,
rootNote: track.sampler?.rootNote ?? samplerConfig?.rootNote ?? 60,
sampleDuration: track.sampler?.sampleDuration ?? samplerConfig?.trimEnd ?? 1,
});
return;
}

if (nextValue === 'fm') {
setTrackInstrument(track.id, createDefaultFmInstrument());
return;
}

setTrackInstrument(track.id, createDefaultSubtractiveInstrument(nextValue));
}}
className="bg-[#111] border border-[#333] rounded px-2 py-1 text-[11px] text-zinc-300"
>
<option value="piano">Piano</option>
Expand All @@ -322,6 +357,7 @@ export function PianoRoll() {
<option value="lead">Lead</option>
<option value="bass">Bass</option>
<option value="organ">Organ</option>
<option value="fm">FM</option>
<option value="sampler">Quick Sampler</option>
</select>

Expand Down Expand Up @@ -411,7 +447,7 @@ export function PianoRoll() {
</div>
</div>

{track.synthPreset === 'sampler' && (
{trackInstrument?.kind === 'sampler' && (
<div
aria-label="Quick Sampler target"
className={samplerDropActive ? 'ring-1 ring-cyan-400/70 shrink-0' : 'shrink-0'}
Expand All @@ -429,6 +465,13 @@ export function PianoRoll() {
</div>
)}

{trackInstrument && trackInstrument.kind !== 'sampler' && (
<SynthInstrumentEditor
instrument={trackInstrument}
onInstrumentChange={(nextInstrument) => setTrackInstrument(track.id, nextInstrument)}
/>
)}

{clip ? (
<PianoRollCanvas
clip={clip}
Expand Down
18 changes: 14 additions & 4 deletions src/components/pianoroll/PianoRollCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { useUIStore } from '../../store/uiStore';
import { ContextMenuWrapper, ContextMenuItem } from '../ui/ContextMenu';
import { useTransportStore } from '../../store/transportStore';
import type { Clip, MidiNote, PianoRollGrid, Track } from '../../types/project';
import {
getTrackInstrumentSelectValue,
resolveTrackInstrument,
} from '../../utils/trackInstrument';
import { drawPianoRollKeyboard } from './PianoRollKeyboard';
import { drawVelocityLane } from './VelocityLane';
import { DEFAULT_CHORD_SHAPE_ABBR, getChordShapeByAbbr } from '../../utils/chords';
Expand Down Expand Up @@ -108,14 +112,20 @@ export function PianoRollCanvas({
const bpm = useProjectStore((s) => s.project?.bpm ?? 120);
const currentTime = useTransportStore((s) => s.currentTime);
const previewEnabled = true;
const synthPreset = track.synthPreset ?? 'piano';
const trackInstrument = resolveTrackInstrument(track);
const instrumentSelectValue = getTrackInstrumentSelectValue(track);
const synthSource = trackInstrument ?? (
instrumentSelectValue === 'fm' || instrumentSelectValue === 'sampler'
? 'piano'
: instrumentSelectValue
);
const previewNoteAtPitch = useCallback((pitch: number, velocity = 100, duration = 0.3) => {
if (synthPreset === 'sampler') {
if (trackInstrument?.kind === 'sampler') {
void samplerEngine.previewTrackNote(track, pitch, velocity, duration);
return;
}
void synthEngine.previewNote(pitch, velocity, duration, synthPreset);
}, [synthPreset, track]);
void synthEngine.previewNote(pitch, velocity, duration, synthSource);
}, [synthSource, track, trackInstrument]);

const keyHeight = PIANO_ROLL_KEY_HEIGHT * prZoomY;
const pixelsPerBeat = 40 * prZoomX;
Expand Down
Loading
Loading