ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/PDL-Audio/audio.pd
Revision: 1.6
Committed: Tue Dec 28 02:14:49 2004 UTC (19 years, 4 months ago) by root
Branch: MAIN
Changes since 1.5: +8 -10 lines
Log Message:
*** empty log message ***

File Contents

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