Skip to content
Snippets Groups Projects
Commit 21984c93 authored by David Huss's avatar David Huss :speech_balloon:
Browse files

Add LFO Modes and more

This commit adds:
- A Square, Random and Jump LFO-Waveform
- A possibility to record the last active buffer into the new one
- Crude crossfading at the beginning and end of the loop
- Some minor fixes to reverb levels
- Fixes to the saturation function (was too silent before)
parent 11bd90c4
No related branches found
No related tags found
No related merge requests found
%% Cell type:code id:52c8bec3-db2d-4522-b692-b035a71410de tags:
``` python
from matplotlib import pyplot as plt
%matplotlib inline
import math
!{sys.executable} -m pip install clipboard
%matplotlib inline
import clipboard
def draw(r, g, b):
x = [x for x in range(256)]
fig = plt.figure(figsize=(8, 4))
ax = fig.add_axes([0, 0, 1, 1])
ax.axhline(y=0.5, color='black', linestyle='--')
ax.set_xticks(range(0, 256, 64))
ax.set_yticks(range(-256*2, 256*2+1, 128))
ax.grid()
ax.plot(x, r, 'r')
ax.plot(x, g, 'g')
ax.plot(x, b, 'b')
def r():
start = 100
start2 = 220
last_a = 0
for i in range(256):
if i < start:
yield 0
elif i < start2:
span = 255-start
d = (i-start)/span
last_a = int(d*30.0)
yield min(255, last_a)
else:
span = 255-start2
d = (i-start2)/span
d = d*d*d
yield min(255, last_a + int(d*350.0))
def g():
start = 0
end = 180-80
scale = 0.25
for i in range(256):
if i < start:
yield 0
elif i > end:
d = 1.0 - ((i-end)/(295-end))
# d = (d*d)/4 + d/2
yield max(0, min(255, int(d*175*scale)))
else:
d = ((i-start)/(255-start))
d = (d*d*d)
yield min(255, int(d*2800*scale))
def b():
start = 4
end = 40
scale = 0.2
for i in range(256):
if i < start:
yield 0
elif i > end:
d = (i-end)/(60)
d = d*d
d = 1.0 - d
# d = math.sqrt(d)
yield max(0, min(255, int(d*32*scale)))
else:
d = (i-start)/(255-start)
d = math.sqrt(d)/2 + d/2
yield max(0, min(255, int(d*107*scale)))
r, g, b = list(r()), list(g()), list(b())
draw(r, g, b)
text = ""
text += "// Lookup Table for Red LED Channel\n"
text += f"int red_lookup[] = {{{', '.join(str(v) for v in r)}}};\n\n"
text += "// Lookup Table for Green LED Channel\n"
text += f"int green_lookup[] = {{{', '.join(str(v) for v in g)}}};\n\n"
text += "// Lookup Table for Blue LED Channel\n"
text += f"int blue_lookup[] = {{{', '.join(str(v) for v in b)}}};\n\n"
import ipywidgets as widgets
from IPython.display import display, HTML, Javascript
mybtn = widgets.Button(description='copy C++ to clipboard', button_style='success')
def mybtn_event_handler(b):
print("copied")
clipboard.copy(text)
mybtn.on_click(mybtn_event_handler)
display(mybtn)
```
%% Output
zsh:1: parse error near `-m'
%% Cell type:code id:41562cc6-9911-4fb1-87ec-f2b3c8bfb3c2 tags:
``` python
from matplotlib import pyplot as plt
%matplotlib inline
import math
def draw(r):
l = len(r)
x = [x for x in range(l)]
fig = plt.figure(figsize=(8, 4))
ax = fig.add_axes([0, 0, 1, 1])
ax.axhline(y=0.5, color='black', linestyle='--')
ax.axvline(x=l/2, color='black', linestyle='--')
ax.set_xticks(range(0, l, 64))
ax.set_yticks(range(-l*2, l*2+1, 128))
ax.grid()
ax.plot(x, r)
def deadband(length, deadband=0.04):
readings = []
for i in range(length):
current_reading = i/(length-1)
scaler = (1.0) / (1.0 - deadband)
scaler += 0.1
if current_reading < 0.5:
current_reading += deadband
current_reading = min(0.5, current_reading)
current_reading = 0.5 - current_reading
current_reading *= scaler
current_reading = 0.5 - current_reading
# current_reading =
else:
current_reading -= deadband
current_reading = max(0.5, current_reading)
current_reading = 0.5 - current_reading
current_reading *= scaler
current_reading = 0.5 - current_reading
val = min(length, max(0, current_reading))
readings.append(val)
return readings
bip = deadband(16, deadband = 0.08)
draw(bip)
text = ""
text += "// Lookup Table for Bipolar Curve with deadband\n"
text += f"float bip_lookup[] = {{{', '.join(str(v) for v in bip)}}};\n\n"
import ipywidgets as widgets
from IPython.display import display, HTML, Javascript
mybtn = widgets.Button(description='copy C++ to clipboard', button_style='success')
def mybtn_event_handler(b):
print("copied")
clipboard.copy(text)
mybtn.on_click(mybtn_event_handler)
display(mybtn)
len(bip)
```
%% Output
16
%% Cell type:code id:ecc666b0-8195-4276-a576-39d41753b540 tags:
``` python
from matplotlib import pyplot as plt
%matplotlib inline
import math
import sys
def draw(r):
l = len(r)
x = [x for x in range(l)]
fig = plt.figure(figsize=(8, 4))
ax = fig.add_axes([0, 0, 1, 1])
ax.axhline(y=0.5, color='black', linestyle='--')
ax.set_xticks(range(0, l, 64))
ax.set_yticks(range(-l*2, l*2+1, 128))
ax.grid()
ax.plot(x, r)
def lin_to_log(length, strength=1.0):
# Limit to range 0.0 and 1.0
strength = min(1.0, max(0.0, strength))
readings = []
linear_readings = []
for i in range(length):
current_reading = i/length
linear_readings.append(current_reading)
# Log of 0 is error, so handle it explicitly
if i == 0:
current_reading = 0.0
else:
current_reading = math.log10(i)
readings.append(current_reading)
# Normalize to scale 0.1 to one
maxima = max(readings)
scaler = 1.0 / maxima
readings = [r*scaler for r in readings]
output = []
for i, r in enumerate(readings):
val = r*strength + linear_readings[i] * (1.0 - strength)
output.append(val)
# Convert to integer value range
output = [o for o in output]
return output
# lilo = lin_to_log(4096, strength=1.0)
lilo = lin_to_log(32, strength=1.0)
# lilo = [l/256.0 for l in lilo]
draw(lilo)
text = ""
text += "// Lookup Table for Logarithmic Curve\n"
text += f"float log_lookup[] = {{{', '.join(str(v) for v in lilo)}}};\n\n"
# print(text)
import ipywidgets as widgets
from IPython.display import display, HTML, Javascript
mybtn = widgets.Button(description='copy C++ to clipboard', button_style='success')
def mybtn_event_handler(b):
print("copied")
clipboard.copy(text)
mybtn.on_click(mybtn_event_handler)
display(mybtn)
min(lilo)
```
%% Output
0.0
%% Cell type:code id:2ecfda42-4a3c-489a-bc90-8576648c339c tags:
``` python
from matplotlib import pyplot as plt
%matplotlib inline
import math
import sys
def draw(r):
l = len(r)
x = [x for x in range(l)]
fig = plt.figure(figsize=(8, 4))
ax = fig.add_axes([0, 0, 1, 1])
ax.axhline(y=0.5, color='black', linestyle='--')
ax.set_xticks(range(0, l, 64))
ax.set_yticks(range(-l*2, l*2+1, 128))
ax.grid()
ax.plot(x, r)
def exp_lookup(length, strength=1.0):
# Limit to range 0.0 and 1.0
strength = min(1.0, max(0.0, strength))
readings = []
linear_readings = []
for i in range(length):
current_reading = i/length
linear_readings.append(current_reading)
# Log of 0 is error, so handle it explicitly
if i == 0:
current_reading = 0.0
else:
current_reading = i*i
readings.append(current_reading)
# Normalize to scale 0.1 to one
maxima = max(readings)
scaler = 1.0 / maxima
readings = [r*scaler for r in readings]
output = []
for i, r in enumerate(readings):
val = r*strength + linear_readings[i] * (1.0 - strength)
output.append(val)
# Convert to integer value range
output = [o for o in output]
return output
# lilo = lin_to_log(4096, strength=1.0)
lilo = exp_lookup(8, strength=1.0)
draw(lilo)
text = ""
text += "// Lookup Table for Exponential Curve\n"
text += f"float exp_lookup[] = {{{', '.join(str(v) for v in lilo)}}};\n\n"
# print(text)
import ipywidgets as widgets
from IPython.display import display, HTML, Javascript
mybtn = widgets.Button(description='copy C++ to clipboard', button_style='success')
def mybtn_event_handler(b):
print("copied")
clipboard.copy(text)
mybtn.on_click(mybtn_event_handler)
display(mybtn)
max(lilo)
```
%% Output
0.9999999999999999
%% Cell type:code id:e51416f3-f34d-4513-9f0c-fa52c468274e tags:
``` python
from matplotlib import pyplot as plt
%matplotlib inline
from ipywidgets import interact, FloatSlider
import matplotlib.transforms as transforms
import math
import clipboard
def draw(f):
fig = plt.figure(figsize=(8, 4))
ax = fig.add_axes([0, 0, 1, 1])
b = scan2d(x, y, f)
ax.axhline(y=b, color='red', linestyle='--')
ax.axvline(x=f, color='red', linestyle='--')
trans = transforms.blended_transform_factory(
ax.get_yticklabels()[0].get_transform(), ax.transData)
ax.text(0.95, b, "{:.02f}".format(b), color="red", transform=trans, ha="right", va="bottom")
ax.grid()
ax.plot(x, y)
def lerp(a, b, f=0.5) -> float:
f = min(1.0, max(0.0, f))
if f == 0.0:
return a
elif f == 1.0:
return b
else:
return a * (1.0-f) + b * f
def lerp2d(x1, y1, x2, y2, f=0.5):
if f == 0.0:
return [x1, x2]
elif f == 1.0:
return [x1, x2]
else:
x = lerp(x1, x2, f)
y = lerp(y1, y2, f)
return [x, y]
# A function that scans through two lists representing x/y values using a
# third value called f and returns the linear interpolation between those points
def scan2d(x, y, f):
# f = min(1.0, max(0.0, f))
assert len(x) == len(y)
# Find ax and bx for given factor
xa = None
last_value = None
idx = None
for i, v in enumerate(x):
# this = abs(f-v)
this = f-v
if xa is None or this > 0:
xa = this
idx = i
idx2 = min(idx+1, len(x)-1)
if idx == idx2:
return y[idx]
xa = x[idx]
xb = x[idx2]
ya = y[idx]
yb = y[idx2]
xspan = xb-xa
xscaler = 1/xspan
new_f = (f-xa)*xscaler
return lerp(ya, yb, new_f)
# lines_orig = [
# [0.0, 0.0, -0.5],
# [0.45, 0.5, 0.0],
# [0.55, 0.5, 1.0],
# [1.0, 1.0, 0.0],
# ]
# half_deadband = 0.005
# lines_orig = [
# [0.0, -1.0, 0.0],
# [0.0+half_deadband, -1.0, 0.0],
# ]
# steps = list(range(-9, 11))
# for i in steps:
# f = float(i)
# print(f/10)
# lines_orig.append([0.5+f/20-half_deadband, f/10, 0.0])
# lines_orig.append([0.5+f/20+half_deadband, f/10, 0.0])
db = 0.005
hb = db/2.0
lines_orig = [
[0.0, -1.0, 0.0], # -1000%
[0.0+hb, -1.0, 0.0], # -1000%
[0.02-hb, -0.9, 0.0], # -900%
[0.02+hb, -0.9, 0.0], # -900%
[0.04-hb, -0.8, 0.0], # -800%
[0.04+hb, -0.8, 0.0], # -800%
[0.06-hb, -0.7, 0.0], # -700%
[0.06+hb, -0.7, 0.0], # -700%
[0.08-hb, -0.6, 0.0], # -600%
[0.08+hb, -0.6, 0.0], # -600%
[0.1-hb, -0.5, 0.0], # -500%
[0.1+hb, -0.5, 0.0], # -500%
[0.12-hb, -0.4, 0.0], # -400%
[0.12+hb, -0.4, 0.0], # -400%
[0.14-hb, -0.3, 0.0], # -300%
[0.14+hb, -0.3, 0.0], # -300%
[0.16-hb, -0.2, 0.0], # -200%
[0.16+hb, -0.2, 0.0], # -200%
[0.2-hb, -0.1, 0.0], # -100%
[0.2+hb, -0.1, 0.0], # -100%
[0.25-hb, -0.05, 0.0], # -50%
[0.25+hb, -0.05, 0.0], # -50%
[0.3-hb, -0.025, 0.0], # -25%
[0.3+hb, -0.025, 0.0], # -25%
[0.38-hb, -0.0125, 0.0], # -12.5%
[0.38+hb, -0.0125, 0.0], # -12.5%
[0.42-hb, -0.00625, 0.0], # -6.25%
[0.42+hb, -0.00625, 0.0], # -6.25%
[0.46-hb, -0.003125, 0.0], # -3.125%
[0.46+hb, -0.003125, 0.0], # -3.125%
[0.5-hb, 0.0, 0.0], # 0%
[0.5+hb, 0.0, 0.0], # 0%
[1.0-0.46-hb, 0.003125, 0.0], # 3.125%
[1.0-0.46+hb, 0.003125, 0.0], # 3.125%
[1.0-0.42-hb, 0.00625, 0.0], # 6.25%
[1.0-0.42+hb, 0.00625, 0.0], # 6.25%
[1.0-0.38-hb, 0.0125, 0.0], # 12.5%
[1.0-0.38+hb, 0.0125, 0.0], # 12.5%
[1.0-0.3-hb, 0.025, 0.0], # 25%
[1.0-0.3+hb, 0.025, 0.0], # 25%
[1.0-0.25-hb, 0.05, 0.0], # 50%
[1.0-0.25+hb, 0.05, 0.0], # 50%
[1.0-0.2-hb, 0.1, 0.0], # 100%
[1.0-0.2+hb, 0.1, 0.0], # 100%
[1.0-0.16-hb, 0.2, 0.0], # 200%
[1.0-0.16+hb, 0.2, 0.0], # 200%
[1.0-0.14-hb, 0.3, 0.0], # 300%
[1.0-0.14+hb, 0.3, 0.0], # 300%
[1.0-0.12-hb, 0.4, 0.0], # 400%
[1.0-0.12+hb, 0.4, 0.0], # 400%
[1.0-0.1-hb, 0.5, 0.0], # 500%
[1.0-0.1+hb, 0.5, 0.0], # 500%
[1.0-0.08-hb, 0.6, 0.0], # 600%
[1.0-0.08+hb, 0.6, 0.0], # 600%
[1.0-0.06-hb, 0.7, 0.0], # 700%
[1.0-0.06+hb, 0.7, 0.0], # 700%
[1.0-0.04-hb, 0.8, 0.0], # 800%
[1.0-0.04+hb, 0.8, 0.0], # 800%
[1.0-0.02-hb, 0.9, 0.0], # 900%
[1.0-0.02+hb, 0.9, 0.0], # 900%
[1.0-0.0-hb, 1.0, 0.0], # 1000%
[1.0-0.0, 1.0, 0.0], # 1000%
]
# Calculate curves for points of curvature
def make_lines(lines_orig, resolution=20):
lines = []
for i, l in enumerate(lines_orig):
i2 = min(len(lines_orig)-1, i+1)
if l[2] == 0.0:
lines.append(l)
else:
xa = lines_orig[i][0]
xb = lines_orig[i2][0]
ya = lines_orig[i][1]
yb = lines_orig[i2][1]
x_span = xb-xa
y_span = yb-ya
x_step = 1/20
y_step = 1/20
for j in range(resolution):
x = x_step * j
y = y_step * j
y_curve = 0
if l[2] > 0.0:
y_curve = y*y*y
else:
y_curve = y*y
y = (1.0-l[2]) * y + l[2] * y_curve
lines.append([xa+x*x_span, ya+y*y_span, 0.0])
return lines
lines = make_lines(lines_orig, 20)
x = [a[0] for a in lines]
y = [a[1] for a in lines]
c = [a[2] for a in lines]
# draw(x, y, 0.45/2)
interact(draw, f=FloatSlider(min=min(x), max=max(x), step=0.001, value=0.2))
length = len(x)
text = ""
text += "// Lookup Table for Pitch Knob\n"
text += f"float pitch_knob_lookup_x[] = {{{', '.join(str(xv) for xv in x)}}};\n"
text += f"float pitch_knob_lookup_y[] = {{{', '.join(str(yv) for yv in y)}}};\n"
text += f"size_t pitch_knob_lookup_length = {length};\n"
import ipywidgets as widgets
from IPython.display import display, HTML, Javascript
mybtn = widgets.Button(description='copy C++ to clipboard', button_style='success')
def mybtn_event_handler(b):
print("copied")
clipboard.copy(text)
mybtn.on_click(mybtn_event_handler)
display(mybtn)
```
%% Output
%% Cell type:code id:f35f1609-3a10-4dce-b7dd-201d79f2c39c tags:
``` python
# Saturation curve
# X / Y / Curvature
lines_orig = [
[-1.5, -1.0, 1.0],
[-0.7, -0.7, 0.0],
[-0.9, -0.8, 0.0],
[0.0, 0.0, 0.0],
[0.7, 0.7, -1.4],
[0.9, 0.8, -1.1],
[1.5, 1.0, 0.0],
]
lines = make_lines(lines_orig, 20)
lines = make_lines(lines_orig, 8)
x = [a[0] for a in lines]
y = [a[1]*0.5 for a in lines]
y = [min(1.0, a[1]) for a in lines]
c = [a[2] for a in lines]
# draw(x, y, 0.45/2)
interact(draw, f=FloatSlider(min=min(x), max=max(x), step=0.001, value=0.2))
length = len(x)
text = ""
text += "// Lookup Curves for Saturation b\n"
text += f"float saturation_lookup_x[] = {{{', '.join(str(xv) for xv in x)}}};\n"
text += f"float saturation_lookup_y[] = {{{', '.join(str(yv) for yv in y)}}};\n"
text += f"size_t saturation_lookup_length = {length};\n"
import ipywidgets as widgets
from IPython.display import display, HTML, Javascript
mybtn = widgets.Button(description='copy C++ to clipboard', button_style='success')
def mybtn_event_handler(b):
print("copied")
clipboard.copy(text)
mybtn.on_click(mybtn_event_handler)
display(mybtn)
print(len(x))
```
%% Output
19
%% Cell type:code id:f0cd9d06-a15a-46b4-a3ef-4aee3d4f7cd0 tags:
``` python
# X / Y / Curvature
def draw_rgb(xr, yr, xg, yg, xb, yb):
fig = plt.figure(figsize=(8, 4))
ax = fig.add_axes([0, 0, 1, 1])
ax.grid()
ax.plot(xr, yr, 'r')
ax.plot(xg, yg, 'g')
ax.plot(xb, yb, 'b')
# BLUE
lines_b = [
[0, 0, 0.8],
[40, 10, -1.0],
[170, 0, 1.0],
]
lines_b = make_lines(lines_b, 16)
xb = [a[0] for a in lines_b]
yb = [a[1] for a in lines_b]
# Green
lines_g = [
[10, 0, 1.0],
[180, 60, -0.2],
[250, 0, 0.0],
]
lines_g = make_lines(lines_g, 16)
xg = [a[0] for a in lines_g]
yg = [a[1] for a in lines_g]
# RED
lines_r = [
[170, 0, 1.0],
[240, 30, 0.9],
[255, 255, 0.0],
]
lines_r = make_lines(lines_r, 16)
xr = [a[0] for a in lines_r]
yr = [a[1] for a in lines_r]
draw_rgb(xr, yr, xg, yg, xb, yb)
length = len(xr)
text = ""
text += "// Lookup Curves LED Red b\n"
text += f"float red_lut_x[] = {{{', '.join(str(xv) for xv in xr)}}};\n"
text += f"float red_lut_y[] = {{{', '.join(str(yv) for yv in yr)}}};\n"
text += f"size_t red_lut_len = {length};\n\n"
length = len(xg)
text += "// Lookup Curves LED Green b\n"
text += f"float green_lut_x[] = {{{', '.join(str(xv) for xv in xg)}}};\n"
text += f"float green_lut_y[] = {{{', '.join(str(yv) for yv in yg)}}};\n"
text += f"size_t green_lut_len = {length};\n\n"
length = len(xb)
text += "// Lookup Curves LED Blue b\n"
text += f"float blue_lut_x[] = {{{', '.join(str(xv) for xv in xb)}}};\n"
text += f"float blue_lut_y[] = {{{', '.join(str(yv) for yv in yb)}}};\n"
text += f"size_t blue_lut_len = {length};\n\n"
import ipywidgets as widgets
from IPython.display import display, HTML, Javascript
mybtn = widgets.Button(description='copy C++ to clipboard', button_style='success')
def mybtn_event_handler(b):
print("copied")
clipboard.copy(text)
mybtn.on_click(mybtn_event_handler)
display(mybtn)
```
%% Output
%% Cell type:code id:59b56dbc-6852-4989-bc19-3525ee7caf8b tags:
``` python
```
%% Output
1.0
%% Cell type:code id:5a511d24-cc91-450f-83e4-8295647d9391 tags:
``` python
```
......
......@@ -12,6 +12,7 @@
#include "helpers.h"
#include "luts.h"
#include "ui.h"
#include "lfo.h"
MIDI_CREATE_DEFAULT_INSTANCE();
#define BUFFER_LENGTH_SECONDS 5
......@@ -84,8 +85,9 @@ float reverb_tone = 15000.0f;
float reverb_decay = 0.95f;
float lfo_speed = 8.0f;
int lfo_kind = 0;
float rand_pitch_mod = 0.0f;
float pressure = 0.0f;
//float pressure = 0.0f;
// Actual audio-processing is orchestrated here
void AudioCallback(float **in, float **out, size_t size) {
......@@ -98,10 +100,33 @@ void AudioCallback(float **in, float **out, size_t size) {
// Iterate through the samples in the buffer
for (size_t i = 0; i < size; i++) {
float lfo_value = 0.0f;
switch (lfo_kind) {
case LfoKind::LFO_KIND_TRI:
lfo.SetWaveform(Oscillator::WAVE_TRI);
lfo_value = lfo.Process();
break;
case LfoKind::LFO_KIND_SQR:
lfo.SetWaveform(Oscillator::WAVE_SQUARE);
lfo_value = lfo.Process();
break;
default:
break;
}
tick.SetFreq(0.25f+lfo_speed*49.75f);
uint8_t trig = tick.Process();
float lfo_value = lfo.Process();
float noise_value = noise.Process();
switch (lfo_kind) {
case LfoKind::LFO_KIND_RAND:
easer.setFactor(0.01);
break;
case LfoKind::LFO_KIND_JUMP:
easer.setFactor(1.0);
break;
}
float rand = easer.Process(
sample_and_hold.Process(
trig,
......@@ -114,21 +139,36 @@ void AudioCallback(float **in, float **out, size_t size) {
}
// When the metro ticks, trigger the envelope to start.
float random_amount = lfo_amount * 2.0;
if (trig) {
// Random LFO
if (lfo_kind == 1) {
switch (lfo_kind) {
case LfoKind::LFO_KIND_RAND:
rand_pitch_mod = rand * random_amount * 5.0f;
break;
case LfoKind::LFO_KIND_JUMP:
// Chance
if (random(0.0f, 1.0f) < lfo_amount) {
ui.activeLooper()->addToPlayhead(rand * random_amount * 48000.0f);
if (drand(0.0f, 1.0f) < lfo_amount) {
ui.activeLooper()->addToPlayhead(rand * random_amount * 8000.0f);
}
break;
default:
break;
}
}
// Add the LFO to the signal if it is active
if (lfo_kind == 0) {
switch (lfo_kind) {
case LfoKind::LFO_KIND_TRI:
case LfoKind::LFO_KIND_SQR:
ui.activeLooper()->setPlaybackSpeed(pitch_val + lfo_value * lfo_amount + midi_pitch_offset);
} else {
break;
case LfoKind::LFO_KIND_RAND:
ui.activeLooper()->setPlaybackSpeed(pitch_val + rand_pitch_mod + midi_pitch_offset);
break;
default:
ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset);
break;
}
......@@ -138,6 +178,11 @@ void AudioCallback(float **in, float **out, size_t size) {
ui.activeLooper()->Record(in[1][i]);
}
if (ui.rec_source == REC_SOURCE_LAST_BUF) {
// FIXME: This might process the previous looper twice later below, add check...
ui.activeLooper()->Record(ui.previousLooper()->Process());
}
// Process the input envelope
input_envelope_follower.Process(in[1][i]);
......@@ -145,7 +190,7 @@ void AudioCallback(float **in, float **out, size_t size) {
switch (ui.buffer_summing_mode) {
case BUFFER_SUM_MODE_SOLO:
// Only play active looper
looper_out = saturate(ui.activeLooper()->Process());
looper_out = ui.activeLooper()->Process();
break;
case BUFFER_SUM_MODE_SUM:
// Sum all loopers
......@@ -154,7 +199,7 @@ void AudioCallback(float **in, float **out, size_t size) {
looper_out += looper_c.Process();
looper_out += looper_d.Process();
looper_out += looper_e.Process();
looper_out = saturate(looper_out);
looper_out = looper_out;
break;
case BUFFER_SUM_MODE_RING:
// Sum all loopers and ringmodulate with input
......@@ -170,12 +215,12 @@ void AudioCallback(float **in, float **out, size_t size) {
deadbanded_input = max(0.0f, in[1][i] - 0.05f);
}
looper_out *= deadbanded_input*2.0f;
looper_out = saturate(looper_out);
looper_out = looper_out;
break;
}
// looper_out = pressure * looper_out;
looper_out = volume * looper_out;
looper_out = saturate(volume * looper_out);
// Mix the dry/Wet of the looper
output = drywetmix * looper_out + in[1][i] * (1.0f - drywetmix);
......@@ -187,7 +232,8 @@ void AudioCallback(float **in, float **out, size_t size) {
reverb.Process(output, output, &out1, &out2);
// Short decays are silent, so increase level here
out1 = out1 * map(reverb_decay, 0.0f, 1.0f, 2.0f, 1.0f);
float dec_fac = 1.0f + (1.0f - reverb_decay) * 2.0f;
out1 = out1 * dec_fac;
// Mix reverb with the dry signal depending on the amount dialed
output = output * (1.0f - reverbmix) + out1 * reverbmix;
......@@ -238,16 +284,16 @@ void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) {
midi_pitch_offset = 0.0f;
}
void handleAftertouch(byte channel, byte channel_pressure) {
#ifdef DEBUGMODE
Serial.print("[MIDI AFTER] chn<");
Serial.print((int) channel);
Serial.print("> pressure<");
Serial.print((int) channel_pressure);
Serial.println(">");
#endif
pressure = float(channel_pressure)/127.0f;
}
// void handleAftertouch(byte channel, byte channel_pressure) {
// #ifdef DEBUGMODE
// Serial.print("[MIDI AFTER] chn<");
// Serial.print((int) channel);
// Serial.print("> pressure<");
// Serial.print((int) channel_pressure);
// Serial.println(">");
// #endif
// pressure = float(channel_pressure)/127.0f;
// }
......@@ -261,6 +307,7 @@ void setup() {
blocksize = 64.0f;
// Create a Tick and a noise source for the Sample and Hold
tick.SetFreq(1.0f+lfo_amount*99.0f);
tick.Init(10, sample_rate);
noise.Init();
......@@ -313,14 +360,14 @@ void setup() {
// Setup MIDI handlers
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
MIDI.setHandleAfterTouchChannel(handleAftertouch);
// MIDI.setHandleAfterTouchChannel(handleAftertouch);
MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages
// Set Knob names and display functions
pot_1.name = "Start";
pot_2.name = "Length";
pot_3.setDisplayMode("Pitch", 1000.0f, POT_DISPLAY_MODE_PERCENT);
pot_3.setDisplayMode("Speed", 1000.0f, POT_DISPLAY_MODE_PERCENT);
pot_4.setDisplayMode("Mix", 100.0f, POT_DISPLAY_MODE_PERCENT);
pot_5.setDisplayMode("LFO", 100.0f, POT_DISPLAY_MODE_PERCENT);
pot_6.setDisplayMode("Volume", 400.0f, POT_DISPLAY_MODE_PERCENT);
......
......@@ -139,4 +139,20 @@ int button_multi(const char *buf, int x, int y, int color, int underline_line=0,
return w;
}
float saturate(float x) {
if (x < -3.0f) {
return -1.0f;
} else if (x > 3.0f) {
return 1.0f;
} else {
return x * (27.0f + x * x ) / (27.0f + 9.0f * x * x);
}
}
double drand(double minf, double maxf){
return minf + random(1UL << 31) * (maxf - minf) / (1UL << 31); // use 1ULL<<63 for max double values)
}
#endif
\ No newline at end of file
#pragma once
enum LfoKind {
LFO_KIND_TRI,
LFO_KIND_SQR,
LFO_KIND_RAND,
LFO_KIND_JUMP,
LFO_KIND_NONE,
};
\ No newline at end of file
......@@ -6,7 +6,6 @@ namespace atoav {
enum RecPitchMode {
REC_PITCH_MODE_NORMAL,
REC_PITCH_MODE_PITCHED,
REC_PITCH_MODE_UNPITCHED,
REC_PITCH_MODE_LAST,
};
......@@ -236,11 +235,6 @@ void Looper::Record(float in) {
case REC_PITCH_MODE_UNPITCHED:
rec_head.setIncrement(playheads[0].increment);
break;
case REC_PITCH_MODE_PITCHED:
if (playheads[0].increment != 0.0) {
rec_head.setIncrement(1.0f/playheads[0].increment);
}
break;
}
// Increment recording head
rec_head.update();
......@@ -307,32 +301,41 @@ float Looper::Process() {
if (!playheads[i].isActive()) continue;
// Ensure we are actually inside the buffer
int play_pos = int(loop_start + playheads[i].read()) % buffer_length;
int from_start = int(playheads[i].read()) % loop_length;
int play_pos = loop_start + from_start;
float vol = 1.0f;
if (from_start <= kFadeLength) {
vol = from_start / float(kFadeLength);
}
int from_end = abs(int(loop_length) - from_start);
if (from_end <= kFadeLength) {
vol = from_end / float(kFadeLength);
}
// Read from the buffer
mix += buffer[play_pos];
mix += buffer[play_pos] * vol;
// Advance the playhead
playheads[i].update();
// Ensure the playhead stays within bounds of the loop
if ((playheads[i].read()) >= loop_length) {
playheads[i].setPosition(0.0f);
} else if (playheads[i].read() <= 0.0f) {
playheads[i].setPosition(loop_length);
float pos = playheads[i].read();
if (pos >= loop_length || pos <= 0.0f) {
playheads[i].setPosition(fmod(pos, float(loop_length)));
}
}
return saturate(mix);
return mix;
}
float Looper::GetPlayhead() {
return float(playheads[0].read()) / float(buffer_length);
return float(int(playheads[0].read()) % loop_length) / float(buffer_length);
}
float* Looper::GetPlayheads() {
static float playhead_positions[9];
for (size_t i=0; i<9; i++) {
playhead_positions[i] = float(playheads[i].read()) / float(buffer_length);
playhead_positions[i] = float(int(playheads[i].read()) % loop_length) / float(buffer_length);
}
return playhead_positions;
}
......
......@@ -13,11 +13,6 @@ float pitch_knob_lookup_y[] = {-1.0, -1.0, -0.9, -0.9, -0.8, -0.8, -0.7, -0.7, -
size_t pitch_knob_lookup_length = 62;
// Lookup Curves for Saturation b
float saturation_lookup_x[] = {-1.5, -1.46, -1.42, -1.38, -1.3399999999999999, -1.3, -1.26, -1.22, -0.7, 0.0, 0.7, 0.74, 0.78, 0.82, 0.86, 0.8999999999999999, 0.94, 0.98, 1.5};
float saturation_lookup_y[] = {-0.5, -0.49998125, -0.49985, -0.49949375, -0.4988, -0.49765625, -0.49595, -0.49356875, -0.35, 0.0, 0.35, 0.367475, 0.38389999999999996, 0.399275, 0.41359999999999997, 0.426875, 0.4391, 0.450275, 0.5};
size_t saturation_lookup_length = 19;
class Easer {
float output = 0.0f;
......@@ -67,13 +62,5 @@ float get_from_xy_table(float* xtable, float* ytable, float f, size_t length) {
return multiMap<float>(f, xtable, ytable, length);
}
float saturate(float input) {
return get_from_xy_table(
saturation_lookup_x,
saturation_lookup_y,
min(1.5f, max(-1.5f, input)),
saturation_lookup_length
);
}
#endif
\ No newline at end of file
......@@ -69,7 +69,7 @@ class Potentiometer {
bool last_was_nan = false;
uint8_t switch_positions;
uint8_t switch_offset = 0;
const char* const switch_labels[2] = {"LFO", "RANDOM"};
const char* const switch_labels[4] = {"TRI", "SQR", "RAND", "JUMP"};
};
Potentiometer::Potentiometer(int pin) {
......
......@@ -46,7 +46,7 @@ enum RecMode {
// Represents possible recording sources
enum RecSource {
REC_SOURCE_PRE, // Record Incoming audio
REC_SOURCE_POST, // Record effects
REC_SOURCE_LAST_BUF, // Record Last selected Buffer
REC_SOURCE_OUT, // Record the buffer output
REC_SOURCE_NOISE, // Record Noise
REC_SOURCE_LAST
......@@ -104,11 +104,11 @@ class Ui {
Ui() : button_grids {
ButtonGrid((int) UI_MODE_REC_MENU, {
GridButton("REC\nMENU", &button_1, true),
GridButton("MOM\nTOGGLE", &button_2, false, BUTTON_TYPE_TOGGLE, 0, "REC/OVERDUB BUTTONS\n\nMOM->Record while \n Button is held\n\nTOGGLE->Press once to\n start, press \n again to stop"),
GridButton("PRE\nPOST\nOUT\nNOISE", &button_3, false, BUTTON_TYPE_MULTITOGGLE, 0, "REC/OVERDUB SOURCE\n\nPRE----->Direct Input\nPOST---->With Effects\nOUT---->Looper Output\nNOISE--->Noise Source"),
GridButton("FULL\nLOOP\nSHOT", &button_4, false, BUTTON_TYPE_MULTITOGGLE, 1, "RECORDING REGION\n\nFULL---->Whole Buffer\nLOOP----->Loop Bounds\nSHOT->Full Buffer but\n stop at the end"),
GridButton("NORMAL\nPITCHD\nUNPTCH", &button_5, false, BUTTON_TYPE_MULTITOGGLE, 0, "SPEED OF THE REC HEAD\n\nNORMAL--->Fixed Speed\nPITCHD->Inverse Playh\nUNPITCH----->Playhead\n Speed"),
GridButton("START\nLOOPST\nPLAYHD", &button_6, false, BUTTON_TYPE_MULTITOGGLE, 0, "START RECORDING AT\n\nSTART--->Start of the\n Buffer\nLOOP---->Start of the\n Loop\nPLAYH--->Position of\n the Playhead"),
GridButton("MOM\nTOGGLE", &button_2, false, BUTTON_TYPE_TOGGLE, 0),
GridButton("PRE\nLAST\nOUT\nNOISE", &button_3, false, BUTTON_TYPE_MULTITOGGLE, 0),
GridButton("FULL\nLOOP\nSHOT", &button_4, false, BUTTON_TYPE_MULTITOGGLE, 1),
GridButton("NORMAL\nUNPTCH", &button_5, false, BUTTON_TYPE_MULTITOGGLE, 0),
GridButton("START\nLOOPST\nPLAYHD", &button_6, false, BUTTON_TYPE_MULTITOGGLE, 0),
}),
ButtonGrid((int) UI_MODE_PLAY_MENU, {
GridButton("STOP\nLOOP\nMULTI\nMIDI", &button_1, false, BUTTON_TYPE_MULTITOGGLE, 1),
......@@ -158,6 +158,7 @@ class Ui {
// Default active buffer
ActiveBuffer active_buffer = ACTIVE_BUFFER_A;
ActiveBuffer previous_buffer = ACTIVE_BUFFER_A;
// Default active summing mode
BufferSummingMode buffer_summing_mode = BUFFER_SUM_MODE_SOLO;
......@@ -208,28 +209,6 @@ class Ui {
// Renders a splash screen (runs once)
void renderSplash() {
display.setTextSize(1);
if (show_splash) {
display.setTextColor(SH110X_BLACK);
// Play a fancy intro splash screen
for (int i=0; i < 91; i++) {
display.clearDisplay();
display.fillCircle(display.width()/2, display.height()/2, i, SH110X_WHITE);
display.fillCircle(display.width()/2, display.height()/2, max(0, i-2), SH110X_BLACK);
if (i < 50) {
centeredText("DAISYY", display.width()/2, display.height()/2-4, SH110X_WHITE);
centeredText("LOOPER", display.width()/2, display.height()/2+4, SH110X_WHITE);
}
display.display();
delay(1);
}
display.invertDisplay(true);
display.display();
delay(800);
display.clearDisplay();
display.invertDisplay(false);
display.display();
}
// Splash rendering is now done, go to next UI Mode
setMode(UI_MODE_DEFAULT);
......@@ -354,22 +333,27 @@ class Ui {
Button* home_button = setupButtonGrid(n);
button_1.onPress([this, n](){
previous_buffer = active_buffer;
active_buffer = ACTIVE_BUFFER_A;
waveform_cache_dirty = true;
});
button_2.onPress([this, n](){
previous_buffer = active_buffer;
active_buffer = ACTIVE_BUFFER_B;
waveform_cache_dirty = true;
});
button_3.onPress([this, n](){
previous_buffer = active_buffer;
active_buffer = ACTIVE_BUFFER_C;
waveform_cache_dirty = true;
});
button_4.onPress([this, n](){
previous_buffer = active_buffer;
active_buffer = ACTIVE_BUFFER_D;
waveform_cache_dirty = true;
});
button_5.onPress([this, n](){
previous_buffer = active_buffer;
active_buffer = ACTIVE_BUFFER_E;
waveform_cache_dirty = true;
});
......@@ -443,7 +427,7 @@ class Ui {
fx_mode = FX_MODE_LFO;
pot_5.setDisplayMode("LFO Mode", 100.0f, POT_DISPLAY_MODE_SWITCH);
pot_5.setSwitch();
pot_5.switch_positions = 2;
pot_5.switch_positions = 4;
pot_5.switch_offset = 0;
pot_6.setDisplayMode("LFO Speed", 100.0f, POT_DISPLAY_MODE_PERCENT);
pot_7.setDisplayMode("LFO Amount", 100.0f, POT_DISPLAY_MODE_PERCENT);
......@@ -818,6 +802,40 @@ class Ui {
return ptr;
}
// Returns a pointer to the currently active looper
atoav::Looper * previousLooper() {
switch(previous_buffer) {
case ACTIVE_BUFFER_A: {
atoav::Looper * ptr = &looper_a;
return ptr;
break;
}
case ACTIVE_BUFFER_B: {
atoav::Looper * ptr = &looper_b;
return ptr;
break;
}
case ACTIVE_BUFFER_C: {
atoav::Looper * ptr = &looper_c;
return ptr;
break;
}
case ACTIVE_BUFFER_D: {
atoav::Looper * ptr = &looper_d;
return ptr;
break;
}
case ACTIVE_BUFFER_E: {
atoav::Looper * ptr = &looper_e;
return ptr;
break;
}
}
// Unreachable, but makes the compiler shut up
atoav::Looper * ptr = &looper_a;
return ptr;
}
// Set the Looper start/length to a given value
void setLoop(float start, float length) {
activeLooper()->SetLoop(start, length);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment