Last active
August 7, 2024 18:32
-
-
Save Zetaphor/9c44bd411163ecb84f643e20f003d999 to your computer and use it in GitHub Desktop.
ToneJS Beat Generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Intelligent Melody Generator</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; | |
flex-direction: column; | |
align-items: center; | |
min-height: 100vh; | |
margin: 0; | |
background-color: #f0f0f0; | |
padding: 20px; | |
box-sizing: border-box; | |
} | |
.container { | |
text-align: center; | |
background-color: white; | |
padding: 20px; | |
border-radius: 10px; | |
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); | |
max-width: 800px; | |
width: 100%; | |
} | |
h1 { | |
color: #333; | |
} | |
button { | |
font-size: 1.2em; | |
padding: 10px 20px; | |
margin: 10px; | |
cursor: pointer; | |
background-color: #4CAF50; | |
color: white; | |
border: none; | |
border-radius: 5px; | |
transition: background-color 0.3s; | |
} | |
button:hover { | |
background-color: #45a049; | |
} | |
button:disabled { | |
background-color: #cccccc; | |
cursor: not-allowed; | |
} | |
#melodyDisplay { | |
margin-top: 20px; | |
padding: 10px; | |
background-color: #f9f9f9; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
width: 100%; | |
font-family: monospace; | |
font-size: 1.2em; | |
box-sizing: border-box; | |
white-space: pre-wrap; | |
} | |
select, | |
input { | |
font-size: 1em; | |
padding: 5px; | |
margin: 5px; | |
} | |
#bpmInput { | |
width: 60px; | |
} | |
label { | |
font-weight: bold; | |
display: inline-block; | |
width: 150px; | |
text-align: right; | |
margin-right: 10px; | |
} | |
.control-group { | |
margin-bottom: 10px; | |
display: flex; | |
align-items: center; | |
justify-content: flex-start; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>Intelligent Melody Generator</h1> | |
<div class="control-group"> | |
<label for="instrumentSelect">Instrument:</label> | |
<select id="instrumentSelect"></select> | |
</div> | |
<div class="control-group"> | |
<label for="keySelect">Key:</label> | |
<select id="keySelect"></select> | |
</div> | |
<div class="control-group"> | |
<label for="scaleSelect">Scale:</label> | |
<select id="scaleSelect"></select> | |
</div> | |
<div class="control-group"> | |
<label for="chordProgressionSelect">Chord Progression:</label> | |
<select id="chordProgressionSelect"></select> | |
</div> | |
<div class="control-group"> | |
<label for="rhythmPatternSelect">Rhythm Pattern:</label> | |
<select id="rhythmPatternSelect"></select> | |
</div> | |
<div class="control-group"> | |
<label for="bpmInput">BPM:</label> | |
<input type="number" id="bpmInput" value="120" min="60" max="200"> | |
</div> | |
<button id="playButton">Play</button> | |
<button id="regenerateButton">Regenerate</button> | |
<button id="testButton">Test Sound</button> | |
<div id="melodyDisplay">Generated melody will appear here</div> | |
</div> | |
<script> | |
let isPlaying = false; | |
let currentInstrument; | |
let sequence; | |
const playButton = document.getElementById('playButton'); | |
const regenerateButton = document.getElementById('regenerateButton'); | |
const testButton = document.getElementById('testButton'); | |
const melodyDisplay = document.getElementById('melodyDisplay'); | |
const bpmInput = document.getElementById('bpmInput'); | |
const keySelect = document.getElementById('keySelect'); | |
const scaleSelect = document.getElementById('scaleSelect'); | |
const instrumentSelect = document.getElementById('instrumentSelect'); | |
const chordProgressionSelect = document.getElementById('chordProgressionSelect'); | |
const rhythmPatternSelect = document.getElementById('rhythmPatternSelect'); | |
const allNotes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; | |
const scales = { | |
major: [0, 2, 4, 5, 7, 9, 11], | |
minor: [0, 2, 3, 5, 7, 8, 10], | |
pentatonic: [0, 2, 4, 7, 9], | |
blues: [0, 3, 5, 6, 7, 10], | |
}; | |
const instruments = { | |
synth: Tone.Synth, | |
amSynth: Tone.AMSynth, | |
fmSynth: Tone.FMSynth, | |
membraneSynth: Tone.MembraneSynth, | |
metalSynth: Tone.MetalSynth | |
}; | |
const chordProgressions = { | |
'I-V-vi-IV': ['I', 'V', 'vi', 'IV'], | |
'ii-V-I': ['ii', 'V', 'I'], | |
'I-IV-V': ['I', 'IV', 'V'], | |
'vi-IV-I-V': ['vi', 'IV', 'I', 'V'] | |
}; | |
const rhythmPatterns = { | |
'Basic': [1, 0, 1, 0, 1, 0, 1, 0], | |
'Syncopated': [1, 0, 0, 1, 0, 1, 0, 0], | |
'Waltz': [1, 0, 0, 1, 0, 0], | |
'Complex': [1, 0, 1, 1, 0, 1, 0, 0] | |
}; | |
let generatedMelody = []; | |
function setupTone() { | |
populateSelects(); | |
changeInstrument(); | |
regenerateMelody(); | |
playButton.addEventListener('click', togglePlay); | |
regenerateButton.addEventListener('click', regenerateMelody); | |
testButton.addEventListener('click', testSound); | |
bpmInput.addEventListener('change', updateBPM); | |
instrumentSelect.addEventListener('change', changeInstrument); | |
// Add event listeners for other selects to regenerate melody on change | |
keySelect.addEventListener('change', regenerateMelody); | |
scaleSelect.addEventListener('change', regenerateMelody); | |
chordProgressionSelect.addEventListener('change', regenerateMelody); | |
rhythmPatternSelect.addEventListener('change', regenerateMelody); | |
} | |
function populateSelects() { | |
// Populate instrument select | |
Object.keys(instruments).forEach(instrument => { | |
let option = document.createElement('option'); | |
option.value = instrument; | |
option.textContent = instrument.charAt(0).toUpperCase() + instrument.slice(1); | |
instrumentSelect.appendChild(option); | |
}); | |
// Populate key select | |
allNotes.forEach(note => { | |
let option = document.createElement('option'); | |
option.value = note; | |
option.textContent = note; | |
keySelect.appendChild(option); | |
}); | |
// Populate scale select | |
Object.keys(scales).forEach(scale => { | |
let option = document.createElement('option'); | |
option.value = scale; | |
option.textContent = scale.charAt(0).toUpperCase() + scale.slice(1); | |
scaleSelect.appendChild(option); | |
}); | |
// Populate chord progression select | |
Object.keys(chordProgressions).forEach(progression => { | |
let option = document.createElement('option'); | |
option.value = progression; | |
option.textContent = progression; | |
chordProgressionSelect.appendChild(option); | |
}); | |
// Populate rhythm pattern select | |
Object.keys(rhythmPatterns).forEach(pattern => { | |
let option = document.createElement('option'); | |
option.value = pattern; | |
option.textContent = pattern; | |
rhythmPatternSelect.appendChild(option); | |
}); | |
} | |
function changeInstrument() { | |
const selectedInstrument = instrumentSelect.value; | |
if (currentInstrument) { | |
currentInstrument.dispose(); | |
} | |
currentInstrument = new instruments[selectedInstrument]().toDestination(); | |
} | |
function getScaleNotes() { | |
const rootNote = keySelect.value; | |
const scale = scales[scaleSelect.value]; | |
const rootIndex = allNotes.indexOf(rootNote); | |
return scale.map(interval => { | |
const noteIndex = (rootIndex + interval) % 12; | |
return allNotes[noteIndex]; | |
}); | |
} | |
function generateChord(scaleNotes, chordType) { | |
const chordIntervals = { | |
'I': [0, 2, 4], | |
'ii': [1, 3, 5], | |
'IV': [3, 5, 0], | |
'V': [4, 6, 1], | |
'vi': [5, 0, 2] | |
}; | |
return chordIntervals[chordType].map(interval => scaleNotes[interval]); | |
} | |
function regenerateMelody() { | |
const scaleNotes = getScaleNotes(); | |
const progression = chordProgressions[chordProgressionSelect.value]; | |
const rhythmPattern = rhythmPatterns[rhythmPatternSelect.value]; | |
generatedMelody = []; | |
progression.forEach(chord => { | |
const chordNotes = generateChord(scaleNotes, chord); | |
rhythmPattern.forEach(beat => { | |
if (beat === 1) { | |
const note = chordNotes[Math.floor(Math.random() * chordNotes.length)]; | |
const octave = Math.floor(Math.random() * 2) + 3; // Randomize between octaves 3 and 4 | |
generatedMelody.push(note + octave); | |
} else { | |
generatedMelody.push(null); // Rest | |
} | |
}); | |
}); | |
displayMelody(); | |
if (sequence) { | |
sequence.dispose(); | |
} | |
sequence = new Tone.Sequence((time, note) => { | |
if (note !== null) { | |
currentInstrument.triggerAttackRelease(note, "8n", time); | |
} | |
}, generatedMelody, "8n"); | |
} | |
function displayMelody() { | |
melodyDisplay.textContent = generatedMelody.map((note, index) => { | |
if (note === null) return '-'; | |
if (index % 8 === 0) return '\n' + note; | |
return note; | |
}).join(' '); | |
} | |
function togglePlay() { | |
isPlaying = !isPlaying; | |
if (isPlaying) { | |
Tone.start(); | |
Tone.Transport.start(); | |
sequence.start(0); | |
playButton.textContent = 'Stop'; | |
playButton.style.backgroundColor = '#f44336'; | |
} else { | |
Tone.Transport.stop(); | |
sequence.stop(); | |
playButton.textContent = 'Play'; | |
playButton.style.backgroundColor = '#4CAF50'; | |
} | |
} | |
function testSound() { | |
Tone.start(); | |
const testNote = keySelect.value + '4'; | |
currentInstrument.triggerAttackRelease(testNote, "8n"); | |
} | |
function updateBPM() { | |
let bpm = parseInt(bpmInput.value); | |
if (bpm >= 60 && bpm <= 200) { | |
Tone.Transport.bpm.value = bpm; | |
} else { | |
alert("Please enter a BPM between 60 and 200"); | |
bpmInput.value = Tone.Transport.bpm.value; | |
} | |
} | |
window.addEventListener('load', setupTone); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment