Skip to content

Commit

Permalink
feat: add first kit (hihat, snare, kick, crash) generated by my robot…
Browse files Browse the repository at this point in the history
… friend
  • Loading branch information
domi7777 committed Sep 27, 2024
1 parent af8df91 commit 9bce277
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 21 deletions.
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<link rel="stylesheet" href="https://matcha.mizu.sh/matcha.css">
<title>Larsen app</title>
</head>
<body style="padding-bottom: 40px">
<div id="root">
<body style="padding: 0;margin: 0">
<div id="root" style>
<img src="./icons/pwa-512x512.png" alt="loading" />
</div>
<script type="module" src="/src/main.tsx"></script>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --force",
"dev": "vite --force --port 3000",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "npm run lint -- --fix",
Expand Down
21 changes: 3 additions & 18 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
import {useEffect, useState} from 'react';
import {Kit} from './Kit.tsx';

function App() {
const [isLoading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 2000);
}, []);

return (
<>
{
isLoading ?
<img src="./icons/pwa-512x512.png" alt="loading" />
:<>kikoo</>
}
</>
)
return <Kit/>;
}

export default App
export default App;
47 changes: 47 additions & 0 deletions src/Kit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {playHiHat} from './samples/hihat.ts';
import {playSnare} from './samples/snare.ts';
import {playKick} from './samples/kick.ts';
import {playCrashCymbal} from './samples/crash.ts';

const buttonWidth = '49%';
const buttonHeight = '99%';

const buttonStyle = {
width: buttonWidth,
height: buttonHeight,
color: 'white',
border: 'none',
borderRadius: '0',
}

export const Kit = () => {
return <div style={{
width: '100%',
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
}}>
<div style={{
display: 'flex',
width: '100%',
height: '50vh',
flexDirection: 'row',
}}>
<button style={{...buttonStyle, backgroundColor: '#FDA341'}} onClick={() => playHiHat()}>HiHat</button>
<button style={{...buttonStyle, backgroundColor: '#FE5156'}} onClick={() => playSnare()}>Snare</button>
</div>

<div style={{
display: 'flex',
width: '100%',
height: '50vh',
flexDirection: 'row',
}}>
<button style={{...buttonStyle, backgroundColor: '#0BDAFE'}} onClick={() => playKick()}>Kick</button>
<button style={{...buttonStyle, backgroundColor: '#C56BFE'}} onClick={() => playCrashCymbal()}>Crash</button>
</div>

</div>
}
67 changes: 67 additions & 0 deletions src/samples/crash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Create an Audio Context
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
//
// How It Works:
// White Noise: The crash cymbal sound is built from a long burst of white noise (1.5 seconds) to capture the "wash" of the cymbal.
// High-Pass Filter: We apply a high-pass filter with a high cutoff frequency (4000 Hz) to remove low-end noise and focus on the brighter, metallic part of the cymbal.
// Low-Pass Filter: A low-pass filter at 12,000 Hz smooths out the very high frequencies, making the sound more pleasant and less harsh.
// Gain Envelope: A long exponential decay simulates the ringing-out characteristic of a crash cymbal.
// Metallic Overtones: A high-pitched oscillator (triangle wave) is added subtly to give the crash some metallic shimmer.
// Customization:
// Decay Time: You can adjust the decay time (currently 1.5 seconds) for a longer or shorter crash cymbal.
// Filter Frequencies: Adjusting the high-pass and low-pass filter frequencies will change how bright or dark the cymbal sounds.
// Oscillator Volume: The metallic shimmer is controlled by oscGain.gain. You can increase or decrease it to blend the overtone more or less with the noise.
export function playCrashCymbal() {
// --- 1. White Noise for the main crash sound ---
const bufferSize = audioContext.sampleRate * 1.5; // 1.5 second buffer for long decay
const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = noiseBuffer.getChannelData(0);

for (let i = 0; i < bufferSize; i++) {
output[i] = Math.random() * 2 - 1; // Generate white noise
}

const noise = audioContext.createBufferSource();
noise.buffer = noiseBuffer;

// --- 2. High-pass filter to remove lower frequencies ---
const highPassFilter = audioContext.createBiquadFilter();
highPassFilter.type = 'highpass';
highPassFilter.frequency.setValueAtTime(4000, audioContext.currentTime); // High cutoff for cymbal brightness

// --- 3. Low-pass filter to shape the high-end frequencies ---
const lowPassFilter = audioContext.createBiquadFilter();
lowPassFilter.type = 'lowpass';
lowPassFilter.frequency.setValueAtTime(12000, audioContext.currentTime); // Smooth out harsh high frequencies

// --- 4. Gain envelope to control the crash sound decay ---
const gainNode = audioContext.createGain();
gainNode.gain.setValueAtTime(1, audioContext.currentTime); // Start loud
gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 1.5); // Long decay (1.5 seconds)

