Compare commits
24 Commits
406ae270b2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cb6749034 | |||
| 7d75324835 | |||
| 605ccb2893 | |||
| 629999e44d | |||
| 2e7c4e7c19 | |||
| f096b13c65 | |||
| 69c16647cf | |||
| ad05712e20 | |||
| 18b03a4975 | |||
| 257a354be7 | |||
| 40900f472c | |||
| 455f96bb3d | |||
| 182de5b3fc | |||
| e97c535fc6 | |||
| 90df10e141 | |||
| 0a09ad4e24 | |||
| fe0e618c76 | |||
| 3fac8105d2 | |||
| a85f092ce7 | |||
| 0d024ad390 | |||
| 8fe3699d70 | |||
| e960b48b27 | |||
| b0cc701226 | |||
| 22e15c6397 |
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 !
|
||||||
|
|
||||||
|

|
||||||
212
app.js
212
app.js
@ -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
BIN
img/fireworks.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 196 KiB |
BIN
img/keyboard.png
BIN
img/keyboard.png
Binary file not shown.
|
Before Width: | Height: | Size: 626 B After Width: | Height: | Size: 4.2 KiB |
53
index.html
53
index.html
@ -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 ! 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>
|
||||||
|
|||||||
BIN
midi/1.mid
BIN
midi/1.mid
Binary file not shown.
BIN
midi/4.mid
Normal file
BIN
midi/4.mid
Normal file
Binary file not shown.
42
style.css
42
style.css
@ -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;
|
||||||
|
|||||||
50
test.html
50
test.html
@ -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>
|
|
||||||
Reference in New Issue
Block a user