Source code for sound.filter

from .signal import ConstantSignal, Signal
from .tone import SineWave

__all__ = ('LowPassFilter', 'BetterLowPassFilter', 'HighPassFilter', 'FakeFMFilter', 'FMFilter', 'ring_filter', 'bessel_wave', 'PitchShift')

numty = (int, float)

[docs] class LowPassFilter(Signal): """ Filter the argument signal, cutting out higher-frequency components. The beta parameter controls the strength of the filter. """ def __init__(self, src, beta=0.5, pure=False, iir=True): self.src = src self.beta = beta self.pure_param = pure self.pure = src.pure and pure self.iir = iir self.duration = src.duration if pure and iir: raise Exception("Can't be both pure and infinite") self.last_sample = 0 self.last_frame = -1 def amplitude(self, frame): self.last_frame += 1 if self.last_frame != frame: if self.pure: self.last_frame = frame self.last_sample = self.src.amplitude(frame-1) else: raise Exception("Can't access pure samples out of order") cur_sample = self.src.amplitude(frame) out = cur_sample * self.beta + self.last_sample * (1-self.beta) self.last_sample = out if self.iir else cur_sample return out
[docs] class BetterLowPassFilter(Signal): """ This is a different implementation of the low pass filter. Pass it a signal to filter, and then a number of numerical parameters. Each frame of the output signal is the linear combination of the given parameters and the last n frames from the input signal. The last passes parameter corresponds with the most recent input frame. """ def __init__(self, src, *params): self.params = list(params) eq = float(sum(self.params)) self.params = [x / eq for x in self.params] self.cache = [0.]*len(self.params) self.pure = False self.duration = src.duration self.src = src def amplitude(self, frame): for i in range(len(self.cache) - 1): self.cache[i] = self.cache[i+1] self.cache[-1] = self.src.amplitude(frame) return sum(x * y for x, y in zip(self.params, self.cache))
[docs] class HighPassFilter(Signal): """ Filter the input signal, cutting out low-frequency components. The beta parameter controls the strength of the filter. """ def __init__(self, src, beta=0.5): self.src = src self.beta = beta self.pure = False self.last_sample = 0 self.last_out = 0 self.last_frame = -1 self.duration = src.duration def amplitude(self, frame): self.last_frame += 1 if self.last_frame != frame: raise Exception("Out of order access on impure sample: HighPassFilter") cur_sample = self.src.amplitude(frame) out = self.beta * (self.last_out + cur_sample - self.last_sample) self.last_sample = cur_sample self.last_out = out return out
[docs] class FakeFMFilter(Signal): """ Don't use this class please, I am so embarassed """ def __init__(self, carrier_class, modulator, carrier_freq=440, mod_quantity=300): self.carrier_class = carrier_class self.modulator = modulator self.carrier_freq = carrier_freq self.mod_quantity = mod_quantity sample_carrier = carrier_class(carrier_freq) if not sample_carrier.pure: raise Exception("Can't use an impure carrier for a FM filter") self.duration = min(sample_carrier.duration, modulator.duration) self.trigger = 0 self.trigger_state = True self.pure = False # :/ def _amplitude(self, frame, tframe): return self.carrier_class( self.carrier_freq + self.mod_quantity * self.modulator.amplitude(frame) ).amplitude(tframe) def amplitude(self, frame): out = self._amplitude(frame, frame - self.trigger) if out > 0 and not self.trigger_state: if self._amplitude(frame - 1, frame - 1 - self.trigger) <= 0: self.trigger = frame self.trigger_state = True elif out < 0 and self.trigger_state: if self._amplitude(frame - 1, frame - 1 - self.trigger) >= 0: self.trigger_state = False return out
[docs] class FMFilter(Signal): """ Modulate the frequency of the carrier signal with the modulator signal. The strength of the modulator may be adjusted by the mod_quantity parameter. """ def __init__(self, carrier, modulator, mod_quantity=300): if not carrier.pure: raise Exception("Can't use an impure carrier for an FM filter") self.carrier = carrier self.modulator = modulator self.mod_quantity = mod_quantity self.duration = carrier.duration self.pure = modulator.pure def amplitude(self, frame): return self.carrier.amplitude(frame + self.mod_quantity * self.modulator.amplitude(frame))
[docs] def ring_filter(data): """ Perform ring modulation on a given number of signals. :param data: A list of signals to modulate together """ out = 1 for src in data: out *= src return out
[docs] def bessel_wave(freq, alpha, beta): """ I literally don't care if this isn't technically a thing Outputs an FM-synthesized sample with carrier frequency freq, modulator frequency freq * alpha, and modulator intensity beta """ return FMFilter(SineWave(freq), SineWave(alpha), beta)
[docs] class PitchShift(Signal): """ Perform a pitch shift on the source signal by the shift signal. The value of the shift signal serves as a multiplier for the frequency of the source. Note that this works just by playing the source faster, so putting this on top of an enveloped sound is a bad idea, you need to put this under the envelope. """ def __init__(self, src, shift): if isinstance(shift, numty): shift = ConstantSignal(shift) self.src = src self.shift = shift - 1 self.phase = 0 self.pure = False self.duration = src.duration self.done = False def amplitude(self, frame): self.phase += self.shift.amplitude(frame) intpart = int(self.phase) fracpart = self.phase - intpart f1 = frame + intpart f2 = f1 + 1 if f2 >= self.duration: self.done = True s1 = self.src.amplitude(f1) s2 = self.src.amplitude(f2) return s1 * (1-fracpart) + s2 * fracpart
class Convolution(Signal): def __init__(self, a, b): self.a = a self.b = b self.duration = a.duration + b.duration def amplitude(self, frame): out = 0. f = frame g = 0 while f > 0 and g < self.b.duration: out += self.a.amplitude(f) * self.b.amplitude(g) f -= 1 g += 1 return g