728x90

비트박스 

 

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 자동 작곡 비트박스</title>

<style>
*{
    margin:0;
    padding:0;
    box-sizing:border-box;
}

body{
    background:#0f0f0f;
    color:white;
    font-family:Arial;
    padding:20px;
}

h1{
    text-align:center;
    margin-bottom:20px;
    font-size:40px;
}

.controls{
    display:flex;
    gap:10px;
    justify-content:center;
    flex-wrap:wrap;
    margin-bottom:20px;
}

button{
    padding:12px 20px;
    background:#222;
    border:none;
    border-radius:10px;
    color:white;
    cursor:pointer;
    font-size:16px;
}

button:hover{
    background:#444;
}

.grid{
    display:grid;
    grid-template-columns:120px repeat(16,1fr);
    gap:5px;
    max-width:1400px;
    margin:auto;
}

.label{
    background:#1c1c1c;
    border-radius:8px;
    display:flex;
    justify-content:center;
    align-items:center;
    font-weight:bold;
    min-height:45px;
}

.cell{
    aspect-ratio:1;
    background:#181818;
    border:2px solid #333;
    border-radius:8px;
    cursor:pointer;
    transition:0.1s;
}

.cell.active{
    background:#00d2ff;
    box-shadow:0 0 10px #00d2ff;
}

.cell.playing{
    border-color:white;
    transform:scale(1.1);
}

.info{
    text-align:center;
    margin-top:20px;
    color:#aaa;
    line-height:1.8;
}

select,input{
    padding:10px;
    border-radius:8px;
    border:none;
}
</style>
</head>
<body>

<h1>🤖 AI 자동 작곡 비트박스</h1>

<div class="controls">

    <button id="playBtn">▶ 재생</button>
    <button id="stopBtn">■ 정지</button>
    <button id="aiBtn">🎲 AI 자동 작곡</button>
    <button id="clearBtn">🧹 전체 삭제</button>

    <select id="style">
        <option value="hiphop">HipHop</option>
        <option value="trap">Trap</option>
        <option value="house">House</option>
        <option value="edm">EDM</option>
        <option value="dnb">DnB</option>
    </select>

    <span>BPM</span>
    <input type="range" id="bpm" min="60" max="200" value="110">
    <span id="bpmText">110</span>

</div>

<div class="grid" id="grid"></div>

<div class="info">
AI 버튼을 누르면 자동으로 비트를 생성합니다.<br>
Kick / Snare / HiHat / Clap / Bass / Laser 지원
</div>

<script>

const instruments = [
    "Kick",
    "Snare",
    "HiHat",
    "Clap",
    "Bass",
    "Laser"
];

const STEPS = 16;

const grid = document.getElementById("grid");

const pattern = [];

instruments.forEach((inst,row)=>{

    pattern[row] = [];

    const label = document.createElement("div");
    label.className = "label";
    label.innerText = inst;

    grid.appendChild(label);

    for(let i=0;i<STEPS;i++){

        pattern[row][i] = false;

        const cell = document.createElement("div");
        cell.className = "cell";

        cell.dataset.row = row;
        cell.dataset.step = i;

        cell.onclick = ()=>{

            pattern[row][i] = !pattern[row][i];

            cell.classList.toggle("active");
        };

        grid.appendChild(cell);
    }
});

function updateGrid(){

    document.querySelectorAll(".cell").forEach(cell=>{

        const row = Number(cell.dataset.row);
        const step = Number(cell.dataset.step);

        cell.classList.toggle("active", pattern[row][step]);
    });
}

function clearPattern(){

    for(let r=0;r<instruments.length;r++){

        for(let s=0;s<STEPS;s++){

            pattern[r][s] = false;
        }
    }

    updateGrid();
}

function randomChance(percent){

    return Math.random() < percent;
}

function aiCompose(){

    clearPattern();

    const style = document.getElementById("style").value;

    const map = {};

    instruments.forEach((v,i)=>map[v]=i);

    // 공통 하이햇
    for(let i=0;i<16;i+=2){

        pattern[map.HiHat][i] = true;
    }

    if(style==="hiphop"){

        [0,4,8,12].forEach(i=>{
            pattern[map.Kick][i] = true;
        });

        [4,12].forEach(i=>{
            pattern[map.Snare][i] = true;
        });

        pattern[map.Bass][0] = true;
        pattern[map.Bass][8] = true;
    }

    if(style==="trap"){

        [0,6,10].forEach(i=>{
            pattern[map.Kick][i] = true;
        });

        [4,12].forEach(i=>{
            pattern[map.Snare][i] = true;
        });

        for(let i=0;i<16;i++){

            if(randomChance(0.9))
                pattern[map.HiHat][i] = true;
        }

        pattern[map.Bass][0] = true;
        pattern[map.Bass][4] = true;
        pattern[map.Bass][11] = true;
    }

    if(style==="house"){

        [0,4,8,12].forEach(i=>{
            pattern[map.Kick][i] = true;
        });

        [4,12].forEach(i=>{
            pattern[map.Clap][i] = true;
        });

        [2,6,10,14].forEach(i=>{
            pattern[map.HiHat][i] = true;
        });
    }

    if(style==="edm"){

        [0,4,8,12].forEach(i=>{
            pattern[map.Kick][i] = true;
        });

        [4,12].forEach(i=>{
            pattern[map.Snare][i] = true;
        });

        for(let i=0;i<16;i++){

            pattern[map.HiHat][i] = true;
        }

        [2,6,10,14].forEach(i=>{
            pattern[map.Laser][i] = true;
        });

        for(let i=0;i<16;i+=2){

            pattern[map.Bass][i] = true;
        }
    }

    if(style==="dnb"){

        [0,6,12].forEach(i=>{
            pattern[map.Kick][i] = true;
        });

        [4,12].forEach(i=>{
            pattern[map.Snare][i] = true;
        });

        for(let i=0;i<16;i++){

            pattern[map.HiHat][i] = true;
        }

        for(let i=0;i<16;i+=2){

            pattern[map.Bass][i] = true;
        }
    }

    // AI 랜덤 변형
    for(let r=0;r<instruments.length;r++){

        for(let s=0;s<16;s++){

            if(randomChance(0.05)){

                pattern[r][s] = !pattern[r][s];
            }
        }
    }

    updateGrid();
}

