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
'Software > JavaScript' 카테고리의 다른 글
| Javascript 시작하기 - 그림판 (0) | 2026.05.10 |
|---|---|
| Javascript 시작하기 - 40음계 피아노 (0) | 2026.05.09 |
| Javascript 시작하기 - ThreeJS로 육각형 그려서 크기 바꾸기#1 (0) | 2026.02.08 |
| Javascript 시작하기 - ThreeJS로 선그리기 #1 (0) | 2026.02.08 |
| 음성 인식 & 출력 예제 #1 (0) | 2026.02.06 |
