«tb303» by olaf

on 04 Sep'14 00:54 in guisynthtb303
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
//this is code which emulates a tb303 it is not written by me but i found it on the sc-users mailing list.
//it is missing some features as accent and portamento which are actually important for the 303 sound ...
//the tb303 has the portamento at the end of the node, towards the next node in a fixed time.

(
//s =  Server .internal;
s.boot;
)

(
 SynthDef ( "sc303" , {  arg  out=0, freq=440, wave=0, ctf=100, res=0.2,
		sus=0, dec=1.0, env=1000, gate=0, vol=0.2;
	 var  filEnv, volEnv, waves;

	 // can't use adsr with exp curve???
	 //volEnv = EnvGen.ar(Env.adsr(1, 0, 1, dec, vol, 'exp'), In.kr(bus));
	volEnv =  EnvGen .ar( Env .new([10e-10, 1, 1, 10e-10], [0.01, sus, dec],  'exp' ), gate);
	filEnv =  EnvGen .ar( Env .new([10e-10, 1, 10e-10], [0.01, dec],  'exp' ), gate);

	waves = [ Saw .ar(freq, volEnv),  Pulse .ar(freq, 0.5, volEnv)];

	 Out .ar(out,  RLPF .ar(  Select .ar(wave, waves), ctf + (filEnv * env), res).dup * vol);
}).send(s);

)

 /*
s.sendMsg("s_new", "sc303", 3000, 0, 0);
s.sendMsg("n_set", 3000, 'gate', 1, 'freq', 220);
s.sendMsg("n_set", 3000, 'gate', 0);
s.sendMsg("n_set", 3000, 'dec', 1);

s.sendMsg("n_free", ~node)
*/


 // make GUI!
