Skip to content
Open
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
11 changes: 6 additions & 5 deletions src/components/SequencerLoop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,12 @@ export function SequencerLoop() {

// 1. Drums (Euclidean - using cached patterns)
const patterns = drumPatternsRef.current
if (patterns.kick[step % patterns.kick.length]) drumMachine.triggerDrum('kick', time)
if (patterns.snare[step % patterns.snare.length]) drumMachine.triggerDrum('snare', time)
if (patterns.hihat[step % patterns.hihat.length]) drumMachine.triggerDrum('hihat', time)
if (patterns.hihatOpen[step % patterns.hihatOpen.length]) drumMachine.triggerDrum('hihatOpen', time)
if (patterns.clap[step % patterns.clap.length]) drumMachine.triggerDrum('clap', time)
const drumVelocity = 0.8
if (patterns.kick[step % patterns.kick.length]) drumMachine.triggerDrum('kick', time, drumVelocity)
if (patterns.snare[step % patterns.snare.length]) drumMachine.triggerDrum('snare', time, drumVelocity)
if (patterns.hihat[step % patterns.hihat.length]) drumMachine.triggerDrum('hihat', time, drumVelocity)
if (patterns.hihatOpen[step % patterns.hihatOpen.length]) drumMachine.triggerDrum('hihatOpen', time, drumVelocity)
if (patterns.clap[step % patterns.clap.length]) drumMachine.triggerDrum('clap', time, drumVelocity)

// 2. Bass (Sting logic)
const bassStep = currentBass.pattern[step]
Expand Down
20 changes: 10 additions & 10 deletions src/logic/DrumMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,19 @@ export class DrumMachine {

if (this.currentKit === '808') {
switch (drum) {
case 'kick': kit808.kick.trigger(time, p.pitch, p.decay); break
case 'snare': kit808.snare.trigger(time, p.pitch, p.decay); break
case 'hihat': kit808.hihat.trigger(time, false, p.pitch, p.decay); break
case 'hihatOpen': kit808.hihatOpen.trigger(time, true, p.pitch, p.decay); break
case 'clap': kit808.clap.trigger(time, p.pitch, p.decay); break
case 'kick': kit808.kick.trigger(time, p.pitch, p.decay, velocity); break
case 'snare': kit808.snare.trigger(time, p.pitch, p.decay, velocity); break
case 'hihat': kit808.hihat.trigger(time, false, p.pitch, p.decay, velocity); break
case 'hihatOpen': kit808.hihatOpen.trigger(time, true, p.pitch, p.decay, velocity); break
case 'clap': kit808.clap.trigger(time, p.pitch, p.decay, velocity); break
}
} else {
switch (drum) {
case 'kick': kit909.kick.trigger(time, p.pitch, p.decay); break
case 'snare': kit909.snare.trigger(time, p.pitch, p.decay); break
case 'hihat': kit909.hihat.trigger(time, false, p.pitch, p.decay); break
case 'hihatOpen': kit909.hihatOpen.trigger(time, true, p.pitch, p.decay); break
case 'clap': kit909.clap.trigger(time, p.pitch, p.decay); break
case 'kick': kit909.kick.trigger(time, p.pitch, p.decay, velocity); break
case 'snare': kit909.snare.trigger(time, p.pitch, p.decay, velocity); break
case 'hihat': kit909.hihat.trigger(time, false, p.pitch, p.decay, velocity); break
case 'hihatOpen': kit909.hihatOpen.trigger(time, true, p.pitch, p.decay, velocity); break
case 'clap': kit909.clap.trigger(time, p.pitch, p.decay, velocity); break
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/logic/drums/TR808Clap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class TR808Clap {
for (let i = 0; i < data.length; i++) data[i] = Math.random() * 2 - 1;
}

trigger(time: number, pitch: number, decay: number) {
trigger(time: number, pitch: number, decay: number, velocity: number = 0.8) {
const noiseSrc = new Tone.BufferSource(this.noiseBuffer);
const filterVariance = 1 + (Math.random() * 0.04 - 0.02); // +/- 2% filter
const bpf = new Tone.Filter((1000 + pitch * 1000) * filterVariance, "bandpass");
Expand All @@ -26,16 +26,16 @@ export class TR808Clap {

for (let i = 0; i < snapCount; i++) {
const snapTime = time + i * snapInterval;
gain.gain.setValueAtTime(1, snapTime);
gain.gain.exponentialRampToValueAtTime(0.1, snapTime + snapInterval * 0.8);
gain.gain.setValueAtTime(velocity, snapTime);
gain.gain.exponentialRampToValueAtTime(0.1 * velocity, snapTime + snapInterval * 0.8);
}

// Final decay
const finalDecayStart = time + snapCount * snapInterval;
const decayTimeBase = 0.1 + decay * 0.5;
const decayTime = decayTimeBase * (1 + (Math.random() * 0.04 - 0.02)); // +/- 2% decay

gain.gain.setValueAtTime(1, finalDecayStart);
gain.gain.setValueAtTime(velocity, finalDecayStart);
gain.gain.exponentialRampToValueAtTime(0.001, finalDecayStart + decayTime);

noiseSrc.start(time).stop(finalDecayStart + decayTime);
Expand Down
4 changes: 2 additions & 2 deletions src/logic/drums/TR808HiHat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export class TR808HiHat {

constructor(private destination: Tone.ToneAudioNode) { }

trigger(time: number, isOpen: boolean, pitch: number, decay: number) {
trigger(time: number, isOpen: boolean, pitch: number, decay: number, velocity: number = 0.8) {
// Create nodes
const mixGain = new Tone.Gain(0.15);
const bpf1 = new Tone.Filter(3440, "bandpass");
Expand Down Expand Up @@ -48,7 +48,7 @@ export class TR808HiHat {
const decayTime = decayBase * (1 + (Math.random() * 0.04 - 0.02)); // +/- 2% decay

// VCA Envelope
envGain.gain.setValueAtTime(1, time);
envGain.gain.setValueAtTime(velocity, time);
envGain.gain.exponentialRampToValueAtTime(0.001, time + decayTime);

// Scheduling
Expand Down
8 changes: 4 additions & 4 deletions src/logic/drums/TR808Kick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Tone from 'tone'
export class TR808Kick {
constructor(private destination: Tone.ToneAudioNode) { }

trigger(time: number, pitch: number, decay: number) {
trigger(time: number, pitch: number, decay: number, velocity: number = 0.8) {
// pitch: 0.5 -> 52.5Hz, maps to 45-60Hz range
const tune = 45 + pitch * 15;
// decay: 0.5 -> 1.7s, maps to 0.4-3.0s range
Expand All @@ -17,8 +17,8 @@ export class TR808Kick {
osc.connect(masterGain);
masterGain.connect(this.destination);

// Micro-randomization: Pitch Drift (+/- 0.5Hz)
const drift = (Math.random() * 2 - 1) * 0.5;
// Micro-randomization: Pitch Drift (+/- 1Hz)
const drift = (Math.random() * 2 - 1) * 1.0;
// VCA Decay variance (+/- 2%)
const finalDecay = decayTime * (1 + (Math.random() * 0.04 - 0.02));

Expand All @@ -31,7 +31,7 @@ export class TR808Kick {
osc.frequency.exponentialRampToValueAtTime(endFreq, time + pitchDropTime);

// VCA Amp Envelope: Instant attack, adjustable exponential decay
masterGain.gain.setValueAtTime(1, time);
masterGain.gain.setValueAtTime(velocity, time);
masterGain.gain.exponentialRampToValueAtTime(0.001, time + finalDecay);

osc.start(time).stop(time + finalDecay);
Expand Down
6 changes: 3 additions & 3 deletions src/logic/drums/TR808Snare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class TR808Snare {
}
}

trigger(time: number, pitch: number, snappy: number) {
trigger(time: number, pitch: number, snappy: number, velocity: number = 0.8) {
// pitch maps to tone balance here (balance between low and high modes)
const toneBalance = pitch;

Expand All @@ -40,7 +40,7 @@ export class TR808Snare {
gainHigh.connect(masterTonalGain);
masterTonalGain.connect(this.destination);

masterTonalGain.gain.setValueAtTime(1, time);
masterTonalGain.gain.setValueAtTime(velocity, time);
// Tonal body decay is short (~200ms)
masterTonalGain.gain.exponentialRampToValueAtTime(0.001, time + vcaDecay);

Expand All @@ -54,7 +54,7 @@ export class TR808Snare {
noiseFilter.connect(snappyGain);
snappyGain.connect(this.destination);

snappyGain.gain.setValueAtTime(0.8, time);
snappyGain.gain.setValueAtTime(0.8 * velocity, time);
snappyGain.gain.exponentialRampToValueAtTime(0.001, time + snappyDecay);

oscLow.start(time).stop(time + vcaDecay);
Expand Down
14 changes: 8 additions & 6 deletions src/logic/drums/TR909Kick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ export class TR909Kick {
}
}

trigger(time: number, pitch: number, decay: number) {
trigger(time: number, pitch: number, decay: number, velocity: number = 0.8) {
// pitch: 0.5 -> 50Hz, maps to 45-55Hz
const tune = 45 + pitch * 10;
// decay: 0.5 -> 0.45s, maps to 0.3-0.6s
const decayTime = 0.3 + decay * 0.3;

// Micro-randomization
const drift = (Math.random() * 2 - 1) * 0.5; // +/- 0.5Hz drift
const drift = (Math.random() * 2 - 1) * 1.0; // +/- 1Hz drift
const vcaDecay = decayTime * (1 + (Math.random() * 0.04 - 0.02)); // +/- 2% decay
const filterVariance = 1 + (Math.random() * 0.04 - 0.02); // +/- 2% filter

Expand All @@ -32,15 +32,17 @@ export class TR909Kick {
bodyOsc.connect(bodyGain);
bodyGain.connect(this.destination);

// Aggressive Pitch Envelope: Start at Tune * 4.7 (~235Hz) and drop over 100ms
// Aggressive Pitch Envelope: Start at Tune * 4.7 (~235Hz)
// Decay time of pitch envelope is controlled by Tune (0.05s - 0.15s)
const pitchEnvDecay = 0.05 + pitch * 0.1;
const startFreq = tune * 4.7 + drift;
const endFreq = tune + drift;

bodyOsc.frequency.setValueAtTime(startFreq, time);
bodyOsc.frequency.exponentialRampToValueAtTime(endFreq, time + 0.1);
bodyOsc.frequency.exponentialRampToValueAtTime(endFreq, time + pitchEnvDecay);

// VCA Envelope
bodyGain.gain.setValueAtTime(1, time);
bodyGain.gain.setValueAtTime(velocity, time);
bodyGain.gain.exponentialRampToValueAtTime(0.001, time + vcaDecay);

// Click Layer (Noise)
Expand All @@ -54,7 +56,7 @@ export class TR909Kick {

// Ultra short envelope (10-20ms) for the click
const clickDecay = 0.02 * (1 + (Math.random() * 0.04 - 0.02));
noiseGain.gain.setValueAtTime(0.7, time);
noiseGain.gain.setValueAtTime(0.7 * velocity, time);
noiseGain.gain.exponentialRampToValueAtTime(0.001, time + clickDecay);

bodyOsc.start(time).stop(time + vcaDecay);
Expand Down
6 changes: 3 additions & 3 deletions src/logic/drums/TR909Snare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class TR909Snare {
}
}

trigger(time: number, pitch: number, snappy: number) {
trigger(time: number, pitch: number, snappy: number, velocity: number = 0.8) {
// 909 Snare Body: 2 triangle oscillators fixed at ~160Hz and ~220Hz
const freq1 = 160;
const freq2 = 220;
Expand Down Expand Up @@ -43,7 +43,7 @@ export class TR909Snare {
osc2.frequency.setValueAtTime(freq2 * 2 + drift, time);
osc2.frequency.exponentialRampToValueAtTime(freq2 + drift, time + sweepTime);

tonalGain.gain.setValueAtTime(1, time);
tonalGain.gain.setValueAtTime(velocity, time);
tonalGain.gain.exponentialRampToValueAtTime(0.001, time + vcaDecay);

// Snappy Layer
Expand All @@ -58,7 +58,7 @@ export class TR909Snare {
lpf.connect(noiseGain);
noiseGain.connect(this.destination);

noiseGain.gain.setValueAtTime(0.7, time);
noiseGain.gain.setValueAtTime(0.7 * velocity, time);
noiseGain.gain.exponentialRampToValueAtTime(0.001, time + snappyDecay);

osc1.start(time).stop(time + vcaDecay);
Expand Down
2 changes: 1 addition & 1 deletion src/store/instrumentStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const useDrumStore = create<DrumState>((set) => ({
snare: { steps: 16, pulses: 2, rotate: 4, decay: 0.5, pitch: 0.5 },
hihat: { steps: 16, pulses: 12, rotate: 0, decay: 0.5, pitch: 0.5 },
hihatOpen: { steps: 16, pulses: 2, rotate: 2, decay: 0.5, pitch: 0.5 },
clap: { steps: 16, pulses: 0, rotate: 0, decay: 0.5, pitch: 0.5 },
clap: { steps: 16, pulses: 2, rotate: 4, decay: 0.5, pitch: 0.5 },
kit: '909',
setParams: (drum, params) => set((state) => ({
[drum]: { ...state[drum], ...params }
Expand Down