Skip to content

Commit d3559d5

Browse files
rjtokenringdsammaruga
authored andcommitted
Move utils code into brick
1 parent 6a9113f commit d3559d5

File tree

4 files changed

+74
-68
lines changed

4 files changed

+74
-68
lines changed

src/arduino/app_bricks/sound_generator/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
#
33
# SPDX-License-Identifier: MPL-2.0
44

5-
from arduino.app_utils import WaveGenerator, brick
5+
from arduino.app_utils import brick
66
from arduino.app_peripherals.speaker import Speaker
77
import threading
88
from typing import Iterable
99
import numpy as np
1010
import time
1111

12+
from .generator import WaveSamplesBuilder
1213
from .effects import *
1314
from .loaders import ABCNotationLoader
1415

@@ -95,7 +96,7 @@ def stop(self):
9596

9697
def _init_wave_generator(self, wave_form: str):
9798
with self._cfg_lock:
98-
self._wave_gen = WaveGenerator(sample_rate=self.SAMPLE_RATE, wave_form=wave_form)
99+
self._wave_gen = WaveSamplesBuilder(sample_rate=self.SAMPLE_RATE, wave_form=wave_form)
99100

100101
def set_wave_form(self, wave_form: str):
101102
"""
@@ -315,7 +316,7 @@ def play_polyphonic(self, notes: list[list[tuple[str, float]]], as_tone: bool =
315316
blk = mixed.astype(np.float32)
316317
blk = self._apply_sound_effects(blk, base_frequency)
317318
return (self._to_bytes(blk), max_duration)
318-
319+
319320
def play_chord(self, notes: list[str], note_duration: float | str = 1 / 4, volume: float = None) -> bytes:
320321
"""
321322
Play a chord consisting of multiple musical notes simultaneously for a specified duration and volume.

src/arduino/app_bricks/sound_generator/examples/4_effects.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
# Play as a retro-game sound
4545
player.set_wave_form("square")
46-
player.set_effects([SoundEffect.adsr()]) # For a more synththetic sound, add SoundEffect.bitcrusher() effect
46+
player.set_effects([SoundEffect.adsr()]) # For a more synththetic sound, add SoundEffect.bitcrusher() effect
4747
for note, duration in tune_sequence:
4848
player.play_tone(note, duration)
4949

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# SPDX-FileCopyrightText: Copyright (C) 2025 ARDUINO SA <http://www.arduino.cc>
2+
#
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
import numpy as np
6+
7+
8+
class WaveSamplesBuilder:
9+
"""Generate wave audio blocks.
10+
11+
This class produces wave blocks as NumPy buffers.
12+
13+
Attributes:
14+
sample_rate (int): Audio sample rate in Hz.
15+
"""
16+
17+
def __init__(self, wave_form: str = "sine", sample_rate: int = 16000):
18+
"""Create a new WaveGenerator.
19+
20+
Args:
21+
wave_form (str): The type of wave form to generate. Supported values
22+
are "sine", "square", "triangle", "white_noise" and "sawtooth".
23+
sample_rate (int): The playback sample rate (Hz) used to compute
24+
phase increments and buffer sizes.
25+
"""
26+
self.wave_form = wave_form.lower()
27+
self.sample_rate = int(sample_rate)
28+
29+
def generate_block(self, freq: float, block_dur: float, master_volume: float = 1.0):
30+
"""Generate a block of float32 audio samples.
31+
32+
Returned buffer is a NumPy view (float32) into an internal preallocated array and is valid
33+
until the next call to this method.
34+
35+
Args:
36+
freq (float): Target frequency in Hz for this block.
37+
block_dur (float): Duration of the requested block in seconds.
38+
master_volume (float, optional): Global gain multiplier. Defaults
39+
to 1.0.
40+
41+
Returns:
42+
numpy.ndarray: A 1-D float32 NumPy array containing the generated
43+
audio samples for the requested block.
44+
"""
45+
N = max(1, int(self.sample_rate * block_dur))
46+
47+
# compute wave form based on selected type
48+
t = np.arange(N, dtype=np.float32) / self.sample_rate
49+
50+
match self.wave_form:
51+
case "square":
52+
samples = 0.5 * (1 + np.sign(np.sin(2.0 * np.pi * freq * t)))
53+
case "triangle":
54+
samples = 2.0 * np.abs(2.0 * (freq * t % 1) - 1.0) - 1.0
55+
case "sawtooth":
56+
samples = 2.0 * (freq * t % 1.0) - 1.0
57+
case "white_noise":
58+
samples = np.random.uniform(-1.0, 1.0, size=N).astype(np.float32)
59+
case _: # "sine" e default
60+
samples = np.sin(2.0 * np.pi * freq * t)
61+
62+
samples = samples.astype(np.float32)
63+
64+
# apply gain
65+
mg = float(master_volume)
66+
if mg != 1.0:
67+
np.multiply(samples, mg, out=samples)
68+
69+
return samples

src/arduino/app_utils/audio.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,70 +6,6 @@
66
import numpy as np
77

88

9-
class WaveGenerator:
10-
"""Generate wave audio blocks.
11-
12-
This class produces wave blocks as NumPy buffers.
13-
14-
Attributes:
15-
sample_rate (int): Audio sample rate in Hz.
16-
"""
17-
18-
def __init__(self, wave_form: str = "sine", sample_rate: int = 16000):
19-
"""Create a new WaveGenerator.
20-
21-
Args:
22-
wave_form (str): The type of wave form to generate. Supported values
23-
are "sine", "square", "triangle", "white_noise" and "sawtooth".
24-
sample_rate (int): The playback sample rate (Hz) used to compute
25-
phase increments and buffer sizes.
26-
"""
27-
self.wave_form = wave_form.lower()
28-
self.sample_rate = int(sample_rate)
29-
30-
def generate_block(self, freq: float, block_dur: float, master_volume: float = 1.0):
31-
"""Generate a block of float32 audio samples.
32-
33-
Returned buffer is a NumPy view (float32) into an internal preallocated array and is valid
34-
until the next call to this method.
35-
36-
Args:
37-
freq (float): Target frequency in Hz for this block.
38-
block_dur (float): Duration of the requested block in seconds.
39-
master_volume (float, optional): Global gain multiplier. Defaults
40-
to 1.0.
41-
42-
Returns:
43-
numpy.ndarray: A 1-D float32 NumPy array containing the generated
44-
audio samples for the requested block.
45-
"""
46-
N = max(1, int(self.sample_rate * block_dur))
47-
48-
# compute wave form based on selected type
49-
t = np.arange(N, dtype=np.float32) / self.sample_rate
50-
51-
match self.wave_form:
52-
case "square":
53-
samples = 0.5 * (1 + np.sign(np.sin(2.0 * np.pi * freq * t)))
54-
case "triangle":
55-
samples = 2.0 * np.abs(2.0 * (freq * t % 1) - 1.0) - 1.0
56-
case "sawtooth":
57-
samples = 2.0 * (freq * t % 1.0) - 1.0
58-
case "white_noise":
59-
samples = np.random.uniform(-1.0, 1.0, size=N).astype(np.float32)
60-
case _: # "sine" e default
61-
samples = np.sin(2.0 * np.pi * freq * t)
62-
63-
samples = samples.astype(np.float32)
64-
65-
# apply gain
66-
mg = float(master_volume)
67-
if mg != 1.0:
68-
np.multiply(samples, mg, out=samples)
69-
70-
return samples
71-
72-
739
class SineGenerator:
7410
"""Generate sine-wave audio blocks with amplitude envelope smoothing.
7511

0 commit comments

Comments
 (0)