Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/arduino/app_bricks/wave_generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Wave Generator brick

This brick provides continuous wave generation for real-time audio synthesis with multiple waveform types and smooth transitions.

## Overview

The Wave Generator brick allows you to:

- Generate continuous audio waveforms in real-time
- Select between different waveform types (sine, square, sawtooth, triangle)
- Control frequency and amplitude dynamically during playback
- Configure smooth transitions with attack, release, and glide (portamento) parameters
- Stream audio to USB speakers with minimal latency

It runs continuously in a background thread, producing audio blocks at a steady rate with configurable envelope parameters for professional-sounding synthesis.

## Features

- Four waveform types: sine, square, sawtooth, and triangle
- Real-time frequency and amplitude control with smooth transitions
- Configurable envelope parameters (attack, release, glide)
- Hardware volume control support
- Thread-safe operation for concurrent access
- Efficient audio generation using NumPy vectorization
- Custom speaker configuration support

## Prerequisites

Before using the Wave Generator brick, ensure you have the following:

- USB-C® Hub with external power supply (5V, 3A)
- USB audio device (USB speaker or USB-C → 3.5mm adapter)
- Arduino UNO Q running in Network Mode or SBC Mode (USB-C port needed for the hub)

## Code example and usage

Here is a basic example for generating a 440 Hz sine wave tone:

```python
from arduino.app_bricks.wave_generator import WaveGenerator
from arduino.app_utils import App

wave_gen = WaveGenerator()

App.start_brick(wave_gen)

# Set frequency to A4 note (440 Hz)
wave_gen.set_frequency(440.0)

# Set amplitude to 80%
wave_gen.set_amplitude(0.8)

App.run()
```

You can customize the waveform type and envelope parameters:

```python
wave_gen = WaveGenerator(
wave_type="square",
attack=0.01,
release=0.03,
glide=0.02
)

App.start_brick(wave_gen)

# Change waveform during playback
wave_gen.set_wave_type("triangle")

# Adjust envelope parameters
wave_gen.set_envelope_params(attack=0.05, release=0.1, glide=0.05)

App.run()
```

For specific hardware configurations, you can provide a custom Speaker instance:

```python
from arduino.app_bricks.wave_generator import WaveGenerator
from arduino.app_peripherals.speaker import Speaker
from arduino.app_utils import App

speaker = Speaker(
device=Speaker.USB_SPEAKER_2,
sample_rate=16000,
channels=1,
format="S16_LE"
)

wave_gen = WaveGenerator(sample_rate=16000, speaker=speaker)

App.start_brick(wave_gen)
wave_gen.set_frequency(440.0)
wave_gen.set_amplitude(0.7)

App.run()
```

## Understanding Wave Generation

The Wave Generator brick produces audio through continuous waveform synthesis.

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.

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.

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.
7 changes: 7 additions & 0 deletions src/arduino/app_bricks/wave_generator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
#
# SPDX-License-Identifier: MPL-2.0

from .wave_generator import *

__all__ = ["WaveGenerator"]
4 changes: 4 additions & 0 deletions src/arduino/app_bricks/wave_generator/brick_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
id: arduino:wave_generator
name: Wave Generator
description: "Continuous wave generator for audio synthesis. Generates sine, square, sawtooth, and triangle waveforms with smooth frequency and amplitude transitions."
category: audio
34 changes: 34 additions & 0 deletions src/arduino/app_bricks/wave_generator/examples/01_basic_tone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
#
# SPDX-License-Identifier: MPL-2.0

"""
Basic Wave Generator Example
Generates a simple 440Hz sine wave (A4 note) and demonstrates
basic frequency and amplitude control.
"""

from arduino.app_bricks.wave_generator import WaveGenerator
from arduino.app_utils import App

# Create wave generator with default settings
wave_gen = WaveGenerator(
sample_rate=16000,
wave_type="sine",
glide=0.02, # 20ms smooth frequency transitions
)

# Start the generator
App.start_brick(wave_gen)

# Set initial frequency and amplitude
wave_gen.set_frequency(440.0) # A4 note (440 Hz)
wave_gen.set_amplitude(0.7) # 70% amplitude
wave_gen.set_volume(80) # 80% hardware volume

print("Playing 440Hz sine wave (A4 note)")
print("Press Ctrl+C to stop")

# Keep the application running
App.run()
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
#
# SPDX-License-Identifier: MPL-2.0

"""
Waveform Comparison Example
Cycles through different waveform types to hear the difference
between sine, square, sawtooth, and triangle waves.
"""

import time
from arduino.app_bricks.wave_generator import WaveGenerator
from arduino.app_utils import App

wave_gen = WaveGenerator(sample_rate=16000, glide=0.02)
App.start_brick(wave_gen)

# Set constant frequency and amplitude
wave_gen.set_frequency(440.0)
wave_gen.set_amplitude(0.6)

waveforms = ["sine", "square", "sawtooth", "triangle"]


def cycle_waveforms():
"""Cycle through different waveform types."""
for wave_type in waveforms:
print(f"Playing {wave_type} wave...")
wave_gen.set_wave_type(wave_type)
time.sleep(3)
# Silence
wave_gen.set_amplitude(0.0)
time.sleep(2)


