pachet commited on
Commit
56785fd
·
1 Parent(s): 86a0791

debugged the phrase playing js function

Browse files
Files changed (2) hide show
  1. app.py +48 -180
  2. phrase_catching.py +16 -8
app.py CHANGED
@@ -5,49 +5,35 @@ import json
5
  # ✅ Create FastAPI App
6
  app = FastAPI()
7
 
 
 
8
 
9
- # ✅ MIDI Processing Function in Python
10
- @app.post("/midi_input")
11
- async def process_midi(request: Request):
12
- try:
13
- midi_data = await request.json()
14
- note = midi_data["note"]
15
- velocity = midi_data["velocity"]
16
-
17
- print(f"🎹 Received MIDI Note: {note}, Velocity: {velocity}")
18
-
19
- # 🚀 Process MIDI data (example: Transpose + Generate New Notes)
20
- generated_note = (note + 5) % 128 # Transpose up by 3 semitones
21
- generated_velocity = min(velocity + 10, 127) # Increase velocity slightly
22
-
23
- # ✅ Send MIDI Response Back to Client
24
- return {
25
- "status": "success",
26
- "generated_note": generated_note,
27
- "generated_velocity": generated_velocity,
28
- "original_note": note
29
- }
30
 
31
- except Exception as e:
32
- print(f"🚨 Error processing MIDI: {str(e)}")
33
- return {"status": "error", "message": str(e)}
 
 
 
34
 
35
 
 
36
  @app.post("/midi_phrase")
37
  async def process_midi_phrase(request: Request):
38
  try:
 
39
  data = await request.json()
40
- phrase = data["phrase"] # List of MIDI notes
41
 
42
- print(f"🎹 Received MIDI Phrase ({len(phrase)} notes)")
43
 
44
- # 🚀 Process Phrase: Example - Transpose Each Note Up by 3 Semitones
45
  generated_phrase = [
46
  {
47
- "note": (note["note"] + 3) % 128,
48
  "velocity": note["velocity"],
49
- "duration": note["duration"], # ✅ Keep original duration
50
- "inter_onset": note["inter_onset"] # ✅ Keep inter-onset time
51
  }
52
  for note in phrase
53
  ]
@@ -59,24 +45,17 @@ async def process_midi_phrase(request: Request):
59
  return {"status": "error", "message": str(e)}
60
 
61
 
