Compare commits
12 Commits
182de5b3fc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cb6749034 | |||
| 7d75324835 | |||
| 605ccb2893 | |||
| 629999e44d | |||
| 2e7c4e7c19 | |||
| f096b13c65 | |||
| 69c16647cf | |||
| ad05712e20 | |||
| 18b03a4975 | |||
| 257a354be7 | |||
| 40900f472c | |||
| 455f96bb3d |
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Angry Notes From Outer Space
|
||||
|
||||
Blast angry notes from outer space with your MIDI keyboard !
|
||||
|
||||

|
||||
77
app.js
77
app.js
@ -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) => {
|
||||
|
||||
33
index.html
33
index.html
@ -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 ! 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
BIN
midi/4.mid
Normal file
Binary file not shown.
Reference in New Issue
Block a user