ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/PDL-Audio/audio.pd
Revision: 1.8
Committed: Tue Nov 8 18:48:47 2005 UTC (18 years, 6 months ago) by root
Branch: MAIN
Changes since 1.7: +20 -15 lines
Log Message:
*** empty log message ***

File Contents

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