音效合成器edit icon

Fork(复制)
下载
嵌入
BUG反馈
index.html
现在支持上传本地图片了!
index.html
            
            <!DOCTYPE html>
<html lang="zh-Hant">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>互動音樂合成器</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background: #1a1a1a;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center; /* 新增居中 */
            font-family: Arial, sans-serif;
        }

        .container {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 15px;
            width: 90%; /* 响应式宽度 */
            max-width: 800px;
            padding: 20px;
        }

        .box {
            width: 100%;
            padding-bottom: 100%; /* 保持正方形 */
            background: #4CAF50;
            border-radius: 12px;
            cursor: pointer;
            position: relative;
            overflow: hidden;
            box-shadow: 0 4px 15px rgba(0,0,0,0.2);
            transition: 
                transform 0.1s ease,
                filter 0.3s ease;
        }

        .box-inner {
            position: absolute;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white; /* 新增文字 */
            font-size: 1.2em;
        }

        .box.holding {
            filter: brightness(1.3) saturate(1.2);
            transform: scale(0.95);
            box-shadow: 0 0 25px rgba(255,255,255,0.4);
        }

        /* 動畫效果修正 */
        .wave::after {
            content: '';
            position: absolute;
            width: 200%;
            height: 200%;
            background: radial-gradient(circle, rgba(255,255,255,0.8) 10%, transparent 30%);
            animation: wave 1.2s linear;
        }

        .pulse {
            animation: pulse 0.8s ease-out infinite;
        }

        .spin {
            animation: spin 1.2s linear;
        }

        .gradient::before {
            content: '';
            position: absolute;
            width: 200%;
            height: 200%;
            background: linear-gradient(45deg, 
                #ff0000 0%,
                #00ff00 50%,
                #0000ff 100%);
            animation: gradient 4s linear infinite;
        }

        @keyframes wave {
            from { transform: scale(0); opacity: 1; }
            to { transform: scale(1); opacity: 0; }
        }

        @keyframes pulse {
            50% { transform: scale(1.1); }
        }

        @keyframes spin {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }

        @keyframes gradient {
            0% { transform: translate(-50%, -50%) rotate(0deg); }
            100% { transform: translate(-50%, -50%) rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="container" id="container"></div>

    <script>
        // 強制初始化音頻上下文
        let audioContext;
        function initAudio() {
            if(!audioContext) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
            }
        }

        // 自動生成和諧音階
        const generateHarmonicFrequencies = () => {
            const baseFreq = 130.81; // C3
            const ratios = [1, 1.25, 1.5, 1.875]; // C大三和弦+七音
            const octaves = [1, 2, 4, 8]; // 4個八度
            return octaves.flatMap(oct => 
                ratios.map(r => baseFreq * oct * r)
            ).slice(0, 16);
        };

        // 創建可視化方塊
        const createBox = (freq, index) => {
            const box = document.createElement('div');
            box.className = 'box';
            
            const inner = document.createElement('div');
            inner.className = 'box-inner';
            inner.textContent = `${Math.round(freq)}Hz`; // 顯示頻率
            
            box.appendChild(inner);
            box.dataset.frequency = freq;
            box.dataset.animation = ['wave', 'pulse', 'spin', 'gradient'][index % 4];
            box.style.backgroundColor = `hsl(${index * 45}, 70%, 50%)`;
            
            return box;
        };

        // 初始化界面
        const container = document.getElementById('container');
        generateHarmonicFrequencies().forEach((freq, i) => {
            container.appendChild(createBox(freq, i));
        });

        // 音頻系統
        const activeSynths = new Map();

        const createSynth = (freq) => {
            initAudio();
            
            const osc = audioContext.createOscillator();
            const gain = audioContext.createGain();
            
            osc.type = ['sine', 'square', 'sawtooth'][Math.floor(Math.random()*3)];
            osc.frequency.value = freq;
            
            gain.gain.setValueAtTime(0.2, audioContext.currentTime);
            
            osc.connect(gain);
            gain.connect(audioContext.destination);
            
            osc.start();
            return { osc, gain };
        };

        // 事件處理
        const handleStart = (e) => {
            const box = e.target.closest('.box');
            if (!box) return;
            
            box.classList.add('holding', box.dataset.animation);
            const freq = parseFloat(box.dataset.frequency);
            
            if (!activeSynths.has(box)) {
                activeSynths.set(box, createSynth(freq));
            }
        };

        const handleEnd = (e) => {
            const box = e.target.closest('.box');
            if (!box) return;
            
            box.classList.remove('holding');
            box.classList.remove(box.dataset.animation);
            
            const synth = activeSynths.get(box);
            if (synth) {
                synth.gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.5);
                synth.osc.stop(audioContext.currentTime + 0.5);
                activeSynths.delete(box);
            }
        };

        // 綁定事件
        container.addEventListener('mousedown', handleStart);
        container.addEventListener('mouseup', handleEnd);
        container.addEventListener('mouseleave', handleEnd);
        container.addEventListener('touchstart', (e) => {
            e.preventDefault();
            handleStart(e.touches[0]);
        }, { passive: false });
        container.addEventListener('touchend', handleEnd);
    </script>
</body>
</html>
        
编辑器加载中
预览
控制台