गेम ऑडियो के लिए Tone.js
Tone.js, Web Audio API के ऊपर बैठता है और synthesized ऑडियो को व्यावहारिक बना देता है। ऑडियो फाइलें लोड करने के बजाय आप sounds को प्रोग्रामैटिक तरीके से जनरेट करते हैं। इसका मतलब है ज़ीरो लोड टाइम, असीमित वैरिएशन और ऐसे sounds जो रियल टाइम में gameplay पर रिएक्ट कर सकते हैं।
इसे आज़माएं (synthesized गेम sounds सुनने के लिए बटन क्लिक करें):
ऑडियो फाइलों के बजाय Tone.js क्यों
म्यूज़िक और जटिल sounds के लिए ऑडियो फाइलें ठीक काम करती हैं। लेकिन गेम के sound effects के लिए synthesis के असली फायदे हैं। आप हर बार बजने पर pitch और टाइमिंग बदल सकते हैं ताकि sounds बार-बार एक जैसे न लगें। आप गेम स्टेट के आधार पर sounds को मौके पर ही जनरेट कर सकते हैं। और आप नेटवर्क रिक्वेस्ट को पूरी तरह छोड़ देते हैं।
नुकसान यह है कि synthesized ऑडियो किसी फाइल को बजाने के मुकाबले ज़्यादा CPU लेता है। साधारण गेम के लिए यह कोई दिक्कत नहीं है। जटिल गेम के लिए आप दोनों तरीकों को मिला सकते हैं।
सेटअप
Tone.js को किसी CDN या npm से लोड करें:
<script src="https://unpkg.com/tone@15/build/Tone.js"></script>या npm के साथ:
npm install toneimport * as Tone from 'tone'ऑटोप्ले की समस्या
ब्राउज़र, यूज़र के इंटरैक्शन तक ऑडियो को रोक देते हैं। Tone.js इसे संभालता है, लेकिन आपको किसी यूज़र gesture पर Tone.start() कॉल करना होगा:
document.addEventListener('click', async () => {
await Tone.start()
console.log('Audio ready')
}, { once: true })इसके बिना आपका AudioContext suspended रहता है और कुछ भी नहीं बजता।
आपका पहला synth
एक synth, sound जनरेट करता है। सबसे सरल synth है Tone.Synth:
const synth = new Tone.Synth().toDestination()
// Play a note
synth.triggerAttackRelease('C4', '8n')triggerAttackRelease एक note को किसी अवधि के लिए बजाता है। 'C4' मिडल C है। '8n' मौजूदा tempo पर एक eighth note है।
synth में एक oscillator (sound का स्रोत) और एक envelope (sound कैसे fade in और fade out होता है) होता है। आप दोनों को कस्टमाइज़ कर सकते हैं:
const synth = new Tone.Synth({
oscillator: { type: 'triangle' },
envelope: {
attack: 0.02,
decay: 0.2,
sustain: 0.3,
release: 0.4,
},
}).toDestination()Oscillator के टाइप में sine (शुद्ध, मुलायम), triangle (नरम), sawtooth (भिनभिनाता हुआ) और square (खोखला, रेट्रो) शामिल हैं।
sound effects सिस्टम बनाना
किसी गेम के लिए आपको अलग-अलग sound टाइप के लिए कई synth चाहिए। यहां एक पैटर्न है जो अच्छा काम करता है:
let isInitialized = false
let softSynth = null
let subSynth = null
let noiseSynth = null
let sfxGain = null
function initSFX() {
if (isInitialized) return
// Master volume control
sfxGain = new Tone.Gain(0.7).toDestination()
// Soft synth for melodic sounds
softSynth = new Tone.PolySynth(Tone.Synth, {
maxPolyphony: 6,
oscillator: { type: 'sine' },
envelope: {
attack: 0.02,
decay: 0.2,
sustain: 0.3,
release: 0.4,
},
volume: -8,
}).connect(sfxGain)
// Sub bass for deep impacts
subSynth = new Tone.Synth({
oscillator: { type: 'sine' },
envelope: {
attack: 0.02,
decay: 0.3,
sustain: 0.1,
release: 0.3,
},
volume: -8,
}).connect(sfxGain)
// Noise for texture
noiseSynth = new Tone.NoiseSynth({
noise: { type: 'pink' },
envelope: {
attack: 0.02,
decay: 0.15,
sustain: 0,
release: 0.15,
},
volume: -20,
}).connect(sfxGain)
isInitialized = true
}PolySynth से आप एक साथ कई notes बजा सकते हैं। सामान्य Synth monophonic होता है।
गेम sound के उदाहरण
यहां कुछ आम गेम sounds और उन्हें बनाने का तरीका दिया गया है:
सिक्का इकट्ठा करना - ऊपर चढ़ते notes अच्छा महसूस कराते हैं:
function coinCollect() {
const now = Tone.now()
softSynth.triggerAttackRelease('C4', '8n', now, 0.3)
softSynth.triggerAttackRelease('E4', '8n', now + 0.1, 0.35)
softSynth.triggerAttackRelease('G4', '4n', now + 0.2, 0.4)
}कूद (Jump) - तेज़ी से ऊपर जाता sweep:
function jump() {
const synth = new Tone.Synth({
oscillator: { type: 'square' },
envelope: { attack: 0.01, decay: 0.1, sustain: 0, release: 0.1 },
}).toDestination()
const now = Tone.now()
synth.frequency.setValueAtTime(150, now)
synth.frequency.exponentialRampToValueAtTime(400, now + 0.1)
synth.triggerAttackRelease('C4', '8n', now, 0.2)
}हिट/नुकसान - noise के साथ नीचे उतरता sound:
function hit() {
const now = Tone.now()
subSynth.triggerAttackRelease('E2', '4n', now, 0.5)
noiseSynth.triggerAttackRelease('4n', now, 0.25)
}मौत (Death) - उदास, नीचे उतरते notes:
function death() {
const now = Tone.now()
softSynth.triggerAttackRelease('E4', '4n', now, 0.4)
softSynth.triggerAttackRelease('C4', '4n', now + 0.2, 0.35)
softSynth.triggerAttackRelease('A3', '4n', now + 0.4, 0.3)
}effects जोड़ना
effects, sounds को और समृद्ध बनाते हैं। उन्हें synth और destination के बीच chain करें:
const filter = new Tone.Filter({
frequency: 3000,
type: 'lowpass',
rolloff: -12,
})
const reverb = new Tone.Reverb({
decay: 1.2,
wet: 0.25,
})
const gain = new Tone.Gain(0.7)
// Chain: synth -> filter -> reverb -> gain -> destination
filter.connect(reverb)
reverb.connect(gain)
gain.toDestination()
const synth = new Tone.Synth().connect(filter)Lowpass filter, कर्कश high frequencies को काट देता है। Reverb जगह का एहसास जोड़ता है। Gain, volume को कंट्रोल करता है।
रेट लिमिटिंग
अगर आपका गेम तेज़ी से बहुत सारे sounds ट्रिगर करता है, तो वे आपस में ओवरलैप होकर गड़बड़ कर सकते हैं। रेट लिमिटिंग जोड़ें:
const MIN_INTERVAL_MS = 35
let lastNoteTime = 0
function canPlayNote() {
const now = performance.now()
if (now - lastNoteTime < MIN_INTERVAL_MS) {
return false
}
lastNoteTime = now
return true
}
function playSound() {
if (!canPlayNote()) return
synth.triggerAttackRelease('C4', '8n')
}डायनामिक बैकग्राउंड म्यूज़िक
ऐसे म्यूज़िक के लिए जो gameplay पर रिएक्ट करता है, beat-आधारित सिस्टम का इस्तेमाल करें:
const BPM = 140
const BEAT_MS = (60 / BPM) * 1000
let bassSynth = null
let kickDrum = null
let beatInterval = null
let beatCount = 0
let energy = 0.3
const PROGRESSION = [
{ root: 'E2', chord: ['E3', 'G3', 'B3'] },
{ root: 'C2', chord: ['C3', 'E3', 'G3'] },
{ root: 'D2', chord: ['D3', 'F#3', 'A3'] },
{ root: 'A1', chord: ['A2', 'C3', 'E3'] },
]
function playBeat() {
const beatInMeasure = beatCount % 8
const chordIndex = Math.floor(beatCount / 8) % PROGRESSION.length
const chord = PROGRESSION[chordIndex]
const now = Tone.now()
// Bass on beat 1
if (beatInMeasure === 0) {
bassSynth.triggerAttackRelease(chord.root, '4n', now, 0.6)
}
// Kick drum - only when energy is high
if (beatInMeasure === 0 && energy > 0.3) {
kickDrum.triggerAttackRelease('C1', '8n', now, 0.4)
}
beatCount++
}
function startMusic() {
beatInterval = setInterval(playBeat, BEAT_MS)
}
function stopMusic() {
clearInterval(beatInterval)
}
// Game events affect energy
function onEnemyKill() {
energy = Math.min(1, energy + 0.1)
}गेम में जो हो रहा है उसके आधार पर म्यूज़िक की तीव्रता बदलती है।
MembraneSynth और NoiseSynth के साथ drums
percussion के लिए विशेष synth का इस्तेमाल करें:
// Kick drum
const kick = new Tone.MembraneSynth({
pitchDecay: 0.05,
octaves: 4,
envelope: {
attack: 0.001,
decay: 0.2,
sustain: 0,
release: 0.1,
},
volume: -14,
}).toDestination()
kick.triggerAttackRelease('C1', '8n')
// Snare
const snare = new Tone.NoiseSynth({
noise: { type: 'white' },
envelope: {
attack: 0.002,
decay: 0.15,
sustain: 0,
release: 0.1,
},
volume: -12,
}).toDestination()
snare.triggerAttackRelease('8n')
// Hi-hat
const hihat = new Tone.NoiseSynth({
noise: { type: 'white' },
envelope: {
attack: 0.001,
decay: 0.04,
sustain: 0,
release: 0.03,
},
volume: -22,
}).toDestination()
hihat.triggerAttackRelease('32n')क्लीनअप
काम पूरा होने पर resources फ्री करने के लिए synth को dispose करें:
function cleanup() {
if (softSynth) softSynth.dispose()
if (subSynth) subSynth.dispose()
if (noiseSynth) noiseSynth.dispose()
if (sfxGain) sfxGain.dispose()
}आम गलतियां
Tone.start() का इंतज़ार न करना - जब तक context यूज़र इंटरैक्शन पर स्टार्ट नहीं होता, ऑडियो नहीं बजेगा।
हर sound पर नया synth बनाना - synth को init पर एक बार बनाएं और दोबारा इस्तेमाल करें। हर बार नए बनाने से memory लीक होती है।
बहुत सारे ओवरलैप होते sounds - ऑडियो की गड़बड़ रोकने के लिए रेट लिमिटिंग और deduplication का इस्तेमाल करें।
कर्कश frequencies - ऐसे sounds के लिए lowpass filter और नरम oscillator टाइप (sine, triangle) का इस्तेमाल करें जो खिलाड़ियों को परेशान न करें।
और संसाधन
गेम के लिए Web Audio API में अंतर्निहित API और ऑडियो फाइलें लोड करना कवर किया गया है।
Tone.js documentation में पूरा API रेफरेंस है।
मुफ्त गेम assets कहां पाएं में Freesound जैसे ऑडियो और sound effect प्रदाता शामिल हैं।
मोबाइल-फ्रेंडली वेब गेम में iOS ऑडियो प्रतिबंध और touch-to-unlock पैटर्न कवर किए गए हैं।