Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.pyc

.tox/
12 changes: 6 additions & 6 deletions audioread/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,45 +70,45 @@ def _mad_available():
return True


def audio_open(path):
def audio_open(path, block_samples=4096):
"""Open an audio file using a library that is available on this
system.
"""
# Standard-library WAV and AIFF readers.
from . import rawread
try:
return rawread.RawAudioFile(path)
return rawread.RawAudioFile(path, block_samples=block_samples)
except DecodeError:
pass

# Core Audio.
if _ca_available():
from . import macca
try:
return macca.ExtAudioFile(path)
return macca.ExtAudioFile(path, block_samples=block_samples)
except DecodeError:
pass

# GStreamer.
if _gst_available():
from . import gstdec
try:
return gstdec.GstAudioFile(path)
return gstdec.GstAudioFile(path, block_samples=block_samples)
except DecodeError:
pass

# MAD.
if _mad_available():
from . import maddec
try:
return maddec.MadAudioFile(path)
return maddec.MadAudioFile(path, block_samples=block_samples)
except DecodeError:
pass

# FFmpeg.
from . import ffdec
try:
return ffdec.FFmpegAudioFile(path)
return ffdec.FFmpegAudioFile(path, block_samples=block_samples)
except DecodeError:
pass

Expand Down
14 changes: 12 additions & 2 deletions audioread/macca.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ class ExtAudioFile(object):
>>> do_something(block)

"""
def __init__(self, filename):
block_samples = None
def __init__(self, filename, block_samples=4096):
url = CFURL(filename)
try:
self._obj = self._open_url(url)
Expand All @@ -204,6 +205,7 @@ def __init__(self, filename):
raise
del url

self.block_samples = block_samples
self.closed = False
self._file_fmt = None
self._client_fmt = None
Expand Down Expand Up @@ -295,9 +297,12 @@ def setup(self, bitdepth=16):
newfmt.mBytesPerFrame = newfmt.mBytesPerPacket
self.set_client_format(newfmt)

def read_data(self, blocksize=4096):
def read_data(self, blocksize=None):
"""Generates byte strings reflecting the audio data in the file.
"""
if blocksize is None:
blocksize = self.block_samples * self._client_fmt.mBytesPerFrame

frames = ctypes.c_uint(blocksize // self._client_fmt.mBytesPerFrame)
buf = ctypes.create_string_buffer(blocksize)

Expand All @@ -323,6 +328,11 @@ def read_data(self, blocksize=4096):
blob = data[:size]
yield blob

def seek(self, pos):
"""Seeks to the position in the file"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nitpicking: it's nice to write docstrings (a) in the imperative voice, and (b) as complete sentences, including a period. So I suggest:

Seek to a position in the file.

check(_coreaudio.ExtAudioFileSeek(self._obj, pos))


def close(self):
"""Close the audio file and free associated memory."""
if not self.closed:
Expand Down
15 changes: 12 additions & 3 deletions audioread/rawread.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ class RawAudioFile(object):
"""An AIFF, WAV, or Au file that can be read by the Python standard
library modules ``wave``, ``aifc``, and ``sunau``.
"""
def __init__(self, filename):
block_samples = None
def __init__(self, filename, block_samples=1024):
self._fh = open(filename, 'rb')
self.block_samples = block_samples

try:
self._file = aifc.open(self._fh)
Expand All @@ -71,7 +73,7 @@ def __init__(self, filename):
return

try:
self._file = wave.open(self._fh)
self._file = wave.open(self._fh, 'r')
except wave.Error:
self._fh.seek(0)
pass
Expand Down Expand Up @@ -107,6 +109,11 @@ def close(self):
self._file.close()
self._fh.close()

def seek(self, pos):
"""Seek to the position in the file"""
# All three libraries have the same method for seeking
self._file.setpos(pos)

@property
def channels(self):
"""Number of audio channels."""
Expand All @@ -122,7 +129,9 @@ def duration(self):
"""Length of the audio in seconds (a float)."""
return float(self._file.getnframes()) / self.samplerate

def read_data(self, block_samples=1024):
def read_data(self, block_samples=None):
if block_samples is None:
block_samples = self.block_samples
"""Generates blocks of PCM data found in the file."""
old_width = self._file.getsampwidth()

Expand Down
10 changes: 10 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import unittest
import sys

if __name__ == '__main__':
loader = unittest.TestLoader()
tests = loader.discover('test')
testRunner = unittest.runner.TextTestRunner()
result = testRunner.run(tests)
if not result.wasSuccessful():
sys.exit(1)
23 changes: 23 additions & 0 deletions test/fixtures/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Audio file fixtures for the tests.

#### test.wav
Test.wav was produced by doing:

```py
import numpy as np
from scipy.io import wavfile

if __name__ == '__main__':
size = 512
a = np.full((size, ), 0.)
b = np.full((size, ), 0.2)
c = np.full((size, ), 0.5)
d = np.full((size, ), 0.9)
t = np.concatenate((a, b, c, d))

wavfile.write('test.wav', 44100, t)
```

#### wavetest.wav

Produced with `make_test_wave.py`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little confusing that there are files called test.wav and wavtest.wav. Maybe it would be useful to describe what each one is for, and how they're different?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using test.wav for the macca backend--- I'll find some better names for it. We also might not need both