print("Cycling through waveforms:")
print("sine → square → sawtooth → triangle")
print("Press Ctrl+C to stop")

App.run(user_loop=cycle_waveforms)
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
#
# SPDX-License-Identifier: MPL-2.0

"""
Frequency Sweep Example

Demonstrates smooth frequency transitions (glide/portamento effect)
by sweeping through different frequency ranges.
"""

import time
from arduino.app_bricks.wave_generator import WaveGenerator
from arduino.app_utils import App

wave_gen = WaveGenerator(
wave_type="sine",
glide=0.05, # 50ms glide for noticeable portamento
)

App.start_brick(wave_gen)
wave_gen.set_amplitude(0.7)


def frequency_sweep():
"""Sweep through frequency ranges."""

# Low to high sweep
print("Sweeping low to high (220Hz → 880Hz)...")
for freq in range(220, 881, 20):
wave_gen.set_frequency(float(freq))
time.sleep(0.1)

time.sleep(0.5)

# High to low sweep
print("Sweeping high to low (880Hz → 220Hz)...")
for freq in range(880, 219, -20):
wave_gen.set_frequency(float(freq))
time.sleep(0.1)
# Fade out
print("Fading out...")
wave_gen.set_amplitude(0.0)
time.sleep(2)


print("Frequency sweep demonstration")
print("Listen for smooth glide between frequencies")
print("Press Ctrl+C to stop")

App.run(user_loop=frequency_sweep)
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
#
# SPDX-License-Identifier: MPL-2.0

"""
Envelope Control Example

Demonstrates amplitude envelope control with different
attack and release times for various sonic effects.
"""

import time
from arduino.app_bricks.wave_generator import WaveGenerator
from arduino.app_utils import App

wave_gen = WaveGenerator(wave_type="sine")
App.start_brick(wave_gen)

wave_gen.set_frequency(440.0)
wave_gen.set_volume(80)


def envelope_demo():
"""Demonstrate different envelope settings."""

# Fast attack, fast release (percussive)
print("1. Percussive (fast attack/release)...")
wave_gen.set_envelope_params(attack=0.001, release=0.01, glide=0.0)
wave_gen.set_amplitude(0.8)
time.sleep(0.5)
wave_gen.set_amplitude(0.0)
time.sleep(1)

# Slow attack, fast release (pad-like)
print("2. Pad-like (slow attack, fast release)...")
wave_gen.set_envelope_params(attack=0.2, release=0.05, glide=0.0)
wave_gen.set_amplitude(0.8)
time.sleep(1)
wave_gen.set_amplitude(0.0)
time.sleep(1)

# Fast attack, slow release (sustained)
print("3. Sustained (fast attack, slow release)...")
wave_gen.set_envelope_params(attack=0.01, release=0.3, glide=0.0)
wave_gen.set_amplitude(0.8)
time.sleep(0.5)
wave_gen.set_amplitude(0.0)
time.sleep(1.5)

# Medium attack and release (balanced)
print("4. Balanced (medium attack/release)...")
wave_gen.set_envelope_params(attack=0.05, release=0.05, glide=0.0)
wave_gen.set_amplitude(0.8)
time.sleep(0.8)
wave_gen.set_amplitude(0.0)
time.sleep(2)


print("Envelope Control Demonstration")
print("Listen to different attack/release characteristics")
print("Press Ctrl+C to stop")

App.run(user_loop=envelope_demo)
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
#
# SPDX-License-Identifier: MPL-2.0

"""
Custom Speaker Configuration Example
Demonstrates how to use a pre-configured Speaker instance with WaveGenerator.
Use this approach when you need:
- Specific USB speaker selection (USB_SPEAKER_2, etc.)
- Different audio format (S16_LE, etc.)
- Explicit device name ("plughw:CARD=Device,DEV=0")
"""

import time
from arduino.app_bricks.wave_generator import WaveGenerator
from arduino.app_peripherals.speaker import Speaker
from arduino.app_utils import App

# List available USB speakers
available_speakers = Speaker.list_usb_devices()
print(f"Available USB speakers: {available_speakers}")

# Create and configure a Speaker with specific parameters
speaker = Speaker(
device=Speaker.USB_SPEAKER_1, # or None for auto-detect, or specific device
sample_rate=16000,
channels=1,
format="FLOAT_LE",
)

# Create WaveGenerator with the external speaker
# WaveGenerator will manage the speaker's lifecycle (start/stop)
wave_gen = WaveGenerator(
sample_rate=16000,
speaker=speaker, # Pass pre-configured speaker
wave_type="sine",
glide=0.02,
)

# Start the WaveGenerator (which will also start the speaker)
App.start_brick(wave_gen)


def play_sequence():
"""Play a simple frequency sequence."""
frequencies = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25] # C4 to C5

for freq in frequencies:
print(f"Playing {freq:.2f} Hz")
wave_gen.set_frequency(freq)
wave_gen.set_amplitude(0.7)
time.sleep(0.5)

# Fade out
wave_gen.set_amplitude(0.0)
time.sleep(1)


print("Playing musical scale with external speaker...")
print("Press Ctrl+C to stop")

App.run(user_loop=play_sequence)

# WaveGenerator automatically stops the speaker when it stops
print("Done")
Loading