Skip to content

Commit 5d3f7b9

Browse files
rjtokenringdsammaruga
authored andcommitted
Added wav playback
1 parent 26b927f commit 5d3f7b9

File tree

1 file changed

+50
-9
lines changed

1 file changed

+50
-9
lines changed

src/arduino/app_bricks/sound_generator/__init__.py

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Iterable
99
import numpy as np
1010
import time
11+
from pathlib import Path
1112

1213
from .generator import WaveSamplesBuilder
1314
from .effects import *
@@ -411,6 +412,27 @@ def play_abc(self, abc_string: str, volume: float = None) -> Iterable[tuple[byte
411412
data = self._apply_sound_effects(data, frequency)
412413
yield (self._to_bytes(data), duration)
413414

415+
def play_wav(self, wav_file: str) -> tuple[bytes, float]:
416+
"""
417+
Play a WAV audio data block.
418+
Args:
419+
wav_file (str): The WAV audio file path.
420+
Returns:
421+
tuple[bytes, float]: The audio block of the WAV file (float32) and its duration in seconds.
422+
"""
423+
import wave
424+
425+
file_path = Path(wav_file)
426+
if not file_path.exists() or not file_path.is_file():
427+
raise FileNotFoundError(f"WAV file not found: {wav_file}")
428+
429+
with wave.open(wav_file, "rb") as wav:
430+
# Read all frames (raw PCM data)
431+
duration = wav.getnframes() / wav.getframerate()
432+
return (wav.readframes(wav.getnframes()), duration)
433+
434+
return (None, None)
435+
414436

415437
@brick
416438
class SoundGenerator(SoundGeneratorStreamer):
@@ -482,7 +504,7 @@ def set_effects(self, effects: list):
482504
"""
483505
super().set_effects(effects)
484506

485-
def play_polyphonic(self, notes: list[list[tuple[str, float]]], as_tone: bool = False, volume: float = None, wait_completion: bool = False):
507+
def play_polyphonic(self, notes: list[list[tuple[str, float]]], as_tone: bool = False, volume: float = None, block: bool = False):
486508
"""
487509
Play multiple sequences of musical notes simultaneously (poliphony).
488510
It is possible to play multi track music by providing a list of sequences,
@@ -492,53 +514,66 @@ def play_polyphonic(self, notes: list[list[tuple[str, float]]], as_tone: bool =
492514
notes (list[list[tuple[str, float]]]): List of sequences, each sequence is a list of tuples (note, duration).
493515
as_tone (bool): If True, play as tones, considering duration in seconds
494516
volume (float, optional): Volume level (0.0 to 1.0). If None, uses master volume.
495-
wait_completion (bool): If True, block until the entire sequence has been played.
517+
block (bool): If True, block until the entire sequence has been played.
496518
"""
497519
blk, duration = super().play_polyphonic(notes, as_tone, volume)
498520
self._output_device.play(blk, block_on_queue=False)
499-
if wait_completion and duration > 0.0:
521+
if block and duration > 0.0:
500522
time.sleep(duration)
501523

502-
def play_chord(self, notes: list[str], note_duration: float | str = 1 / 4, volume: float = None):
524+
def play_chord(self, notes: list[str], note_duration: float | str = 1 / 4, volume: float = None, block: bool = False):
503525
"""
504526
Play a chord consisting of multiple musical notes simultaneously for a specified duration and volume.
505527
Args:
506528
notes (list[str]): List of musical notes to play (e.g., ['A4', 'C#5', 'E5']).
507529
note_duration (float | str): Duration of the chord as a float (like 1/4, 1/8) or a symbol ('W', 'H', 'Q', etc.).
508530
volume (float, optional): Volume level (0.0 to 1.0). If None, uses master volume.
531+
block (bool): If True, block until the entire chord has been played.
509532
"""
510533
blk = super().play_chord(notes, note_duration, volume)
511534
self._output_device.play(blk, block_on_queue=False)
535+
if block:
536+
duration = self._note_duration(note_duration)
537+
if duration > 0.0:
538+
time.sleep(duration)
512539

513-
def play(self, note: str, note_duration: float | str = 1 / 4, volume: float = None):
540+
def play(self, note: str, note_duration: float | str = 1 / 4, volume: float = None, block: bool = False):
514541
"""
515542
Play a musical note for a specified duration and volume.
516543
Args:
517544
note (str): The musical note to play (e.g., 'A4', 'C#5', 'REST').
518545
note_duration (float | str): Duration of the note as a float (like 1/4, 1/8) or a symbol ('W', 'H', 'Q', etc.).
519546
volume (float, optional): Volume level (0.0 to 1.0). If None, uses master volume.
547+
block (bool): If True, block until the entire note has been played.
520548
"""
521549
data = super().play(note, note_duration, volume)
522550
self._output_device.play(data, block_on_queue=False)
551+
if block:
552+
duration = self._note_duration(note_duration)
553+
if duration > 0.0:
554+
time.sleep(duration)
523555

524-
def play_tone(self, note: str, duration: float = 0.25, volume: float = None):
556+
def play_tone(self, note: str, duration: float = 0.25, volume: float = None, block: bool = False):
525557
"""
526558
Play a musical note for a specified duration and volume.
527559
Args:
528560
note (str): The musical note to play (e.g., 'A4', 'C#5', 'REST').
529561
duration (float): Duration of the note as a float in seconds.
530562
volume (float, optional): Volume level (0.0 to 1.0). If None, uses master volume.
563+
block (bool): If True, block until the entire note has been played.
531564
"""
532565
data = super().play_tone(note, duration, volume)
533566
self._output_device.play(data, block_on_queue=False)
567+
if block and duration > 0.0:
568+
time.sleep(duration)
534569

535-
def play_abc(self, abc_string: str, volume: float = None, wait_completion: bool = False):
570+
def play_abc(self, abc_string: str, volume: float = None, block: bool = False):
536571
"""
537572
Play a sequence of musical notes defined in ABC notation.
538573
Args:
539574
abc_string (str): ABC notation string defining the sequence of notes.
540575
volume (float, optional): Volume level (0.0 to 1.0). If None, uses master volume.
541-
wait_completion (bool): If True, block until the entire sequence has been played.
576+
block (bool): If True, block until the entire sequence has been played.
542577
"""
543578
if not abc_string or abc_string.strip() == "":
544579
return
@@ -547,9 +582,15 @@ def play_abc(self, abc_string: str, volume: float = None, wait_completion: bool
547582
for data, duration in player:
548583
self._output_device.play(data, block_on_queue=True)
549584
overall_duration += duration
550-
if wait_completion:
585+
if block:
551586
time.sleep(overall_duration)
552587

588+
def play_wav(self, wav_file: str, volume: float = None, block: bool = False):
589+
to_play, duration = super().play_wav(wav_file)
590+
self._output_device.play(to_play, block_on_queue=False)
591+
if block and duration > 0.0:
592+
time.sleep(duration)
593+
553594
def clear_playback_queue(self):
554595
"""
555596
Clear the playback queue of the output device.

0 commit comments

Comments
 (0)