«wavetable interpolation drone» byeli.fieldsteel

on 28 Feb'19 07:06 in

A lush drone using a waveshaping algorithm for interpolating between multiple wavetables. This code is based off of a similar code example by totalgee:

http://sccode.org/1-4V1

It was only after coding this example that I realized the UGens VOsc and VOsc3 are specifically designed for this wavetable interpolating/morphing function. But this code was still very fun to build.

EF

```1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194```
```(
/*
A lush drone using a waveshaping algorithm for interpolating between multiple wavetables. This code is based off of a similar code example by totalgee:

http://sccode.org/1-4V1

It was only after coding this example that I realized the UGens VOsc and VOsc3 are specifically designed for this wavetable interpolating/morphing function. But this code was still very fun to build.

EF
*/

s.freeAll;
Window.closeAll;
Buffer.freeAll;

/*
Function for interpolating between two
values based on an interpolation value:

0 ==> old, 1 ==> new,
0.5 ==> halfway between old/new, etc
*/
~interpFn = {
arg old=20, new=10, bal=0.5;
old + ((new - old) * bal);
};

/*
three wacky envelopes, each converted to Signal format.
each has 12 level points. the first and last value are
always zero. the inner 10 points are random between -1
and +1. the internal functions normalize the levels so
the highest value is always +/-1.0
*/
~wt0 = Env(
[0]++
{
var levs, peak;
levs = {rrand(-1.0,1.0)}!10;
peak = levs.abs.maxItem;
levs = levs * peak.reciprocal;
}.value ++
[0],
{exprand(0.01,1)}!11,
{exprand(0.1,4)}!11
).asSignal(512);

~wt1 = Env(
[0]++
{
var levs, peak;
levs = {rrand(-1.0,1.0)}!10;
peak = levs.abs.maxItem;
levs = levs * peak.reciprocal;
}.value ++
[0],
{exprand(0.01,1)}!11,
{exprand(0.1,4)}!11
).asSignal(512);

~wt2 = Env(
[0]++
{
var levs, peak;
levs = {rrand(-1.0,1.0)}!10;
peak = levs.abs.maxItem;
levs = levs * peak.reciprocal;
}.value ++
[0],
{exprand(0.01,1)}!11,
{exprand(0.1,4)}!11
).asSignal(512);

/*
Signals that contain values representing interpolations between two wavetables.
~i0 interpolates between ~wt0 <==> ~wt1
~i1 interpolates between ~wt1 <==> ~wt2
*/
~i0 = ~wt0.copy;
~i1 = ~wt2.copy;

s.waitForBoot({

//load signals to buffers in wavetable format
~i0Buf = Buffer.alloc(s, 1024, 1);
~i1Buf = Buffer.alloc(s, 1024, 1);
s.sync;
~i0Buf.setn(0, ~i0.asWavetable);
~i1Buf.setn(0, ~i1.asWavetable);

s.sync;

/*
Create two Osc Synths, reading their wavetable
data from ~i0Buf and ~i1Buf
*/
~synths = [~i0Buf, ~i1Buf].collect{
arg buf, i;
{
/*
detune=0 makes a much more boring sound, but also
makes the wavetable interpolation more
observable (evaluate s.scope and adjust the
horizontal slider to watch the waveform)
*/
arg detune=0.15, freq=40, amp=0.1;
var sig;
sig = Osc.ar(
buf.bufnum,

//frequency with random moving detune value (in semitones)
freq * LFNoise1.kr({Rand(0.08,0.15)}!8).bipolar(detune).midiratio,

{Rand(0,2pi)}!8
);

//spread 8-channel detuned Osc texture across two channels
sig = Splay.ar(sig);

//avoid funky DC bias
sig = LeakDC.ar(sig);

sig = sig * amp;
};

{
arg min=0.2, max=1;
var sig;
sig = LFDNoise1.kr(
LFNoise1.kr(8!2).exprange(min,max)
).unipolar(1);

/*
uncomment this line to control wavetable
interpolation with horizontal mouse position
*/
//sig = MouseX.kr!2;

0
}.play(target:h);

s.sync;

OSCdef(\mouse, {
arg msg;

/*
when SendReply sends a value to the language,
use that value as an interpolation parameter
and update the wavetables from which the Osc
*/

//modify values
~i0.waveFill({
arg x, val, i;
~interpFn.(~wt0[i], ~wt1[i], msg[3])
});
~i1.waveFill({
arg x, val, i;
~interpFn.(~wt1[i], ~wt2[i], msg[4])
});

//dynamically update Buffers
~i0Buf.setn(0, ~i0.asWavetable);
~i1Buf.setn(0, ~i1.asWavetable);
}, '/mouse').permanent_(true);
})
)

//watch:
s.scope;
/*(adjust horizontal slider on scope window
as needed to stabilize waveform view)
*/

//change frequency
~synths.do(_.set(\freq, 50));

//the two Synths can be changed independently
(
~synths[0].set(\freq, 60);
~synths[1].set(\freq, 106, \amp, 0.05);
)