Skip to content

Commit 30b65c6

Browse files
committed
tbish: wired up accent param better, tweaked distortion, added saw2 wave
1 parent 6441934 commit 30b65c6

File tree

5 files changed

+119
-49
lines changed

5 files changed

+119
-49
lines changed

circuitpython/tbish/code.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
from synth_setup_pts import (mixer, knobA, knobB, keys,
2020
setup_display, setup_touch, check_touch)
2121

22-
from param_set import ParamSet, Param
22+
from paramset import ParamSet, Param
2323

24-
from tbish_synth import TBishSynth
24+
from tbish_synth import TBishSynth, waves
2525
from tbish_sequencer import TBishSequencer
2626
from tbish_ui import TBishUI
2727

@@ -35,6 +35,9 @@
3535
[[36, 36, 48, 36, 48, 48+7, 36, 48], # notes, 0 = rest
3636
[127, 80, 80, 80, 127, 1, 30, 1]], # vels, 1=slide, 127=accent
3737

38+
[[36, 48, 36, 48, 36, 48, 36, 48], # notes, 0 = rest
39+
[127, 80, 1, 1, 127, 1, 30, 1]], # vels, 1=slide, 127=accent
40+
3841
[[34, 36, 34, 36, 48, 48, 36, 48], # notes, 0 = rest
3942
[127, 80, 120, 80, 127, 11, 127, 80]], # vels 127=accent
4043

@@ -50,37 +53,41 @@
5053

5154
params = [
5255
# name val, min, max, str, tb.attr name
53-
Param("cutoff", 4000, 100, 6000, "%4d", 'cutoff'),
56+
Param("cutoff", 3000, 100, 5000, "%4d", 'cutoff'),
5457
Param('envmod', 0.5, 0.0, 1.0, "%.2f",'envmod'),
5558

5659
Param("resQ", 1.0, 0.5, 4.0, "%.2f", 'resonance'),
5760
Param('decay', 0.75, 0.0, 1.0, "%.2f", 'decay'),
5861

59-
Param('drive', 30, 5, 40, "%2d", 'drive'),
62+
63+
Param('accent', 0.2, 0.0, 1.0, "%.2f", 'accent'),
64+
Param('wave', 0, 0, len(waves)-1, "%1d", 'wavenum'),
65+
66+
Param('drive', 0, 0.0, 1.0, "%.2f", 'drive'),
6067
Param('drivemix', 0.2, 0.0, 1.0, "%.2f", 'drive_mix'),
61-
62-
Param('delay', 0.0, 0.0, 1.0, "%.2f", 'delay_mix'),
68+
6369
Param('dtime', 0.25, 0.0, 1.0, "%.2f", 'delay_time'),
70+
Param('delaymix', 0.0, 0.0, 1.0, "%.2f", 'delay_mix'),
6471

65-
Param('seq', 0, 0, len(seqs), "%1d"),
66-
Param('transpose', 0, -13, 13, "%2d", 'transpose'),
6772

68-
Param('wave', 1, 0, 2, "%1d", 'wavenum'),
73+
Param('transpose', 0, -13, 13, "%2d", 'transpose'),
74+
Param('what', 0.0, 0.0, 1.0, "%.2f", ),
75+
76+
Param('seq', 0, 0, len(seqs)-1, "%1d"),
6977
Param('bpm', bpm, 40, 200, "%3d"),
7078

7179
]
7280

73-
touchpad_to_knobset = [1,3,6,8,10,13]
81+
touchpad_to_knobset = [1,3,6,8,10,13,15]
7482
touchpad_to_transpose = [0,2,4,5,7,9,11,12,14]
7583

7684
tb = TBishSynth(mixer.sample_rate, mixer.channel_count)
7785
tb_audio = tb.add_audioeffects()
7886
mixer.voice[0].play(tb_audio)
7987

80-
sequencer = TBishSequencer(tb, seqs)
88+
sequencer = TBishSequencer(tb, seqs=seqs)
8189
sequencer.bpm = bpm
8290
sequencer.steps_per_beat = steps_per_beat
83-
8491
param_set = ParamSet(params, num_knobs=2, knob_smooth=0.125)
8592
param_set.apply_params(tb) # set up synth with param set
8693

@@ -122,6 +129,7 @@ def update_ui():
122129

123130
tb_disp.update_param_pairs()
124131

132+
sequencer.on_step_callback = tb_disp.show_beat
125133
sequencer.start()
126134

