CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-gpiozero

A simple interface to GPIO devices with Raspberry Pi

Pending
Overview
Eval results
Files

tone-system.mddocs/

Tone System

Musical tone representation and manipulation system for use with TonalBuzzer and other audio devices. The tone system provides comprehensive support for musical notation, MIDI notes, and frequency representations.

Core Imports

from gpiozero.tones import Tone
from gpiozero import TonalBuzzer

Capabilities

Tone Class

Represents a frequency of sound in various musical notations.

class Tone(float):
    def __init__(self, value=None, *, frequency=None, midi=None, note=None):
        """
        Construct a Tone from frequency, MIDI note, or note string.
        
        Parameters:
        - value: float, int, or str - Auto-detected value (frequency, MIDI, or note)
        - frequency: float - Frequency in Hz (0 < freq <= 20000)
        - midi: int - MIDI note number (0-127)
        - note: str - Musical note string (e.g., 'A4', 'C#5', 'Bb3')
        """

    @classmethod
    def from_frequency(cls, freq: float) -> 'Tone':
        """
        Construct a Tone from a frequency in Hz.
        
        Parameters:
        - freq: float - Frequency in Hz (0 < freq <= 20000)
        
        Returns:
        Tone object
        """

    @classmethod
    def from_midi(cls, midi_note: int) -> 'Tone':
        """
        Construct a Tone from a MIDI note number.
        
        Parameters:
        - midi_note: int - MIDI note number (0-127)
        
        Returns:
        Tone object
        """

    @classmethod
    def from_note(cls, note: str) -> 'Tone':
        """
        Construct a Tone from a musical note string.
        
        Parameters:
        - note: str - Musical note (A-G + optional sharp/flat + octave 0-9)
        
        Returns:
        Tone object
        """

    @property
    def frequency(self) -> float:
        """Return the frequency of the tone in Hz."""

    @property
    def midi(self) -> int:
        """Return the (nearest) MIDI note number (0-127)."""

    @property
    def note(self) -> str:
        """Return the (nearest) note string representation."""

    def up(self, n: int = 1) -> 'Tone':
        """
        Return the Tone n semi-tones above this frequency.
        
        Parameters:
        - n: int - Number of semi-tones to step up (default: 1)
        
        Returns:
        Tone object
        """

    def down(self, n: int = 1) -> 'Tone':
        """
        Return the Tone n semi-tones below this frequency.
        
        Parameters:
        - n: int - Number of semi-tones to step down (default: 1)
        
        Returns:
        Tone object
        """

Musical Notation Support

Note String Format

Musical notes are specified as strings with the format: [A-G][accidental][octave]

  • Note letter: A, B, C, D, E, F, G
  • Accidental (optional):
    • # or for sharp (raises pitch by one semi-tone)
    • b or for flat (lowers pitch by one semi-tone)
    • for natural (no modification)
  • Octave: 0-9 (4 is the octave containing middle C and concert A at 440Hz)

Reference Notes

  • Concert A: A4 = 440Hz = MIDI note 69
  • Middle C: C4 = ~261.63Hz = MIDI note 60
  • Note range: A0 (~27.5Hz) to G9 (~12.5KHz)
  • MIDI range: 0-127 (approximately 8Hz to 12.5KHz)

Usage Examples

Basic Tone Construction

from gpiozero.tones import Tone

# All these create concert A (440Hz, MIDI note 69)
tone1 = Tone(440.0)           # From frequency
tone2 = Tone(69)              # From MIDI (auto-detected)
tone3 = Tone('A4')            # From note string

# Explicit construction methods
tone4 = Tone.from_frequency(440)
tone5 = Tone.from_midi(69)
tone6 = Tone.from_note('A4')

# Using keyword arguments
tone7 = Tone(frequency=440)
tone8 = Tone(midi=69)
tone9 = Tone(note='A4')

Musical Scale Construction

from gpiozero.tones import Tone

# C major scale starting from middle C
c_major_scale = [
    Tone('C4'),   # Do
    Tone('D4'),   # Re  
    Tone('E4'),   # Mi
    Tone('F4'),   # Fa
    Tone('G4'),   # Sol
    Tone('A4'),   # La
    Tone('B4'),   # Ti
    Tone('C5'),   # Do (octave)
]

# Or using stepping methods
base_note = Tone('C4')
c_major_scale = [
    base_note,           # C4
    base_note.up(2),     # D4
    base_note.up(4),     # E4  
    base_note.up(5),     # F4
    base_note.up(7),     # G4
    base_note.up(9),     # A4
    base_note.up(11),    # B4
    base_note.up(12),    # C5
]

# Chromatic scale (all 12 semi-tones)
chromatic = [Tone('C4').up(i) for i in range(13)]

Playing Melodies with TonalBuzzer

from gpiozero import TonalBuzzer
from gpiozero.tones import Tone
from time import sleep

buzzer = TonalBuzzer(18)

# Simple melody - Mary Had a Little Lamb
melody = [
    'E4', 'D4', 'C4', 'D4',
    'E4', 'E4', 'E4',
    'D4', 'D4', 'D4',
    'E4', 'G4', 'G4',
    'E4', 'D4', 'C4', 'D4',
    'E4', 'E4', 'E4', 'E4',
    'D4', 'D4', 'E4', 'D4',
    'C4'
]