62
- # ✅ JavaScript to Capture and Send MIDI Data
63
  midi_js = """
64
  <script>
65
-
66
- let style = document.createElement("style");
67
- style.innerHTML = `
68
- * {
69
- font-family: Arial, sans-serif !important;
70
- }
71
- `;
72
- document.head.appendChild(style);
73
- console.log("✅ Applied fallback font to prevent 404 error.");
74
-
75
  let midiAccess = null;
76
  let selectedInput = null;
77
  let selectedOutput = null;
 
 
 
 
78
 
79
- // ✅ Request MIDI Access
80
  navigator.requestMIDIAccess()
81
  .then(access => {
82
  console.log("✅ MIDI Access Granted!");
@@ -86,21 +65,15 @@ navigator.requestMIDIAccess()
86
  })
87
  .catch(err => console.error("🚨 MIDI API Error:", err));
88
 
89
- // ✅ Update MIDI Input & Output Menus
90
  function updateMIDIDevices() {
91
  let inputSelect = document.getElementById("midiInput");
92
  let outputSelect = document.getElementById("midiOutput");
93
 
94
- if (!inputSelect || !outputSelect) {
95
- console.error("❌ MIDI dropdowns not found!");
96
- return;
97
- }
98
 
99
- // Clear existing options
100
  inputSelect.innerHTML = '<option value="">Select MIDI Input</option>';
101
  outputSelect.innerHTML = '<option value="">Select MIDI Output</option>';
102
 
103
- // Populate MIDI Input Devices
104
  midiAccess.inputs.forEach((input, key) => {
105
  let option = document.createElement("option");
106
  option.value = key;
@@ -108,7 +81,6 @@ function updateMIDIDevices() {
108
  inputSelect.appendChild(option);
109
  });
110
 
111
- // Populate MIDI Output Devices
112
  midiAccess.outputs.forEach((output, key) => {
113
  let option = document.createElement("option");
114
  option.value = key;
@@ -119,14 +91,11 @@ function updateMIDIDevices() {
119
  console.log("🎛 Updated MIDI Input & Output devices.");
120
  }
121
 
122
- // ✅ Handle MIDI Input Selection
123
  function selectMIDIInput() {
124
  let inputSelect = document.getElementById("midiInput");
125
  let inputId = inputSelect.value;
126
 
127
- if (selectedInput) {
128
- selectedInput.onmidimessage = null;
129
- }
130
 
131
  if (midiAccess.inputs.has(inputId)) {
132
  selectedInput = midiAccess.inputs.get(inputId);
@@ -135,7 +104,6 @@ function selectMIDIInput() {
135
  }
136
  }
137
 
138
- // ✅ Handle MIDI Output Selection
139
  function selectMIDIOutput() {
140
  let outputSelect = document.getElementById("midiOutput");
141
  let outputId = outputSelect.value;
@@ -146,73 +114,31 @@ function selectMIDIOutput() {
146
  }
147
  }
148
 
149
-
150
- // ✅ Play a MIDI Note Sent Back from Python
151
- function playMIDINote(note, velocity) {
152
- if (!selectedOutput) {
153
- console.warn("⚠️ No MIDI output selected.");
154
- return;
155
- }
156
-
157
- let noteOnMessage = [0x90, note, velocity]; // Note On
158
- let noteOffMessage = [0x80, note, 0]; // Note Off
159
-
160
- console.log(`🎵 Playing Generated MIDI Note ${note}, Velocity ${velocity}`);
161
-
162
- try {
163
- selectedOutput.send(noteOnMessage);
164
- setTimeout(() => {
165
- selectedOutput.send(noteOffMessage);
166
- console.log(`🔇 Note Off ${note}`);
167
- }, 500);
168
- } catch (error) {
169
- console.error("🚨 Error playing MIDI note:", error);
170
- }
171
- }
172
-
173
- // ✅ Send MIDI Data to Python
174
- // ✅ Handle Incoming MIDI Messages and Send to Python
175
-
176
- let midiPhrase = []; // ✅ Buffer to store incoming notes
177
- let activeNotes = new Map(); // ✅ Track active notes with start times
178
- let phraseTimeout = null; // ✅ Timer to detect phrase end
179
- let lastNoteOnTime = null; // ✅ Track previous note-on time for inter-onset calculation
180
-
181
- // ✅ Handle Incoming MIDI Messages (Phrase Buffering)
182
  function handleMIDIMessage(event) {
183
- let command = event.data[0] & 0xf0; // Extract MIDI command
184
  let note = event.data[1];
185
  let velocity = event.data[2];
186
- let timestamp = performance.now(); // ✅ Get precise timing
187
 
188
  if (command === 0x90 && velocity > 0) {
189
- // ✅ Note On: Store start time and inter-onset interval
190
  let interOnsetTime = lastNoteOnTime ? timestamp - lastNoteOnTime : 0;
191
  lastNoteOnTime = timestamp;
192
- console.log(interOnsetTime);
193
-
194
- activeNotes.set(note, timestamp); // ✅ Store note start time
195
 
 
196
  midiPhrase.push({ note, velocity, start_time: timestamp, duration: null, inter_onset: interOnsetTime });
197
 
198
- console.log(`🎤 Note ON: ${note}, Velocity ${velocity}, IOT ${interOnsetTime}ms`);
199
  }
200
  else if (command === 0x80 || (command === 0x90 && velocity === 0)) {
201
- // ✅ Note Off: Calculate duration and update phrase
202
  if (activeNotes.has(note)) {
203
  let startTime = activeNotes.get(note);
204
  let duration = timestamp - startTime;
205
  activeNotes.delete(note);
206
 
207
- // ✅ Find the note in the phrase and update duration
208
  let noteIndex = midiPhrase.findIndex(n => n.note === note && n.duration === null);
209
- if (noteIndex !== -1) {
210
- midiPhrase[noteIndex].duration = duration;
211
- console.log(`🎹 Note OFF: ${note}, Duration ${duration}ms`);
212
- }
213
  }
214
 
215
- // ✅ If no active notes, start phrase timeout (1s delay before sending)
216
  if (activeNotes.size === 0) {
217
  if (phraseTimeout) clearTimeout(phraseTimeout);
218
  phraseTimeout = setTimeout(sendPhraseToPython, 1000);
@@ -220,11 +146,10 @@ function handleMIDIMessage(event) {
220
  }
221
  }
222
 
223
- // ✅ Send the Phrase to Python After 1 Second of Silence
224
  function sendPhraseToPython() {
225
- if (midiPhrase.length === 0 || activeNotes.size > 0) return; // ✅ Do not send if notes are still active
226
 
227
- console.log("📨 Sending MIDI phrase to Python:", midiPhrase);
228
 
229
  fetch("/midi_phrase", {
230
  method: "POST",
@@ -234,105 +159,48 @@ function sendPhraseToPython() {
234
  .then(response => response.json())
235
  .then(data => {
236
  console.log("📩 Python Response:", data);
237
- if (data.generated_phrase) {
238
- playGeneratedPhrase(data.generated_phrase);
239
- }
240
- })
241
- .catch(error => console.error("🚨 Error sending MIDI phrase:", error));
242
 
243
- // ✅ Clear the phrase buffer
244
  midiPhrase = [];
245
  lastNoteOnTime = null;
246
-
247
  }
248
 
249
- // ✅ Play Generated MIDI Phrase from Python (Keeping Timing)
250
  function playGeneratedPhrase(phrase) {
251
- if (!selectedOutput) {
252
- console.warn("⚠️ No MIDI output selected.");
253
- return;
254
- }
255
 
256
  console.log("🎵 Playing Generated Phrase:", phrase);
 
257
 
258
- let accumulatedDelay = 0; // ✅ Track total delay from phrase start
259
- phrase.forEach((noteData, index) => {
260
- accumulatedDelay += noteData.inter_onset; // ✅ Accumulate inter-onset time
261
-
262
- setTimeout(() => {
263
- let noteOnMessage = [0x90, noteData.note, noteData.velocity];
264
- let noteOffMessage = [0x80, noteData.note, 0];
265
-
266
- selectedOutput.send(noteOnMessage);
267
- console.log(`🎶 Note ON: ${noteData.note}, Velocity: ${noteData.velocity}, Start Delay: ${accumulatedDelay}ms`);
268
 
269
  setTimeout(() => {
270
- selectedOutput.send(noteOffMessage);
271
- console.log(`🔇 Note OFF: ${noteData.note}`);
272
- }, noteData.duration); // ✅ Use stored duration
273
-
274
- }, accumulatedDelay); // ✅ Use accumulated delay for precise timing
275
- });
276
- }
277
-
278
-
279
- // ✅ Attach Generate Button Event
280
- function attachButtonEvent() {
281
- let generateButton = document.getElementById("generateButton");
282
 
283
- if (generateButton) {
284
- console.log("✅ Generate button found! Attaching event listener...");
285
 
286
- generateButton.addEventListener("click", function () {
287
- console.log("🎹 Generate button clicked.");
288
-
289
- if (!selectedOutput) {
290
- alert("⚠️ Please select a MIDI Output first!");
291
- return;
292
- }
293
-
294
- let randomNote = 60 + Math.floor(Math.random() * 12); // Random note from C4 to B4
295
- console.log(`🎵 Generating MIDI Note: ${randomNote}`);
296
- playMIDINote(randomNote, 100);
297
- });
298
-
299
- } else {
300
- console.log("⏳ Waiting for button to be available...");
301
- setTimeout(attachButtonEvent, 500); // Try again in 500ms
302
- }
303
  }
304
-
305
- // ✅ Ensure the Button and Menus Are Loaded
306
- window.onload = function() {
307
- console.log("✅ Page fully loaded. Checking for elements...");
308
- updateMIDIDevices();
309
- attachButtonEvent();
310
- };
311
  </script>
312
-
313
- <!-- 🎛 MIDI Input & Output Selection -->
314
- <div>
315
- <label for="midiInput">MIDI Input: </label>
316
- <select id="midiInput" onchange="selectMIDIInput()"></select>
317
-
318
- <label for="midiOutput">MIDI Output: </label>
319
- <select id="midiOutput" onchange="selectMIDIOutput()"></select>
320
- </div>
321
-
322
- <!-- 🎶 "Generate MIDI" Button -->
323
- <button id="generateButton">🎵 Generate MIDI Note</button>
324
-
325
  """
