Skip to content

Instantly share code, notes, and snippets.

@Zetaphor
Last active August 7, 2024 18:32
Show Gist options
  • Save Zetaphor/9c44bd411163ecb84f643e20f003d999 to your computer and use it in GitHub Desktop.
Save Zetaphor/9c44bd411163ecb84f643e20f003d999 to your computer and use it in GitHub Desktop.
ToneJS Beat Generator
<!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