1 | $VERSION = '1.01'; |
1 | $VERSION = '1.2'; |
2 | |
2 | |
3 | pp_setversion $VERSION; |
3 | pp_setversion $VERSION; |
4 | pp_beginwrap (); # force error with older PPs |
4 | pp_beginwrap (); # force error with older PPs |
5 | |
5 | |
6 | pp_addpm {At => Top}, <<'EOD'; |
6 | pp_addpm {At => Top}, <<'EOD'; |
… | |
… | |
13 | use PDL; |
13 | use PDL; |
14 | use PDL::Audio; |
14 | use PDL::Audio; |
15 | |
15 | |
16 | =head1 DESCRIPTION |
16 | =head1 DESCRIPTION |
17 | |
17 | |
18 | Oh well ;) Not much "introductury documentation" has been written yet! See |
18 | Oh well ;) Not much "introductory documentation" has been written yet :( |
19 | my other modules for even worse documentation ;) |
19 | |
|
|
20 | Installing this distribution also installs F<pdlaudio-demo>, which |
|
|
21 | showcases some of the oeprators, and C<pdlaudio-birds>, which imites some |
|
|
22 | bird calls with PDL::Audio. You should study them to get the hang of it. |
20 | |
23 | |
21 | =head2 NOTATION |
24 | =head2 NOTATION |
22 | |
25 | |
23 | Brackets around parameters indicate that the respective parameter is |
26 | Brackets around parameters indicate that the respective parameter is |
24 | optional and will be replaced with some default value when absent (or |
27 | optional and will be replaced with some default value when absent (or |
… | |
… | |
33 | |
36 | |
34 | # Syntax: gen_oscil duration*, frequency/ |
37 | # Syntax: gen_oscil duration*, frequency/ |
35 | $signal = gen_oscil 2*HZ, 440/HZ; |
38 | $signal = gen_oscil 2*HZ, 440/HZ; |
36 | # with a sampling frequency of 44100 Hertz: |
39 | # with a sampling frequency of 44100 Hertz: |
37 | $signal = gen_oscil 2*44100, 440/44100; |
40 | $signal = gen_oscil 2*44100, 440/44100; |
|
|
41 | |
|
|
42 | print describe_audio $signal, "\n"; |
|
|
43 | playaudio $signal->scale2short; |
38 | |
44 | |
39 | To help you, the required unit is given as a type suffix in the parameter |
45 | To help you, the required unit is given as a type suffix in the parameter |
40 | name. A "/" means that you have to divide by the sampling frequency (to |
46 | name. A "/" means that you have to divide by the sampling frequency (to |
41 | convert from Hertz) and a suffix of "*" indicates that a multiplication is |
47 | convert from Hertz) and a suffix of "*" indicates that a multiplication is |
42 | required. |
48 | required. |
… | |
… | |
276 | |
282 | |
277 | =cut |
283 | =cut |
278 | |
284 | |
279 | sub describe_audio($) { |
285 | sub describe_audio($) { |
280 | my $pdl = shift; |
286 | my $pdl = shift; |
281 | my ($samples, $channels) = $pdl->dims; |
287 | my ($channels, $samples) = $pdl->dims; |
|
|
288 | ($channels, $samples) = (1, $channels) unless defined $samples; |
282 | my $chan = $channels < 2 ? "mono" : |
289 | my $chan = $channels < 2 ? "mono" : |
283 | $channels == 2 ? "stereo" : |
290 | $channels == 2 ? "stereo" : |
284 | $channels == 4 ? "quad channel" : |
291 | $channels == 4 ? "quad channel" : |
285 | "$channel channel"; |
292 | "$channels channel"; |
286 | my $desc = "$chan sound with $samples samples"; |
293 | my $desc = "$chan sound with $samples samples"; |
287 | $desc .= sprintf ", original name \"%s\"", $pdl->path if $pdl->path; |
294 | $desc .= sprintf ", original name \"%s\"", $pdl->path if $pdl->path; |
288 | $desc .= sprintf ", type %d (%s)", $pdl->filetype, sound_type_name($pdl->filetype) if $pdl->filetype; |
295 | $desc .= sprintf ", type %d (%s)", $pdl->filetype, sound_type_name($pdl->filetype) if $pdl->filetype; |
289 | $desc .= sprintf ", rate %d/s (duration %.2fs)", $pdl->rate, $samples/$pdl->rate if $pdl->rate; |
296 | $desc .= sprintf ", rate %d/s (duration %.2fs)", $pdl->rate, $samples/$pdl->rate if $pdl->rate; |
290 | $desc .= sprintf ", format %d (%s)", $pdl->format, sound_format_name($pdl->format) if $pdl->format; |
297 | $desc .= sprintf ", format %d (%s)", $pdl->format, sound_format_name($pdl->format) if $pdl->format; |
291 | $desc; |
298 | $desc |
292 | } |
299 | } |
293 | |
300 | |
294 | =head2 raudio path, [option-hash], option => value, ... |
301 | =head2 raudio path, [option-hash], option => value, ... |
295 | |
302 | |
296 | Reads audio data into the piddle. Options can be anything, most useful values are |
303 | Reads audio data into the piddle. Options can be anything, most useful |
297 | C<filetype>, C<rate>, C<channels> and C<format>. |
304 | values are C<filetype>, C<rate>, C<channels> and C<format>. The returned |
|
|
305 | piddle is represents "time" in the outer dimension, and samples in the |
|
|
306 | inner (i.e. scalars for mono files, 2-vectors for stereo files): |
|
|
307 | |
|
|
308 | [ [left0, right0], [left1, right1], [left2, right2], ...] |
298 | |
309 | |
299 | # read any file |
310 | # read any file |
300 | $pdl = raudio "file.wav"; |
311 | $pdl = raudio "file.wav"; |
301 | # read a file. if it is a raw file preset values |
312 | # read a file. if it is a raw file preset values |
302 | $pdl = raudio "file.raw", filetype => FILE_RAW, rate => 44100, channels => 2; |
313 | $pdl = raudio "file.raw", filetype => FILE_RAW, rate => 44100, channels => 2; |
303 | |
314 | |
304 | =head2 waudio pdl, [option-hash], option => value, ... |
315 | =head2 waudio pdl, [option-hash], option => value, ... |
305 | |
316 | |
306 | Writes a pdl as a file. The path is taken from the header (or the options), e.g.: |
317 | Writes a pdl as a file. See L<raudio> for options and format. The path and |
|
|
318 | other metadata is taken from the header, whcih cna be overwritten using |
|
|
319 | options, e.g.: |
307 | |
320 | |
308 | # write a file, using the header of another piddle |
321 | # write a file, using the header of another piddle |
309 | $pdl->waudio($orig_file->gethdr); |
322 | $pdl->waudio ($orig_file->gethdr); |
310 | # write pdl as au file, take rate from the header |
323 | # write pdl as .au file, take rate from the header |
311 | $pdl->waudio(path => "piddle.au", filetype => FILE_AU, format => FORMAT_16_LINEAR; |
324 | $pdl->waudio (path => "piddle.au", filetype => FILE_AU, format => FORMAT_16_LINEAR; |
312 | |
325 | |
313 | =cut |
326 | =cut |
314 | |
327 | |
315 | # read a sound file |
328 | # read a sound file |
316 | sub raudio { |
329 | sub raudio { |
… | |
… | |
339 | (close_sound_input $fd) >= 0 or barf "$path: ".audio_error_name audio_error; |
352 | (close_sound_input $fd) >= 0 or barf "$path: ".audio_error_name audio_error; |
340 | $pdl = $pdl->short->xchg(0,1); |
353 | $pdl = $pdl->short->xchg(0,1); |
341 | $pdl = $pdl->clump(2) if $channels == 1; |
354 | $pdl = $pdl->clump(2) if $channels == 1; |
342 | $pdl->sever; |
355 | $pdl->sever; |
343 | $pdl->sethdr(\%hdr); |
356 | $pdl->sethdr(\%hdr); |
344 | $pdl; |
357 | $pdl |
345 | } |
358 | } |
346 | |
359 | |
347 | sub _audio_make_plain { |
360 | sub _audio_make_plain { |
348 | my $pdl = shift; |
361 | my $pdl = shift; |
349 | if ($pdl->getndims == 1) { |
362 | if ($pdl->getndims == 1) { |
350 | ($pdl, 1, $pdl->getdim(0)); |
363 | ($pdl, 1, $pdl->getdim(0)) |
351 | } else { |
364 | } else { |
352 | ($pdl->xchg(0,1)->clump(-1), ($pdl->dims)[1,0]); |
365 | ($pdl->xchg(0,1)->clump(-1), $pdl->dims) |
353 | } |
366 | } |
354 | } |
367 | } |
355 | |
368 | |
356 | sub waudio { |
369 | sub waudio { |
357 | my $pdl = shift; |
370 | my $pdl = shift; |
… | |
… | |
362 | $hdr{format} ||= FORMAT_16_LINEAR; |
375 | $hdr{format} ||= FORMAT_16_LINEAR; |
363 | $hdr{rate} ||= 44100; |
376 | $hdr{rate} ||= 44100; |
364 | |
377 | |
365 | ($pdl, $channels, $frames) = _audio_make_plain $pdl->convert(long); |
378 | ($pdl, $channels, $frames) = _audio_make_plain $pdl->convert(long); |
366 | |
379 | |
|
|
380 | 1 <= $channels && $channels <= 2 |
|
|
381 | or croak "can only write mono or stereo (one or two channel) files, not $channels channel files"; |
|
|
382 | |
367 | my $fd = open_sound_output $hdr{path}, $hdr{rate}, $channels, $hdr{format}, $hdr{filetype}, $hdr{comment}; |
383 | my $fd = open_sound_output $hdr{path}, $hdr{rate}, $channels, $hdr{format}, $hdr{filetype}, $hdr{comment}; |
368 | $fd >= 0 or barf "$hdr{$path}: ".audio_error_name audio_error; |
384 | $fd >= 0 or barf "$hdr{$path}: ".audio_error_name audio_error; |
369 | $pdl->clump(-1)->write_sound($fd, $channels, $frames) |
385 | $pdl->clump(-1)->write_sound($fd, $channels, $frames) |
370 | >= 0 or barf "$path: ".audio_error_name audio_error; |
386 | >= 0 or barf "$path: ".audio_error_name audio_error; |
371 | (close_sound_output $fd, mus_samples2bytes $hdr{format}, $frames) |
387 | (close_sound_output $fd, mus_samples2bytes $hdr{format}, $frames * $channels) |
372 | >= 0 or barf "$hdr{$path}: ".audio_error_name audio_error; |
388 | >= 0 or barf "$hdr{$path}: ".audio_error_name audio_error; |
373 | } |
389 | } |
374 | |
390 | |
375 | =head2 cut_leading_silence pdl, level |
391 | =head2 cut_leading_silence pdl, level |
376 | |
392 | |
… | |
… | |
391 | sub cut_leading_silence { |
407 | sub cut_leading_silence { |
392 | my $pdl = shift; |
408 | my $pdl = shift; |
393 | my $level = 1*shift; |
409 | my $level = 1*shift; |
394 | my $skip = which (abs($pdl) > $level); |
410 | my $skip = which (abs($pdl) > $level); |
395 | $skip = $skip->nelem ? $skip->at(0) : 0; |
411 | $skip = $skip->nelem ? $skip->at(0) : 0; |
396 | $pdl->slice("$skip:-1"); |
412 | $pdl->slice("$skip:-1") |
397 | } |
413 | } |
398 | |
414 | |
399 | sub cut_trailing_silence { |
415 | sub cut_trailing_silence { |
400 | my $pdl = shift; |
416 | my $pdl = shift; |
401 | my $level = 1*shift; |
417 | my $level = 1*shift; |
402 | $level = 400000; |
418 | $level = 400000; |
403 | my $skip = which (abs($pdl) > $level); |
419 | my $skip = which (abs($pdl) > $level); |
404 | $skip = $skip->nelem ? $skip->at(-1) : -1; |
420 | $skip = $skip->nelem ? $skip->at(-1) : -1; |
405 | $skip-- if $skip > 0; |
421 | $skip-- if $skip > 0; |
406 | $pdl->slice("0:$skip"); |
422 | $pdl->slice("0:$skip") |
407 | } |
423 | } |
408 | |
424 | |
409 | sub cut_silence { |
425 | sub cut_silence { |
410 | $_[0]->cut_leading_silence($_[1]) |
426 | $_[0]->cut_leading_silence($_[1]) |
411 | ->cut_trailing_silence($_[1]); |
427 | ->cut_trailing_silence($_[1]) |
412 | } |
428 | } |
413 | |
429 | |
414 | # have we been a bad boy? |
430 | # have we been a bad boy? |
415 | |
431 | |
416 | for (@METHODS) { |
432 | for (@METHODS) { |
… | |
… | |
753 | in C<table>, linearly interpolating between successive points of the |
769 | in C<table>, linearly interpolating between successive points of the |
754 | C<waveform>. |
770 | C<waveform>. |
755 | |
771 | |
756 | =head2 partials2waveshape size*, partials, amplitudes, [phase], [fm_mod/] |
772 | =head2 partials2waveshape size*, partials, amplitudes, [phase], [fm_mod/] |
757 | |
773 | |
758 | Take a list (perl list or pdl) of (integer) C<partials> and a list of |
774 | Take a (perl or pdl) list of (integer) C<partials> and a list of |
759 | C<amplitudes> and generate a single wave shape that results by adding |
775 | C<amplitudes> and generate a single wave shape that results by adding |
760 | these partial sines. |
776 | these partial sines. |
761 | |
777 | |
762 | This could (and should) be used by the C<gen_from_table> generator. |
778 | This could (and should) be used by the C<gen_from_table> generator. |
763 | |
779 | |
764 | =head2 gen_from_partials duration*, frequency/, partials, amplitudes, [phase], [fm_mod/] |
780 | =head2 gen_from_partials duration*, frequency/, partials, amplitudes, [phase], [fm_mod/] |
765 | |
781 | |
766 | Take a list (perl list or pdl) of (possibly noninteger) C<partials> and a |
782 | Take a (perl list or pdl) list of (possibly noninteger) C<partials> and a |
767 | list of C<amplitudes> and generate the waveform resulting by summing up |
783 | list of C<amplitudes> and generate the waveform resulting by summing up |
768 | all these partial sines. |
784 | all these partial sines. |
769 | |
785 | |
770 | =cut |
786 | =cut |
771 | |
787 | |
… | |
… | |
1209 | Calculates the optimal (in the Chebyshev/minimax sense) FIR filter |
1225 | Calculates the optimal (in the Chebyshev/minimax sense) FIR filter |
1210 | impulse response given a set of band edges, the desired reponse on those |
1226 | impulse response given a set of band edges, the desired reponse on those |
1211 | bands, and the weight given to the error in those bands, using the |
1227 | bands, and the weight given to the error in those bands, using the |
1212 | Parks-McClellan exchange algorithm. |
1228 | Parks-McClellan exchange algorithm. |
1213 | |
1229 | |
1214 | The first argument (the one with the funny name) sets the filter |
1230 | The first argument sets the filter size: C<design_remez_fir> returns as |
1215 | size: C<design_remez_fir> returns as many coefficients as specified via |
1231 | many coefficients as specified by this parameter. |
1216 | this parameter. |
|
|
1217 | |
1232 | |
1218 | C<bands> is a vector of band edge pairs (start - end), who specify the |
1233 | C<bands> is a vector of band edge pairs (start - end), which specify the |
1219 | start and end of the bands in the filter specification. These must be |
1234 | start and end of the bands in the filter specification. These must be |
1220 | non-overlapping and sorted in increasing order. Only values between C<0> |
1235 | non-overlapping and sorted in increasing order. Only values between C<0> |
1221 | (0 Hz) and C<0.5> (the Nyquist frequency) are allowed. |
1236 | (0 Hz) and C<0.5> (the Nyquist frequency) are allowed. |
1222 | |
1237 | |
1223 | C<des> specifies the desired gain in these bands. |
1238 | C<des> specifies the desired gain in these bands. |
… | |
… | |
1500 | $beta = 2.5 unless defined $beta; |
1515 | $beta = 2.5 unless defined $beta; |
1501 | |
1516 | |
1502 | $size = $size->getdim(0) if ref $size; |
1517 | $size = $size->getdim(0) if ref $size; |
1503 | $size > 2 or barf "fft window size too small"; |
1518 | $size > 2 or barf "fft window size too small"; |
1504 | |
1519 | |
1505 | my $midn = $size >> 1; |
1520 | my $midn = $size >> 1; |
1506 | my $midm1 = ($size-1) >> 1; |
1521 | my $midm1 = ($size-1) >> 1; |
1507 | my $midp1 = ($size+1) >> 1; |
1522 | my $midp1 = ($size+1) >> 1; |
1508 | my $dur = zeroes $size; |
1523 | my $dur = zeroes $size; |
1509 | my $sf = ($size-1)/$size; |
1524 | my $sf = ($size-1)/$size; |
1510 | %fft_window = ( |
1525 | %fft_window = ( |
1511 | RECTANGULAR => sub { |
1526 | RECTANGULAR => sub { |
1512 | $dur->ones |
1527 | $dur->ones |
1513 | }, |
1528 | }, |
… | |
… | |
1615 | my $fftw_loaded; |
1630 | my $fftw_loaded; |
1616 | sub _fftw { |
1631 | sub _fftw { |
1617 | defined $fftw_loaded or eval { |
1632 | defined $fftw_loaded or eval { |
1618 | $fftw_loaded = 0; |
1633 | $fftw_loaded = 0; |
1619 | require PDL::FFTW; |
1634 | require PDL::FFTW; |
1620 | PDL::FFTW::load_wisdom("$ENV{HOME}/.pdl_wisdom") if $ENV{HOME}; |
1635 | PDL::FFTW::load_wisdom("$ENV{HOME}/.pdl_wisdom") |
|
|
1636 | if -r "$ENV{HOME}/.pdl_wisdom"; |
1621 | $fftw_loaded = 1; |
1637 | $fftw_loaded = 1; |
1622 | }; |
1638 | }; |
1623 | $fftw_loaded; |
1639 | $fftw_loaded; |
1624 | } |
1640 | } |
1625 | |
1641 | |
1626 | sub rfft { |
1642 | sub rfft { |
1627 | my $data = $_[0]; |
1643 | my $data = $_[0]; |
1628 | if (_fftw) { |
1644 | if (_fftw) { |
1629 | my $x = $data->r2C; |
1645 | my $x = $data->r2C; |
1630 | PDL::FFTW::nfftw($x); |
1646 | $x = PDL::FFTW::fftw $x; |
1631 | $x; |
1647 | $x; |
1632 | } else { |
1648 | } else { |
1633 | require PDL::FFT; |
1649 | require PDL::FFT; |
1634 | my @fft = ($data->copy, $data->zeroes); |
1650 | my @fft = ($data->copy, $data->zeroes); |
1635 | PDL::FFT::fft(@fft); |
1651 | PDL::FFT::fft(@fft); |
… | |
… | |
1638 | } |
1654 | } |
1639 | |
1655 | |
1640 | sub irfft { |
1656 | sub irfft { |
1641 | if (_fftw) { |
1657 | if (_fftw) { |
1642 | $x = $_[0]->copy; |
1658 | $x = $_[0]->copy; |
1643 | PDL::FFTW::infftw($x); |
1659 | $x = PDL::FFTW::ifftw $x; |
1644 | re $x / $x->getdim(1); |
1660 | re $x / $x->getdim(1); |
1645 | } else { |
1661 | } else { |
1646 | require PDL::FFT; |
1662 | require PDL::FFT; |
1647 | my @fft = $_[0]->xchg(0,1)->dog({Break => 1}); |
1663 | my @fft = $_[0]->xchg(0,1)->dog({Break => 1}); |
1648 | PDL::FFT::ifft(@fft); |
1664 | PDL::FFT::ifft(@fft); |
… | |
… | |
1652 | |
1668 | |
1653 | =head2 spectrum data, [norm], [window], [beta] |
1669 | =head2 spectrum data, [norm], [window], [beta] |
1654 | |
1670 | |
1655 | Returns the spectrum of a given pdl. If C<norm> is absent (or C<undef>), |
1671 | Returns the spectrum of a given pdl. If C<norm> is absent (or C<undef>), |
1656 | it returns the magnitude of the fft of C<data>. When C<norm> == 1 (or |
1672 | it returns the magnitude of the fft of C<data>. When C<norm> == 1 (or |
1657 | C<eq 'NORM'>, in any case), it returns the magnitude, normalized to be |
1673 | C<eq 'NORM'>, case-insensitive), it returns the magnitude, normalized to be |
1658 | between zero and one. If C<norm> == 0 (or C<eq 'dB'>, in any case), then |
1674 | between zero and one. If C<norm> == 0 (or C<eq 'dB'>, case-insensitive), then |
1659 | it returns the magnitude in dB. |
1675 | it returns the magnitude in dB. |
1660 | |
1676 | |
1661 | C<data> is multiplied with C<window> (if not C<undef>) before calculating |
1677 | C<data> is multiplied with C<window> (if not C<undef>) before calculating |
1662 | the fft, and usually contains a window created with C<gen_fft_window> |
1678 | the fft, and usually contains a window created with C<gen_fft_window> |
1663 | (using C<beta>). If C<window> is a string, it is handed over to |
1679 | (using C<beta>). If C<window> is a string, it is handed over to |
… | |
… | |
1670 | |
1686 | |
1671 | sub spectrum { |
1687 | sub spectrum { |
1672 | my ($data, $norm, $window, $beta) = @_; |
1688 | my ($data, $norm, $window, $beta) = @_; |
1673 | my $len; |
1689 | my $len; |
1674 | if (defined $window) { |
1690 | if (defined $window) { |
1675 | $window = gen_fft_window ($data->getdim(0), $window, $beta) unless ref $window; |
1691 | $window = gen_fft_window ($data->getdim (0), $window, $beta) unless ref $window; |
1676 | $data = $data * $window; |
1692 | $data = $data * $window; |
1677 | $len = $window->getdim(0); |
1693 | $len = $window->getdim (0); |
1678 | } else { |
1694 | } else { |
1679 | $len = $data->getdim(0); |
1695 | $len = $data->getdim (0); |
1680 | } |
1696 | } |
1681 | $data = rfft ($data->slice("0:".($len-1))->sever)->slice(",0:".int($len/2))->Cr2p->slice("(0)"); |
1697 | $data = rfft ( |
|
|
1698 | $data->slice ("0:" . ($len - 1)) |
|
|
1699 | ->sever |
|
|
1700 | ) |
|
|
1701 | ->slice (",0:" . int ($len / 2)) |
|
|
1702 | ->PDL::Complex::Cr2p |
|
|
1703 | ->slice ("(0)"); |
1682 | if ($norm == 1 || lc $norm eq "norm") { |
1704 | if ($norm == 1 || lc $norm eq "norm") { |
1683 | $data / max $data; |
1705 | $data / max $data; |
1684 | } elsif (($norm =~ /^[.0]+$/) || (lc $norm eq "db")) { |
1706 | } elsif (($norm =~ /^[.0]+$/) || (lc $norm eq "db")) { |
1685 | log (1e-37 + $data / max $data) * (20 / log 10); |
1707 | log (1e-37 + $data / max $data) * (20 / log 10); |
1686 | } else { |
1708 | } else { |
… | |
… | |
1884 | |
1906 | |
1885 | pp_addpm {At => Bot}, <<'EOD'; |
1907 | pp_addpm {At => Bot}, <<'EOD'; |
1886 | |
1908 | |
1887 | =head1 AUTHOR |
1909 | =head1 AUTHOR |
1888 | |
1910 | |
1889 | Marc Lehmann <pcg@goof.com>. The ideas were mostly taken from common |
1911 | Marc Lehmann <schmorp@schmorp.de>. The ideas were mostly taken from common |
1890 | lisp music (CLM), by Bill Schottstaedt C<bil@ccrma.stanford.edu>. I also |
1912 | lisp music (CLM), by Bill Schottstaedt C<bil@ccrma.stanford.edu>. I also |
1891 | borrowed many explanations (and references) from the clm docs and some |
1913 | borrowed many explanations (and references) from the clm docs and some |
1892 | code from clm.c. Highly inspiring! |
1914 | code from clm.c. Highly inspiring! |
1893 | |
1915 | |
1894 | =head1 SEE ALSO |
1916 | =head1 SEE ALSO |