bloops 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/c/notation.rl ADDED
@@ -0,0 +1,549 @@
1
+ //
2
+ // notation.rl
3
+ // the musical notation parser
4
+ //
5
+ // (c) 2009 why the lucky stiff
6
+ // See COPYING for the license
7
+ //
8
+ #include <stdio.h>
9
+ #include <stdlib.h>
10
+ #include <string.h>
11
+ #include <math.h>
12
+ #include <sys/stat.h>
13
+ #include "bloopsaphone.h"
14
+
15
+ #define ATOI(X,N) ({ \
16
+ char *Ap = X; \
17
+ int Ai = 0; \
18
+ size_t Al = N; \
19
+ while (Al--) { \
20
+ if ((*Ap >= '0') && (*Ap <= '9')) { \
21
+ Ai = (Ai * 10) + (*Ap - '0'); \
22
+ Ap++; \
23
+ } \
24
+ else break; \
25
+ } \
26
+ Ai; \
27
+ })
28
+
29
+ #define NOTE S->notes[S->nlen]
30
+
31
+ #define NEXT() \
32
+ NOTE.duration = len; \
33
+ NOTE.octave = oct; \
34
+ mod = 0; \
35
+ tone = 0; \
36
+ len = 4; \
37
+ fxval = 0; \
38
+ fxmod = 0; \
39
+ S->nlen++
40
+
41
+ %%{
42
+ machine bloopnotes;
43
+
44
+ action Alen {
45
+ len = ATOI(ts, p - ts);
46
+ }
47
+
48
+ action Aoct {
49
+ oct = ATOI(p - 1, 1);
50
+ }
51
+
52
+ action Anote {
53
+ switch (tone) {
54
+ case 'a': case 'A':
55
+ if (mod == 'b') NOTE.tone = 'a';
56
+ else if (mod == '#') NOTE.tone = 'b';
57
+ else NOTE.tone = 'A';
58
+ break;
59
+ case 'b': case 'B':
60
+ if (mod == 'b') NOTE.tone = 'b';
61
+ else if (mod == '#') NOTE.tone = 'C';
62
+ else NOTE.tone = 'B';
63
+ break;
64
+ case 'c': case 'C':
65
+ if (mod == 'b') NOTE.tone = 'B';
66
+ else if (mod == '#') NOTE.tone = 'd';
67
+ else NOTE.tone = 'C';
68
+ break;
69
+ case 'd': case 'D':
70
+ if (mod == 'b') NOTE.tone = 'd';
71
+ else if (mod == '#') NOTE.tone = 'e';
72
+ else NOTE.tone = 'D';
73
+ break;
74
+ case 'e': case 'E':
75
+ if (mod == 'b') NOTE.tone = 'e';
76
+ else if (mod == '#') NOTE.tone = 'F';
77
+ else NOTE.tone = 'E';
78
+ break;
79
+ case 'f': case 'F':
80
+ if (mod == 'b') NOTE.tone = 'E';
81
+ else if (mod == '#') NOTE.tone = 'g';
82
+ else NOTE.tone = 'F';
83
+ break;
84
+ case 'g': case 'G':
85
+ if (mod == 'b') NOTE.tone = 'g';
86
+ else if (mod == '#') NOTE.tone = 'a';
87
+ else NOTE.tone = 'G';
88
+ break;
89
+ }
90
+ }
91
+
92
+ action Afx {
93
+ bloopsafx *fx = (bloopsafx *)malloc(sizeof(bloopsafx));
94
+ fx->next = NOTE.FX;
95
+ fx->cmd = fxcmd;
96
+ fx->val = fxval;
97
+ fx->mod = fxmod;
98
+ fxval = fxmod = 0;
99
+ NOTE.FX = fx;
100
+ }
101
+
102
+ action fxval1 {
103
+ fxval = atoi(p-1) * 1.0f;
104
+ }
105
+
106
+ action fxval2 {
107
+ fxval += ATOI(pf, p - pf) * pow(0.1f, p - pf);
108
+ }
109
+
110
+ dec = digit+ %fxval1 ("." %{ pf = p; } digit+ )? %fxval2;
111
+ float = ("-" dec %{ fxval *= -1.0f; } | dec);
112
+ fxcmd = "volume" %{ fxcmd = BLOOPS_FX_VOLUME; } |
113
+ "punch" %{ fxcmd = BLOOPS_FX_PUNCH; } |
114
+ "attack" %{ fxcmd = BLOOPS_FX_ATTACK; } |
115
+ "sustain" %{ fxcmd = BLOOPS_FX_SUSTAIN; } |
116
+ "decay" %{ fxcmd = BLOOPS_FX_DECAY; } |
117
+ "square" %{ fxcmd = BLOOPS_FX_SQUARE; } |
118
+ "sweep" %{ fxcmd = BLOOPS_FX_SWEEP; } |
119
+ "vibe" %{ fxcmd = BLOOPS_FX_VIBE; } |
120
+ "vspeed" %{ fxcmd = BLOOPS_FX_VSPEED; } |
121
+ "vdelay" %{ fxcmd = BLOOPS_FX_VDELAY; } |
122
+ "lpf" %{ fxcmd = BLOOPS_FX_LPF; } |
123
+ "lsweep" %{ fxcmd = BLOOPS_FX_LSWEEP; } |
124
+ "resonance" %{ fxcmd = BLOOPS_FX_RESONANCE; } |
125
+ "hpf" %{ fxcmd = BLOOPS_FX_HPF; } |
126
+ "hsweep" %{ fxcmd = BLOOPS_FX_HSWEEP; } |
127
+ "arp" %{ fxcmd = BLOOPS_FX_ARP; } |
128
+ "aspeed" %{ fxcmd = BLOOPS_FX_ASPEED; } |
129
+ "phase" %{ fxcmd = BLOOPS_FX_PHASE; } |
130
+ "psweep" %{ fxcmd = BLOOPS_FX_PSWEEP; } |
131
+ "repeat" %{ fxcmd = BLOOPS_FX_REPEAT; };
132
+
133
+ len = [1-9] [0-9]? ":"? %Alen;
134
+ up = "+" %{ len = 1; } len?;
135
+ down = "-" %{ len = 1; } len?;
136
+ mod = [b#] %{ mod = p[-1]; };
137
+ oct = [1-8] %Aoct;
138
+ fxmod = ( ("+"|"-") %{ fxmod = p[-1]; } (":"|space+) )?;
139
+ fx = ("[" fxcmd (":"|space*) fxmod float "]" %Afx );
140
+ note = len? [a-gA-G] %{ tone = p[-1]; } mod? oct? fx* %Anote;
141
+
142
+ main := |*
143
+ len => {
144
+ NOTE.tone = 0;
145
+ NEXT();
146
+ };
147
+ note => { NEXT(); };
148
+ up => { oct++; len = 4; };
149
+ down => { oct--; len = 4; };
150
+ space;
151
+ *|;
152
+
153
+ write data nofinal;
154
+ }%%
155
+
156
+ extern void _bloops_track_add(bloops *B, bloopsatrack *track);
157
+
158
+ bloopsatrack *
159
+ bloops_track(bloops *B, bloopsaphone *P, char *track, int tracklen)
160
+ {
161
+ int cs, act, oct = 4, len = 4;
162
+ bloopsatrack *S = (bloopsatrack *)malloc(sizeof(bloopsatrack));
163
+ char tone, mod, fxmod, *p, *pe, *pf, *ts, *te, *eof = 0;
164
+ bloopsafxcmd fxcmd = (bloopsafxcmd)0;
165
+ float fxval = 0;
166
+
167
+ S->refcount = 1;
168
+ S->nlen = 0;
169
+ S->capa = 1024;
170
+ S->notes = (bloopsanote *)calloc(sizeof(bloopsanote), 1024);
171
+
172
+ p = track;
173
+ pe = track + tracklen + 1;
174
+
175
+ %% write init;
176
+ %% write exec;
177
+
178
+ memcpy(&S->params, &P->params, sizeof(bloopsaparams));
179
+
180
+ _bloops_track_add(B, S);
181
+
182
+ return S;
183
+ }
184
+
185
+ bloopsatrack *
186
+ bloops_track2(bloops *B, bloopsaphone *P, char *track)
187
+ {
188
+ return bloops_track(B, P, track, strlen(track));
189
+ }
190
+
191
+ char *
192
+ bloops_track_str(bloopsatrack *track)
193
+ {
194
+ int bufsize = sizeof(char) * (track->nlen * 6 + 1024);
195
+ char *str = (char *)malloc(bufsize), *ptr = str;
196
+ int i, adv = 0;
197
+
198
+ for (i = 0; i < track->nlen; i++)
199
+ {
200
+ if (ptr - str + adv + sizeof(char) * 256 > bufsize) {
201
+ char *new_str;
202
+ bufsize += sizeof(char) * 1024;
203
+ new_str = realloc(str, bufsize);
204
+ if (new_str == NULL) {
205
+ free(str);
206
+ return NULL;
207
+ }
208
+ }
209
+
210
+ if (ptr > str)
211
+ strcat(ptr++, " ");
212
+
213
+ if (track->notes[i].duration != 4)
214
+ {
215
+ adv = sprintf(ptr, "%d:", (int)track->notes[i].duration);
216
+ ptr += adv;
217
+ }
218
+
219
+ if (track->notes[i].tone)
220
+ {
221
+ char tone[3] = "\0\0\0";
222
+ tone[0] = track->notes[i].tone;
223
+ switch (tone[0]) {
224
+ case 'a': tone[0] = 'A'; tone[1] = 'b'; break;
225
+ case 'b': tone[0] = 'B'; tone[1] = 'b'; break;
226
+ case 'd': tone[0] = 'C'; tone[1] = '#'; break;
227
+ case 'e': tone[0] = 'E'; tone[1] = 'b'; break;
228
+ case 'g': tone[0] = 'F'; tone[1] = '#'; break;
229
+ }
230
+ adv = sprintf(ptr, "%s", tone);
231
+ ptr += adv;
232
+
233
+ adv = sprintf(ptr, "%d", (int)track->notes[i].octave);
234
+ ptr += adv;
235
+ bloopsafx *fx = (bloopsafx *)track->notes[i].FX;
236
+ while (fx) {
237
+ if (fx->mod == 0)
238
+ adv = sprintf(ptr, "[%s %0.3f]", bloops_fxcmd_name(fx->cmd), fx->val);
239
+ else
240
+ adv = sprintf(ptr, "[%s %c %0.3f]", bloops_fxcmd_name(fx->cmd), fx->mod, fx->val);
241
+ ptr += adv;
242
+ fx = (bloopsafx *)fx->next;
243
+ }
244
+ }
245
+ }
246
+
247
+ return str;
248
+ }
249
+
250
+ char *
251
+ bloops_fxcmd_name(bloopsafxcmd fxcmd) {
252
+ char *fxname = "\0";
253
+ switch (fxcmd) {
254
+ case BLOOPS_FX_VOLUME: fxname = "volume"; break;
255
+ case BLOOPS_FX_PUNCH: fxname = "punch"; break;
256
+ case BLOOPS_FX_ATTACK: fxname = "attack"; break;
257
+ case BLOOPS_FX_SUSTAIN: fxname = "sustain"; break;
258
+ case BLOOPS_FX_DECAY: fxname = "decay"; break;
259
+ case BLOOPS_FX_SQUARE: fxname = "square"; break;
260
+ case BLOOPS_FX_SWEEP: fxname = "sweep"; break;
261
+ case BLOOPS_FX_VIBE: fxname = "vibe"; break;
262
+ case BLOOPS_FX_VSPEED: fxname = "vspeed"; break;
263
+ case BLOOPS_FX_VDELAY: fxname = "vdelay"; break;
264
+ case BLOOPS_FX_LPF: fxname = "lpf"; break;
265
+ case BLOOPS_FX_LSWEEP: fxname = "lsweep"; break;
266
+ case BLOOPS_FX_RESONANCE: fxname = "resonance"; break;
267
+ case BLOOPS_FX_HPF: fxname = "hpf"; break;
268
+ case BLOOPS_FX_HSWEEP: fxname = "hsweep"; break;
269
+ case BLOOPS_FX_ARP: fxname = "arp"; break;
270
+ case BLOOPS_FX_ASPEED: fxname = "aspeed"; break;
271
+ case BLOOPS_FX_PHASE: fxname = "phase"; break;
272
+ case BLOOPS_FX_PSWEEP: fxname = "psweep"; break;
273
+ case BLOOPS_FX_REPEAT: fxname = "repeat"; break;
274
+ }
275
+ return fxname;
276
+ }
277
+
278
+ float
279
+ bloops_note_freq(char note, int octave)
280
+ {
281
+ switch (note)
282
+ {
283
+ case 'A': // A
284
+ if (octave <= 0) return 0.0;
285
+ else if (octave == 1) return 0.121;
286
+ else if (octave == 2) return 0.175;
287
+ else if (octave == 3) return 0.248;
288
+ else if (octave == 4) return 0.353;
289
+ else if (octave == 5) return 0.500;
290
+ break;
291
+
292
+ case 'b': // A# or Bb
293
+ if (octave <= 0) return 0.0;
294
+ else if (octave == 1) return 0.125;
295
+ else if (octave == 2) return 0.181;
296
+ else if (octave == 3) return 0.255;
297
+ else if (octave == 4) return 0.364;
298
+ else if (octave == 5) return 0.515;
299
+ break;
300
+
301
+ case 'B': // B
302
+ if (octave <= 0) return 0.0;
303
+ else if (octave == 1) return 0.129;
304
+ else if (octave == 2) return 0.187;
305
+ else if (octave == 3) return 0.263;
306
+ else if (octave == 4) return 0.374;
307
+ else if (octave == 5) return 0.528;
308
+ break;
309
+
310
+ case 'C': // C
311
+ if (octave <= 1) return 0.0;
312
+ else if (octave == 2) return 0.133;
313
+ else if (octave == 3) return 0.192;
314
+ else if (octave == 4) return 0.271;
315
+ else if (octave == 5) return 0.385;
316
+ else if (octave == 6) return 0.544;
317
+ break;
318
+
319
+ case 'd': // C# or Db
320
+ if (octave <= 1) return 0.0;
321
+ else if (octave == 2) return 0.138;
322
+ else if (octave == 3) return 0.198;
323
+ else if (octave == 4) return 0.279;
324
+ else if (octave == 5) return 0.395;
325
+ else if (octave == 6) return 0.559;
326
+ break;
327
+
328
+ case 'D': // D
329
+ if (octave <= 1) return 0.0;
330
+ else if (octave == 2) return 0.143;
331
+ else if (octave == 3) return 0.202;
332
+ else if (octave == 4) return 0.287;
333
+ else if (octave == 5) return 0.406;
334
+ else if (octave == 6) return 0.575;
335
+ break;
336
+
337
+ case 'e': // D# or Eb
338
+ if (octave <= 1) return 0.0;
339
+ else if (octave == 2) return 0.148;
340
+ else if (octave == 3) return 0.208;
341
+ else if (octave == 4) return 0.296;
342
+ else if (octave == 5) return 0.418;
343
+ else if (octave == 6) return 0.593;
344
+ break;
345
+
346
+ case 'E': // E
347
+ if (octave <= 1) return 0.0;
348
+ else if (octave == 2) return 0.152;
349
+ else if (octave == 3) return 0.214;
350
+ else if (octave == 4) return 0.305;
351
+ else if (octave == 5) return 0.429;
352
+ else if (octave == 6) return 0.608;
353
+ break;
354
+
355
+ case 'F': // F
356
+ if (octave <= 1) return 0.0;
357
+ else if (octave == 2) return 0.155;
358
+ else if (octave == 3) return 0.220;
359
+ else if (octave == 4) return 0.314;
360
+ else if (octave == 5) return 0.441;
361
+ break;
362
+
363
+ case 'g': // F# or Gb
364
+ if (octave <= 1) return 0.0;
365
+ else if (octave == 2) return 0.160;
366
+ else if (octave == 3) return 0.227;
367
+ else if (octave == 4) return 0.323;
368
+ else if (octave == 5) return 0.454;
369
+ break;
370
+
371
+ case 'G': // G
372
+ if (octave <= 1) return 0.0;
373
+ else if (octave == 2) return 0.164;
374
+ else if (octave == 3) return 0.234;
375
+ else if (octave == 4) return 0.332;
376
+ else if (octave == 5) return 0.468;
377
+ break;
378
+
379
+ case 'a': // G# or Ab
380
+ if (octave <= 1) return 0.117;
381
+ else if (octave == 2) return 0.170;
382
+ else if (octave == 3) return 0.242;
383
+ else if (octave == 4) return 0.343;
384
+ else if (octave == 5) return 0.485;
385
+ break;
386
+ }
387
+
388
+ return 0.0;
389
+ }
390
+
391
+ #define KEY(name) key = (void *)&P->params.name
392
+
393
+ %%{
394
+ machine bloopserial;
395
+
396
+ action ival {
397
+ ival = ATOI(ts, p - ts);
398
+ }
399
+
400
+ action fval1 {
401
+ fval = ATOI(ts, p - ts) * 1.0f;
402
+ }
403
+
404
+ action fval2 {
405
+ fval = ATOI(pf, p - pf) * pow(0.1f, p - pf);
406
+ }
407
+
408
+ dec = [0-9]+ %fval1 "." %{ pf = p; } [0-9]+ %fval2;
409
+ float = ("-" dec %{ fval *= -1.0f; } | dec);
410
+ key = "volume" %{ KEY(volume); } |
411
+ "arp" %{ KEY(arp); } |
412
+ "aspeed" %{ KEY(aspeed); } |
413
+ "attack" %{ KEY(attack); } |
414
+ "decay" %{ KEY(decay); } |
415
+ "dslide" %{ KEY(dslide); } |
416
+ "freq" %{ KEY(freq); } |
417
+ "hpf" %{ KEY(hpf); } |
418
+ "hsweep" %{ KEY(hsweep); } |
419
+ "limit" %{ KEY(limit); } |
420
+ "lpf" %{ KEY(lpf); } |
421
+ "lsweep" %{ KEY(lsweep); } |
422
+ "phase" %{ KEY(phase); } |
423
+ "psweep" %{ KEY(psweep); } |
424
+ "repeat" %{ KEY(repeat); } |
425
+ "resonance" %{ KEY(resonance); } |
426
+ "slide" %{ KEY(slide); } |
427
+ "square" %{ KEY(square); } |
428
+ "sustain" %{ KEY(sustain); } |
429
+ "sweep" %{ KEY(sweep); } |
430
+ "punch" %{ KEY(punch); } |
431
+ "vibe" %{ KEY(vibe); } |
432
+ "vspeed" %{ KEY(vspeed); } |
433
+ "vdelay" %{ KEY(vdelay); } |
434
+ "volume" %{ KEY(volume); };
435
+
436
+ main := |*
437
+ key space+ float space* => { *((float *)key) = fval; };
438
+ "type" space+ "square" => { P->params.type = BLOOPS_SQUARE; };
439
+ "type" space+ "sawtooth" => { P->params.type = BLOOPS_SAWTOOTH; };
440
+ "type" space+ "sine" => { P->params.type = BLOOPS_SINE; };
441
+ "type" space+ "noise" => { P->params.type = BLOOPS_NOISE; };
442
+ space+;
443
+ *|;
444
+
445
+ write data nofinal;
446
+ }%%
447
+
448
+ bloopsaphone *
449
+ bloops_sound_file(bloops *B, char *fname)
450
+ {
451
+ FILE *fp;
452
+ struct stat stats;
453
+ int cs, act, len;
454
+ float fval;
455
+ void *key;
456
+ char *str, *p, *pe, *pf, *ts, *te, *eof = 0;
457
+ bloopsaphone *P;
458
+
459
+ if (stat(fname, &stats) == -1)
460
+ return NULL;
461
+
462
+ fp = fopen(fname, "rb");
463
+ if (!fp)
464
+ return NULL;
465
+
466
+ len = stats.st_size;
467
+ str = (char *)malloc(stats.st_size + 1);
468
+ if (fread(str, 1, stats.st_size, fp) != stats.st_size)
469
+ goto done;
470
+
471
+ p = str;
472
+ pe = str + len + 1;
473
+ p[len] = '\0';
474
+
475
+ P = bloops_square();
476
+ %% write init;
477
+ %% write exec;
478
+
479
+ done:
480
+ fclose(fp);
481
+ return P;
482
+ }
483
+
484
+ char *
485
+ bloops_sound_str(bloopsaphone *P)
486
+ {
487
+ char *lines = (char *)malloc(4096), *str = lines;
488
+ bloopsaphone *sq = bloops_square();
489
+ if (P->params.type == BLOOPS_SQUARE)
490
+ str += sprintf(str, "type square\n");
491
+ else if (P->params.type == BLOOPS_SAWTOOTH)
492
+ str += sprintf(str, "type sawtooth\n");
493
+ else if (P->params.type == BLOOPS_SINE)
494
+ str += sprintf(str, "type sine\n");
495
+ else if (P->params.type == BLOOPS_NOISE)
496
+ str += sprintf(str, "type noise\n");
497
+
498
+ if (P->params.volume != sq->params.volume)
499
+ str += sprintf(str, "volume %0.3f\n", P->params.volume);
500
+ if (P->params.punch != sq->params.punch)
501
+ str += sprintf(str, "punch %0.3f\n", P->params.punch);
502
+ if (P->params.attack != sq->params.attack)
503
+ str += sprintf(str, "attack %0.3f\n", P->params.attack);
504
+ if (P->params.sustain != sq->params.sustain)
505
+ str += sprintf(str, "sustain %0.3f\n", P->params.sustain);
506
+ if (P->params.decay != sq->params.decay)
507
+ str += sprintf(str, "decay %0.3f\n", P->params.decay);
508
+ if (P->params.freq != sq->params.freq)
509
+ str += sprintf(str, "freq %0.3f\n", P->params.freq);
510
+ if (P->params.limit != sq->params.limit)
511
+ str += sprintf(str, "limit %0.3f\n", P->params.limit);
512
+ if (P->params.slide != sq->params.slide)
513
+ str += sprintf(str, "slide %0.3f\n", P->params.slide);
514
+ if (P->params.dslide != sq->params.dslide)
515
+ str += sprintf(str, "dslide %0.3f\n", P->params.dslide);
516
+ if (P->params.square != sq->params.square)
517
+ str += sprintf(str, "square %0.3f\n", P->params.square);
518
+ if (P->params.sweep != sq->params.sweep)
519
+ str += sprintf(str, "sweep %0.3f\n", P->params.sweep);
520
+ if (P->params.vibe != sq->params.vibe)
521
+ str += sprintf(str, "vibe %0.3f\n", P->params.vibe);
522
+ if (P->params.vspeed != sq->params.vspeed)
523
+ str += sprintf(str, "vspeed %0.3f\n", P->params.vspeed);
524
+ if (P->params.vdelay != sq->params.vdelay)
525
+ str += sprintf(str, "vdelay %0.3f\n", P->params.vdelay);
526
+ if (P->params.lpf != sq->params.lpf)
527
+ str += sprintf(str, "lpf %0.3f\n", P->params.lpf);
528
+ if (P->params.lsweep != sq->params.lsweep)
529
+ str += sprintf(str, "lsweep %0.3f\n", P->params.lsweep);
530
+ if (P->params.resonance != sq->params.resonance)
531
+ str += sprintf(str, "resonance %0.3f\n", P->params.resonance);
532
+ if (P->params.hpf != sq->params.hpf)
533
+ str += sprintf(str, "hpf %0.3f\n", P->params.hpf);
534
+ if (P->params.hsweep != sq->params.hsweep)
535
+ str += sprintf(str, "hsweep %0.3f\n", P->params.hsweep);
536
+ if (P->params.arp != sq->params.arp)
537
+ str += sprintf(str, "arp %0.3f\n", P->params.arp);
538
+ if (P->params.aspeed != sq->params.aspeed)
539
+ str += sprintf(str, "aspeed %0.3f\n", P->params.aspeed);
540
+ if (P->params.phase != sq->params.phase)
541
+ str += sprintf(str, "phase %0.3f\n", P->params.phase);
542
+ if (P->params.psweep != sq->params.psweep)
543
+ str += sprintf(str, "psweep %0.3f\n", P->params.psweep);
544
+ if (P->params.repeat != sq->params.repeat)
545
+ str += sprintf(str, "repeat %0.3f\n", P->params.repeat);
546
+
547
+ bloops_sound_destroy(sq);
548
+ return lines;
549
+ }
@@ -0,0 +1,15 @@
1
+ require 'mkmf'
2
+ require 'fileutils'
3
+
4
+ $CFLAGS << " -I../../c #{ENV['CFLAGS']}"
5
+ $LIBS << " -lm -lportaudio #{ENV['LDFLAGS']}"
6
+
7
+ %w[notation.c bloopsaphone-internal.h bloopsaphone.c bloopsaphone.h].each do |fn|
8
+ fn = "../../c/#{fn}"
9
+ abort "!! ERROR !!\n** #{fn} not found; type 'make ruby' in the top directory\n\n" \
10
+ unless File.exist? fn
11
+ FileUtils.cp(fn, ".")
12
+ end
13
+
14
+ have_library("portaudio")
15
+ create_makefile("bloops")