Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better sounds #230

Merged
merged 6 commits into from
Jan 25, 2024
Merged
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
77 changes: 77 additions & 0 deletions src/ui/pages/sounds.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
import Layout from '../layouts/Layout.astro'
import '../styles/index.css'

const sounds = ['startGame', 'startTurn', 'endTurn', 'selectCard', 'cardToHand', 'playCard']
---

<script>
import sounds from '../sounds.js'
import {beep, playCoin, playWhoosh} from '../sounds2.js'

const buttons = document.querySelectorAll('menu.sounds button')
buttons.forEach((button) => {
button.addEventListener('click', (event) => {
const sound = event.currentTarget.dataset.sound
console.log('playing', sound)
sounds[sound]()
})
})

document.querySelectorAll('[data-sfx]').forEach((el) => {
el.addEventListener('click', () => {
const {sfx, coin} = el.dataset
if (sfx === 'whoosh') {
playWhoosh(0.6)
} else if (coin) {
playCoin(coin)
} else {
beep(sfx, sfx * 2, 0.3)
}
})
})
</script>

<Layout title="Slay the Web">
<div class="Container">
<h1>Sounds</h1>
<p>Play any of the sound effects used in the game.</p>

<menu>
<button type="button" data-sfx="220">220</button>
<button type="button" data-sfx="440">440</button>
<button type="button" data-sfx="880">880</button>
<button type="button" data-sfx data-coin="minor">Coin minor</button>
<button type="button" data-sfx data-coin="major">Coin major</button>
<button type="button" data-sfx data-coin="pentatonic">Coin penta</button>
<button type="button" data-sfx="whoosh">Whoosh</button>
</menu>

<menu class="sounds">
{
sounds.map((s) => (
<button type="button" data-sound={s}>
{s}
</button>
))
}
</menu>
</div>
</Layout>

<style>
h1 + p {
margin-left: 0.8em;
margin-bottom: 2rem;
}
menu {
margin: 0 0 1rem;
display: flex;
flex-flow: column wrap;
gap: 0.5em;
max-width: 14em;
}
button {
margin: 0;
}
</style>
101 changes: 31 additions & 70 deletions src/ui/sounds.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,98 +2,68 @@
import * as Tone from 'tone'

// Create synths and connect it to the main output (your speakers).
const polySynth = new Tone.PolySynth(Tone.AMSynth, {volume: -36}).toDestination()
const amSynth = new Tone.AMSynth({volume: -14}).toDestination()
const polySynth = new Tone.PolySynth(Tone.AMSynth, {volume: -20}).toDestination()
const amSynth = new Tone.AMSynth({volume: -10}).toDestination()

export async function init() {
await Tone.start()
console.log('audio is ready')
console.log('Sound initialized')
}

function startGame() {
polySynth.triggerAttackRelease(['D4', 'F4', 'A4', 'C5', 'E5'], 0.7)
polySynth.triggerAttackRelease(['D4', 'F4', 'A4', 'C5', 'E5'], '4n')
}

