Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions playground/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,8 +540,10 @@ class FrameBuffer:

def __init__(self):
self._buffer: Optional[collections.deque] = None
self._frame_size = 0

def push(self, frame: np.ndarray, max_frames_to_buffer: int):
self._frame_size = frame.shape[0]
buffer = self._update_buffer(frame.shape, max_frames_to_buffer)
buffer.append(frame) # We append on the right.

Expand All @@ -552,6 +554,14 @@ def iter_buffered(self) -> typing.Iterable[np.ndarray]:
assert self._buffer
return iter(self._buffer)

@property
def maxlen(self):
return self._buffer.maxlen

@property
def frame_size(self):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is available as clock_signal.num_samples.

return self._frame_size

def _update_buffer(self, frame_shape: typing.Tuple[int, int], max_frames_to_buffer: int) -> collections.deque:
if self._buffer is None:
# Start out with just 0s
Expand Down Expand Up @@ -981,6 +991,130 @@ def to_melody(trigger, melody: _Cycler):
return np.concatenate(output, 0)


# Question, do we have access to the sampling frequency of the whole mod synt?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is available as clock_signal.sample_rate (should be renamed :D )

# if yes it's nice cause we could do actual physical values for example milliseconds for delay
# and hz for filter cutoffs...
class DelayElement:
def __init__(self, time, feedback, gain=1, proportional=False, lo_cut=0.01, hi_cut=0.9, limit=False):
"""
Basic delay element to create delays or other types of spatial effects

Parameters
----------
time: int
number of buffer steps the signal is delayed
feedback: float
how much of the signal is fed back (0-1)
gain: float
amplification applied to the delay
proportional: bool
whether or not the normal signal amplitude is decreased when the feedback increases
(requires some gain to keep the volume)
lo_cut: float
lower cutoff frequency of the filter relative to the nyquist frequency
hi_cut: float
upper cutoff frequency of the filter relative to the nyquist frequency
limit: bool
use a basic limiter to avoid distortion
"""

self.time = time
self.feedback = feedback
self.gain = gain
self.delay = np.array([0])
self.proportional = proportional
self.limit = limit

self.filter_coeff_b, self.filter_coeff_a = scipy.signal.butter(4, [lo_cut, hi_cut], 'band')

def __call__(self, signal_buffer):
n = signal_buffer.maxlen - self.time - 1
full_signal = signal_buffer.get()
delayed_signal = full_signal[n*signal_buffer.frame_size:(n+1)*signal_buffer.frame_size]

if self.proportional:
self.delay = delayed_signal * (1 - self.feedback) + self.delay * self.feedback
else:
self.delay = delayed_signal + self.delay * self.feedback

self.delay = scipy.signal.lfilter(self.filter_coeff_b, self.filter_coeff_a, self.delay)

delay = self.delay * self.gain

if self.limit:
# TODO: only experimental, need to check the proper dynamic range
# you can get some drive if you set a high gain (~100) and enable limiting
delay = scipy.special.expit(delay*6)

return delay


class SimpleDelay(Module):

def __init__(self, signal: Module, time, feedback, mix, gain=1, proportional=True, hi_cut=0.8, lo_cut=0.01, limit=False):
super().__init__()

self.signal = signal
self.buffer = FrameBuffer()

self.delay = DelayElement(time=time,
feedback=feedback,
gain=gain,
proportional=proportional,
lo_cut=lo_cut,
hi_cut=hi_cut,
limit=limit)

self.mix = mix
self.delay_buffer_size = time + 1

def out(self, clock_signal: ClockSignal):
input_signal = self.signal(clock_signal)

self.buffer.push(input_signal, max_frames_to_buffer=self.delay_buffer_size)

return input_signal * (1 - self.mix) + self.delay(self.buffer) * self.mix


# WIP not really a reverb at the moment...
class DReverb(Module):

def __init__(self, signal: Module, mix, decay):
super().__init__()

self.signal = signal
self.buffer = FrameBuffer()
self.decay = decay # not implemented

self.delay_elements = [
DelayElement(time=2, feedback=0.8, proportional=True, gain=20, limit=True, hi_cut=0.6),
DelayElement(time=3, feedback=0.7, proportional=True, gain=3, limit=False, hi_cut=0.8),
DelayElement(time=10, feedback=0.7, proportional=True, gain=2, hi_cut=0.9),
DelayElement(time=7, feedback=0.8, proportional=True, gain=4, hi_cut=0.5),
DelayElement(time=16, feedback=0.9, proportional=True, gain=10, hi_cut=0.6, lo_cut=0.1),
DelayElement(time=14, feedback=0.8, proportional=True, gain=6, hi_cut=0.6),
]

self.mix = mix
self.delay_buffer_size = 20
self.limit = False

def out(self, clock_signal: ClockSignal):
input_signal = self.signal(clock_signal)

self.buffer.push(input_signal, max_frames_to_buffer=self.delay_buffer_size)

pseudo_reverb = 0
for delay in self.delay_elements:
pseudo_reverb += delay(self.buffer)

# TODO: only experimental, need to check the proper dynamic range
if self.limit:
pseudo_reverb = scipy.special.expit(pseudo_reverb * 6)

return input_signal * (1 - self.mix) + pseudo_reverb * self.mix


class Reverb(Module):
def __init__(self, m, alpha: Module, max_decay: Module):
super().__init__()
Expand Down Expand Up @@ -1080,6 +1214,29 @@ def __init__(self):
P = Parameter
from scipy import signal

class DelayTester(Module):
def __init__(self):
super().__init__()

self.base_freq = Parameter(220, lo=220/4, hi=440, key="q")
self.base_freq2 = Parameter(220/4, lo=220/16, hi=440, key="w")
self.bpm = Parameter(100, lo=10, hi=300, key="e", clip=True)
bpm_melody = self.bpm * 2
self.e =Parameter(2000, key="x")
self.melody_highs = StepSequencer(
SawSource,
self.base_freq,
bpm_melody,
melody=[1, 0, 12, 11, 8, 1],
#melody=[1, 2, 3, 4],
steps=[1],
gate='SSSH',
envelope=EnvelopeGenerator(self.e),
melody_randomizer=Parameter(0, knob="z")
)
self.out = DReverb(self.melody_highs, mix=0.8, decay="x")
# self.out = SimpleDelay(self.melody_highs, mix=0.7, time=7, feedback=0.6, hi_cut=0.6)


class Buttering(Module):
def __init__(self):
Expand All @@ -1101,6 +1258,7 @@ def __init__(self):
envelope=EnvelopeGenerator(self.e),
melody_randomizer=Parameter(0, knob="z")
)

self.melody_lows = self.melody_highs.copy(base_frequency=self.base_freq/2,
wave_generator_cls=SawSource,)

Expand All @@ -1118,6 +1276,7 @@ def __init__(self):
gate="SSSH")

self.filtered = ButterworthFilter(inp=self.melody+self.step_bass*2, f_low=P(0.01, key="g"), f_high=P(10000, key="h"), mode="bp")

self.out = self.filtered


Expand Down