22 changes: 22 additions & 0 deletions test/fixtures/make_test_wave.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import numpy as np
import wave
import struct

def getData():
size = 512

a = np.full((size, ), 0., dtype=np.float16)
b = np.full((size, ), 0.2, dtype=np.float16)
c = np.full((size, ), 0.5, dtype=np.float16)
d = np.full((size, ), 0.9, dtype=np.float16)
return np.concatenate((a, b, c, d))


if __name__ == '__main__':
fout = wave.open('test/fixtures/wavetest.wave', 'w')
data = getData()
fout.setnchannels(1)
fout.setframerate(44100)
fout.setsampwidth(2)
fout.writeframes(data.tobytes())
fout.close()
Binary file added test/fixtures/test.wav
Binary file not shown.
Binary file added test/fixtures/wavetest.wave
Binary file not shown.
152 changes: 152 additions & 0 deletions test/test_audioread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import os
import unittest
import audioread
from audioread import rawread, macca

testMaccaFilename = os.path.abspath(os.path.join('test', 'fixtures', 'test.wav'))

rowLookup = [
b'\x00\x00',
b'\x9a\x19',
b'\x00@',
b'3s',
]
numSamples = 512

class TestAudioread(unittest.TestCase):

def test_audio_open_as_generator(self):
result = []
with audioread.audio_open(testMaccaFilename, block_samples=numSamples) as f:
gen = f.read_data()
try:
while True:
data = next(gen)
result.append(data)
except StopIteration:
pass

self.assertEqual(len(bytes(result[0])), numSamples*2)
self.assertEqual(len(rowLookup), len(result))
for i, row in enumerate(result):
self.assertEqual(bytes(row[0:2]), rowLookup[i])


def test_audio_open_as_forloop(self):
result = []
with audioread.audio_open(testMaccaFilename, block_samples=numSamples) as f:
self.assertEqual(f.nframes, 2048)
for buf in f:
result.append(buf)

self.assertEqual(len(bytes(result[0])), numSamples*2)
self.assertEqual(len(rowLookup), len(result))
for i, row in enumerate(result):
self.assertEqual(bytes(row[0:2]), rowLookup[i])


class TestMacca(unittest.TestCase):

def test_macca_as_generator(self):
result = []
with macca.ExtAudioFile(testMaccaFilename, block_samples=numSamples) as f:
gen = f.read_data()
try:
while True:
data = next(gen)
result.append(data)
except StopIteration:
pass

self.assertEqual(len(bytes(result[0])), numSamples*2)
self.assertEqual(len(rowLookup), len(result))
for i, row in enumerate(result):
self.assertEqual(bytes(row[0:2]), rowLookup[i])


def test_macca_as_forloop(self):
result = []
with macca.ExtAudioFile(testMaccaFilename, block_samples=numSamples) as f:
self.assertEqual(f.nframes, 2048)
for buf in f:
result.append(buf)

self.assertEqual(len(bytes(result[0])), numSamples*2)
self.assertEqual(len(rowLookup), len(result))
for i, row in enumerate(result):
self.assertEqual(bytes(row[0:2]), rowLookup[i])

def test_seek(self):
result = []
with macca.ExtAudioFile(testMaccaFilename, block_samples=numSamples) as input_file:
gen = input_file.read_data()

# move forward
row = next(gen)
row = next(gen)
row = next(gen)

# go back
input_file.seek(512)
row = next(gen)
self.assertEqual(bytes(row[0:2]), rowLookup[1])
row = next(gen)
self.assertEqual(bytes(row[0:2]), rowLookup[2])


testWaveFilename = os.path.abspath(os.path.join('test', 'fixtures', 'wavetest.wave'))
waveRowLookup = [
b'\x00\x00',
b'f2',
b'\x008',
b'3;',
]

class TestRawRead(unittest.TestCase):

def test_open_as_generator(self):
result = []
with rawread.RawAudioFile(testWaveFilename, block_samples=numSamples) as input_file:
gen = input_file.read_data()
try:
while True:
data = next(gen)
result.append(data)
except StopIteration:
pass

self.assertEqual(len(bytes(result[0])), numSamples*2)
self.assertEqual(len(rowLookup), len(result))
for i, row in enumerate(result):
self.assertEqual(bytes(row[0:2]), waveRowLookup[i])


def test_open_as_forloop(self):
result = []
with audioread.rawread.RawAudioFile(testWaveFilename, block_samples=numSamples) as input_file:
for buf in input_file:
result.append(buf)

for i, row in enumerate(result):
self.assertEqual(bytes(row[0:2]), waveRowLookup[i])

def test_seek(self):
result = []
with rawread.RawAudioFile(testWaveFilename, block_samples=numSamples) as input_file:
gen = input_file.read_data()

# move forward
row = next(gen)
row = next(gen)
row = next(gen)

# go back
input_file.seek(512)
row = next(gen)
self.assertEqual(bytes(row[0:2]), waveRowLookup[1])
row = next(gen)
self.assertEqual(bytes(row[0:2]), waveRowLookup[2])


if __name__ == '__main__':
unittest.main()
6 changes: 6 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[tox]
envlist = py27,py36

[testenv]
commands =
python test.py