(
~bpm = 140;
~step = 0;
	 // set default note ons
~defaultNoteOns =  Array .fill(16,1);
	 // this is the note on array that will be used in the sequencer routine
~noteOns = ~defaultNoteOns;

	 // set root
~root = 36;

	 // set default pitches
~defaultPitches = [ 0, 12, 0, 0, -12, 0, 0, 0, -12, 0, 12, 0, 3, -5, 2, 0 ];
	 // this is the pitch array that will be used in the sequencer routine
~pitches = ~defaultPitches;

~tStepLED =  Array .new; 	 // the step indicator LEDs as an array
~sNoteOn =  Array .new;  	 // the note-on sliders as an array
~tNoteOn =  Array .new;	 // the note-on value boxes as an array
~sPitch =  Array .new;	 // the pitch sliders as an array
~tPitch =  Array .new; 	 // the pitch value boxes as an array

w =  Window ( "-303" ,  Rect (128, 64, 510, 320));
w.view.background =  Color .white;
w.front;

~layoutLeft =  CompositeView (w,  Rect (0, 0, 400, 320));	 // make the left pane of the window
~layoutLeft.decorator =  FlowLayout (~layoutLeft.bounds);  // auto-arrange the elements
~layoutRight =  CompositeView (w,  Rect (400, 0, 400, 120));
~layoutRight.decorator =  FlowLayout (~layoutRight.bounds);

 // make transport controls

	 // Tempo
 StaticText (~layoutRight,  Rect (0,0, 45, 14)).string_( "BPM: " );
~tBpm =  NumberBox (~layoutRight,  Rect (0,0, 45, 14))
	.string_(~bpm)
	.action_({  arg  item;
		~bpm = item.value;
		~sBpm.value_((~bpm-40)/200);
	});

~layoutRight.decorator.nextLine;
~sBpm =  Slider (~layoutRight,  Rect (0,0, 100, 20))
	.background_( Color .new(0.8,0.8,0.8))
	.value_((~bpm-40)/200)
	.action_({  arg  sldr;
		~bpm = ((sldr.value)*200) + 40;
		~tBpm.string_(~bpm);
	});

~layoutRight.decorator.nextLine;

	 // Reset
 Button (~layoutRight,  Rect (0,0,45,20))
	.states_([[ "|<" ]])
	.action_({  arg  item;
		~tStepLED[~step].background_( Color .new(0.6,0,0));
		~step = 0;
		~tStepLED[0].background_( Color .new(1,0,0));
	});

	 // Play/Pause
 Button (~layoutRight,  Rect (0,0,45,20))
	.states_([[ ">" ],[ "||" ]])
	.action_({  arg  item;
		if( item.value == 0, {
			~seqr.stop;
		},{
			~seqr.reset;
			~seqr.play;
		});
	});

~layoutRight.decorator.nextLine;

	 // Root note
 StaticText (~layoutRight,  Rect (0,0, 45, 14)).string_( "Root: " );
~tRoot =  StaticText (~layoutRight,  Rect (0,0, 45, 14)).string_(~root);
~layoutRight.decorator.nextLine;
 Slider (~layoutRight,  Rect (0,0, 100, 20))
	.background_( Color .new(0.8,0.8,0.8))
	.value_((~root-24)/36)
	.step_(1/48)
	.action_({  arg  sldr;
		~root = ((sldr.value)*36) + 24;
		~tRoot.string_(~root);
	});


 // make synth control labels
 StaticText (~layoutLeft,  Rect (0,0, 45, 14)).string_( "Wave" );
 StaticText (~layoutLeft,  Rect (0,0, 80, 14)).string_( "Cutoff" );
 StaticText (~layoutLeft,  Rect (0,0, 80, 14)).string_( "Resonance" );
 StaticText (~layoutLeft,  Rect (0,0, 80, 14)).string_( "Decay" );
 StaticText (~layoutLeft,  Rect (0,0, 80, 14)).string_( "Envelope" );
~layoutLeft.decorator.nextLine;

 // make the synth controls
	 // wave-type
 Button (~layoutLeft,  Rect (0,0,45,20))
	.states_([[ "Saw" ],[ "Square" ]])
	.action_({  arg  item;
		s.sendMsg( "n_set" , ~node,  'wave' , item.value);
	});
	 // cut-off frequency
 Slider (~layoutLeft,  Rect (0,0, 80, 20))
	.background_( Color .new(0.8,0.8,0.8))
	.action_({  arg  sldr;
		s.sendMsg( "n_set" , ~node,  'ctf' , ((sldr.value)*(10000-50))+50);  // modify synth param
	});

	 // resonance amt
 Slider (~layoutLeft,  Rect (0,0, 80, 20))
	.background_( Color .new(0.8,0.8,0.8))
	.action_({  arg  sldr;
		s.sendMsg( "n_set" , ~node,  'res' , (1-sldr.value)*(0.97)+0.03);  // modify synth param
	});

	 // decay amt
 Slider (~layoutLeft,  Rect (0,0, 80, 20))
	.background_( Color .new(0.8,0.8,0.8))
	.action_({  arg  sldr;
		s.sendMsg( "n_set" , ~node,  'dec' , (sldr.value)*2);  // modify synth param
	});

	 // envelope amt
 Slider (~layoutLeft,  Rect (0,0, 80, 20))
	.background_( Color .new(0.8,0.8,0.8))
	.action_({  arg  sldr;
		s.sendMsg( "n_set" , ~node,  'env' , (sldr.value)*10000);  // modify synth param
	});

~layoutLeft.decorator.nextLine;

	 // make step LEDs
16.do({  arg  i;
	~tStepLED = ~tStepLED.add(
		 StaticText (~layoutLeft,  Rect (0,0,20,12))
			.background_( Color .new(0.6,0,0))
			.stringColor_( Color .white)
			.string_(i+1)
	);
});
~tStepLED[~step].background_( Color .new(1,0,0));
~layoutLeft.decorator.nextLine;

	 // make the note-on sliders
16.do({  arg  i;
	~sNoteOn = ~sNoteOn.add(
		 Slider (~layoutLeft,  Rect (0,0,20,60))			 // position doesn't matter; width, height do
			.background_( Color .new(0,0,0.7))		 // color the slider
			.step_(0.1)						 // values are stepped by 0.1
			.value_(~noteOns[i])				 // assign it its value from the note on array
			.action_({  arg  sldr;				 // when the slider is moved...
				~noteOns[i] = sldr.value;		 // ... update note-on array
				~tNoteOn[i].string = sldr.value;  // ... update its value box
					 // ...change color of value box
				~tNoteOn[i].background =  Color .new((1-~noteOns[i]),1,(1-~noteOns[i]));
			})
	);
});
~layoutLeft.decorator.nextLine;

	 // make the note-on value boxes
16.do({  arg  i;
	~tNoteOn = ~tNoteOn.add(
		 StaticText (~layoutLeft,  Rect (0,0,20,20))
			.background_( Color .new((1-~noteOns[i]),1,(1-~noteOns[i])))
			.string_(~sNoteOn[i].value)
			.align_(0)
			.action_({  arg  text;
				text.string.postln;
			})
	);
});
~layoutLeft.decorator.nextLine;

	 // make the pitch sliders
16.do({  arg  i;
	~sPitch = ~sPitch.add(
		 Slider (~layoutLeft,  Rect (0,0,20,120))
			.value_((~pitches[i]+12)/24)
			.step_(1/24)
			.background_( Color .new(0.8,0.8,0.8))
			.action_({  arg  item;
				~pitches[i] = ((item.value * 24)-12);
				~tPitch[i].string = ((item.value * 24)-12);
			})
	);
});
~layoutLeft.decorator.nextLine;

	 // make the pitch value boxes
16.do({  arg  i;
	~tPitch = ~tPitch.add(
		 StaticText (~layoutLeft,  Rect (0,0,20,20))
			.background_( Color .white)
			.string_(~pitches[i].value)
			.align_(0)
	);
});
 /*~tempButt = Button(w, Rect(0,0,60,60))
			.states_([
				["0", Color.red, Color.black]
			]);*/

)

 // do other stuff

