Compare commits

..

24 Commits

Author SHA1 Message Date
3cb6749034 Add readme 2025-05-21 10:17:15 +02:00
7d75324835 meta 2025-05-09 18:14:24 +02:00
605ccb2893 🎶🎶🎵🎶 2025-02-23 13:21:34 +01:00
629999e44d add toccata 2025-01-24 10:34:33 +01:00
2e7c4e7c19 autofocus 2024-10-04 17:16:03 +02:00
f096b13c65 enhancements 2024-09-25 01:11:30 +02:00
69c16647cf accordeon buttons 2024-09-23 22:02:57 +02:00
ad05712e20 remove comments 2024-09-23 21:37:57 +02:00
18b03a4975 Events scheduled inside of scheduled callbacks should use the passed in scheduling time 2024-09-23 19:29:55 +02:00
257a354be7 exponentialRampToValueAtTime 2024-09-23 19:26:42 +02:00
40900f472c exponentialRampToValueAtTime 2024-09-23 19:16:22 +02:00
455f96bb3d exponentialRampToValueAtTime 2024-09-23 19:14:37 +02:00
182de5b3fc add cannon sound 2024-09-15 21:23:19 +02:00
e97c535fc6 promise 2024-08-31 01:54:50 +02:00
90df10e141 Play Him Off, Keyboard Cat 2024-08-31 01:37:27 +02:00
0a09ad4e24 victory 2024-08-30 23:47:30 +02:00
fe0e618c76 same width 2024-08-29 01:23:39 +02:00
3fac8105d2 flex direction 2024-08-28 23:40:07 +02:00
a85f092ce7 right arrow 2024-08-28 23:33:05 +02:00
0d024ad390 keyMapInput selection 2024-08-28 23:23:43 +02:00
8fe3699d70 save midiKeyboard 2024-08-28 22:56:30 +02:00
e960b48b27 settings menu 2024-08-28 20:46:38 +02:00
b0cc701226 smaller 2024-08-28 20:35:36 +02:00
22e15c6397 key select 2024-08-28 20:33:10 +02:00
9 changed files with 209 additions and 157 deletions

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Angry Notes From Outer Space
Blast angry notes from outer space with your MIDI keyboard !
![screenshot](https://git.malingrey.fr/adrien/angry-notes/raw/branch/main/thumbnail.png)

212
app.js
View File

@ -86,7 +86,7 @@ class Sprite {
} else { } else {
if (this.frame < this.frames -1) { if (this.frame < this.frames -1) {
this.frame++ this.frame++
} else { } else if (this.frame == this.frames -1) {
this.onanimationend() this.onanimationend()
} }
} }
@ -116,9 +116,10 @@ class Sprite {
class Cannon extends Sprite { class Cannon extends Sprite {
constructor(canvasCtx, note) { constructor(canvasCtx, note) {
let sharp = [1, 3, 6, 8, 10].includes(note % 12) let sharp = [1, 3, 6, 8, 10].includes(note % 12)
super(canvasCtx, "cannon.png", 34 * (note - FIRST_NOTE) + 66, sharp? 422:426, 11, 26, 4) //super(canvasCtx, "cannon.png", 34 * (note - FIRST_NOTE) + 66, sharp? 418:424, 11, 26, 4)
super(canvasCtx, "cannon.png", 34 * (note - FIRST_NOTE) + 66, 424 - 8*(note % 3), 11, 26, 4)
this.note = note this.note = note
this.key = keyMap[note - FIRST_NOTE].toUpperCase() this.key = keyMap[note - FIRST_NOTE]?.toUpperCase() || ""
this.impactHeight = 9 this.impactHeight = 9
this.impactY = 0 this.impactY = 0
this.sy = sharp? 0 : this.sHeight this.sy = sharp? 0 : this.sHeight
@ -164,6 +165,7 @@ class Note extends Sprite {
this.shotAnimationPeriod = shotAnimationPeriod this.shotAnimationPeriod = shotAnimationPeriod
this.shot = false this.shot = false
this.time = 0 this.time = 0
this.angriness = 1
} }
animate() { animate() {
@ -228,6 +230,7 @@ class Quarter extends Note {
class Whole extends Note { class Whole extends Note {
constructor(canvasCtx, note, duration, velocity) { constructor(canvasCtx, note, duration, velocity) {
super(canvasCtx, note, duration, velocity, 36, 100, 36, 40, 1) super(canvasCtx, note, duration, velocity, 36, 100, 36, 40, 1)
this.angriness = 2
} }
animate() {} animate() {}
@ -258,7 +261,7 @@ canvasCtx.imageSmoothingEnabled = false
canvasCtx.font = '12px "Press Start 2P"' canvasCtx.font = '12px "Press Start 2P"'
canvasCtx.textAlign = "center" canvasCtx.textAlign = "center"
let consoleSprite = new Sprite(canvasCtx, "console.png", canvas.width/2, 554, 482, 86) let consoleSprite = new Sprite(canvasCtx, "console.png", canvas.width/2, 554, 482, 104)
let syntheSprite = new Sprite(canvasCtx, "synthe.png", canvas.width/2, 546, 110, 80) let syntheSprite = new Sprite(canvasCtx, "synthe.png", canvas.width/2, 546, 110, 80)
let cannonSprites = [] let cannonSprites = []
for (let note=FIRST_NOTE; note<LAST_NOTE; note++) cannonSprites[note] = new Cannon(canvasCtx, note) for (let note=FIRST_NOTE; note<LAST_NOTE; note++) cannonSprites[note] = new Cannon(canvasCtx, note)
@ -268,7 +271,6 @@ batterySprite.frame = 12
window.onload = function() { window.onload = function() {
draw() draw()
startDialog.showModal() startDialog.showModal()
//window.setInterval(draw, 60)
} }
let audioCtx let audioCtx
@ -308,8 +310,13 @@ function init() {
Tone.Transport.scheduleRepeat(draw, DRAW_PERIOD) Tone.Transport.scheduleRepeat(draw, DRAW_PERIOD)
updateTaskId = Tone.Transport.scheduleRepeat(update, UPDATE_PERIOD) updateTaskId = Tone.Transport.scheduleRepeat(update, UPDATE_PERIOD)
if (window.localStorage.midiKeyboard) {
midiSelect.onfocus()
keyMapInput.onchange()
}
settingsDialog.onclose = newGame settingsDialog.onclose = newGame
showSettings() pause()
} }
startDialog.onclose = init startDialog.onclose = init
@ -333,19 +340,24 @@ function draw() {
batterySprite.draw() batterySprite.draw()
} }
function showSettings() { window.onblur = pause
pause()
settingsDialog.showModal()
}
window.onblur = showSettings
function pause() { function pause() {
Tone.Transport.pause() Tone.Transport.pause()
//window.clearInterval(updateTaskId)
playing = false playing = false
settingsDialog.showModal()
} }
settingsButton.onclick = showSettings settingsButton.onclick = pause
keyMapInput.onclick = keyMapInput.onkeyup = function(event) {
let cursorPosition = keyMapInput.selectionEnd
if (cursorPosition == 0 || (event.key == "ArrowRight" && cursorPosition <= keyMapInput.value.length)) {
keyMapInput.setSelectionRange(cursorPosition, cursorPosition+1)
} else {
keyMapInput.setSelectionRange(cursorPosition-1, cursorPosition)
}
}
keyMapInput.onchange = function(event) { keyMapInput.onchange = function(event) {
keyMap = keyMapInput.value keyMap = keyMapInput.value
@ -361,9 +373,10 @@ keyMapInput.onchange = function(event) {
} }
var midiIputs var midiIputs
var midiKeyboard = window.localStorage.midiKeyboard || ""
midiSelect.onfocus = function() { midiSelect.onfocus = function() {
midiIputs = {} midiIputs = {}
midiSelect.innerHTML = `<option value="">Aucun</option>` midiSelect.innerHTML = `<option value="">Aucun (utiliser les touches ci-dessous)</option>`
navigator.requestMIDIAccess().then( navigator.requestMIDIAccess().then(
midiAccess => { midiAccess => {
if (midiAccess.inputs.size) { if (midiAccess.inputs.size) {
@ -375,6 +388,7 @@ midiSelect.onfocus = function() {
midiSelect.add(option) midiSelect.add(option)
input.onmidimessage = null input.onmidimessage = null
} }
if(midiIputs[midiKeyboard]) midiSelect.value = midiKeyboard
} }
}, },
error => { error => {
@ -383,7 +397,6 @@ midiSelect.onfocus = function() {
) )
} }
var midiKeyboard = ""
midiSelect.oninput = () => { midiSelect.oninput = () => {
for (const id in midiIputs) midiIputs[id].onmidimessage = null for (const id in midiIputs) midiIputs[id].onmidimessage = null
midiKeyboard = midiSelect.value midiKeyboard = midiSelect.value
@ -391,6 +404,7 @@ midiSelect.oninput = () => {
midiIputs[midiKeyboard].onmidimessage = onMIDIMessage midiIputs[midiKeyboard].onmidimessage = onMIDIMessage
} }
keyMapInput.onchange() keyMapInput.onchange()
window.localStorage.midiKeyboard = midiKeyboard
} }
volRange.oninput = function(event) { volRange.oninput = function(event) {
@ -412,16 +426,6 @@ function onpartialinput() {
} }
} }
function onMIDIMessage(event) {
let [code, note, velocity] = event.data
if (144 <= code && code <= 159 && cannonSprites[note]) {
cannonSprites[note].shooting = true
} else if (128 <= code && code <= 143 && cannonSprites[note]) {
cannonSprites[note].shooting = false
}
}
let level let level
function newGame() { function newGame() {
level = 0 level = 0
@ -432,13 +436,13 @@ let midiSong
let noteSprites = [] let noteSprites = []
let explosionSprites = [] let explosionSprites = []
let health let health
async function nextLevel(time=0) { function nextLevel(time=Tone.Transport.seconds) {
Tone.Transport.pause() Tone.Transport.pause(time)
level++ level++
if (level <= MAX_LEVEL) { midiSong = Midi.fromUrl(`midi/${level}.mid`).then((midi) => {
midiSong = midi
health = 12 health = 12
batterySprite.frame = health batterySprite.frame = health
midiSong = await Midi.fromUrl(`midi/${level}.mid`)
levelTitle.innerText = `Niveau ${level}` levelTitle.innerText = `Niveau ${level}`
songNameTitle.innerText = midiSong.name songNameTitle.innerText = midiSong.name
noteSprites = [] noteSprites = []
@ -454,12 +458,12 @@ async function nextLevel(time=0) {
Tone.Transport.scheduleOnce(time => noteSprites.push(noteSprite), time + note.time) Tone.Transport.scheduleOnce(time => noteSprites.push(noteSprite), time + note.time)
}) })
}) })
Tone.Transport.scheduleOnce(time => nextLevel(time), time + midiSong.duration + TIME_TO_SCREEN) Tone.Transport.scheduleOnce(nextLevel, time + midiSong.duration + TIME_TO_SCREEN)
levelDialog.showModal() levelDialog.showModal()
} else { }).catch((error) => {
// win victory(time)
} })
} }
levelDialog.onclose = resume levelDialog.onclose = resume
@ -470,6 +474,69 @@ function resume() {
Tone.Transport.start() Tone.Transport.start()
} }
document.onkeydown = function(event) {
if (event.altKey || event.ctrlKey) return
let keyIndex = keyMap.indexOf(event.key.toLowerCase())
if (keyIndex >= 0) {
if (event.target != keyMapInput) event.preventDefault()
shoot(FIRST_NOTE + keyIndex)
}
}
document.onkeyup = function(event) {
if (event.altKey || event.ctrlKey) return
if (event.key =="Escape") pause()
let keyIndex = keyMap.indexOf(event.key.toLowerCase())
if (keyIndex >= 0) {
if (event.target != keyMapInput) event.preventDefault()
stopShoot(FIRST_NOTE + keyIndex)
}
}
function onMIDIMessage(event) {
let [code, note, velocity] = event.data
if (144 <= code && code <= 159 && cannonSprites[note]) {
shoot(note)
} else if (128 <= code && code <= 143 && cannonSprites[note]) {
stopShoot(note)
}
}
function shoot(note) {
cannonSprites[note].shooting = true
if (cannonSprites[note].oscillator) return
var oscillator = audioCtx.createOscillator({type: "sawtooth"})
oscillator.type = "sawtooth"
oscillator.frequency.value = FREQUENCIES[note - 24]
oscillator.velocity = audioCtx.createGain()
oscillator.velocity.gain.value = 0
oscillator.velocity.gain.linearRampToValueAtTime(0.15, Tone.Transport.seconds + 0.05)
oscillator.connect(oscillator.velocity)
oscillator.start()
oscillator.velocity.connect(volume)
depth.connect(oscillator.detune)
cannonSprites[note].oscillator = oscillator
}
function stopShoot(note) {
cannonSprites[note].shooting = false
if (!cannonSprites[note].oscillator) return
var oscillator = cannonSprites[note].oscillator
oscillator.velocity.gain.exponentialRampToValueAtTime(0.01, Tone.Transport.seconds, 0.5)
oscillator.stop(Tone.Transport.seconds + 0.5)
delete(cannonSprites[note].oscillator)
}
function update(time) { function update(time) {
noteSprites.filter(noteSprite => !noteSprite.shot).forEach(noteSprite => { noteSprites.filter(noteSprite => !noteSprite.shot).forEach(noteSprite => {
noteSprite.y += STEP noteSprite.y += STEP
@ -492,10 +559,11 @@ function update(time) {
cannonSprites.filter(cannonSprite => cannonSprite.shooting).forEach(cannonSprite => { cannonSprites.filter(cannonSprite => cannonSprite.shooting).forEach(cannonSprite => {
let noteSprite = noteSprites.find(noteSprite => noteSprite.note == cannonSprite.note) let noteSprite = noteSprites.find(noteSprite => noteSprite.note == cannonSprite.note)
cannonSprite.impactY = noteSprite?.y || 0
if (noteSprite) { if (noteSprite) {
cannonSprite.impactY = noteSprite.y
if (!noteSprite.shot) { if (!noteSprite.shot) {
playNote(noteSprite.note, noteSprite.velocity, noteSprite.duration, time) playNote(noteSprite.note, noteSprite.velocity, noteSprite.duration, time)
cannonSprite.impactY = noteSprite.y
noteSprite.shot = true noteSprite.shot = true
window.setTimeout(() => { window.setTimeout(() => {
noteSprites.remove(noteSprite) noteSprites.remove(noteSprite)
@ -510,35 +578,7 @@ function update(time) {
}) })
} }
function gameOver(time) { function playNote(note, velocity=0.7, duration=0, time=audioCtx.currentTime) {
playing = false
cannonSprites.forEach(cannonSprite => {
let explosionSprite = cannonSprite.explose()
explosionSprites.push(explosionSprite)
explosionSprite.play().then(() => explosionSprites.remove(explosionSprite))
})
noteSprites.forEach(noteSprite => {
let explosionSprite = noteSprite.explose()
explosionSprites.push(explosionSprite)
explosionSprite.play().then(() => explosionSprites.remove(explosionSprite))
})
noteSprites = []
playNoise(0.7, 400, 2, time)
Tone.Transport.clear(updateTaskId)
Tone.Transport.scheduleOnce((time) => {
Tone.Transport.stop(time)
gameOverDialog.showModal()
}, time + 0.2)
}
gameOverDialog.onclose = () => {
document.location = ""
}
function playNote(note, velocity=0.7, duration=0, time=0) {
if(oscillators[note]) return if(oscillators[note]) return
var oscillator = audioCtx.createOscillator() var oscillator = audioCtx.createOscillator()
@ -554,19 +594,14 @@ function playNote(note, velocity=0.7, duration=0, time=0) {
depth.connect(oscillator.detune) depth.connect(oscillator.detune)
if (duration) {
oscillator.velocity.gain.setValueCurveAtTime([velocity, velocity/10, velocity/20, 0], time + duration, 0.5)
oscillator.stop(time + duration + 0.5)
} else {
oscillators[note] = oscillator oscillators[note] = oscillator
} if (duration) stopNote(note, time + duration)
} }
function stopNote(note, time=0) { function stopNote(note, time=audioCtx.currentTime) {
if(!oscillators[note]) return if(!oscillators[note]) return
velocity = oscillators[note].velocity.gain.value oscillators[note].velocity.gain.exponentialRampToValueAtTime(0.01, time + 0.5)
oscillators[note].velocity.gain.setValueCurveAtTime([velocity, velocity/10, velocity/20, 0], time, 0.5)
oscillators[note].stop(time + 0.5) oscillators[note].stop(time + 0.5)
delete(oscillators[note]) delete(oscillators[note])
@ -590,24 +625,35 @@ function playNoise(startGain=0.5, bandHz=1000, duration, time) {
frequency: bandHz, frequency: bandHz,
}) })
const gain = new GainNode(audioCtx) const gain = new GainNode(audioCtx)
gain.gain.setValueCurveAtTime([startGain, startGain/5, 0], time, duration) gain.gain.value = startGain
gain.gain.exponentialRampToValueAtTime(0.01, time, duration)
noise.connect(bandpass).connect(gain).connect(audioCtx.destination) noise.connect(bandpass).connect(gain).connect(audioCtx.destination)
noise.start() noise.start()
noise.stop(time + duration) noise.stop(time + duration)
} }
document.onkeydown = function(event) { function victory(time) {
if (playing && keyMap.includes(event.key)) { canvas.classList = "victory"
event.preventDefault() victoryDialog.showModal()
let note = FIRST_NOTE + keyMap.indexOf(event.key)
cannonSprites[note].shooting = true
}
} }
document.onkeyup = function(event) { function gameOver(time) {
if (playing && keyMap.includes(event.key)) { playing = false
event.preventDefault()
let note = FIRST_NOTE + keyMap.indexOf(event.key) cannonSprites.forEach(cannonSprite => {
cannonSprites[note].shooting = false let explosionSprite = cannonSprite.explose()
explosionSprites.push(explosionSprite)
explosionSprite.play().then(() => explosionSprites.remove(explosionSprite))
})
playNoise(0.7, 400, 2, time)
Tone.Transport.clear(updateTaskId)
Tone.Transport.scheduleOnce((time) => {
Tone.Transport.stop(time)
}, time + 0.1)
gameOverDialog.showModal()
} }
victoryDialog.onclose = gameOverDialog.onclose = function() {
document.location = ""
} }

BIN
img/fireworks.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 B

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,8 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang='fr' dir="ltr" prefix="og: https://ogp.me/ns#">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Angry notes from outer space</title> <title>🎶🎶🎶</title>
<link rel="icon" href="img/favicon.png"> <link rel="icon" href="img/favicon.png">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin=""> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
@ -11,10 +11,19 @@
<link href="style.css" rel="stylesheet"> <link href="style.css" rel="stylesheet">
<script src="https://unpkg.com/tone" ></script> <script src="https://unpkg.com/tone" ></script>
<script src="https://unpkg.com/@tonejs/midi" ></script> <script src="https://unpkg.com/@tonejs/midi" ></script>
<meta property="og:title" content="🎶Angry notes from outer space🎶"/>
<meta property="og:type" content="game"/>
<meta property="og:url" content="https://adrien.malingrey.fr/jeux/angry-notes/"/>
<meta property="og:image" content="https://adrien.malingrey.fr/jeux/angry-notes/thumbnail.png"/>
<meta property="og:image:width" content="149"/>
<meta property="og:image:height" content="108"/>
<meta property="og:description" content="Jean-Michel, les notes furieuses de l'espace attaquent&nbsp;! Détruisez-les à l'aide de votre harpe laser."/>
<meta property="og:locale" content="fr_FR"/>
<meta property="og:site_name" content="adrien.malingrey.fr"/>
</head> </head>
<body> <body>
<section class="nes-container is-dark" style="padding: 0;"> <section class="nes-container is-dark" style="padding: 0;">
<canvas style="background-image: url(img/pixel-city-chill.gif); background-size: cover;" id="canvas" width="960" height="540" tabindex="9"></canvas> <canvas id="canvas" width="960" height="540" tabindex="9"></canvas>
</section> </section>
<dialog id="startDialog" class="nes-dialog is-rounded is-dark"> <dialog id="startDialog" class="nes-dialog is-rounded is-dark">
<form method="dialog"> <form method="dialog">
@ -35,21 +44,20 @@
<form method="dialog"> <form method="dialog">
<h2 class="title is-centered">Options</h2> <h2 class="title is-centered">Options</h2>
<section class="nes-container with-title is-dark"> <section class="nes-container with-title is-dark">
<h3 class="title">Contrôles</h3> <h3 class="title">Clavier MIDI</h3>
<div class="nes-field">
<label for="keyMapInput">Clavier d'ordinateur</label>
<div style="overflow-x: scroll;padding: 2px;">
<input type="text" id="keyMapInput" class="nes-input is-dark"
minlength="25" maxlength="25" size="25" required
placeholder="wsxdcvgbhnj,e'r(tyèu_içop" value="wsxdcvgbhnj,e'r(tyèu_içop"/>
</div>
</div>
<label for="midiSelect" style="color:#fff">Clavier MIDI</label>
<div class="nes-select is-dark"> <div class="nes-select is-dark">
<select id="midiSelect"> <select id="midiSelect" tabindex="3">
<option value="">Aucun</option> <option value="">Aucun (utiliser les touches ci-dessous)</option>
</select> </select>
</div> </div>
<div class="nes-field" style="display: flex; flex-direction: column-reverse;">
<div style="overflow-x: scroll;padding: 2px;">
<input type="text" id="keyMapInput" class="nes-textarea is-dark"
minlength="25" maxlength="25" size="25" required tabindex="2"
title="Cliquez pour changer une touche"
placeholder="ccddeffggaabccddeffggaabc" value="wsexdrcftvgybhunji,ko;lp:"/>
</div>
</div>
</section> </section>
<section class="nes-container with-title is-dark"> <section class="nes-container with-title is-dark">
<h3 class="title">Son</h3> <h3 class="title">Son</h3>
@ -69,7 +77,7 @@
</div> </div>
</section> </section>
<menu class="is-centered"> <menu class="is-centered">
<button id="playButton" class="nes-btn is-primary" tabindex="1">OK</button> <button id="playButton" class="nes-btn is-primary" autofocus>OK</button>
</menu> </menu>
</form> </form>
</dialog> </dialog>
@ -78,7 +86,16 @@
<h2 id="levelTitle" class="title is-centered">Niveau X</h2> <h2 id="levelTitle" class="title is-centered">Niveau X</h2>
<h3 id="songNameTitle" class="title is-centered">Titre</h3> <h3 id="songNameTitle" class="title is-centered">Titre</h3>
<div class="is-centered"> <div class="is-centered">
<button class="nes-btn is-primary" tabindex="1">Jouer</button> <button class="nes-btn is-primary" autofocus>Jouer</button>
</div>
</form>
</dialog>
<dialog id="victoryDialog" class="nes-dialog is-rounded is-dark">
<form method="dialog">
<h2 class="title is-centered">Victoire !</h2>
<p>Vous avez vaincu la musique.</p>
<div class="is-centered">
<button class="nes-btn is-primary" autofocus>Rejouer ?</button>
</div> </div>
</form> </form>
</dialog> </dialog>
@ -86,7 +103,7 @@
<form method="dialog"> <form method="dialog">
<h2 class="title is-centered">Game over</h2> <h2 class="title is-centered">Game over</h2>
<div class="is-centered"> <div class="is-centered">
<button class="nes-btn is-primary" tabindex="1">Rejouer ?</button> <button class="nes-btn is-primary" autofocus>Rejouer ?</button>
</div> </div>
</form> </form>
</dialog> </dialog>

Binary file not shown.

BIN
midi/4.mid Normal file

Binary file not shown.

View File

@ -4,7 +4,7 @@ html, body, pre, code, kbd, samp {
body { body {
background-color: #212529; background-color: #212529;
font-size: 12; font-size: 12px;
display: flex; display: flex;
align-content: center; align-content: center;
height: 100vh; height: 100vh;
@ -27,12 +27,22 @@ body::before,
pointer-events: none; pointer-events: none;
} }
#canvas {
background-image: url(img/pixel-city-chill.gif);
background-size: cover;
}
#canvas.victory {
background-image: url(img/fireworks.gif);
background-size: contain;
}
.nes-dialog { .nes-dialog {
max-width: 90%; max-width: 90%;
} }
h1 { h1 {
text-shadow: 0 4px #adafbc; text-shadow: 0 3px #adafbc;
} }
.is-centered { .is-centered {
@ -47,11 +57,22 @@ h1 {
color: white; color: white;
text-shadow: #444 0 2px, #444 2px 0, #444 0 -2px, #444 -2px 0; text-shadow: #444 0 2px, #444 2px 0, #444 0 -2px, #444 -2px 0;
text-transform: uppercase; text-transform: uppercase;
padding-left: 3px; padding-left: 4px;
padding-right: 0; padding-right: 0;
letter-spacing: 4px; letter-spacing: 4px;
width: calc(25em + 108px); width: calc(25em + 108px);
height: 54px; height: 54px;
font-size: 16px;
caret-color: transparent;
}
#keyMapInput::selection {
color: #209cee;
}
#keyMapInput,
input[type=range]{
cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAzElEQVRYR+2X0Q6AIAhF5f8/2jYXZkwEjNSVvVUjDpcrGgT7FUkI2D9xRfQETwNIiWO85wfINfQUEyxBG2ArsLwC0jioGt5zFcwF4OYDPi/mBYKm4t0U8ATgRm3ThFoAqkhNgWkA0jJLvaOVSs7j3qMnSgXWBMiWPXe94QqMBMBc1VZIvaTu5u5pQewq0EqNZvIEMCmxAawK0DNkay9QmfFNAJUXfgGgUkLaE7j/h8fnASkxHTz0DGIBMCnBeeM7AArpUd3mz2x3C7wADglA8BcWMZhZAAAAAElFTkSuQmCC) 14 0, pointer;
} }
.sliders { .sliders {
@ -145,7 +166,20 @@ svg {
height: 32px; height: 32px;
} }
.nes-balloon, .nes-balloon.is-dark, .nes-btn, .nes-container.is-rounded, .nes-container.is-rounded.is-dark, .nes-dialog.is-rounded, .nes-dialog.is-rounded.is-dark, .nes-progress, .nes-progress.is-rounded, .nes-table.is-bordered, .nes-table.is-dark.is-bordered, .nes-input, .nes-textarea, .nes-select select { .nes-container,
.nes-balloon,
.nes-balloon.is-dark,.nes-btn,
.nes-container.is-rounded,
.nes-container.is-rounded.is-dark,
.nes-dialog.is-rounded,
.nes-dialog.is-rounded.is-dark,
.nes-progress,
.nes-progress.is-rounded,
.nes-table.is-bordered,
.nes-table.is-dark.is-bordered,
.nes-input,
.nes-textarea,
.nes-select select {
border-style: solid; border-style: solid;
border-width: 2px; border-width: 2px;
border-image-outset: 2; border-image-outset: 2;

View File

@ -1,50 +0,0 @@
<body>
<label for="duration">Duration</label>
<input
name="duration"
id="duration"
type="range"
min="0"
max="2"
value="1"
step="0.1" />
<label for="band">Band</label>
<input
name="band"
id="band"
type="range"
min="400"
max="1200"
value="1000"
step="5" />
</body>
<script>
const audioCtx = new AudioContext();
function playNoise(noiseDuration, bandHz=1000) {
const bufferSize = audioCtx.sampleRate * noiseDuration
const noiseBuffer = new AudioBuffer({
length: bufferSize,
sampleRate: audioCtx.sampleRate,
})
const data = noiseBuffer.getChannelData(0)
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const noise = new AudioBufferSourceNode(audioCtx, {
buffer: noiseBuffer,
})
const bandpass = new BiquadFilterNode(audioCtx, {
type: "bandpass",
frequency: bandHz,
})
const gain = new GainNode(audioCtx)
gain.gain.setValueCurveAtTime([0.5, 1/10, 0], audioCtx.currentTime, noiseDuration)
noise.connect(bandpass).connect(gain).connect(audioCtx.destination)
noise.start()
}
</script>