ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/PDL-Audio/audio.pd
Revision: 1.8
Committed: Tue Nov 8 18:48:47 2005 UTC (18 years, 7 months ago) by root
Branch: MAIN
Changes since 1.7: +20 -15 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 $VERSION = '1.1';
2
3 pp_setversion $VERSION;
4 pp_beginwrap (); # force error with older PPs
5
6 pp_addpm {At => Top}, <<'EOD';
7 =head1 NAME
8
9 PDL::Audio - Some PDL functions intended for audio processing.
10
11 =head1 SYNOPSIS
12
13 use PDL;
14 use PDL::Audio;
15
16 =head1 DESCRIPTION
17
18 Oh well ;) Not much "introductory documentation" has been written yet :(
19
20 =head2 NOTATION
21
22 Brackets around parameters indicate that the respective parameter is
23 optional and will be replaced with some default value when absent (or
24 C<undef>, which might be different in other packages).
25
26 The sampling frequency and duration are by default (see individual
27 descriptions) given in cycles/sample (or samples in case of a
28 duration). That means if you want to specify a duration of two seconds,
29 you have to multiply by the sampling frequency in HZ, and if you want
30 to specify a frequency of 440 Hz, you have to divide by the sampling
31 frequency:
32
33 # Syntax: gen_oscil duration*, frequency/
34 $signal = gen_oscil 2*HZ, 440/HZ;
35 # with a sampling frequency of 44100 Hertz:
36 $signal = gen_oscil 2*44100, 440/44100;
37
38 To help you, the required unit is given as a type suffix in the parameter
39 name. A "/" means that you have to divide by the sampling frequency (to
40 convert from Hertz) and a suffix of "*" indicates that a multiplication is
41 required.
42
43 Most parameters named "size", "duration" (or marked with "*") can
44 be replaced by a piddle, which is then used to give length and from
45 (mono/stereo).
46
47 =head2 HEADER ATTRIBUTES
48
49 The following header attributes are stored and evaluated by most
50 functions. PDL::Audio provides mutator methods for all them (e.g.
51
52 print "samplerate is ", $pdl->rate;
53 $pdl->comment("set the comment to this string");
54
55 =over 4
56
57 =item rate
58
59 The sampling rate in hz.
60
61 =item filetype
62
63 The filetype (wav, au etc..). Must be one of:
64
65 FILE_NEXT FILE_AIFC FILE_RIFF FILE_BICSF FILE_NIST FILE_INRS FILE_ESPS
66 FILE_SVX FILE_VOC FILE_SNDT FILE_RAW FILE_SMP FILE_SD2 FILE_AVR
67 FILE_IRCAM FILE_SD1 FILE_SPPACK FILE_MUS10 FILE_HCOM FILE_PSION
68 FILE_MAUD FILE_IEEE FILE_DESKMATE FILE_DESKMATE_2500 FILE_MATLAB
69 FILE_ADC FILE_SOUNDEDIT FILE_SOUNDEDIT_16 FILE_DVSM FILE_MIDI
70 FILE_ESIGNAL FILE_SOUNDFONT FILE_GRAVIS FILE_COMDISCO FILE_GOLDWAVE
71 FILE_SRFS FILE_MIDI_SAMPLE_DUMP FILE_DIAMONDWARE FILE_REALAUDIO
72 FILE_ADF FILE_SBSTUDIOII FILE_DELUSION FILE_FARANDOLE FILE_SAMPLE_DUMP
73 FILE_ULTRATRACKER FILE_YAMAHA_SY85 FILE_YAMAHA_TX16 FILE_DIGIPLAYER
74 FILE_COVOX FILE_SPL FILE_AVI FILE_OMF FILE_QUICKTIME FILE_ASF
75 FILE_YAMAHA_SY99 FILE_KURZWEIL_2000 FILE_AIFF FILE_AU
76
77 =item path
78
79 The filename (or file specification) used to load or save a file.
80
81 =item format
82
83 Specifies the type the underlying file format uses. The samples will always be in short or long signed format.
84
85 Must be one of
86
87 FORMAT_NO_SND FORMAT_16_LINEAR FORMAT_8_MULAW FORMAT_8_LINEAR
88 FORMAT_32_FLOAT FORMAT_32_LINEAR FORMAT_8_ALAW FORMAT_8_UNSIGNED
89 FORMAT_24_LINEAR FORMAT_64_DOUBLE FORMAT_16_LINEAR_LITTLE_ENDIAN
90 FORMAT_32_LINEAR_LITTLE_ENDIAN FORMAT_32_FLOAT_LITTLE_ENDIAN
91 FORMAT_64_DOUBLE_LITTLE_ENDIAN FORMAT_16_UNSIGNED
92 FORMAT_16_UNSIGNED_LITTLE_ENDIAN FORMAT_24_LINEAR_LITTLE_ENDIAN
93 FORMAT_32_VAX_FLOAT FORMAT_12_LINEAR FORMAT_12_LINEAR_LITTLE_ENDIAN
94 FORMAT_12_UNSIGNED FORMAT_12_UNSIGNED_LITTLE_ENDIAN COMPATIBLE_FORMAT
95
96 PDL::Audio conviniently defines the following aliases for the following
97 constants, that are already correct for the host byteorder:
98
99 FORMAT_ULAW_BYTE FORMAT_ALAW_BYTE FORMAT_LINEAR_BYTE
100 FORMAT_LINEAR_SHORT FORMAT_LINEAR_USHORT FORMAT_LINEAR_LONG
101 FORMAT_LINEAR_FLOAT FORMAT_LINEAR_DOUBLE
102
103 =item comment
104
105 The file comment (if any).
106
107 =item device
108
109 The device to output audio. One of:
110
111 DEV_DEFAULT DEV_READ_WRITE DEV_ADAT_IN DEV_AES_IN DEV_LINE_OUT
112 DEV_LINE_IN DEV_MICROPHONE DEV_SPEAKERS DEV_DIGITAL_IN DEV_DIGITAL_OUT
113 DEV_DAC_OUT DEV_ADAT_OUT DEV_AES_OUT DEV_DAC_FILTER DEV_MIXER
114 DEV_LINE1 DEV_LINE2 DEV_LINE3 DEV_AUX_INPUT DEV_CD_IN DEV_AUX_OUTPUT
115 DEV_SPDIF_IN DEV_SPDIF_OUT
116
117 =back
118
119 =head2 EXPORTED CONSTANTS
120
121 In addition to the exported constants described above (and later in
122 the function descriptions), this module also exports the mathematical
123 constants M_PI and M_2PI, so watch out for clashes!
124
125 =cut
126 EOD
127
128 @cconsts = qw(
129 SNDLIB_DEFAULT_DEVICE SNDLIB_READ_WRITE_DEVICE
130 SNDLIB_ADAT_IN_DEVICE SNDLIB_AES_IN_DEVICE
131 SNDLIB_LINE_OUT_DEVICE SNDLIB_LINE_IN_DEVICE
132 SNDLIB_MICROPHONE_DEVICE SNDLIB_SPEAKERS_DEVICE
133 SNDLIB_DIGITAL_IN_DEVICE SNDLIB_DIGITAL_OUT_DEVICE
134 SNDLIB_DAC_OUT_DEVICE SNDLIB_ADAT_OUT_DEVICE
135 SNDLIB_AES_OUT_DEVICE SNDLIB_DAC_FILTER_DEVICE
136 SNDLIB_MIXER_DEVICE SNDLIB_LINE1_DEVICE
137 SNDLIB_LINE2_DEVICE SNDLIB_LINE3_DEVICE
138 SNDLIB_AUX_INPUT_DEVICE SNDLIB_CD_IN_DEVICE
139 SNDLIB_AUX_OUTPUT_DEVICE SNDLIB_SPDIF_IN_DEVICE
140 SNDLIB_SPDIF_OUT_DEVICE
141
142 SNDLIB_NO_SND SNDLIB_16_LINEAR SNDLIB_8_MULAW SNDLIB_8_LINEAR
143 SNDLIB_32_FLOAT SNDLIB_32_LINEAR SNDLIB_8_ALAW SNDLIB_8_UNSIGNED
144 SNDLIB_24_LINEAR SNDLIB_64_DOUBLE SNDLIB_16_LINEAR_LITTLE_ENDIAN
145 SNDLIB_32_LINEAR_LITTLE_ENDIAN SNDLIB_32_FLOAT_LITTLE_ENDIAN
146 SNDLIB_64_DOUBLE_LITTLE_ENDIAN SNDLIB_16_UNSIGNED
147 SNDLIB_16_UNSIGNED_LITTLE_ENDIAN SNDLIB_24_LINEAR_LITTLE_ENDIAN
148 SNDLIB_32_VAX_FLOAT SNDLIB_12_LINEAR SNDLIB_12_LINEAR_LITTLE_ENDIAN
149 SNDLIB_12_UNSIGNED SNDLIB_12_UNSIGNED_LITTLE_ENDIAN
150 SNDLIB_COMPATIBLE_FORMAT
151
152 NeXT_sound_file AIFC_sound_file RIFF_sound_file
153 BICSF_sound_file NIST_sound_file INRS_sound_file
154 ESPS_sound_file SVX_sound_file VOC_sound_file
155 SNDT_sound_file raw_sound_file SMP_sound_file
156 SD2_sound_file AVR_sound_file IRCAM_sound_file
157 SD1_sound_file SPPACK_sound_file MUS10_sound_file
158 HCOM_sound_file PSION_sound_file MAUD_sound_file
159 IEEE_sound_file DeskMate_sound_file DeskMate_2500_sound_file
160 Matlab_sound_file ADC_sound_file SoundEdit_sound_file
161 SoundEdit_16_sound_file DVSM_sound_file MIDI_file
162 Esignal_file soundfont_sound_file gravis_sound_file
163 comdisco_sound_file goldwave_sound_file srfs_sound_file
164 MIDI_sample_dump DiamondWare_sound_file RealAudio_sound_file
165 ADF_sound_file SBStudioII_sound_file Delusion_sound_file
166 Farandole_sound_file Sample_dump_sound_file Ultratracker_sound_file
167 Yamaha_SY85_sound_file Yamaha_TX16_sound_file digiplayer_sound_file
168 Covox_sound_file SPL_sound_file AVI_sound_file
169 OMF_sound_file Quicktime_sound_file asf_sound_file
170 Yamaha_SY99_sound_file Kurzweil_2000_sound_file AIFF_sound_file
171
172 BANDPASS DIFFERENTIATOR HILBERT
173 );
174
175 for (@cconsts) {
176 local $_ = $_;
177 s/^SNDLIB_(.*)_DEVICE/DEV_$1/;
178 s/^SNDLIB_/FORMAT_/;
179 s/MIDI_file/FILE_MIDI/;
180 s/MIDI_sample_dump/FILE_MIDI_sample_dump/;
181 s/Esignal_file/FILE_Esignal/;
182 s/(.*)_sound_file/FILE_$1/;
183 push @pconsts, uc $_;
184 }
185
186 @METHODS = qw(
187 waudio cut_leading_silence cut_trailing_silence cut_silence filter_zpolar
188 partials2polynomial scale2short filter_fir filter_iir rfft irfft spectrum
189 filter_comb filter_notch filter_allpass filter_src filter_granulate
190 filter_contrast_enhance describe_audio filter_center ring_modulate
191 amplitude_modulate filter_ppolar design_remez_fir concat
192 gen_oscil gen_sawtooth gen_square gen_triangle gen_asymmetric_fm gen_rand_1f
193 gen_env audiomix gen_sum_of_cosines gen_sine_summation gen_fft_window
194 gen_pulse_train gen_rand gen_from_table gen_from_partials partials2waveshape
195
196 );
197 @EXPORT = (qw(
198 sound_format_name sound_type_name raudio
199 FORMAT_ULAW_BYTE FORMAT_ALAW_BYTE FORMAT_LINEAR_BYTE
200 FORMAT_LINEAR_SHORT FORMAT_LINEAR_USHORT FORMAT_LINEAR_LONG
201 FORMAT_LINEAR_FLOAT FORMAT_LINEAR_DOUBLE
202 M_PI M_2PI
203 ), @METHODS, @pconsts);
204
205 for (@EXPORT) {
206 pp_add_exported "", $_;
207 }
208
209 pp_addpm "\@METHODS = qw(@METHODS);\n";
210
211 $addboot = "{\nHV *stash = gv_stashpvn(\"PDL::Audio\", 10, TRUE);";
212
213 for (0..$#cconsts) {
214 $addboot .= " newCONSTSUB(stash, \"$pconsts[$_]\", newSViv($cconsts[$_]));\n";
215 }
216 $addboot .= " newCONSTSUB(stash, \"M_PI\" , newSVnv(M_PI ));\n";
217 $addboot .= " newCONSTSUB(stash, \"M_2PI\", newSVnv(M_2PI));\n";
218 $addboot .= '}
219 mus_set_error_handler (mus_barfer);
220 ';
221
222 pp_add_boot $addboot;
223
224 pp_addpm <<'EOD';
225
226 use PDL::LiteF;
227 use PDL::Complex;
228 use Carp;
229
230 $bigendian = 0x55 == unpack "S", "\0U";
231
232 sub FORMAT_ULAW_BYTE (){ FORMAT_8_MULAW }
233 sub FORMAT_ALAW_BYTE (){ FORMAT_8_ALAW }
234 sub FORMAT_LINEAR_BYTE (){ FORMAT_8_UNSIGNED }
235 sub FORMAT_LINEAR_SHORT (){ $bigendian ? FORMAT_16_LINEAR : FORMAT_16_LINEAR_LITTLE_ENDIAN }
236 sub FORMAT_LINEAR_USHORT(){ $bigendian ? FORMAT_16_UNSIGNED : FORMAT_16_UNSIGNED_LITTLE_ENDIAN }
237 sub FORMAT_LINEAR_LONG (){ $bigendian ? FORMAT_32_LINEAR : FORMAT_32_LINEAR_LITTLE_ENDIAN }
238 sub FORMAT_LINEAR_FLOAT (){ $bigendian ? FORMAT_32_FLOAT : FORMAT_32_FLOAT_LITTLE_ENDIAN }
239 sub FORMAT_LINEAR_DOUBLE(){ $bigendian ? FORMAT_32_DOUBLE : FORMAT_32_DOUBLE_LITTLE_ENDIAN }
240
241 sub FILE_AU (){ FILE_NEXT }
242
243 # provide some accessor methods
244 for my $field (qw(rate comment filetype path format)) {
245 *{"PDL::$field"} = sub {
246 my $pdl = shift;
247 my $hdr = $pdl->gethdr;
248 if (@_) {
249 $hdr->{$field} = $_[0];
250 $pdl->sethdr($hdr);
251 };
252 $hdr->{$field};
253 };
254 }
255
256 =head2 sound_format_name format_code
257
258 Return the human-readable name of the file format with code C<format_code>.
259
260 =head2 sound_type_name type_code
261
262 Return the human-readable name of the sample type with code C<type_code>.
263
264 =head2 describe_audio piddle
265
266 Describe the audio stream contained in piddle and return it as a string. A
267 fresh piddle might return:
268
269 mono sound with 27411 samples
270
271 Whereas a freshly loaded soundfile might yield:
272
273 stereo sound with 27411 samples, original name "kongas.wav", type 2 (RIFF),
274 rate 11025/s (duration 2.49s), format 7 (8-bit unsigned)
275
276 =cut
277
278 sub describe_audio($) {
279 my $pdl = shift;
280 my ($channels, $samples) = $pdl->dims;
281 my $chan = $channels < 2 ? "mono" :
282 $channels == 2 ? "stereo" :
283 $channels == 4 ? "quad channel" :
284 "$channels channel";
285 my $desc = "$chan sound with $samples samples";
286 $desc .= sprintf ", original name \"%s\"", $pdl->path if $pdl->path;
287 $desc .= sprintf ", type %d (%s)", $pdl->filetype, sound_type_name($pdl->filetype) if $pdl->filetype;
288 $desc .= sprintf ", rate %d/s (duration %.2fs)", $pdl->rate, $samples/$pdl->rate if $pdl->rate;
289 $desc .= sprintf ", format %d (%s)", $pdl->format, sound_format_name($pdl->format) if $pdl->format;
290 $desc
291 }
292
293 =head2 raudio path, [option-hash], option => value, ...
294
295 Reads audio data into the piddle. Options can be anything, most useful
296 values are C<filetype>, C<rate>, C<channels> and C<format>. The returned
297 piddle is represents "time" in the outer dimension, and samples in the
298 inner (i.e. scalars for mono files, 2-vectors for stereo files).
299
300 # read any file
301 $pdl = raudio "file.wav";
302 # read a file. if it is a raw file preset values
303 $pdl = raudio "file.raw", filetype => FILE_RAW, rate => 44100, channels => 2;
304
305 =head2 waudio pdl, [option-hash], option => value, ...
306
307 Writes a pdl as a file. The path is taken from the header (or the options), e.g.:
308
309 # write a file, using the header of another piddle
310 $pdl->waudio ($orig_file->gethdr);
311 # write pdl as au file, take rate from the header
312 $pdl->waudio (path => "piddle.au", filetype => FILE_AU, format => FORMAT_16_LINEAR;
313
314 =cut
315
316 # read a sound file
317 sub raudio {
318 my $path = shift;
319 my %hdr = (); %hdr = (%hdr, %{+shift}) if ref $_[0]; %hdr = (%hdr, @_) if @_;
320
321 # for raw files this is necessary
322 mus_set_raw_header_defaults $hdr{rate} || 44100,
323 $hdr{channels}|| 1,
324 $hdr{format} || FORMAT_LINEAR_SHORT;
325
326 $hdr{path} = $path;
327 $hdr{filetype} = sound_header_type $path;
328 $hdr{filetype} >= 0 or barf "$path: ".audio_error_name audio_error;
329 $hdr{rate} = sound_srate $path;
330 $hdr{comment} = sound_comment $path if defined sound_comment $path;
331 $hdr{format} = sound_data_format $path;
332 my $channels = sound_chans $path;
333 my $frames = sound_frames $path;
334
335 my $fd = open_sound_input $path; $fd >= 0 or barf "$path: ".audio_error_name audio_error;
336
337 my $pdl = zeroes long, $frames, $channels;
338 $pdl->clump(-1)->read_sound ($fd, $channels, $frames)
339 >= 0 or barf "$path: ".audio_error_name audio_error;
340 (close_sound_input $fd) >= 0 or barf "$path: ".audio_error_name audio_error;
341 $pdl = $pdl->short->xchg(0,1);
342 $pdl = $pdl->clump(2) if $channels == 1;
343 $pdl->sever;
344 $pdl->sethdr(\%hdr);
345 $pdl
346 }
347
348 sub _audio_make_plain {
349 my $pdl = shift;
350 if ($pdl->getndims == 1) {
351 ($pdl, 1, $pdl->getdim(0))
352 } else {
353 ($pdl->xchg(0,1)->clump(-1), $pdl->dims)
354 }
355 }
356
357 sub waudio {
358 my $pdl = shift;
359 my %hdr = %{$pdl->gethdr || {}}; %hdr = (%hdr, %{+shift}) if ref $_[0]; %hdr = (%hdr, @_) if @_;
360 my ($channels, $frames);
361
362 $hdr{filetype} = FILE_NEXT unless exists $hdr{filetype};
363 $hdr{format} ||= FORMAT_16_LINEAR;
364 $hdr{rate} ||= 44100;
365
366 ($pdl, $channels, $frames) = _audio_make_plain $pdl->convert(long);
367
368 1 <= $channels && $channels <= 2
369 or croak "can only write mono or stereo (one or two channel) files, not $channels channel files\n";
370
371 my $fd = open_sound_output $hdr{path}, $hdr{rate}, $channels, $hdr{format}, $hdr{filetype}, $hdr{comment};
372 $fd >= 0 or barf "$hdr{$path}: ".audio_error_name audio_error;
373 $pdl->clump(-1)->write_sound($fd, $channels, $frames)
374 >= 0 or barf "$path: ".audio_error_name audio_error;
375 (close_sound_output $fd, mus_samples2bytes $hdr{format}, $frames * $channels)
376 >= 0 or barf "$hdr{$path}: ".audio_error_name audio_error;
377 }
378
379 =head2 cut_leading_silence pdl, level
380
381 Cuts the leading silence (i.e. all samples with absolute value < level)
382 and returns the resulting part.
383
384 =head2 cut_trailing_silence pdl, level
385
386 Cuts the trailing silence.
387
388 =head2 cut_silence pdl, level
389
390 Calls C<cut_leading_silence> and C<cut_trailing_silence> and returns the
391 result.
392
393 =cut
394
395 sub cut_leading_silence {
396 my $pdl = shift;
397 my $level = 1*shift;
398 my $skip = which (abs($pdl) > $level);
399 $skip = $skip->nelem ? $skip->at(0) : 0;
400 $pdl->slice("$skip:-1")
401 }
402
403 sub cut_trailing_silence {
404 my $pdl = shift;
405 my $level = 1*shift;
406 $level = 400000;
407 my $skip = which (abs($pdl) > $level);
408 $skip = $skip->nelem ? $skip->at(-1) : -1;
409 $skip-- if $skip > 0;
410 $pdl->slice("0:$skip")
411 }
412
413 sub cut_silence {
414 $_[0]->cut_leading_silence($_[1])
415 ->cut_trailing_silence($_[1])
416 }
417
418 # have we been a bad boy?
419
420 for (@METHODS) {
421 *{"PDL::$_"} = \&$_;
422 }
423 EOD
424
425 for my $d (qw(read write)) {
426 pp_def($d.'_sound',
427 Pars => ($d eq "read" ? '[o]' : '') . 'sample(n)',
428 OtherPars => "int fd; int chans; int frames",
429 GenericTypes => [L],
430 PMCode => '',
431 Doc => undef,
432 Code => q@
433 int chans = $COMP(chans);
434 int frames = $COMP(frames);
435 int **bufs = malloc (chans * sizeof (int *));
436 int i;
437 dSP;
438
439 /* hmm... */
440 assert (sizeof (PDL_long) == sizeof (int));
441
442 for (i = 0; i < chans; i++)
443 bufs[i] = (int *)$P(sample) + i * frames;
444
445 XPUSHs (sv_2mortal (newSViv (@.$d.q@_sound ($COMP(fd), 0, frames - 1, chans, bufs))));
446
447 free (bufs);
448 @,
449 );
450 }
451
452 pp_addhdr <<'EOH';
453 #include "sndlib/sndlib.h"
454 #include "xlib.h"
455
456 static void
457 mus_barfer (int err_type, char *err_msg)
458 {
459 barf ("%s [MUSERR]", err_msg);
460 }
461
462 EOH
463
464 pp_addpm <<'EOP';
465
466 =head2 playaudio pdl, [option-hash], option => value ...
467
468 Play the piddle as an audio file. Options can be supplied either through
469 the option hash (a hash-reference), through the pdl header or the options:
470
471 # play a piddle that has a valid header (e.g. from raudio)
472 $pdl->playaudio;
473 # play it with a different samplerate
474 $pdl->playaudio(rate => 22050);
475
476 =cut
477
478 EOP
479
480 pp_def('playaudio',
481 Pars => 'sample(n)',
482 OtherPars => "int srate; int chans; int format; int dev; int bufsize",
483 GenericTypes => [B,U,S,L],
484 Doc => undef,
485 PMCode => q!
486 sub PDL::playaudio {
487 my $pdl = shift;
488 my %hdr = %{$pdl->gethdr || {}}; %hdr = (%hdr, %{+shift}) if ref $_[0]; %hdr = (%hdr, @_) if @_;
489 my $chans;
490 $hdr{format} ||= FORMAT_LINEAR_SHORT;
491 ($pdl, $chans) = _audio_make_plain $pdl;
492 &PDL::_playaudio_int($pdl,
493 $hdr{rate} || 44100,
494 $chans,
495 $hdr{format},
496 $hdr{device} || DEV_DEFAULT,
497 $hdr{bufsize} || 32768);
498 }
499 !,
500 Code => q@
501 int ns;
502 int line;
503
504 if (sizeof ($GENERIC ()) != mus_format2bytes ($COMP(format)))
505 barf ("pdl datatype and selected audio format do not match");
506
507 if (initialize_audio () < 0)
508 barf ("playaudio: %s", audio_error_name (audio_error ()));
509
510 if ((line = open_audio_output ($COMP(dev), $COMP(srate), $COMP(chans), $COMP(format), $COMP(bufsize))) < 0)
511 barf ("playaudio: %s", audio_error_name (audio_error ()));
512
513 ns = $SIZE(n);
514 threadloop %{
515 if (write_audio (line, (void *)$P(sample), ns * sizeof ($GENERIC())))
516 barf ("playaudio: %s", audio_error_name (audio_error ()));
517 %}
518 if (close_audio (line) < 0)
519 barf ("playaudio: %s", audio_error_name (audio_error ()));
520 @,
521 );
522
523 pp_def 'ulaw2linear',
524 Pars => 'byte u(n); short [o] s(n)',
525 GenericTypes => [B],
526 Doc => 'conversion from (m)u-law into signed, linear, 16 bit samples (rather slow)',
527 Code => q! loop(n) %{ $s() = st_ulaw_to_linear ($u()); %} !,
528 ;
529
530 pp_def 'linear2ulaw',
531 Pars => 'short s(n); byte [o] u(n)',
532 GenericTypes => [S],
533 Doc => 'conversion from signed, linear, 16 bit samples into (m)u-law (rather slow)',
534 Code => q! loop(n) %{ $u() = st_linear_to_ulaw ($s()); %} !,
535 ;
536
537 pp_def 'alaw2linear',
538 Pars => 'byte u(n); short [o] s(n)',
539 GenericTypes => [B],
540 Doc => 'conversion from A-law into signed, linear, 16 bit samples (rather slow)',
541 Code => q! loop(n) %{ $s() = st_Alaw_to_linear ($u()); %} !,
542 ;
543
544 pp_def 'linear2alaw',
545 Pars => 'short s(n); byte [o] u(n)',
546 GenericTypes => [S],
547 Doc => 'conversion from signed, linear, 16 bit samples into A-law (rather slow)',
548 Code => q! loop(n) %{ $u() = st_linear_to_Alaw ($s()); %} !,
549 ;
550
551 pp_addpm <<'EOP';
552
553 =head2 gen_oscil duration*, freq/, phase-mod, [fm-mod/]
554
555 =head2 gen_sawtooth duration*, freq/, phase-mod, [fm-mod/]
556
557 =head2 gen_square duration*, freq/, phase-mod, duty, [fm-mod/]
558
559 =head2 gen_triangle duration*, freq/, phase-mod, [fm-mod/]
560
561 =head2 gen_pulse_train duration*, freq/, phase-mod, [fm-mod/]
562
563 =head2 gen_rand duration*, freq/
564
565 =head2 gen_rand_1f duration*
566
567 All of these functions generate appropriate waveforms with frequency
568 C<freq> (cycles/sample) and phase C<phase> (0..1).
569
570 The C<duration> might be either a piddle (which gives the form of the
571 output) or the number of samples to generate.
572
573 The output samples are between -1 and +1 (i.e. "-1 <= s <= +1").
574
575 The C<duty> parameter of the square generator influences the duty cycle of
576 the signal. Zero means 50%-50%, 0.5 means 75% on, 25% off, -0.8 means 10%
577 on, 90% off etc... Of course, the C<duty> parameter might also be a vector
578 of size C<duration>.
579
580 =cut
581
582 # return the time offset for duration, freq, phase, fm_mod
583 sub _dur2time($$;$$) {
584 my ($dur, $freq, $phase, $fm_mod) = @_;
585 zeroes($dur)->xvals*$freq+cumusumover($fm_mod)+$phase
586 }
587
588 sub gen_oscil($$;$$) {
589 my ($dur, $freq, $phase, $fm_mod) = @_;
590 sin M_2PI*_dur2time($dur,$freq,$phase,$fm_mod);
591 }
592
593 sub gen_sawtooth($$;$$) {
594 my ($dur, $freq, $phase, $fm_mod) = @_;
595 # maybe better use abs?
596 $freq = $fm_mod * $freq if defined $fm_mod;
597 (_dur2time($dur,$freq,$phase,$fm_mod) % 1 - 0.5) * 2;
598 }
599
600 sub gen_square($$;$$$) {
601 my ($dur, $freq, $phase, $duty, $fm_mod) = @_;
602 (gen_sawtooth($dur,$freq,$phase,$fm_mod) < $duty) * 2 - 1;
603 }
604
605 sub gen_triangle($$;$$) {
606 my ($dur, $freq, $phase, $fm_mod) = @_;
607 my $st = gen_sawtooth($dur,$freq,$phase,$fm_mod);
608 $st * (($st < 0) * 2 - 1);
609 }
610
611 sub gen_pulse_train($$;$$) {
612 my ($dur, $freq, $phase, $fm_mod) = @_;
613 my $st = gen_sawtooth($dur,$freq,$phase,$fm_mod);
614 $st < $st->rotate(1);
615 }
616
617 sub gen_rand($$) {
618 my ($dur, $freq) = @_;
619 my $z = zeroes $dur;
620 my $r = (random zeroes $freq * $z->getdim(0) + 1) * 2 - 1;
621 $r->index($freq * $z->xvals)->sever;
622 }
623
624 sub gen_rand_1f($) {
625 my ($dur) = @_;
626 $dur = zeroes $dur;
627 _gen_noise_1f($dur);
628 $dur;
629 }
630
631 =head2 gen_env duration*, xvals, yvals, [base]
632
633 Generates an interpolated envelope between the points given by xvals and
634 yvals. When base == 1 (the default) then the values will be linearly
635 interpolated, otherwise they follow an exponential curve that is bend
636 inwards (base < 1) or outwards (base > 1).
637
638 # generate a linear envelope with attack in the first 10%
639 gen_env 5000, [0 1 2 9 10], [0 1 0.6 0.6 0];
640
641 =cut
642
643 sub gen_env($;@) {
644 my ($dur, $x, $y, $base) = @_;
645 my $pdl = zeroes $dur;
646 $base ||= 1;
647 $base == 1 or barf "base values != 1 are not yet implemented\n";
648 my @x = ref $x eq "ARRAY" ? @$x : $x->list;
649 my @y = ref $y eq "ARRAY" ? @$y : $y->list;
650 my $f = $pdl->getdim(0) / $x[-1];
651 @x == @y or barf "gen_env: x and y lists have different sizes";
652 my $x0 = 0;
653 my $y0 = 0;
654 for (0..$#x) {
655 my ($x,$y) = (int($x[$_]*$f),$y[$_]);
656 if ($x > $x0) {
657 my $a = $pdl->slice("$x0:".($x-1));
658 if ($y0 != $y) {
659 $a .= $a->xlinvals($y0,$y);
660 } else {
661 $a .= $y;
662 }
663 }
664 ($x0,$y0)=($x,$y);
665 }
666 $pdl;
667 }
668
669 =head2 gen_adsr duration*, sustain-level, attack-time, decay-time, sustain-time, release-time
670
671 Simple ADSR envelope generator. The C<sustain-level> is the amplitude (0
672 to 1) of the sustain level. The other for parameters give the relative
673 interval times, in any unit you like, only their relative ratios
674 are important. Any of these times might be zero, in which case the
675 corresponding part is omitted from the envelope.
676
677 =cut
678
679 sub gen_adsr($$$$$$) {
680 my ($dur, $amp, $a, $d, $s, $r) = @_;
681 my (@x, @y);
682 my $t = 0;
683 push @x, 0; push @y, 0;
684 if ($a > 0) { push @x, $t += $a; push @y, 1 } else { $y[-1] = 1 }
685 if ($d > 0) { push @x, $t += $d; push @y, $amp } else { $y[-1] = $amp }
686 if ($s > 0) { push @x, $t += $s; push @y, $amp } else { $y[-1] = $amp }
687 if ($r > 0) { push @x, $t += $r; push @y, 0 } else { $y[-1] = 0 }
688 zeroes($dur)->xlinvals(0,$t)->linear_interpolate(pdl(@x), pdl(@y));
689 }
690
691 =head2 gen_asymmetric_fm duration*, freq/, phase, [r , [ratio]]
692
693 C<gen_asymmetric_fm> provides a way around the symmetric spectra normally
694 produced by FM. See Palamin and Palamin, "A Method of Generating and
695 Controlling Asymmetrical Spectra" JAES vol 36, no 9, Sept 88, p671-685.
696
697 =cut
698
699 sub gen_asymmetric_fm($$;$$$) {
700 my ($dur, $freq, $phase, $r, $ratio) = @_;
701 $r ||= 1;
702 $ratio ||= 1;
703 my $cosr = 0.5 * ($r - 1/$r);
704 my $sinr = 0.5 * ($r + 1/$r);
705 $phase = zeroes($dur)->xvals*$freq + $phase;
706 my $mth = $phase * $ratio;
707 exp ($cosr * cos $mth) * sin ($phase + $sinr * sin $mth);
708 }
709
710 =head2 gen_sum_of_cosines duration*, freq/, phase, ncosines, [fm_mod/]
711
712 Generates a sum of C<n> cosines C<(1 + 2(cos(x) + cos(2x) + ... cos(nx)) =
713 sin((n+.5)x) / sin(x/2))>. Other arguments are similar to to C<gen_oscil>.
714
715 =head2 gen_sine_summation duration*, freq/, phase, [nsines, [a, [b_ratio, [fm_mod/]]]]
716
717 C<gen_sine_summation> provides a kind of additive synthesis. See
718 J.A.Moorer, "Signal Processing Aspects of Computer Music" and "The
719 Synthesis of Complex Audio Spectra by means of Discrete Summation
720 Formulae" (Stan-M-5). The basic idea is very similar to that used in
721 gen_sum_of_cosines generator.
722
723 The default value for C<nsines> is 1 (but zero is a valid value), for C<a>
724 is 0.5 and for C<b_ratio> is 1.
725
726 (btw, either my formula is broken or the output indeed does not lie
727 between -1 and +1, but rather -5 .. +5).
728
729 =cut
730
731 sub gen_sum_of_cosines {
732 my ($dur, $freq, $phase, $cosines, $fm_mod) = @_;
733 $cosines ||= 1;
734 $dur = M_2PI*_dur2time($dur,$freq,$phase+1e-6,$fm_mod);
735 sin ($dur * ($cosines+0.5)) / (sin ($dur * 0.5) * (1+2*$cosines));
736 }
737
738 sub gen_sine_summation {
739 my ($dur, $freq, $phase, $n, $a, $ratio, $fm_mod) = @_;
740 $n = 1 unless defined $n;
741 $a ||= 0.5;
742 $ratio ||= 1;
743
744 $dur = M_2PI*_dur2time($dur,$freq,$phase+1e-6,$fm_mod);
745 my $an = $n ? $a**($n+1) : 0;
746 my $B = $dur * $ratio;
747 (sin ($dur)
748 - $a * sin ($dur - $B)
749 - $an * (sin ($dur + $B*($n+1))
750 - $a * sin ($dur + $B*$n)))
751 / (1 + $a*$a - (2*$a*cos($B)));
752 }
753
754 =head2 gen_from_table duration*, frequency/, table, [phase], [fm_mod/]
755
756 C<gen_from_table> generates a waveform by repeating a waveform given
757 in C<table>, linearly interpolating between successive points of the
758 C<waveform>.
759
760 =head2 partials2waveshape size*, partials, amplitudes, [phase], [fm_mod/]
761
762 Take a (perl or pdl) list of (integer) C<partials> and a list of
763 C<amplitudes> and generate a single wave shape that results by adding
764 these partial sines.
765
766 This could (and should) be used by the C<gen_from_table> generator.
767
768 =head2 gen_from_partials duration*, frequency/, partials, amplitudes, [phase], [fm_mod/]
769
770 Take a (perl list or pdl) list of (possibly noninteger) C<partials> and a
771 list of C<amplitudes> and generate the waveform resulting by summing up
772 all these partial sines.
773
774 =cut
775
776 sub gen_from_table {
777 my ($dur, $freq, $table, $phase, $fm_mod) = @_;
778 my $r = _dur2time($dur,$freq,$phase,$fm_mod);
779 my $tx = ($table->dims)[0];
780 $table = cat($table, $table->at(0))->clump(-1);
781 $r %= 1; $r += 1; $r %= 1; $r *= $tx;
782 $r->linear_interpolate($table->xvals, $table);
783 }
784
785 sub partials2waveshape {
786 my $dur = zeroes $_[0];
787 my @idx = ref $_[1] eq "ARRAY" ? @{$_[1]} : $_[1]->list;
788 my @amp = ref $_[2] eq "ARRAY" ? @{$_[2]} : $_[2]->list;
789
790 my $angle = $dur->xvals * (M_2PI/$dur->getdim(0));
791
792 for (0..$#idx) {
793 $dur += $amp[$_] * sin $idx[$_]*$angle;
794 }
795 $dur / max abs $dur;
796 }
797
798 sub gen_from_partials {
799 my ($dur, $freq, $ti, $ta, $phase, $fm_mod) = @_;
800 my @idx = ref $ti eq "ARRAY" ? @{$ti} : $ti->list;
801 my @amp = ref $ta eq "ARRAY" ? @{$ta} : $ta->list;
802
803 $dur = zeroes $dur;
804
805 my $angle = _dur2time($dur,$freq,$phase,$fm_mod);
806
807 for (0..$#idx) {
808 $dur += $amp[$_] * fast_sin($idx[$_]*$angle);
809 }
810 $dur / max abs $dur;
811 }
812
813 EOP
814
815 pp_def '_gen_noise_1f',
816 Pars => '[o]f(n)',
817 GenericTypes => [F,D],
818 Doc => undef,
819 PMCode => undef,
820 Code => q^
821 static unsigned long ctz[64] = {
822 6, 0, 1, 0, 2, 0, 1, 0,
823 3, 0, 1, 0, 2, 0, 1, 0,
824 4, 0, 1, 0, 2, 0, 1, 0,
825 3, 0, 1, 0, 2, 0, 1, 0,
826 5, 0, 1, 0, 2, 0, 1, 0,
827 3, 0, 1, 0, 2, 0, 1, 0,
828 4, 0, 1, 0, 2, 0, 1, 0,
829 3, 0, 1, 0, 2, 0, 1, 0,
830 };
831 static unsigned int dice[7];
832 static unsigned int total = 0;
833 unsigned int n, prevrand, newrand, seed, k;
834
835 threadloop %{
836 for (n = 0; n < $SIZE(n); n++)
837 {
838 k = ctz [n & 63];
839
840 prevrand = dice[k];
841 seed = 1664525 * seed + 1013904223;
842 newrand = (seed >> 3);
843 dice[k] = newrand;
844
845 total += newrand - prevrand;
846
847 seed = 1103515245 * seed + 12345;
848 newrand = (seed >> 3);
849
850 $f(n=>n) = ($GENERIC())(total + newrand) * (1./(3<<29)) - 1;
851 }
852 %}
853 ^
854 ;
855
856 pp_def 'filter_one_zero',
857 Pars => 'in(n); [o] out(n)',
858 OtherPars => 'double a0; double a1',
859 GenericTypes => [F,D],
860 Doc => 'apply a one zero filter, y(n) = a0 x(n) + a1 x(n-1)',
861 Code => q!
862 double a0 = $COMP(a0);
863 double a1 = $COMP(a1);
864 double x1 = 0.;
865
866 threadloop %{ loop(n) %{
867 $out() = a0 * $in() + a1 * x1;
868 x1 = $in();
869 %} %}
870 !
871 ;
872
873 pp_def 'filter_one_pole',
874 Pars => 'in(n); [o] out(n)',
875 OtherPars => 'double a0; double b1',
876 GenericTypes => [F,D],
877 Doc => 'apply a one pole filter, y(n) = a0 x(n) - b1 y(n-1)',
878 Code => q!
879 double a0 = $COMP(a0);
880 double b1 = $COMP(b1);
881 double y2 = 0.;
882
883 threadloop %{ loop(n) %{
884 $out() = y2 = a0 * $in() - b1 * y2;
885 %} %}
886 !
887 ;
888
889 pp_def 'filter_two_zero',
890 Pars => 'in(n); [o] out(n)',
891 OtherPars => 'double a0; double a1; double a2',
892 GenericTypes => [F,D],
893 Doc => 'apply a two zero filter, y(n) = a0 x(n) + a1 x(n-1) + a2 x(n-2)',
894 Code => q!
895 double a0 = $COMP(a0);
896 double a1 = $COMP(a1);
897 double a2 = $COMP(a2);
898 double x1 = 0.;
899 double x2 = 0.;
900
901 threadloop %{ loop(n) %{
902 $out() = a0 * $in() + a1 * x1 + a2 * x2;
903 x2 = x1; x1 = $in();
904 %} %}
905 !
906 ;
907
908 pp_def 'filter_two_pole',
909 Pars => 'in(n); [o] out(n)',
910 OtherPars => 'double a0; double b1; double b2',
911 GenericTypes => [F,D],
912 Doc => 'apply a two pole filter, y(n) = a0 x(n) - b1 y(n-1) - b2 y(n-2)',
913 Code => q!
914 double a0 = $COMP(a0);
915 double b1 = $COMP(b1);
916 double b2 = $COMP(b2);
917 double y1 = 0.;
918 double y2 = 0.;
919
920 threadloop %{ loop(n) %{
921 $out() = a0 * $in() - b1 * y1 - b2 * y2;
922 y2 = y1; y1 = $out();
923 %} %}
924 !
925 ;
926
927 pp_def 'filter_formant',
928 Pars => 'in(n); [o] out(n)',
929 OtherPars => 'double radius; double frequency; double gain',
930 GenericTypes => [F,D],
931 Doc => 'apply a formant filter, y(n) = x(n) - r*x(n-2) + 2*r*cos(2*pi*frequency)*y(n-1) - r*r*y(n-2). A good value for C<gain> is 1.',
932 Code => q!
933 threadloop %{
934 double radius = 1. - $COMP(radius);
935 double a0 = $COMP(gain) * sin ($COMP(frequency) * M_2PI) * (1. - (radius * radius));
936 double a2 = -radius;
937 double b1 = -2. * radius * cos ($COMP(frequency) * M_2PI);
938 double b2 = radius * radius;
939 double x1 = 0.;
940 double x2 = 0.;
941 double y1 = 0.;
942 double y2 = 0.;
943
944 loop(n) %{
945 double inval = a0 * $in();
946 $out() = inval + a2 * x2 - b1 * y1 - b2 * y2;
947 y2 = y1; y1 = $out();
948 x2 = x1; x1 = inval;
949 %}
950 %}
951 !
952 ;
953
954 pp_addpm <<'EOP';
955
956 =head2 filter_ppolar pdl, radius/, frequency/
957
958 apply a two pole filter (given in polar form). The filter has two poles,
959 one at (radius,frequency), the other at (radius,-frequency). Radius
960 is between 0 and 1 (but less than 1), and frequency is between 0 and
961 0.5. This is the standard resonator form with poles specified by the
962 polar coordinates of one pole.
963
964 =head2 filter_zpolar pdl, radius/, frequency/
965
966 apply a two zero filter (given in polar form). See C<filter_ppolar>.
967
968 =cut
969
970 sub filter_ppolar {
971 my ($pdl, $radius, $freq) = @_;
972 $pdl->filter_two_pole(1, -2*$radius*cos($freq*M_2PI), $radius**2);
973 }
974
975 sub filter_zpolar {
976 my ($pdl, $radius, $freq) = @_;
977 $pdl->filter_two_zero(1, -2*$radius*cos($freq*M_2PI), $radius**2);
978 }
979
980 =head2 partials2polynomial partials, [kind]
981
982 C<partials2polynomial> takes a list of harmonic amplitudes and returns a
983 list of Chebychev polynomial coefficients. The argument C<kind> determines
984 which kind of Chebychev polynomial we are interested in, 1st kind or 2nd
985 kind. (default is 1).
986
987 =cut
988
989 sub partials2polynomial {
990 my ($partials, $kind) = @_;
991 $kind = 1 unless defined $kind;
992 my $t0 = zeroes($partials->getdim(0)+1);
993 my $t1 = $t0->copy;
994 my $cc1 = $t0->copy;
995
996 $t0->set(0,1);
997 $t1->set(1,$kind);
998
999 for my $amp ($partials->list) {
1000 $cc1 += $amp * $t1;
1001 $tn = 2 * $t1->rshift(1) - $t0;
1002 $t0 = $t1;
1003 $t1 = $tn;
1004 }
1005 $cc1;
1006 }
1007
1008 =head2 ring_modulate in1, in2
1009
1010 ring modulates in1 with in2 (this is just a multiply).
1011
1012 =head2 amplitude_modulate am_carrier, in1, in2
1013
1014 amplitude modulates am_carrier and in2 with in1 (this calculates in1 * (am_carrier + in2)).
1015
1016 =cut
1017
1018 sub ring_modulate {
1019 $_[0] * $_[1];
1020 }
1021
1022 sub amplitude_modulate {
1023 $_[1] * ($_[0] + $_[2]);
1024 }
1025
1026 EOP
1027
1028 pp_def 'filter_sir',
1029 Pars => 'x(n); a(an); b(bn); [o]y(n)',
1030 GenericTypes => [L,F,D],
1031 Doc => <<'EOD',
1032 Generic (short delay) impulse response filter. C<x> is the input signal
1033 (which is supposed to be zero for negative indices). C<a> contains the
1034 input (x) coefficients (a0, a1, .. an), whereas C<b> contains the output
1035 (y) coefficients (b0, b1, ... bn), i.e.:
1036
1037 y(n) = a0 x(n) - b1 y(n-1) + a1 x(n-1) - b2 y(n-2) + a2 x(n-2) - b3 ...
1038
1039 This can be used to generate fir and iir filters of any length, or even
1040 more complicated constructs.
1041
1042 C<b0> (then first element of C<b>) is being ignored currently AND SHOULD
1043 BE SPECIFIED AS ONE FOR FUTURE COMPATIBILITY
1044 EOD
1045 Code => q^
1046 int an = $SIZE(an);
1047 int bn = $SIZE(bn);
1048 int i, n, I;
1049 $GENERIC() v;
1050
1051 threadloop %{
1052 for (n = 0; n < $SIZE(n); n++)
1053 {
1054 v = 0;
1055
1056 /* apply loop splitting manually later! */
1057
1058 for (i = 0; i < an; i++)
1059 {
1060 I = n-i;
1061 if (I >= 0)
1062 v += $a(an=>i) * $x(n=>I);
1063 }
1064
1065 for (i = 0; i < bn; i++)
1066 {
1067 I = n-i;
1068 if (I >= 0)
1069 v -= $b(bn=>i) * $y(n=>I);
1070 }
1071
1072 $y(n=>n) = v;
1073 }
1074 %}
1075 ^
1076 ;
1077
1078 pp_def 'filter_lir',
1079 Pars => 'x(n); int a_x(an); a_y(an); int b_x(bn); b_y(bn); [o]y(n)',
1080 GenericTypes => [L,F,D],
1081 Doc => <<'EOD',
1082
1083 Generic (long delay) impulse response filter. The difference to
1084 C<filter_sir> is that the filter coefficients need not be consecutive,
1085 but instead their indices are given by the C<a_x> and C<b_x> (integer)
1086 vectors, while the corresponding coefficients are in C<a_y> and
1087 C<b_y>. (All C<a_x> must be >= 0, while all the C<b_x> must be >= 1, as
1088 you should expect).
1089
1090 See C<filter_sir> for more info.
1091 EOD
1092 Code => q^
1093 int i, n, I;
1094 $GENERIC() v;
1095
1096 threadloop %{
1097 for (n = 0; n < $SIZE(n); n++)
1098 {
1099 v = 0;
1100
1101 loop(an) %{
1102 I = n-$a_x();
1103 if (I >= 0)
1104 v += $a_y() * $x(n=>I);
1105 %}
1106
1107 loop(bn) %{
1108 I = n-$b_x();
1109 if (I >= 0)
1110 v -= $b_y() * $y(n=>I);
1111 %}
1112
1113 $y(n=>n) = v;
1114 }
1115 %}
1116 ^
1117 ;
1118
1119 pp_addpm <<'EOP';
1120
1121 =head2 filter_fir input, xcoeffs
1122
1123 Apply a fir (finite impulse response) filter to C<input>. This is the same as
1124 calling:
1125
1126 filter_sir input, xcoeffs, pdl()
1127
1128 =head2 filter_iir input, ycoeffs
1129
1130 Apply a iir (infinite impulse response) filter to C<input>. This is just
1131 another way of saying:
1132
1133 filter_sir input, pdl(1), ycoeffs
1134
1135 That is, the first member of C<ycoeffs> is being ignored AND SHOULD BE
1136 SPECIFIED AS ONE FOR FUTURE COMPATIBILITY!
1137
1138 =cut
1139
1140 sub filter_fir($$) {
1141 filter_sir ($_[0], $_[1], PDL->pdl());
1142 }
1143
1144 sub filter_iir($$) {
1145 filter_sir ($_[0], PDL->pdl(1), $_[1]);
1146 }
1147
1148 =head2 filter_comb input, delay*, scaler
1149
1150 Apply a comb filter to the piddle C<input>. This is implemented using a
1151 delay line of length C<delay> (which must be 1 or larger and can be
1152 non-integer) and a feedback scaler.
1153
1154 y(n) = x(n-size-1) + scaler * y(n-size)
1155
1156 cf. C<filter_notch> and http://www.harmony-central.com/Effects/Articles/Reverb/comb.html
1157
1158 =head2 filter_notch input, delay*, scaler
1159
1160 Apply a comb filter to the piddle C<input>. This is implemented using a
1161 delay line of length C<delay> (which must be 1 or larger and can be
1162 non-integer) and a feedforward scaler.
1163
1164 y(n) = x(n-size-1) * scaler + y(n-size)
1165
1166 As a rule of thumb, the decay time of the feedback part is
1167 C<7*delay/(1-scaler)> samples, so to get a decay of Dur seconds,
1168 C<scaler <= 1-7*delay/(Dur*Srate)>. The peak gain is C<1/(1-(abs
1169 scaler))>. The peaks (or valleys in notch's case) are evenly spaced at
1170 C<srate/delay>. The height (or depth) thereof is determined by scaler
1171 -- the closer to 1.0, the more pronounced. See Julius Smith's "An
1172 Introduction to Digital Filter Theory" in Strawn "Digital Audio Signal
1173 Processing", or Smith's "Music Applications of Digital Waveguides"
1174
1175 =head2 filter_allpass input, delay*, scaler-feedback, scaler-feedforward
1176
1177 C<filter_allpass> or "moving average comb" is just like C<filter_comb>
1178 but with an added feedforward term. If C<scaler-feedback == 0>, we get a
1179 moving average comb filter. If both scaler terms == 0, we get a pure delay
1180 line.
1181
1182 y(n) = feedforward*x(n-1) + x(n-size-1) + feedback*y(n-size)
1183
1184 cf. http://www.harmony-central.com/Effects/Articles/Reverb/allpass.html
1185
1186 =cut
1187
1188 sub _filter_combnotchall {
1189 my ($pdl, $size, $scalercomb, $scalernotch, $scalerall) = @_;
1190 my $D = int $size;
1191 my $F = $size - $D;
1192 my $a = zeroes $D+2; $a->set($D,1-$F); $a->set($D+1, $F);
1193 my $b = zeroes $D+2; $b->set($D,$F-1); $b->set($D+1,-$F);
1194 $a->set(1, $a->at(1) + $scalerall);
1195 $b->set(0, 1);
1196 $pdl->filter_sir($a*$scalernotch,$b*$scalercomb);
1197 }
1198
1199 sub filter_comb {
1200 _filter_combnotchall $_[0], $_[1], $_[2], 1, 0;
1201 }
1202
1203 sub filter_notch {
1204 _filter_combnotchall $_[0], $_[1], 1, $_[2], 0;
1205 }
1206
1207 sub filter_allpass {
1208 _filter_combnotchall $_[0], $_[1], $_[2], 1, $_[3];
1209 }
1210
1211 =head2 design_remez_fir filter_size, bands(2,b), desired_gain(b), type, [weight(b)]
1212
1213 Calculates the optimal (in the Chebyshev/minimax sense) FIR filter
1214 impulse response given a set of band edges, the desired reponse on those
1215 bands, and the weight given to the error in those bands, using the
1216 Parks-McClellan exchange algorithm.
1217
1218 The first argument sets the filter size: C<design_remez_fir> returns as
1219 many coefficients as specified by this parameter.
1220
1221 C<bands> is a vector of band edge pairs (start - end), which specify the
1222 start and end of the bands in the filter specification. These must be
1223 non-overlapping and sorted in increasing order. Only values between C<0>
1224 (0 Hz) and C<0.5> (the Nyquist frequency) are allowed.
1225
1226 C<des> specifies the desired gain in these bands.
1227
1228 C<weight> can be used to give each band a different weight. If absent, a
1229 vector of ones is used.
1230
1231 C<type> is any of the exported constants C<BANDPASS>, C<DIFFERENTIATOR> or
1232 C<HILBERT> and can be used to select various design types (use C<BANDPASS>
1233 until this is documented ;)
1234
1235 =cut
1236
1237 sub design_remez_fir {
1238 my ($size, $bands, $des, $type, $weight) = @_;
1239 $weight = ones $des unless defined $weight;
1240 $size = zeroes $size;
1241 _design_remez_fir($bands, $des, $weight, $size, $type);
1242 $size;
1243 }
1244
1245 =head2 filter_src input, srate, [width], [sr-mod]
1246
1247 Generic sampling rate conversion, implemented by convoluting C<input> with
1248 a sinc function of size C<width> (default when unspecified or zero: 5).
1249
1250 C<srate> determines the input rate / output rate ratio, i.e. values > 1
1251 speed up, values < 1 slow down. Values < 0 are allowed and reverse the
1252 signal.
1253
1254 If C<sr_mod> is omitted, the size of the output piddle is calculcated
1255 as C<length(input)/abs(srate)>, e.g. it provides the full stretched or
1256 shrinked input signal.
1257
1258 If C<sr_mod> is specified it must be as large as the desired output, i.e.
1259 it's size determines the output size. Each value in C<sr_mod> is added to
1260 C<srate> at the given point in "time", so it can be used to "modulate" the
1261 sampling rate change.
1262
1263 # create a sound effect in the style of "Forbidden Planet"
1264 $osc = 0.3 * gen_oscil $osc, 30 / $pdl->rate;
1265 $output = filter_src($input, 1, 0, $osc);
1266
1267 =cut
1268
1269 sub filter_src($$;$$) {
1270 my ($input, $srate, $width, $sr_mod) = @_;
1271
1272 $width ||= 5;
1273 $width*2 < $input->getdim(0) or barf "src width too large (> 0.5 * input size)";
1274
1275 my $output;
1276
1277 if (defined $sr_mod) {
1278 $output = zeroes $sr_mod;
1279 } else {
1280 $output = zeroes ($input->getdim(0) / abs($srate));
1281 $sr_mod = PDL->null;
1282 }
1283
1284 _filter_src($input, $output, $sr_mod, $srate, $width);
1285
1286 $output->rate($input->rate / abs($srate)) if $input->rate;
1287 $output;
1288 }
1289
1290 =head2 filter_contrast_enhance input, enhancement
1291
1292 Contrast-enhancement phase-modulates a sound file. It's like audio
1293 MSG. The actual algorithm is (applied to the normalised sound)
1294 C<sin(input*pi/2 + (enhancement*sin(input*2*pi)))>. The result is to
1295 brighten the sound, helping it cut through a huge mix.
1296
1297 =cut
1298
1299 sub filter_contrast_enhance {
1300 my ($pdl, $idx) = @_;
1301 $idx = 0.1 unless defined $idx;
1302 $pdl = $pdl - min $pdl;
1303 $pdl = ($pdl * (M_PI / max $pdl)) - M_PI * 0.5;
1304 sin $pdl + $idx * sin $pdl*4;
1305 }
1306
1307 =head2 filter_granulate input, expansion, [option-hash], option => value...
1308
1309 C<filter_granulate> "granulates" the sound file file. It is the poor
1310 man's way to change the speed at which things happen in a recorded sound
1311 without changing the pitches. It works by slicing the input file into
1312 short pieces, then overlapping these slices to lengthen (or shorten) the
1313 result; this process is sometimes known as granular synthesis, and is
1314 similar to the "freeze" function. The duration of each slice is C<length> --
1315 the longer, the more like reverb the effect. The portion of the length (on
1316 a scale from 0 to 1.0) spent on each ramp (up or down) is C<ramp>. This can
1317 control the smoothness of the result of the overlaps. The more-or-less
1318 average time between successive segments is C<hop>. The accuracy at which we
1319 handle this hopping is set by the float C<jitter> -- if C<jitter> is very small,
1320 you may get an annoying tremolo. The overall amplitude scaler on each
1321 segment is C<scaler> -- this is used to try to to avoid overflows as we add
1322 all these zillions of segments together. C<expansion> determines the input
1323 hop in relation to the output hop; an expansion-amount of C<2.0> should more
1324 or less double the length of the original, whereas an expansion-amount
1325 of C<1.0> should return something close to the original speed.
1326
1327 The defaults for the arguments/options are:
1328
1329 expansion 1.0
1330 length(*) 0.15
1331 scaler 0.6
1332 hop(*) 0.05
1333 ramp 0.4
1334 jitter(*) 0.5
1335 maxsize infinity
1336
1337 The parameters/options marked with (*) actually depend on the sampling
1338 rate, and are always multiplied by the C<rate> attribute of the
1339 piddle internally. If the piddle lacks that attribute, 44100 is
1340 assumed. NOTE: This is different to most other filters, but should be ok
1341 since C<filter_granulate> only makes sense for audiofiles.
1342
1343 =cut
1344
1345 sub filter_granulate {
1346 my $input = shift;
1347 my $expansion = shift || 1;
1348 my %hdr = (); %hdr = (%hdr, %{+shift}) if ref $_[0]; %hdr = (%hdr, @_) if @_;
1349
1350 my $rate = $hdr{rate} || 44100;
1351 my $length = ($hdr{length} || 0.15) * $rate;
1352 my $scaler = $hdr{scaler} || 0.6;
1353 my $hop = ($hdr{hop} || 0.05) * $rate;
1354 my $ramp = $hdr{ramp} || 0.4;
1355 my $jitter = ($hdr{jitter} || 0.5) * $rate;
1356 my $maxsize = $hdr{maxsize}|| 0;
1357
1358 $output = zeroes $expansion * $input->getdim(0);
1359
1360 _filter_granulate($input, $output,
1361 $expansion, $length, $scaler,
1362 $hop, $ramp, $jitter, $maxsize);
1363 $output;
1364 }
1365
1366 EOP
1367
1368 pp_def '_design_remez_fir',
1369 Pars => 'bands(2,b); des(b); weight(b); [o]h(n)',
1370 OtherPars => 'int type',
1371 GenericTypes => [D],
1372 Doc => undef,
1373 PMCode => undef,
1374 Code => q^
1375 int type = $COMP(type);
1376
1377 if (type != BANDPASS
1378 && type != DIFFERENTIATOR
1379 && type != HILBERT)
1380 barf ("design_remez_fir: illegal type specified (none of BANDPASS, DIFFERENTIATOR, HILBERT)");
1381
1382 remez ($P(h), $SIZE(n), $SIZE(b), $P(bands), $P(des), $P(weight), type);
1383 ^
1384 ;
1385
1386 pp_def '_filter_src',
1387 Pars => 'input(n); [o]output(m); sr_mod(m2)',
1388 OtherPars => 'double srate; int width',
1389 GenericTypes => [D],
1390 Doc => undef,
1391 PMCode => undef,
1392 Code => q!
1393 mus_src ($P(input), $SIZE(n), $P(output), $SIZE(m),
1394 $COMP(srate),
1395 $SIZE(m) == $SIZE(m2) ? $P(sr_mod) : 0,
1396 $COMP(width));
1397 !
1398 ;
1399
1400 pp_def '_filter_granulate',
1401 Pars => 'input(n); [o]output(m)',
1402 OtherPars => 'double expansion; double length; double scaler; double hop; double ramp; double jitter; int max_size',
1403 GenericTypes => [D],
1404 Doc => undef,
1405 PMCode => undef,
1406 Code => q!
1407 mus_granulate ($P(input), $SIZE(n), $P(output), $SIZE(m),
1408 $COMP(expansion), $COMP(length), $COMP(scaler),
1409 $COMP(hop), $COMP(ramp), $COMP(jitter),
1410 $COMP(max_size));
1411 !
1412 ;
1413
1414 pp_addpm <<'EOP';
1415
1416 =head2 audiomix pos1, data1, pos2, data2, ...
1417
1418 Generate a mix of all given piddles. The resulting piddle will contain the
1419 sum of all data-piddles at their respective positions, so some scaling
1420 will be necessary before or after the mixing operation (e.g. scale2short).
1421
1422 # mix the sound gong1 at position 0, the sound bass5 at position 22100
1423 # and gong2 at position 44100. The resulting piddle will be large enough
1424 # to accomodate all the sounds:
1425 $mix = audiomix 0, $gong1, 44100, $gong2, 22100, $gong2
1426
1427 =cut
1428
1429 sub audiomix {
1430 my(@x,@p);
1431 my($dur,$i,$end,$s,$p);
1432 for ($i = 0; $i < @_; $i += 2) {
1433 $end = $_[$i] + $_[$i+1]->getdim(0);
1434 $dur = $end if $end > $dur;
1435 }
1436 my $pdl = zeroes $dur;
1437 for ($i = 0; $i < @_; $i += 2) {
1438 ($s,$p) = (int($_[$i]), $_[$i+1]);
1439 ($end = $pdl->slice("$s:".($s+$p->getdim(0)-1))) += $p;
1440 }
1441 $pdl;
1442 }
1443
1444 =head2 filter_center piddle
1445
1446 Normalize the piddle so that it is centered around C<y = 0> and has
1447 maximal amplitude of 1.
1448
1449 =cut
1450
1451 sub filter_center($) {
1452 my $piddle = shift;
1453 $piddle = $piddle - min $piddle;
1454 $piddle * (2 / max $piddle) - 1;
1455 }
1456
1457 =head2 scale2short piddle
1458
1459 This method takes a sound in any format (preferably float or double) and
1460 scales it to fit into a signed short value, suitable for playback using
1461 C<playudio> or similar functions.
1462
1463 =cut
1464
1465 sub scale2short {
1466 my $pdl = shift->float;
1467 ($pdl * (1 / max abs $pdl) * 32767.5 - 0.5)->short;
1468 }
1469
1470 =head2 gen_fft_window size*, type, [$beta]
1471
1472 Creates and returns a specific fft window. The C<type> is any of the following.
1473 These are (case-insensitive) strings, so you might need to quote them.
1474
1475 RECTANGULAR just ones (the identity window)
1476 HANNING 0.50 - 0.50 * cos (0 .. 2pi)
1477 HAMMING 0.54 - 0.46 * cos (0 .. 2pi)
1478 WELCH 1 - (-1 .. 1) ** 2
1479 PARZEN the triangle window
1480 BARTLETT the symmetric triangle window
1481 BLACKMAN2 blackman-harris window of order 2
1482 BLACKMAN3 blackman-harris window of order 3
1483 BLACKMAN4 blackman-harris window of order 4
1484 EXPONENTIAL the exponential window
1485 KAISER the kaiser/bessel window (using the parameter C<beta>)
1486 CAUCHY the cauchy window (using the parameter <beta>)
1487 POISSON the poisson window (exponential using parameter C<beta>)
1488 RIEMANN the riemann window (sinc)
1489 GAUSSIAN the gaussian window of order C<beta>)
1490 TUKEY the tukey window (C<beta> specifies how much of the window
1491 consists of ones).
1492 COSCOS the cosine-squared window (a partition of unity)
1493 SINC same as RIEMANN
1494 HANN same as HANNING (his name was Hann, not Hanning)
1495
1496 LIST this "type" is special in that it returns a list of all types
1497
1498 =cut
1499
1500 sub gen_fft_window {
1501 my ($size, $type, $beta) = @_;
1502
1503 $beta = 2.5 unless defined $beta;
1504
1505 $size = $size->getdim(0) if ref $size;
1506 $size > 2 or barf "fft window size too small";
1507
1508 my $midn = $size >> 1;
1509 my $midm1 = ($size-1) >> 1;
1510 my $midp1 = ($size+1) >> 1;
1511 my $dur = zeroes $size;
1512 my $sf = ($size-1)/$size;
1513 %fft_window = (
1514 RECTANGULAR => sub {
1515 $dur->ones
1516 },
1517 HANNING => sub {
1518 0.5 - 0.5 * cos $dur->xlinvals(0,M_2PI*$sf)
1519 },
1520 HAMMING => sub {
1521 0.53836 - 0.46164 * cos $dur->xlinvals(0,M_2PI*$sf)
1522 },
1523 WELCH => sub {
1524 1 - $dur->xlinvals(-1,$sf)**2;
1525 },
1526 PARZEN => sub {
1527 1 - abs $dur->xlinvals(-$sf,$sf);
1528 },
1529 BARTLETT => sub {
1530 1 - abs $dur->xlinvals(-1,1);
1531 },
1532 BLACKMAN2 => sub {
1533 my $cx = cos $dur->xlinvals(0,M_2PI*$sf);
1534 0.34401 + ($cx * (-0.49755 + ($cx * 0.15844)));
1535 },
1536 BLACKMAN3 => sub {
1537 my $cx = cos $dur->xlinvals(0,M_2PI*$sf);
1538 0.21747 + ($cx * (-0.45325 + ($cx * (0.28256 - ($cx * 0.04672)))));
1539 },
1540 BLACKMAN4 => sub {
1541 my $cx = cos $dur->xlinvals(0,M_2PI*$sf);
1542 0.084037 + ($cx * (-0.29145 + ($cx * (0.375696 + ($cx * (-0.20762 + ($cx * 0.041194)))))));
1543 },
1544 EXPONENTIAL => sub {
1545 2 ** (1 - abs $dur->xlinvals(-1,$sf)) - 1;
1546 },
1547 KAISER => sub {
1548 bessi0 ($beta * sqrt(1 - $dur->xlinvals(-1,$sf)**2)) / bessi0 ($beta);
1549 },
1550 CAUCHY => sub {
1551 1 / (1 + ($dur->xlinvals(-1,$sf)*$beta)**2);
1552 },
1553 POISSON => sub {
1554 exp (-$beta * abs $dur->xlinvals(-1,$sf));
1555 },
1556 RIEMANN => sub {
1557 my $dur1 = $dur->slice("0:$midm1");
1558 my $dur2 = $dur->slice("-1:$midp1:-1");
1559 my $cx;
1560 $cx = $dur1->xlinvals(M_PI,M_2PI/$size); $dur1 .= sin ($cx) / $cx;
1561 $cx = $dur2->xlinvals(M_PI,M_2PI/$size); $dur2 .= sin ($cx) / $cx;
1562 ($cx = $dur->slice("$midm1:$midp1")) .= 1;
1563 $dur;
1564 },
1565 GAUSSIAN => sub {
1566 exp (-0.5 * ($beta * abs $dur->xlinvals(-1,$sf))**2);
1567 },
1568 TUKEY => sub {
1569 $beta >= 0 && $beta <= 1 or barf "beta must be between 0 and 1 for the tukey window";
1570 my $cx = int ($midn * (1 - $beta));
1571 my $cX = $size-$cx-1;
1572 my $dur1 = $dur->slice("0:".($cx-1));
1573 my $dur2 = $dur->slice("-1:".($cX+1).":-1");
1574 my $dur3 = $dur->slice("$cx:$cX");
1575
1576 $dur1 .= 0.5 * (1 - cos $dur1->xlinvals(0, M_PI));
1577 $dur2 .= 0.5 * (1 - cos $dur2->xlinvals(0, M_PI));
1578 $dur3 .= 1;
1579
1580 $dur;
1581 },
1582 COSCOS => sub {
1583 (cos $dur->xlinvals(M_PI/-2,M_PI/2))**2;
1584 },
1585 LIST => sub {
1586 grep $_ ne "LIST", keys %fft_window;
1587 },
1588 );
1589
1590 $fft_window{SINC} = $fft_window{RIEMANN};
1591 $fft_window{HANN} = $fft_window{HANNING};
1592
1593 $type = uc $type;
1594 $fft_window{$type} or barf "$type: no such window";
1595 $fft_window{$type}->();
1596 }
1597
1598 =head2 cplx(2,n) = rfft real(n)
1599
1600 Do a (complex fft) of C<real> (extended to complex so that the imaginary
1601 part is zero), and return the complex fft result. This function tries
1602 to use L<PDL::FFTW|PDL::FFTW> (which is faster for large vectors) when
1603 available, and falls back to L<PDL::FFT|PDL::FFT>, which is likely
1604 to return different phase signs (due to different kernel functions),
1605 so beware! In fact, since C<rfft> has to shuffle the data when using
1606 PDL::FFT, the fallback is always slower.
1607
1608 When using PDL::FFTW, a wisdom file ~/.pdl_wisdom is used and updated, if
1609 possible.
1610
1611 =head2 real(n) = irfft cplx(2,n)
1612
1613 The inverse transformation (see C<rfft>). C<irfft rfft $pdl == $pdl>
1614 always holds.
1615
1616 =cut
1617
1618 my $fftw_loaded;
1619 sub _fftw {
1620 defined $fftw_loaded or eval {
1621 $fftw_loaded = 0;
1622 require PDL::FFTW;
1623 PDL::FFTW::load_wisdom("$ENV{HOME}/.pdl_wisdom")
1624 if -r "$ENV{HOME}/.pdl_wisdom";
1625 $fftw_loaded = 1;
1626 };
1627 $fftw_loaded;
1628 }
1629
1630 sub rfft {
1631 my $data = $_[0];
1632 if (_fftw) {
1633 my $x = $data->r2C;
1634 $x = PDL::FFTW::fftw $x;
1635 $x;
1636 } else {
1637 require PDL::FFT;
1638 my @fft = ($data->copy, $data->zeroes);
1639 PDL::FFT::fft(@fft);
1640 cat(@fft)->xchg(0,1);
1641 }
1642 }
1643
1644 sub irfft {
1645 if (_fftw) {
1646 $x = $_[0]->copy;
1647 $x = PDL::FFTW::ifftw $x;
1648 re $x / $x->getdim(1);
1649 } else {
1650 require PDL::FFT;
1651 my @fft = $_[0]->xchg(0,1)->dog({Break => 1});
1652 PDL::FFT::ifft(@fft);
1653 $fft[0];
1654 }
1655 }
1656
1657 =head2 spectrum data, [norm], [window], [beta]
1658
1659 Returns the spectrum of a given pdl. If C<norm> is absent (or C<undef>),
1660 it returns the magnitude of the fft of C<data>. When C<norm> == 1 (or
1661 C<eq 'NORM'>, case-insensitive), it returns the magnitude, normalized to be
1662 between zero and one. If C<norm> == 0 (or C<eq 'dB'>, case-insensitive), then
1663 it returns the magnitude in dB.
1664
1665 C<data> is multiplied with C<window> (if not C<undef>) before calculating
1666 the fft, and usually contains a window created with C<gen_fft_window>
1667 (using C<beta>). If C<window> is a string, it is handed over to
1668 C<gen_fft_window> (together with the beta parameter) to create a window of
1669 suitable size.
1670
1671 This function could be slightly faster.
1672
1673 =cut
1674
1675 sub spectrum {
1676 my ($data, $norm, $window, $beta) = @_;
1677 my $len;
1678 if (defined $window) {
1679 $window = gen_fft_window ($data->getdim (0), $window, $beta) unless ref $window;
1680 $data = $data * $window;
1681 $len = $window->getdim (0);
1682 } else {
1683 $len = $data->getdim (0);
1684 }
1685 $data = rfft (
1686 $data->slice ("0:" . ($len - 1))
1687 ->sever
1688 )
1689 ->slice (",0:" . int ($len / 2))
1690 ->PDL::Complex::Cr2p
1691 ->slice ("(0)");
1692 if ($norm == 1 || lc $norm eq "norm") {
1693 $data / max $data;
1694 } elsif (($norm =~ /^[.0]+$/) || (lc $norm eq "db")) {
1695 log (1e-37 + $data / max $data) * (20 / log 10);
1696 } else {
1697 $data;
1698 }
1699 }
1700
1701 =head2 concat pdl, pdl...
1702
1703 This is not really an audio-related function. It simply takes all piddles
1704 and concats them into a larger one. At the moment it only supports
1705 single-dimensional piddles and is implemented quite slowly using perl and
1706 data-copying, but that might change...
1707
1708 =cut
1709
1710 sub concat {
1711 my $len = sum pdl map $_->getdim(0), @_;
1712 my $r = zeroes $len;
1713 my $o = 0;
1714 for (@_) {
1715 my $e = $o + $_->getdim(0) - 1;
1716 (my $t = $r->slice("$o:$e")) .= $_;
1717 $o = $e + 1;
1718 }
1719 $r;
1720 }
1721
1722 EOP
1723
1724 # TODO: document!
1725 pp_def 'filter_convolve',
1726 Pars => 'input(n); kernel(m); int fftsize(); [o]output(n)',
1727 GenericTypes => [D],
1728 Code => q!
1729 mus_convolve ($P(input), $P(output), $SIZE(n), $P(kernel), $fftsize(), $SIZE(m));
1730 !
1731 ;
1732
1733 pp_def 'rshift',
1734 Doc => <<'EOD',
1735 =for ref
1736
1737 Shift vector elements without wrap and fill the free space with a
1738 constant. Flows data back & forth, for values that overlap.
1739
1740 Positive values shift right, negative values shift left.
1741
1742 EOD
1743 Pars => 'x(n); int shift(); c(); [oca]y(n)',
1744 DefaultFlow => 1,
1745 Reversible => 1,
1746 PMCode => '
1747 sub PDL::rshift {
1748 my @a = @_;
1749 if($#a == 3) {
1750 &PDL::_rshift_int(@a);@a=();
1751 } elsif($#a == 1 || $#a == 2) {
1752 $a[3] = PDL->nullcreate($a[0]);
1753 &PDL::_rshift_int(@a);
1754 $a[3];
1755 } else {
1756 barf "Invalid number of arguments for shiftin";
1757 }
1758 }
1759 ',
1760 Code=>'
1761 int i,j;
1762 int n_size = $SIZE(n);
1763 for(i = -$shift(), j=0; j < n_size; i++, j++)
1764 $y(n=>j) = i >= 0 && i < n_size ? $x(n=>i) : $c();
1765 ',
1766 BackCode=>'
1767 int i,j;
1768 int n_size = $SIZE(n);
1769 for(i = -$shift(), j=0; j < n_size; i++, j++)
1770 if (i >= 0 && i < n_size)
1771 $x(n=>i) = $y(n=>j);
1772 '
1773 ;
1774
1775 pp_def 'polynomial',
1776 Pars => 'coeffs(n); x(m); [o]out(m)',
1777 Doc => 'evaluate the polynomial with coefficients C<coeffs> at the position(s) C<x>. C<coeffs[0]> is the constant term.',
1778 Code => q!
1779 loop(m) %{
1780 $GENERIC() x = 1;
1781 $GENERIC() o = 0;
1782
1783 loop(n) %{
1784 o += $coeffs() * x;
1785 x *= $x();
1786 %}
1787
1788 $out() = o;
1789 %}
1790 !
1791 ;
1792
1793 pp_def 'linear_interpolate',
1794 Pars => 'x(); fx(n); fy(n); [o]y()',
1795 GenericTypes => [L,F,D],
1796 Doc => '
1797 Look up the ordinate C<x> in the function given by C<fx> and C<fy>
1798 and return a linearly interpolated value (somewhat optimized for many
1799 lookups).
1800
1801 C<fx> specifies the ordinates (x-coordinates) of the function and most be
1802 sorted in increasing order. C<fy> are the y-coordinates of the function in
1803 these points.
1804 ',
1805 Code => q!
1806 int d = 0;
1807 int D = $SIZE(n) - 1; /* a tribute to PP's stupidity */
1808 $GENERIC() xmin =$fx(n=>0);
1809 $GENERIC() xmax =$fx(n=>D);
1810
1811 threadloop %{
1812 $GENERIC() x = $x();
1813 $GENERIC() x1, x2, y1, y2;
1814
1815 if (x <= xmin)
1816 $y() = $fy(n=>0);
1817 else if (x >= xmax)
1818 $y() = $fy(n=>D);
1819 else
1820 {
1821 while ($fx(n=>d) > x) d--;
1822 while ($fx(n=>d) <= x) d++;
1823 /* 0 < d <= D */
1824 /* fx(d-1) < x <= fx(d) */
1825 x2 = $fx(n=>d);
1826 y2 = $fy(n=>d);
1827 d--;
1828 x1 = $fx(n=>d);
1829 y1 = $fy(n=>d);
1830 $y() = y1 + (x-x1)*(y2-y1)/(x2-x1);
1831 }
1832 %}
1833 !
1834 ;
1835
1836 pp_def 'bessi0',
1837 Pars => 'a(); [o]b()',
1838 Doc => 'calculate the (approximate) modified bessel function of the first kind',
1839 GenericTypes => [F,D],
1840 Code => q!
1841 $GENERIC() x = $a();
1842 if (x > -3.75 && x < 3.75)
1843 {
1844 $GENERIC() y = (x / 3.75); y *= y;
1845 $b() = 1 + (y * (3.5156229 +
1846 (y * (3.0899424 +
1847 (y * (1.2067492 +
1848 (y * (0.2659732 +
1849 (y * (0.360768e-1 +
1850 (y * 0.45813e-2)))))))))));
1851 }
1852 else
1853 {
1854 $GENERIC() ax = x < 0 ? -x : x;
1855 $GENERIC() y = ax / 3.75;
1856 $b() = ((exp(ax) / sqrt(ax)) *
1857 (0.39894228 +
1858 (y * (0.1328592e-1 +
1859 (y * (0.225319e-2 +
1860 (y * (-0.157565e-2 +
1861 (y * (0.916281e-2 +
1862 (y * (-0.2057706e-1 +
1863 (y * (0.2635537e-1 +
1864 (y * (-0.1647633e-1 +
1865 (y * 0.392377e-2)))))))))))))))));
1866 }
1867 !,
1868 ;
1869
1870 pp_def 'fast_sin',
1871 Pars => 'r(n); [o]s(n)',
1872 GenericTypes => [F,D],
1873 Doc => 'fast sine function (inaccurate table lookup with ~12 bits precision)',
1874 Code => q^
1875 # define SINE_SIZE 16384
1876 static float *sine_table = 0;
1877
1878 if (!sine_table)
1879 {
1880 int i;
1881 Float phase, incr;
1882 sine_table = (float *) calloc (SINE_SIZE + 1, sizeof (float));
1883 incr = M_2PI / (Float) SINE_SIZE;
1884 for (i = 0, phase = 0.0; i < SINE_SIZE + 1; i++, phase += incr)
1885 sine_table[i] = (float) sin (phase);
1886 }
1887
1888 threadloop %{
1889 loop(n) %{
1890 $s() = sine_table[((int)($r() * (SINE_SIZE / M_2PI)) % SINE_SIZE + SINE_SIZE) % SINE_SIZE];
1891 %}
1892 %}^
1893 ;
1894
1895 pp_addpm {At => Bot}, <<'EOD';
1896
1897 =head1 AUTHOR
1898
1899 Marc Lehmann <schmorp@schmorp.de>. The ideas were mostly taken from common
1900 lisp music (CLM), by Bill Schottstaedt C<bil@ccrma.stanford.edu>. I also
1901 borrowed many explanations (and references) from the clm docs and some
1902 code from clm.c. Highly inspiring!
1903
1904 =head1 SEE ALSO
1905
1906 perl(1), L<PDL>.
1907
1908 =cut
1909 EOD
1910
1911 pp_addxs "\nINCLUDE: xlib.xs\n";
1912
1913 pp_done;
1914