3 Commits

Author SHA1 Message Date
Lars Immisch 917a11b398 Report volume like alsamixer. Maybe.
I have no idea how they calculate dB.
2017-11-07 00:57:02 +01:00
Lars Immisch ce84e69cc1 Fix kwargs, and modernize mixertest.py a bit
Preliminary error handling for dB volume settings
2017-11-06 23:32:24 +01:00
Lars Immisch c2cfe0211b Add setting/getting volume in dB.
Potentially breaking change: getvolume now always returns a list of float values,
not integers as before.
2017-11-03 00:01:56 +01:00
13 changed files with 1983 additions and 2301 deletions
-2
View File
@@ -4,8 +4,6 @@ MANIFEST
doc/gh-pages/ doc/gh-pages/
doc/html/ doc/html/
doc/doctrees/ doc/doctrees/
doc/_build/
gh-pages/ gh-pages/
build/ build/
dist/ dist/
.vscode/
-29
View File
@@ -1,32 +1,3 @@
Version 0.8.6:
- Added four methods to the 'PCM' class to allow users to get detailed information about the device:
- 'getformats()' returns a dictionary of name / value pairs, one for each of the card's
supported formats - e.g. '{"U8": 1, "S16_LE": 2}',
- 'getchannels()' returns a list of the supported channel numbers, e.g. '[1, 2]',
- 'getrates()' returns supported sample rates for the device, e.g. '[48000]',
- 'getratebounds()' returns the device's official minimum and maximum supported
sample rates as a tuple, e.g. '(4000, 48000)'.
(#82 contributed by @jdstmporter)
- Prevent hang on close after capturing audio (#80 contributed by @daym)
Version 0.8.5:
- Return an empty string/bytestring when 'read()' detects an
overrun. Previously the returned data was undefined (contributed by @jcea)
- Unlimited setperiod buffer size when reading frames (contributed by @jcea)
Version 0.8.4:
- Fix Python3 API usage broken in 0.8.3
Version 0.8.3:
- Add DSD sample formats (contributed by @lintweaker)
- Add Mixer.handleevents() to acknowledge events identified by select.poll (contributed by @PaulSD)
- Add functions for listing cards and their names (contributed by @chrisdiamand)
- Add a method for setting enums (contributed by @chrisdiamand)
Version 0.8.2: Version 0.8.2:
- fix #3 (we cannot get the revision from git for pip installs) - fix #3 (we cannot get the revision from git for pip installs)
+1692 -1971
View File
File diff suppressed because it is too large Load Diff
-19
View File
@@ -1,22 +1,3 @@
# Make a new release
Update the version in setup.py
pyalsa_version = '0.9.0'
Commit and push the update.
Create and push a tag naming the version (i.e. 0.9.0):
git tag 0.9.0
git push origin 0.9.0
Upload the package:
python3 setup.py sdist
Don't forget to update the documentation.
# Publish the documentation # Publish the documentation
The documentation is published through the `gh-pages` branch. The documentation is published through the `gh-pages` branch.
+87 -72
View File
@@ -63,6 +63,7 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA.
useful. If you want to see a list of available PCM devices, use :func:`pcms` useful. If you want to see a list of available PCM devices, use :func:`pcms`
instead. instead.
.. function:: mixers(cardindex=-1, device='default') .. function:: mixers(cardindex=-1, device='default')
List the available mixers. The arguments are: List the available mixers. The arguments are:
@@ -107,7 +108,7 @@ PCM objects in :mod:`alsaaudio` can play or capture (record) PCM
sound through speakers or a microphone. The PCM constructor takes the sound through speakers or a microphone. The PCM constructor takes the
following arguments: following arguments:
.. class:: PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, rate=44100, channels=2, format=PCM_FORMAT_S16_LE, periodsize=32, device='default', cardindex=-1) .. class:: PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, device='default', cardindex=-1)
This class is used to represent a PCM device (either for playback and This class is used to represent a PCM device (either for playback and
recording). The arguments are: recording). The arguments are:
@@ -116,14 +117,75 @@ following arguments:
(default). (default).
* *mode* - can be either :const:`PCM_NONBLOCK`, or :const:`PCM_NORMAL` * *mode* - can be either :const:`PCM_NONBLOCK`, or :const:`PCM_NORMAL`
(default). (default).
* *rate* - the sampling rate in Hz. Typical values are ``8000`` * *device* - the name of the PCM device that should be used (for example
(mainly used for telephony), ``16000``, ``44100`` (default), ``48000`` and ``96000``. a value from the output of :func:`pcms`). The default value is
* *channels* - the number of channels. The default value is 2 (stereo). ``'default'``.
* *format* - the data format. This controls how the PCM device interprets data for playback, and how data is encoded in captures. * *cardindex* - the card index. If this argument is given, the device name
The default value is :const:`PCM_FORMAT_S16_LE`. is constructed as 'hw:*cardindex*' and
the `device` keyword argument is ignored.
``0`` is the first hardware sound card.
This will construct a PCM object with these default settings:
* Sample format: :const:`PCM_FORMAT_S16_LE`
* Rate: 44100 Hz
* Channels: 2
* Period size: 32 frames
*Changed in 0.8:*
- The `card` keyword argument is still supported,
but deprecated. Please use `device` instead.
- The keyword argument `cardindex` was added.
The `card` keyword is deprecated because it guesses the real ALSA
name of the card. This was always fragile and broke some legitimate usecases.
PCM objects have the following methods:
.. method:: PCM.pcmtype()
Returns the type of PCM object. Either :const:`PCM_CAPTURE` or
:const:`PCM_PLAYBACK`.
.. method:: PCM.pcmmode()
Return the mode of the PCM object. One of :const:`PCM_NONBLOCK`,
:const:`PCM_ASYNC`, or :const:`PCM_NORMAL`
.. method:: PCM.cardname()
Return the name of the sound card used by this PCM object.
.. method:: PCM.setchannels(nchannels)
Used to set the number of capture or playback channels. Common
values are: ``1`` = mono, ``2`` = stereo, and ``6`` = full 6 channel audio.
Few sound cards support more than 2 channels
.. method:: PCM.setrate(rate)
Set the sample rate in Hz for the device. Typical values are ``8000``
(mainly used for telephony), ``16000``, ``44100`` (CD quality),
``48000`` and ``96000``.
.. method:: PCM.setformat(format)
The sound *format* of the device. Sound format controls how the PCM device
interpret data for playback, and how data is encoded in captures.
The following formats are provided by ALSA:
========================= =============== ========================= ===============
Format Description Format Description
========================= =============== ========================= ===============
``PCM_FORMAT_S8`` Signed 8 bit samples for each channel ``PCM_FORMAT_S8`` Signed 8 bit samples for each channel
``PCM_FORMAT_U8`` Signed 8 bit samples for each channel ``PCM_FORMAT_U8`` Signed 8 bit samples for each channel
@@ -154,65 +216,14 @@ following arguments:
``PCM_FORMAT_U24_3BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 3 bytes) ``PCM_FORMAT_U24_3BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 3 bytes)
========================= =============== ========================= ===============
* *periodsize* - the period size in frames. Each write should consist of *periodsize* frames. The default value is 32.
* *device* - the name of the PCM device that should be used (for example
a value from the output of :func:`pcms`). The default value is
``'default'``.
* *cardindex* - the card index. If this argument is given, the device name
is constructed as 'hw:*cardindex*' and
the `device` keyword argument is ignored.
``0`` is the first hardware sound card.
This will construct a PCM object with the given settings.
*Changed in 0.9:*
- Added the optional named parameters `rate`, `channels`, `format` and `periodsize`.
*Changed in 0.8:*
- The `card` keyword argument is still supported,
but deprecated. Please use `device` instead.
- The keyword argument `cardindex` was added.
The `card` keyword is deprecated because it guesses the real ALSA
name of the card. This was always fragile and broke some legitimate usecases.
PCM objects have the following methods:
.. method:: PCM.pcmtype()
Returns the type of PCM object. Either :const:`PCM_CAPTURE` or
:const:`PCM_PLAYBACK`.
.. method:: PCM.pcmmode()
Return the mode of the PCM object. One of :const:`PCM_NONBLOCK`,
:const:`PCM_ASYNC`, or :const:`PCM_NORMAL`
.. method:: PCM.cardname()
Return the name of the sound card used by this PCM object.
.. method:: PCM.setchannels(nchannels)
.. deprecated:: 0.9 Use the `channels` named argument to :func:`PCM`.
.. method:: PCM.setrate(rate)
.. deprecated:: 0.9 Use the `rate` named argument to :func:`PCM`.
.. method:: PCM.setformat(format)
.. deprecated:: 0.9 Use the `format` named argument to :func:`PCM`.
.. method:: PCM.setperiodsize(period) .. method:: PCM.setperiodsize(period)
.. deprecated:: 0.9 Use the `periodsize` named argument to :func:`PCM`. Sets the actual period size in frames. Each write should consist of
exactly this number of frames, and each read will return this
number of frames (unless the device is in :const:`PCM_NONBLOCK` mode, in
which case it may return nothing at all)
.. method:: PCM.read() .. method:: PCM.read()
@@ -432,31 +443,35 @@ Mixer objects have the following methods:
This method will fail if the mixer has no capture switch capabilities. This method will fail if the mixer has no capture switch capabilities.
.. method:: Mixer.getvolume([direction]) .. method:: Mixer.getvolume(direction=PCM_PLAYBACK, unit=Percent)
Returns a list with the current volume settings for each channel. The list Returns a list with the current volume settings for each channel. The list
elements are integer percentages. elements are percentages or dB values, depending on *unit*.
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or The *direction* argument can be either :const:`PCM_PLAYBACK` or
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both :const:`PCM_CAPTURE`, which is relevant if the mixer can control both
playback and capture volume. The default value is :const:`PCM_PLAYBACK` playback and capture volume. The default value is :const:`PCM_PLAYBACK`
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`. if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
.. method:: Mixer.setvolume(volume, [channel], [direction]) .. method:: Mixer.setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit=Percent)
Change the current volume settings for this mixer. The *volume* argument Change the current volume settings for this mixer. The *volume* argument
controls the new volume setting as an integer percentage. controls the new volume setting as either a percentage or a dB value. Both
integer and floating point values can be given.
If the optional argument *channel* is present, the volume is set The *channel* argument can be used to restrict the channels for which the volume is
only for this channel. This assumes that the mixer can control the set. By default, the volume of all channels is adjusted. This assumes that the mixer
volume for the channels independently. can control the volume for the channels independently.
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or The *direction* argument can be either :const:`PCM_PLAYBACK` or
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both :const:`PCM_CAPTURE`, which is relevant if the mixer can control both
playback and capture volume. The default value is :const:`PCM_PLAYBACK` playback and capture volume. The default value is :const:`PCM_PLAYBACK`
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`. if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
The *unit* argument determines how the volume value is interpreted, as a prcentage
or as a dB value.
.. method:: Mixer.setmute(mute, [channel]) .. method:: Mixer.setmute(mute, [channel])
Sets the mute flag to a new value. The *mute* argument is either 0 for not Sets the mute flag to a new value. The *mute* argument is either 0 for not
+1 -1
View File
@@ -75,7 +75,7 @@ development at the time - and neither are very feature complete.
I wrote PyAlsaAudio to fill this gap. My long term goal is to have the module I wrote PyAlsaAudio to fill this gap. My long term goal is to have the module
included in the standard Python library, but that looks currently unlikely. included in the standard Python library, but that looks currently unlikely.
PyAlsaAudio has full support for sound capture, playback of sound, as well as PyAlsaAudio hass full support for sound capture, playback of sound, as well as
the ALSA Mixer API. the ALSA Mixer API.
MIDI support is not available, and since I don't own any MIDI hardware, it's MIDI support is not available, and since I don't own any MIDI hardware, it's
+4 -1
View File
@@ -56,7 +56,10 @@ class SinePlayer(Thread):
def __init__(self, frequency = 440.0): def __init__(self, frequency = 440.0):
Thread.__init__(self) Thread.__init__(self)
self.setDaemon(True) self.setDaemon(True)
self.device = alsaaudio.PCM(channels=channels, format=format, rate=sampling_rate) self.device = alsaaudio.PCM()
self.device.setchannels(channels)
self.device.setformat(format)
self.device.setrate(sampling_rate)
self.queue = Queue() self.queue = Queue()
self.change(frequency) self.change(frequency)
+10 -6
View File
@@ -46,13 +46,17 @@ def show_mixer(name, kwargs):
print("Capabilities: %s %s" % (' '.join(mixer.volumecap()), print("Capabilities: %s %s" % (' '.join(mixer.volumecap()),
' '.join(mixer.switchcap()))) ' '.join(mixer.switchcap())))
volumes = mixer.getvolume() volumes = mixer.getvolume()
for i in range(len(volumes)): for i, v in enumerate(volumes):
print("Channel %i volume: %i%%" % (i,volumes[i])) print("Channel %i volume: %.02f%%" % (i, v))
volumes = mixer.getvolume(unit=alsaaudio.dB)
for i, v in enumerate(volumes):
print("Channel %i volume: %.02fdB" % (i, v))
try: try:
mutes = mixer.getmute() mutes = mixer.getmute()
for i in range(len(mutes)): for i, m in enumerate(mutes):
if mutes[i]: if m:
print("Channel %i is muted" % i) print("Channel %i is muted" % i)
except alsaaudio.ALSAAudioError: except alsaaudio.ALSAAudioError:
# May not support muting # May not support muting
@@ -60,8 +64,8 @@ def show_mixer(name, kwargs):
try: try:
recs = mixer.getrec() recs = mixer.getrec()
for i in range(len(recs)): for i, r in enumerate(recs):
if recs[i]: if r:
print("Channel %i is recording" % i) print("Channel %i is recording" % i)
except alsaaudio.ALSAAudioError: except alsaaudio.ALSAAudioError:
# May not support recording # May not support recording
+10 -4
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
## playbacktest.py ## playbacktest.py
## ##
@@ -39,11 +38,18 @@ if __name__ == '__main__':
f = open(args[0], 'rb') f = open(args[0], 'rb')
# Open the device in playback mode in Mono, 44100 Hz, 16 bit little endian frames # Open the device in playback mode.
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, device=device)
# Set attributes: Mono, 44100 Hz, 16 bit little endian frames
out.setchannels(1)
out.setrate(44100)
out.setformat(alsaaudio.PCM_FORMAT_S16_LE)
# The period size controls the internal number of frames per period. # The period size controls the internal number of frames per period.
# The significance of this parameter is documented in the ALSA api. # The significance of this parameter is documented in the ALSA api.
out.setperiodsize(160)
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, channels=1, rate=44100, format=alsaaudio.PCM_FORMAT_S16_LE, periodsize=160, device=device)
# Read data from stdin # Read data from stdin
data = f.read(320) data = f.read(320)
while data: while data:
+40 -38
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
# Simple test script that plays (some) wav files # Simple test script that plays (some) wav files
@@ -12,52 +11,55 @@ import alsaaudio
def play(device, f): def play(device, f):
format = None print('%d channels, %d sampling rate\n' % (f.getnchannels(),
f.getframerate()))
# Set attributes
device.setchannels(f.getnchannels())
device.setrate(f.getframerate())
# 8bit is unsigned in wav files # 8bit is unsigned in wav files
if f.getsampwidth() == 1: if f.getsampwidth() == 1:
format = alsaaudio.PCM_FORMAT_U8 device.setformat(alsaaudio.PCM_FORMAT_U8)
# Otherwise we assume signed data, little endian # Otherwise we assume signed data, little endian
elif f.getsampwidth() == 2: elif f.getsampwidth() == 2:
format = alsaaudio.PCM_FORMAT_S16_LE device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
elif f.getsampwidth() == 3: elif f.getsampwidth() == 3:
format = alsaaudio.PCM_FORMAT_S24_3LE device.setformat(alsaaudio.PCM_FORMAT_S24_3LE)
elif f.getsampwidth() == 4: elif f.getsampwidth() == 4:
format = alsaaudio.PCM_FORMAT_S32_LE device.setformat(alsaaudio.PCM_FORMAT_S32_LE)
else: else:
raise ValueError('Unsupported format') raise ValueError('Unsupported format')
periodsize = f.getframerate() // 8 periodsize = f.getframerate() / 8
print('%d channels, %d sampling rate, format %d, periodsize %d\n' % (f.getnchannels(), device.setperiodsize(periodsize)
f.getframerate(),
format,
periodsize))
device = alsaaudio.PCM(channels=f.getnchannels(), rate=f.getframerate(), format=format, periodsize=periodsize, device=device) data = f.readframes(periodsize)
while data:
data = f.readframes(periodsize) # Read data from stdin
while data: device.write(data)
# Read data from stdin data = f.readframes(periodsize)
device.write(data)
data = f.readframes(periodsize)
def usage(): def usage():
print('usage: playwav.py [-d <device>] <file>', file=sys.stderr) print('usage: playwav.py [-d <device>] <file>', file=sys.stderr)
sys.exit(2) sys.exit(2)
if __name__ == '__main__': if __name__ == '__main__':
device = 'default' device = 'default'
opts, args = getopt.getopt(sys.argv[1:], 'd:') opts, args = getopt.getopt(sys.argv[1:], 'd:')
for o, a in opts: for o, a in opts:
if o == '-d': if o == '-d':
device = a device = a
if not args: if not args:
usage() usage()
with wave.open(args[0], 'rb') as f: f = wave.open(args[0], 'rb')
play(device, f) device = alsaaudio.PCM(device=device)
play(device, f)
f.close()
+37 -32
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
## recordtest.py ## recordtest.py
## ##
@@ -23,42 +22,48 @@ import getopt
import alsaaudio import alsaaudio
def usage(): def usage():
print('usage: recordtest.py [-d <device>] <file>', file=sys.stderr) print('usage: recordtest.py [-d <device>] <file>', file=sys.stderr)
sys.exit(2) sys.exit(2)
if __name__ == '__main__': if __name__ == '__main__':
device = 'default' device = 'default'
opts, args = getopt.getopt(sys.argv[1:], 'd:') opts, args = getopt.getopt(sys.argv[1:], 'd:')
for o, a in opts: for o, a in opts:
if o == '-d': if o == '-d':
device = a device = a
if not args: if not args:
usage() usage()
f = open(args[0], 'wb') f = open(args[0], 'wb')
# Open the device in nonblocking capture mode in mono, with a sampling rate of 44100 Hz # Open the device in nonblocking capture mode. The last argument could
# and 16 bit little endian samples # just as well have been zero for blocking mode. Then we could have
# The period size controls the internal number of frames per period. # left out the sleep call in the bottom of the loop
# The significance of this parameter is documented in the ALSA api. inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, device=device)
# For our purposes, it is suficcient to know that reads from the device
# will return this many frames. Each frame being 2 bytes long.
# This means that the reads below will return either 320 bytes of data
# or 0 bytes of data. The latter is possible because we are in nonblocking
# mode.
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK,
channels=1, rate=44100, format=alsaaudio.PCM_FORMAT_S16_LE,
periodsize=160, device=device)
loops = 1000000 # Set attributes: Mono, 44100 Hz, 16 bit little endian samples
while loops > 0: inp.setchannels(1)
loops -= 1 inp.setrate(44100)
# Read data from device inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
l, data = inp.read()
if l: # The period size controls the internal number of frames per period.
f.write(data) # The significance of this parameter is documented in the ALSA api.
time.sleep(.001) # For our purposes, it is suficcient to know that reads from the device
# will return this many frames. Each frame being 2 bytes long.
# This means that the reads below will return either 320 bytes of data
# or 0 bytes of data. The latter is possible because we are in nonblocking
# mode.
inp.setperiodsize(160)
loops = 1000000
while loops > 0:
loops -= 1
# Read data from device
l, data = inp.read()
if l:
f.write(data)
time.sleep(.001)
+1 -1
View File
@@ -8,7 +8,7 @@ from setuptools import setup
from setuptools.extension import Extension from setuptools.extension import Extension
from sys import version from sys import version
pyalsa_version = '0.9.0' pyalsa_version = '0.8.4'
if __name__ == '__main__': if __name__ == '__main__':
setup( setup(
+90 -114
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
# These are internal tests. They shouldn't fail, but they don't cover all # These are internal tests. They shouldn't fail, but they don't cover all
# of the ALSA API. Most importantly PCM.read and PCM.write are missing. # of the ALSA API. Most importantly PCM.read and PCM.write are missing.
@@ -13,148 +12,125 @@ import alsaaudio
import warnings import warnings
# we can't test read and write well - these are tested otherwise # we can't test read and write well - these are tested otherwise
PCMMethods = [ PCMMethods = [('pcmtype', None),
('pcmtype', None), ('pcmmode', None),
('pcmmode', None), ('cardname', None),
('cardname', None) ('setchannels', (2,)),
] ('setrate', (44100,)),
('setformat', (alsaaudio.PCM_FORMAT_S8,)),
PCMDeprecatedMethods = [ ('setperiodsize', (320,))]
('setchannels', (2,)),
('setrate', (44100,)),
('setformat', (alsaaudio.PCM_FORMAT_S8,)),
('setperiodsize', (320,))
]
# A clever test would look at the Mixer capabilities and selectively run the # A clever test would look at the Mixer capabilities and selectively run the
# omitted tests, but I am too tired for that. # omitted tests, but I am too tired for that.
MixerMethods = [('cardname', None), MixerMethods = [('cardname', None),
('mixer', None), ('mixer', None),
('mixerid', None), ('mixerid', None),
('switchcap', None), ('switchcap', None),
('volumecap', None), ('volumecap', None),
('getvolume', None), ('getvolume', None),
('getrange', None), ('getrange', None),
('getenum', None), ('getenum', None),
# ('getmute', None), # ('getmute', None),
# ('getrec', None), # ('getrec', None),
# ('setvolume', (60,)), # ('setvolume', (60,)),
# ('setmute', (0,)) # ('setmute', (0,))
# ('setrec', (0')), # ('setrec', (0')),
] ]
class MixerTest(unittest.TestCase): class MixerTest(unittest.TestCase):
"""Test Mixer objects""" """Test Mixer objects"""
def testMixer(self): def testMixer(self):
"""Open the default Mixers and the Mixers on every card""" """Open the default Mixers and the Mixers on every card"""
for c in alsaaudio.card_indexes(): for c in alsaaudio.card_indexes():
mixers = alsaaudio.mixers(cardindex=c) mixers = alsaaudio.mixers(cardindex=c)
for m in mixers: for m in mixers:
mixer = alsaaudio.Mixer(m, cardindex=c) mixer = alsaaudio.Mixer(m, cardindex=c)
mixer.close() mixer.close()
def testMixerAll(self): def testMixerAll(self):
"Run common Mixer methods on an open object" "Run common Mixer methods on an open object"
mixers = alsaaudio.mixers() mixers = alsaaudio.mixers()
mixer = alsaaudio.Mixer(mixers[0]) mixer = alsaaudio.Mixer(mixers[0])
for m, a in MixerMethods: for m, a in MixerMethods:
f = alsaaudio.Mixer.__dict__[m] f = alsaaudio.Mixer.__dict__[m]
if a is None: if a is None:
f(mixer) f(mixer)
else: else:
f(mixer, *a) f(mixer, *a)
mixer.close() mixer.close()
def testMixerClose(self): def testMixerClose(self):
"""Run common Mixer methods on a closed object and verify it raises an """Run common Mixer methods on a closed object and verify it raises an
error""" error"""
mixers = alsaaudio.mixers() mixers = alsaaudio.mixers()
mixer = alsaaudio.Mixer(mixers[0]) mixer = alsaaudio.Mixer(mixers[0])
mixer.close() mixer.close()
for m, a in MixerMethods: for m, a in MixerMethods:
f = alsaaudio.Mixer.__dict__[m] f = alsaaudio.Mixer.__dict__[m]
if a is None: if a is None:
self.assertRaises(alsaaudio.ALSAAudioError, f, mixer) self.assertRaises(alsaaudio.ALSAAudioError, f, mixer)
else: else:
self.assertRaises(alsaaudio.ALSAAudioError, f, mixer, *a) self.assertRaises(alsaaudio.ALSAAudioError, f, mixer, *a)
class PCMTest(unittest.TestCase): class PCMTest(unittest.TestCase):
"""Test PCM objects""" """Test PCM objects"""
def testPCM(self): def testPCM(self):
"Open a PCM object on every card" "Open a PCM object on every card"
for c in alsaaudio.card_indexes(): for c in alsaaudio.card_indexes():
pcm = alsaaudio.PCM(cardindex=c) pcm = alsaaudio.PCM(cardindex=c)
pcm.close() pcm.close()
def testPCMAll(self): def testPCMAll(self):
"Run all PCM methods on an open object" "Run all PCM methods on an open object"
pcm = alsaaudio.PCM() pcm = alsaaudio.PCM()
for m, a in PCMMethods: for m, a in PCMMethods:
f = alsaaudio.PCM.__dict__[m] f = alsaaudio.PCM.__dict__[m]
if a is None: if a is None:
f(pcm) f(pcm)
else: else:
f(pcm, *a) f(pcm, *a)
pcm.close() pcm.close()
def testPCMClose(self): def testPCMClose(self):
"Run all PCM methods on a closed object and verify it raises an error" "Run all PCM methods on a closed object and verify it raises an error"
pcm = alsaaudio.PCM() pcm = alsaaudio.PCM()
pcm.close() pcm.close()
for m, a in PCMMethods: for m, a in PCMMethods:
f = alsaaudio.PCM.__dict__[m] f = alsaaudio.PCM.__dict__[m]
if a is None: if a is None:
self.assertRaises(alsaaudio.ALSAAudioError, f, pcm) self.assertRaises(alsaaudio.ALSAAudioError, f, pcm)
else: else:
self.assertRaises(alsaaudio.ALSAAudioError, f, pcm, *a) self.assertRaises(alsaaudio.ALSAAudioError, f, pcm, *a)
def testPCMDeprecated(self): def testPCMDeprecated(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered. # Cause all warnings to always be triggered.
warnings.simplefilter("always") warnings.simplefilter("always")
try: try:
pcm = alsaaudio.PCM(card='default') pcm = alsaaudio.PCM(card='default')
except alsaaudio.ALSAAudioError: except alsaaudio.ALSAAudioError:
pass pass
# Verify we got a DepreciationWarning # Verify we got a DepreciationWarning
self.assertEqual(len(w), 1, "PCM(card='default') expected a warning" ) assert len(w) == 1
self.assertTrue(issubclass(w[-1].category, DeprecationWarning), "PCM(card='default') expected a DeprecationWarning") assert issubclass(w[-1].category, DeprecationWarning)
for m, a in PCMDeprecatedMethods:
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
pcm = alsaaudio.PCM()
f = alsaaudio.PCM.__dict__[m]
if a is None:
f(pcm)
else:
f(pcm, *a)
# Verify we got a DepreciationWarning
method = "%s%s" % (m, str(a))
self.assertEqual(len(w), 1, method + " expected a warning")
self.assertTrue(issubclass(w[-1].category, DeprecationWarning), method + " expected a DeprecationWarning")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()