0
# Tone System
1
2
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.
3
4
## Core Imports
5
6
```python
7
from gpiozero.tones import Tone
8
from gpiozero import TonalBuzzer
9
```
10
11
## Capabilities
12
13
### Tone Class
14
15
Represents a frequency of sound in various musical notations.
16
17
```python { .api }
18
class Tone(float):
19
def __init__(self, value=None, *, frequency=None, midi=None, note=None):
20
"""
21
Construct a Tone from frequency, MIDI note, or note string.
22
23
Parameters:
24
- value: float, int, or str - Auto-detected value (frequency, MIDI, or note)
25
- frequency: float - Frequency in Hz (0 < freq <= 20000)
26
- midi: int - MIDI note number (0-127)
27
- note: str - Musical note string (e.g., 'A4', 'C#5', 'Bb3')
28
"""
29
30
@classmethod
31
def from_frequency(cls, freq: float) -> 'Tone':
32
"""
33
Construct a Tone from a frequency in Hz.
34
35
Parameters:
36
- freq: float - Frequency in Hz (0 < freq <= 20000)
37
38
Returns:
39
Tone object
40
"""
41
42
@classmethod
43
def from_midi(cls, midi_note: int) -> 'Tone':
44
"""
45
Construct a Tone from a MIDI note number.
46
47
Parameters:
48
- midi_note: int - MIDI note number (0-127)
49
50
Returns:
51
Tone object
52
"""
53
54
@classmethod
55
def from_note(cls, note: str) -> 'Tone':
56
"""
57
Construct a Tone from a musical note string.
58
59
Parameters:
60
- note: str - Musical note (A-G + optional sharp/flat + octave 0-9)
61
62
Returns:
63
Tone object
64
"""
65
66
@property
67
def frequency(self) -> float:
68
"""Return the frequency of the tone in Hz."""
69
70
@property
71
def midi(self) -> int:
72
"""Return the (nearest) MIDI note number (0-127)."""
73
74
@property
75
def note(self) -> str:
76
"""Return the (nearest) note string representation."""
77
78
def up(self, n: int = 1) -> 'Tone':
79
"""
80
Return the Tone n semi-tones above this frequency.
81
82
Parameters:
83
- n: int - Number of semi-tones to step up (default: 1)
84
85
Returns:
86
Tone object
87
"""
88
89
def down(self, n: int = 1) -> 'Tone':
90
"""
91
Return the Tone n semi-tones below this frequency.
92
93
Parameters:
94
- n: int - Number of semi-tones to step down (default: 1)
95
96
Returns:
97
Tone object
98
"""
99
```
100
101
## Musical Notation Support
102
103
### Note String Format
104
105
Musical notes are specified as strings with the format: `[A-G][accidental][octave]`
106
107
- **Note letter**: A, B, C, D, E, F, G
108
- **Accidental** (optional):
109
- `#` or `♯` for sharp (raises pitch by one semi-tone)
110
- `b` or `♭` for flat (lowers pitch by one semi-tone)
111
- `♮` for natural (no modification)
112
- **Octave**: 0-9 (4 is the octave containing middle C and concert A at 440Hz)
113
114
### Reference Notes
115
116
- **Concert A**: `A4` = 440Hz = MIDI note 69
117
- **Middle C**: `C4` = ~261.63Hz = MIDI note 60
118
- **Note range**: `A0` (~27.5Hz) to `G9` (~12.5KHz)
119
- **MIDI range**: 0-127 (approximately 8Hz to 12.5KHz)
120
121
## Usage Examples
122
123
### Basic Tone Construction
124
125
```python
126
from gpiozero.tones import Tone
127
128
# All these create concert A (440Hz, MIDI note 69)
129
tone1 = Tone(440.0) # From frequency
130
tone2 = Tone(69) # From MIDI (auto-detected)
131
tone3 = Tone('A4') # From note string
132
133
# Explicit construction methods
134
tone4 = Tone.from_frequency(440)
135
tone5 = Tone.from_midi(69)
136
tone6 = Tone.from_note('A4')
137
138
# Using keyword arguments
139
tone7 = Tone(frequency=440)
140
tone8 = Tone(midi=69)
141
tone9 = Tone(note='A4')
142
```
143
144
### Musical Scale Construction
145
146
```python
147
from gpiozero.tones import Tone
148
149
# C major scale starting from middle C
150
c_major_scale = [
151
Tone('C4'), # Do
152
Tone('D4'), # Re
153
Tone('E4'), # Mi
154
Tone('F4'), # Fa
155
Tone('G4'), # Sol
156
Tone('A4'), # La
157
Tone('B4'), # Ti
158
Tone('C5'), # Do (octave)
159
]
160
161
# Or using stepping methods
162
base_note = Tone('C4')
163
c_major_scale = [
164
base_note, # C4
165
base_note.up(2), # D4
166
base_note.up(4), # E4
167
base_note.up(5), # F4
168
base_note.up(7), # G4
169
base_note.up(9), # A4
170
base_note.up(11), # B4
171
base_note.up(12), # C5
172
]
173
174
# Chromatic scale (all 12 semi-tones)
175
chromatic = [Tone('C4').up(i) for i in range(13)]
176
```
177
178
### Playing Melodies with TonalBuzzer
179
180
```python
181
from gpiozero import TonalBuzzer
182
from gpiozero.tones import Tone
183
from time import sleep
184
185
buzzer = TonalBuzzer(18)
186
187
# Simple melody - Mary Had a Little Lamb
188
melody = [
189
'E4', 'D4', 'C4', 'D4',
190
'E4', 'E4', 'E4',
191
'D4', 'D4', 'D4',
192
'E4', 'G4', 'G4',
193
'E4', 'D4', 'C4', 'D4',
194
'E4', 'E4', 'E4', 'E4',
195
'D4', 'D4', 'E4', 'D4',
196
'C4'
197
]
198
199
# Play the melody
200
for note_str in melody:
201
tone = Tone(note_str)
202
buzzer.play(tone)
203
sleep(0.5)
204
buzzer.stop()
205
sleep(0.1)
206
```
207
208
### Frequency and MIDI Conversion
209
210
```python
211
from gpiozero.tones import Tone
212
213
# Convert between different representations
214
tone = Tone('A4')
215
print(f"Note: {tone.note}") # A4
216
print(f"Frequency: {tone.frequency}") # 440.0
217
print(f"MIDI: {tone.midi}") # 69
218
219
# Work with accidentals (sharps and flats)
220
sharp_note = Tone('F#4')
221
flat_note = Tone('Gb4')
222
print(f"F# frequency: {sharp_note.frequency:.2f}Hz")
223
print(f"Gb frequency: {flat_note.frequency:.2f}Hz")
224
# Note: F# and Gb are enharmonic equivalents (same frequency)
225
226
# Stepping through notes
227
base = Tone('C4')
228
print(f"C4: {base.frequency:.2f}Hz")
229
print(f"D4: {base.up(2).frequency:.2f}Hz")
230
print(f"E4: {base.up(4).frequency:.2f}Hz")
231
print(f"B3: {base.down(1).frequency:.2f}Hz")
232
```
233
234
### Musical Intervals and Chords
235
236
```python
237
from gpiozero.tones import Tone
238
239
# Major chord construction (root, major third, perfect fifth)
240
def major_chord(root_note):
241
root = Tone(root_note)
242
third = root.up(4) # Major third (4 semi-tones)
243
fifth = root.up(7) # Perfect fifth (7 semi-tones)
244
return [root, third, fifth]
245
246
# C major chord
247
c_major = major_chord('C4')
248
print(f"C major chord: {[note.note for note in c_major]}")
249
250
# Minor chord construction (root, minor third, perfect fifth)
251
def minor_chord(root_note):
252
root = Tone(root_note)
253
third = root.up(3) # Minor third (3 semi-tones)
254
fifth = root.up(7) # Perfect fifth (7 semi-tones)
255
return [root, third, fifth]
256
257
# A minor chord
258
a_minor = minor_chord('A4')
259
print(f"A minor chord: {[note.note for note in a_minor]}")
260
```
261
262
### Advanced Musical Programming
263
264
```python
265
from gpiozero import TonalBuzzer
266
from gpiozero.tones import Tone
267
from time import sleep
268
import random
269
270
buzzer = TonalBuzzer(18)
271
272
# Pentatonic scale for improvisation
273
def pentatonic_scale(root_note):
274
"""Generate pentatonic scale from root note."""
275
root = Tone(root_note)
276
intervals = [0, 2, 4, 7, 9, 12] # Pentatonic intervals
277
return [root.up(interval) for interval in intervals]
278
279
# Generate random melody from pentatonic scale
280
scale = pentatonic_scale('C4')
281
melody_length = 16
282
283
print("Playing random pentatonic melody...")
284
for _ in range(melody_length):
285
note = random.choice(scale)
286
duration = random.choice([0.25, 0.5, 0.75])
287
288
buzzer.play(note)
289
sleep(duration)
290
buzzer.stop()
291
sleep(0.1)
292
293
# Arpeggio pattern
294
def play_arpeggio(chord_notes, pattern='up', repeats=2):
295
"""Play chord notes in arpeggio pattern."""
296
if pattern == 'up':
297
notes = chord_notes
298
elif pattern == 'down':
299
notes = list(reversed(chord_notes))
300
elif pattern == 'up_down':
301
notes = chord_notes + list(reversed(chord_notes[1:-1]))
302
303
for _ in range(repeats):
304
for note in notes:
305
buzzer.play(note)
306
sleep(0.3)
307
buzzer.stop()
308
sleep(0.05)
309
310
# Play C major arpeggio
311
c_major = major_chord('C4')
312
play_arpeggio(c_major, pattern='up_down')
313
```
314
315
### Error Handling
316
317
```python
318
from gpiozero.tones import Tone
319
from gpiozero.exc import AmbiguousTone
320
import warnings
321
322
# Handle ambiguous tone warnings
323
try:
324
# Numbers below 128 may trigger ambiguity warnings
325
tone = Tone(60) # Could be 60Hz or MIDI note 60
326
except Exception as e:
327
print(f"Error: {e}")
328
329
# Suppress warnings for known values
330
with warnings.catch_warnings():
331
warnings.simplefilter("ignore", AmbiguousTone)
332
tone = Tone(60) # Treated as MIDI note 60 (middle C)
333
334
# Handle invalid inputs
335
try:
336
invalid_note = Tone('H4') # H is not a valid note
337
except ValueError as e:
338
print(f"Invalid note: {e}")
339
340
try:
341
invalid_freq = Tone(frequency=30000) # Above 20kHz limit
342
except ValueError as e:
343
print(f"Invalid frequency: {e}")
344
345
try:
346
invalid_midi = Tone(midi=200) # MIDI notes are 0-127
347
except ValueError as e:
348
print(f"Invalid MIDI note: {e}")
349
```
350
351
## Integration with GPIO Zero Devices
352
353
The Tone system integrates seamlessly with GPIO Zero's audio output devices:
354
355
```python
356
from gpiozero import TonalBuzzer, Button
357
from gpiozero.tones import Tone
358
from signal import pause
359
360
buzzer = TonalBuzzer(18)
361
button = Button(2)
362
363
# Play different notes on button press
364
notes = ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5']
365
current_note = 0
366
367
def play_next_note():
368
global current_note
369
tone = Tone(notes[current_note])
370
buzzer.play(tone)
371
current_note = (current_note + 1) % len(notes)
372
373
def stop_note():
374
buzzer.stop()
375
376
button.when_pressed = play_next_note
377
button.when_released = stop_note
378
379
pause()
380
```
381
382
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.