Initial
This commit is contained in:
parent
1b7b8b6c6f
commit
3393ba4827
0
MANIFEST.in
Normal file
0
MANIFEST.in
Normal file
14
README.md
14
README.md
@ -1,3 +1,15 @@
|
||||
# scaled_soundfile
|
||||
|
||||
A thin wrapper around the `soundfile` module to allow saving files of any scales.
|
||||
A thin wrapper around the `soundfile` module to allow saving files of any scales.
|
||||
|
||||
The `write` function rescales the input to normalize it and maximize the number of bits it is saved on. The RMS of the original sound is saved in the 'comment' metadata field as part of a JSON object. The files are saved as 'PCM_24'.
|
||||
|
||||
The `read` function loads the normalized data, and rescales it so that the RMS matches the original by reading the 'comment' metadata field if it exists.
|
||||
|
||||
In the current version of `soundfile` (and `libsndfile`) this only works with WAV files.
|
||||
|
||||
# Installation
|
||||
|
||||
```
|
||||
pip install https://git.web.rug.nl/dBSPL/scaled_soundfile
|
||||
```
|
||||
|
38
pyproject.toml
Normal file
38
pyproject.toml
Normal file
@ -0,0 +1,38 @@
|
||||
[project]
|
||||
name = "scaled_soundfile"
|
||||
version = "1.0.0"
|
||||
authors = [
|
||||
{ name="Etienne Gaudrain", email="etienne.gaudrain@cnrs.fr" },
|
||||
]
|
||||
description = "A thin wrapper around the `soundfile` module to save sound files with arbitrary scale."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.0"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
]
|
||||
dependencies = [
|
||||
"numpy",
|
||||
"soundfile"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://git.web.rug.nl/dBSPL/scaled_soundfile.git"
|
||||
Issues = "https://git.web.rug.nl/dBSPL/scaled_soundfile/issues"
|
||||
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
# [tool.setuptools]
|
||||
# include-package-data = true
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
# All the following settings are optional:
|
||||
where = ["."] # ["."] by default
|
||||
include = ["scaled_soundfile*"] # ["*"] by default
|
||||
exclude = [] # empty by default
|
||||
namespaces = false # true by default
|
90
scaled_soundfile/__init__.py
Normal file
90
scaled_soundfile/__init__.py
Normal file
@ -0,0 +1,90 @@
|
||||
import soundfile as sf
|
||||
import numpy as np
|
||||
import json
|
||||
|
||||
def read(fname, v=False, **kwargs):
|
||||
"""
|
||||
Read a sound file from its filename. If there is no comment field in the file's
|
||||
metadata, it will silently not scale the file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fname : str
|
||||
The filename.
|
||||
v : bool, default=False
|
||||
Whether to return a boolean indicating whether the file was scaled.
|
||||
**kwargs :
|
||||
Keyword arguements as passed to :fun:`sounfile.read`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
x : ndarray
|
||||
fs : float, int
|
||||
scaled : bool
|
||||
If `v` is True, whether the file was scaled or not.
|
||||
"""
|
||||
|
||||
kwargs_read = {k:v for k,v in kwargs.items() if k in ['frames', 'fill_value', 'atleast_2d', 'dtype']}
|
||||
kwargs_sf = {k:v for k,v in kwargs.items() if k in ['samplerate', 'channels', 'subtype', 'endian', 'format']}
|
||||
|
||||
if 'out' in kwargs:
|
||||
raise ValueError("The `out` arguement cannot be used.")
|
||||
|
||||
with sf.SoundFile(fname, *kwargs_sf) as f:
|
||||
x = f.read()
|
||||
fs = f.samplerate
|
||||
try:
|
||||
cmt = json.loads(f.comment)
|
||||
r = cmt['rms']
|
||||
except:
|
||||
r = 1
|
||||
scaled = False
|
||||
if r != 1:
|
||||
x = x/rms(x) * r
|
||||
scaled = True
|
||||
if v:
|
||||
return x, fs, scaled
|
||||
else:
|
||||
return x, fs
|
||||
|
||||
def rms(x):
|
||||
return np.sqrt(np.mean(x**2))
|
||||
|
||||
def write(fname, x, fs, *args, **kwargs):
|
||||
"""
|
||||
Write an array to a sound file with maximum scale.
|
||||
This only works with WAV files. Note that the extra arguments
|
||||
`args` and `kwargs` will be ignored.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : ndarray
|
||||
The sound array.
|
||||
fs : float, int
|
||||
The sampling rate.
|
||||
fname : str
|
||||
The filename.
|
||||
"""
|
||||
r = rms(x)
|
||||
y = np.atleast_2d(x / np.max(np.abs(x)) * .98)
|
||||
|
||||
if y.shape[1]>y.shape[0]:
|
||||
y = y.T
|
||||
|
||||
with sf.SoundFile(fname, mode='w', samplerate=fs, channels=y.shape[1], subtype='PCM_24') as f:
|
||||
f.write(y)
|
||||
f.comment = json.dumps({'rms': r})
|
||||
|
||||
|
||||
#------------------------------
|
||||
if __name__=='__main__':
|
||||
fs = 44100
|
||||
d = 1
|
||||
t = np.arange(d*fs)/fs
|
||||
x = np.sin(2*np.pi*1000*t)*10
|
||||
|
||||
write(x, fs, "test.wav")
|
||||
y, fs, _ = read("test.wav")
|
||||
|
||||
print(rms(x))
|
||||
print(rms(y))
|
Loading…
x
Reference in New Issue
Block a user