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

File Contents

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