From 907b3fd02d3ce3bce7bd5bf587a2ba213cde8f74 Mon Sep 17 00:00:00 2001 From: Dominique Loiseau <5888729+domi7777@users.noreply.github.com> Date: Sat, 5 Oct 2024 14:17:26 +0200 Subject: [PATCH] feat: add more pads and drums (before: 4, now: 8) --- src/samples/hihat-open.ts | 41 +++++++++++++++++++++++++ src/samples/ride.ts | 41 +++++++++++++++++++++++++ src/samples/tom-high.ts | 5 +++ src/samples/tom-low.ts | 27 +++++++++++++++++ src/scenes/KitScene.ts | 64 ++++++++++++++++++++++++--------------- 5 files changed, 153 insertions(+), 25 deletions(-) create mode 100644 src/samples/hihat-open.ts create mode 100644 src/samples/ride.ts create mode 100644 src/samples/tom-high.ts create mode 100644 src/samples/tom-low.ts diff --git a/src/samples/hihat-open.ts b/src/samples/hihat-open.ts new file mode 100644 index 0000000..50e3c2c --- /dev/null +++ b/src/samples/hihat-open.ts @@ -0,0 +1,41 @@ +// Create an Audio Context +const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); + +export function playOpenHiHat() { + // --- 1. White Noise for the hi-hat --- + const bufferSize = audioContext.sampleRate * 0.5; // 0.5 second buffer for longer open 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; // 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(5000, audioContext.currentTime); // High cutoff for brightness + + // --- 3. Low-pass filter to shape the high-end frequencies --- + const lowPassFilter = audioContext.createBiquadFilter(); + lowPassFilter.type = 'lowpass'; + lowPassFilter.frequency.setValueAtTime(10000, audioContext.currentTime); // Smooth out the very high frequencies + + // --- 4. Gain envelope for the sound decay --- + const gainNode = audioContext.createGain(); + gainNode.gain.setValueAtTime(0.6, audioContext.currentTime); // Start at a moderate volume + gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.5); // Decay for a sustained open hi-hat sound + + // 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 + 0.5); // Stop after 0.5 seconds for an open hi-hat effect +} diff --git a/src/samples/ride.ts b/src/samples/ride.ts new file mode 100644 index 0000000..56b0d38 --- /dev/null +++ b/src/samples/ride.ts @@ -0,0 +1,41 @@ +// Create an Audio Context +const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); + +export function playRide() { + // --- 1. White Noise for the metallic sound --- + const bufferSize = audioContext.sampleRate * 1.2; // Slightly longer buffer for sustained ride sound + 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 focus on high frequencies --- + const highPassFilter = audioContext.createBiquadFilter(); + highPassFilter.type = 'highpass'; + highPassFilter.frequency.setValueAtTime(3000, audioContext.currentTime); // Lower frequency cutoff than hi-hat for more depth + + // --- 3. Low-pass filter to smooth out the harshness --- + const lowPassFilter = audioContext.createBiquadFilter(); + lowPassFilter.type = 'lowpass'; + lowPassFilter.frequency.setValueAtTime(9000, audioContext.currentTime); // Tame the very high frequencies + + // --- 4. Gain envelope for the sound decay --- + const gainNode = audioContext.createGain(); + gainNode.gain.setValueAtTime(0.8, audioContext.currentTime); // Start at a higher volume for prominence + gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 1.2); // Longer decay for a sustained ride sound + + // Connect the nodes: 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.2); // Stop after 1.2 seconds for a ringing ride cymbal effect +} diff --git a/src/samples/tom-high.ts b/src/samples/tom-high.ts new file mode 100644 index 0000000..fbaf54e --- /dev/null +++ b/src/samples/tom-high.ts @@ -0,0 +1,5 @@ +import {playTom1Low} from './tom-low.ts'; + +export function playTom2High() { + playTom1Low(120, 400); +} diff --git a/src/samples/tom-low.ts b/src/samples/tom-low.ts new file mode 100644 index 0000000..9ddcdb9 --- /dev/null +++ b/src/samples/tom-low.ts @@ -0,0 +1,27 @@ +// Create an Audio Context +const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); + +export function playTom1Low(hz = 99, durationInMs = 500) { + // --- 1. Create an oscillator for the base tone --- + const oscillator = audioContext.createOscillator(); + oscillator.type = 'sine'; // Use a sine wave for the base + oscillator.frequency.setValueAtTime(hz, audioContext.currentTime); // Lower frequency for a deep tom sound + + // --- 2. Create a gain node for controlling volume --- + const gainNode = audioContext.createGain(); + gainNode.gain.setValueAtTime(1, audioContext.currentTime); // Increase initial gain for louder sound + gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.5); // Quick decay + + // --- 3. Add a slight detuning effect to make the tom sound richer --- + oscillator.detune.setValueAtTime(-5, audioContext.currentTime); // Small detuning for realism + + // --- 4. Connect nodes: oscillator -> gain -> output --- + oscillator.connect(gainNode); + gainNode.connect(audioContext.destination); + + // Start the oscillator + oscillator.start(); + + // Stop after 0.5 seconds (length of the sound) + oscillator.stop(audioContext.currentTime + (durationInMs / 1000)); +} diff --git a/src/scenes/KitScene.ts b/src/scenes/KitScene.ts index a6eae10..77c0207 100644 --- a/src/scenes/KitScene.ts +++ b/src/scenes/KitScene.ts @@ -5,6 +5,10 @@ import {playSnare} from '../samples/snare.ts'; import {playKick} from '../samples/kick.ts'; import {playCrashCymbal} from '../samples/crash.ts'; import {HexaColor, hexToColor} from '../colors.ts'; +import {playOpenHiHat} from '../samples/hihat-open.ts'; +import {playRide} from '../samples/ride.ts'; +import {playTom2High} from '../samples/tom-high.ts'; +import {playTom1Low} from '../samples/tom-low.ts'; // loop let isRecording = false; @@ -57,22 +61,20 @@ function stopPlaying() { console.log('Loop stopped'); } +const instrumentToSample: Record void> = { + hihat: playHiHat, + kick: playKick, + snare: playSnare, + crash: playCrashCymbal, + 'hihat-open': playOpenHiHat, + ride: playRide, + 'tom-low': playTom1Low, + 'tom-high': playTom2High, +} + const playInstrument = (instrument: Instrument) => { console.log(`Playing ${instrument}`); - switch (instrument) { - case 'hihat': - playHiHat(); - break; - case 'snare': - playSnare(); - break; - case 'kick': - playKick(); - break; - case 'crash': - playCrashCymbal(); - break; - } + instrumentToSample[instrument](); if (isRecording) { const time = Date.now() - startRecordingTime; loop.push({ @@ -84,7 +86,8 @@ const playInstrument = (instrument: Instrument) => { } // pads -type Instrument = 'hihat' | 'snare' | 'kick' | 'crash'; +type Instrument = 'hihat' | 'hihat-open' | 'ride' | 'crash' + | 'snare' | 'kick' | 'tom-low' | 'tom-high'; type Pad = { instrument: Instrument, @@ -96,7 +99,11 @@ const padColors: Record = { kick: '#F24E1E', snare: '#4A90E2', crash: '#A0D8C5', -} + 'hihat-open': '#F9F871', + ride: '#F5C542', + 'tom-low': '#FF7F50', + 'tom-high': '#9B59B6', +}; // controls type ControlState = 'idle' | 'readyToRecord' | 'recording' | 'playing'; @@ -127,11 +134,18 @@ export class KitScene extends Phaser.Scene { } private createPads() { + // change the order of the pads if needed, top to bottom and left to right const pads: Pad[] = [ + // top + this.createPad('crash'), + this.createPad('ride'), + this.createPad('hihat-open'), this.createPad('hihat'), - this.createPad('kick'), + // bottom this.createPad('snare'), - this.createPad('crash'), + this.createPad('tom-low'), + this.createPad('tom-high'), + this.createPad('kick'), ]; pads.forEach(({button, instrument}) => button @@ -150,13 +164,13 @@ export class KitScene extends Phaser.Scene { ); const resizePads = () => { - const width = window.innerWidth; - const height = window.innerHeight; - // Update the pads - pads[0].button.setSize(width / 2, height / 2).setPosition(0, 0); - pads[1].button.setSize(width / 2, height / 2).setPosition(width / 2, 0); - pads[2].button.setSize(width / 2, height / 2).setPosition(0, height / 2); - pads[3].button.setSize(width / 2, height / 2).setPosition(width / 2, height / 2); + const width = window.innerWidth / 4; + const height = window.innerHeight / 2; + pads.forEach(({button}, index) => { + const x = index % 4 * width; + const y = Math.floor(index / 4) * height; + button.setSize(width, height).setPosition(x, y); + }) }; window.addEventListener('resize', resizePads); resizePads();