ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/PDL-Audio/audio.pd
Revision: 1.10
Committed: Wed Apr 18 08:28:18 2012 UTC (12 years ago) by root
Branch: MAIN
CVS Tags: rel-1_2, HEAD
Changes since 1.9: +9 -1 lines
Log Message:
1.2

File Contents

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