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

File Contents

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