awacke1's picture
Create app.py
ff7a92b verified
import streamlit as st
import streamlit.components.v1 as components
def main():
st.title("5-Octave Synth with Arpeggiator & Drum Pads")
# Load and embed synth interface
components.html(get_synth_interface(), height=800)
def get_synth_interface():
return """
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/tone@14.8.39/build/Tone.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@magenta/music@1.23.1/dist/magentamusic.min.js"></script>
<style>
.container { max-width: 1200px; margin: 0 auto; }
.keyboard { display: flex; margin: 20px 0; }
.key {
width: 40px; height: 150px;
border: 1px solid #000;
background: white;
margin-right: 2px;
}
.key.black {
width: 24px; height: 100px;
background: black;
margin: 0 -12px;
z-index: 1;
}
.key.active { background: #ff6961; }
.drum-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin: 20px 0;
}
.drum-pad {
aspect-ratio: 1;
background: #444;
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 4px;
}
.drum-pad.active { background: #ff6961; }
.controls {
display: flex;
gap: 20px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="controls">
<div>
<label>Arpeggiator:</label>
<select id="arpMode">
<option value="off">Off</option>
<option value="up">Up</option>
<option value="down">Down</option>
<option value="updown">Up/Down</option>
<option value="random">Random</option>
</select>
<input type="range" id="arpSpeed" min="100" max="1000" value="200">
</div>
<div>
<label>Synth Type:</label>
<select id="synthType">
<option value="simple">Simple</option>
<option value="fm">FM</option>
<option value="am">AM</option>
</select>
</div>
</div>
<div id="keyboard" class="keyboard"></div>
<div id="drumPads" class="drum-grid"></div>
</div>
<script>
// WebMIDI initialization
let midiIn = null;
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess()
.then(access => {
const inputs = access.inputs.values();
for (let input of inputs) {
midiIn = input;
input.onmidimessage = handleMIDIMessage;
}
access.onstatechange = e => {
if (e.port.type === 'input') {
if (e.port.state === 'connected') {
midiIn = e.port;
e.port.onmidimessage = handleMIDIMessage;
} else {
midiIn = null;
}
}
};
})
.catch(err => console.warn('WebMIDI not available:', err));
}
function handleMIDIMessage(event) {
const [status, note, velocity] = event.data;
const command = status >> 4;
if (command === 9) { // Note On
playNote(midiToNoteName(note), note, velocity);
} else if (command === 8) { // Note Off
stopNote(midiToNoteName(note), note);
}
}
// Initialize Tone.js instruments
const synth = new Tone.PolySynth().toDestination();
const drumSampler = new Tone.Sampler({
'C2': 'https://tonejs.github.io/audio/drum-samples/kicks/kick.mp3',
'D2': 'https://tonejs.github.io/audio/drum-samples/snare/snare.mp3',
'E2': 'https://tonejs.github.io/audio/drum-samples/hh/hh.mp3',
'F2': 'https://tonejs.github.io/audio/drum-samples/tom/tom.mp3'
}).toDestination();
// Build 5-octave keyboard (61 keys)
const startNote = 36; // C2
const noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
const keyboard = document.getElementById('keyboard');
for (let i = 0; i < 61; i++) {
const midiNote = startNote + i;
const octave = Math.floor(midiNote / 12) - 1;
const noteName = noteNames[midiNote % 12] + octave;
const isBlack = noteName.includes('#');
const key = document.createElement('div');
key.className = `key ${isBlack ? 'black' : ''}`;
key.dataset.note = noteName;
key.dataset.midi = midiNote;
key.addEventListener('mousedown', () => playNote(noteName, midiNote));
key.addEventListener('mouseup', () => stopNote(noteName, midiNote));
key.addEventListener('mouseleave', () => stopNote(noteName, midiNote));
keyboard.appendChild(key);
}
// Build 16 drum pads
const drumPads = document.getElementById('drumPads');
for (let i = 0; i < 16; i++) {
const pad = document.createElement('div');
pad.className = 'drum-pad';
pad.textContent = `Pad ${i + 1}`;
pad.addEventListener('mousedown', () => triggerDrum(i));
drumPads.appendChild(pad);
}
// Arpeggiator implementation
let arpNotes = [];
let arpInterval = null;
document.getElementById('arpMode').addEventListener('change', updateArpeggiator);
document.getElementById('arpSpeed').addEventListener('change', updateArpeggiator);
function updateArpeggiator() {
const mode = document.getElementById('arpMode').value;
const speed = document.getElementById('arpSpeed').value;
if (arpInterval) clearInterval(arpInterval);
if (mode !== 'off' && arpNotes.length) {
let index = 0;
arpInterval = setInterval(() => {
const note = arpNotes[index];
playNote(note, true);
setTimeout(() => stopNote(note), speed * 0.8);
switch(mode) {
case 'up':
index = (index + 1) % arpNotes.length;
break;
case 'down':
index = (index - 1 + arpNotes.length) % arpNotes.length;
break;
case 'updown':
// Implementation for up/down pattern
break;
case 'random':
index = Math.floor(Math.random() * arpNotes.length);
break;
}
}, speed);
}
}
function playNote(note, midiNote) {
Tone.start();
synth.triggerAttack(note);
const key = document.querySelector(`[data-midi="${midiNote}"]`);
if (key) key.classList.add('active');
if (document.getElementById('arpMode').value !== 'off') {
if (!arpNotes.includes(note)) {
arpNotes.push(note);
updateArpeggiator();
}
}
}
function stopNote(note, midiNote) {
synth.triggerRelease(note);
const key = document.querySelector(`[data-midi="${midiNote}"]`);
if (key) key.classList.remove('active');
if (document.getElementById('arpMode').value !== 'off') {
arpNotes = arpNotes.filter(n => n !== note);
if (!arpNotes.length && arpInterval) {
clearInterval(arpInterval);
arpInterval = null;
}
}
}
function triggerDrum(index) {
const notes = ['C2', 'D2', 'E2', 'F2'];
const note = notes[index % notes.length];
drumSampler.triggerAttackRelease(note, '8n');
const pad = drumPads.children[index];
pad.classList.add('active');
setTimeout(() => pad.classList.remove('active'), 100);
}
// Handle synth type changes
document.getElementById('synthType').addEventListener('change', (e) => {
const type = e.target.value;
let newSynth;
switch(type) {
case 'fm':
newSynth = new Tone.PolySynth(Tone.FMSynth).toDestination();
break;
case 'am':
newSynth = new Tone.PolySynth(Tone.AMSynth).toDestination();
break;
default:
newSynth = new Tone.PolySynth(Tone.Synth).toDestination();
}
synth.dispose();
window.synth = newSynth;
});
</script>
</body>
</html>
"""
if __name__ == "__main__":
main()