awacke1 commited on
Commit
a6fd850
·
verified ·
1 Parent(s): 08ae50b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +19 -122
app.py CHANGED
@@ -1,94 +1,12 @@
1
  import streamlit as st
2
  import streamlit.components.v1 as components
3
- import mido
4
- from queue import Queue
5
- import threading
6
-
7
- # Store MIDI messages in session state
8
- if 'incoming_midi' not in st.session_state:
9
- st.session_state.incoming_midi = Queue()
10
-
11
- def midi_in_callback(msg):
12
- if msg.type in ['note_on', 'note_off']:
13
- st.session_state.incoming_midi.put({
14
- 'type': 'noteOn' if msg.type == 'note_on' else 'noteOff',
15
- 'note': msg.note,
16
- 'velocity': msg.velocity
17
- })
18
-
19
- def open_midi_port(port_name: str, is_input: bool):
20
- if port_name == "None":
21
- return None
22
- return mido.open_input(port_name, callback=midi_in_callback) if is_input else mido.open_output(port_name)
23
 
24
  def main():
25
  st.title("5-Octave Synth with Arpeggiator & Drum Pads")
26
-
27
- # MIDI port selection
28
- ports = {"in": ["None"] + mido.get_input_names(),
29
- "out": ["None"] + mido.get_output_names()}
30
- selections = {
31
- "in": st.selectbox("MIDI Input", ports["in"]),
32
- "out": st.selectbox("MIDI Output", ports["out"])
33
- }
34
-
35
- # Initialize/update MIDI ports
36
- for direction in ["in", "out"]:
37
- key = f'midi_{direction}'
38
- if key not in st.session_state:
39
- st.session_state[key] = None
40
-
41
- curr_port = st.session_state[key]
42
- if curr_port and curr_port.name != selections[direction]:
43
- curr_port.close()
44
- st.session_state[key] = open_midi_port(selections[direction], direction=="in")
45
- elif not curr_port and selections[direction] != "None":
46
- st.session_state[key] = open_midi_port(selections[direction], direction=="in")
47
-
48
  # Load and embed synth interface
49
  components.html(get_synth_interface(), height=800)
50
 
51
- # Event handling setup
52
- if 'js_events' not in st.session_state:
53
- st.session_state.js_events = Queue()
54
- if 'browser_events' not in st.session_state:
55
- from collections import deque
56
- st.session_state.browser_events = deque()
57
-
58
- # Process hardware MIDI
59
- while not st.session_state.incoming_midi.empty():
60
- evt = st.session_state.incoming_midi.get_nowait()
61
- st.session_state.js_events.put(evt)
62
-
63
- # Send events to browser
64
- js_events = []
65
- while not st.session_state.js_events.empty():
66
- js_events.append(st.session_state.js_events.get_nowait())
67
-
68
- if js_events:
69
- js_code = "<script>\n"
70
- for evt in js_events:
71
- js_code += f"""
72
- window.postMessage({{
73
- type: 'hardwareMidi',
74
- data: {{
75
- eventType: '{evt["type"]}',
76
- note: {evt["note"]},
77
- velocity: {evt.get("velocity", 100)}
78
- }}
79
- }}, '*');\n"""
80
- js_code += "</script>"
81
- components.html(js_code, height=0)
82
-
83
- # Cleanup on session end
84
- def cleanup():
85
- for direction in ["in", "out"]:
86
- port = st.session_state.get(f'midi_{direction}')
87
- if port:
88
- port.close()
89
-
90
- st.on_session_end(cleanup)
91
-
92
  def get_synth_interface():
