diff --git a/src/remucs/__main__.py b/src/remucs/__main__.py index 905213c..dab384e 100644 --- a/src/remucs/__main__.py +++ b/src/remucs/__main__.py @@ -40,6 +40,10 @@ default=','.join(["1"]*len(STEMS)), show_default=True, help=f'Gain of individual stems \"{",".join(sorted(STEMS))}\", e.g. \"2,1,0.5,0\".') +@click.option('-a', '--a4', + default=None, + type=int, + help='Target tuning reference frequency, to automatically estimate the pitch shifting factor (experimental).') @click.option('-p', '--pitch', default='0', show_default=True, @@ -57,7 +61,7 @@ VERSION, '-V', '--version', message='%(version)s') -def main(files, fine, norm, mono, bala, gain, pitch, data, quiet): +def main(files, fine, norm, mono, bala, gain, a4, pitch, data, quiet): try: @@ -72,6 +76,7 @@ def main(files, fine, norm, mono, bala, gain, pitch, data, quiet): mono=mono, bala=bala, gain=gain, + a4=a4, pitch=pitch) for file in list(set(files)): diff --git a/src/remucs/options.py b/src/remucs/options.py index 0b64a1e..2db5875 100644 --- a/src/remucs/options.py +++ b/src/remucs/options.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from typing import List +from typing import List, Union @dataclass @@ -14,6 +14,7 @@ class RemucsOptions: bala: List[float] = field(default_factory=lambda: [0]*4) gain: List[float] = field(default_factory=lambda: [1]*4) + a4: Union[int, None] = None pitch: float = 1 quefrency: float = 1e-3 diff --git a/src/remucs/remucs.py b/src/remucs/remucs.py index a9b2d1d..2078819 100644 --- a/src/remucs/remucs.py +++ b/src/remucs/remucs.py @@ -8,6 +8,7 @@ from remucs.options import RemucsOptions from remucs.analysis import analyze from remucs.synthesis import synthesize +from remucs.tuning import howto_shift_pitch def remucs(file: Union[str, PathLike], data: Union[str, PathLike] = '~', opts: Union[RemucsOptions, None] = None): @@ -36,4 +37,10 @@ def remucs(file: Union[str, PathLike], data: Union[str, PathLike] = '~', opts: U dst = file.with_suffix(opts.remucs + file.suffix) analyze(src, data, opts) + + if opts.a4: + + file = data / opts.model / ('other' + src.suffix) + opts.pitch = howto_shift_pitch(file, opts) + synthesize(dst, data, opts) diff --git a/src/remucs/tuning.py b/src/remucs/tuning.py index 85abb9b..7d40013 100644 --- a/src/remucs/tuning.py +++ b/src/remucs/tuning.py @@ -61,7 +61,7 @@ def analyze(src: Path, opts: RemucsOptions) -> Tuple[NDArray, NDArray]: x, samplerate = resample(src, samplerate) reference = 440 - bandwidth = (100, 4000) + bandwidth = 100, 4000 resolution = int(1200 / 25) batchsize = int(1 * samplerate) numpeaks = 3 @@ -117,3 +117,25 @@ def analyze(src: Path, opts: RemucsOptions) -> Tuple[NDArray, NDArray]: assert numpy.all(numpy.isfinite(weights)) return estimates, weights + + +def howto_shift_pitch(src: Path, opts: RemucsOptions) -> float: + + estimates, weights = analyze(src, opts) + + values = numpy.round(estimates).astype(int) + bounds = numpy.min(values), numpy.max(values) + bins = numpy.arange(bounds[0], bounds[1] + 1) + edges = numpy.arange(bounds[0], bounds[1] + 2) - 0.5 + hist = numpy.histogram(values, bins=edges, weights=weights) + + assert hist[0].shape == bins.shape + assert hist[1].shape == edges.shape + + a4 = bins[numpy.argmax(hist[0])] + q = opts.a4 / a4 + + if not opts.quiet: + click.echo(f'Estimated pitch shifting factor {q} (from {a4} Hz to {opts.a4} Hz)') + + return q diff --git a/src/sandbox/tuning.py b/src/sandbox/tuning.py index 087c4fc..bb8633d 100644 --- a/src/sandbox/tuning.py +++ b/src/sandbox/tuning.py @@ -32,9 +32,9 @@ def main(): cp2, weights = analyze(test, RemucsOptions()) values = np.round(cp2).astype(int) - minmax = np.min(values), np.max(values) - bins = np.arange(minmax[0], minmax[1] + 1) - edges = np.arange(minmax[0], minmax[1] + 2) - 0.5 + bounds = np.min(values), np.max(values) + bins = np.arange(bounds[0], bounds[1] + 1) + edges = np.arange(bounds[0], bounds[1] + 2) - 0.5 hist = np.histogram(values, bins=edges, weights=weights) assert hist[0].shape == bins.shape