awacke1's picture
Update index.html
bd8ee36 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multi-Instrument Synthesizer</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
#root {
width: 100%;
max-width: 600px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 10px;
padding: 20px;
}
.button {
padding: 10px;
margin: 5px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.bg-blue-500 {
background-color: #4299e1;
}
.bg-gray-300 {
background-color: #e2e8f0;
}
.bg-green-500 {
background-color: #48bb78;
}
.bg-red-500 {
background-color: #f56565;
}
.text-white {
color: white;
}
.p-4 {
padding: 16px;
}
.space-y-4 > * + * {
margin-top: 16px;
}
.bg-gray-100 {
background-color: #f7fafc;
}
.rounded-xl {
border-radius: 12px;
}
.text-2xl {
font-size: 1.5rem;
}
.font-bold {
font-weight: 700;
}
.text-center {
text-align: center;
}
.mb-6 {
margin-bottom: 24px;
}
.block {
display: block;
}
.text-sm {
font-size: 0.875rem;
}
.font-medium {
font-weight: 500;
}
.text-gray-700 {
color: #4a5568;
}
.mb-2 {
margin-bottom: 8px;
}
.grid {
display: grid;
}
.grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.gap-2 {
gap: 8px;
}
.w-full {
width: 100%;
}
.px-4 {
padding-left: 16px;
padding-right: 16px;
}
.py-2 {
padding-top: 8px;
padding-bottom: 8px;
}
.rounded {
border-radius: 4px;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState, useEffect, useCallback, useRef } = React;
const MultiInstrumentSynthesizer = () => {
const [audioContext, setAudioContext] = useState(null);
const [isPlaying, setIsPlaying] = useState(false);
const [tempo, setTempo] = useState(120);
const [rootNote, setRootNote] = useState(261.63); // C4
const [selectedProgression, setSelectedProgression] = useState(0);
const [selectedInstrument, setSelectedInstrument] = useState('fm');
const sequencerIntervalRef = useRef(null);
const currentBarRef = useRef(0);
const currentBeatRef = useRef(0);
useEffect(() => {
const context = new (window.AudioContext || window.webkitAudioContext)();
setAudioContext(context);
return () => {
context.close();
};
}, []);
const noteFrequencies = {
'C': 261.63, 'C#': 277.18, 'D': 293.66, 'D#': 311.13, 'E': 329.63, 'F': 349.23,
'F#': 369.99, 'G': 392.00, 'G#': 415.30, 'A': 440.00, 'A#': 466.16, 'B': 493.88
};
const progressions = [
{ name: "Blues I", progression: [1, 1, 1, 1, 4, 4, 1, 1, 5, 4, 1, 5] },
{ name: "Blues II", progression: [1, 4, 1, 1, 4, 4, 1, 1, 5, 4, 1, 5] },
{ name: "Rock", progression: [1, 5, 6, 4, 1, 5, 6, 4, 1, 5, 6, 4] },
{ name: "Electronica", progression: [1, 6, 4, 5, 1, 6, 4, 5, 1, 6, 4, 5] },
{ name: "Chillwave", progression: [1, 5, 6, 4, 1, 5, 6, 4, 1, 5, 6, 4] },
{ name: "EDM", progression: [1, 4, 5, 6, 1, 4, 5, 6, 1, 4, 5, 6] }
];
const generateChord = (root, chordType) => {
const majorScale = [0, 2, 4, 5, 7, 9, 11];
let chordIntervals;
switch (chordType) {
case 1: case 4: case 5: // Major (or dominant 7th for blues)
chordIntervals = [0, 4, 7, 10]; // Dominant 7th
break;
case 2: case 3: case 6: // Minor
chordIntervals = [0, 3, 7, 10]; // Minor 7th
break;
default:
chordIntervals = [0, 4, 7, 10]; // Default to dominant 7th
}
return chordIntervals.map(interval => {
const scaleStep = (majorScale[chordType - 1] + interval) % 12;
return root * Math.pow(2, scaleStep / 12);
});
};
const playFMSynth = useCallback((frequency, duration) => {
const osc = audioContext.createOscillator();
const mod = audioContext.createOscillator();
const modGain = audioContext.createGain();
const env = audioContext.createGain();
mod.type = 'sine';
osc.type = 'sine';
mod.connect(modGain);
modGain.connect(osc.frequency);
osc.connect(env);
env.connect(audioContext.destination);
const now = audioContext.currentTime;
const modFreq = frequency * 2;
const modIndex = 100;
mod.frequency.setValueAtTime(modFreq, now);
modGain.gain.setValueAtTime(modIndex, now);
osc.frequency.setValueAtTime(frequency, now);
env.gain.setValueAtTime(0, now);
env.gain.linearRampToValueAtTime(0.5, now + 0.01);
env.gain.exponentialRampToValueAtTime(0.01, now + duration - 0.1);
env.gain.linearRampToValueAtTime(0, now + duration);
mod.start(now);
osc.start(now);
osc.stop(now + duration);
}, [audioContext]);
const playPiano = useCallback((frequency, duration) => {
const osc = audioContext.createOscillator();
const gainNode = audioContext.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(frequency, audioContext.currentTime);
osc.connect(gainNode);
gainNode.connect(audioContext.destination);
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(0.5, audioContext.currentTime + 0.01);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration - 0.1);
osc.start();
osc.stop(audioContext.currentTime + duration);
}, [audioContext]);
const playGuitar = useCallback((frequency, duration) => {
const osc = audioContext.createOscillator();
const gainNode = audioContext.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(frequency, audioContext.currentTime);
osc.connect(gainNode);
gainNode.connect(audioContext.destination);
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(0.5, audioContext.currentTime + 0.005);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration - 0.05);
osc.start();
osc.stop(audioContext.currentTime + duration);
}, [audioContext]);
const playDrum = useCallback((type) => {
const osc = audioContext.createOscillator();
const gainNode = audioContext.createGain();
osc.connect(gainNode);
gainNode.connect(audioContext.destination);
if (type === 'kick') {
osc.frequency.setValueAtTime(150, audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
gainNode.gain.setValueAtTime(1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
} else if (type === 'snare') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(100, audioContext.currentTime);
gainNode.gain.setValueAtTime(1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
} else if (type === 'hihat') {
osc.type = 'square';
osc.frequency.setValueAtTime(1000, audioContext.currentTime);
gainNode.gain.setValueAtTime(0.2, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.05);
}
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 0.5);
}, [audioContext]);
const playChord = useCallback((frequencies) => {
const noteDuration = 60 / tempo; // Duration of one beat
frequencies.forEach((frequency, index) => {
const playTime = audioContext.currentTime + index * 0.05; // Slight arpeggio effect
switch (selectedInstrument) {
case 'fm':
playFMSynth(frequency, noteDuration);
break;
case 'piano':
playPiano(frequency, noteDuration);
break;
case 'guitar':
playGuitar(frequency, noteDuration);
break;
}
});
}, [audioContext, tempo, selectedInstrument, playFMSynth, playPiano, playGuitar]);
const startSequencer = useCallback(() => {
if (sequencerIntervalRef.current) {
clearInterval(sequencerIntervalRef.current);
}
currentBarRef.current = 0;
currentBeatRef.current = 0;
const beatDuration = 60 / tempo / 4; // Duration of a 16th note
sequencerIntervalRef.current = setInterval(() => {
if (currentBeatRef.current % 4 === 0) { // On each quarter note
const chordType = progressions[selectedProgression].progression[currentBarRef.current];
const chord = generateChord(rootNote, chordType);
playChord(chord);
// Play kick drum on the 1 and 3
if (currentBeatRef.current % 8 === 0) {
playDrum('kick');
}
}
// Play snare on the 2 and 4
if (currentBeatRef.current % 8 === 4) {
playDrum('snare');
}
// Play hihat on every 16th note
playDrum('hihat');
currentBeatRef.current = (currentBeatRef.current + 1) % 16;
if (currentBeatRef.current === 0) {
currentBarRef.current = (currentBarRef.current + 1) % 12;
}
}, beatDuration * 1000);
setIsPlaying(true);
}, [tempo, playChord, playDrum, selectedProgression, rootNote]);
const stopSequencer = useCallback(() => {
if (sequencerIntervalRef.current) {
clearInterval(sequencerIntervalRef.current);
}
setIsPlaying(false);
}, []);
const togglePlay = () => {
if (isPlaying) {
stopSequencer();
} else {
startSequencer();
}
};
const handleRootNoteChange = (note) => {
setRootNote(noteFrequencies[note]);
if (isPlaying) {
stopSequencer();
setTimeout(startSequencer, 100);
}
};
const handleProgressionChange = (index) => {
setSelectedProgression(index);
if (isPlaying) {
stopSequencer();
setTimeout(startSequencer, 100);
}
};
const handleInstrumentChange = (instrument) => {
setSelectedInstrument(instrument);
};
const handleTempoChange = (value) => {
setTempo(value[0]);
if (isPlaying) {
stopSequencer();
setTimeout(startSequencer, 100);
}
};
return (
<div className="p-4 space-y-4 bg-gray-100 rounded-xl">
<h2 className="text-2xl font-bold text-center mb-6">Multi-Instrument Twelve-Bar Synthesizer with Drums</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Root Note</label>
<div className="grid grid-cols-4 gap-2">
{Object.keys(noteFrequencies).map((note) => (
<button
key={note}
onClick={() => handleRootNoteChange(note)}
className={`button ${note === Object.keys(noteFrequencies).find(key => noteFrequencies[key] === rootNote) ? 'bg-blue-500' : 'bg-gray-300'} text-white`}
>
{note}
</button>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Progression</label>
<div className="grid grid-cols-2 gap-2">
{progressions.map((prog, index) => (
<button
key={index}
onClick={() => handleProgressionChange(index)}
className={`button ${index === selectedProgression ? 'bg-green-500' : 'bg-gray-300'} text-white text-xs`}
>
{prog.name}
</button>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Instrument</label>
<select onChange={(e) => handleInstrumentChange(e.target.value)} value={selectedInstrument} className="w-full">
<option value="fm">FM Synth</option>
<option value="piano">Piano</option>
<option value="guitar">Guitar</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Tempo: {tempo} BPM</label>
<input
type="range"
min="60"
max="180"
step="1"
value={tempo}
onChange={(e) => handleTempoChange([parseInt(e.target.value)])}
className="w-full"
/>
</div>
<button onClick={togglePlay} className={`button w-full ${isPlaying ? 'bg-red-500' : 'bg-green-500'} text-white px-4 py-2 rounded`}>
{isPlaying ? 'Stop' : 'Play'}
</button>
</div>
</div>
);
};
const rootElement = document.getElementById('root');
ReactDOM.render(<MultiInstrumentSynthesizer />, rootElement);
</script>
</body>
</html>