awacke1 commited on
Commit
ff7a92b
·
verified ·
1 Parent(s): 7ce23c3

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +263 -0
app.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import streamlit.components.v1 as components
3
+
4
+ def main():
5
+ st.title("5-Octave Synth with Arpeggiator & Drum Pads")
6
+
7
+ # Load and embed synth interface
8
+ components.html(get_synth_interface(), height=800)
9
+
10
+ def get_synth_interface():
11
+ return """
12
+ <!DOCTYPE html>
13
+ <html>
14
+ <head>
15
+ <script src="https://cdn.jsdelivr.net/npm/tone@14.8.39/build/Tone.js"></script>
16
+ <script src="https://cdn.jsdelivr.net/npm/@magenta/music@1.23.1/dist/magentamusic.min.js"></script>
17
+ <style>
18
+ .container { max-width: 1200px; margin: 0 auto; }
19
+ .keyboard { display: flex; margin: 20px 0; }
20
+ .key {
21
+ width: 40px; height: 150px;
22
+ border: 1px solid #000;
23
+ background: white;
24
+ margin-right: 2px;
25
+ }
26
+ .key.black {
27
+ width: 24px; height: 100px;
28
+ background: black;
29
+ margin: 0 -12px;
30
+ z-index: 1;
31
+ }
32
+ .key.active { background: #ff6961; }
33
+ .drum-grid {
34
+ display: grid;
35
+ grid-template-columns: repeat(4, 1fr);
36
+ gap: 10px;
37
+ margin: 20px 0;
38
+ }
39
+ .drum-pad {
40
+ aspect-ratio: 1;
41
+ background: #444;
42
+ color: white;
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ cursor: pointer;
47
+ border-radius: 4px;
48
+ }
49
+ .drum-pad.active { background: #ff6961; }
50
+ .controls {
51
+ display: flex;
52
+ gap: 20px;
53
+ margin: 20px 0;
54
+ }
55
+ </style>
56
+ </head>
57
+ <body>
58
+ <div class="container">
59
+ <div class="controls">
60
+ <div>
61
+ <label>Arpeggiator:</label>
62
+ <select id="arpMode">
63
+ <option value="off">Off</option>
64
+ <option value="up">Up</option>
65
+ <option value="down">Down</option>
66
+ <option value="updown">Up/Down</option>
67
+ <option value="random">Random</option>
68
+ </select>
69
+ <input type="range" id="arpSpeed" min="100" max="1000" value="200">
70
+ </div>
71
+ <div>
72
+ <label>Synth Type:</label>
73
+ <select id="synthType">
74
+ <option value="simple">Simple</option>
75
+ <option value="fm">FM</option>
76
+ <option value="am">AM</option>
77
+ </select>
78
+ </div>
79
+ </div>
80
+ <div id="keyboard" class="keyboard"></div>
81
+ <div id="drumPads" class="drum-grid"></div>
82
+ </div>
83
+ <script>
84
+ // WebMIDI initialization
85
+ let midiIn = null;
86
+
87
+ if (navigator.requestMIDIAccess) {
88
+ navigator.requestMIDIAccess()
89
+ .then(access => {
90
+ const inputs = access.inputs.values();
91
+ for (let input of inputs) {
92
+ midiIn = input;
93
+ input.onmidimessage = handleMIDIMessage;
94
+ }
95
+
96
+ access.onstatechange = e => {
97
+ if (e.port.type === 'input') {
98
+ if (e.port.state === 'connected') {
99
+ midiIn = e.port;
100
+ e.port.onmidimessage = handleMIDIMessage;
101
+ } else {
102
+ midiIn = null;
103
+ }
104
+ }
105
+ };
106
+ })
107
+ .catch(err => console.warn('WebMIDI not available:', err));
108
+ }
109
+
110
+ function handleMIDIMessage(event) {
111
+ const [status, note, velocity] = event.data;
112
+ const command = status >> 4;
113
+
114
+ if (command === 9) { // Note On
115
+ playNote(midiToNoteName(note), note, velocity);
116
+ } else if (command === 8) { // Note Off
117
+ stopNote(midiToNoteName(note), note);
118
+ }
119
+ }
120
+
121
+ // Initialize Tone.js instruments
122
+ const synth = new Tone.PolySynth().toDestination();
123
+ const drumSampler = new Tone.Sampler({
124
+ 'C2': 'https://tonejs.github.io/audio/drum-samples/kicks/kick.mp3',
125
+ 'D2': 'https://tonejs.github.io/audio/drum-samples/snare/snare.mp3',
126
+ 'E2': 'https://tonejs.github.io/audio/drum-samples/hh/hh.mp3',
127
+ 'F2': 'https://tonejs.github.io/audio/drum-samples/tom/tom.mp3'
128
+ }).toDestination();
129
+
130
+ // Build 5-octave keyboard (61 keys)
131
+ const startNote = 36; // C2
132
+ const noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
133
+ const keyboard = document.getElementById('keyboard');
134
+
135
+ for (let i = 0; i < 61; i++) {
136
+ const midiNote = startNote + i;
137
+ const octave = Math.floor(midiNote / 12) - 1;
138
+ const noteName = noteNames[midiNote % 12] + octave;
139
+ const isBlack = noteName.includes('#');
140
+
141
+ const key = document.createElement('div');
142
+ key.className = `key ${isBlack ? 'black' : ''}`;
143
+ key.dataset.note = noteName;
144
+ key.dataset.midi = midiNote;
145
+
146
+ key.addEventListener('mousedown', () => playNote(noteName, midiNote));
147
+ key.addEventListener('mouseup', () => stopNote(noteName, midiNote));
148
+ key.addEventListener('mouseleave', () => stopNote(noteName, midiNote));
149
+
150
+ keyboard.appendChild(key);
151
+ }
152
+
153
+ // Build 16 drum pads
154
+ const drumPads = document.getElementById('drumPads');
155
+ for (let i = 0; i < 16; i++) {
156
+ const pad = document.createElement('div');
157
+ pad.className = 'drum-pad';
158
+ pad.textContent = `Pad ${i + 1}`;
159
+ pad.addEventListener('mousedown', () => triggerDrum(i));
160
+ drumPads.appendChild(pad);
161
+ }
162
+
163
+ // Arpeggiator implementation
164
+ let arpNotes = [];
165
+ let arpInterval = null;
166
+
167
+ document.getElementById('arpMode').addEventListener('change', updateArpeggiator);
168
+ document.getElementById('arpSpeed').addEventListener('change', updateArpeggiator);
169
+
170
+ function updateArpeggiator() {
171
+ const mode = document.getElementById('arpMode').value;
172
+ const speed = document.getElementById('arpSpeed').value;
173
+
174
+ if (arpInterval) clearInterval(arpInterval);
175
+ if (mode !== 'off' && arpNotes.length) {
176
+ let index = 0;
177
+ arpInterval = setInterval(() => {
178
+ const note = arpNotes[index];
179
+ playNote(note, true);
180
+ setTimeout(() => stopNote(note), speed * 0.8);
181
+
182
+ switch(mode) {
183
+ case 'up':
184
+ index = (index + 1) % arpNotes.length;
185
+ break;
186
+ case 'down':
187
+ index = (index - 1 + arpNotes.length) % arpNotes.length;
188
+ break;
189
+ case 'updown':
190
+ // Implementation for up/down pattern
191
+ break;
192
+ case 'random':
193
+ index = Math.floor(Math.random() * arpNotes.length);
194
+ break;
195
+ }
196
+ }, speed);
197
+ }
198
+ }
199
+
200
+ function playNote(note, midiNote) {
201
+ Tone.start();
202
+ synth.triggerAttack(note);
203
+ const key = document.querySelector(`[data-midi="${midiNote}"]`);
204
+ if (key) key.classList.add('active');
205
+
206
+ if (document.getElementById('arpMode').value !== 'off') {
207
+ if (!arpNotes.includes(note)) {
208
+ arpNotes.push(note);
209
+ updateArpeggiator();
210
+ }
211
+ }
212
+ }
213
+
214
+ function stopNote(note, midiNote) {
215
+ synth.triggerRelease(note);
216
+ const key = document.querySelector(`[data-midi="${midiNote}"]`);
217
+ if (key) key.classList.remove('active');
218
+
219
+ if (document.getElementById('arpMode').value !== 'off') {
220
+ arpNotes = arpNotes.filter(n => n !== note);
221
+ if (!arpNotes.length && arpInterval) {
222
+ clearInterval(arpInterval);
223
+ arpInterval = null;
224
+ }
225
+ }
226
+ }
227
+
228
+ function triggerDrum(index) {
229
+ const notes = ['C2', 'D2', 'E2', 'F2'];
230
+ const note = notes[index % notes.length];
231
+ drumSampler.triggerAttackRelease(note, '8n');
232
+
233
+ const pad = drumPads.children[index];
234
+ pad.classList.add('active');
235
+ setTimeout(() => pad.classList.remove('active'), 100);
236
+ }
237
+
238
+ // Handle synth type changes
239
+ document.getElementById('synthType').addEventListener('change', (e) => {
240
+ const type = e.target.value;
241
+ let newSynth;
242
+
243
+ switch(type) {
244
+ case 'fm':
245
+ newSynth = new Tone.PolySynth(Tone.FMSynth).toDestination();
246
+ break;
247
+ case 'am':
248
+ newSynth = new Tone.PolySynth(Tone.AMSynth).toDestination();
249
+ break;
250
+ default:
251
+ newSynth = new Tone.PolySynth(Tone.Synth).toDestination();
252
+ }
253
+
254
+ synth.dispose();
255
+ window.synth = newSynth;
256
+ });
257
+ </script>
258
+ </body>
259
+ </html>
260
+ """
261
+
262
+ if __name__ == "__main__":
263
+ main()