# Play the melody
for note_str in melody:
    tone = Tone(note_str)
    buzzer.play(tone)
    sleep(0.5)
    buzzer.stop()
    sleep(0.1)

Frequency and MIDI Conversion

from gpiozero.tones import Tone

# Convert between different representations
tone = Tone('A4')
print(f"Note: {tone.note}")           # A4
print(f"Frequency: {tone.frequency}") # 440.0
print(f"MIDI: {tone.midi}")           # 69

# Work with accidentals (sharps and flats)
sharp_note = Tone('F#4')
flat_note = Tone('Gb4')
print(f"F# frequency: {sharp_note.frequency:.2f}Hz")
print(f"Gb frequency: {flat_note.frequency:.2f}Hz")
# Note: F# and Gb are enharmonic equivalents (same frequency)

# Stepping through notes
base = Tone('C4')
print(f"C4: {base.frequency:.2f}Hz")
print(f"D4: {base.up(2).frequency:.2f}Hz")
print(f"E4: {base.up(4).frequency:.2f}Hz")
print(f"B3: {base.down(1).frequency:.2f}Hz")

Musical Intervals and Chords

from gpiozero.tones import Tone

# Major chord construction (root, major third, perfect fifth)
def major_chord(root_note):
    root = Tone(root_note)
    third = root.up(4)  # Major third (4 semi-tones)
    fifth = root.up(7)  # Perfect fifth (7 semi-tones)
    return [root, third, fifth]

# C major chord
c_major = major_chord('C4')
print(f"C major chord: {[note.note for note in c_major]}")

# Minor chord construction (root, minor third, perfect fifth)
def minor_chord(root_note):
    root = Tone(root_note)
    third = root.up(3)  # Minor third (3 semi-tones)
    fifth = root.up(7)  # Perfect fifth (7 semi-tones)
    return [root, third, fifth]

# A minor chord
a_minor = minor_chord('A4')
print(f"A minor chord: {[note.note for note in a_minor]}")

Advanced Musical Programming

from gpiozero import TonalBuzzer
from gpiozero.tones import Tone
from time import sleep
import random

buzzer = TonalBuzzer(18)

# Pentatonic scale for improvisation
def pentatonic_scale(root_note):
    """Generate pentatonic scale from root note."""
    root = Tone(root_note)
    intervals = [0, 2, 4, 7, 9, 12]  # Pentatonic intervals
    return [root.up(interval) for interval in intervals]

# Generate random melody from pentatonic scale
scale = pentatonic_scale('C4')
melody_length = 16

print("Playing random pentatonic melody...")
for _ in range(melody_length):
    note = random.choice(scale)
    duration = random.choice([0.25, 0.5, 0.75])
    
    buzzer.play(note)
    sleep(duration)
    buzzer.stop()
    sleep(0.1)

# Arpeggio pattern
def play_arpeggio(chord_notes, pattern='up', repeats=2):
    """Play chord notes in arpeggio pattern."""
    if pattern == 'up':
        notes = chord_notes
    elif pattern == 'down':
        notes = list(reversed(chord_notes))
    elif pattern == 'up_down':
        notes = chord_notes + list(reversed(chord_notes[1:-1]))
    
    for _ in range(repeats):
        for note in notes:
            buzzer.play(note)
            sleep(0.3)
            buzzer.stop()
            sleep(0.05)

# Play C major arpeggio
c_major = major_chord('C4')
play_arpeggio(c_major, pattern='up_down')

Error Handling

from gpiozero.tones import Tone
from gpiozero.exc import AmbiguousTone
import warnings

# Handle ambiguous tone warnings
try:
    # Numbers below 128 may trigger ambiguity warnings
    tone = Tone(60)  # Could be 60Hz or MIDI note 60
except Exception as e:
    print(f"Error: {e}")

# Suppress warnings for known values
with warnings.catch_warnings():
    warnings.simplefilter("ignore", AmbiguousTone)
    tone = Tone(60)  # Treated as MIDI note 60 (middle C)

# Handle invalid inputs
try:
    invalid_note = Tone('H4')  # H is not a valid note
except ValueError as e:
    print(f"Invalid note: {e}")

try:
    invalid_freq = Tone(frequency=30000)  # Above 20kHz limit
except ValueError as e:
    print(f"Invalid frequency: {e}")

try:
    invalid_midi = Tone(midi=200)  # MIDI notes are 0-127
except ValueError as e:
    print(f"Invalid MIDI note: {e}")

Integration with GPIO Zero Devices

The Tone system integrates seamlessly with GPIO Zero's audio output devices:

from gpiozero import TonalBuzzer, Button
from gpiozero.tones import Tone
from signal import pause

buzzer = TonalBuzzer(18)
button = Button(2)

# Play different notes on button press
notes = ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5']
current_note = 0

def play_next_note():
    global current_note
    tone = Tone(notes[current_note])
    buzzer.play(tone)
    current_note = (current_note + 1) % len(notes)

def stop_note():
    buzzer.stop()

button.when_pressed = play_next_note
button.when_released = stop_note

pause()

The Tone system provides a powerful and intuitive way to work with musical data in GPIO Zero, supporting everything from simple note playback to complex musical composition and real-time audio synthesis.

Install with Tessl CLI

npx tessl i tessl/pypi-gpiozero

docs

composite-devices.md

index.md

input-devices.md

output-devices.md

pin-factories.md

spi-devices.md

system-monitoring.md

tone-system.md

tools.md

tile.json