const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();

function noiseBuffer(){

    const buffer = audioCtx.createBuffer(1,44100,44100);

    const data = buffer.getChannelData(0);

    for(let i=0;i<44100;i++){

        data[i] = Math.random()*2-1;
    }

    return buffer;
}

function playSound(type){

    const now = audioCtx.currentTime;

    if(type==="Kick"){

        const osc = audioCtx.createOscillator();
        const gain = audioCtx.createGain();

        osc.type="sine";

        osc.frequency.setValueAtTime(150,now);
        osc.frequency.exponentialRampToValueAtTime(0.001,now+0.5);

        gain.gain.setValueAtTime(1,now);
        gain.gain.exponentialRampToValueAtTime(0.001,now+0.5);

        osc.connect(gain);
        gain.connect(audioCtx.destination);

        osc.start();
        osc.stop(now+0.5);
    }

    if(type==="Snare"){

        const noise = audioCtx.createBufferSource();
        noise.buffer = noiseBuffer();

        const gain = audioCtx.createGain();

        gain.gain.setValueAtTime(1,now);
        gain.gain.exponentialRampToValueAtTime(0.01,now+0.2);

        noise.connect(gain);
        gain.connect(audioCtx.destination);

        noise.start();
        noise.stop(now+0.2);
    }

    if(type==="HiHat"){

        const osc = audioCtx.createOscillator();
        const gain = audioCtx.createGain();

        osc.type="square";
        osc.frequency.value = 1200;

        gain.gain.setValueAtTime(0.3,now);
        gain.gain.exponentialRampToValueAtTime(0.01,now+0.05);

        osc.connect(gain);
        gain.connect(audioCtx.destination);

        osc.start();
        osc.stop(now+0.05);
    }

    if(type==="Clap"){

        for(let i=0;i<3;i++){

            const noise = audioCtx.createBufferSource();

            noise.buffer = noiseBuffer();

            const gain = audioCtx.createGain();

            const t = now + i*0.03;

            gain.gain.setValueAtTime(0.7,t);
            gain.gain.exponentialRampToValueAtTime(0.01,t+0.08);

            noise.connect(gain);
            gain.connect(audioCtx.destination);

            noise.start(t);
            noise.stop(t+0.08);
        }
    }

    if(type==="Bass"){

        const osc = audioCtx.createOscillator();
        const gain = audioCtx.createGain();

        osc.type="sawtooth";
        osc.frequency.value = 60;

        gain.gain.setValueAtTime(0.4,now);
        gain.gain.exponentialRampToValueAtTime(0.01,now+0.4);

        osc.connect(gain);
        gain.connect(audioCtx.destination);

        osc.start();
        osc.stop(now+0.4);
    }

    if(type==="Laser"){

        const osc = audioCtx.createOscillator();
        const gain = audioCtx.createGain();

        osc.type="sawtooth";

        osc.frequency.setValueAtTime(1500,now);
        osc.frequency.exponentialRampToValueAtTime(100,now+0.3);

        gain.gain.setValueAtTime(0.5,now);
        gain.gain.exponentialRampToValueAtTime(0.01,now+0.3);

        osc.connect(gain);
        gain.connect(audioCtx.destination);

        osc.start();
        osc.stop(now+0.3);
    }
}

let currentStep = 0;
let timer = null;

function highlight(step){

    document.querySelectorAll(".cell").forEach(c=>{
        c.classList.remove("playing");
    });

    document.querySelectorAll(`.cell[data-step='${step}']`)
        .forEach(c=>c.classList.add("playing"));
}

function start(){

    if(timer) return;

    const bpm = Number(document.getElementById("bpm").value);

    const interval = (60000 / bpm) / 4;

    timer = setInterval(()=>{

        highlight(currentStep);

        instruments.forEach((inst,row)=>{

            if(pattern[row][currentStep]){

                playSound(inst);
            }
        });

        currentStep++;

        if(currentStep>=16){

            currentStep = 0;
        }

    },interval);
}

function stop(){

    clearInterval(timer);

    timer = null;

    currentStep = 0;

    document.querySelectorAll(".cell").forEach(c=>{
        c.classList.remove("playing");
    });
}

document.getElementById("playBtn").onclick = async ()=>{

    await audioCtx.resume();

    stop();
    start();
};

document.getElementById("stopBtn").onclick = stop;

document.getElementById("aiBtn").onclick = ()=>{

    aiCompose();
};

document.getElementById("clearBtn").onclick = clearPattern;

document.getElementById("bpm").oninput = e=>{

    document.getElementById("bpmText").innerText = e.target.value;

    if(timer){

        stop();
        start();
    }
};

</script>

</body>
</html>
728x90

+ Recent posts