Compare commits
3 Commits
012161c4d6
...
406ae270b2
Author | SHA1 | Date | |
---|---|---|---|
406ae270b2 | |||
1286d0b8c4 | |||
f2b1e696bb |
154
app.js
154
app.js
@ -1,11 +1,15 @@
|
|||||||
const MAX_LEVEL = 3
|
const MAX_LEVEL = 3
|
||||||
const DRAW_PERIOD = 0.04 // s
|
const DRAW_PERIOD = 0.04 // s
|
||||||
|
const FLOOR = 390 // px
|
||||||
|
const TIME_TO_SCREEN = 10 // s
|
||||||
const UPDATE_PERIOD = 0.01 // s
|
const UPDATE_PERIOD = 0.01 // s
|
||||||
const FLOOR = 386 // px
|
const STEP = FLOOR * UPDATE_PERIOD / TIME_TO_SCREEN // px
|
||||||
const FIRST_NOTE = 48 // C2
|
const FIRST_NOTE = 48 // C2
|
||||||
const LAST_NOTE = 73 // C4
|
const LAST_NOTE = 73 // C4
|
||||||
|
const NOTE_NAMES = [
|
||||||
|
"C", "C♯", "D", "D♯", "E", "F", "F♯", "G", "G♯", "A", "A♯", "B"
|
||||||
|
]
|
||||||
const FREQUENCIES = [
|
const FREQUENCIES = [
|
||||||
// C C♯ / D♭ D D♯ / E♭ E F F♯ / G♭ G G♯ / A♭ A A♯ / B♭ B
|
|
||||||
16.35, 17.32, 18.35, 19.45, 20.6, 21.83, 23.12, 24.5, 25.96, 27.5, 29.14, 30.87,
|
16.35, 17.32, 18.35, 19.45, 20.6, 21.83, 23.12, 24.5, 25.96, 27.5, 29.14, 30.87,
|
||||||
32.7, 34.65, 36.71, 38.89, 41.2, 43.65, 46.25, 49, 51.91, 55, 58.27, 61.74,
|
32.7, 34.65, 36.71, 38.89, 41.2, 43.65, 46.25, 49, 51.91, 55, 58.27, 61.74,
|
||||||
65.41, 69.3, 73.42, 77.78, 82.41, 87.31, 92.5, 98, 103.83, 110, 116.54, 123.47,
|
65.41, 69.3, 73.42, 77.78, 82.41, 87.31, 92.5, 98, 103.83, 110, 116.54, 123.47,
|
||||||
@ -16,7 +20,6 @@ const FREQUENCIES = [
|
|||||||
2093, 2217.46, 2349.32, 2489.02, 2637.02, 2793.83, 2959.96, 3135.96, 3322.44, 3520, 3729.31, 3951.07,
|
2093, 2217.46, 2349.32, 2489.02, 2637.02, 2793.83, 2959.96, 3135.96, 3322.44, 3520, 3729.31, 3951.07,
|
||||||
4186.01, 4434.92, 4698.64, 4978.03, 5274.04, 5587.65, 5919.91, 6271.93, 6644.88, 7040, 7458.62, 7902.13,
|
4186.01, 4434.92, 4698.64, 4978.03, 5274.04, 5587.65, 5919.91, 6271.93, 6644.88, 7040, 7458.62, 7902.13,
|
||||||
]
|
]
|
||||||
const NOTE_NAMES = ["C", "C♯", "D", "D♯", "E", "F", "F♯", "G", "G♯", "A", "A♯", "B"]
|
|
||||||
|
|
||||||
|
|
||||||
Array.prototype.remove = function(item) {
|
Array.prototype.remove = function(item) {
|
||||||
@ -92,15 +95,15 @@ class Sprite {
|
|||||||
|
|
||||||
onanimationend () {}
|
onanimationend () {}
|
||||||
|
|
||||||
draw() {
|
draw(deltaX=0, deltaY=0) {
|
||||||
canvasCtx.drawImage(
|
canvasCtx.drawImage(
|
||||||
this.sprite,
|
this.sprite,
|
||||||
this.sx + this.frame * this.sWidth,
|
this.sx + this.frame * this.sWidth,
|
||||||
this.sy,
|
this.sy,
|
||||||
this.sWidth,
|
this.sWidth,
|
||||||
this.sHeight,
|
this.sHeight,
|
||||||
this.dx,
|
this.dx + deltaX,
|
||||||
this.dy,
|
this.dy + deltaY,
|
||||||
this.dWidth,
|
this.dWidth,
|
||||||
this.dHeight
|
this.dHeight
|
||||||
)
|
)
|
||||||
@ -124,7 +127,7 @@ class Cannon extends Sprite {
|
|||||||
this.pipeSprite = new Sprite(canvasCtx, "pipe.png", this.x-1, this.y+36, 16, 18)
|
this.pipeSprite = new Sprite(canvasCtx, "pipe.png", this.x-1, this.y+36, 16, 18)
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw(deltaX=0, deltaY=0) {
|
||||||
if (this.shooting) {
|
if (this.shooting) {
|
||||||
this.frame += this.df
|
this.frame += this.df
|
||||||
if (this.frame >= this.frames) this.df = -1
|
if (this.frame >= this.frames) this.df = -1
|
||||||
@ -132,17 +135,21 @@ class Cannon extends Sprite {
|
|||||||
} else {
|
} else {
|
||||||
if (this.frame > 0) this.frame--
|
if (this.frame > 0) this.frame--
|
||||||
}
|
}
|
||||||
this.pipeSprite.draw()
|
this.pipeSprite.draw(deltaX, deltaY)
|
||||||
this.canvasCtx.fillStyle = "#d3d6cf"
|
this.canvasCtx.fillStyle = "#d3d6cf"
|
||||||
this.canvasCtx.fillText(this.key, this.pipeSprite.x+2, this.pipeSprite.y+10)
|
this.canvasCtx.fillText(this.key, this.pipeSprite.x+2, this.pipeSprite.y+9)
|
||||||
this.canvasCtx.fillStyle = "#222327"
|
this.canvasCtx.fillStyle = "#222327"
|
||||||
this.canvasCtx.fillText(this.key, this.pipeSprite.x, this.pipeSprite.y+8)
|
this.canvasCtx.fillText(this.key, this.pipeSprite.x, this.pipeSprite.y+8)
|
||||||
super.draw()
|
super.draw(deltaX, deltaY)
|
||||||
if (this.frame) {
|
if (this.frame) {
|
||||||
this.canvasCtx.drawImage(this.sprite, this.sWidth*(this.frame), 0, this.sWidth, 1, this.dx, this.impactY, 22, this.dy - this.impactY)
|
this.canvasCtx.drawImage(this.sprite, this.sWidth*(this.frame), 0, this.sWidth, 1, this.dx, this.impactY, 22, this.dy - this.impactY)
|
||||||
if (this.impactY) this.canvasCtx.drawImage(this.sprite, this.sWidth*(this.frame), 2*this.sHeight, this.sWidth, this.impactHeight, this.dx, this.impactY, this.dWidth, 2*this.impactHeight)
|
if (this.impactY) this.canvasCtx.drawImage(this.sprite, this.sWidth*(this.frame), 2*this.sHeight, this.sWidth, this.impactHeight, this.dx, this.impactY, this.dWidth, 2*this.impactHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
explose() {
|
||||||
|
return new Sprite(canvasCtx, "big-explosion.png", this.x, this.y, 48, 48, 8)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -164,11 +171,11 @@ class Note extends Sprite {
|
|||||||
this.time++
|
this.time++
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw(deltaX=0, deltaY=0) {
|
||||||
if (this.shot) {
|
if (this.shot) {
|
||||||
this.drawShot()
|
this.drawShot()
|
||||||
} else {
|
} else {
|
||||||
super.draw()
|
super.draw(deltaX, deltaY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +227,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, 99, 36, 40, 1)
|
super(canvasCtx, note, duration, velocity, 36, 100, 36, 40, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
animate() {}
|
animate() {}
|
||||||
@ -255,8 +262,11 @@ let consoleSprite = new Sprite(canvasCtx, "console.png", canvas.width/2, 554, 4
|
|||||||
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)
|
||||||
|
let batterySprite = new Sprite(canvasCtx, "battery.png", 890, 40, 44, 13, 13)
|
||||||
|
batterySprite.frame = 12
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
|
draw()
|
||||||
startDialog.showModal()
|
startDialog.showModal()
|
||||||
//window.setInterval(draw, 60)
|
//window.setInterval(draw, 60)
|
||||||
}
|
}
|
||||||
@ -268,6 +278,7 @@ let mod
|
|||||||
let depth
|
let depth
|
||||||
let compressor
|
let compressor
|
||||||
let oscillators = {}
|
let oscillators = {}
|
||||||
|
let updateTaskId
|
||||||
function init() {
|
function init() {
|
||||||
Tone.start()
|
Tone.start()
|
||||||
|
|
||||||
@ -295,20 +306,31 @@ function init() {
|
|||||||
onpartialinput()
|
onpartialinput()
|
||||||
|
|
||||||
Tone.Transport.scheduleRepeat(draw, DRAW_PERIOD)
|
Tone.Transport.scheduleRepeat(draw, DRAW_PERIOD)
|
||||||
Tone.Transport.scheduleRepeat(update, UPDATE_PERIOD)
|
updateTaskId = Tone.Transport.scheduleRepeat(update, UPDATE_PERIOD)
|
||||||
|
|
||||||
|
settingsDialog.onclose = newGame
|
||||||
showSettings()
|
showSettings()
|
||||||
}
|
}
|
||||||
startDialog.onclose = init
|
startDialog.onclose = init
|
||||||
|
|
||||||
|
let animateConsole = 0
|
||||||
function draw() {
|
function draw() {
|
||||||
canvasCtx.clearRect(0, 0, canvas.width, canvas.height)
|
canvasCtx.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
|
|
||||||
consoleSprite.draw()
|
if (animateConsole) {
|
||||||
cannonSprites.forEach(cannonSprite => cannonSprite.draw())
|
animateConsole++
|
||||||
|
if (animateConsole > 5) {
|
||||||
|
animateConsole = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let deltaY = animateConsole? 4 : 0;
|
||||||
|
|
||||||
|
consoleSprite.draw(0, deltaY)
|
||||||
|
cannonSprites.forEach(cannonSprite => cannonSprite.draw(0, deltaY))
|
||||||
noteSprites.forEach(noteSprite => noteSprite.draw())
|
noteSprites.forEach(noteSprite => noteSprite.draw())
|
||||||
syntheSprite.draw()
|
syntheSprite.draw(0, -deltaY)
|
||||||
explosionSprites.forEach(explosionSprite => explosionSprite.draw())
|
explosionSprites.forEach(explosionSprite => explosionSprite.draw())
|
||||||
|
batterySprite.draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSettings() {
|
function showSettings() {
|
||||||
@ -394,19 +416,14 @@ function onMIDIMessage(event) {
|
|||||||
let [code, note, velocity] = event.data
|
let [code, note, velocity] = event.data
|
||||||
|
|
||||||
if (144 <= code && code <= 159 && cannonSprites[note]) {
|
if (144 <= code && code <= 159 && cannonSprites[note]) {
|
||||||
//playNote(note, velocity / 128)
|
|
||||||
cannonSprites[note].shooting = true
|
cannonSprites[note].shooting = true
|
||||||
} else if (128 <= code && code <= 143 && cannonSprites[note]) {
|
} else if (128 <= code && code <= 143 && cannonSprites[note]) {
|
||||||
//stopNote(note)
|
|
||||||
cannonSprites[note].shooting = false
|
cannonSprites[note].shooting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsDialog.onclose = newGame
|
|
||||||
|
|
||||||
let level
|
let level
|
||||||
function newGame() {
|
function newGame() {
|
||||||
settingsDialog.onclose = resume
|
|
||||||
level = 0
|
level = 0
|
||||||
nextLevel()
|
nextLevel()
|
||||||
}
|
}
|
||||||
@ -414,52 +431,65 @@ function newGame() {
|
|||||||
let midiSong
|
let midiSong
|
||||||
let noteSprites = []
|
let noteSprites = []
|
||||||
let explosionSprites = []
|
let explosionSprites = []
|
||||||
let speed
|
let health
|
||||||
async function nextLevel() {
|
async function nextLevel(time=0) {
|
||||||
|
Tone.Transport.pause()
|
||||||
level++
|
level++
|
||||||
midiSong = await Midi.fromUrl(`midi/${level}.mid`)
|
if (level <= MAX_LEVEL) {
|
||||||
levelTitle.innerText = `Niveau ${level}`
|
health = 12
|
||||||
songNameTitle.innerText = midiSong.name
|
batterySprite.frame = health
|
||||||
speed = 4 * UPDATE_PERIOD * FLOOR / midiSong.header.tempos[0].bpm
|
midiSong = await Midi.fromUrl(`midi/${level}.mid`)
|
||||||
noteSprites = []
|
levelTitle.innerText = `Niveau ${level}`
|
||||||
midiSong.tracks.forEach(track => {
|
songNameTitle.innerText = midiSong.name
|
||||||
//console.log(track.name)
|
noteSprites = []
|
||||||
track.notes.filter(note => FIRST_NOTE <= note.midi && note.midi <= LAST_NOTE).forEach(note => {
|
midiSong.tracks.forEach(track => {
|
||||||
let noteSprite
|
//console.log(track.name)
|
||||||
let durationInQuarter = note.durationTicks / midiSong.header.ppq
|
track.notes.filter(note => FIRST_NOTE <= note.midi && note.midi <= LAST_NOTE).forEach(note => {
|
||||||
if (durationInQuarter <= 0.25) noteSprite = new Sixteenth(canvasCtx, note.midi, note.duration)
|
let noteSprite
|
||||||
else if (durationInQuarter <= 0.5) noteSprite = new Eighth(canvasCtx, note.midi, note.duration)
|
let durationInQuarter = note.durationTicks / midiSong.header.ppq
|
||||||
else if (durationInQuarter <= 1) noteSprite = new Quarter(canvasCtx, note.midi, note.duration)
|
if (durationInQuarter <= 0.25) noteSprite = new Sixteenth(canvasCtx, note.midi, note.duration)
|
||||||
else noteSprite = new Whole(canvasCtx, note.midi, note.duration)
|
else if (durationInQuarter <= 0.5) noteSprite = new Eighth(canvasCtx, note.midi, note.duration)
|
||||||
Tone.Transport.scheduleOnce(time => noteSprites.push(noteSprite), note.time)
|
else if (durationInQuarter <= 1) noteSprite = new Quarter(canvasCtx, note.midi, note.duration)
|
||||||
})
|
else noteSprite = new Whole(canvasCtx, note.midi, note.duration)
|
||||||
})
|
Tone.Transport.scheduleOnce(time => noteSprites.push(noteSprite), time + note.time)
|
||||||
Tone.Transport.scheduleOnce(time => nextLevel, midiSong.duration)
|
})
|
||||||
|
})
|
||||||
levelDialog.showModal()
|
Tone.Transport.scheduleOnce(time => nextLevel(time), time + midiSong.duration + TIME_TO_SCREEN)
|
||||||
|
|
||||||
|
levelDialog.showModal()
|
||||||
|
} else {
|
||||||
|
// win
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
levelDialog.onclose = resume
|
levelDialog.onclose = resume
|
||||||
|
|
||||||
let updateTaskId
|
|
||||||
function resume() {
|
function resume() {
|
||||||
|
settingsDialog.onclose = resume
|
||||||
playing = true
|
playing = true
|
||||||
Tone.Transport.start()
|
Tone.Transport.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
function update(time) {
|
function update(time) {
|
||||||
noteSprites.forEach(noteSprite => {
|
noteSprites.filter(noteSprite => !noteSprite.shot).forEach(noteSprite => {
|
||||||
noteSprite.y += speed
|
noteSprite.y += STEP
|
||||||
})
|
})
|
||||||
noteSprites.filter(noteSprite => noteSprite.y >= FLOOR).forEach(noteSprite => {
|
noteSprites.filter(noteSprite => noteSprite.y >= FLOOR).forEach(noteSprite => {
|
||||||
stopNote(noteSprite.note)
|
|
||||||
let explosionSprite = noteSprite.explose()
|
let explosionSprite = noteSprite.explose()
|
||||||
explosionSprites.push(explosionSprite)
|
explosionSprites.push(explosionSprite)
|
||||||
explosionSprite.play().then(() => explosionSprites.remove(explosionSprite))
|
explosionSprite.play().then(() => explosionSprites.remove(explosionSprite))
|
||||||
noteSprite.playNoise(time)
|
noteSprite.playNoise(time)
|
||||||
|
animateConsole = 1
|
||||||
|
health--
|
||||||
|
batterySprite.frame = health
|
||||||
})
|
})
|
||||||
noteSprites = noteSprites.filter(note => note.y < FLOOR)
|
noteSprites = noteSprites.filter(note => note.y < FLOOR)
|
||||||
|
|
||||||
|
if (health <= 0) {
|
||||||
|
gameOver(time)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
if (noteSprite) {
|
if (noteSprite) {
|
||||||
@ -480,6 +510,34 @@ function update(time) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gameOver(time) {
|
||||||
|
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) {
|
function playNote(note, velocity=0.7, duration=0, time=0) {
|
||||||
if(oscillators[note]) return
|
if(oscillators[note]) return
|
||||||
|
|
||||||
|
BIN
img/battery.png
Normal file
BIN
img/battery.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 949 B |
@ -82,6 +82,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
<dialog id="gameOverDialog" class="nes-dialog is-rounded is-dark">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
<button id="settingsButton" type="button" class="nes-btn">
|
<button id="settingsButton" type="button" class="nes-btn">
|
||||||
<span>
|
<span>
|
||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm16 5H4v2h16v-2z" fill="currentColor"></path></svg>
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M4 6h16v2H4V6zm0 5h16v2H4v-2zm16 5H4v2h16v-2z" fill="currentColor"></path></svg>
|
||||||
|
BIN
midi/1.mid
BIN
midi/1.mid
Binary file not shown.
BIN
midi/2.mid
BIN
midi/2.mid
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user