const selectCard = () => {
function startTurn() {
// initialize the noise and start
const noise = new Tone.Noise({
type: 'brown',
fadeOut: 0.07,
volume: -33,
type: 'pink',
fadeOut: 0.4,
volume: -20,
}).start()

// make an autofilter to shape the noise
const autoFilter = new Tone.AutoFilter({
frequency: '5n',
baseFrequency: 3000,
octaves: 2,
frequency: '2n',
baseFrequency: 800,
octaves: 1,
})
.toDestination()
.start()
autoFilter.stop('+0.1')
autoFilter.stop('+0.25')

// connect the noise
noise.connect(autoFilter)
// start the autofilter LFO
noise.stop('+0.04')
noise.stop('+0.3')
}

function endTurn() {
// initialize the noise and start
const noise = new Tone.Noise({
const pinkNoise = new Tone.Noise({
type: 'pink',
fadeOut: 0.2,
volume: -28,
}).start()

// make an autofilter to shape the noise
const autoFilter = new Tone.AutoFilter({
frequency: '2n',
baseFrequency: 200,
octaves: 2,
})
.toDestination()
.start()
autoFilter.stop('+0.4')

// connect the noise
noise.connect(autoFilter)
// start the autofilter LFO
noise.stop('+0.2')
}

function startTurn() {
/* synth.triggerAttackRelease('C4', '8n') */

// initialize the noise and start
const noise = new Tone.Noise({
type: 'pink',
fadeOut: 0.4,
volume: -33,
}).start()

// make an autofilter to shape the noise
pinkNoise.start()
const autoFilter = new Tone.AutoFilter({
frequency: '2n',
baseFrequency: 800,
octaves: 1,
// baseFrequency: 200,
octaves: 4,
volume: -10,
})
.toDestination()
.start()
autoFilter.stop('+0.25')

// connect the noise
noise.connect(autoFilter)
// start the autofilter LFO
noise.stop('+0.3')
.stop('+0.4')
pinkNoise.connect(autoFilter).stop('+0.3')
}

function cardToHand() {
// initialize the noise and start
const noise = new Tone.Noise({
type: 'pink',
fadeOut: 0.1,
volume: -35,
volume: -20,
}).start()

// make an autofilter to shape the noise
Expand All @@ -104,34 +74,25 @@ function cardToHand() {
})
.toDestination()
.start()
autoFilter.stop('+0.08')
.stop('+0.08')

// connect the noise
noise.connect(autoFilter)
// start the autofilter LFO
noise.stop('+0.1')
noise
.connect(autoFilter)
// start the autofilter LFO
.stop('+0.1')
}

function playCard({card}) {
const cardType = card.damage ? 'attack' : 'defense'
if (cardType === 'attack') {
playAttackCard()
}
if (cardType === 'defense') {
playDefenseCard()
}
}

const playAttackCard = () => {
amSynth.triggerAttackRelease('G#3', 0.2)
const selectCard = () => {
amSynth.triggerAttackRelease('C4', '8n')
}

const playDefenseCard = () => {
amSynth.triggerAttackRelease('G#2', 0.02)
function playCard(card) {
amSynth.triggerAttackRelease('G#3', '16n')
}

export const toggleMute = (shouldMute) => {
Tone.Master.mute = shouldMute
Tone.Destination.mute = shouldMute
}

export default {
Expand Down
79 changes: 79 additions & 0 deletions src/ui/sounds2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const audioContext = new (window.AudioContext || window.webkitAudioContext)()

/**
* Plays a beep sound using web audio api
* AudioContext > Oscillator -> Gain
*/
export function beep(
startFrequency = 440,
endFrequency = 880,
duration = 0.5,
waveform = 'square',
volume = 0.1,
) {
const oscillator = audioContext.createOscillator()
oscillator.type = waveform // 'square', 'sawtooth', 'triangle', or 'sine'
oscillator.frequency.setValueAtTime(startFrequency, audioContext.currentTime)
if (endFrequency && duration > 0) {
oscillator.frequency.exponentialRampToValueAtTime(endFrequency, audioContext.currentTime + duration)
}

// Adding a simple gain envelope for a more "blippy" sound
const gainNode = audioContext.createGain()
gainNode.gain.setValueAtTime(0, audioContext.currentTime)
gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + 0.01) // Quick ramp up
gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + duration) // And down

oscillator.connect(gainNode)
gainNode.connect(audioContext.destination)

oscillator.start()
oscillator.stop(audioContext.currentTime + duration)

// var compressor = audioContext.createDynamicsCompressor()
// mix.connect(compressor)
// compressor.connect(context.destination)
}

const majorScale = [523.25, 587.33, 659.25, 698.46, 783.99, 880.0, 987.77, 1046.5] // C5, D5, E5, F5, G5, A5, B5, C6
const minorScale = [440.0, 523.25, 587.33, 659.25, 698.46, 783.99, 880.0, 987.77] // A4, C5, D5, E5, F5, G5, A5, B5
const pentatonicScale = [523.25, 659.25, 783.99, 880.0, 1046.5] // C5, E5, G5, A5, C6

/**
* Plays a coin/reward high pitched sound
*/
export function playCoin(type, volume = 0.5) {
const scales = [majorScale, minorScale, pentatonicScale]
let selectedScale = scales[Math.floor(Math.random() * scales.length)]
if (type === 'major') selectedScale = majorScale
if (type === 'minor') selectedScale = minorScale
if (type === 'pentatonic') selectedScale = pentatonicScale

selectedScale.forEach((note, index) => {
setTimeout(() => {
beep(note, note, 0.1, 'triangle', volume)
}, index * 100)
})
}

export function playWhoosh(duration = 1, startFreq = 200, endFreq = 800, volume = 0.2) {
const audioContext = new (window.AudioContext || window.webkitAudioContext)()

// Sine wave oscillator
const oscillator = audioContext.createOscillator()
oscillator.type = 'sine'
oscillator.frequency.setValueAtTime(startFreq, audioContext.currentTime)
oscillator.frequency.exponentialRampToValueAtTime(endFreq, audioContext.currentTime + duration)

// Gain envelope for smooth fade-in and fade-out
const gainNode = audioContext.createGain()
gainNode.gain.setValueAtTime(0, audioContext.currentTime)
gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + duration * 0.2)
gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + duration)

oscillator.connect(gainNode)
gainNode.connect(audioContext.destination)

oscillator.start()
oscillator.stop(audioContext.currentTime + duration)
}