(
~node = s.nextNodeID;

 // start the synth
s.sendMsg( "s_new" ,  "sc303" , ~node, 1, 1);

 // make the step sequencer
~seqr =  Routine .new({
	loop({

			 // turn on LED
		{~tStepLED[~step].background_( Color .new(1,0,0));  /*(~step.asString + "on").postln;*/ }.defer;

		if(~noteOns[~step].coin, {
			 // play note!
				 // this is out of sync

  //
			s.sendMsg("n_set", ~node, 'gate', 1, 'freq', (~pitches[~step]+~root).midicps);

			//
			(7.5/~bpm).wait;

			//
			s.sendMsg("n_set", ~node, 'gate', 0);

			//
			(7.5/~bpm).wait;

			// buffer playback on server (adds start/stop delay, but better inter-note timing)
			//s.sendBundle(s.latency, [ "n_set" , ~node,  'gate' , 1,  'freq' , (~pitches[~step]+~root).midicps]);
			//(7.5/~bpm).wait;
			//s.sendBundle(s.latency, [ "n_set" , ~node,  'gate' , 0]);
			//(7.5/~bpm).wait;
		},{
				 // it's a rest
			(15/~bpm).wait;
		});

			 // turn off LED
		{~tStepLED[(~step-1)%16].background_( Color .new(0.6,0,0));  /*(((~step-1)%16).asString + "off").postln;*/ }.defer;

		~step = (~step + 1)%16;

	});
});
)

~seqr.play;
~seqr.stop;
~seqr.reset;
~pitches
raw 8630 chars (focus & ctrl+a+c to copy)
reception
comments
mauro user 17 Aug'18 11:13

original code: http://superdupercollider.blogspot.com/2009/02/303-emulator.html