Skip to content

Commit 83ffaa2

Browse files
EricPobotddemidov
authored andcommitted
music module added (#300)
* added music module and demo * added PyCharm files to .gitignore
1 parent d91d2d6 commit 83ffaa2

File tree

4 files changed

+324
-0
lines changed

4 files changed

+324
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ dist
77
RELEASE-VERSION
88
ev3dev/version.py
99
build
10+
.idea

demo/misc/snd/r2d2.wav

396 KB
Binary file not shown.

demo/misc/sound.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
# ------------------------------------------------------------------------------
5+
# Copyright (c) 2017 Eric Pascual
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in
15+
# all copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
# THE SOFTWARE.
24+
# -----------------------------------------------------------------------------
25+
26+
""" Sound capabilities demonstration.
27+
"""
28+
29+
from textwrap import dedent
30+
import os
31+
32+
from ev3dev.ev3 import Sound
33+
34+
_HERE = os.path.dirname(__file__)
35+
36+
print(dedent("""
37+
A long time ago
38+
in a galaxy far,
39+
far away...
40+
"""))
41+
42+
Sound.play_song((
43+
('D4', 'e3'),
44+
('D4', 'e3'),
45+
('D4', 'e3'),
46+
('G4', 'h'),
47+
('D5', 'h'),
48+
('C5', 'e3'),
49+
('B4', 'e3'),
50+
('A4', 'e3'),
51+
('G5', 'h'),
52+
('D5', 'q'),
53+
('C5', 'e3'),
54+
('B4', 'e3'),
55+
('A4', 'e3'),
56+
('G5', 'h'),
57+
('D5', 'q'),
58+
('C5', 'e3'),
59+
('B4', 'e3'),
60+
('C5', 'e3'),
61+
('A4', 'h.'),
62+
)).wait()
63+
64+
Sound.play(os.path.join(_HERE, 'snd/r2d2.wav')).wait()
65+
66+
Sound.speak("Luke, I am your father").wait()

ev3dev/core.py

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3163,6 +3163,16 @@ def update(self):
31633163
raise Exception("Not supported")
31643164

31653165

3166+
def _make_scales(notes):
3167+
""" Utility function used by Sound class for building the note frequencies table """
3168+
res = dict()
3169+
for note, freq in notes:
3170+
freq = round(freq)
3171+
for n in note.split('/'):
3172+
res[n] = freq
3173+
return res
3174+
3175+
31663176
class Sound:
31673177
"""
31683178
Sound-related functions. The class has only static methods and is not
@@ -3273,6 +3283,10 @@ def speak(text, espeak_opts='-a 200 -s 130'):
32733283

32743284
@staticmethod
32753285
def _get_channel():
3286+
"""
3287+
:return: the detected sound channel
3288+
:rtype: str
3289+
"""
32763290
if Sound.channel is None:
32773291
# Get default channel as the first one that pops up in
32783292
# 'amixer scontrols' output, which contains strings in the
@@ -3324,3 +3338,246 @@ def get_volume(channel=None):
33243338
return int(m.group('volume'))
33253339
else:
33263340
raise Exception('Failed to parse output of `amixer get {}`'.format(channel))
3341+
3342+
@classmethod
3343+
def play_song(cls, song, tempo=120, delay=50):
3344+
""" Plays a song provided as a list of tuples containing the note name and its
3345+
value using music conventional notation instead of numerical values for frequency
3346+
and duration.
3347+
3348+
It supports symbolic notes (e.g. ``A4``, ``D#3``, ``Gb5``) and durations (e.g. ``q``, ``h``).
3349+
3350+
Accepted note symbols and values are defined by the :py:attr:`NOTES` and :py:attr:`NOTE_VALUES`
3351+
dictionaries. The value can be suffixed by modifiers:
3352+
3353+
- a *divider* introduced by a ``/`` to obtain triplets for instance
3354+
(e.g. ``q/3`` for a triplet of eight note)
3355+
- a *multiplier* introduced by ``*`` (e.g. ``*1.5`` is a dotted note).
3356+
3357+
Shortcuts exist for common modifiers:
3358+
3359+
- ``3`` produces a triplet member note. For instance `e3` gives a triplet of eight notes,
3360+
i.e. 3 eight notes in the duration of a single quarter. You must ensure that 3 triplets
3361+
notes are defined in sequence to match the count, otherwise the result will not be the
3362+
expected one.
3363+
- ``.`` produces a dotted note, i.e. which duration is one and a half the base one. Double dots
3364+
are not currently supported.
3365+
3366+
Example::
3367+
3368+
>>> # A long time ago in a galaxy far,
3369+
>>> # far away...
3370+
>>> Sound.play_song((
3371+
>>> ('D4', 'e3'), # intro anacrouse
3372+
>>> ('D4', 'e3'),
3373+
>>> ('D4', 'e3'),
3374+
>>> ('G4', 'h'), # meas 1
3375+
>>> ('D5', 'h'),
3376+
>>> ('C5', 'e3'), # meas 2
3377+
>>> ('B4', 'e3'),
3378+
>>> ('A4', 'e3'),
3379+
>>> ('G5', 'h'),
3380+
>>> ('D5', 'q'),
3381+
>>> ('C5', 'e3'), # meas 3
3382+
>>> ('B4', 'e3'),
3383+
>>> ('A4', 'e3'),
3384+
>>> ('G5', 'h'),
3385+
>>> ('D5', 'q'),
3386+
>>> ('C5', 'e3'), # meas 4
3387+
>>> ('B4', 'e3'),
3388+
>>> ('C5', 'e3'),
3389+
>>> ('A4', 'h.'),
3390+
>>> ))
3391+
3392+
.. important::
3393+
3394+
Only 4/4 signature songs are supported with respect to note durations.
3395+
3396+
Args:
3397+
song (iterable[tuple(str, str)]): the song
3398+
tempo (int): the song tempo, given in quarters per minute
3399+
delay (int): delay in ms between notes
3400+
3401+
Returns:
3402+
subprocess.Popen: the spawn subprocess
3403+
"""
3404+
meas_duration = 60000 / tempo * 4
3405+
3406+
def beep_args(note, value):
3407+
""" Builds the arguments string for producing a beep matching
3408+
the requested note and value.
3409+
3410+
Args:
3411+
note (str): the note note and octave
3412+
value (str): the note value expression
3413+
Returns:
3414+
str: the arguments to be passed to the beep command
3415+
"""
3416+
freq = Sound.NOTE_FREQUENCIES[note.upper()]
3417+
if '/' in value:
3418+
base, factor = value.split('/')
3419+
duration = meas_duration * Sound.NOTE_VALUES[base] / float(factor)
3420+
elif '*' in value:
3421+
base, factor = value.split('*')
3422+
duration = meas_duration * Sound.NOTE_VALUES[base] * float(factor)
3423+
elif value.endswith('.'):
3424+
base = value[:-1]
3425+
duration = meas_duration * Sound.NOTE_VALUES[base] * 1.5
3426+
elif value.endswith('3'):
3427+
base = value[:-1]
3428+
duration = meas_duration * Sound.NOTE_VALUES[base] * 2 / 3
3429+
else:
3430+
duration = meas_duration * Sound.NOTE_VALUES[value]
3431+
3432+
return '-f %d -l %d -D %d' % (freq, duration, delay)
3433+
3434+
return Sound.beep(' -n '.join(
3435+
[beep_args(note, value) for note, value in song]
3436+
))
3437+
3438+
#: Note frequencies.
3439+
#:
3440+
#: This dictionary gives the rounded frequency of a note specified by its
3441+
#: standard US abbreviation and its octave number (e.g. ``C3``).
3442+
#: Alterations use the ``#`` and ``b`` symbols, respectively for
3443+
#: *sharp* and *flat*, between the note code and the octave number (e.g. ``D#4``, ``Gb5``).
3444+
NOTE_FREQUENCIES = _make_scales((
3445+
('C0', 16.35),
3446+
('C#0/Db0', 17.32),
3447+
('D0', 18.35),
3448+
('D#0/Eb0', 19.45), # expanded in one entry per symbol by _make_scales
3449+
('E0', 20.60),
3450+
('F0', 21.83),
3451+
('F#0/Gb0', 23.12),
3452+
('G0', 24.50),
3453+
('G#0/Ab0', 25.96),
3454+
('A0', 27.50),
3455+
('A#0/Bb0', 29.14),
3456+
('B0', 30.87),
3457+
('C1', 32.70),
3458+
('C#1/Db1', 34.65),
3459+
('D1', 36.71),
3460+
('D#1/Eb1', 38.89),
3461+
('E1', 41.20),
3462+
('F1', 43.65),
3463+
('F#1/Gb1', 46.25),
3464+
('G1', 49.00),
3465+
('G#1/Ab1', 51.91),
3466+
('A1', 55.00),
3467+
('A#1/Bb1', 58.27),
3468+
('B1', 61.74),
3469+
('C2', 65.41),
3470+
('C#2/Db2', 69.30),
3471+
('D2', 73.42),
3472+
('D#2/Eb2', 77.78),
3473+
('E2', 82.41),
3474+
('F2', 87.31),
3475+
('F#2/Gb2', 92.50),
3476+
('G2', 98.00),
3477+
('G#2/Ab2', 103.83),
3478+
('A2', 110.00),
3479+
('A#2/Bb2', 116.54),
3480+
('B2', 123.47),
3481+
('C3', 130.81),
3482+
('C#3/Db3', 138.59),
3483+
('D3', 146.83),
3484+
('D#3/Eb3', 155.56),
3485+
('E3', 164.81),
3486+
('F3', 174.61),
3487+
('F#3/Gb3', 185.00),
3488+
('G3', 196.00),
3489+
('G#3/Ab3', 207.65),
3490+
('A3', 220.00),
3491+
('A#3/Bb3', 233.08),
3492+
('B3', 246.94),
3493+
('C4', 261.63),
3494+
('C#4/Db4', 277.18),
3495+
('D4', 293.66),
3496+
('D#4/Eb4', 311.13),
3497+
('E4', 329.63),
3498+
('F4', 349.23),
3499+
('F#4/Gb4', 369.99),
3500+
('G4', 392.00),
3501+
('G#4/Ab4', 415.30),
3502+
('A4', 440.00),
3503+
('A#4/Bb4', 466.16),
3504+
('B4', 493.88),
3505+
('C5', 523.25),
3506+
('C#5/Db5', 554.37),
3507+
('D5', 587.33),
3508+
('D#5/Eb5', 622.25),
3509+
('E5', 659.25),
3510+
('F5', 698.46),
3511+
('F#5/Gb5', 739.99),
3512+
('G5', 783.99),
3513+
('G#5/Ab5', 830.61),
3514+
('A5', 880.00),
3515+
('A#5/Bb5', 932.33),
3516+
('B5', 987.77),
3517+
('C6', 1046.50),
3518+
('C#6/Db6', 1108.73),
3519+
('D6', 1174.66),
3520+
('D#6/Eb6', 1244.51),
3521+
('E6', 1318.51),
3522+
('F6', 1396.91),
3523+
('F#6/Gb6', 1479.98),
3524+
('G6', 1567.98),
3525+
('G#6/Ab6', 1661.22),
3526+
('A6', 1760.00),
3527+
('A#6/Bb6', 1864.66),
3528+
('B6', 1975.53),
3529+
('C7', 2093.00),
3530+
('C#7/Db7', 2217.46),
3531+
('D7', 2349.32),
3532+
('D#7/Eb7', 2489.02),
3533+
('E7', 2637.02),
3534+
('F7', 2793.83),
3535+
('F#7/Gb7', 2959.96),
3536+
('G7', 3135.96),
3537+
('G#7/Ab7', 3322.44),
3538+
('A7', 3520.00),
3539+
('A#7/Bb7', 3729.31),
3540+
('B7', 3951.07),
3541+
('C8', 4186.01),
3542+
('C#8/Db8', 4434.92),
3543+
('D8', 4698.63),
3544+
('D#8/Eb8', 4978.03),
3545+
('E8', 5274.04),
3546+
('F8', 5587.65),
3547+
('F#8/Gb8', 5919.91),
3548+
('G8', 6271.93),
3549+
('G#8/Ab8', 6644.88),
3550+
('A8', 7040.00),
3551+
('A#8/Bb8', 7458.62),
3552+
('B8', 7902.13)
3553+
))
3554+
3555+
#: Common note values.
3556+
#:
3557+
#: See https://en.wikipedia.org/wiki/Note_value
3558+
#:
3559+
#: This dictionary provides the multiplier to be applied to de whole note duration
3560+
#: to obtain subdivisions, given the corresponding symbolic identifier:
3561+
#:
3562+
#: = ===============================
3563+
#: w whole note (UK: semibreve)
3564+
#: h half note (UK: minim)
3565+
#: q quarter note (UK: crotchet)
3566+
#: e eight note (UK: quaver)
3567+
#: s sixteenth note (UK: semiquaver)
3568+
#: = ===============================
3569+
#:
3570+
#:
3571+
#: Triplets can be obtained by dividing the corresponding reference by 3.
3572+
#: For instance, the note value of a eight triplet will be ``NOTE_VALUE['e'] / 3``.
3573+
#: It is simpler however to user the ``3`` modifier of notes, as supported by the
3574+
#: :py:meth:`Sound.play_song` method.
3575+
NOTE_VALUES = {
3576+
'w': 1.,
3577+
'h': 1./2,
3578+
'q': 1./4,
3579+
'e': 1./8,
3580+
's': 1./16,
3581+
}
3582+
3583+

0 commit comments

Comments
 (0)