93
  return """
94
  <!DOCTYPE html>
@@ -162,7 +80,7 @@ def get_synth_interface():
162
  <div id="drumPads" class="drum-grid"></div>
163
  </div>
164
  <script>
165
- // Initialize Tone.js instruments and UI
166
  const synth = new Tone.PolySynth().toDestination();
167
  const drumSampler = new Tone.Sampler({
168
  'C2': 'https://tonejs.github.io/audio/drum-samples/kicks/kick.mp3',
@@ -246,14 +164,6 @@ def get_synth_interface():
246
  synth.triggerAttack(note);
247
  const key = document.querySelector(`[data-midi="${midiNote}"]`);
248
  if (key) key.classList.add('active');
249
-
250
- // Send MIDI message
251
- window.parent.postMessage({
252
- type: 'toneEvent',
253
- eventType: 'noteOn',
254
- note: midiNote,
255
- velocity: 100
256
- }, '*');
257
 
258
  if (document.getElementById('arpMode').value !== 'off') {
259
  if (!arpNotes.includes(note)) {
@@ -267,13 +177,6 @@ def get_synth_interface():
267
  synth.triggerRelease(note);
268
  const key = document.querySelector(`[data-midi="${midiNote}"]`);
269
  if (key) key.classList.remove('active');
270
-
271
- window.parent.postMessage({
272
- type: 'toneEvent',
273
- eventType: 'noteOff',
274
- note: midiNote,
275
- velocity: 0
276
- }, '*');
277
 
278
  if (document.getElementById('arpMode').value !== 'off') {
279
  arpNotes = arpNotes.filter(n => n !== note);
@@ -292,33 +195,27 @@ def get_synth_interface():
292
  const pad = drumPads.children[index];
293
  pad.classList.add('active');
294
  setTimeout(() => pad.classList.remove('active'), 100);
295
-
296
- window.parent.postMessage({
297
- type: 'toneEvent',
298
- eventType: 'drum',
299
- padIndex: index,
300
- velocity: 100
301
- }, '*');
302
  }
303
 
304
- // Handle incoming MIDI messages
305
- window.addEventListener('message', e => {
306
- if (e.data?.type === 'hardwareMidi') {
307
- const { eventType, note, velocity } = e.data.data;
308
- const noteName = midiToNoteName(note);
309
-
310
- if (eventType === 'noteOn') {
311
- playNote(noteName, note);
312
- } else if (eventType === 'noteOff') {
313
- stopNote(noteName, note);
314
- }
 
 
 
315
  }
 
 
 
316
  });
317
-
318
- function midiToNoteName(midi) {
319
- const octave = Math.floor(midi / 12) - 1;
320
- return noteNames[midi % 12] + octave;
321
- }
322
  </script>
323
  </body>
324
  </html>
 
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>
 
80
  <div id="drumPads" class="drum-grid"></div>
81
  </div>
82
  <script>
83
+ // Initialize Tone.js instruments
84
  const synth = new Tone.PolySynth().toDestination();
85
  const drumSampler = new Tone.Sampler({
86
  'C2': 'https://tonejs.github.io/audio/drum-samples/kicks/kick.mp3',
 
164
  synth.triggerAttack(note);
165
  const key = document.querySelector(`[data-midi="${midiNote}"]`);
166
  if (key) key.classList.add('active');
 
 
 
 
 
 
 
 
167
 
168
  if (document.getElementById('arpMode').value !== 'off') {
169
  if (!arpNotes.includes(note)) {
 
177
  synth.triggerRelease(note);
178
  const key = document.querySelector(`[data-midi="${midiNote}"]`);
179
  if (key) key.classList.remove('active');
 
 
 
 
 
 
 
180
 
181
  if (document.getElementById('arpMode').value !== 'off') {
182
  arpNotes = arpNotes.filter(n => n !== note);
 
195
  const pad = drumPads.children[index];
196
  pad.classList.add('active');
197
  setTimeout(() => pad.classList.remove('active'), 100);
 
 
 
 
 
 
 
198
  }
199
 
200
+ // Handle synth type changes
201
+ document.getElementById('synthType').addEventListener('change', (e) => {
202
+ const type = e.target.value;
203
+ let newSynth;
204
+
205
+ switch(type) {
206
+ case 'fm':
207
+ newSynth = new Tone.PolySynth(Tone.FMSynth).toDestination();
208
+ break;
209
+ case 'am':
210
+ newSynth = new Tone.PolySynth(Tone.AMSynth).toDestination();
211
+ break;
212
+ default:
213
+ newSynth = new Tone.PolySynth(Tone.Synth).toDestination();
214
  }
215
+
216
+ synth.dispose();
217
+ window.synth = newSynth;
218
  });
 
 
 
 
 
219
  </script>
220
  </body>
221
  </html>