Compare commits

...

12 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
4 changed files with 60 additions and 55 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)

77
app.js
View File

@ -116,7 +116,8 @@ class Sprite {
class Cannon extends Sprite {
constructor(canvasCtx, note) {
let sharp = [1, 3, 6, 8, 10].includes(note % 12)
super(canvasCtx, "cannon.png", 34 * (note - FIRST_NOTE) + 66, sharp? 418:424, 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.key = keyMap[note - FIRST_NOTE]?.toUpperCase() || ""
this.impactHeight = 9
@ -260,7 +261,7 @@ canvasCtx.imageSmoothingEnabled = false
canvasCtx.font = '12px "Press Start 2P"'
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 cannonSprites = []
for (let note=FIRST_NOTE; note<LAST_NOTE; note++) cannonSprites[note] = new Cannon(canvasCtx, note)
@ -315,7 +316,7 @@ function init() {
}
settingsDialog.onclose = newGame
showSettings()
pause()
}
startDialog.onclose = init
@ -339,22 +340,19 @@ function draw() {
batterySprite.draw()
}
function showSettings() {
pause()
settingsDialog.showModal()
}
window.onblur = showSettings
window.onblur = pause
function pause() {
Tone.Transport.pause()
playing = false
settingsDialog.showModal()
}
settingsButton.onclick = showSettings
settingsButton.onclick = pause
keyMapInput.onclick = keyMapInput.onkeyup = function(event) {
let cursorPosition = keyMapInput.selectionEnd
if ((event.key == "ArrowRight" && cursorPosition <= keyMapInput.value.length) || cursorPosition == 0) {
if (cursorPosition == 0 || (event.key == "ArrowRight" && cursorPosition <= keyMapInput.value.length)) {
keyMapInput.setSelectionRange(cursorPosition, cursorPosition+1)
} else {
keyMapInput.setSelectionRange(cursorPosition-1, cursorPosition)
@ -438,8 +436,8 @@ let midiSong
let noteSprites = []
let explosionSprites = []
let health
function nextLevel(time=0) {
Tone.Transport.pause()
function nextLevel(time=Tone.Transport.seconds) {
Tone.Transport.pause(time)
level++
midiSong = Midi.fromUrl(`midi/${level}.mid`).then((midi) => {
midiSong = midi
@ -460,7 +458,7 @@ function nextLevel(time=0) {
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()
}).catch((error) => {
@ -477,18 +475,23 @@ function resume() {
}
document.onkeydown = function(event) {
if (playing && keyMap.includes(event.key)) {
event.preventDefault()
let note = FIRST_NOTE + keyMap.indexOf(event.key)
shoot(note)
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 (playing && keyMap.includes(event.key)) {
event.preventDefault()
let note = FIRST_NOTE + keyMap.indexOf(event.key)
stopShoot(note)
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)
}
}
@ -508,7 +511,7 @@ function shoot(note) {
var oscillator = audioCtx.createOscillator({type: "sawtooth"})
oscillator.type = "sawtooth"
oscillator.frequency.value = FREQUENCIES[note - 12]
oscillator.frequency.value = FREQUENCIES[note - 24]
oscillator.velocity = audioCtx.createGain()
oscillator.velocity.gain.value = 0
@ -528,8 +531,7 @@ function stopShoot(note) {
if (!cannonSprites[note].oscillator) return
var oscillator = cannonSprites[note].oscillator
velocity = oscillator.velocity.gain.value
oscillator.velocity.gain.setValueCurveAtTime([velocity, velocity/10, velocity/20, 0], Tone.Transport.seconds, 0.5)
oscillator.velocity.gain.exponentialRampToValueAtTime(0.01, Tone.Transport.seconds, 0.5)
oscillator.stop(Tone.Transport.seconds + 0.5)
delete(cannonSprites[note].oscillator)
@ -576,7 +578,7 @@ function update(time) {
})
}
function playNote(note, velocity=0.7, duration=0, time=0) {
function playNote(note, velocity=0.7, duration=0, time=audioCtx.currentTime) {
if(oscillators[note]) return
var oscillator = audioCtx.createOscillator()
@ -592,19 +594,14 @@ function playNote(note, velocity=0.7, duration=0, time=0) {
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
velocity = oscillators[note].velocity.gain.value
oscillators[note].velocity.gain.setValueCurveAtTime([velocity, velocity/10, velocity/20, 0], time, 0.5)
oscillators[note].velocity.gain.exponentialRampToValueAtTime(0.01, time + 0.5)
oscillators[note].stop(time + 0.5)
delete(oscillators[note])
@ -628,7 +625,8 @@ function playNoise(startGain=0.5, bandHz=1000, duration, time) {
frequency: bandHz,
})
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.start()
noise.stop(time + duration)
@ -645,16 +643,9 @@ function gameOver(time) {
cannonSprites.forEach(cannonSprite => {
let explosionSprite = cannonSprite.explose()
explosionSprites.push(explosionSprite)
explosionSprite.play().then(() => {console.log(cannonSprite.note); explosionSprites.remove(explosionSprite)})
})
playNoise(0.7, 400, 2, time)
/*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) => {

View File

@ -1,8 +1,8 @@
<!DOCTYPE html>
<html lang="fr">
<html lang='fr' dir="ltr" prefix="og: https://ogp.me/ns#">
<head>
<meta charset="utf-8">
<title>Angry notes from outer space</title>
<title>🎶🎶🎶</title>
<link rel="icon" href="img/favicon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
@ -11,6 +11,15 @@
<link href="style.css" rel="stylesheet">
<script src="https://unpkg.com/tone" ></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>
<body>
<section class="nes-container is-dark" style="padding: 0;">
@ -36,17 +45,17 @@
<h2 class="title is-centered">Options</h2>
<section class="nes-container with-title is-dark">
<h3 class="title">Clavier MIDI</h3>
<div class="nes-select is-dark">
<select id="midiSelect" tabindex="3">
<option value="">Aucun (utiliser les touches ci-dessous)</option>
</select>
</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="wsxdcvgbhnj,e'r(tyèu_içop" value="wsxdcvgbhnj,e'r(tyèu_içop"/>
</div>
<div class="nes-select is-dark">
<select id="midiSelect" tabindex="3">
<option value="">Aucun (utiliser les touches ci-dessous)</option>
</select>
placeholder="ccddeffggaabccddeffggaabc" value="wsexdrcftvgybhunji,ko;lp:"/>
</div>
</div>
</section>
@ -68,7 +77,7 @@
</div>
</section>
<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>
</form>
</dialog>
@ -77,7 +86,7 @@
<h2 id="levelTitle" class="title is-centered">Niveau X</h2>
<h3 id="songNameTitle" class="title is-centered">Titre</h3>
<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>
@ -86,7 +95,7 @@
<h2 class="title is-centered">Victoire !</h2>
<p>Vous avez vaincu la musique.</p>
<div class="is-centered">
<button class="nes-btn is-primary" tabindex="1">Rejouer ?</button>
<button class="nes-btn is-primary" autofocus>Rejouer ?</button>
</div>
</form>
</dialog>
@ -94,7 +103,7 @@
<form method="dialog">
<h2 class="title is-centered">Game over</h2>
<div class="is-centered">
<button class="nes-btn is-primary" tabindex="1">Rejouer ?</button>
<button class="nes-btn is-primary" autofocus>Rejouer ?</button>
</div>
</form>
</dialog>

BIN
midi/4.mid Normal file

Binary file not shown.