326
 
327
- # ✅ Inject JavaScript and HTML
328
  with gr.Blocks() as demo:
329
  gr.HTML(midi_js)
 
 
 
 
330
 
331
  # ✅ Mount FastAPI with Gradio
332
  app = gr.mount_gradio_app(app, demo, path="/")
333
 
 
334
  if __name__ == "__main__":
335
  import uvicorn
336
 
337
- print("🚀 Starting FastAPI with Gradio...")
338
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
5
  # ✅ Create FastAPI App
6
  app = FastAPI()
7
 
8
+ # ✅ Variable to store transposition value
9
+ transpose_amount = 0
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ # Update transposition value when slider changes
13
+ def update_transposition(value):
14
+ global transpose_amount
15
+ transpose_amount = value
16
+ print(f"🎚️ Transposition set to: {transpose_amount}")
17
+ return f"Transposition: {transpose_amount}"
18
 
19
 
20
+ # ✅ MIDI Processing Function in Python
21
  @app.post("/midi_phrase")
22
  async def process_midi_phrase(request: Request):
23
  try:
24
+ global transpose_amount
25
  data = await request.json()
26
+ phrase = data["phrase"]
27
 
28
+ print(f"🎹 Received MIDI Phrase ({len(phrase)} notes) with transposition {transpose_amount}")
29
 
30
+ # 🚀 Apply transposition based on user selection
31
  generated_phrase = [
32
  {
33
+ "note": (note["note"] + transpose_amount) % 128,
34
  "velocity": note["velocity"],
35
+ "duration": note["duration"],
36
+ "inter_onset": note["inter_onset"]
37
  }
38
  for note in phrase
39
  ]
 
45
  return {"status": "error", "message": str(e)}
46
 
47
 
48
+ # ✅ JavaScript & HTML for the Web UI
49
  midi_js = """
50
  <script>
 
 
 
 
 
 
 
 
 
 
51
  let midiAccess = null;
52
  let selectedInput = null;
53
  let selectedOutput = null;
54
+ let midiPhrase = [];
55
+ let activeNotes = new Map();
56
+ let phraseTimeout = null;
57
+ let lastNoteOnTime = 0;
58
 
 
59
  navigator.requestMIDIAccess()
60
  .then(access => {
61
  console.log("✅ MIDI Access Granted!");
 
65
  })
66
  .catch(err => console.error("🚨 MIDI API Error:", err));
67
 
 
68
  function updateMIDIDevices() {
69
  let inputSelect = document.getElementById("midiInput");
70
  let outputSelect = document.getElementById("midiOutput");
71
 
72
+ if (!inputSelect || !outputSelect) return;
 
 
 
73
 
 
74
  inputSelect.innerHTML = '<option value="">Select MIDI Input</option>';
75
  outputSelect.innerHTML = '<option value="">Select MIDI Output</option>';
76
 
 
77
  midiAccess.inputs.forEach((input, key) => {
78
  let option = document.createElement("option");
79
  option.value = key;
 
81
  inputSelect.appendChild(option);
82
  });
83
 
 
84
  midiAccess.outputs.forEach((output, key) => {
85
  let option = document.createElement("option");
86
  option.value = key;
 
91
  console.log("🎛 Updated MIDI Input & Output devices.");
92
  }
93
 
 
94
  function selectMIDIInput() {
95
  let inputSelect = document.getElementById("midiInput");
96
  let inputId = inputSelect.value;
97
 
98
+ if (selectedInput) selectedInput.onmidimessage = null;
 
 
99
 
100
  if (midiAccess.inputs.has(inputId)) {
101
  selectedInput = midiAccess.inputs.get(inputId);
 
104
  }
105
  }
106
 
 
107
  function selectMIDIOutput() {
108
  let outputSelect = document.getElementById("midiOutput");
109
  let outputId = outputSelect.value;
 
114
  }
115
  }
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  function handleMIDIMessage(event) {
118
+ let command = event.data[0] & 0xf0;
119
  let note = event.data[1];
120
  let velocity = event.data[2];
121
+ let timestamp = performance.now();
122
 
123
  if (command === 0x90 && velocity > 0) {
 
124
  let interOnsetTime = lastNoteOnTime ? timestamp - lastNoteOnTime : 0;
125
  lastNoteOnTime = timestamp;
 
 
 
126
 
127
+ activeNotes.set(note, timestamp);
128
  midiPhrase.push({ note, velocity, start_time: timestamp, duration: null, inter_onset: interOnsetTime });
129
 
130
+ console.log(`🎤 Note ON: ${note}, IOT ${interOnsetTime}ms`);
131
  }
132
  else if (command === 0x80 || (command === 0x90 && velocity === 0)) {
 
133
  if (activeNotes.has(note)) {
134
  let startTime = activeNotes.get(note);
135
  let duration = timestamp - startTime;
136
  activeNotes.delete(note);
137
 
 
138
  let noteIndex = midiPhrase.findIndex(n => n.note === note && n.duration === null);
139
+ if (noteIndex !== -1) midiPhrase[noteIndex].duration = duration;
 
 
 
140
  }
141
 
 
142
  if (activeNotes.size === 0) {
143
  if (phraseTimeout) clearTimeout(phraseTimeout);
144
  phraseTimeout = setTimeout(sendPhraseToPython, 1000);
 
146
  }
147
  }
148
 
 
149
  function sendPhraseToPython() {
150
+ if (midiPhrase.length === 0 || activeNotes.size > 0) return;
151
 
152
+ console.log(`📨 Sending MIDI phrase to Python`);
153
 
154
  fetch("/midi_phrase", {
155
  method: "POST",
 
159
  .then(response => response.json())
160
  .then(data => {
161
  console.log("📩 Python Response:", data);
162
+ if (data.generated_phrase) playGeneratedPhrase(data.generated_phrase);
163
+ });
 
 
 
164
 
 
165
  midiPhrase = [];
166
  lastNoteOnTime = null;
 
167
  }
168
 
 
169
  function playGeneratedPhrase(phrase) {
170
+ if (!selectedOutput) return;
 
 
 
171
 
172
  console.log("🎵 Playing Generated Phrase:", phrase);
173
+ let accumulatedDelay = 0;
174
 
175
+ phrase.forEach((noteData, index) => {
176
+ accumulatedDelay += noteData.inter_onset;
 
 
 
 
 
 
 
 
177
 
178
  setTimeout(() => {
179
+ let noteOnMessage = [0x90, noteData.note, noteData.velocity];
180
+ let noteOffMessage = [0x80, noteData.note, 0];
 
 
 
 
 
 
 
 
 
 
181
 
182
+ selectedOutput.send(noteOnMessage);
183
+ setTimeout(() => selectedOutput.send(noteOffMessage), noteData.duration);
184
 
185
+ }, accumulatedDelay);
186
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
 
 
 
 
 
 
 
188
  </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  """
