@@ -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+
31663176class 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