Add independent voice control to OSC sender with improved display
This commit is contained in:
parent
1dff1022ce
commit
dfdc0497a0
|
|
@ -26,6 +26,8 @@ class OSCSender:
|
|||
self.chords = []
|
||||
self.current_index = 0
|
||||
self.preview_mode = True
|
||||
self.num_voices = 0
|
||||
self.voice_indices = [] # Track position for each voice independently
|
||||
|
||||
def load_chords(self, chords_file):
|
||||
"""Load chords from output_chords.json."""
|
||||
|
|
@ -38,7 +40,15 @@ class OSCSender:
|
|||
else:
|
||||
self.chords = chords_data
|
||||
|
||||
print(f"Loaded {len(self.chords)} chords")
|
||||
# Initialize voice indices to 0 for all voices
|
||||
if self.chords:
|
||||
self.num_voices = len(self.chords[0])
|
||||
self.voice_indices = [0] * self.num_voices
|
||||
else:
|
||||
self.num_voices = 0
|
||||
self.voice_indices = []
|
||||
|
||||
print(f"Loaded {len(self.chords)} chords, {self.num_voices} voices")
|
||||
|
||||
def send_chord(self, index):
|
||||
"""Send frequencies for a specific chord index."""
|
||||
|
|
@ -59,9 +69,33 @@ class OSCSender:
|
|||
self.client.send(msg.build())
|
||||
print(f" [Sent chord {index + 1}/{len(self.chords)}]")
|
||||
|
||||
def send_voice(self, voice_idx):
|
||||
"""Send frequency for a single voice at its current position."""
|
||||
if voice_idx < 0 or voice_idx >= len(self.voice_indices):
|
||||
return
|
||||
|
||||
chord_idx = self.voice_indices[voice_idx]
|
||||
if chord_idx < 0 or chord_idx >= len(self.chords):
|
||||
return
|
||||
|
||||
chord = self.chords[chord_idx]
|
||||
pitch = chord[voice_idx]
|
||||
|
||||
from fractions import Fraction
|
||||
|
||||
frac = Fraction(pitch["fraction"])
|
||||
freq = self.fundamental * float(frac)
|
||||
|
||||
msg = osc_message_builder.OscMessageBuilder(address="/freq")
|
||||
msg.add_arg(voice_idx + 1) # Voice number (1-indexed)
|
||||
msg.add_arg(freq)
|
||||
self.client.send(msg.build())
|
||||
|
||||
def send_current(self):
|
||||
"""Send the current chord."""
|
||||
self.send_chord(self.current_index)
|
||||
"""Send all voices at their current positions."""
|
||||
for voice_idx in range(self.num_voices):
|
||||
self.send_voice(voice_idx)
|
||||
print(f" [Sent all {self.num_voices} voices at their positions]")
|
||||
|
||||
def _get_chord_preview(self, index):
|
||||
"""Get a one-line preview of a chord."""
|
||||
|
|
@ -82,48 +116,76 @@ class OSCSender:
|
|||
return self.chords[index]
|
||||
|
||||
def display_chords(self):
|
||||
"""Display previous, current, and next chords."""
|
||||
"""Display previous, current, and next chords with per-voice positions."""
|
||||
total = len(self.chords)
|
||||
prev_idx = self.current_index - 1
|
||||
next_idx = self.current_index + 1
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(
|
||||
f"PREVIOUS: Chord {prev_idx + 1}/{total}"
|
||||
if prev_idx >= 0
|
||||
else "PREVIOUS: None"
|
||||
)
|
||||
if prev_idx >= 0:
|
||||
print(f" {self._get_chord_preview(prev_idx)}")
|
||||
print("-" * 60)
|
||||
mode_str = "[PREVIEW]" if self.preview_mode else "[SEND]"
|
||||
print(f">>> CURRENT: Chord {self.current_index + 1}/{total} {mode_str} <<<")
|
||||
|
||||
chord = self._get_chord_full(self.current_index)
|
||||
print("Frequencies (Hz):")
|
||||
from fractions import Fraction
|
||||
|
||||
for voice_idx, pitch in enumerate(chord):
|
||||
# Build prev/next info for each voice independently
|
||||
prev_freqs = []
|
||||
prev_positions = []
|
||||
next_freqs = []
|
||||
next_positions = []
|
||||
current_freqs = []
|
||||
|
||||
for voice_idx in range(self.num_voices):
|
||||
curr_idx = self.voice_indices[voice_idx]
|
||||
|
||||
# Previous for this voice
|
||||
prev_idx = max(0, curr_idx - 1)
|
||||
chord = self.chords[prev_idx]
|
||||
pitch = chord[voice_idx]
|
||||
frac = Fraction(pitch["fraction"])
|
||||
freq = self.fundamental * float(frac)
|
||||
print(f" Voice {voice_idx + 1}: {freq:.2f}")
|
||||
prev_freqs.append(f"{freq:.2f}")
|
||||
prev_positions.append(prev_idx + 1)
|
||||
|
||||
print("\nHS Arrays:")
|
||||
for voice_idx, pitch in enumerate(chord):
|
||||
print(f" Voice {voice_idx + 1}: {pitch['hs_array']}")
|
||||
# Current for this voice
|
||||
chord = self.chords[curr_idx]
|
||||
pitch = chord[voice_idx]
|
||||
frac = Fraction(pitch["fraction"])
|
||||
freq = self.fundamental * float(frac)
|
||||
current_freqs.append((freq, pitch["hs_array"]))
|
||||
|
||||
# Next for this voice
|
||||
next_idx = min(len(self.chords) - 1, curr_idx + 1)
|
||||
chord = self.chords[next_idx]
|
||||
pitch = chord[voice_idx]
|
||||
frac = Fraction(pitch["fraction"])
|
||||
freq = self.fundamental * float(frac)
|
||||
next_freqs.append(f"{freq:.2f}")
|
||||
next_positions.append(next_idx + 1)
|
||||
|
||||
# Get current positions for display
|
||||
curr_positions = [v + 1 for v in self.voice_indices]
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
|
||||
# PREVIOUS line
|
||||
print(f"PREVIOUS ({prev_positions}/{total})")
|
||||
print(f"{', '.join(prev_freqs)} Hz")
|
||||
|
||||
print("-" * 60)
|
||||
|
||||
# CURRENT line with positions
|
||||
print(f"CURRENT ({curr_positions}/{total})")
|
||||
for voice_idx in range(self.num_voices):
|
||||
freq, hs = current_freqs[voice_idx]
|
||||
print(f" Voice {voice_idx + 1}: {freq:.2f} Hz {hs}")
|
||||
|
||||
print("-" * 60)
|
||||
|
||||
# NEXT line
|
||||
print(f"Next ({next_positions}/{total})")
|
||||
print(f"{', '.join(next_freqs)} Hz")
|
||||
|
||||
print("-" * 60)
|
||||
|
||||
mode_str = "[PREVIEW]" if self.preview_mode else "[SEND]"
|
||||
print(
|
||||
f"NEXT: Chord {next_idx + 1}/{total}" if next_idx < total else "NEXT: None"
|
||||
)
|
||||
if next_idx < total:
|
||||
print(f" {self._get_chord_preview(next_idx)}")
|
||||
print("=" * 60)
|
||||
print(
|
||||
f"Fundamental: {self.fundamental} Hz | Destination: {self.ip}:{self.port}"
|
||||
f"Fundamental: {self.fundamental} Hz | Destination: {self.ip}:{self.port} {mode_str}"
|
||||
)
|
||||
print(
|
||||
"Controls: <-|-> navigate | p=preview | k/K=kill | Enter=send | [n]=jump | Esc=quit"
|
||||
"Controls: <-|-> all | [n]<-/->=voice n | p=preview | k/K=kill | Enter=send all | Esc=quit"
|
||||
)
|
||||
|
||||
def play(self):
|
||||
|
|
@ -167,52 +229,101 @@ class OSCSender:
|
|||
break
|
||||
|
||||
elif key == "\x1b[D":
|
||||
if self.current_index > 0:
|
||||
self.current_index -= 1
|
||||
self.display_chords()
|
||||
if not self.preview_mode:
|
||||
self.send_current()
|
||||
# Back arrow
|
||||
if num_buffer:
|
||||
# Individual voice: [n]<- move voice n back
|
||||
try:
|
||||
voice_num = int(num_buffer) - 1
|
||||
if 0 <= voice_num < self.num_voices:
|
||||
if self.voice_indices[voice_num] > 0:
|
||||
self.voice_indices[voice_num] -= 1
|
||||
self.display_chords()
|
||||
if not self.preview_mode:
|
||||
self.send_voice(voice_num)
|
||||
else:
|
||||
print(f"\n Voice {voice_num + 1} at start")
|
||||
else:
|
||||
print(f"\n Invalid voice: {num_buffer}")
|
||||
except ValueError:
|
||||
pass
|
||||
num_buffer = ""
|
||||
else:
|
||||
# All voices: move all voice_indices back
|
||||
can_move = any(vi > 0 for vi in self.voice_indices)
|
||||
if can_move:
|
||||
for voice_idx in range(self.num_voices):
|
||||
if self.voice_indices[voice_idx] > 0:
|
||||
self.voice_indices[voice_idx] -= 1
|
||||
self.display_chords()
|
||||
if not self.preview_mode:
|
||||
self.send_current()
|
||||
|
||||
elif key == "\x1b[C":
|
||||
if self.current_index < len(self.chords) - 1:
|
||||
self.current_index += 1
|
||||
self.display_chords()
|
||||
if not self.preview_mode:
|
||||
self.send_current()
|
||||
# Forward arrow
|
||||
if num_buffer:
|
||||
# Individual voice: [n]-> move voice n forward
|
||||
try:
|
||||
voice_num = int(num_buffer) - 1
|
||||
if 0 <= voice_num < self.num_voices:
|
||||
if self.voice_indices[voice_num] < len(self.chords) - 1:
|
||||
self.voice_indices[voice_num] += 1
|
||||
self.display_chords()
|
||||
if not self.preview_mode:
|
||||
self.send_voice(voice_num)
|
||||
else:
|
||||
print(f"\n Voice {voice_num + 1} at end")
|
||||
else:
|
||||
print(f"\n Invalid voice: {num_buffer}")
|
||||
except ValueError:
|
||||
pass
|
||||
num_buffer = ""
|
||||
else:
|
||||
# All voices: move all voice_indices forward
|
||||
can_move = any(
|
||||
vi < len(self.chords) - 1 for vi in self.voice_indices
|
||||
)
|
||||
if can_move:
|
||||
for voice_idx in range(self.num_voices):
|
||||
if self.voice_indices[voice_idx] < len(self.chords) - 1:
|
||||
self.voice_indices[voice_idx] += 1
|
||||
self.display_chords()
|
||||
if not self.preview_mode:
|
||||
self.send_current()
|
||||
|
||||
elif key == "p":
|
||||
self.preview_mode = not self.preview_mode
|
||||
self.display_chords()
|
||||
|
||||
elif key == "k":
|
||||
num_voices = len(self.chords[self.current_index])
|
||||
for voice_idx in range(num_voices):
|
||||
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 {num_voices} voices]")
|
||||
print(f" [Sent kill soft (15.0) to {self.num_voices} voices]")
|
||||
|
||||
elif key == "K":
|
||||
num_voices = len(self.chords[self.current_index])
|
||||
for voice_idx in range(num_voices):
|
||||
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 {num_voices} voices]")
|
||||
print(f" [Sent kill hard (0.0) to {self.num_voices} voices]")
|
||||
|
||||
elif key.isdigit():
|
||||
num_buffer += key
|
||||
print(f"\rJump to: {num_buffer}", end="", flush=True)
|
||||
print(f"\rVoice: {num_buffer}", end="", flush=True)
|
||||
|
||||
elif key == "\r":
|
||||
if num_buffer:
|
||||
try:
|
||||
idx = int(num_buffer) - 1
|
||||
if 0 <= idx < len(self.chords):
|
||||
self.current_index = idx
|
||||
# Set all voices to this chord index
|
||||
self.voice_indices = [idx] * self.num_voices
|
||||
self.display_chords()
|
||||
if not self.preview_mode:
|
||||
self.send_current()
|
||||
else:
|
||||
print(f"\nIndex must be 1-{len(self.chords)}")
|
||||
except ValueError:
|
||||
|
|
@ -225,9 +336,9 @@ class OSCSender:
|
|||
if num_buffer:
|
||||
num_buffer = num_buffer[:-1]
|
||||
if num_buffer:
|
||||
print(f"\rJump to: {num_buffer}", end="", flush=True)
|
||||
print(f"\rVoice: {num_buffer}", end="", flush=True)
|
||||
else:
|
||||
print("\r ", end="", flush=True)
|
||||
print("\r ", end="", flush=True)
|
||||
print("\r", end="", flush=True)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
|
|
|
|||
Loading…
Reference in a new issue