190
 
191
+ # ✅ Create Gradio App with Transposition Slider
192
  with gr.Blocks() as demo:
193
  gr.HTML(midi_js)
194
+ with gr.Row():
195
+ slider = gr.Slider(minimum=-12, maximum=12, step=1, value=0, label="Transposition", interactive=True)
196
+ gr.Textbox("Transposition: 0", label="Selected Transposition", interactive=False).bind(slider,
197
+ update_transposition)
198
 
199
  # ✅ Mount FastAPI with Gradio
200
  app = gr.mount_gradio_app(app, demo, path="/")
201
 
202
+ # ✅ Run the app
203
  if __name__ == "__main__":
204
  import uvicorn
205
 
 
206
  uvicorn.run(app, host="0.0.0.0", port=7860)
phrase_catching.py CHANGED
@@ -255,16 +255,24 @@ function playGeneratedPhrase(phrase) {
255
 
256
  console.log("🎵 Playing Generated Phrase:", phrase);
257
 
258
- phrase.forEach((noteData, index) => {
259
- let delay = noteData.inter_onset; // ✅ Use stored inter-onset time
 
 
 
 
 
 
 
 
 
260
  setTimeout(() => {
261
- let noteOnMessage = [0x90, noteData.note, noteData.velocity];
262
- let noteOffMessage = [0x80, noteData.note, 0];
 
263
 
264
- selectedOutput.send(noteOnMessage);
265
- setTimeout(() => selectedOutput.send(noteOffMessage), noteData.duration); // ✅ Use stored duration
266
- }, delay);
267
- });
268
  }
269
 
270
 
 
255
 
256
  console.log("🎵 Playing Generated Phrase:", phrase);
257
 
258
+ let accumulatedDelay = 0; // ✅ Track total delay from phrase start
259
+ phrase.forEach((noteData, index) => {
260
+ accumulatedDelay += noteData.inter_onset; // ✅ Accumulate inter-onset time
261
+
262
+ setTimeout(() => {
263
+ let noteOnMessage = [0x90, noteData.note, noteData.velocity];
264
+ let noteOffMessage = [0x80, noteData.note, 0];
265
+
266
+ selectedOutput.send(noteOnMessage);
267
+ console.log(`🎶 Note ON: ${noteData.note}, Velocity: ${noteData.velocity}, Start Delay: ${accumulatedDelay}ms`);
268
+
269
  setTimeout(() => {
270
+ selectedOutput.send(noteOffMessage);
271
+ console.log(`🔇 Note OFF: ${noteData.note}`);
272
+ }, noteData.duration); // ✅ Use stored duration
273
 
274
+ }, accumulatedDelay); // ✅ Use accumulated delay for precise timing
275
+ });
 
 
276
  }
277
 
278