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