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