// Connect the nodes together: noise -> highPass -> lowPass -> gain -> output
noise.connect(highPassFilter);
highPassFilter.connect(lowPassFilter);
lowPassFilter.connect(gainNode);
gainNode.connect(audioContext.destination);

// Play the noise
noise.start();
noise.stop(audioContext.currentTime + 1.5); // Stop after 1.5 seconds

// --- 5. Optional: Add metallic overtones using an oscillator ---
const osc = audioContext.createOscillator();
osc.type = 'triangle'; // Triangle wave for metallic shimmer
osc.frequency.setValueAtTime(10000, audioContext.currentTime); // High frequency for shimmer effect

const oscGain = audioContext.createGain();
oscGain.gain.setValueAtTime(0.05, audioContext.currentTime); // Low volume to blend with noise
oscGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 1.5); // Decay along with noise

// Connect oscillator for shimmer
osc.connect(oscGain);
oscGain.connect(audioContext.destination);

osc.start();
osc.stop(audioContext.currentTime + 1.5); // Same duration as the noise
}
59 changes: 59 additions & 0 deletions src/samples/hihat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Create an Audio Context
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();

// Improvements:
// Shorter Noise Burst: The noise buffer duration is now much shorter (0.05 seconds), which results in a sharper, crisper hi-hat sound.
// High-Pass Filter Tweaks: The cutoff frequency is set to 8000 Hz to eliminate more of the low-end and leave the high frequencies that give the hi-hat its metallic sound.
// Resonance: Adding a slight resonance (Q = 1) to the high-pass filter emphasizes the higher frequencies and creates a sharper, more defined hi-hat.
// Metallic Overtones: A very high-frequency square wave oscillator adds a metallic "shimmer" to the sound, blending with the noise.
// Further Adjustments:
// Decay: You can modify the decay time (currently 0.05 seconds for the noise and 0.03 for the oscillator) to make the hi-hat longer or shorter depending on whether you want a closed or open hi-hat.
// Oscillator Volume: The oscillator (oscGain) is set at a low volume to subtly blend with the noise, creating a realistic metallic feel.
//

export function playHiHat() {
// --- 1. White Noise (for metallic hiss) ---
const bufferSize = audioContext.sampleRate * 0.05; // Very short burst for a hi-hat
const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = noiseBuffer.getChannelData(0);

for (let i = 0; i < bufferSize; i++) {
output[i] = Math.random() * 2 - 1; // White noise
}

const noise = audioContext.createBufferSource();
noise.buffer = noiseBuffer;

// High-pass filter to remove low frequencies and sharpen the sound
const highPassFilter = audioContext.createBiquadFilter();
highPassFilter.type = 'highpass';
highPassFilter.frequency.setValueAtTime(8000, audioContext.currentTime); // High cutoff for a crisp sound
highPassFilter.Q.setValueAtTime(1, audioContext.currentTime); // Slight resonance for metallic touch

// Envelope to quickly fade out the noise
const noiseGain = audioContext.createGain();
noiseGain.gain.setValueAtTime(1, audioContext.currentTime);
noiseGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.05); // Fast decay for hi-hat sound

noise.connect(highPassFilter);
highPassFilter.connect(noiseGain);
noiseGain.connect(audioContext.destination);

noise.start();
noise.stop(audioContext.currentTime + 0.05); // Short, sharp sound

// --- 2. Additional metallic tone (optional, for a more "shimmering" hi-hat) ---
const osc = audioContext.createOscillator();
osc.type = 'square'; // Square wave for more metallic sound
osc.frequency.setValueAtTime(10000, audioContext.currentTime); // Very high frequency for shimmer

const oscGain = audioContext.createGain();
oscGain.gain.setValueAtTime(0.1, audioContext.currentTime); // Low volume to blend with noise
oscGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.03); // Fast decay

