File Coverage

blib/lib/Music/Voss.pm
Criterion Covered Total %
statement 88 104 84.6
branch 29 62 46.7
condition 14 56 25.0
subroutine 17 18 94.4
pod 4 4 100.0
total 152 244 62.3


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # Functions for fractal noise generation functions.
4             #
5             # Run perldoc(1) on this file for additional documentation.
6              
7             package Music::Voss;
8              
9 4     4   487210 use 5.010000;
  4         45  
10 4     4   21 use strict;
  4         6  
  4         77  
11 4     4   17 use warnings;
  4         6  
  4         134  
12 4     4   23 use Carp qw(croak);
  4         7  
  4         205  
13 4     4   23 use Exporter 'import';
  4         7  
  4         133  
14 4     4   26 use List::Util ();
  4         8  
  4         128  
15 4     4   2093 use Math::Trig qw(pi);
  4         54572  
  4         326  
16 4     4   33 use Scalar::Util qw(looks_like_number);
  4         8  
  4         5074  
17              
18             our $VERSION = '0.16';
19              
20             our @EXPORT_OK = qw(bitchange powers powers_stateless weierstrass);
21              
22             # This method derived from the "White and brown music, fractal curves
23             # and one-over-f fluctuations" article. Should also be possible with
24             # physical dice and a binary chart to show how the bits change between
25             # tries and thus which dice need be re-rolled for a particular try.
26             sub bitchange {
27 1     1 1 795 my (%params) = @_;
28 1 50 0     5 if ( !exists $params{rollers} ) {
    0          
29 1         4 $params{rollers} = 3;
30             } elsif ( !defined $params{rollers} or !looks_like_number $params{rollers} ) {
31 0         0 croak "rollers must be a number";
32             } else {
33 0         0 $params{rollers} = int $params{rollers};
34             }
35 1 50 33     13 if ( !exists $params{roll} ) {
    50          
36 0     0   0 $params{roll} = sub { int rand 6 };
  0         0  
37             } elsif ( !defined $params{roll} or ref $params{roll} ne 'CODE' ) {
38 0         0 croak "roll must be code reference";
39             }
40 1 50 0     4 if ( !exists $params{summer} ) {
    0          
41 1         3 $params{summer} = \&List::Util::sum0;
42             } elsif ( !defined $params{summer} or ref $params{summer} ne 'CODE' ) {
43 0         0 croak "summer must be code reference";
44             }
45 1         6 my @nums = map { $params{roll}->( undef, $_ ) } 0 .. $params{rollers} - 1;
  3         12  
46 1         6 my $prev;
47             return sub {
48 10     10   44 my ($n) = @_;
49 10 50 33     37 croak "input must be number" if !defined $n or !looks_like_number $n;
50 10 100       17 if ( defined $prev ) {
51 9         20 for my $rnum ( 0 .. $params{rollers} - 1 ) {
52 27 100       68 if ( ( $n >> $rnum & 1 ) != ( $prev >> $rnum & 1 ) ) {
53 15         29 $nums[$rnum] = $params{roll}->( $n, $rnum );
54             }
55             }
56             }
57 10         14 $prev = $n;
58 10         29 return $params{summer}->(@nums);
59 1         7 };
60             }
61              
62             # "Musimathics, Vol 1" p.358 based function generator based on powers of
63             # (by default) 2, the supplied number, and a list of subroutines to
64             # (perhaps) run. (Was called "voss", orginally.)
65             sub powers {
66 2     2 1 602 my (%params) = @_;
67             croak "must be given list of calls"
68             if !$params{calls}
69 2 50 33     17 or ref $params{calls} ne 'ARRAY';
70 2 50 33     13 if ( !exists $params{summer} ) {
    50          
71 0         0 $params{summer} = \&List::Util::sum0;
72             } elsif ( !defined $params{summer} or ref $params{summer} ne 'CODE' ) {
73 0         0 croak "summer must be code reference";
74             }
75 2 50 0     5 if ( !exists $params{e} ) {
    0          
76 2         38 $params{e} = 2;
77             } elsif ( !defined $params{e} or !looks_like_number $params{e} ) {
78 0         0 croak "e must be a number";
79             }
80 2         5 my @nums = (0) x @{ $params{calls} };
  2         6  
81             return sub {
82 3     3   549 my ($n) = @_;
83 3 50 33     26 croak "input must be number" if !defined $n or !looks_like_number $n;
84 3         5 for my $k ( 0 .. $#{ $params{calls} } ) {
  3         10  
85 3 50       11 if ( $n % $params{e}**$k == 0 ) {
86 3         7 $nums[$k] = $params{calls}->[$k]->( $n, $k );
87             }
88             }
89 3         16 return $params{summer}->(@nums);
90 2         15 };
91             }
92              
93             sub powers_stateless {
94 2     2 1 1206 my (%params) = @_;
95             croak "must be given list of calls"
96             if !$params{calls}
97 2 50 33     17 or ref $params{calls} ne 'ARRAY';
98 2 50 0     6 if ( !exists $params{summer} ) {
    0          
99 2         5 $params{summer} = \&List::Util::sum0;
100             } elsif ( !defined $params{summer} or ref $params{summer} ne 'CODE' ) {
101 0         0 croak "summer must be code reference";
102             }
103 2 50 0     5 if ( !exists $params{e} ) {
    0          
104 2         5 $params{e} = 2;
105             } elsif ( !defined $params{e} or !looks_like_number $params{e} ) {
106 0         0 croak "e must be a number";
107             }
108             return sub {
109 18     18   53 my ($n) = @_;
110 18 50 33     61 croak "input must be number" if !defined $n or !looks_like_number $n;
111 18         21 my @nums;
112 18         32 for my $k ( 0 .. $#{ $params{calls} } ) {
  18         35  
113 28 100       68 if ( $n % $params{e}**$k == 0 ) {
114 23         44 push @nums, $params{calls}->[$k]->( $n, $k );
115             }
116             }
117 18         101 return $params{summer}->(@nums);
118 2         11 };
119             }
120              
121             # also from "Musimathics, Vol 1" around the same place as the above
122             sub weierstrass {
123 2     2 1 2343 my (%params) = @_;
124 2 50 33     21 if ( !defined $params{r} or !looks_like_number $params{r} ) {
125 0         0 croak "r must be a number (0 < r <= 1)";
126             }
127 2 50 33     14 if ( !defined $params{H} or !looks_like_number $params{H} ) {
128 0         0 croak "H must be a number (0 < H <= 1)";
129             }
130 2 50 33     11 if ( !defined $params{N} or !looks_like_number $params{N} ) {
131 0         0 croak "N must be a number";
132             }
133 2 100 33     14 if ( !exists $params{phase} ) {
    50          
134 1     224   6 $params{phase} = sub { 0 };
  224         807  
135             } elsif ( !defined $params{phase} or ref $params{phase} ne 'CODE' ) {
136 0         0 croak "phase must be code reference";
137             }
138 2 50 0     5 if ( !exists $params{summer} ) {
    0          
139 2         6 $params{summer} = \&List::Util::sum0;
140             } elsif ( !defined $params{summer} or ref $params{summer} ne 'CODE' ) {
141 0         0 croak "summer must be code reference";
142             }
143             return sub {
144 8     8   33 my ( $t, $x ) = @_;
145 8 50 33     45 croak "input must be number" if !defined $t or !looks_like_number $t;
146 8   100     25 $x //= 0;
147 8         12 my @nums;
148 8         17 for my $k ( 0 .. $params{N} - 1 ) {
149             push @nums,
150             $params{r}**( $k * $params{H} ) *
151             sin(
152 256         1263 $t * pi * $params{r}**( -$k ) + $params{phase}->( $t, $x, $k, %params ) );
153             }
154 8         44 return $params{summer}->(@nums);
155 2         18 };
156             }
157              
158             1;
159             __END__