|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Chord Arpeggio Pad</title> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script> |
|
<style> |
|
body { |
|
font-family: Arial, sans-serif; |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
height: 100vh; |
|
margin: 0; |
|
background-color: #121212; |
|
color: #ffffff; |
|
} |
|
.container { |
|
text-align: center; |
|
} |
|
.arpeggio-pad { |
|
display: grid; |
|
grid-template-columns: repeat(4, 1fr); |
|
gap: 10px; |
|
margin-bottom: 20px; |
|
} |
|
.pad { |
|
width: 80px; |
|
height: 80px; |
|
font-size: 14px; |
|
border: none; |
|
color: white; |
|
cursor: pointer; |
|
transition: all 0.3s; |
|
border-radius: 8px; |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
align-items: center; |
|
} |
|
.pad:hover { |
|
opacity: 0.8; |
|
} |
|
.pad:active, .pad.active { |
|
transform: scale(0.95); |
|
box-shadow: 0 0 15px rgba(255, 255, 255, 0.5); |
|
} |
|
h1 { |
|
margin-bottom: 20px; |
|
} |
|
#effectsButton { |
|
font-size: 18px; |
|
padding: 10px 20px; |
|
background-color: #4CAF50; |
|
border: none; |
|
color: white; |
|
cursor: pointer; |
|
border-radius: 5px; |
|
transition: background-color 0.3s; |
|
} |
|
#effectsButton:hover { |
|
background-color: #45a049; |
|
} |
|
#effectsButton.active { |
|
background-color: #e91e63; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<h1>Chord Arpeggio Pad</h1> |
|
<div class="arpeggio-pad" id="arpeggiopad"></div> |
|
<button id="effectsButton">Effects: OFF</button> |
|
</div> |
|
|
|
<script> |
|
const arpeggiopad = document.getElementById('arpeggiopad'); |
|
const effectsButton = document.getElementById('effectsButton'); |
|
const synth = new Tone.PolySynth(Tone.Synth).toDestination(); |
|
|
|
|
|
const distortion = new Tone.Distortion(0.8).toDestination(); |
|
const reverb = new Tone.Reverb(2).toDestination(); |
|
let effectsOn = false; |
|
|
|
const arpeggios = [ |
|
|
|
{ chord: 'C', notes: ['C4', 'E4', 'G4', 'C5'], color: '#FF0000' }, |
|
{ chord: 'C', notes: ['E4', 'G4', 'C5', 'E5'], color: '#FF4000' }, |
|
{ chord: 'C', notes: ['G4', 'C5', 'E5', 'G5'], color: '#FF8000' }, |
|
{ chord: 'C', notes: ['C5', 'E5', 'G5', 'C6'], color: '#FFC000' }, |
|
|
|
|
|
{ chord: 'F', notes: ['F4', 'A4', 'C5', 'F5'], color: '#FFFF00' }, |
|
{ chord: 'F', notes: ['A4', 'C5', 'F5', 'A5'], color: '#C0FF00' }, |
|
{ chord: 'F', notes: ['C5', 'F5', 'A5', 'C6'], color: '#80FF00' }, |
|
{ chord: 'F', notes: ['F5', 'A5', 'C6', 'F6'], color: '#40FF00' }, |
|
|
|
|
|
{ chord: 'G', notes: ['G4', 'B4', 'D5', 'G5'], color: '#00FF00' }, |
|
{ chord: 'G', notes: ['B4', 'D5', 'G5', 'B5'], color: '#00FF40' }, |
|
{ chord: 'G', notes: ['D5', 'G5', 'B5', 'D6'], color: '#00FF80' }, |
|
{ chord: 'G', notes: ['G5', 'B5', 'D6', 'G6'], color: '#00FFC0' }, |
|
|
|
|
|
{ chord: 'Am', notes: ['A4', 'C5', 'E5', 'A5'], color: '#00FFFF' }, |
|
{ chord: 'Am', notes: ['C5', 'E5', 'A5', 'C6'], color: '#00C0FF' }, |
|
{ chord: 'Am', notes: ['E5', 'A5', 'C6', 'E6'], color: '#0080FF' }, |
|
{ chord: 'Am', notes: ['A5', 'C6', 'E6', 'A6'], color: '#0040FF' } |
|
]; |
|
|
|
arpeggios.forEach(({ chord, notes, color }, index) => { |
|
const pad = document.createElement('button'); |
|
pad.innerHTML = `${chord}<br>${notes[0]}-${notes[3]}`; |
|
pad.className = 'pad'; |
|
pad.dataset.notes = JSON.stringify(notes); |
|
pad.style.backgroundColor = color; |
|
arpeggiopad.appendChild(pad); |
|
}); |
|
|
|
function toggleEffects() { |
|
effectsOn = !effectsOn; |
|
if (effectsOn) { |
|
synth.disconnect(); |
|
synth.connect(distortion); |
|
synth.connect(reverb); |
|
effectsButton.textContent = 'Effects: ON'; |
|
effectsButton.classList.add('active'); |
|
} else { |
|
synth.disconnect(); |
|
synth.toDestination(); |
|
effectsButton.textContent = 'Effects: OFF'; |
|
effectsButton.classList.remove('active'); |
|
} |
|
} |
|
|
|
function playArpeggio(notes, pad) { |
|
pad.classList.add('active'); |
|
const now = Tone.now(); |
|
notes.forEach((note, index) => { |
|
synth.triggerAttackRelease(note, "8n", now + index * 0.2); |
|
}); |
|
setTimeout(() => pad.classList.remove('active'), notes.length * 200); |
|
} |
|
|
|
arpeggiopad.addEventListener('mousedown', (e) => { |
|
if (e.target.classList.contains('pad')) { |
|
const notes = JSON.parse(e.target.dataset.notes); |
|
playArpeggio(notes, e.target); |
|
} |
|
}); |
|
|
|
effectsButton.addEventListener('click', toggleEffects); |
|
|
|
|
|
document.addEventListener('click', async () => { |
|
await Tone.start(); |
|
console.log('Audio is ready'); |
|
}, { once: true }); |
|
</script> |
|
</body> |
|
</html> |