Skip to content

Commit 149f389

Browse files
rjtokenringdsammaruga
authored andcommitted
Add play poliphonic sounds
1 parent e3e6ac4 commit 149f389

File tree

1 file changed

+56
-0
lines changed

1 file changed

+56
-0
lines changed

src/arduino/app_bricks/sound_generator/__init__.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,62 @@ def _get_note(self, note: str) -> float | None:
245245
return None
246246
return self._notes.get(note.strip().upper())
247247

248+
def play_polyphonic(self, notes: list[list[tuple[str, float]]], as_tone: bool = False, volume: float = None):
249+
"""
250+
Play multiple sequences of musical notes simultaneously (poliphony).
251+
It is possible to play multi track music by providing a list of sequences,
252+
where each sequence is a list of tuples (note, duration).
253+
Duration is in notes fractions (e.g., 1/4 for quarter note).
254+
Args:
255+
notes (list[list[tuple[str, float]]]): List of sequences, each sequence is a list of tuples (note, duration).
256+
as_tone (bool): If True, play as tones, considering duration in seconds
257+
volume (float, optional): Volume level (0.0 to 1.0). If None, uses master volume.
258+
"""
259+
if volume is None:
260+
volume = self._master_volume
261+
262+
# Multi track mixing
263+
sequences_data = []
264+
base_frequency = None
265+
for sequence in notes:
266+
sequence_waves = []
267+
for note, duration in sequence:
268+
frequency = self._get_note(note)
269+
if frequency >= 0.0:
270+
if base_frequency is None:
271+
base_frequency = frequency
272+
if as_tone == False:
273+
duration = self._note_duration(duration)
274+
data = self._wave_gen.generate_block(float(frequency), duration, volume)
275+
sequence_waves.append(data)
276+
else:
277+
continue
278+
if len(sequence_waves) > 0:
279+
single_track_data = np.concatenate(sequence_waves)
280+
sequences_data.append(single_track_data)
281+
282+
if len(sequences_data) == 0:
283+
return
284+
285+
# Mix sequences - align lengths
286+
max_length = max(len(seq) for seq in sequences_data)
287+
# Pad shorter sequences with zeros
288+
for i in range(len(sequences_data)):
289+
seq = sequences_data[i]
290+
if len(seq) < max_length:
291+
padding = np.zeros(max_length - len(seq), dtype=np.float32)
292+
sequences_data[i] = np.concatenate((seq, padding))
293+
294+
# Sum all sequences
295+
mixed = np.sum(sequences_data, axis=0, dtype=np.float32)
296+
mixed /= np.max(np.abs(mixed)) # Normalize to prevent clipping
297+
blk = mixed.astype(np.float32)
298+
blk = self._apply_sound_effects(blk, base_frequency)
299+
try:
300+
self._output_device.play(blk, block_on_queue=False)
301+
except Exception as e:
302+
print(f"Error playing multiple sequences: {e}")
303+
248304
def play_chord(self, notes: list[str], note_duration: float | str = 1 / 4, volume: float = None):
249305
"""
250306
Play a chord consisting of multiple musical notes simultaneously for a specified duration and volume.

0 commit comments

Comments
 (0)