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
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
6 changes: 3 additions & 3 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 @@ -47,8 +47,8 @@ export class TR808HiHat {
const decayBase = isOpen ? (0.3 + decay * 0.2) : (0.04 + decay * 0.02);
const decayTime = decayBase * (1 + (Math.random() * 0.04 - 0.02)); // +/- 2% decay

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

// Scheduling
Expand Down
6 changes: 3 additions & 3 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 @@ -30,8 +30,8 @@ export class TR808Kick {
osc.frequency.setValueAtTime(startFreq, time);
osc.frequency.exponentialRampToValueAtTime(endFreq, time + pitchDropTime);

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

osc.start(time).stop(time + finalDecay);
Expand Down
13 changes: 7 additions & 6 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 @@ -24,9 +24,9 @@ export class TR808Snare {
const snappyDecay = snappyDecayBase * (1 + (Math.random() * 0.04 - 0.02));
const filterVariance = 1 + (Math.random() * 0.04 - 0.02);

// 808 Membrane modes: fixed at ~238Hz and ~476Hz according to research
const oscLow = new Tone.Oscillator(238 + drift, "sine");
const oscHigh = new Tone.Oscillator(476 + drift, "sine");
// 808 Membrane modes: 180Hz and 330Hz as per research average
const oscLow = new Tone.Oscillator(180 + drift, "sine");
const oscHigh = new Tone.Oscillator(330 + drift, "sine");
oscLow.phase = Math.random() * 360;
oscHigh.phase = Math.random() * 360;

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

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

Expand All @@ -54,7 +55,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
29 changes: 17 additions & 12 deletions src/logic/drums/TR909Kick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,38 @@ export class TR909Kick {
}
}

trigger(time: number, pitch: number, decay: number) {
// pitch: 0.5 -> 50Hz, maps to 45-55Hz
trigger(time: number, pitch: number, decay: number, velocity: number = 0.8) {
// Research: Tune controls the pitch envelope decay time (0.05s - 0.15s)
const pitchEnvDecay = 0.05 + pitch * 0.1;
// Final frequency is relatively stable for 909 (45Hz - 55Hz)
const tune = 45 + pitch * 10;
// decay: 0.5 -> 0.45s, maps to 0.3-0.6s
// Amp 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 vcaDecay = decayTime * (1 + (Math.random() * 0.04 - 0.02)); // +/- 2% decay
const filterVariance = 1 + (Math.random() * 0.04 - 0.02); // +/- 2% filter

// 909 Kick Body: Triangle Oscillator
const bodyOsc = new Tone.Oscillator(tune * 4.7 + drift, "triangle");
// 909 Kick Body: Triangle Oscillator + Smoothing LowPass Filter
const bodyOsc = new Tone.Oscillator(0, "triangle"); // Freq set via envelope
bodyOsc.phase = Math.random() * 360;
const bodyFilter = new Tone.Filter(1000 * filterVariance, "lowpass");
const bodyGain = new Tone.Gain(0);

bodyOsc.connect(bodyGain);
bodyOsc.connect(bodyFilter);
bodyFilter.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 and drop over variable duration (from Tune knob)
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);
// VCA Envelope with velocity scaling
bodyGain.gain.setValueAtTime(velocity, time);
bodyGain.gain.exponentialRampToValueAtTime(0.001, time + vcaDecay);

// Click Layer (Noise)
Expand All @@ -52,16 +56,17 @@ export class TR909Kick {
noiseFilter.connect(noiseGain);
noiseGain.connect(this.destination);

// Ultra short envelope (10-20ms) for the click
// Ultra short envelope (10-20ms) for the click, also velocity scaled
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);
noiseSrc.start(time).stop(time + clickDecay);

bodyOsc.onstop = () => {
bodyOsc.dispose();
bodyFilter.dispose();
bodyGain.dispose();
};
noiseSrc.onended = () => {
Expand Down
15 changes: 10 additions & 5 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 All @@ -30,10 +30,14 @@ export class TR909Snare {
const osc2 = new Tone.Oscillator(freq2 * 2 + drift, "triangle");
osc1.phase = Math.random() * 360;
osc2.phase = Math.random() * 360;

// Slight saturation/waveshaping for tonal body
const shaper = new Tone.WaveShaper((x) => Math.tanh(x * 1.5));
const tonalGain = new Tone.Gain(0);

osc1.connect(tonalGain);
osc2.connect(tonalGain);
osc1.connect(shaper);
osc2.connect(shaper);
shaper.connect(tonalGain);
tonalGain.connect(this.destination);

// 2x Pitch Sweep over 30ms (research says ~30ms for 909 Snare)
Expand All @@ -43,7 +47,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 +62,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 All @@ -68,6 +72,7 @@ export class TR909Snare {
osc1.onstop = () => {
osc1.dispose();
osc2.dispose();
shaper.dispose();
tonalGain.dispose();
};
noiseSrc.onended = () => {
Expand Down
6 changes: 5 additions & 1 deletion src/store/instrumentStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@ interface DrumState {
}

export const useDrumStore = create<DrumState>((set) => ({
// Kick: 4-on-the-floor
kick: { steps: 16, pulses: 4, rotate: 0, decay: 0.5, pitch: 0.5 },
// Snare/Clap: Backbeat (2 and 4), rotated by 4 steps (16/2 rot 4)
snare: { steps: 16, pulses: 2, rotate: 4, decay: 0.5, pitch: 0.5 },
clap: { steps: 16, pulses: 2, rotate: 4, decay: 0.5, pitch: 0.5 },
// Hi-hat: 16th note pattern
hihat: { steps: 16, pulses: 12, rotate: 0, decay: 0.5, pitch: 0.5 },
// Open Hi-hat: Off-beat (rotated by 2)
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 },
kit: '909',
setParams: (drum, params) => set((state) => ({
[drum]: { ...state[drum], ...params }
Expand Down