This repository has been archived by the owner on Jul 2, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 10
/
top.w
334 lines (273 loc) · 10.4 KB
/
top.w
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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
@* Top-level Functions.
Broadly speaking, the top-level functions are in charge of computing
samples for the DSP inner-loop before, after, and during runtime. They get their
name from the fact that they are the top level of abstraction in the program.
These are the functions that get called in the Sporth Unit Generator
implementation |@(ugen.c@>|.
@<Top Level Functions@>=
@<Voc Create@>@/
@<Voc Destroy@>@/
@<Voc Init...@>@/
@<Voc Compute@>@/
@<Voc Tract Compute@>@/
@<Voc Set Frequency@>@/
@<Voc Get Frequency@>@/
@<Voc Get Tract Diameters@>@/
@<Voc Get Current Tract Diameters@>@/
@<Voc Get Tract Size@>@/
@<Voc Get Nose Diameters@>@/
@<Voc Get Nose Size@>@/
@<Voc Set Diameters@>@/
@<Voc Set Tongue Shape@>@/
@<Voc Get Counter@>@/
@<Voc Set Glottis Enable@>@/
@<Voc Set Tenseness@>@/
@<Voc Get Tenseness@>@/
@<Voc Set Velum@>@/
@<Voc Get Velum@>@/
@ In the function |sp_voc_create|, an instance of Voc is created via |malloc|.
@<Voc Create@>=
int sp_voc_create(sp_voc **voc)
{
*voc = malloc(sizeof(sp_voc));
return SP_OK;
}
@ As a counterpart to |sp_voc_compute|, |sp_voc_destroy| frees all data
previous allocated.
@<Voc Destroy@>=
int sp_voc_destroy(sp_voc **voc)
{
free(*voc);
return SP_OK;
}
@ After data has been allocated with |sp_voc_create|, it must be initialized
with |sp_voc_init|.
@<Voc Initialization@>=
int sp_voc_init(sp_data *sp, sp_voc *voc)
{
glottis_init(&voc->glot, sp->sr); /* initialize glottis */
tract_init(sp, &voc->tr); /* initialize vocal tract */
voc->counter = 0;
return SP_OK;
}
@ The function |sp_voc_compute| is called during runtime to generate audio.
This computation function will generate a single sample of audio and store it
in the |SPFLOAT| pointer |*out|.
@<Voc Compute@>=
int sp_voc_compute(sp_data *sp, sp_voc *voc, SPFLOAT *out)
{
SPFLOAT vocal_output, glot;
SPFLOAT lambda1, lambda2;
int i;
@q vocal_output = 0; @>
if(voc->counter == 0) {
glottis_update(&voc->glot, voc->tr.block_time);
tract_reshape(&voc->tr);
tract_calculate_reflections(&voc->tr);
for(i = 0; i < 512; i++) {
vocal_output = 0;
lambda1 = (SPFLOAT) i / 512;
lambda2 = (SPFLOAT) (i + 0.5) / 512;
glot = glottis_compute(sp, &voc->glot, lambda1);
tract_compute(sp, &voc->tr, glot, lambda1);
vocal_output += voc->tr.lip_output + voc->tr.nose_output;
tract_compute(sp, &voc->tr, glot, lambda2);
vocal_output += voc->tr.lip_output + voc->tr.nose_output;
voc->buf[i] = vocal_output * 0.125;
}
}
*out = voc->buf[voc->counter];
voc->counter = (voc->counter + 1) % 512;
return SP_OK;
}
@ The function |sp_voc_compute_tract| computes the vocal tract component of
Voc separately from the glottis. This provides the ability to use any input
signal as an glottal excitation, turning the model into a formant filter.
Compared to the main implementation in |@<Voc Compute@>|, this function
does not have the 512 sample delay.
@<Voc Tract Compute@>=
int sp_voc_tract_compute(sp_data *sp, sp_voc *voc, SPFLOAT *in, SPFLOAT *out)
{
SPFLOAT vocal_output;
SPFLOAT lambda1, lambda2;
if(voc->counter == 0) {
tract_reshape(&voc->tr);
tract_calculate_reflections(&voc->tr);
}
vocal_output = 0;
lambda1 = (SPFLOAT) voc->counter / 512;
lambda2 = (SPFLOAT) (voc->counter + 0.5) / 512;
tract_compute(sp, &voc->tr, *in, lambda1);
vocal_output += voc->tr.lip_output + voc->tr.nose_output;
tract_compute(sp, &voc->tr, *in, lambda2);
vocal_output += voc->tr.lip_output + voc->tr.nose_output;
*out = vocal_output * 0.125;
voc->counter = (voc->counter + 1) % 512;
return SP_OK;
}
@ The function |sp_voc_set_frequency| sets the fundamental frequency
for the glottal wave.
@<Voc Set Frequency@>=
void sp_voc_set_frequency(sp_voc *voc, SPFLOAT freq)
{
voc->glot.freq = freq;
}
@ The function |sp_voc_get_frequency_ptr| returns a pointer to the variable holding
the frequency. This allows values to be set and read directly without. The
use of a helper function. This function was notably created for use in a
demo using the GUI library Nuklear.
@<Voc Get Frequency@>=
SPFLOAT * sp_voc_get_frequency_ptr(sp_voc *voc)
{
return &voc->glot.freq;
}
@ This getter function returns the cylindrical diameters representing
tract.
@<Voc Get Tract Diameters@>=
SPFLOAT* sp_voc_get_tract_diameters(sp_voc *voc)
{
return voc->tr.target_diameter;
}
@ Similar to |sp_voc_get_tract_diameters| in |@<Voc Get Tract Diameters@>|,
the function |sp_voc_get_current_tract_diameters| returns the diameters
of the tract. The difference is that this function returns the
actual slewed diameters used in |@<Reshape Vocal Tract@>|, rather than
the target diameters.
@<Voc Get Current Tract Diameters@>=
SPFLOAT* sp_voc_get_current_tract_diameters(sp_voc *voc)
{
return voc->tr.diameter;
}
@ This getter function returns the size of the vocal tract.
@<Voc Get Tract Size@>=
int sp_voc_get_tract_size(sp_voc *voc)
{
return voc->tr.n;
}
@ This function returns the cylindrical diameters of the nasal cavity.
@<Voc Get Nose Diameters@>=
SPFLOAT* sp_voc_get_nose_diameters(sp_voc *voc)
{
return voc->tr.nose_diameter;
}
@ This function returns the nose size.
@<Voc Get Nose Size@>=
int sp_voc_get_nose_size(sp_voc *voc)
{
return voc->tr.nose_length;
}
@ The function |sp_voc_set_diameter()| is a function adopted from Neil Thapen's
Pink Trombone in a function he called setRestDiameter. It is the main function
in charge of the "tongue position" XY control. Modifications to the original
function have been made in an attempt to make the function more generalized.
Instead of relying on internal state, all variables used are parameters in
the function. Because of this fact, there are quite a few function
parameters:
\item{$\bullet$} {\bf voc}, the core Voc data struct
\item{$\bullet$} {\bf blade\_start}, index where the blade (?) starts.
this is set to 10 in pink trombone
\item{$\bullet$} {\bf lip\_start}, index where lip starts. this constant is
set to 39.
\item{$\bullet$} {\bf tip\_start}, this is set to 32.
\item{$\bullet$} {\bf tongue\_index}, nominal range [12 .. 29]
\item{$\bullet$} {\bf tongue\_diameter}, nominal range [2 .. 3.5]
\item{$\bullet$} {\bf diameters}, the floating point array to write to
For practical use cases, it is not ideal to call this function directly.
Instead, it can be indirectly called using a more sane function
|sp_voc_set_tongue_shape()|, found in the section |@<Voc Set Tongue Shape@>|.
@<Voc Set Diameters@>=
void sp_voc_set_diameters(sp_voc *voc, @/
int blade_start, @/
int lip_start, @/
int tip_start, @/
SPFLOAT tongue_index,@/
SPFLOAT tongue_diameter, @/
SPFLOAT *diameters) {
SPFLOAT grid_offset = 1.7;
SPFLOAT fixed_tongue_diameter = 2+(tongue_diameter-2)/1.5;
SPFLOAT tongue_amplitude = (1.5 - fixed_tongue_diameter + grid_offset);
int i;
SPFLOAT t;
SPFLOAT curve;
for(i = blade_start; i < lip_start; i++) {
t = 1.1 * M_PI *
(SPFLOAT)(tongue_index - i)/(tip_start - blade_start);
fixed_tongue_diameter = 2+(tongue_diameter-2)/1.5;
curve = tongue_amplitude * cos(t);
if(i == lip_start - 1) curve *= 0.8;
if(i == blade_start || i == lip_start - 2) curve *= 0.94;
diameters[i] = 1.5 - curve;
}
}
@ The function |sp_voc_set_tongue_shape()| will set the shape of the
tongue using the two primary arguments |tongue_index| and |tongue_diameter|.
It is a wrapper around the function described in |@<Voc Set Diameters@>|,
filling in the constants used, and thereby making it simpler to work with.
A few tract shapes shaped using this function have been generated below:
\displayfig{plots/tongueshape1.eps}
\displayfig{plots/tongueshape2.eps}
\displayfig{plots/tongueshape3.eps}
@<Voc Set Tongue Shape@>=
void sp_voc_set_tongue_shape(sp_voc *voc,
SPFLOAT tongue_index, @/
SPFLOAT tongue_diameter) {
SPFLOAT *diameters;
diameters = sp_voc_get_tract_diameters(voc);
sp_voc_set_diameters(voc, 10, 39, 32,
tongue_index, tongue_diameter, diameters);
}
@ Voc keeps an internal counter for control rate operations called inside
of the audio-rate compute function in |@<Voc Compute@>|. The function
|sp_voc_get_counter()| gets the current counter position. When the counter
is 0, the next call to |sp_voc_compute| will compute another block of audio.
Getting the counter position before the call allows control-rate variables
to be set before then.
@<Voc Get Counter@>=
int sp_voc_get_counter(sp_voc *voc)
{
return voc->counter;
}
@ The function |sp_voc_set_glottis_enable| controls the on/off state of
the glottis. Attack and release envelopes are applied on transitions.
@<Voc Set Glottis Enable@>=
void sp_voc_set_glottis_enable(sp_voc *voc, int enable)
{
voc->glot.enable = enable;
}
@ The function |sp_voc_set_tenseness| is used to set the tenseness variable,
used when calculating glottal time coefficients in
|@<Set up Glottis Waveform@>|, and is the main factor in calculating
aspiration noise in |@<Glottis Computation@>|. Typically this is a value
between 0 and 1. A value of 1 gives a full vocal sound, while a value of 0
is all breathy. It is ideal to have a little bit of aspiration noise.
Empirically good values tend to be in the range of $[0.6,0.9]$.
@<Voc Set Tenseness@>=
void sp_voc_set_tenseness(sp_voc *voc, SPFLOAT tenseness)
{
voc->glot.tenseness = tenseness;
}
@ The function |sp_voc_get_tenseness_ptr| returns an |SPFLOAT| pointer to the
parameter value directly controlling tenseness. This function is useful for
GUI frontends that use direct pointer manipulation like Nuklear, the
cross-platform UI framework used to make a demo for Voc.
@<Voc Get Tenseness@>=
SPFLOAT * sp_voc_get_tenseness_ptr(sp_voc *voc)
{
return &voc->glot.tenseness;
}
@ The function |sp_voc_set_velum| sets the {\it velum}, or soft pallette of
tract model. In the original implementation, the default value is 0.01, and
set to a value of 0.04 to get a nasally sound.
@<Voc Set Velum@>=
void sp_voc_set_velum(sp_voc *voc, SPFLOAT velum)
{
voc->tr.velum_target = velum;
}
@ The function |sp_voc_get_velum_ptr| returns the pointer associated with
the velum, allowing direct control of the velum parameter. This function was
created for use with a demo requiring direct access.
@<Voc Get Velum@>=
SPFLOAT *sp_voc_get_velum_ptr(sp_voc *voc)
{
return &voc->tr.velum_target;
}