
HTMLだけで作る面白いビジュアルシンセサイザー
自宅をクラブにして遊んでみた時に「シンセサイザー」があれば面白いと思いませんか?
超簡単HTML1枚のみでそれを実現することができます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>生体ピクセル・シンセサイザー</title>
<style>
/* 制限を逆手に取る:あえてピクセルを粗くして「レトロ/生体感」を演出 */
body {
margin: 0;
overflow: hidden;
background-color: #0a0a0a;
font-family: 'Courier New', monospace;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
/* キモ:ピクセルをボヤけさせず、ジャギー(角張り)を強調することで生物的な揺らぎを際立たせる */
image-rendering: crisp-edges;
image-rendering: pixelated;
cursor: none;
}
#ui {
position: fixed;
bottom: 30px;
left: 0;
right: 0;
text-align: center;
color: rgba(180, 255, 200, 0.6);
font-size: 14px;
letter-spacing: 2px;
pointer-events: none;
text-shadow: 0 0 15px rgba(0, 255, 100, 0.3);
background: rgba(0,0,0,0.4);
padding: 12px;
backdrop-filter: blur(4px);
width: fit-content;
margin: 0 auto;
border-radius: 40px;
border: 1px solid rgba(255,255,255,0.05);
}
#ui span {
display: inline-block;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0% { opacity: 0.4; transform: scale(1); }
50% { opacity: 1; transform: scale(1.02); }
100% { opacity: 0.4; transform: scale(1); }
}
/* マウスの座標を表示するミニマルなクロスヘア(代替) */
#freq-display {
position: fixed;
top: 20px;
right: 30px;
color: #8affb2;
font-size: 13px;
background: rgba(0,0,0,0.7);
padding: 8px 16px;
border-radius: 20px;
border-left: 3px solid #00ff88;
pointer-events: none;
opacity: 0.7;
backdrop-filter: blur(2px);
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="freq-display">🎵 440Hz</div>
<div id="ui"><span>🖱️ 左右で音程 / 上下で音量 & 歪み | クリックで音をON</span></div>
<script>
(function(){
"use strict";
// --- 1. キャンバス設定 (制限の中での解像度戦略) ---
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// あえて解像度を 120x80 に制限。これが「制限を面白さに変える」魔法。
const COLS = 120;
const ROWS = 80;
let W, H;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = COLS;
canvas.height = ROWS;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
}
window.addEventListener('resize', resize);
resize();
// --- 2. Web Audio API (ブラウザの制限: ユーザージェスチャーで起動) ---
let audioCtx = null;
let oscillator = null;
let gainNode = null;
let isAudioReady = false;
function initAudio() {
if (isAudioReady) return;
try {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// メイン音源:サイン波+少しの歪みを加えるため、2つ目のオシレーターを用意
oscillator = audioCtx.createOscillator();
const oscillator2 = audioCtx.createOscillator();
const gain2 = audioCtx.createGain();
gainNode = audioCtx.createGain();
gainNode.gain.value = 0.15; // 初期音量
// 倍音を加えて厚みを出す(制限環境でもリッチに聞こせる工夫)
oscillator.type = 'sawtooth'; // 鋸歯状波で温かみ
oscillator.frequency.value = 440;
oscillator2.type = 'sine';
oscillator2.frequency.value = 880; // 1オクターブ上
gain2.gain.value = 0.03; // サブ的に混ぜる
// 接続
oscillator.connect(gainNode);
oscillator2.connect(gain2);
gain2.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.start();
oscillator2.start();
isAudioReady = true;
document.getElementById('ui').innerHTML = '<span>🔊 音が鳴ってる! マウスを動かして遊んでね</span>';
} catch(e) {
alert('このブラウザでは音が出せませんが、ビジュアルは楽しめます!');
}
}
// ユーザーのクリックで音を起動 (ブラウザの自動再生ポリシー対策)
document.addEventListener('click', () => {
if (!isAudioReady) {
initAudio();
} else if (audioCtx && audioCtx.state === 'suspended') {
audioCtx.resume();
}
});
// --- 3. マウス / タッチ 情報 ---
let mouseX = 0.5; // 0-1 正規化
let mouseY = 0.5;
const freqDisplay = document.getElementById('freq-display');
function updateMouse(clientX, clientY) {
mouseX = Math.min(1, Math.max(0, clientX / window.innerWidth));
mouseY = Math.min(1, Math.max(0, clientY / window.innerHeight));
// 音程を更新 (100Hz ~ 1200Hz)
if (isAudioReady && oscillator) {
const freq = 80 + (mouseX * 1100);
oscillator.frequency.setTargetAtTime(freq, audioCtx.currentTime, 0.01);
freqDisplay.textContent = `🎵 ${Math.round(freq)}Hz`;
// 音量をマウスYに連動 (0.01 ~ 0.35)
const vol = 0.01 + (mouseY * 0.34);
gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.01);
}
}
document.addEventListener('mousemove', (e) => {
updateMouse(e.clientX, e.clientY);
});
// タッチ対応 (スマホでも遊べる)
document.addEventListener('touchmove', (e) => {
e.preventDefault();
const touch = e.touches[0];
if (touch) updateMouse(touch.clientX, touch.clientY);
}, { passive: false });
// マウスがウィンドウ外に出たら、中央に固定しない(最後の位置を保持)
// --- 4. コア:ピクセルシェーダーごっこ (生のImageDataを操る) ---
// ここが「マップ」や「3D」より遥かに面白いエンジン
let time = 0;
function draw() {
time += 0.02;
// ImageData を取得 (ピクセル配列)
const imageData = ctx.getImageData(0, 0, COLS, ROWS);
const data = imageData.data;
// マウスの影響をピクセル座標に変換 (0~COLS, 0~ROWS)
const mx = mouseX * COLS;
const my = mouseY * ROWS;
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
const i = (y * COLS + x) * 4;
// --- ここが「生物的な歪み」の核 ---
// 1. マウスからの距離で波紋を作る
const dx = x - mx;
const dy = y - my;
const dist = Math.sqrt(dx*dx + dy*dy) + 0.1;
// 2. 時間と座標で変化する複雑な波 (サインとコサインを掛け合わせてノイズ感)
const wave1 = Math.sin(x * 0.3 + time * 1.5) * Math.cos(y * 0.2 + time * 1.2);
const wave2 = Math.sin(dist * 0.4 - time * 2.0) * 0.8;
const wave3 = Math.sin(x * 0.05 + y * 0.07 + time * 0.8) * 0.5;
// 3. マウスYで「歪みの強さ」が変わる (上に上げると激しく歪む)
const distortion = 0.5 + mouseY * 1.5;
let val = (wave1 * 0.7 + wave2 * 0.5 + wave3 * 0.3) * distortion;
// 4. -1~1 の値を 0~1 にクランプ
val = (val + 1) / 2;
val = Math.min(1, Math.max(0, val));
// --- カラーマッピング (単なるグレーじゃない! 有機的な蛍光色) ---
// マウスXで色相がシフト (左:青緑系、右:赤紫系)
const hueShift = mouseX * 0.8;
// 赤: 波の値に応じて、かつX/Yで偏りをつける
const r = Math.sin(val * Math.PI * 2 + hueShift) * 0.8 + 0.6;
// 緑: 中心は強めに
const g = Math.cos(val * Math.PI * 1.5 - hueShift * 0.5) * 0.7 + 0.7;
// 青: 波の逆相で
const b = Math.sin(val * Math.PI * 1.8 + 1.2 - hueShift) * 0.6 + 0.6;
// 距離が近いほど白っぽく光らせる (マウス周辺がホットスポットに)
const glow = Math.min(1, 2.5 / (dist + 0.5));
data[i] = Math.min(255, (r * 0.8 + glow * 0.4) * 255);
data[i + 1] = Math.min(255, (g * 0.7 + glow * 0.5) * 255);
data[i + 2] = Math.min(255, (b * 0.9 + glow * 0.3) * 255);
data[i + 3] = 255; // 不透明度
}
}
// 描画
ctx.putImageData(imageData, 0, 0);
requestAnimationFrame(draw);
}
// --- 5. 起動 ---
draw();
// 初期表示用にダミーで音程表示
freqDisplay.textContent = '🎵 待機中... (クリックで起動)';
// ウィンドウリサイズ時はキャンバスの見た目だけ変わる (解像度は固定)
console.log('🔥 生体ピクセルシンセ起動! 制限を楽しむ新しい遊び方だ!');
})();
</script>
</body>
</html>