Skip to content

Commit aa4122e

Browse files
BeanRepodsammarugastefanotorneo
authored
[app_bricks] Add WaveGenerator brick (#15)
* Add WaveGenerator brick * re-format examples * add external Speaker instance handling * add tests * fix mixer selection in Speaker * improve volume handling in WaveGenerator and add logging * adapt tests * update readme and examples * remove notes reference * update license * remove scale example * update licenses * Add WaveGenerator brick * re-format examples * add external Speaker instance handling * fix mixer selection in Speaker * update readme and examples * remove notes reference * update license * remove scale example * fix license --------- Co-authored-by: Dario Sammaruga <d.sammaruga@ext.arduino.cc> Co-authored-by: Stefano Torneo <s.torneo@arduino.cc>
1 parent 9b33ada commit aa4122e

File tree

12 files changed

+1251
-2
lines changed

12 files changed

+1251
-2
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Wave Generator brick
2+
3+
This brick provides continuous wave generation for real-time audio synthesis with multiple waveform types and smooth transitions.
4+
5+
## Overview
6+
7+
The Wave Generator brick allows you to:
8+
9+
- Generate continuous audio waveforms in real-time
10+
- Select between different waveform types (sine, square, sawtooth, triangle)
11+
- Control frequency and amplitude dynamically during playback
12+
- Configure smooth transitions with attack, release, and glide (portamento) parameters
13+
- Stream audio to USB speakers with minimal latency
14+
15+
It runs continuously in a background thread, producing audio blocks at a steady rate with configurable envelope parameters for professional-sounding synthesis.
16+
17+
## Features
18+
19+
- Four waveform types: sine, square, sawtooth, and triangle
20+
- Real-time frequency and amplitude control with smooth transitions
21+
- Configurable envelope parameters (attack, release, glide)
22+
- Hardware volume control support
23+
- Thread-safe operation for concurrent access
24+
- Efficient audio generation using NumPy vectorization
25+
- Custom speaker configuration support
26+
27+
## Prerequisites
28+
29+
Before using the Wave Generator brick, ensure you have the following:
30+
31+
- USB-C® Hub with external power supply (5V, 3A)
32+
- USB audio device (USB speaker or USB-C → 3.5mm adapter)
33+
- Arduino UNO Q running in Network Mode or SBC Mode (USB-C port needed for the hub)
34+
35+
## Code example and usage
36+
37+
Here is a basic example for generating a 440 Hz sine wave tone:
38+
39+
```python
40+
from arduino.app_bricks.wave_generator import WaveGenerator
41+
from arduino.app_utils import App
42+
43+
wave_gen = WaveGenerator()
44+
45+
App.start_brick(wave_gen)
46+
47+
# Set frequency to A4 note (440 Hz)
48+
wave_gen.set_frequency(440.0)
49+
50+
# Set amplitude to 80%
51+
wave_gen.set_amplitude(0.8)
52+
53+
App.run()
54+
```
55+
56+
You can customize the waveform type and envelope parameters:
57+
58+
```python
59+
wave_gen = WaveGenerator(
60+
wave_type="square",
61+
attack=0.01,
62+
release=0.03,
63+
glide=0.02
64+
)
65+
66+
App.start_brick(wave_gen)
67+
68+
# Change waveform during playback
69+
wave_gen.set_wave_type("triangle")
70+
71+
# Adjust envelope parameters
72+
wave_gen.set_envelope_params(attack=0.05, release=0.1, glide=0.05)
73+
74+
App.run()
75+
```
76+
77+
For specific hardware configurations, you can provide a custom Speaker instance:
78+
79+
```python
80+
from arduino.app_bricks.wave_generator import WaveGenerator
81+
from arduino.app_peripherals.speaker import Speaker
82+
from arduino.app_utils import App
83+
84+
speaker = Speaker(
85+
device=Speaker.USB_SPEAKER_2,
86+
sample_rate=16000,
87+
channels=1,
88+
format="S16_LE"
89+
)
90+
91+
wave_gen = WaveGenerator(sample_rate=16000, speaker=speaker)
92+
93+
App.start_brick(wave_gen)
94+
wave_gen.set_frequency(440.0)
95+
wave_gen.set_amplitude(0.7)
96+
97+
App.run()
98+
```
99+
100+
## Understanding Wave Generation
101+
102+
The Wave Generator brick produces audio through continuous waveform synthesis.
103+
104+
The `frequency` parameter controls the pitch of the output sound, measured in Hertz (Hz), where typical audible frequencies range from 20 Hz to 8000 Hz.
105+
106+
The `amplitude` parameter controls the volume as a value between 0.0 (silent) and 1.0 (maximum), with smooth transitions handled by the attack and release envelope parameters.
107+
108+
The `glide` parameter (also known as portamento) smoothly transitions between frequencies over time, creating sliding pitch effects similar to a theremin or synthesizer. Setting glide to 0 disables this effect but may cause audible clicks during fast frequency changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
2+
#
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
from .wave_generator import *
6+
7+
__all__ = ["WaveGenerator"]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
id: arduino:wave_generator
2+
name: Wave Generator
3+
description: "Continuous wave generator for audio synthesis. Generates sine, square, sawtooth, and triangle waveforms with smooth frequency and amplitude transitions."
4+
category: audio
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
2+
#
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
"""
6+
Basic Wave Generator Example
7+
8+
Generates a simple 440Hz sine wave (A4 note) and demonstrates
9+
basic frequency and amplitude control.
10+
"""
11+
12+
from arduino.app_bricks.wave_generator import WaveGenerator
13+
from arduino.app_utils import App
14+
15+
# Create wave generator with default settings
16+
wave_gen = WaveGenerator(
17+
sample_rate=16000,
18+
wave_type="sine",
19+
glide=0.02, # 20ms smooth frequency transitions
20+
)
21+
22+
# Start the generator
23+
App.start_brick(wave_gen)
24+
25+
# Set initial frequency and amplitude
26+
wave_gen.set_frequency(440.0) # A4 note (440 Hz)
27+
wave_gen.set_amplitude(0.7) # 70% amplitude
28+
wave_gen.set_volume(80) # 80% hardware volume
29+
30+
print("Playing 440Hz sine wave (A4 note)")
31+
print("Press Ctrl+C to stop")
32+
33+
# Keep the application running
34+
App.run()
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
2+
#
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
"""
6+
Waveform Comparison Example
7+
8+
Cycles through different waveform types to hear the difference
9+
between sine, square, sawtooth, and triangle waves.
10+
"""
11+
12+
import time
13+
from arduino.app_bricks.wave_generator import WaveGenerator
14+
from arduino.app_utils import App
15+
16+
wave_gen = WaveGenerator(sample_rate=16000, glide=0.02)
17+
App.start_brick(wave_gen)
18+
19+
# Set constant frequency and amplitude
20+
wave_gen.set_frequency(440.0)
21+
wave_gen.set_amplitude(0.6)
22+
23+
waveforms = ["sine", "square", "sawtooth", "triangle"]
24+
25+
26+
def cycle_waveforms():
27+
"""Cycle through different waveform types."""
28+
for wave_type in waveforms:
29+
print(f"Playing {wave_type} wave...")
30+
wave_gen.set_wave_type(wave_type)
31+
time.sleep(3)
32+
# Silence
33+
wave_gen.set_amplitude(0.0)
34+
time.sleep(2)
35+
36+
37+
print("Cycling through waveforms:")
38+
print("sine → square → sawtooth → triangle")
39+
print("Press Ctrl+C to stop")
40+
41+
App.run(user_loop=cycle_waveforms)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
2+
#
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
"""
6+
Frequency Sweep Example
7+
8+
Demonstrates smooth frequency transitions (glide/portamento effect)
9+
by sweeping through different frequency ranges.
10+
"""
11+
12+
import time
13+
from arduino.app_bricks.wave_generator import WaveGenerator
14+
from arduino.app_utils import App
15+
16+
wave_gen = WaveGenerator(
17+
wave_type="sine",
18+
glide=0.05, # 50ms glide for noticeable portamento
19+
)
20+
21+
App.start_brick(wave_gen)
22+
wave_gen.set_amplitude(0.7)
23+
24+
25+
def frequency_sweep():
26+
"""Sweep through frequency ranges."""
27+
28+
# Low to high sweep
29+
print("Sweeping low to high (220Hz → 880Hz)...")
30+
for freq in range(220, 881, 20):
31+
wave_gen.set_frequency(float(freq))
32+
time.sleep(0.1)
33+
34+
time.sleep(0.5)
35+
36+
# High to low sweep
37+
print("Sweeping high to low (880Hz → 220Hz)...")
38+
for freq in range(880, 219, -20):
39+
wave_gen.set_frequency(float(freq))
40+
time.sleep(0.1)
41+
# Fade out
42+
print("Fading out...")
43+
wave_gen.set_amplitude(0.0)
44+
time.sleep(2)
45+
46+
47+
print("Frequency sweep demonstration")
48+
print("Listen for smooth glide between frequencies")
49+
print("Press Ctrl+C to stop")
50+
51+
App.run(user_loop=frequency_sweep)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
2+
#
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
"""
6+
Envelope Control Example
7+
8+
Demonstrates amplitude envelope control with different
9+
attack and release times for various sonic effects.
10+
"""
11+
12+
import time
13+
from arduino.app_bricks.wave_generator import WaveGenerator
14+
from arduino.app_utils import App
15+
16+
wave_gen = WaveGenerator(wave_type="sine")
17+
App.start_brick(wave_gen)
18+
19+
wave_gen.set_frequency(440.0)
20+
wave_gen.set_volume(80)
21+
22+
23+
def envelope_demo():
24+
"""Demonstrate different envelope settings."""
25+
26+
# Fast attack, fast release (percussive)
27+
print("1. Percussive (fast attack/release)...")
28+
wave_gen.set_envelope_params(attack=0.001, release=0.01, glide=0.0)
29+
wave_gen.set_amplitude(0.8)
30+
time.sleep(0.5)
31+
wave_gen.set_amplitude(0.0)
32+
time.sleep(1)
33+
34+
# Slow attack, fast release (pad-like)
35+
print("2. Pad-like (slow attack, fast release)...")
36+
wave_gen.set_envelope_params(attack=0.2, release=0.05, glide=0.0)
37+
wave_gen.set_amplitude(0.8)
38+
time.sleep(1)
39+
wave_gen.set_amplitude(0.0)
40+
time.sleep(1)
41+
42+
# Fast attack, slow release (sustained)
43+
print("3. Sustained (fast attack, slow release)...")
44+
wave_gen.set_envelope_params(attack=0.01, release=0.3, glide=0.0)
45+
wave_gen.set_amplitude(0.8)
46+
time.sleep(0.5)
47+
wave_gen.set_amplitude(0.0)
48+
time.sleep(1.5)
49+
50+
# Medium attack and release (balanced)
51+
print("4. Balanced (medium attack/release)...")
52+
wave_gen.set_envelope_params(attack=0.05, release=0.05, glide=0.0)
53+
wave_gen.set_amplitude(0.8)
54+
time.sleep(0.8)
55+
wave_gen.set_amplitude(0.0)
56+
time.sleep(2)
57+
58+
59+
print("Envelope Control Demonstration")
60+
print("Listen to different attack/release characteristics")
61+
print("Press Ctrl+C to stop")
62+
63+
App.run(user_loop=envelope_demo)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
2+
#
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
"""
6+
Custom Speaker Configuration Example
7+
8+
Demonstrates how to use a pre-configured Speaker instance with WaveGenerator.
9+
Use this approach when you need:
10+
- Specific USB speaker selection (USB_SPEAKER_2, etc.)
11+
- Different audio format (S16_LE, etc.)
12+
- Explicit device name ("plughw:CARD=Device,DEV=0")
13+
"""
14+
15+
import time
16+
from arduino.app_bricks.wave_generator import WaveGenerator
17+
from arduino.app_peripherals.speaker import Speaker
18+
from arduino.app_utils import App
19+
20+
# List available USB speakers
21+
available_speakers = Speaker.list_usb_devices()
22+
print(f"Available USB speakers: {available_speakers}")
23+
24+
# Create and configure a Speaker with specific parameters
25+
speaker = Speaker(
26+
device=Speaker.USB_SPEAKER_1, # or None for auto-detect, or specific device
27+
sample_rate=16000,
28+
channels=1,
29+
format="FLOAT_LE",
30+
)
31+
32+
# Create WaveGenerator with the external speaker
33+
# WaveGenerator will manage the speaker's lifecycle (start/stop)
34+
wave_gen = WaveGenerator(
35+
sample_rate=16000,
36+
speaker=speaker, # Pass pre-configured speaker
37+
wave_type="sine",
38+
glide=0.02,
39+
)
40+
41+
# Start the WaveGenerator (which will also start the speaker)
42+
App.start_brick(wave_gen)
43+
44+
45+
def play_sequence():
46+
"""Play a simple frequency sequence."""
47+
frequencies = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25] # C4 to C5
48+
49+
for freq in frequencies:
50+
print(f"Playing {freq:.2f} Hz")
51+
wave_gen.set_frequency(freq)
52+
wave_gen.set_amplitude(0.7)
53+
time.sleep(0.5)
54+
55+
# Fade out
56+
wave_gen.set_amplitude(0.0)
57+
time.sleep(1)
58+
59+
60+
print("Playing musical scale with external speaker...")
61+
print("Press Ctrl+C to stop")
62+
63+
App.run(user_loop=play_sequence)
64+
65+
# WaveGenerator automatically stops the speaker when it stops
66+
print("Done")

0 commit comments

Comments
 (0)