Sound and music¶
Play sounds from your script, and draw what the music does.
What you get¶
class Bell
def on_button(btn)
if btn == "select" && !sound.playing()
sound.play({'rtttl': 'bell:d=4,o=5,b=100:e,c'})
end
end
def draw()
clear()
text(1, 6, "Bell", 0xFFFFFF)
end
end
return Bell()
Press select while the app is shown, and the clock plays a short melody. sound.playing() keeps
a quick double press from playing it twice.
How it behaves¶
sound.play()returns at once, and the sound plays while your script goes on.truemeans the clock took the sound, not that you hear it. A name that is not stored plays nothing, and the volume may be0.falsemeans this clock can play none of it. What the answer means lists every case.- Your script plays one sound at a time. Another sound replaces it.
- An alert is never cut off by a script. The sound of a notification plays over your script's sounds.
musicturns sound into numbers you can draw. Read them indraw(): the readings for playback match the moment you hear the sound.- Start sounds in a button handler or in
loop(), never indraw(), which runs about 40 times a second.
What to play¶
sound.play(x) takes a sound, the same one /api/v1/audio/play
takes. In Berry it is a string, a map or a list:
sound.play("doorbell") # a stored sound, by name
sound.play({'rtttl': 'beep:d=16,o=6,b=200:c'}) # a melody in the call
sound.play({'track': 3}) # a DFPlayer track
sound.play({'file': 'siren', 'loop': true}) # repeats until you stop it
A map has exactly one of these keys. The key says what plays:
| Key | Value | Plays |
|---|---|---|
'file' |
a name such as 'doorbell', or a script's sound such as 'Racer/boost' |
a stored MP3 or melody |
'rtttl' |
an RTTTL melody | the melody |
'song' |
song text | a song on the synthesizer |
'track' |
a whole number from 1 to 2999 | a track from the DFPlayer |
'station' |
a station name, a position in the list, or a stream address | internet radio, see Internet radio |
Next to the key, a map may have:
'loop': trueto repeat the sound until it is stopped.
A plain name is short for {'file': name}. A list holds 1 to 4 sounds, and the clock plays the
first one it can play.
'station' starts the radio, as the web UI does. It is not allowed in a list, and sound.stop()
does not stop it.
One sound after another¶
sound.playing() lets you wait for one sound to finish before you start the next. A button that
plays a sound needs this, or a quick double press plays it twice. See
What you get.
Play sounds one after another in loop(), never in draw():
var queue
def init()
self.queue = ["chime", "alarm"] # sounds waiting to play
end
def loop()
if size(self.queue) > 0 && !sound.playing()
sound.play(self.queue.pop(0))
end
end
Use notify() when the sound belongs to an event that should also interrupt the rotation and show
something. Use sound when you only want the sound. A notify() sound is an alert, so it plays
over your script's sounds. See Notifications.
Different clocks¶
Clocks have different sound hardware. Ask sound.can() before you play something only some clocks
have:
def on_button(btn)
if btn == "select" && !sound.playing()
if sound.can('mp3') sound.play("doorbell")
else sound.play({'rtttl': 'bell:d=4,o=5,b=100:e,c'}) end
end
end
A list does the same in one call. The clock plays the first entry it can play:
sound.can() answers these keys, among others. They are the audio flags of
GET /api/v1/capabilities:
| Key | true when the clock can |
|---|---|
'mp3' |
play MP3 files |
'rtttl' |
play melodies |
'song' |
play song text on its synthesizer |
'track' |
play DFPlayer tracks |
'radio' |
play internet radio |
A script that cannot work without one of them says so in its header, for example
# @needs audio.rtttl. See Sharing.
A sound that repeats¶
Your script plays one sound at a time. A sound with 'loop': true repeats until it is stopped or
another sound replaces it, an alert too.
Songs¶
On a clock with a synthesizer, sound.can('song'), your script plays music written as
song text: instruments and notes in a string, with no file to upload.
A song is a sound like any other: sound.play({'song': text}) plays it once, and with
'loop': true it repeats as a sound that repeats does.
# @name Pulse
# @needs audio.song
class Pulse
var tune
def init()
self.tune = "bpm 100\n"
"inst pad wave=saw unison=12 attack=200 sustain=60 release=400 cutoff=1500 volume=65\n"
"inst kick wave=sine note=c2 pitch=24/25 attack=0 decay=200 sustain=0 volume=200\n"
"pad: [a3 c4 e]:16 | [f3 a c4]:16 |\n"
"kick: (%x...x...x...x...)2"
end
def on_show()
sound.play({'song': self.tune, 'loop': true}) # the music starts with the app
end
def on_hide()
sound.stop() # and stops with it
end
def draw()
clear()
text(1, 6, "Pulse", 0xFF4000)
end
end
return Pulse()
Berry joins string literals that follow each other, even across line breaks and comments. So you can
write a song one statement per line, as above. Every piece except the last ends in \n, the line
break between two statements.
- On a clock without a synthesizer,
sound.playreturnsfalse. - A mistake in the text raises a
value_errorwith the reason, line and column, for examplesong: unexpected 'x' (line 2, column 7). Text you build while the script runs belongs in atry. - A song plays to the end of its longest track, then starts again from its first bar when it
repeats. Its
loopline makes no difference on this clock.
Sounds for your script¶
A script can bring its own sounds: MP3 files in a folder named after the script, next to it.
The script plays them by name, like any MP3:
def on_show()
sound.play({'file': 'theme', 'loop': true}) # /SCRIPTS/Racer/theme.mp3
end
def on_button(btn)
if btn == "select" sound.play("boost") end
end
A plain name is looked up in this order:
- the script's own folder,
/SCRIPTS/Racer/boost.mp3; - the shared MP3s,
/MP3/boost.mp3; - the melodies,
/MELODIES/boost.txt.
sound.play() and the sound of a notify()
look this way. Anywhere else, in a request or in another script, the sound is "Racer/boost".
A module has no sounds of its own. Its code plays from the folder of the app that imports it, so that app brings the sounds.
Add sounds to a script:
- From the AWTRIX Hub, sounds come with the script. This works from its Hub page, and in the web UI when it updates a script or installs one that another script needs. A clock that cannot play MP3s gets the script without them.
- By hand (a shared
.axfile carries no sounds): open the script on the Scripts tab and press the note button in the editor bar. It opens the script's group on the Audio tab, where you upload, play and delete its sounds. See The web UI → MP3s.
Deleting the script asks about its sounds. Delete with sounds removes them. Keep sounds leaves them on the clock, and a script installed again under the same name uses them. Saving a new version keeps them. Renaming the script in the web UI moves them to the new name.
The files follow the rules for every MP3 on AWTRIX:
- The file name is what the script plays: 1 to 32 characters of
A-Z,a-z,0-9,_and-, plus.mp3.boost.mp3is played as"boost". - The sound must be an MP3, mono or stereo, at 8 to 48 kHz. A file that is not an MP3 is refused on upload or stays silent.
- They share storage with everything else on AWTRIX, so keep them short.
- A script's sound may have the same name as a melody or a shared MP3. Its own sound wins.
Listing, uploading and deleting over the API: Script sounds.
Music¶
music turns sound into numbers you can draw.
It analyses what your clock plays: a station or an MP3. This needs a speaker.
The readings for playback match the moment you hear the sound, so picture and sound stay in step.
music.bands() answers how loud each range of notes is, from bass to treble, music.level() how
loud it is, and music.beat() when a beat lands. music.station() and music.title() name what
the radio plays. Music calls lists every answer.
None of them ever returns nil. When there is no input, they return zeros, false and "".
The radio does not put the station or the song on the display by itself. A script that shows them
builds the line in loop(). music.title() changes whenever the station announces a new song:
def init()
self.line = ""
end
def loop()
self.line = music.station() + ": " + music.title()
end
def draw()
clear()
scroll_text(self.line, 0xFFFFFF)
end
def should_show()
return music.title() != ""
end
A spectrum display takes one line. bar_chart() with autoscale off draws a fixed 0-to-8 range,
so ask for the bands with max 8:
The classic look adds a peak dot per bar that hangs for a moment and then falls. This one also skips its turn while nothing plays:
class Spectrum
var peaks
var hold
def init()
self.peaks = []
self.hold = []
for i: 0..15
self.peaks.push(0)
self.hold.push(0)
end
end
def should_show()
return music.playing()
end
def draw()
var bands = music.bands(16, 8)
var bar_w = (width() - 15) / 16
bar_chart(bands, "Rainbow", false)
for i: 0..15
if bands[i] >= self.peaks[i]
self.peaks[i] = bands[i]
self.hold[i] = 12
elif self.hold[i] > 0
self.hold[i] -= 1
elif self.peaks[i] > 0
self.peaks[i] -= 1
end
if self.peaks[i] > 0
pixel(i * (bar_w + 1), height() - 1 - self.peaks[i], 0xFFFFFF)
end
end
end
end
return Spectrum()
music.beat() is for a pulse, a circle that flares on every beat and shrinks again:
var glow
def init()
self.glow = 0
end
def draw()
if music.beat() self.glow = 4 end
if self.glow > 0
circle_fill(width() / 2, height() / 2, self.glow, hsv(300, 100, 100))
self.glow -= 1
end
end
- The levels adjust themselves: a quiet track fills the display just like a loud one, and the volume setting does not change the picture.
- Read
music.beat()indraw(), never inloop().loop()runs once a second and would miss most beats. music.playing()turnstrueas soon as playback starts. The first numbers follow a fraction of a second later.
Good to know¶
- A quick double press plays a sound twice. Check
!sound.playing()before you play, as in What you get. - Start sounds in a button handler or in
loop(), never indraw().draw()runs about 40 times a second. - A sound that repeats keeps playing when your app leaves. Stop it in
on_hide()when it belongs to what your app shows. - A mistake in song text raises a
value_error. Put song text you build while the script runs into atry. - Read
music.beat()indraw(), never inloop().loop()runs once a second and misses most beats.
Details¶
Calls¶
| Call | Does | Returns |
|---|---|---|
sound.play(x) |
plays a sound once. With 'loop': true it repeats until it is stopped |
true when the clock took it, false when it can play none of it |
sound.stop() |
stops every sound your script plays | nil |
sound.stop('loop') |
stops your script's sound that repeats with 'loop': true |
nil |
sound.playing() |
whether a sound from your script plays right now | true or false |
sound.can() |
what this clock can play | a map such as {'mp3': true, 'rtttl': true, ...} |
sound.can(key) |
one entry of that map, for example sound.can('mp3') |
true or false |
What the answer means¶
true means the clock took the request, and the sound starts right away. true does not mean
you hear it:
- A name that is not stored plays nothing.
- While an alert plays, for example the sound of a notification, a single sound from your script is not played. An alert is never cut off by a script.
- The volume may be
0.
false means this clock cannot play any of it, for example
{'track': 3} on a clock without a DFPlayer.
It never stops your script.
A mistake in the sound raises a value_error at the call, with the key and the reason:
A map with two sound keys, an unknown key, a bad name or a track out of range raise the same way. The reasons are listed in When it goes wrong.
Music calls¶
| Call | Answer |
|---|---|
music.bands(n?, max?) |
a list of n numbers, bass on the left, treble on the right. n is 1 to 32 (default 32), and each number is scaled from 0 to max (default 255) |
music.level() |
how loud it is right now, 0 to 255, relative to the quietest and loudest moment of the last few seconds, so a compressed station still moves the needle |
music.beat() |
true for exactly one frame each time a beat lands |
music.playing() |
true while a station or an MP3 is playing |
music.station() |
the name of the playing station, or its URL when it was started by URL. "" when no station plays |
music.title() |
the song title the station sends right now, or "" when it sends none or no station plays |
Every sound limit is in Limits.
Related¶
- Sound: stored sounds, melodies, the volume and what each clock can play
- Song text: instruments and notes in a string
- Internet radio: the stations
musicreads - Controlling the device: a sound together with a notification