«Vocal Clouds (work in progress)» by emergent

on 08 Mar'21 08:16 in instrumentgranularsynthesis

A sample-based granular synthesizer to make a cloud-like texture from short vocal samples, which can then be played with a MIDI controller

ToDo:

  • add MIDI controls for more parameters (e.g. density, detune, pan, grain position)
  • add mechanism to choose different vocal samples
  • add mechanism to balance amplitude variations brought about by different grain durations and densities
  • possibly add more controls for rate manipulation/overtone generation & grain position
  • add mechanism to choose between Dust and Impulse as grain triggers (Dust for a slightly more glitchy texture, Impulse for smoother sound)
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
// Vocal clouds - Proof of concept

(
// configure server
s = Server.local;
s.options.sampleRate_(44100);
s.options.memSize_(65536 * 4);
s.newBusAllocators;

// global variables
~out = 0;
~path = PathName(thisProcess.nowExecutingPath).parentPath++"/vocal-samples/"; // expects folder "vocal-samples" with audio samples in the same folder as the project file

// make buffers

~makeBuffers = {
	~buf = Array.new;
	PathName(~path).entries.do({
		arg path;
		~buf = ~buf.add(Buffer.read(s, path.fullPath));
	});
};

// make buses
~makeBuses = {
	~revBus = Bus.audio(s, 2); // effects bus
};

// make groups
~makeNodes = {
	s.bind({
		~src = Group.new(s);
		~efx = Group.after(~src);
		~reverb = Synth.new(\reverb, [\in, ~revBus, \out, ~out], ~efx);
	});
};

// clean up on ServerQuit
~cleanup = {
	s.freeAll;
	MIDIdef.freeAll;
	s.newBusAllocators;
	ServerBoot.removeAll;
	ServerTree.removeAll;
	ServerQuit.removeAll;
};
// MIDI stuff!
~bend = 8192; // initializing global variable for pitch bend wheel
~mod = 0; // global variable

MIDIIn.connectAll;

~notes = Array.fill(128, {nil});

MIDIdef.noteOn(\on, {
	arg val, num, chan, src;
	~notes[num] = Synth.new(\voxgrains, [
		\baserate, (num-60).midiratio,
		\amp, val.linexp(1,127,0.02,0.3), //velocity detection
		\gate, 1,
		\bend, ~bend.linlin(0,16383,-2,2),
		\basedur, ~mod.linlin(0,127,0.07,2),
		\detune, 0.025,
		\dens, 12,
		\out, ~revBus
	],
	~src
	);
});

MIDIdef.noteOff(\off, {
	arg val, num, chan, src;
	~notes[num].set(\gate, 0);
	~notes[num] = nil; //probably optional, just to be on the safe side
});

MIDIdef.bend(\bendTest, {
	arg val, chan, src;
	~bend = val;
	~notes.do{arg synth; synth.set(\bend, val.linlin(0,16383,-2,2))};
}, chan:0); //chan:0 makes the def only listen to events on chan 0

MIDIdef.cc(\modWheelTest, {
	arg val, chan, num;
	~mod = val;
	~notes.do{arg synth; synth.set(\basedur, val.linlin(0, 127, 0.07, 2))};
}, 1);

// register functions
ServerBoot.add(~makeBuses);
ServerBoot.add(~makeBuffers);
ServerQuit.add(~cleanup);

s.waitForBoot({
	s.sync;

	SynthDef.new(\voxgrains, {
		var sig, env, rate, dur, pan;
		env = Env.asr(\atk.ir(0.12), 1, \rel.ir(1)).kr(2, \gate.kr(1));
		rate = \baserate.kr(1) * LFNoise1.kr(\dens.kr(36) * 1.01).exprange(\baserate.kr(1) * 4).round(\baserate.kr(1)) + LFNoise1.kr(100).bipolar(\detune.kr(0.025)) * \bend.kr(~bend).midiratio; // randomly generated overtones 
		dur = \basedur.kr(0.5) + LFNoise1.ar(50).bipolar(0.02);
		pan = \basepan.kr(0) + LFNoise1.ar(100).bipolar(0.25); // don't assign values greater than +- 0.7 to basepan!
		sig = GrainBuf.ar(
			2, // 2-channel output
			Dust.kr(\dens.kr(36)), // using Dust as trigger
			dur,
			\buf.ir(~buf[5]), // choose buffer from which grains are taken
			rate,
			\pos.kr(0.1),
			2, // linear interpolation
			pan,
			\grainenv.ir(-1), // using built-in envelope
			\maxgrains.kr(128)
		);
		sig = sig * env * \amp.kr(0.12);
		sig = Splay.ar(sig);
		Out.ar(\out.ir(~out), sig); // possibly add dry/wet control via additional out here
	}).add;

	SynthDef.new(\reverb, {
		var sig;
		sig = JPverb.ar(
			In.ar(\in.ir(~revBus), 2),
			\rtime.kr(4),
			\damp.kr(0.75),
			\size.kr(4.5),
			\earlyDiff.kr(0.8),
			\modDepth.kr(0.12),
			\modFreq.kr(2),
			\low.kr(1),
			\mid.kr(0.9),
			\high.kr(0.8)
		);
		sig = sig * \revAmp.kr(0.5);
		Out.ar(\out.ir(~out), sig);
	}).add;

	s.sync;
	ServerTree.add(~makeNodes);
	s.freeAll;

	s.sync;
	"done".postln;
});
)

s.quit; // quit performance
raw 3605 chars (focus & ctrl+a+c to copy)
reception
comments