"""
sndio is a simple interface for reading and writing arbitrary sound files.
The module contains 3 functions.
| **functions:**
| :meth:`pysndfile.sndio.get_info`:: retrieve information from a sound file.
| :meth:`pysndfile.sndio.get_markers`:: retrieve markers from aiff/ or wav files.
| :meth:`pysndfile.sndio.read`:: read data and meta data from sound file.
| :meth:`pysndfile.sndio.write`:: create a sound file from a given numpy array.
"""
#
# Copyright (C) 2014 IRCAM
#
# All rights reserved.
#
# This file is part of pysndfile.
#
# pysndfile is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pysndfile is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pysndfile. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import absolute_import
from ._pysndfile import PySndfile, construct_format, stringtype_name_to_id, max_supported_string_length, fileformat_name_to_id, fileformat_id_to_name
import numpy as np
[docs]def get_info(name, extended_info=False) :
"""
retrieve information from a sound file
:param name: sndfile name
:type name: str
:param extended_info:
:type extended_info: bool
:return: 3 or 5 tuple with meta information read from the soundfile
in case extended_info is False a 3-tuple comprising samplerate, encoding (str), major format is returned
in case extended_info is True a 5-tuple comprising additionally the number of frames and the number of channels
is returned.
"""
with PySndfile(name) as sf:
if extended_info:
return sf.samplerate(), sf.encoding_str(), sf.major_format_str(), sf.frames(), sf.channels()
return sf.samplerate(), sf.encoding_str(), sf.major_format_str()
[docs]def get_markers(name) :
"""
retrieve markers from sound file
:param name: sound file name
:type name: str
:return: list of marker tuples containing the marker time
and marker label.
:rtype: List
Note: following the implementation of libsndfile marker labels will be empty strings for all but aiff files.
"""
with PySndfile(name) as sf:
return sf.get_cue_mrks()
[docs]def write(name, data, rate=44100, format="aiff", enc='pcm16', sf_strings=None) :
"""
Write datavector to sndfile using samplerate, format and encoding as specified
valid format strings are all the keys in the dict pysndfile.fileformat_name_to_id
valid encodings are those that are supported by the selected format
from the list of keys in pysndfile.encoding_name_to_id.
:param name: sndfile name
:type name: str
:param data: array containing sound data. For mono files an 1d array can be given, for multi channel sound files
sound frames are in the rows and data channels in the columns.
:type data: numpy.array
:param rate: sample rate default s to 44100
:type rate: int
:param format: sndfile major format default=aiff
:type format: str
:param enc: sndfile encoding default=pcm16
:type enc: str
:param sf_strings: dictionary containing bytes in ascii encoding to be written as meta data into the sound file.
dictionary keys are limited to the keys available in `stringtype_name_to_id``
sf_strings arguments are only supported when the file format supports it.
This are currently only the [aiff, wav, wavex, caf] formats.
Note that each format imposes a particular limit to the length of
individual strings. These lengths are stored in the dict `max_supported_string_length`. If any of your strings exceeds the limit given in that dict a RuntimeError will be produced
:type sf_strings: Union[dict, None]
:return: number of sample frames written.
:rtype: int
"""
nchans = len(data.shape)
if nchans == 2 :
nchans = data.shape[1]
elif nchans != 1:
raise RuntimeError("error:sndio.write:can only be called with vectors or matrices ")
with PySndfile(name, "w",
format=construct_format(format, enc),
channels = nchans, samplerate = rate) as sf:
if sf_strings is not None:
sf.set_strings(sf_strings)
nf = sf.write_frames(data)
if nf != data.shape[0]:
raise IOError("sndio.write::error::writing of samples failed")
return nf
enc_norm_map = {
"pcm8" : np.float64(2**7),
"pcm16": np.float64(2**15),
"pcm24": np.float64(2**23),
"pcm32": np.float64(2**31),
}
[docs]def read(name, end=None, start=0, dtype=np.float64, return_format=False,
sf_strings=None, force_2d=False):
"""
read samples from arbitrary sound files into a numpy array.
May return subsets of samples as specified by start and end arguments (Def all samples)
normalizes samples to [-1,1] if the datatype is a floating point type
The returned array is 1D for mono sound files and 2D with the channels in the columns
for higher number of channels. If force_2d is given mono sound files will be returned
in an array with shape (num_frames, 1)
*Parameters*
:param name: sound file name
:type name: str
:param end: end sample frame position (not included into the segment to be returned) default=None -> read all samples
:type end: Union[int, None]
:param start: first sample frame to read default=0
:type start: int
:param dtype: data type of the numpy array that will be returned.
:type dtype: numpy.dtype
:param return_format: if set then the return tuple will contain an additional element containing the sound file major format
:type return_format: bool
:param sf_strings: if a dict is given the dict elements will be set to the strings that are available in the
sound file.
:type sf_strings: Union[None,dict]
:param force_2d: forces the returned array to have 2 dimensions with
:type force_2d: bool
:return: 3 or 4 -tuple containing
data (1d for mon sounds 2d for multi channel sounds, where channels are in the columns),
samplerate (int) and encoding (str),
in case return_format is True then the next element contains the major
format of the sound file (can be used to recreate a sound file with an identical format).
:rtype: Union[Tuple(numpy.array, int, str),Tuple(numpy.array, int, str, str)]
"""
with PySndfile(name) as sf:
enc = sf.encoding_str()
nf = sf.seek(start, 0)
if not nf == start:
raise IOError("sndio.read::error:: while seeking at starting position")
if end == None:
ff = sf.read_frames(sf.frames() - start, dtype=dtype, force_2d=force_2d)
else:
ff = sf.read_frames(end-start, dtype=dtype, force_2d=force_2d)
if isinstance(sf_strings, dict):
sf_strings.clear()
sf_strings.update(sf.get_strings())
if return_format:
return ff, sf.samplerate(), enc, sf.major_format_str()
return ff, sf.samplerate(), enc