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   411475 use 5.010000;
  4         79  
10 4     4   19 use strict;
  4         9  
  4         64  
11 4     4   14 use warnings;
  4         8  
  4         101  
12 4     4   18 use Carp qw(croak);
  4         7  
  4         169  
13 4     4   19 use Exporter 'import';
  4         5  
  4         118  
14 4     4   16 use List::Util ();
  4         7  
  4         82  
15 4     4   1785 use Math::Trig qw(pi);
  4         51900  
  4         298  
16 4     4   33 use Scalar::Util qw(looks_like_number);
  4         11  
  4         4954  
17              
18             our $VERSION = '0.08';
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 589 my (%params) = @_;
28 1 50 0     5 if ( !exists $params{rollers} ) {
    0          
29 1         2 $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     11 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     3 if ( !exists $params{summer} ) {
    0          
41 1         2 $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         4 my @nums = map { $params{roll}->( undef, $_ ) } 0 .. $params{rollers} - 1;
  3         8  
46 1         5 my $prev;
47             return sub {
48 10     10   34 my ($n) = @_;
49 10 50 33     32 croak "input must be number" if !defined $n or !looks_like_number $n;
50 10 100       13 if ( defined $prev ) {
51 9         16 for my $rnum ( 0 .. $params{rollers} - 1 ) {
52 27 100       79 if ( ( $n >> $rnum & 1 ) != ( $prev >> $rnum & 1 ) ) {
53 15         21 $nums[$rnum] = $params{roll}->( $n, $rnum );
54             }
55             }
56             }
57 10         12 $prev = $n;
58 10         25 return $params{summer}->(@nums);
59 1         5 };
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 590 my (%params) = @_;
67             croak "must be given list of calls"
68             if !$params{calls}
69 2 50 33     14 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         5 $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         3 my @nums = (0) x @{ $params{calls} };
  2         5  
81             return sub {
82 3     3   451 my ($n) = @_;
83 3 50 33     16 croak "input must be number" if !defined $n or !looks_like_number $n;
84 3         4 for my $k ( 0 .. $#{ $params{calls} } ) {
  3         8  
85 3 50       9 if ( $n % $params{e}**$k == 0 ) {
86 3         6 $nums[$k] = $params{calls}->[$k]->( $n, $k );
87             }
88             }
89 3         11 return $params{summer}->(@nums);
90 2         9 };
91             }
92              
93             sub powers_stateless {
94 2     2 1 1146 my (%params) = @_;
95             croak "must be given list of calls"
96             if !$params{calls}
97 2 50 33     15 or ref $params{calls} ne 'ARRAY';
98 2 50 0     6 if ( !exists $params{summer} ) {
    0          
99 2         6 $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     4 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   44 my ($n) = @_;
110 18 50 33     51 croak "input must be number" if !defined $n or !looks_like_number $n;
111 18         19 my @nums;
112 18         28 for my $k ( 0 .. $#{ $params{calls} } ) {
  18         31  
113 28 100       58 if ( $n % $params{e}**$k == 0 ) {
114 23         35 push @nums, $params{calls}->[$k]->( $n, $k );
115             }
116             }
117 18         95 return $params{summer}->(@nums);
118 2         8 };
119             }
120              
121             # also from "Musimathics, Vol 1" around the same place as the above
122             sub weierstrass {
123 2     2 1 1993 my (%params) = @_;
124 2 50 33     16 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     11 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     8 if ( !defined $params{N} or !looks_like_number $params{N} ) {
131 0         0 croak "N must be a number";
132             }
133 2 100 33     11 if ( !exists $params{phase} ) {
    50          
134 1     231   5 $params{phase} = sub { 0 };
  231         698  
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     4 if ( !exists $params{summer} ) {
    0          
139 2         4 $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   27 my ( $t, $x ) = @_;
145 8 50 33     34 croak "input must be number" if !defined $t or !looks_like_number $t;
146 8   100     22 $x //= 0;
147 8         8 my @nums;
148 8         14 for my $k ( 0 .. $params{N} ) {
149             push @nums,
150             $params{r}**( $k * $params{H} ) *
151             sin(
152 264         1060 $t * pi * $params{r}**( -$k ) + $params{phase}->( $t, $x, $k, %params ) );
153             }
154 8         44 return $params{summer}->(@nums);
155 2         15 };
156             }
157              
158             1;
159             __END__