osc.connect(oscGain);
oscGain.connect(audioContext.destination);

osc.start();
osc.stop(audioContext.currentTime + 0.03); // Short burst for shimmer
}
35 changes: 35 additions & 0 deletions src/samples/kick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Create an Audio Context
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();

// How It Works:
// Low-Frequency Oscillator: We start with a sine wave, which provides the smooth, deep bass characteristic of a kick drum.
// Frequency Envelope: The frequency starts at 150 Hz (giving it an initial "punch") and quickly decays to 60 Hz, giving the typical bass drum "thump."
// Gain Envelope: The volume starts high and quickly decays over 0.5 seconds to simulate the fast attack and tail of a real kick drum.
// Customization:
// Pitch: Adjust the initial frequency (150 Hz) or the final frequency (60 Hz) to create different kinds of kicks (e.g., higher for more punch, lower for deeper bass).
// Decay: The duration of the gain envelope can be adjusted to make the kick longer or shorter.

export function playKick() {
// Create an oscillator for the low "thump"
const osc = audioContext.createOscillator();
osc.type = 'sine'; // Sine wave for a smooth, deep bass sound

// Create a gain node to control the amplitude envelope
const gainNode = audioContext.createGain();

// Frequency (pitch) envelope: Start higher and quickly decay to a lower frequency
osc.frequency.setValueAtTime(150, audioContext.currentTime); // Initial higher frequency for punch
osc.frequency.exponentialRampToValueAtTime(60, audioContext.currentTime + 0.1); // Drop to 60Hz

// Gain envelope: Start loud and quickly decay
gainNode.gain.setValueAtTime(1, audioContext.currentTime); // Start loud
gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.5); // Decay to quiet over 0.5 seconds

// Connect oscillator to gain, then to the audio context destination
osc.connect(gainNode);
gainNode.connect(audioContext.destination);

// Start and stop the oscillator
osc.start();
osc.stop(audioContext.currentTime + 0.5); // Stop after 0.5 seconds (length of the kick)
}
61 changes: 61 additions & 0 deletions src/samples/snare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Create an Audio Context
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();

// Breakdown:
// White Noise:
//
// Generates a burst of noise for the "rattle" effect of the snare wires.
// A high-pass filter is applied to remove lower frequencies, leaving the sharper noise.
// The noise fades out quickly using an envelope for a natural decay.
// Low-Frequency Oscillator:
//
// A sine wave oscillator creates the low-pitched "thump" of the drum body.
// This sound also decays quickly using an envelope to mimic the drumhead vibration stopping.
// Envelope Control:
//
// Both the noise and oscillator have quick decay envelopes to simulate the natural sound of a snare hit.
export function playSnare() {
// --- 1. White Noise (for the snare wires) ---
const bufferSize = audioContext.sampleRate * 0.2; // Duration: 0.2 seconds
const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = noiseBuffer.getChannelData(0);

for (let i = 0; i < bufferSize; i++) {
output[i] = Math.random() * 2 - 1; // White noise
}

const noise = audioContext.createBufferSource();
noise.buffer = noiseBuffer;

// Apply a high-pass filter to the noise
const noiseFilter = audioContext.createBiquadFilter();
noiseFilter.type = 'highpass';
noiseFilter.frequency.setValueAtTime(1000, audioContext.currentTime);

// Envelope for the noise
const noiseGain = audioContext.createGain();
noiseGain.gain.setValueAtTime(1, audioContext.currentTime);
noiseGain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2); // Quick decay

noise.connect(noiseFilter);
noiseFilter.connect(noiseGain);
noiseGain.connect(audioContext.destination);

noise.start();
noise.stop(audioContext.currentTime + 0.2); // Short burst

// --- 2. Low-Frequency Oscillator (for the drum body) ---
const osc = audioContext.createOscillator();
osc.type = 'sine'; // Sine wave for low-pitched tone
osc.frequency.setValueAtTime(150, audioContext.currentTime); // Low frequency (150 Hz for a snare body)

const oscGain = audioContext.createGain();
oscGain.gain.setValueAtTime(0.7, audioContext.currentTime);
oscGain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1); // Quick decay to mimic drum hit

osc.connect(oscGain);
oscGain.connect(audioContext.destination);

osc.start();
osc.stop(audioContext.currentTime + 0.1); // Short, punchy tone
}

0 comments on commit 9bce277

Please sign in to comment.