<!DOCTYPE html>
<html lang="ja" >
<head>
<meta charset="UTF-8" >
<title>MIDI Game Final Fix</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js" ></script>
<script src="https://unpkg.com/@tonejs/midi" ></script>
<style>
body { margin: 0 ; background:
canvas {
display: block;
margin: 10 px auto;
background:
border: 2 px solid
cursor: crosshair;
}
position: absolute;
top: 0 ;
left: 0 ;
width: 100 %;
background: rgba(0 , 0 , 0 , 0.8 );
padding: 10 px;
box-sizing: border-box;
border-bottom: 1 px solid
display: flex;
gap: 10 px;
align-items: center;
justify-content: center;
}
button {
background:
padding: 5 px 15 px; cursor: pointer; font-size: 14 px;
}
button:hover { background:
</style>
</head>
<body>
<div id="ui" >
<span>MIDI音ゲー</span>
<input type="file" id="fileInput" accept=".mid,.midi" style="width: 200px;" >
<button id="testBtn" >🔊 テスト音</button>
<button id="startBtn" disabled>スタート</button>
<span id="debugInfo" >待機中...</span>
</div>
<canvas id="gameCanvas" width="600" height="500" ></canvas>
<script>
const CANVAS_WIDTH = 600 ;
const CANVAS_HEIGHT = 500 ;
const TARGET_Y = 400 ;
const TARGET_HEIGHT = 60 ;
const TARGET_BOTTOM = TARGET_Y + TARGET_HEIGHT;
let canvas = document.getElementById('gameCanvas' );
let ctx = canvas.getContext('2d' );
let notes = [];
let isPlaying = false ;
let startTime = 0 ;
let synth = null ;
let areaFlash = 0.0 ;
let debugClick = null ;
const debugInfo = document.getElementById('debugInfo' );
window.onload = () => {
console.log("初期化開始" );
synth = new Tone.PolySynth(Tone.Synth, {
volume: -5 ,
oscillator: { type: "square" }
}).toDestination();
requestAnimationFrame(loop);
};
function loop () {
ctx.fillStyle = "#222" ;
ctx.fillRect(0 , 0 , CANVAS_WIDTH, CANVAS_HEIGHT);
if (areaFlash > 0 ) areaFlash -= 0.1 ;
if (areaFlash < 0 ) areaFlash = 0 ;
let r = 0 , g = 255 , b = 255 ;
if (areaFlash > 0 ) {
r = 255 ;
}
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${0.3 + areaFlash * 0.4 })`;
ctx.fillRect(0 , TARGET_Y, CANVAS_WIDTH, TARGET_HEIGHT);
ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, 1 )`;
ctx.lineWidth = 2 ;
ctx.strokeRect(0 , TARGET_Y, CANVAS_WIDTH, TARGET_HEIGHT);
ctx.fillStyle = "white" ;
ctx.font = "14px monospace" ;
ctx.fillText("JUDGEMENT AREA (CLICK HERE)" , 10 , TARGET_Y + 35 );
const now = isPlaying ? (Tone.now() - startTime) : 0 ;
notes.forEach (note => {
if (isPlaying) {
note.y = TARGET_Y - (note.time - now) * 200 ;
}
if (note.flash > 0 ) {
note.flash -= 0.1 ;
if (note.flash < 0 ) note.flash = 0 ;
}
if (note.y > -50 && note.y < CANVAS_HEIGHT) {
ctx.fillStyle = note.flash > 0 ? "white" : note.color;
ctx.fillRect(note.x, note.y, note.w, note.h);
ctx.strokeRect(note.x, note.y, note.w, note.h);
}
});
if (debugClick) {
ctx.beginPath();
ctx.arc(debugClick.x, debugClick.y, 10 , 0 , Math.PI * 2 );
ctx.fillStyle = "yellow" ;
ctx.fill();
debugClick.timer--;
if (debugClick.timer <= 0 ) debugClick = null ;
}
requestAnimationFrame(loop);
}
canvas.addEventListener('mousedown' , onInput);
canvas.addEventListener('touchstart' , (e) => { e.preventDefault(); onInput(e.touches[0 ]); }, {passive: false });
async function onInput (e) {
if (Tone.context.state !== 'running' ) {
await Tone.start();
}
const rect = canvas.getBoundingClientRect();
const x = (e.clientX || e.pageX) - rect.left;
const y = (e.clientY || e.pageY) - rect.top;
debugInfo.innerText = `Click: X=${Math.floor(x)} Y=${Math.floor(y)}`;
debugClick = { x: x, y: y, timer: 10 };
if (y >= TARGET_Y && y <= TARGET_BOTTOM) {
areaFlash = 1.0 ;
synth.triggerAttackRelease("C5" , "32n" );
notes.forEach (note => {
if (note.y + note.h >= TARGET_Y && note.y <= TARGET_BOTTOM) {
playNote(note);
}
});
}
else {
notes.forEach (note => {
if (x >= note.x && x <= note.x + note.w &&
y >= note.y && y <= note.y + note.h) {
playNote(note);
}
});
}
}
function playNote (note) {
synth.triggerAttackRelease(note.name, "8n" );
note.flash = 1.0 ;
}
document.getElementById('testBtn' ).addEventListener('click' , async () => {
await Tone.start();
synth.triggerAttackRelease("C4" , "8n" );
debugInfo.innerText = "テスト音再生" ;
areaFlash = 1.0 ;
});
document.getElementById('fileInput' ).addEventListener('change' , async (e) => {
const file = e.target.files[0 ];
if (!file) return ;
debugInfo.innerText = "MIDI解析中..." ;
const reader = new FileReader();
reader.onload = function (ev) {
try {
const midiData = new Midi(ev.target.result);
notes = [];
const colors = ["#F00" , "#0F0" , "#00F" , "#FF0" , "#0FF" , "#F0F" ];
midiData.tracks.forEach ((track, i) => {
track.notes.forEach (n => {
notes.push({
name: n.name, time: n.time,
x: 50 + (n.midi % 12 ) * 42 , y: -100 , w: 40 , h: 20 ,
color: colors[i % colors.length], flash: 0
});
});
});
notes.sort((a,b) => a.time - b.time);
document.getElementById('startBtn' ).disabled = false ;
debugInfo.innerText = `読込完了: ${notes.length}音`;
} catch (err) {
console.error(err);
debugInfo.innerText = "エラー" ;
}
};
reader.readAsArrayBuffer(file);
});
document.getElementById('startBtn' ).addEventListener('click' , async () => {
await Tone.start();
startTime = Tone.now() + 1.0 ;
isPlaying = true ;
debugInfo.innerText = "再生中" ;
});
</script>
</body>
</html>
copy
コメント