127135
while True:

circuitpython/tbish/param_set.py renamed to circuitpython/tbish/paramset.py

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818

1919
import json
2020

21-
KNOB_MODE_PICKUP = 0
22-
KNOB_MODE_SCALE = 1
23-
KNOB_MODE_RELATIVE = 2
24-
2521
class Param:
2622
"""Params are a UI- and implementation-independent way of describing
2723
a named numerical parameter with a min/max, a display format, and
@@ -64,12 +60,19 @@ def apply_to_obj(self,o):
6460
setattr(o, self.objattr, self.val)
6561

6662
class ParamSet:
63+
64+
KNOB_PICKUP = 0
65+
KNOB_SCALE = 1
66+
KNOB_RELATIVE = 2
67+
6768
"""ParamSet is a collection of Params that track normalized knob positions,
6869
especially for the case when there are fewer knobs than Params.
6970
"""
7071

71-
def __init__(self, params, num_knobs, min_knob_change=0.05, knob_smooth=0.5):
72+
def __init__(self, params, num_knobs, min_knob_change=0.05,
73+
knob_smooth=0.5, knob_mode = KNOB_PICKUP):
7274
self.params = params
75+
self.knob_mode = knob_mode
7376
self.nparams = len(params)
7477
self.nknobs = num_knobs
7578
self.min_change = min_knob_change
@@ -95,17 +98,50 @@ def idx(self, i):
9598
self._idx = i
9699

97100
def update_knobs(self, new_knob_vals):
98-
"""new_knob_val is list of new knob vals, each 0.0-1.0"""
101+
if self.knob_mode == ParamSet.KNOB_PICKUP:
102+
self.update_knobs_pickup(new_knob_vals)
103+
elif self.knob_mode == ParamSet.KNOB_SCALE:
104+
self.update_knobs_scale(new_knob_vals)
105+
106+
def update_knobs_pickup(self, new_knob_vals):
107+
"""new_knob_vals is list of new knob vals, each 0.0-1.0"""
99108
for i in range(self.nknobs):
100109
param = self.params[ (self._idx * self.nknobs) + i ]
101110
new_val = param.knob_to_val(new_knob_vals[i])
102111
if self.is_tracking[i]:
103-
param.val = self.smoothing * new_val + (1-self.smoothing) * param.val
112+
# only change param val if difference is big enough FIXME
113+
if abs(new_val - param.val) >= 0.1 * self.min_change * param.span:
114+
param.val = new_val
104115
else:
105116
delta = param.val - new_val
106117
if abs(delta) < self.min_change * param.span:
107118
self.is_tracking[i] = True
108-
#param.val = new_val
119+
120+
def update_knobs_scale(self, new_knob_vals):
121+
"""new_knob_val is list of new knob vals, each normalized 0.0-1.0"""
122+
# note this sucks currently
123+
for i in range(self.nknobs):
124+
param = self.params[ (self._idx * self.nknobs) + i ]
125+
new_val = param.knob_to_val(new_knob_vals[i])
126+
delta_val = new_val - param.val
127+
128+
val_min, val_max = param.vmin, param.vmax
129+
knob_min, knob_max = 0.0, 1.0
130+
131+
val_max_pos_delta = val_max - param.val
132+
val_min_pos_delta = param.val - val_min
133+
knob_max_pos_delta = val_max - new_val
134+
knob_min_pos_delta = new_val - val_min
135+
136+
if delta_val > 0 and knob_max_pos_delta != 0:
137+
val_percent_change = delta_val * val_max_pos_delta / knob_max_pos_delta
138+
elif delta_val < 0 and knob_min_pos_delta != 0:
139+
val_percent_change = delta_val * val_min_pos_delta / knob_min_pos_delta
140+
else:
141+
val_percent_change = 0
142+
143+
param.val = min(max(param.val + val_percent_change, val_min), val_max)
144+
109145

110146
def apply_params(self, obj):
111147
""" Apply all params to given object """

circuitpython/tbish/tbish_sequencer.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import time
22

33
class TBishSequencer:
4-
def __init__(self, tb, seqs):
4+
def __init__(self, tb, steps_per_beat=4, bpm=120, seqs=None):
55
self.tb = tb
66
self.seqs = seqs
7-
self._steps_per_beat = 4 # 4 = 16th note, 2 = 8th note, 1 = quarter note
8-
self.bpm = 120
7+
self._steps_per_beat = steps_per_beat # 4 = 16th note, 2 = 8th note, 1 = quarter note
8+
self.bpm = bpm
99
self.gate_time = 0
1010
self.gate_amount = 0.75 # traiditional 303 gate length
1111
self.next_step_time = 0
@@ -16,6 +16,7 @@ def __init__(self, tb, seqs):
1616
self.transpose = 0
1717
self.i = 0 # step number
1818
self.playing = True
19+
self.on_step_callback = None
1920

2021
@property
2122
def steps_per_beat(self):
@@ -43,10 +44,9 @@ def start(self):
4344
def stop(self):
4445
self.playing = False
4546
self.tb.note_off(self.midi_note)
46-
47+
4748
def update(self):
48-
if not self.playing:
49-
return
49+
5050
now = time.monotonic()
5151

5252
# turn off note (not really a TB-303 thing, but a MIDI thing)
@@ -60,6 +60,12 @@ def update(self):
6060
self.next_step_time = now + self._secs_per_step + dt
6161
# add dt delta to attempt to make up for display hosing us
6262

63+
if self.on_step_callback: # and (self.i % self._steps_per_beat)==0:
64+
self.on_step_callback(self.i, self.steps_per_beat)
65+
66+
if not self.playing:
67+
return
68+
6369
midi_note = self.seqs[self.seq_num][0][self.i]
6470
vel = self.seqs[self.seq_num][1][self.i]
6571
if midi_note != 0: # midi_note == 0 = rest

circuitpython/tbish/tbish_synth.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,17 @@
5252
from pitch_glider import Glider
5353

5454
wave_size = 128
55-
wave_vol = 32677
55+
wave_vol = 32767
56+
saw_offset = 5
57+
wave_saw = np.linspace(wave_vol, -wave_vol, num=wave_size, dtype=np.int16) # saw
58+
wave_squ = np.concatenate((np.ones(wave_size//2, dtype=np.int16) * wave_vol, # square
59+
np.ones(wave_size//2, dtype=np.int16) * -wave_vol))
60+
wave_saw2 = wave_saw//2 + np.roll(wave_saw,saw_offset)//2 # equiv to np.add()
61+
5662
waves = (
57-
np.linspace(wave_vol, -wave_vol, num=wave_size, dtype=np.int16), # saw
58-
np.concatenate((np.ones(wave_size//2, dtype=np.int16) * wave_vol, # square
59-
np.ones(wave_size//2, dtype=np.int16) * -wave_vol)),
63+
wave_squ,
64+
wave_saw,
65+
wave_saw2,
6066
)
6167

6268
class TBishSynth:
@@ -66,7 +72,7 @@ def __init__(self, sample_rate, channel_count):
6672
self.note = None
6773
self.secs_per_step = 0.125 # fixme
6874
self.transpose = 0
69-
self.wavenum = 1 # which waveform to use: saw=0, square=1
75+
self.wavenum = 0 # which waveform to use: square=0, saw=1, saw2=2
7076
self.cutoff = 8000 # aka 'filter frequency'
7177
self.envmod = 0.5 # aka 'filter depth'
7278
self.decay = 0.01
@@ -87,12 +93,14 @@ def add_audioeffects(self):
8793

8894
# No distortion on rp2040 (not enough CPU)
8995
self.fx_distortion = Distortion(**fxcfg, mix = 0.0,
96+
#mode = audiofilters.DistortionMode.CLIP,
9097
mode = audiofilters.DistortionMode.LOFI,
9198
# other distortion modes are too slow?
9299
#mode = audiofilters.DistortionMode.OVERDRIVE,
100+
drive = 0.5, # hmm this doesn't do what I expect
93101
soft_clip = True,
94-
pre_gain = 30,
95-
post_gain = -10)
102+
pre_gain = 10, # so we'll use pre-gain instead
103+
post_gain = 0)
96104

97105
# but yes filter on rp2040 with custom compile
98106
self.fx_filter1 = Filter(**fxcfg, mix=1.0)
@@ -108,7 +116,7 @@ def add_audioeffects(self):
108116

109117
self.fx_delay = audiodelays.Echo(**fxcfg, mix=0.0,
110118
max_delay_ms = 400,
111-
delay_ms = self.secs_per_step * 1000 * 2,
119+
delay_ms = self.secs_per_step * 1000 * 4,
112120
decay = 0.1,
113121
freq_shift = False,
114122
)
@@ -121,18 +129,18 @@ def add_audioeffects(self):
121129

122130
def note_on_step(self, midi_note, slide=False, accent=False):
123131
"""Trigger a note, with slide and accent"""
124-
print("note_on_step: %3d %1d %1d" % (midi_note, slide, accent))
132+
print("note_on_step: %3d %1d %1d" % (midi_note, slide, accent), self.accent)
125133
self.note_off(midi_note) # must do when in step mode
126134
attack_level = 0.8
127135
cutoff = self.cutoff
128136
# resonance = self.resonance # fixme how to do this
129137
envmod = self.envmod
130138
glide_time = 0.001 # minimal slide
131139
if accent:
132-
attack_level += 0.2
133-
cutoff *= 1.3 # FIXME: verify
134-
#resonance *= 1.3
135-
envmod *= 0.5
140+
attack_level = min(1.0, attack_level + 0.5 * self.accent)
141+
cutoff = cutoff + 0.3 * self.accent # FIXME: verify
142+
#resonance *= 1.3 # FIXME: how to do
143+
envmod = min(1.0, envmod + 0.25 * self.accent)
136144
if slide:
137145
glide_time = 0.1
138146
# FIXME also do appropriate other actions for slide
@@ -180,13 +188,15 @@ def resonance(self,q):
180188

181189
@property
182190
def drive(self):
183-
return self.fx_distortion.pre_gain
191+
#return self.fx_distortion.drive
192+
return self.fx_distortion.pre_gain / 50
184193

185194
@drive.setter
186195
def drive(self,d):
187-
self.fx_distortion.post_gain = -d/2
188-
self.fx_distortion.pre_gain = d
189-
196+
#self.fx_distortion.drive = d # hmm doesn't seem to work like I expect
197+
self.fx_distortion.pre_gain = d * 50
198+
self.fx_distortion.post_gain = self.fx_distortion.pre_gain * -0.5
199+
190200
@property
191201
def drive_mix(self):
192202
return self.fx_distortion.mix
@@ -205,8 +215,8 @@ def delay_mix(self, m):
205215

206216
@property
207217
def delay_time(self):
208-
return self.fx_delay.delay_ms / (self.secs_per_step * 100 * 4)
218+
return self.fx_delay.delay_ms / (self.secs_per_step * 1000 * 4)
209219

210220
@delay_time.setter
211-
def delay_time(self, m):
212-
self.fx_delay.delay_ms = self.secs_per_step * 1000 * 4 * m
221+
def delay_time(self, t):
222+
self.fx_delay.delay_ms = self.secs_per_step * 1000 * 4 * t # FIXME: explain 4

circuitpython/tbish/tbish_ui.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ def __init__(self, display, params={}): #, knobAval, knobBval):
2626
palette = displayio.Palette(1)
2727
palette[0] = cw
2828
#self.rect = vectorio.Rectangle(pixel_shader=palette, width=64, height=1, x=32, y=1)
29-
self.rect = vectorio.Rectangle(pixel_shader=palette, width=4, height=4, x=32, y=5)
30-
self.append(self.rect)
29+
self.paramspot = vectorio.Rectangle(pixel_shader=palette, width=4, height=4, x=32, y=5)
30+
self.stepspot = vectorio.Rectangle(pixel_shader=palette, width=5, height=5, x=64, y=60)
31+
self.append(self.paramspot)
32+
self.append(self.stepspot)
3133

3234
for l in (self.textA, self.textB, self.labelA, self.labelB):
3335
#for l in (self.textA, self.textB):
@@ -37,9 +39,17 @@ def __init__(self, display, params={}): #, knobAval, knobBval):
3739

3840
def next_param_pair(self):
3941
self.curr_param_pair = (self.curr_param_pair+1) % self.num_param_pairs
42+
43+
def show_beat(self, step, steps_per_beat):
44+
# this doesn't work right but sorta works
45+
if step % steps_per_beat == 0 :
46+
self.stepspot.hidden = False
47+
else:
48+
self.stepspot.hidden = True
4049

4150
def update_param_pairs(self):
42-
self.rect.x = 45 + 5*(self.curr_param_pair)
51+
self.paramspot.x = 45 + 4*(self.curr_param_pair)
52+
4353
paramL = self.params[self.curr_param_pair*2+0]
4454
paramR = self.params[self.curr_param_pair*2+1]
4555
textAnew = paramL.fmt % paramL.val

0 commit comments

Comments
 (0)