Skip to content

Commit

Permalink
Merge branch 'release/v1.5.0a2'
Browse files Browse the repository at this point in the history
  • Loading branch information
ChromaticIsobar committed Aug 20, 2021
2 parents 43cbb63 + 59a07e2 commit 697e1d9
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 51 deletions.
32 changes: 32 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
cff-version: 1.2.0
message: "If you use this software in your work, please cite the SMC 2020 paper as below."
authors:
- family-names: "Tiraboschi"
given-names: "Marco"
orcid: "https://orcid.org/0000-0001-5761-4837"
title: "SAMPLE"
version: 1.5.0a2
date-released: 2021-02-26
url: "https://github.com/limunimi/sample"
repository-code: "https://github.com/limunimi/sample"
license: MIT
type: software
preferred-citation:
type: proceedings
title: "Spectral Analysis for Modal Parameters Linear Estimate"
authors:
- family-names: "Tiraboschi"
given-names: "Marco"
orcid: "https://orcid.org/0000-0001-5761-4837"
- family-names: "Avanzini"
given-names: "Federico"
orcid: "https://orcid.org/0000-0002-1257-5878"
- family-names: "Ntalampiras"
given-names: "Stavros"
orcid: "https://orcid.org/0000-0003-3482-9215"
doi: "10.5281/zenodo.3898795"
journal: "Proceedings of the 17th Sound and Music Computing Conference"
month: 6
year: 2020
start: 276
end: 283
22 changes: 1 addition & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ You can install the GUI from the command line with Python via pip.
It is recommended run these commands in a virtual environment in
order to to keep your system clean

```pip install lim-sample[gui]==1.5.0a0```
```pip install lim-sample[gui]==1.5.0a2```

To run the GUI from the command line, run

Expand Down Expand Up @@ -87,23 +87,3 @@ https://github.com/limunimi/sample
### Notebooks
For learning to use the package, you can refer to the interactive
notebooks in the [notebooks](https://github.com/limunimi/sample/tree/master/notebooks) folder

## Paper
Your can find the paper in the SMC 2020 proceedings [here](https://smc2020torino.it/adminupload/file/SMCCIM_2020_paper_167.pdf).

### Abstract
*Modal synthesis is used to generate the sounds associated with the vibration of rigid bodies, according to the characteristics of the force applied onto the object. Towards obtaining sounds of high quality, a great quantity of modes is necessary, the development of which is a long and tedious task for sound designers as they have to manually write the modal parameters.
This paper presents a new approach for practical modal parameter estimation based on the spectral analysis of a single audio example. The method is based on modelling the spectrum of the sound with a time-varying sinusoidal model and fitting the modal parameters with linear and semi-linear techniques.
We also detail the physical and mathematical principles that motivate the algorithm design choices.
A Python implementation of the proposed approach has been developed and tested on a dataset of impact sounds considering objects of different shapes and materials. We assess the performance of the algorithm by evaluating the quality of the resynthesised sounds. Resynthesis is carried out via the Sound Design Toolkit (SDT) modal engine and compared to the sounds resynthesised from parameters extracted by SDT's own estimator. The proposed method was thoroughly evaluated both objectively using perceptually relevant features and subjectively following the MUSHRA protocol.*

### Cite
```
@inproceedings{tiraboschi2020spectral,
title={Spectral Analysis for Modal Parameters Linear Estimate},
author={Tiraboschi, M and Avanzini, F and Ntalampiras, S},
booktitle={Sound \& Music Computing Conference},
year={2020},
organization={SMC},
}
```
2 changes: 1 addition & 1 deletion changelog/v1_5_0.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## v1.5.0a0
## v1.5.0a2
This version is still in its alpha phase

### GUI
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ requests
ttkthemes
Pillow
pygame
throttle

# Pylint
pylint
Expand Down
2 changes: 1 addition & 1 deletion sample/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from sample.sample import SAMPLE


__version__ = "1.5.0a0"
__version__ = "1.5.0a2"


@cli.main(__name__)
Expand Down
37 changes: 18 additions & 19 deletions sample/gui.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
"""SAMPLE GUI launcher"""
from chromatictools import cli
from sample.widgets import main, logging
import sample
import multiprocessing
import logging as _logging
import argparse
import sys


if sys.platform == "linux":
default_theme = "radiance"
else:
default_theme = "arc"


def launch(args):
"""Launch the GUI main loop
Args:
args (Namespace): Command-line arguments namespace"""
from sample.widgets import main, logging # pylint: disable=C0415
import logging as _logging # pylint: disable=C0415
import sample # pylint: disable=C0415

_logging.basicConfig(
level=_logging.WARNING,
format="%(asctime)s %(name)-12s %(levelname)-8s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
_logging.captureWarnings(True)
logging.setLevel(args.log_level)
logging.info("SAMPLE: version %s", sample.__version__)
logging.info("Args: %s", args)

root = main.main(
splash_time=args.splash_time,
gui_kwargs=dict(
Expand All @@ -31,12 +36,10 @@ def launch(args):
@cli.main(__name__, *sys.argv[1:])
def run(*argv):
"""Launch the SAMPLE GUI"""
_logging.basicConfig(
level=_logging.WARNING,
format="%(asctime)s %(name)-12s %(levelname)-8s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
_logging.captureWarnings(True)
if sys.platform == "linux":
default_theme = "radiance"
else:
default_theme = "arc"

parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
Expand All @@ -57,10 +60,6 @@ def run(*argv):
)
args, _ = parser.parse_known_args(argv)

logging.setLevel(args.log_level)
logging.info("SAMPLE: version %s", sample.__version__)
logging.info("Args: %s", args)

root = multiprocessing.Process(target=launch, args=(args,))
root.start()
root.join()
Expand Down
11 changes: 11 additions & 0 deletions sample/plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,19 @@ def sine_tracking_2d(m: sm.SinusoidalModel, ax=None):
The axes list"""
if ax is None:
_, ax = plt.subplots(1, 2, sharex=True)
tmax = 0
if m.reverse:
if m.save_intermediate:
tmax = len(m.intermediate_["stft"]) * m.h / m.fs
else:
tmax = max(
(track["start_frame"] + track["freq"].size) * m.h / m.fs
for track in m.sine_tracker_.all_tracks_
)
for track in m.sine_tracker_.all_tracks_:
t_x = (track["start_frame"] + np.arange(track["freq"].size)) * m.h / m.fs
if m.reverse:
t_x = tmax - t_x
ax[0].plot(t_x, track["freq"])
ax[1].plot(t_x, track["mag"])

Expand Down
15 changes: 8 additions & 7 deletions sample/widgets/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@ def __init__(
ax.set_yticks(())
# ------------------------------------------------------------------------

self.progressbar = tk.Progressbar(self, maximum=1, value=0)
self.progressbar.grid(row=1)

self.bottom_row = tk.Frame(self)
self.bottom_row.grid(row=1)
self.bottom_row.grid(row=2)
self.bottom_row.responsive(1, 4)

# Analysis button
Expand Down Expand Up @@ -99,11 +102,9 @@ def update_plot(self):
ax.clear()
m = self.sample_object.sinusoidal_model
stft = np.array([mx for mx, _ in m.intermediate_["stft"]]).T
tmax = max((
track["start_frame"] + track["freq"].size
for track in m.sine_tracker_.all_tracks_
), default=0,
) * m.h / m.fs
if m.reverse:
stft = np.fliplr(stft)
tmax = len(m.intermediate_["stft"]) * m.h / m.fs

plots.sine_tracking_2d(m, ax=self.ax)

Expand Down Expand Up @@ -175,13 +176,13 @@ def analysis_cbk(self, *args, **kwargs): # pylint: disable=W0613
self.sample_object.fit(
x, sinusoidal_model__fs=self.audio_sr,
sinusoidal_model__save_intermediate=True,
sinusoidal_model__progressbar=self.progressbar,
)
except Exception as e: # pylint: disable=W0703
messagebox.showerror(
type(e).__name__, str(e)
)
return
messagebox.showinfo("Success", "Analysis done!")
self.audio_resynth_x = None
self.update_plot()

Expand Down
107 changes: 107 additions & 0 deletions sample/widgets/sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""SAMPLE class for use in GUI"""
from sample import sample
from sample.sms import mm
import tkinter as tk
import numpy as np
import throttle
from typing import Optional, Tuple


class SAMPLE(sample.SAMPLE):
"""SAMPLE model for use in the GUI. For a full list of arguments see
:class:`sample.sample.SAMPLE`"""
class SinusoidalModel(mm.ModalModel):
"""Sinusoidal tracker for use in the GUI. For a full list of
arguments see :class:`sample.sms.mm.ModalModel`
Args:
progressbar (optional): Progressbar widget for visualizing
the peak tracking progress"""
def __init__(
self,
progressbar: Optional[tk.Widget] = None,
fs: int = 44100,
w: Optional[np.ndarray] = None,
n: int = 2048,
h: int = 500,
t: float = -90,
max_n_sines: int = 100,
min_sine_dur: float = 0.04,
freq_dev_offset: float = 20,
freq_dev_slope: float = 0.01,
reverse: bool = False,
sine_tracker_cls: type = mm.ModalTracker,
save_intermediate: bool = False,
frequency_bounds: Tuple[Optional[float], Optional[float]] = (20, 16000),
peak_threshold: float = -90,
merge_strategy: str = "average",
strip_t: Optional[float] = None,
):
self.progressbar = progressbar
super().__init__(
fs=fs, w=w, n=n, h=h, t=t,
max_n_sines=max_n_sines,
min_sine_dur=min_sine_dur,
freq_dev_offset=freq_dev_offset,
freq_dev_slope=freq_dev_slope,
reverse=reverse,
sine_tracker_cls=sine_tracker_cls,
save_intermediate=save_intermediate,
frequency_bounds=frequency_bounds,
peak_threshold=peak_threshold,
merge_strategy=merge_strategy,
strip_t=strip_t,
)

def fit(self, x: np.ndarray, y=None, **kwargs):
"""Analyze audio data
Args:
x (array): audio input
y (ignored): exists for compatibility
kwargs: Any parameter, overrides initialization
Returns:
SinusoidalModel: self"""
self.set_params(**kwargs)
self.w_ = self.normalized_window
if self.progressbar is not None:
self.progressbar["maximum"] = -1
self.progressbar.config(
value=0,
maximum=len(list(
self.time_frames(x)
))
)
s = super().fit(x=x, y=y)
if self.progressbar is not None:
self.progressbar.config(value=1, maximum=1)
return s

@throttle.wrap(.0125, 1)
def progressbar_update(self, value: Optional[float] = None):
"""Update the progress bar. This function is throttled"""
if value is not None:
self.progressbar.config(value=value)
self.progressbar.update()

def time_frames(self, x: np.ndarray):
"""Generator of frames for a given input. Also,
updates the progressbar if one has been specified
Args:
x (array): Input
Returns:
generator: Generator of overlapping frames of the padded input"""
it = super().time_frames(x)
if self.progressbar is not None and self.progressbar["maximum"] > 0:
def func(t):
self.progressbar_update(value=t[0])
return t[-1]
it = map(func, enumerate(it))
for f in it:
yield f

def __init__(self, sinusoidal_model=SinusoidalModel(), **kwargs):
super().__init__(sinusoidal_model=sinusoidal_model, **kwargs)
3 changes: 1 addition & 2 deletions sample/widgets/settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Settings tab"""
import sample
from sample.widgets import responsive as tk, utils, logging
from sample.widgets import responsive as tk, utils, logging, sample
from matplotlib.backends import _backend_tk
from scipy import signal
import functools
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"pygame",
"matplotlib",
"Pillow",
"throttle",
],
"plots": [
"matplotlib",
Expand Down

0 comments on commit 697e1d9

Please sign in to comment.