1# ----------------------------------------------------------------------------
2#
3#  Copyright (C) 2008-2014 Fons Adriaensen <fons@linuxaudio.org>
4#
5#  This program is free software; you can redistribute it and/or modify
6#  it under the terms of the GNU General Public License as published by
7#  the Free Software Foundation; either version 3 of the License, or
8#  (at your option) any later version.
9#
10#  This program is distributed in the hope that it will be useful,
11#  but WITHOUT ANY WARRANTY; without even the implied warranty of
12#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#  GNU General Public License for more details.
14#
15#  You should have received a copy of the GNU General Public License
16#  along with this program.  If not, see <http:#www.gnu.org/licenses/>.
17#
18# ----------------------------------------------------------------------------
19
20
21import numpy as np
22from audiotools import audiofile_ext
23
24
25class AudioFile() :
26
27    """
28    Read/write audio file data to/from numpy arrays.
29
30    Data type of the arrays must be 32-bit float,
31    with sample values in the range (-1,+1).
32    """
33
34    default_maxread  = 1000000
35    default_sampfreq = 48000
36    default_filetype = 'wav,24bit'
37
38    MODE_RD = 1
39    MODE_WR = 2
40
41    def __init__(self):
42        """
43        Create a new AudioFile object.
44
45        An AudioFile object has no public data members.
46        All acces should be via the function members only.
47        """
48        self._capsule = audiofile_ext.create (self)
49        self._channels = None
50        self._sampfreq = None
51        self._filesize = None
52        self._filename = None
53        self._filemode = None
54        self._filetype = None
55
56
57    def open_read (self, filename):
58        """
59        Open an existing audio file for reading.
60
61        Any currently open file is closed first. Returns MODE_RD,
62        or None if opening the file fails.
63        """
64        self.close ()
65        audiofile_ext.open_read (self._capsule, filename)
66        if self.info (): self._filename = filename
67        return self._filemode
68
69
70    def open_write (self, filename, channels, sampfreq, filetype = None):
71        """
72        Create and open a new audio file for writing.
73
74        The filetype argument should be a string consisting of the
75        words listed below, comma separated and without spaces.
76        Dither is applied to 16-bit files only.
77
78        File types:      caf, wav, amb, aiff, flac.
79        Sample formats:  16bit, 24bit, 32bit, float.
80        Dither type:     none, rec, tri, lips.
81
82        Any currently open file is closed first. Returns MODE_WR,
83        or None if opening the file fails.
84        """
85        self.close ()
86        audiofile_ext.open_write (self._capsule, filename, channels, sampfreq, filetype)
87        if self.info (): self._filename = filename
88        return self._filemode
89
90
91    def info (self):
92        """
93        Read parameters of the current file to local copies.
94
95        For internal use only, called by the open* members.
96        Returns the current access mode or 0.
97        """
98        R = audiofile_ext.info (self._capsule)
99        if R [0] > 0:
100            self._filemode = R [0]
101            self._channels = R [1]
102            self._sampfreq = R [2]
103            self._filesize = R [3]
104            self._filetype = R [4] + "," + R [5]
105        return R [0]
106
107
108    def close (self):
109        """
110        Close the current audio file.
111        """
112        self._channels = None
113        self._sampfreq = None
114        self._filesize = None
115        self._filename = None
116        self._filemode = None
117        self._filetype = None
118        return audiofile_ext.close(self._capsule)
119
120
121    def seek (self, posit, mode = 0):
122        """
123        Seek to new position, in frames.
124        """
125        return audiofile_ext.seek(self._capsule, posit, mode)
126
127
128    def read (self, data):
129        """
130        Read audio frames from file into a numpy array 'data'.
131
132        The 'data' argument can be an array or an array view
133        created by slicing or reshaping. The first dimension
134        determines the number of frames, the second dimension
135        must be equal to the number of channels in the audio
136        file. For a single channel file a 1-dimensional array
137        or view, or a python array is accepted as well.
138
139        Note that if the slicing operation results in a copy,
140        no data will be returned as the copy will be discarded
141        after being filled in. Basic slicing and reshaping
142        always create valid view.
143        """
144        return audiofile_ext.read(self._capsule, data)
145
146
147    def write (self, data):
148        """
149        Write audio frames from numpy array 'data' to file.
150
151        See read() for more info on the 'data' argument.
152        """
153        return audiofile_ext.write(self._capsule, data)
154
155
156    def channels (self):
157        """
158        Return the number of channels in the current file.
159        """
160        return self._channels
161
162
163    def sampfreq (self):
164        """
165        Return the sample frequency of the current file.
166        """
167        return self._sampfreq
168
169
170    def filesize (self):
171        """
172        Return the size of the current file, in frames.
173        """
174        return self._filesize
175
176
177    def filename (self):
178        """
179        Return the current filename.
180        """
181        return self._filename
182
183
184    def filemode (self):
185        """
186        Return the current access mode.
187        """
188        return self._filemode
189
190
191    def filetype (self):
192        """
193        Return a string describing the file type and format.
194        """
195        return self._filetype
196
197
198
199def read_audio (filename, maxread = AudioFile.default_maxread):
200    """
201    Commodity function. Read an entire file and return a
202    2-D array containing the audio data, the sample rate
203    and the file type. The optional second parameter puts
204    a limit on the number of frames read and returned.
205    """
206    F = AudioFile ()
207    F.open_read (filename)
208    ns = min (F.filesize (), maxread)
209    sf = F.sampfreq ()
210    ft = F.filetype ()
211    A = np.empty ([ns, F.channels ()], dtype = np.float32)
212    F.read (A)
213    F.close ()
214    return (A, sf, ft)
215
216
217def write_audio (A, filename, sampfreq = AudioFile.default_sampfreq, filetype = None):
218    """
219    Commodity fuction. Writes the vector or 2-D array A to
220    an audio file with the given sample rate and type. The
221    type options are the same as for write().
222    """
223    F = AudioFile ()
224    if A.ndim == 1: nc = 1
225    elif A.ndim == 2: nc = A.shape [1]
226    else: raise TypeError ("Array dimension must be 1 or 2")
227    F.open_write (filename, nc, sampfreq, filetype)
228    F.write (A)
229    F.close ()
230