Source code for spplugin

# -*- coding: utf-8 -*-
"""
A python module for plugin-based audio file I/O based on `spPlugin
<https://www-ie.meijo-u.ac.jp/labs/rj001/spLibs/index.html>`_
which supports several sound formats including WAV, AIFF, MP3,
Ogg Vorbis, FLAC, ALAC, raw, and more.

Example:
    The following is the example plotting the waveform of an input audio file.
    ::

        import os
        import sys
        import spplugin
        import numpy as np
        import matplotlib.pyplot as plt


        def plotfilebyplugin(filename):
            with spplugin.open(filename, 'r') as pf:
                nchannels = pf.getnchannels()
                samprate = pf.getsamprate()
                sampbit = pf.getsampbit()
                nframes = pf.getnframes()
                duration = nframes / samprate

                y = pf.createndarray(nframes * nchannels)
                nread = pf.read(y)
                print('nread = %d' % nread)

                y.resize((nframes, nchannels))

                x = np.linspace(0.0, duration, nframes)
                for i in range(nchannels):
                    plt.plot(x, y[:,i])
                plt.xlim(0.0, duration)
                plt.xlabel('Time [s]')
                plt.ylabel('Amplitude (normalized)')
                plt.show()


        if __name__ == '__main__':
            if len(sys.argv) <= 1:
                print('usage: %s filename'
                      % os.path.basename(sys.argv[0]), file=sys.stderr)
                quit()

            plotfilebyplugin(sys.argv[1])


    The following is the another example using a high-level function of
    :func:`~spplugin.audioread` which is similar to MATLAB's one
    (version 0.7.16+).
    ::

        import os
        import sys
        import spplugin
        import spaudio


        def audioreadexample(filename):
            data, samprate, params = spplugin.audioread(filename)
            print('samprate = ' + str(samprate) + ', params =\\n' + str(params))

            with spaudio.open('wo', params=params) as a:
                nwframes = a.writeframes(data)
                print('write frames = %d' % nwframes)


        if __name__ == '__main__':
            if len(sys.argv) <= 1:
                print('usage: %s filename'
                      % os.path.basename(sys.argv[0]), file=sys.stderr)
                quit()

            audioreadexample(sys.argv[1])


    The following is the 'write' version of above example using a high-level
    function of :func:`~spplugin.audiowrite` which is similar to MATLAB's one
    (version 0.7.16+).
    ::

        import os
        import sys
        import spplugin
        import spaudio


        def audiowriteexample(filename):
            duration = 2.0
            with spaudio.open('ro', nchannels=2, samprate=44100) as a:
                nframes = round(duration * a.getsamprate())
                data = a.readframes(nframes, channelwise=True)
                print('nread = %d' % len(data))

                nwframes = spplugin.audiowrite(filename, data, a.getsamprate())
                print('write frames = %d' % nwframes)


        if __name__ == '__main__':
            if len(sys.argv) <= 1:
                print('usage: %s filename'
                      % os.path.basename(sys.argv[0]), file=sys.stderr)
                quit()

            audiowriteexample(sys.argv[1])


"""


import sys
import os
import locale
import warnings
import array
from collections import namedtuple
from _spplugin import spplugin_c as _spplugin_c


__version__ = '0.7.16'


_encoding = locale.getpreferredencoding()
_defaultdir = _spplugin_c.spGetDefaultDir()
_ischarbinary = isinstance(_defaultdir, bytes)


def _encodetocstr(pstr, encoding=None):
    if pstr is not None:
        global _ischarbinary
        if _ischarbinary:
            if encoding is None:
                global _encoding
                encoding = _encoding

            cstr = pstr.encode(encoding)
        else:
            cstr = pstr
    else:
        cstr = None
    return cstr


def _decodefromcstr(cstr, encoding=None):
    if cstr is None:
        return None

    global _ischarbinary
    if _ischarbinary:
        if encoding is None:
            global _encoding
            encoding = _encoding

        return cstr.decode(encoding)
    else:
        return cstr


_is_64bits = sys.maxsize > 2**32
_sysdirname = ''
_py_plugindir = ''

if sys.platform == 'win32':
    if _is_64bits:
        _sysdirname = 'win64'
    else:
        _sysdirname = 'win32'
elif sys.platform == 'darwin':
    _sysdirname = 'mac64'


def _listup_plugin_files():
    i = 0
    while True:
        cstr = _spplugin_c.spSearchPluginFile(i)
        if cstr is None:
            break
        print(_decodefromcstr(cstr))
        i += 1


if _sysdirname:
    # d = os.path.dirname(sys.modules['spplugin'].__file__)
    d = os.path.dirname(os.path.abspath(__file__))
    _py_plugindir = os.path.join(d, '_spplugins', _sysdirname)
    if os.path.isdir(_py_plugindir):
        _spplugin_c.spSetPluginSearchPath(_encodetocstr(_py_plugindir))
    else:
        _py_plugindir = ''


