A simple interface to GPIO devices with Raspberry Pi
—
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.
from gpiozero.tones import Tone
from gpiozero import TonalBuzzerRepresents 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 notes are specified as strings with the format: [A-G][accidental][octave]
# or ♯ for sharp (raises pitch by one semi-tone)b or ♭ for flat (lowers pitch by one semi-tone)♮ for natural (no modification)A4 = 440Hz = MIDI note 69C4 = ~261.63Hz = MIDI note 60A0 (~27.5Hz) to G9 (~12.5KHz)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')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)]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)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")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]}")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')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}")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