Add Shift+click for siren and k/K for kill
- Add /api/play-siren endpoint for Shift+click - Add /api/kill-siren endpoint for k/K keys - Add set_chords method to OSCSender for dynamic voice count - Update soft kill to 20Hz in osc_sender.py - Refactor CLI to use send_kill method - Fix kill to send to all voices regardless of count
This commit is contained in:
parent
6ab162003e
commit
94b34b4dc4
|
|
@ -26,7 +26,7 @@ class OSCSender:
|
|||
self.chords = []
|
||||
self.current_index = 0
|
||||
self.preview_mode = True
|
||||
self.num_voices = 0
|
||||
self.num_voices = 4 # Default to 4 voices
|
||||
self.voice_indices = [] # Track position for each voice independently
|
||||
|
||||
def load_chords(self, chords_file):
|
||||
|
|
@ -50,6 +50,13 @@ class OSCSender:
|
|||
|
||||
print(f"Loaded {len(self.chords)} chords, {self.num_voices} voices")
|
||||
|
||||
def set_chords(self, chords):
|
||||
"""Set chords directly (list of chord pitch arrays)."""
|
||||
self.chords = chords
|
||||
if self.chords:
|
||||
self.num_voices = len(self.chords[0])
|
||||
self.voice_indices = [0] * self.num_voices
|
||||
|
||||
def send_chord(self, index):
|
||||
"""Send frequencies for a specific chord index."""
|
||||
if index < 0 or index >= len(self.chords):
|
||||
|
|
@ -98,6 +105,15 @@ class OSCSender:
|
|||
msg.add_arg(frequency)
|
||||
self.client.send(msg.build())
|
||||
|
||||
def send_kill(self, soft=True):
|
||||
"""Send kill to all voices (soft=20Hz, hard=0Hz)."""
|
||||
kill_freq = 20.0 if soft else 0.0
|
||||
for voice in range(1, self.num_voices + 1):
|
||||
self.send_single(kill_freq, voice)
|
||||
print(
|
||||
f" [Sent kill {'soft' if soft else 'hard'} ({kill_freq}) to {self.num_voices} voices]"
|
||||
)
|
||||
|
||||
def send_current(self):
|
||||
"""Send all voices at their current positions."""
|
||||
for voice_idx in range(self.num_voices):
|
||||
|
|
@ -302,20 +318,10 @@ class OSCSender:
|
|||
self.display_chords()
|
||||
|
||||
elif key == "k":
|
||||
for voice_idx in range(self.num_voices):
|
||||
msg = osc_message_builder.OscMessageBuilder(address="/freq")
|
||||
msg.add_arg(voice_idx + 1)
|
||||
msg.add_arg(15.0)
|
||||
self.client.send(msg.build())
|
||||
print(f" [Sent kill soft (15.0) to {self.num_voices} voices]")
|
||||
self.send_kill(soft=True)
|
||||
|
||||
elif key == "K":
|
||||
for voice_idx in range(self.num_voices):
|
||||
msg = osc_message_builder.OscMessageBuilder(address="/freq")
|
||||
msg.add_arg(voice_idx + 1)
|
||||
msg.add_arg(0.0)
|
||||
self.client.send(msg.build())
|
||||
print(f" [Sent kill hard (0.0) to {self.num_voices} voices]")
|
||||
self.send_kill(soft=False)
|
||||
|
||||
elif key.isdigit():
|
||||
num_buffer += key
|
||||
|
|
|
|||
|
|
@ -433,9 +433,14 @@
|
|||
const chordIndex = node.data('chordIndex');
|
||||
const localId = node.data('localId');
|
||||
|
||||
console.log('Sending play request:', chordIndex, localId);
|
||||
// Check if Shift key is held - send to siren, otherwise send to SuperCollider
|
||||
const isShift = evt.originalEvent && evt.originalEvent.shiftKey;
|
||||
const endpoint = isShift ? '/api/play-siren' : '/api/play-freq';
|
||||
const destination = isShift ? 'siren' : 'SuperCollider';
|
||||
|
||||
fetch('/api/play-freq', {
|
||||
console.log('Sending play request to', destination, ':', chordIndex, localId);
|
||||
|
||||
fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
|
|
@ -443,7 +448,7 @@
|
|||
nodeIndex: localId
|
||||
})
|
||||
}).then(r => r.json()).then(data => {
|
||||
console.log('Playing:', data.frequency.toFixed(2), 'Hz on voice', data.voice);
|
||||
console.log('Playing on', destination + ':', data.frequency.toFixed(2), 'Hz on voice', data.voice);
|
||||
}).catch(err => {
|
||||
console.log('Error playing freq:', err);
|
||||
});
|
||||
|
|
@ -945,6 +950,28 @@
|
|||
navigate('prev');
|
||||
} else if (e.key === "ArrowRight") {
|
||||
navigate('next');
|
||||
} else if (e.key === "k") {
|
||||
// Soft kill - send 20 Hz to stop voices gently
|
||||
fetch('/api/kill-siren', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ soft: true })
|
||||
}).then(r => r.json()).then(data => {
|
||||
console.log('Soft kill sent (20 Hz)');
|
||||
}).catch(err => {
|
||||
console.log('Error sending kill:', err);
|
||||
});
|
||||
} else if (e.key === "K") {
|
||||
// Hard kill - send 0 Hz to stop voices immediately
|
||||
fetch('/api/kill-siren', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ soft: false })
|
||||
}).then(r => r.json()).then(data => {
|
||||
console.log('Hard kill sent (0 Hz)');
|
||||
}).catch(err => {
|
||||
console.log('Error sending kill:', err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -327,6 +327,64 @@ def play_freq():
|
|||
)
|
||||
|
||||
|
||||
@app.route("/api/play-siren", methods=["POST"])
|
||||
def play_siren():
|
||||
"""Play a single frequency for a node on the siren."""
|
||||
data = request.json
|
||||
chord_index = data.get("chordIndex")
|
||||
node_index = data.get("nodeIndex")
|
||||
|
||||
if chord_index < 0 or chord_index >= len(chords):
|
||||
return jsonify({"error": "Invalid chord index"}), 400
|
||||
|
||||
chord = chords[chord_index]
|
||||
if node_index < 0 or node_index >= len(chord):
|
||||
return jsonify({"error": "Invalid node index"}), 400
|
||||
|
||||
pitch = chord[node_index]
|
||||
fraction = Fraction(pitch.get("fraction", "1"))
|
||||
frequency = fundamental * float(fraction)
|
||||
voice = node_index + 1 # 1-indexed
|
||||
|
||||
# Send to siren (192.168.4.200:54001) using current fundamental
|
||||
siren_sender = OSCSender(ip="192.168.4.200", port=54001, fundamental=fundamental)
|
||||
siren_sender.set_chords(chords) # Set chords to ensure proper voice count
|
||||
siren_sender.send_single(frequency, voice)
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"frequency": frequency,
|
||||
"voice": voice,
|
||||
"fundamental": fundamental,
|
||||
"fraction": pitch.get("fraction", "1"),
|
||||
"destination": "siren",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/kill-siren", methods=["POST"])
|
||||
def kill_siren():
|
||||
"""Send kill message to siren (soft or hard)."""
|
||||
data = request.json
|
||||
soft = data.get("soft", True) # default to soft kill
|
||||
|
||||
kill_freq = 20.0 if soft else 0.0
|
||||
kill_type = "soft" if soft else "hard"
|
||||
|
||||
# Send kill to all voices on siren using send_kill
|
||||
siren_sender = OSCSender(ip="192.168.4.200", port=54001, fundamental=fundamental)
|
||||
siren_sender.set_chords(chords) # Set chords to get correct num_voices
|
||||
siren_sender.send_kill(soft=soft)
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"kill_freq": kill_freq,
|
||||
"kill_type": kill_type,
|
||||
"voices": list(range(1, siren_sender.num_voices + 1)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Starting Path Navigator server...")
|
||||
print(f"Loading chords from: {get_chords_file()}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue