diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..a46f44bd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-Present, Descript + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/audiotools/__init__.py b/audiotools/__init__.py index 66ae138e..573ffd06 100644 --- a/audiotools/__init__.py +++ b/audiotools/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.7.2" +__version__ = "0.7.3" from .core import AudioSignal from .core import STFTParams from .core import Meter diff --git a/audiotools/preference.py b/audiotools/preference.py index 77c4f86f..800a852e 100644 --- a/audiotools/preference.py +++ b/audiotools/preference.py @@ -4,6 +4,8 @@ import copy import csv import random +import sys +import traceback from collections import defaultdict from pathlib import Path from typing import List @@ -123,11 +125,11 @@ console.log("Created WaveSurfer object.") } - load_script('https://unpkg.com/wavesurfer.js') + load_script('https://unpkg.com/wavesurfer.js@6.6.4') .then(() => { - load_script("https://unpkg.com/wavesurfer.js/dist/plugin/wavesurfer.timeline.min.js") + load_script("https://unpkg.com/wavesurfer.js@6.6.4/dist/plugin/wavesurfer.timeline.min.js") .then(() => { - load_script('https://unpkg.com/wavesurfer.js/dist/plugin/wavesurfer.regions.min.js') + load_script('https://unpkg.com/wavesurfer.js@6.6.4/dist/plugin/wavesurfer.regions.min.js') .then(() => { console.log("Loaded regions"); create_wavesurfer(); @@ -535,7 +537,7 @@ def __init__(self, folder: str, shuffle: bool = True, n_samples: int = None): if shuffle: random.shuffle(self.names) - self.n_samples = n_samples + self.n_samples = len(self.names) if n_samples is None else n_samples def get_updates(self, idx, order): key = self.names[idx] @@ -544,7 +546,7 @@ def get_updates(self, idx, order): def progress(self): try: pct = self.current / len(self) * 100 - except: + except: # pragma: no cover pct = 100 text = f"On {self.current} / {len(self)} samples" pbar = ( @@ -555,7 +557,7 @@ def progress(self): return gr.update(value=pbar) def __len__(self): - return len(self.names) + return self.n_samples def filter_completed(self, user, save_path): if not self.filtered: @@ -565,6 +567,7 @@ def filter_completed(self, user, save_path): reader = csv.DictReader(f) done = [r["sample"] for r in reader if r["user"] == user] self.names = [k for k in self.names if k not in done] + self.names = self.names[: self.n_samples] self.filtered = True # Avoid filtering more than once per session. def get_next_sample(self, reference, conditions): @@ -580,6 +583,7 @@ def get_next_sample(self, reference, conditions): done = gr.update(interactive=True) pbar = self.progress() except: + traceback.print_exc() updates = [gr.update() for _ in range(len(self.order))] done = gr.update(value="No more samples!", interactive=False) self.current = len(self) diff --git a/examples/mushra.py b/examples/mushra.py index fd5b1e6d..27dd6b86 100644 --- a/examples/mushra.py +++ b/examples/mushra.py @@ -1,108 +1,105 @@ -import math import string from dataclasses import dataclass from pathlib import Path +from typing import List +import argbind import gradio as gr -import numpy as np -import soundfile as sf from audiotools import preference as pr +@argbind.bind(without_prefix=True) @dataclass class Config: folder: str = None save_path: str = "results.csv" - conditions: list = None + conditions: List[str] = None reference: str = None seed: int = 0 - - -def random_sine(f): - fs = 44100 # sampling rate, Hz, must be integer - duration = 5.0 # in seconds, may be float - - # generate samples, note conversion to float32 array - volume = 0.1 - num_samples = int(fs * duration) - samples = volume * np.sin(2 * math.pi * (f / fs) * np.arange(num_samples)) - - return samples, fs - - -def create_data(path): - path = Path(path) - hz = [110, 140, 180] - - for i in range(6): - name = f"condition_{string.ascii_lowercase[i]}" - for j in range(3): - sample_path = path / name / f"sample_{j}.wav" - sample_path.parent.mkdir(exist_ok=True, parents=True) - audio, sr = random_sine(hz[j] * (2**i)) - sf.write(sample_path, audio, sr) - - -config = Config( - folder="/tmp/pref/audio/", - save_path="/tmp/pref/results.csv", - conditions=["condition_a", "condition_b"], - reference="condition_c", -) - -create_data(config.folder) - -with gr.Blocks() as app: - save_path = config.save_path - samples = gr.State(pr.Samples(config.folder)) - - reference = config.reference - conditions = config.conditions - - player = pr.Player(app) - player.create() - if reference is not None: - player.add("Play Reference") - - user = pr.create_tracker(app) - ratings = [] - - with gr.Row(): - gr.HTML("") - with gr.Column(scale=9): - gr.HTML(pr.slider_mushra) - - for i in range(len(conditions)): - with gr.Row().style(equal_height=True): - x = string.ascii_uppercase[i] - player.add(f"Play {x}") - with gr.Column(scale=9): - ratings.append(gr.Slider(value=50, interactive=True)) - - def build(user, samples, *ratings): - # Filter out samples user has done already, by looking in the CSV. - samples.filter_completed(user, save_path) - - # Write results to CSV - if samples.current > 0: - start_idx = 1 if reference is not None else 0 - name = samples.names[samples.current - 1] - result = {"sample": name, "user": user} - for k, r in zip(samples.order[start_idx:], ratings): - result[k] = r - pr.save_result(result, save_path) - - updates, done, pbar = samples.get_next_sample(reference, conditions) - return updates + [gr.update(value=50) for _ in ratings] + [done, samples, pbar] - - progress = gr.HTML() - begin = gr.Button("Submit", elem_id="start-survey") - begin.click( - fn=build, - inputs=[user, samples] + ratings, - outputs=player.to_list() + ratings + [begin, samples, progress], - ).then(None, _js=pr.reset_player) - - # Comment this back in to actually launch the script. - app.launch() + share: bool = False + n_samples: int = 10 + + +def get_text(wav_file: str): + txt_file = Path(wav_file).with_suffix(".txt") + if Path(txt_file).exists(): + with open(txt_file, "r") as f: + txt = f.read() + else: + txt = "" + return f"""