@@ -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