以前のブラウザには、MIDI を再生させるための機能がサポートされていましたが、HTML5 が出たあたりから、そのしようがなくなってしまったそうです。 現代のブラウザで、どうやってがんばるかいろいろ大変で、私自身も試行錯誤しました。
このホームページは、Wordpress を利用しており、Thema として、Cocoon を使っています。 いろいろ試行錯誤して、うまくいったのを忘備録として残しておきます。
外観 → テーマファイルエディター → functions.php に追加したのは、下記のコードです。 当初はローカルでの仕組みがなければ、Microsoft GS Wavetable Synth が勝手になってくれると思ったのですがそうはいきませんでした。 そこで、midijs.net の助けを借りることにしました。 このコードでは Website が表にないと再生がおかしくなりますが、この点は目をつぶることにしました。 令和時代の MIDI 再生 で示したようにすれば、下記のコードで、仮想 MIDI ポートを通して、サウンドフォントを利用した再生が可能です。
// Web MIDI API 演奏システム(MIDIjs ハイブリッド版・表示テキスト最適化版)
function add_local_midi_player_script() {
echo '<script src="https://midijs.net"></script>';
?>
<script>
document.addEventListener('DOMContentLoaded', () => {
let midiOutputPort = null;
let currentMidiBuffer = null;
let activeTimeouts = [];
let isWebAudioMode = true;
let midiUrl = "";
const playBtn = document.getElementById('localMidiPlayBtn');
const stopBtn = document.getElementById('localMidiStopBtn');
const statusText = document.getElementById('midi-status-text');
if (!playBtn || !stopBtn) return;
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess().then((midiAccess) => {
const outputs = Array.from(midiAccess.outputs.values());
midiOutputPort = outputs.find(port =>
port.name.toLowerCase().includes("loopmidi") ||
port.name.toLowerCase().includes("iac")
);
if (midiOutputPort) {
// ご希望の「🎹 [ポート名] から再生します」に変更
statusText.innerText = `🎹 ${midiOutputPort.name} から再生`;
isWebAudioMode = false;
} else {
statusText.innerText = "🔊 内部ブラウザ音源(MIDIjs)で再生";
isWebAudioMode = true;
}
loadMidiFile();
}).catch(err => {
statusText.innerText = "🔊 内部ブラウザ音源(MIDIjs)で再生";
isWebAudioMode = true;
loadMidiFile();
});
} else {
statusText.innerText = "🔊 内部ブラウザ音源(MIDIjs)で再生";
isWebAudioMode = true;
loadMidiFile();
}
function loadMidiFile() {
midiUrl = playBtn.getAttribute('data-midi-url');
if (!midiUrl) return;
fetch(midiUrl)
.then(response => {
if (!response.ok) throw new Error('Network error');
return response.arrayBuffer();
})
.then(arrayBuffer => {
currentMidiBuffer = arrayBuffer;
playBtn.disabled = false;
console.log("MIDIバイナリデータの取得に成功しました。");
})
.catch(err => {
statusText.innerText = "❌ MIDIファイルの読み込みに失敗しました";
console.error(err);
});
}
function resetButtons() {
playBtn.innerText = "再生する";
playBtn.disabled = false;
stopBtn.disabled = true;
}
function allNotesOff() {
activeTimeouts.forEach(timeoutId => clearTimeout(timeoutId));
activeTimeouts = [];
if (midiOutputPort) {
for (let ch = 0; ch < 16; ch++) {
try {
midiOutputPort.send([0xB0 + ch, 0x7B, 0]);
midiOutputPort.send([0xB0 + ch, 0x78, 0]);
midiOutputPort.send([0xB0 + ch, 0x40, 0]);
} catch(e) {}
}
}
if (typeof MIDIjs !== 'undefined') {
try {
MIDIjs.stop();
} catch(e) {}
}
}
function playMidiStream(buffer) {
const data = new DataView(buffer);
let p = 0;
if (data.getUint32(p) !== 0x4D546864) {
console.error("無効なMIDIファイル形式です");
return;
}
p += 8;
const format = data.getUint16(p); p += 2;
const numTracks = data.getUint16(p); p += 2;
const ticksPerBeat = data.getUint16(p); p += 2;
let tempo = 500000;
let allEvents = [];
for (let t = 0; t < numTracks; t++) {
if (p >= data.byteLength) break;
if (data.getUint32(p) !== 0x4D54726B) {
p += 4; let len = data.getUint32(p); p += 4 + len; continue;
}
p += 4; let trackLen = data.getUint32(p); p += 4;
let trackEnd = p + trackLen;
let currentTicks = 0;
let runningStatus = 0;
while (p < trackEnd && p < data.byteLength) {
let delta = 0;
while (true) {
let b = data.getUint8(p++);
delta = (delta << 7) | (b & 0x7F);
if (!(b & 0x80)) break;
}
currentTicks += delta;
let status = data.getUint8(p);
if (status & 0x80) {
runningStatus = status;
p++;
} else {
status = runningStatus;
}
let msgType = status & 0xF0;
let channel = status & 0x0F;
if (status === 0xFF) {
let metaType = data.getUint8(p++);
let len = 0;
while (true) {
let b = data.getUint8(p++);
len = (len << 7) | (b & 0x7F);
if (!(b & 0x80)) break;
}
if (metaType === 0x51) {
let tVal = (data.getUint8(p) << 16) | (data.getUint8(p+1) << 8) | data.getUint8(p+2);
allEvents.push({ ticks: currentTicks, type: 'tempo', val: tVal });
}
p += len;
} else if (status === 0xF0 || status === 0xF7) {
let len = 0;
while (true) {
let b = data.getUint8(p++);
len = (len << 7) | (b & 0x7F);
if (!(b & 0x80)) break;
}
let sysexData = [status];
for(let i=0; i<len; i++) sysexData.push(data.getUint8(p+i));
allEvents.push({ ticks: currentTicks, type: 'midi', bytes: sysexData });
p += len;
} else {
let bytes = [status];
if (msgType === 0xC0 || msgType === 0xD0) {
bytes.push(data.getUint8(p++));
} else {
bytes.push(data.getUint8(p++));
bytes.push(data.getUint8(p++));
}
allEvents.push({ ticks: currentTicks, type: 'midi', bytes: bytes });
}
}
}
allEvents.sort((a, b) => a.ticks - b.ticks);
let lastTicks = 0;
let currentTimeMs = 0;
allEvents.forEach(ev => {
let deltaTicks = ev.ticks - lastTicks;
let deltaTimeMs = (deltaTicks * tempo) / (ticksPerBeat * 1000);
currentTimeMs += deltaTimeMs;
lastTicks = ev.ticks;
if (ev.type === 'tempo') {
tempo = ev.val;
} else if (ev.type === 'midi') {
const tId = setTimeout(() => {
if (midiOutputPort) {
midiOutputPort.send(ev.bytes);
}
}, currentTimeMs);
activeTimeouts.push(tId);
}
});
const endId = setTimeout(() => {
resetButtons();
}, currentTimeMs + 500);
activeTimeouts.push(endId);
}
playBtn.addEventListener('click', () => {
allNotesOff();
playBtn.innerText = "⏳ 再生中";
playBtn.disabled = true;
stopBtn.disabled = false;
if (isWebAudioMode) {
if (typeof MIDIjs !== 'undefined' && midiUrl) {
try {
MIDIjs.play(midiUrl);
MIDIjs.get_duration(midiUrl, (duration) => {
if (duration > 0) {
const endId = setTimeout(() => {
resetButtons();
}, (duration * 1000) + 500);
activeTimeouts.push(endId);
}
});
} catch (e) {
console.error("MIDIjs 再生エラー:", e);
resetButtons();
}
} else {
resetButtons();
}
} else {
if (currentMidiBuffer) {
playMidiStream(currentMidiBuffer);
} else {
resetButtons();
}
}
});
stopBtn.addEventListener('click', () => {
allNotesOff();
resetButtons();
});
});
</script>
<?php
}
add_action('wp_footer', 'add_local_midi_player_script');
再生のコードは次の通りです。 外観 → カスタマイズ → ウィジェット → サイドバーとして、カスタムHTML を選んでいます。 なぜか、外観 → ウィジェットとして入力すると、カスタムHTML では保存ができません。 また、テキストにすれば保存できますが、いつのまにか、.mid ファイルのあたりが消えてしまいます。
<div class="midi-player" style="margin: 20px 0; padding: 15px; border: 1px solid #e29fa7; border-radius: 5px; background: #fffafb;">
<p style="margin: 0 0 0px 0; color: #a1646c; font-weight: bold;">Widmung (Schumann-Liszt)</p>
<!-- ポート名を表示するエリア(自動で切り替わります) -->
<p id="midi-status-text" style="margin: 0 0 10px 0; color: #1c1516; font-size: 14px; padding-left: 30px;">サウンドフォント使用推奨(読み込み中...)</p>
<div style="display: flex; gap: 10px;">
<!-- 再生ボタン: ' ' の中に実際のMIDIファイルのURLを書き換えてください -->
<button id="localMidiPlayBtn"
data-midi-url="https://schumann.jp/wp-content..............."
style="padding: 10px 20px; background: #e29fa7; color: #fff; border: none; border-radius: 3px; cursor: pointer;"
disabled>再生する</button>
<!-- 停止ボタン -->
<button id="localMidiStopBtn"
style="padding: 10px 20px; background: #8e8586; color: #fff; border: none; border-radius: 3px; cursor: pointer;"
disabled>停止する</button>
</div>
</div>
コメント