def first_sound(samples, threshold=-120.0, periodsamps=256, hopratio=0.5, skip=0) -> int: """ Find the first sample in samples whith a rms exceeding the given threshold Returns: time of the first sample holding sound or -1 if no sound found """ threshold_amp = db2amp(threshold) hopsamples = int(periodsamps * hopratio) i = 0 while True: i0 = skip + i * hopsamples i1 = i0 + periodsamps if i1 > len(samples): break chunk = samples[i0:i1] rms_now = rms(chunk) if rms_now > threshold_amp: return i0 return -1
def atx(self, time: float, freqs: _S[float]) -> _S[float]: s = self.sp.partials_at(time) data = [(p.freq(time), p.amp(time)) for p in s] data.sort() out = [] d = self.decay mindb = self.mindb for freq in freqs: idx1 = bisect.bisect(data, (freq, 0)) if idx1 >= len(data): idx1 = idx0 = idx1 - 1 idx0 = max(0, idx1 - 1) f0, a0 = data[idx0] f1, a1 = data[idx1] a0db = amp2db(a0) a1db = amp2db(a1) df0 = (a0db - mindb) / d df1 = (a1db - mindb) / d if f1 - df1 <= freq <= f0 + df0: db0 = _linlin(freq, f0, f0 + df0, a0db, -120) db1 = _linlin(freq, f1 - df1, f1, -120, a1db) db = max(db0, db1) elif freq <= f0 + df0: db = _linlin(freq, f0, f0 + df0, a0db, -120) elif f1 - df1 <= freq: db = _linlin(freq, f1 - df1, f1, -120, a1db) else: db = mindb out.append(db2amp(db)) return out """
def nearestx(self, time: float, freqs: _S[float], timemargin=0.01) -> _S[float]: """ Return a list of (freq, amp), representing the freq and amp of the nearest Partial for each given freq. Example: freqs = [100, 200] out = surface.nearest(0.5, freqs) for freq, row in zip(freq, out): print(f"Nearest partial from {freq}Hz @ 0.5s has a freq. of {row[0]}") """ s = self.sp.partials_between(time - timemargin, time + timemargin) if not s: return [(0, 0)] * len(freqs) data = [(p.freq(time), p.amp(time)) for p in s] mindb = self.mindb decay = self.decay out = [] for freq in freqs: nearest = min(data, key=lambda row: abs(row[0] - freq)) f, a = nearest db = amp2db(a) diff = abs(f - freq) db2 = max(db - diff * decay, mindb) out.append((f, db2amp(db2))) return out
def loudest_task(self=self, minamp=db2amp(minamp_db)): pl = self._plot mousepos = self.ui.mousepos() time = mousepos.x() lasttime = self._state.get('synth_loudest.lasttime', -1) if abs(time - lasttime) < 0.001 or time <= 0: return best = loudest(time, maxpartials, minamp, self._filter_active, *self._filter_range) if not best: return freqs = [row[1] for row in best] synth = self._chordsynth lastdraw = self._state.get('synth_loudest.lastdraw', 0) if abs(time - lastdraw) > 0.005: self._chordcursor.setData([time] * len(freqs), freqs) self._state['synth_loudest.lastdraw'] = time for i, row in enumerate(best): synth.setOsc(i, row[1], row[0], row[2]) self._state['synth_loudest.lasttime'] = time self._chordcursortxt.setPos(time, freqs[-1]) txt = "%.3f" % time self._chordcursortxt.setText(txt)
def normalize(self, headroom=0.) -> Sample: """Normalize in place, returns self headroom: maximum peak in dB """ max_peak_possible = db2amp(headroom) peak = np.abs(self.samples).max() ratio = max_peak_possible / peak self.samples *= ratio return self
def create_shape(shape='expon(3)', mindb: U[int, float] = -90, maxdb: U[int, float] = 0) -> _bpf.BpfInterface: """ Return a bpf mapping 0-1 to amplitudes, as needed to be passed to DynamicsCurve Args: shape: a descriptor of the curve to use to map amplitude to dynamics mindb: the min. representable amplitude (in dB) maxdb: the max. representable amplitude (in dB) If X is dynamic and Y is amplitude, an exponential curve with exp > 1 will allocate more dynamics to the soft amplitude range, resulting in more resolution for small amplitudes. A curve with exp < 1 will result in more resolution for high dynamics """ minamp, maxamp = db2amp(mindb), db2amp(maxdb) return _bpf.util.makebpf(shape, [0, 1], [minamp, maxamp])
def create_shape(shape='expon(3)', mindb=-90, maxdb=0) -> BpfInterface: """ Return a bpf mapping 0-1 to amplitudes, as needed to be passed to DynamicsCurve * descr: a descriptor of the curve to use to map amplitude to dynamics * mindb, maxdb: the maximum and minimum representable amplitudes (in dB) * dynamics: - a list of dynamic-strings, - None to use the default (from pppp to ffff) If X is dynamic and Y is amplitude, an exponential curve with exp > 1 will allocate more dynamics to the soft amplitude range, resulting in more resolution for small amplitudes. A curve with exp < 1 will result in more resolution for high dynamics import bpf4 as bpf shape = bpf.util.makebpf("expon(3)", [0, 1], [-90, 0]).db2amp() """ minamp, maxamp = db2amp(mindb), db2amp(maxdb) return _bpf.util.makebpf(shape, [0, 1], [minamp, maxamp])
def _synth_nearest_partial(self, state: bool, margin_hz=50, minamp_db=-60, gain=2.0): currstate = self._state.get('synth_nearest', False) if state == currstate: return if not state: self._synthcursor.hide() self._synthcursortxt.hide() self._removetask("nearest") fadetime = 0.2 self._sinesynth.fadeout(fadetime) def stop(synth=self._sinesynth, state=self._state): synth.stop() state['synth_nearest'] = False QtCore.QTimer.singleShot(fadetime * 1000 + 50, stop) return minamp = db2amp(minamp_db) def update(self=self): synth = self._sinesynth pl = self._plot mousepos = self.ui.mousepos() time = mousepos.x() mousefreq = mousepos.y() freq, amp = self._surface.nearest(time, mousefreq) if amp < minamp: return if abs(freq - mousefreq) > margin_hz: amp = 0 if freq <= 0: return synth.setOsc(0, freq, amp) self._synthcursor.setData([time], [freq]) self._synthcursortxt.setPos(time, freq) txt = "%d Hz %s %.3f" % (int(freq), f2n(freq), time) self._synthcursortxt.setText(txt) self._synthcursor.show() self._synthcursortxt.show() self._surface = SpectralSurface(self.spectrum, decay=0.01) self._sinesynth = SineSynth(freq=self.ui.mousepos().y(), amp=0, freqport=0.2, gain=gain) self._addtask("nearest", update) self._state['synth_nearest'] = True
def first_silence(samples: np.ndarray, threshold=-100, period=256, hopratio=0.5, soundthreshold=-60, startidx=0) -> int: """ Return the sample where rms decays below threshold Args: samples: the samples data threshold: the threshold in dBs (rms) period: how many samples to use for rms calculation hopratio: how many samples to skip before taking the next measurement soundthreshold: the threshold to considere that the sound started (rms) startidx: the sample to start looking for silence (0 to start from beginning) Returns: the index where the first silence is found (-1 if no silence found) """ soundstarted = False hopsamples = int(period * hopratio) thresholdamp = db2amp(threshold) soundthreshamp = db2amp(soundthreshold) lastrms = rms(samples[startidx:startidx + period]) idx = hopsamples + startidx while idx < len(samples) - period: win = samples[idx:idx + period] rmsnow = rms(win) if rmsnow >= soundthreshamp: soundstarted = True elif rmsnow <= thresholdamp and lastrms > thresholdamp and soundstarted: return idx lastrms = rmsnow idx += hopsamples return -1
def nearest(self, time: float, freq: float, timemargin=0.01) -> float: s = self.sp.partials_between(time - timemargin, time + timemargin) if not s: return (0, 0) mindist = float("inf") for p in s: p_freq = p.freq(time) dist = abs(p_freq - freq) if dist < mindist: mindist = dist best_freq = p_freq best_partial = p best_amp = best_partial.amp(time) db = max(amp2db(best_amp) - mindist * self.decay, self.mindb) return best_freq, db2amp(db)
def _synth_nearest_partial(self, state:bool, margin_hz=50, minamp_db=-60, gain=2.0): currstate = self._state.get('synth_nearest', False) if state == currstate: return if not state: self._synthcursor.hide() self._synthcursortxt.hide() self._removetask("nearest") fadetime = 0.2 self._sinesynth.fadeout(fadetime) def stop(synth=self._sinesynth, state=self._state): synth.stop() state['synth_nearest'] = False QtCore.QTimer.singleShot(fadetime * 1000 + 50, stop) return minamp = db2amp(minamp_db) def update(self=self): synth = self._sinesynth pl = self._plot time = pl._mousex mousefreq = pl._mousey freq, amp = self._surface.nearest(time, mousefreq) if amp < minamp: return if abs(freq - mousefreq) > margin_hz: amp = 0 if freq <= 0: return synth.setOsc(0, freq, amp) self._synthcursor.setData([time], [freq]) self._synthcursortxt.setPos(time, freq) txt = "%d Hz %s %.3f" % (int(freq), f2n(freq), time) self._synthcursortxt.setText(txt) self._synthcursor.show() self._synthcursortxt.show() self._surface = SpectralSurface(self.spectrum, decay=0.01) self._sinesynth = SineSynth(freq=self._plot._mousey, amp=0, freqport=0.2, gain=gain) self._addtask("nearest", update) self._state['synth_nearest'] = True
def make_weighter(mode='speech'): if mode == 'speech': freqcurve = bpf.linear(0, 0, 50, 0, 80, 1, 200, 1, 250, 2, 4500, 2, 6000, 1, 8000, 1, 10000, 0) durcurve = bpf.linear(0, 0, 0.01, 0, 0.02, 1) ampcurve = bpf.linear(0, 0, db2amp(-75), 0, db2amp(-65), 1) elif mode == 'transcription': freqcurve = bpf.linear(0, 0, 30, 0, 50, 1, 4500, 1, 5000, 0) durcurve = bpf.linear(0, 0, 0.01, 0, 0.02, 1) ampcurve = bpf.linear(0, 0, db2amp(-70), 0, db2amp(-60), 1) elif mode == 'default': freqcurve = bpf.linear(0, 0, 30, 0, 50, 1, 8000, 1, 12000, 0.5, 16000, 0) durcurve = bpf.linear(0, 0, 0.01, 0, 0.02, 1) ampcurve = bpf.linear(0, 0, db2amp(-80), 0, db2amp(-60), 1) elif mode == 'speech-transcription': freqcurve = bpf.linear(0, 0, 50, 0, 80, 1, 200, 1, 250, 2, 4500, 2, 5000, 0) durcurve = bpf.linear(0, 0, 0.01, 0, 0.02, 1) ampcurve = bpf.linear(0, 0, db2amp(-75), 0, db2amp(-65), 1) else: raise KeyError("mode unknown, must be one of 'speech', 'transcription'") return PartialWeighter(freqcurve=freqcurve, durcurve=durcurve, ampcurve=ampcurve, freqweight=1, durweight=1, ampweight=2)
def contrast(spectrum: sp.Spectrum, mid:float, exp=1.0) -> sp.Spectrum: """ Change the contrast of spectrum mid: a dB amplitude exp: 0 - no effect 1 - full effect > 1: possible, needs rescaling formula: B = A * (A/mid)**exp """ assert mid <= 0 mid = db2amp(mid) newpartials = [] for partial in spectrum.partials: A = partial.amp.points()[1] B = A * (A/mid)**exp newpartial = partial.clone(amps=B) newpartials.append(newpartial) return spectrum.__class__(newpartials)
def contrast(spectrum: sp.Spectrum, mid: float, exp=1.0) -> sp.Spectrum: """ Change the contrast of spectrum mid: a dB amplitude exp: 0 - no effect 1 - full effect > 1: possible, needs rescaling formula: newamps = amps * (amps/mid)**exp """ assert mid <= 0 mid = db2amp(mid) newpartials = [] for partial in spectrum.partials: A = partial.amps B = A * (A / mid)**exp newpartial = partial.clone(amps=B) newpartials.append(newpartial) return spectrum.__class__(newpartials)
def loudest_task(self=self, minamp=db2amp(minamp_db)): pl = self._plot time = pl._mousex lasttime = self._state.get('synth_loudest.lasttime', -1) if abs(time - lasttime) < 0.001 or time <= 0: return best = loudest(time, maxpartials, minamp, self._filter_active, *self._filter_range) if not best: return freqs = [row[1] for row in best] synth = self._chordsynth lastdraw = self._state.get('synth_loudest.lastdraw', 0) if abs(time - lastdraw) > 0.005: self._chordcursor.setData([time]*len(freqs), freqs) self._state['synth_loudest.lastdraw'] = time for i, row in enumerate(best): synth.setOsc(i, row[1], row[0], row[2]) self._state['synth_loudest.lasttime'] = time self._chordcursortxt.setPos(time, freqs[-1]) txt = "%.3f" % time self._chordcursortxt.setText(txt)
def db2dyn(self, db: float) -> str: return self.amp2dyn(db2amp(db))
def db2dyn(db: float, nearest=True) -> str: amp = db2amp(db) return _default.amp2dyn(amp, nearest)
def scaleAmps(n, maxdb=-3): return 1 / sqrt(n) * db2amp(maxdb)