ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/PDL-Audio/audio.pd
Revision: 1.9
Committed: Tue Nov 8 18:56:06 2005 UTC (18 years, 6 months ago) by root
Branch: MAIN
CVS Tags: rel-1_1
Changes since 1.8: +8 -4 lines
Log Message:
*** empty log message ***

File Contents

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