[docs]def getplugininfo(name): """Gets detailed information of the plugin which has a name specified.""" cname = _encodetocstr(name) plugin = _spplugin_c.spLoadPlugin(cname) info = None if plugin is not None: info = _spplugin_c.spGetPluginInformation(plugin) if info is not None: info = _decodefromcstr(info) _spplugin_c.spFreePlugin(plugin) return info
[docs]def getplugindesc(name): """Gets short description of the plugin which has a name specified.""" cname = _encodetocstr(name) plugin = _spplugin_c.spLoadPlugin(cname) desc = None if plugin is not None: desc = _spplugin_c.spGetPluginDescription(plugin) if desc is not None: desc = _decodefromcstr(desc) _spplugin_c.spFreePlugin(plugin) return desc
# https://stackoverflow.com/questions/2166818/how-to-check-if-an-object-is-an-instance-of-a-namedtuple def _isnamedtupleinstance(x): t = type(x) b = t.__bases__ if len(b) != 1 or b[0] != tuple: return False f = getattr(t, '_fields', None) if not isinstance(f, tuple): return False return all(type(n) == str for n in f)
[docs]class Error(Exception): """Base exception class for spplugin.""" pass
[docs]class FileError(Error): """Exception raised by audio file problems.""" pass
[docs]class WrongPluginError(Error): """Exception raised by a wrong plugin.""" pass
[docs]class SuitableNotFoundError(Error): """Exception raised if no suitable plugin is found.""" pass
[docs]class BogusFileError(Error): """Exception raised if the audio file is bogus.""" pass
[docs]class FileTypeError(Error): """Exception raised if the specified file type is not accepted.""" pass
[docs]class SampleRateError(Error): """Exception raised if the specified sample rate is not accepted.""" pass
[docs]class SampleBitError(Error): """Exception raised if the specified bits/sample is not accepted.""" pass
[docs]class NChannelsError(Error): """Exception raised if the specified number of channels is not accepted.""" pass
[docs]class NFramesRequiredError(Error): """Exception raised if the total number of frames is required to use the plugin.""" pass
_StandardLibParams = namedtuple('_standard_lib_params', 'nchannels sampwidth framerate nframes comptype compname') _STANDARD_LIB_KEYS = ['nchannels', 'sampwidth', 'framerate', 'nframes', 'comptype', 'compname'] PARAMS_KEYS = ['nchannels', 'sampbit', 'samprate', 'nframes', 'pluginid', 'filetype', 'filedesc', 'filefilter', 'songinfo', 'sampwidth', 'framerate', 'length'] SONGINFO_KEYS_IN_NUMBER = ['track', 'track_toral', 'disc', 'disc_total', 'tempo'] SONGINFO_KEYS_IN_STRING = ['title', 'artist', 'album', 'genre', 'release', 'copyright', 'engineer', 'source', 'software', 'subject', 'comment', 'album_artist', 'composer', 'lyricist', 'producer', 'isrc']
[docs]class SpFilePlugin: """A class for audio file I/O. This class is similar to one provided by the standard libraries such as aifc, wave, or sunau. The important difference is that ``set*()`` functions must be called before :func:`~spplugin.SpFilePlugin.open` in this class. You can set parameters by using optional arguments of :func:`~spplugin.open` function. """ def __init__(self): self._pluginname = None self._open_mode = ' ' self._songinfo = {} self._plugin = None self._waveinfo_c = _spplugin_c.spWaveInfo() _spplugin_c.spInitWaveInfo(self._waveinfo_c) self._songinfo_c = _spplugin_c.spSongInfo() _spplugin_c.spInitSongInfo(self._songinfo_c) self._currentpos = 0 self._setparams_pluginid = None def __del__(self): self.close() def __enter__(self): return self def __exit__(self, *args): self.close()
[docs] def open(self, filename, mode, *, pluginname=None, samprate=0, sampbit=0, nchannels=0, filetype=None, songinfo=None, params=None): """Opens the file associated with the filename by using a plugin. Args: filename (str): The name of the file to open. mode (str): The opening mode. ``'r'`` means read mode, ``'w'`` means write mode. pluginname (str, optional): The name of the plugin used when this function cannot find the suitable plugin. Otherwise, :class:`~spplugin.SuitableNotFoundError` exception will be raised. If you want to read a raw file, specify ``'input_raw'`` . samprate (double, optional): Sample rate. sampbit (int, optional): Bits/sample. nchannels (int, optional): The number of channels. filetype (str, optional): File type string. songinfo (dict, optional): Song information. params (dict, optional): All acceptable parameters in dict format. Raises: SuitableNotFoundError: If no suitable plugin is found. """ if pluginname is not None: self._pluginname = pluginname if params is not None: self.setparams(params) if samprate != 0: self.setsamprate(samprate) if sampbit != 0: self.setsampbit(sampbit) if nchannels != 0: self.setnchannels(nchannels) if filetype: self.setfiletype(filetype) if songinfo: self.setsonginfo(songinfo) self._setsonginfo_toc() cpluginname = _encodetocstr(self._pluginname) cfilename = _encodetocstr(filename) cmode = _encodetocstr(mode, 'utf-8') self._plugin, errorcode \ = _spplugin_c.spOpenFilePluginAuto_(cpluginname, cfilename, cmode, 0, # SP_PLUGIN_DEVICE_FILE self._waveinfo_c, self._songinfo_c) if self._setparams_pluginid and errorcode == -11: _spplugin_c.spFreePlugin(self._plugin) _spplugin_c.spSetWaveInfoFileType_(self._waveinfo_c, None) self._plugin, errorcode \ = _spplugin_c.spOpenFilePluginAuto_(cpluginname, cfilename, cmode, 0, # SP_PLUGIN_DEVICE_FILE self._waveinfo_c, self._songinfo_c) self._procopenerror(errorcode) self._open_mode = mode self._currentpos = 0
[docs] def close(self): """Closes the current audio file.""" if self._plugin is not None: _spplugin_c.spCloseFilePlugin(self._plugin) self._plugin = None self._open_mode = ' ' self._currentpos = 0 self._songinfo = {} self._setparams_pluginid = None
def _procopenerror(self, errorcode): if errorcode != 1: if errorcode == -1: raise WrongPluginError('wrong plugin') elif errorcode == -6: raise SuitableNotFoundError('no suitable plugin is found') elif errorcode == -10: raise BogusFileError('this audio file is bogus') elif errorcode == -11: raise FileTypeError('the file type is not accepted') elif errorcode == -12: raise SampleRateError('the sample rate is not accepted') elif errorcode == -13: raise SampleBitError('the bits/sample is not accepted') elif errorcode == -14: raise NChannelsError('the number of channels is not accepted') elif errorcode == -16: raise NFramesRequiredError('the total number of frames is required') else: raise FileError('unknown file error') _spplugin_c.spFreePlugin(self._plugin) self._plugin = None
[docs] def getpluginversion(self): """Gets the version of the plugin currently used.""" if self._plugin is None: raise RuntimeError('file must be opened') flag, version, revision = _spplugin_c.spGetPluginVersion(self._plugin) if not flag: raise RuntimeError('cannot get plugin version') return version, revision
[docs] def getpluginname(self): """Gets the name of the plugin currently used.""" if self._plugin is None: raise RuntimeError('file must be opened') name = _spplugin_c.spGetPluginName(self._plugin) if name is not None: name = _decodefromcstr(name) return name
[docs] def getpluginid(self): """Gets the ID of the plugin currently used.""" if self._plugin is None: raise RuntimeError('file must be opened') id = _spplugin_c.spGetPluginId(self._plugin) if id is not None: id = _decodefromcstr(id) return id
[docs] def getplugininfo(self): """Gets the detailed information of the plugin currently used.""" if self._plugin is None: raise RuntimeError('file must be opened') info = _spplugin_c.spGetPluginInformation(self._plugin) if info is not None: info = _decodefromcstr(info) return info
[docs] def getplugindesc(self): """Gets the short description of the plugin currently used.""" if self._plugin is None: raise RuntimeError('file must be opened') desc = _spplugin_c.spGetPluginDescription(self._plugin) if desc is not None: desc = _decodefromcstr(desc) return desc
def _setsonginfo_toc(self): if self._songinfo: for key, value in self._songinfo.items(): ckey = _encodetocstr(key, 'utf-8') if isinstance(value, str): cvalue = _encodetocstr(value) _spplugin_c.spUpdateSongInfoStringField_(self._songinfo_c, ckey, cvalue) else: _spplugin_c.spUpdateSongInfoNumberField_(self._songinfo_c, ckey, value) def _getsonginfo_fromc(self): for _, key in enumerate(SONGINFO_KEYS_IN_NUMBER): ckey = _encodetocstr(key, 'utf-8') number = _spplugin_c.spGetSongInfoNumberField_(self._songinfo_c, ckey) if number >= 0: self._songinfo[key] = number for _, key in enumerate(SONGINFO_KEYS_IN_STRING): ckey = _encodetocstr(key, 'utf-8') cstring = _spplugin_c.xspGetSongInfoStringField_(self._songinfo_c, ckey) if cstring: self._songinfo[key] = _decodefromcstr(cstring)
[docs] def setsonginfo(self, songinfo): """Sets song information to the file.""" if self._plugin is not None: raise RuntimeError('set before opening file') self._songinfo = songinfo self._setsonginfo_toc()
[docs] def appendsonginfo(self, songinfo): """Appends song information to the current internal information.""" if self._plugin is not None: raise RuntimeError('set before opening file') # append dict of 'songinfo' arg to '_songinfo' self._songinfo = dict(self._songinfo, **songinfo) self._setsonginfo_toc()
[docs] def getsonginfo(self): """Gets song information of the current file.""" self._getsonginfo_fromc() return self._songinfo
[docs] def setfiletype(self, filetype): """Sets file type to the file.""" if self._plugin is not None: raise RuntimeError('set before opening file') cfiletype = _encodetocstr(filetype) _spplugin_c.spSetWaveInfoFileType_(self._waveinfo_c, cfiletype)
[docs] def getfiletype(self): """Gets file type of the current file.""" cfiletype = _spplugin_c.xspGetWaveInfoStringField_(self._waveinfo_c, 0) if cfiletype: filetype = _decodefromcstr(cfiletype) else: filetype = None return filetype
[docs] def getfiledesc(self): """Gets file description of the current file. For example, ``'Microsoft PCM'`` for a WAV file in PCM format. """ cfiledesc = _spplugin_c.xspGetWaveInfoStringField_(self._waveinfo_c, 1) if cfiledesc: filedesc = _decodefromcstr(cfiledesc) else: filedesc = None return filedesc
[docs] def getfilefilter(self): """Gets file filter (e.g. ``'*.wav'``) of the current file.""" cfilefilter = _spplugin_c.xspGetWaveInfoStringField_(self._waveinfo_c, 2) if cfilefilter: filefilter = _decodefromcstr(cfilefilter) else: filefilter = None return filefilter
[docs] def setsamprate(self, samprate): """Sets sample rate to the file.""" if self._plugin is not None: raise RuntimeError('set before opening file') self._waveinfo_c.samp_rate = samprate
[docs] def setframerate(self, samprate): """Sets sample rate to the file.""" self.setsamprate(samprate)
[docs] def getsamprate(self): """Gets sample rate of the current file.""" return self._waveinfo_c.samp_rate
[docs] def getframerate(self): """Gets sample rate of the current file.""" return self.getsamprate()
[docs] def setsampbit(self, sampbit): """Sets bits/sample to the file. sampbit = 33 means 32bit float.""" if self._plugin is not None: raise RuntimeError('set before opening file') if sampbit < 8: raise ValueError('sampbit >= 8 is required') self._waveinfo_c.samp_bit = sampbit
[docs] def setsampwidth(self, sampwidth, floatflag=False): """Sets bytes/sample of the current file.""" sampbit = sampwidth * 8 if floatflag: sampbit += 1 return self.setsampbit(sampbit)
[docs] def getsampbit(self): """Gets bits/sample of the current file. sampbit = 33 means 32bit float.""" return self._waveinfo_c.samp_bit
[docs] def getsampwidth(self): """Gets bytes/sample of the current file.""" return self.getsampbit() // 8
[docs] def getrawsampbit(self): """Gets the bits/sample for a raw array.""" if self._waveinfo_c.samp_bit < 16: return 16 elif 16 < self._waveinfo_c.samp_bit <= 32: return 32 else: return self.getsampwidth() * 8
[docs] def getrawsampwidth(self): """Gets the bytes/sample for a raw array.""" return self.getrawsampbit() // 8
[docs] def setnchannels(self, nchannels): """Sets the number of channels to the file.""" if self._plugin is not None: raise RuntimeError('set before opening file') if nchannels <= 0: raise ValueError('nchannels >= 1 is required') self._waveinfo_c.num_channel = nchannels
[docs] def getnchannels(self): """Gets the number of channels of the current file.""" return self._waveinfo_c.num_channel
[docs] def setnframes(self, nframes): """Sets the total number of frames of the current file.""" if self._plugin is not None: raise RuntimeError('set before opening file') if nframes <= 0: raise ValueError('nframes >= 1 is required') self._waveinfo_c.length = nframes
[docs] def getnframes(self): """Gets the total number of frames of the current file.""" return self._waveinfo_c.length
[docs] def getcomptype(self, decodebytes=False): """Returns compression type. Currently, ``'NONE'`` (decodebytes = ``True``) or ``b'NONE'`` (decodebytes = ``False``) will be returned.""" if decodebytes: return 'NONE' else: return b'NONE'
[docs] def getcompname(self, decodebytes=False): """Returns human-readable name of compression type. Currently, ``'not compressed'`` (decodebytes = ``True``) or ``b'not compressed'`` (decodebytes = ``False``) will be returned. """ if decodebytes: return 'not compressed' else: return b'not compressed'
[docs] def setcomptype(self, encodestr=True): """Sets compression type. Currently, this parameter is ignored.""" pass
[docs] def setparams(self, params): """Sets supported all parameters described in dict or namedtuple object to the file. Args: params (dict): a dict object of parameters whose keys are ``'nchannels'``, ``'sampbit'``, ``'samprate'``, ``'nframes'``, ``'filetype'``, or ``'songinfo'``. The namedtuple generated by standard libraries such as aifc, wave, or sunau is also acceptable. """ if self._plugin is not None: raise RuntimeError('set before opening file') # if isinstance(params, namedtuple): if _isnamedtupleinstance(params): # some versions of Python include _asdist() bug. params = dict(zip(params._fields, params)) elif isinstance(params, tuple): params = dict(zip(_STANDARD_LIB_KEYS, params)) sampbit = 0 pluginid = None self._setparams_pluginid = None for key, value in params.items(): if key == 'sampwidth': if sampbit == 0: sampbit = value * 8 elif key == 'sampbit': sampbit = value elif key == 'nchannels': self.setnchannels(value) elif key in ('samprate', 'framerate'): self.setsamprate(value) elif key in ('nframes', 'length'): self.setnframes(value) elif key == 'pluginid': pluginid = value elif key == 'filetype': self.setfiletype(value) elif key == 'songinfo': self.setsonginfo(value) if sampbit > 0: self.setsampbit(sampbit) if pluginid: self._setparams_pluginid = pluginid
[docs] def getparams(self): """Gets supported all parameters of the current file in dict object. Returns: dict: A dict object including all parameters of the current file whose keys are ``'nchannels'``, ``'sampbit'``, ``'samprate'``, ``'nframes'``, ``'filetype'``, and ``'songinfo'``. """ return dict(nchannels=self.getnchannels(), sampbit=self.getsampbit(), samprate=self.getsamprate(), nframes=self.getnframes(), pluginid=self.getpluginid(), filetype=self.getfiletype(), filedesc=self.getfiledesc(), filefilter=self.getfilefilter(), songinfo=self.getsonginfo(), sampwidth=self.getrawsampwidth(), framerate=self.getframerate())
[docs] def getparamstuple(self, decodebytes=False): """Gets supported all parameters of the current file in namedtuple object. Args: decodebytes (bool, optional): ``True`` decodes bytes objects into string objects obtained by :func:`~spplugin.SpFilePlugin.getcomptype` and :func:`~spplugin.SpFilePlugin.getcompname` . The standard libraries of wave and sunau expect a decoded string object while the standard aifc library expects a bytes object. Returns: namedtuple: A namedtuple object including all parameters of the current file whose entries are ``(nchannels, sampwidth, framerate, nframes, comptype, compname)`` . This object is compatible with the argument of ``setparams()`` of standard libraries such as aifc, wave, or sunau. """ return _StandardLibParams(self.getnchannels(), self.getsampwidth(), self.getframerate(), self.getnframes(), self.getcomptype(decodebytes), self.getcompname(decodebytes))
[docs] def getrawarraytypecode(self): """Gets the type code for python array to store raw data. Returns: char: A type code for the current settings. """ sampbit = self.getrawsampbit() if sampbit >= 64: typecode = 'd' elif sampbit >= 33: typecode = 'f' elif sampbit > 16: # sampbit >= 32 typecode = 'l' else: typecode = 'h' return typecode
[docs] def createrawarray(self, length, nframesflag=False): """Creates a raw array for the current file settings. Args: length: A length of the array. Note that this length is not identical to the number of frames (length = nframes * nchannels). If you want to specify the number of frames, the second argument must be ``True``. nframesflag (bool, optional): ``True`` makes the first argument be treated as the number of frames. Returns: array.array: An array class object for the current file settings. """ if nframesflag: length = int(length) * self.getnchannels() size = int(length) * self.getrawsampwidth() buffer = bytearray(size) return array.array(self.getrawarraytypecode(), buffer)
[docs] def getarraytypecode(self): """Gets the type code for python array to store double-precision data. Returns: char: A type code of a double-precision array for the current settings. """ return 'd'
[docs] def createarray(self, length, nframesflag=False): """Creates a double-precision array for the current file settings. Args: length: A length of the array. Note that this length is not identical to the number of frames (length = nframes * nchannels). If you want to specify the number of frames, the second argument must be ``True``. nframesflag (bool, optional): ``True`` makes the first argument be treated as the number of frames. Returns: array.array: An array class object for the current file settings. """ if nframesflag: length = int(length) * self.getnchannels() size = int(length) * 8 buffer = bytearray(size) return array.array(self.getarraytypecode(), buffer)
[docs] def getrawndarraydtype(self): """Gets the dtype string for numpy ndarray to store raw data. Returns: string: A dtype string for the current settings. """ sampbit = self.getrawsampbit() if sampbit >= 64: dtypestr = 'f8' elif sampbit >= 33: dtypestr = 'f4' elif sampbit > 16: # sampbit >= 32 dtypestr = 'i4' else: dtypestr = 'i2' return dtypestr
[docs] def createrawndarray(self, length, nframesflag=False, channelwise=False): """Creates a raw numpy ndarray for the current file settings. Args: length: A length of the array. Note that this length is not identical to the number of frames (length = nframes * nchannels). If you want to specify the number of frames, the second argument must be ``True``. nframesflag (bool, optional): ``True`` makes the first argument be treated as the number of frames. channelwise (bool, optional): ``True`` resizes the returned array into (nframes, nchannels) matrix. This argument is introduced in Version 0.7.16. Returns: numpy.ndarray: An ndarray class object for the current file settings. """ import numpy as np nchannels = self.getnchannels() if nframesflag: nframes = int(length) length = nframes * nchannels else: length = int(length) nframes = length // nchannels size = length * self.getrawsampwidth() buffer = bytearray(size) oarray = np.frombuffer(buffer, dtype=self.getrawndarraydtype()) if channelwise: oarray.resize((nframes, nchannels)) return oarray
[docs] def getndarraydtype(self): """Gets the dtype string for numpy ndarray to store double-precision data. Returns: string: A dtype string for the current settings. """ return 'f8'
[docs] def createndarray(self, length, nframesflag=False, channelwise=False): """Creates a numpy double-precision array for the current file settings. Args: length: A length of the array. Note that this length is not identical to the number of frames (length = nframes * nchannels). If you want to specify the number of frames, the second argument must be ``True``. nframesflag (bool, optional): ``True`` makes the first argument be treated as the number of frames. channelwise (bool, optional): ``True`` resizes the returned array into (nframes, nchannels) matrix. This argument is introduced in Version 0.7.16. Returns: numpy.ndarray: An ndarray class object for the current file settings. """ import numpy as np nchannels = self.getnchannels() if nframesflag: nframes = int(length) length = nframes * nchannels else: length = int(length) nframes = length // nchannels size = length * 8 buffer = bytearray(size) oarray = np.frombuffer(buffer, dtype=self.getndarraydtype()) if channelwise: oarray.resize((nframes, nchannels)) return oarray
[docs] def setpos(self, pos): """Seeks to the specified position. Args: pos: a position to seek. """ if pos < 0 or pos > self.getnframes(): raise ValueError('position not in range') if self._plugin is None: raise RuntimeError('file must be opened') if self._open_mode[0] != 'r': raise RuntimeError('file must be opened with read mode') if not _spplugin_c.spSeekPlugin(self._plugin, pos): raise FileError('seek error') else: self._currentpos = pos
[docs] def rewind(self): """Rewinds to the beginning of the file.""" self.setpos(0)
[docs] def tell(self): """Gets the current position in the file.""" return self._currentpos
[docs] def setmark(self, id, pos, name): """Does nothing (for compatibility with standard libraries such as aifc, wave, and sunau).""" raise Error('setmark() not supported')
[docs] def getmark(self, id): """Does nothing (for compatibility with standard libraries such as aifc, wave, and sunau).""" raise Error('no marks')
[docs] def getmarkers(self): """Does nothing (for compatibility with standard libraries such as aifc, wave, and sunau).""" return None
[docs] def copyraw2array(self, rawdata, sampwidth, bigendian_or_signed8bit=False): """Copies raw bytes (bytearray) data contents to a new raw array (array.array). Args: rawdata (bytes or bytearray): Input bytes or bytearray object. sampwidth (int): bytes/sample of rawdata bigendian_or_signed8bit (bool, optional): Specify ``True`` if endianness of rawdata is big endian (16- or 32-bit case) or data type of rawdata (8-bit case) is signed 8-bit. Returns: array.array: An array class object which contains converted data. """ dest_bigendian = sys.byteorder == 'big' dest_sampwidth = self.getrawsampwidth() length = len(rawdata) // sampwidth outarray = self.createrawarray(length) _spplugin_c.spCopyBuffer_(memoryview(outarray), dest_sampwidth, dest_bigendian, rawdata, sampwidth, bigendian_or_signed8bit, 0) if sampwidth == 3: for n in range(length): outarray[n] //= 256 return outarray
[docs] def copyarray2raw(self, inarray, sampwidth, bigendian_or_signed8bit=False): """Copies raw array (array.array) contents to a new raw bytes (bytearray) data. Args: inarray (array.array): Input array object. sampwidth (int): bytes/sample of output data. bigendian_or_signed8bit (bool, optional): Specify ``True`` if endianness of output data is big endian (16- or 32-bit case) or data type of output data is signed 8-bit (8-bit case). Returns: bytearray: A bytearray class object which contains converted data. """ if not isinstance(inarray, array.array): raise RuntimeError('input type must be array.array') src_bigendian = sys.byteorder == 'big' src_sampwidth = self.getrawsampwidth() length = len(inarray) rawdata = bytearray(length * sampwidth) _spplugin_c.spCopyBuffer_(rawdata, sampwidth, bigendian_or_signed8bit, memoryview(inarray), src_sampwidth, src_bigendian, 1 if self.getsampbit() == 24 else 0) return rawdata
[docs] def readraw(self, data, offset=0, length=0): """Reads raw data from the audio file. Args: data (bytearray, array.array or numpy.ndarray): A raw array to receive raw data from the audio file. offset (int, optional): Optional offset location for the array. length (int, optional): Optional read length for the array. Returns: int: The read size if successful, -1 otherwise. Note: The keyword arguments of `offset` and `length` were introduced in Version 0.7.15. """ if self._plugin is None: raise RuntimeError('file must be opened') if data is None or len(data) <= 0: raise ValueError('a valid buffer must be specified') if self._open_mode[0] != 'r': raise RuntimeError('file must be opened with read mode') if isinstance(data, bytearray): buffer = data elif isinstance(data, array.array): buffer = memoryview(data) elif type(data).__name__ == 'ndarray': import numpy as np buffer = data.data else: raise RuntimeError('unsupported data type') offsetbyte = int(offset) * self.getrawsampwidth() if offset > 0 else 0 lengthbyte = int(length) * self.getrawsampwidth() if length > 0 else 0 nread = _spplugin_c.spReadPluginInByte_(self._plugin, buffer, offsetbyte, lengthbyte) nread2 = nread // self.getrawsampwidth() if nread > 0 else nread if nread > 0: self._currentpos += nread2 // self._waveinfo_c.num_channel return nread2
[docs] def read(self, data, weight=1.0, offset=0, length=0): """Reads data to a double-precision array from the audio file. Args: data (bytearray, array.array or numpy.ndarray): A double-precision array to receive data from the audio file. weight (double, optional): A weighting factor multiplied to data after reading. offset (int, optional): Optional offset location for the array. length (int, optional): Optional read length for the array. Returns: int: The read size if successful, -1 otherwise. Note: The keyword arguments of `offset` and `length` were introduced in Version 0.7.15. """ if self._plugin is None: raise RuntimeError('file must be opened') if data is None or len(data) <= 0: raise ValueError('a valid buffer must be specified') if self._open_mode[0] != 'r': raise RuntimeError('file must be opened with read mode') if isinstance(data, bytearray): buffer = data elif isinstance(data, array.array): if data.typecode != 'd': raise RuntimeError('the typecode must be \'d\'') buffer = memoryview(data) elif type(data).__name__ == 'ndarray': import numpy as np if not np.issubdtype('f8', data.dtype): raise RuntimeError('the dtype must be \'f8\' (\'float64\')') buffer = data.data else: raise RuntimeError('unsupported data type') nread = _spplugin_c.spReadPluginDoubleWeighted_(self._plugin, buffer, weight, offset, length) if nread > 0: self._currentpos += nread // self._waveinfo_c.num_channel return nread
[docs] def writeraw(self, data, offset=0, length=0): """Writes data of a raw array to the audio file. Args: data (bytearray, array.array or numpy.ndarray): A raw array to send data to the audio file. offset (int, optional): Optional offset location for the array. length (int, optional): Optional write length for the array. Returns: int: The written size if successful, -1 otherwise. Note: The keyword arguments of `offset` and `length` were introduced in Version 0.7.15. """ if self._plugin is None: raise RuntimeError('file must be opened') if data is None or len(data) <= 0: raise ValueError('a valid buffer must be specified') if self._open_mode[0] != 'w': raise RuntimeError('file must be opened with write mode') if isinstance(data, (bytearray, bytes)): buffer = data elif isinstance(data, array.array): buffer = memoryview(data) elif type(data).__name__ == 'ndarray': import numpy as np buffer = data.data else: raise RuntimeError('unsupported data type') offsetbyte = int(offset) * self.getrawsampwidth() if offset > 0 else 0 lengthbyte = int(length) * self.getrawsampwidth() if length > 0 else 0 nwrite = _spplugin_c.spWritePluginInByte_(self._plugin, buffer, offsetbyte, lengthbyte) nwrite2 = nwrite // self.getrawsampwidth() if nwrite > 0 else nwrite if nwrite > 0: self._currentpos += nwrite2 // self._waveinfo_c.num_channel return nwrite2
[docs] def write(self, data, weight=1.0, offset=0, length=0): """Writes data of a double-precision array to the audio file. Args: data (bytearray, array.array or numpy.ndarray): A double-precision array to send data to the audio file. weight (double, optional): A weighting factor multiplied to data before writing. offset (int, optional): Optional offset location for the array. length (int, optional): Optional write length for the array. Returns: int: The written size if successful, -1 otherwise. Note: The keyword arguments of `offset` and `length` were introduced in Version 0.7.15. """ if self._plugin is None: raise RuntimeError('file must be opened') if data is None or len(data) <= 0: raise ValueError('a valid buffer must be specified') if self._open_mode[0] != 'w': raise RuntimeError('file must be opened with write mode') if isinstance(data, (bytearray, bytes)): buffer = data elif isinstance(data, array.array): if data.typecode != 'd': raise RuntimeError('the typecode must be \'d\'') buffer = memoryview(data) elif type(data).__name__ == 'ndarray': import numpy as np if not np.issubdtype('f8', data.dtype): raise RuntimeError('the dtype must be \'f8\' (\'float64\')') buffer = data.data else: raise RuntimeError('unsupported data type') nwrite = _spplugin_c.spWritePluginDoubleWeighted_(self._plugin, buffer, weight, offset, length) if nwrite > 0: self._currentpos += nwrite // self._waveinfo_c.num_channel return nwrite
[docs] def readframes(self, nframes, weight=1.0, arraytype='ndarray', channelwise=False): """Reads and returns the next `nframes` data of a double-precision array. Args: nframes (int): The number of frames to read. A negative value means the total number of frames. weight (double, optional): A weighting factor multiplied to data after reading. arraytype (str, optional): The type of output array. The value must be ``'ndarray'`` (default), ``'array'``, or ``'bytearray'``. channelwise (bool, optional): ``True`` resizes the returned ndarray into (nframes, nchannels) matrix. This argument is valid only in ``arraytype='ndarray'`` case. Returns: numpy.ndarray, array.array or bytearray: The output array object containing read data. Note: This function was introduced in Version 0.7.16. """ remainlen = self.getnframes() - self.tell() if nframes <= 0 or remainlen < nframes: nframes = remainlen if nframes <= 0: raise RuntimeError('invalid nframes value') length = int(nframes) * self.getnchannels() if arraytype in ('ndarray', 'numpy.ndarray'): data = self.createndarray(length, channelwise=channelwise) elif arraytype in ('array', 'array.array'): data = self.createarray(length) elif arraytype == 'bytearray': data = bytearray(length * 8) else: raise RuntimeError('unknown arraytype: %s' % arraytype) nread = self.read(data, weight=weight) if nread != length: warnings.warn('The read length (%d) is different from the buffer length (%d)' % (nread, length)) return data
[docs] def readrawframes(self, nframes, arraytype='ndarray', channelwise=False): """Reads and returns the next `nframes` data of a raw array. Args: nframes (int): The number of frames to read. A negative value means the total number of frames. arraytype (str, optional): The type of output array. The value must be ``'ndarray'`` (default), ``'array'``, or ``'bytearray'``. channelwise (bool, optional): ``True`` resizes the returned ndarray into (nframes, nchannels) matrix. This argument is valid only in ``arraytype='ndarray'`` case. Returns: numpy.ndarray, array.array or bytearray: The output array object containing read data. Note: This function was introduced in Version 0.7.16. """ remainlen = self.getnframes() - self.tell() if nframes <= 0 or remainlen < nframes: nframes = remainlen if nframes <= 0: raise RuntimeError('invalid nframes value') length = int(nframes) * self.getnchannels() if arraytype in ('ndarray', 'numpy.ndarray'): data = self.createrawndarray(length, channelwise=channelwise) elif arraytype in ('array', 'array.array'): data = self.createrawarray(length) elif arraytype == 'bytearray': data = bytearray(length * self.getrawsampwidth()) else: raise RuntimeError('unknown arraytype: %s' % arraytype) nread = self.readraw(data) if nread != length: warnings.warn('The read length (%d) is different from the buffer length (%d)' % (nread, length)) return data
[docs] def writeframes(self, data, weight=1.0): """Writes data of a double-precision array to the audio file. Args: data (bytearray, array.array or numpy.ndarray): A double-precision array to send data to the audio file. weight (double, optional): A weighting factor multiplied to data before writing. Returns: int: The written number of frames if successful, -1 otherwise. Note: This function was introduced in Version 0.7.16. """ nwrite = self.write(data, weight=weight) if nwrite > 0: nwrite = nwrite // self._waveinfo_c.num_channel return nwrite
[docs] def writerawframes(self, data): """Writes data of a raw array to the audio file. Args: data (bytearray, array.array or numpy.ndarray): A raw array to send data to the audio file. Returns: int: The written number of frames if successful, -1 otherwise. Note: This function was introduced in Version 0.7.16. """ nwrite = self.writeraw(data) if nwrite > 0: nwrite = nwrite // self._waveinfo_c.num_channel return nwrite
[docs]def open(filename, mode, *, pluginname=None, samprate=0, sampbit=0, nchannels=0, filetype=None, songinfo=None, params=None): """Opens the file associated with the filename by using a plugin. This function may be used in a ``with`` statement. Args: filename (str): The name of the file to open. mode (str): The opening mode. ``'r'`` means read mode, ``'w'`` means write mode. pluginname (str, optional): The name of the plugin used when this function cannot find the suitable plugin. Otherwise, :class:`~spplugin.SuitableNotFoundError` exception will be raised. If you want to read a raw file, specify ``'input_raw'`` . samprate (double, optional): Sample rate. sampbit (int, optional): Bits/sample. nchannels (int, optional): The number of channels. filetype (str, optional): File type string. songinfo (dict, optional): Song information. params (dict, optional): All acceptable parameters in dict format. Returns: SpFilePlugin: A new instance of :class:`~spplugin.SpFilePlugin` class. Raises: SuitableNotFoundError: If no suitable plugin is found. """ pf = SpFilePlugin() try: pf.open(filename, mode, pluginname=pluginname, samprate=samprate, sampbit=sampbit, nchannels=nchannels, filetype=filetype, songinfo=songinfo, params=params) except Exception: pf = None raise return pf
[docs]def audioread(filename, samples=(0, -1), datatype='double', *, weight=1.0, arraytype='ndarray', channelwise=True, getparamstuple=False, decodebytes=False, pluginname=None, samprate=0, sampbit=0, nchannels=0, filetype=None, params=None): """Reads contents of an audio file by using a plugin. Args: filename (str): The name of the file to open. samples (tuple, optional): The audio sample (frame) range which has the form ``(start, finish)`` . If ``finish`` is negative, it means the end of the file. datatype (str, optional): The format of the output data. ``'double'`` case makes :func:`~spplugin.SpFilePlugin.readframes` method used and ``'raw'`` case makes :func:`~spplugin.SpFilePlugin.readrawframes` method used internally. Note that ``'raw'`` case ignores `weight` argument. weight (double, optional): A weighting factor multiplied to data after reading. arraytype (str, optional): The type of output array. The value must be ``'ndarray'`` (default), ``'array'``, or ``'bytearray'``. channelwise (bool, optional): ``True`` resizes the returned ndarray into (nframes, nchannels) matrix. This argument is valid only in ``arraytype='ndarray'`` case. getparamstuple (bool, optional): ``True`` makes :func:`~spplugin.SpFilePlugin.getparamstuple` used internally. decodebytes (bool, optional): ``True`` decodes bytes objects into string objects in calling :func:`~spplugin.SpFilePlugin.getparamstuple` method. This argument is valid only in ``getparamstuple=True`` case. pluginname (str, optional): The name of the plugin used when this function cannot find the suitable plugin. Otherwise, :class:`~spplugin.SuitableNotFoundError` exception will be raised. If you want to read a raw file, specify ``'input_raw'`` . samprate (double, optional): Sample rate. sampbit (int, optional): Bits/sample. nchannels (int, optional): The number of channels. filetype (str, optional): File type string. songinfo (dict, optional): Song information. params (dict, optional): All acceptable parameters in dict format. Returns: tuple: The output tuple whose elements are the following. * `numpy.ndarray, array.array or bytearray` -- The output array object containing read data. * `double` -- Sample rate of the audio file. * `dict or namedtuple` -- If ``getparamstuple=False`` , A dict object same as that obtained by :func:`~spplugin.SpFilePlugin.getparams` method will be returned. If ``getparamstuple=True`` , A namedtuple object same as that obtained by :func:`~spplugin.SpFilePlugin.getparamstuple` method will be returned. Raises: SuitableNotFoundError: If no suitable plugin is found. Note: This function was introduced in Version 0.7.16. """ rettup = (None, 0.0, None) try: with open(filename, 'r', pluginname=pluginname, samprate=samprate, sampbit=sampbit, nchannels=nchannels, filetype=filetype, params=params) as pf: nframes = pf.getnframes() if isinstance(samples, str): datatype = samples if isinstance(datatype, tuple): samples = datatype start = 0 finish = -1 if samples: if samples[0] > 0: start = int(samples[0]) if len(samples) >= 2 and samples[1] > 0: finish = max(int(samples[1]), start) if start > 0: pf.setpos(start) nframes -= start remain = finish - start if remain >= 0: nframes = min(nframes, remain) if nframes <= 0: data = None else: if datatype == 'double': data = pf.readframes(nframes, weight=weight, arraytype=arraytype, channelwise=channelwise) elif datatype in ('raw', 'native'): data = pf.readrawframes(nframes, weight=weight, arraytype=arraytype, channelwise=channelwise) else: raise RuntimeError('unknown datatype: %s' % datatype) samprate = pf.getsamprate() if getparamstuple: oparams = pf.getparamstuple(decodebytes) else: oparams = pf.getparams() rettup = (data, samprate, oparams) except Exception: raise return rettup
[docs]def audiowrite(filename, data, samprate=0, nchannels=0, sampbit=0, *, datatype=None, weight=1.0, offset=0, length=0, pluginname=None, filetype=None, songinfo=None, params=None): """Writes data to an audio file by using a plugin. Args: filename (str): The name of the file to open. data (bytearray, array.array or numpy.ndarray): A array to send data to the audio file. samprate (double, optional): Sample rate. nchannels (int, optional): The number of channels. sampbit (int, optional): Bits/sample. datatype (str, optional): The format of the input data. ``'double'`` case makes :func:`~spplugin.SpFilePlugin.write` method used and ``'raw'`` case makes :func:`~spplugin.SpFilePlugin.writeraw` method used internally. Note that ``'raw'`` case ignores `weight` argument. In some array types, this parameter can be detected automatically. weight (double, optional): A weighting factor multiplied to data before writing. offset (int, optional): Optional offset location for the array. length (int, optional): Optional write length for the array. pluginname (str, optional): The name of the plugin used when this function cannot find the suitable plugin. Otherwise, :class:`~spplugin.SuitableNotFoundError` exception will be raised. If you want to write data to a raw file, specify ``'output_raw'`` . filetype (str, optional): File type string. songinfo (dict, optional): Song information. params (dict, optional): All acceptable parameters in dict format. Returns: int: The written number of frames if successful, -1 otherwise. Raises: SuitableNotFoundError: If no suitable plugin is found. Note: This function was introduced in Version 0.7.16. """ if type(data).__name__ == 'ndarray': import numpy as np if nchannels <= 0 and data.ndim >= 2: nchannels = data.shape[1] if np.issubdtype('f8', data.dtype): datatype = 'double' else: datatype = 'raw' elif isinstance(data, array.array): if data.typecode == 'd': datatype = 'double' else: datatype = 'raw' if not datatype: raise RuntimeError('datatype must be specified') nwframes = 0 try: with open(filename, 'w', pluginname=pluginname, samprate=samprate, sampbit=sampbit, nchannels=nchannels, filetype=filetype, params=params) as pf: if datatype == 'double': nwframes = pf.write(data, weight=weight, offset=offset, length=length) elif datatype in ('raw', 'native'): nwframes = pf.writeraw(data, offset=offset, length=length) else: raise RuntimeError('unknown datatype: %s' % datatype) if nwframes > 0: nwframes = nwframes // pf.getnchannels() except Exception: raise return nwframes