File Coverage

blib/lib/Tie/Cycle/Sinewave.pm
Criterion Covered Total %
statement 70 72 97.2
branch 47 48 97.9
condition 12 12 100.0
subroutine 12 12 100.0
pod 4 4 100.0
total 145 148 97.9


line stmt bran cond sub pod time code
1             package Tie::Cycle::Sinewave;
2              
3 2     2   37082 use strict;
  2         3  
  2         103  
4              
5             =head1 NAME
6              
7             Tie::Cycle::Sinewave - Cycle through a series of values on a sinewave
8              
9             =head1 VERSION
10              
11             This document describes version 0.05 of Tie::Cycle::Sinewave, released
12             2007-11-07.
13              
14             =cut
15              
16 2     2   11 use vars '$VERSION';
  2         4  
  2         173  
17              
18             $VERSION = '0.05';
19              
20             =head1 SYNOPSIS
21              
22             This module allows you to make a scalar iterate through the values
23             on a sinewave. You set the maximum and minimum values and the number
24             of steps and you're set.
25              
26             use strict;
27             use Tie::Cycle::Sinewave;
28              
29             tie my $cycle, 'Tie::Cycle::Sinewave', {
30             min => 10,
31             max => 50,
32             period => 12,
33             };
34             printf("%0.2f\n", $cycle) for 1..10;
35              
36             =head1 PARAMETERS
37              
38             A number of parameters can be passed in to the creation of the tied
39             object. They are as follows (in order of likely usefulness):
40              
41             =over 4
42              
43             =item min
44              
45             Sets the minimum value. If not specified, 0 will be used as a
46             default minimum.
47              
48             =item max
49              
50             Sets the maximum value. Should be higher than min, but the values
51             will be swapped if necessary. If not specified, 100 will be used
52             as a default maximum.
53              
54             =item period
55              
56             Sets the period of the curve. The cycle will go through this many
57             values from min to max. If not specified, 20 will be used as a
58             default. If period is set to 0, it will be silently changed to 1,
59             to prevent internal calculations from attempting to divide by 0.
60              
61             =item start_max
62              
63             Optional. When set to 1 (or anything), the cyle will start at the
64             maximum value. (C exists as a an alias).
65              
66             =item start_min
67              
68             Optional. When set to 1 (or anything), the cyle will start at the
69             minimum value. (C exists as a an alias). If neither
70             C nor C are specified, it will at the origin
71             (thus, mid-way between min and max and will move to max).
72              
73             =item at_max
74              
75             Optional. When set to a coderef, will be executed when the cycle
76             reaches the maximum value. This allows the modification of the
77             cycle, I modifying the minimum value or the period. (The key
78             C exists as an alias).
79              
80             =item at_min
81              
82             Optional. When set to a coderef, will be executed when the cycle
83             reaches the minimum value. This allows the modification of the
84             cycle, I modifying the maximum value or the period. (The key
85             C exists as an alias).
86              
87             =back
88              
89             =cut
90              
91 2     2   11 use constant PI => 3.1415926535_8979323846_2643383280;
  2         8  
  2         224  
92 2     2   12 use constant PI_2 => 2 * PI;
  2         3  
  2         2881  
93              
94             sub TIESCALAR {
95 7     7   4793 my $class = shift;
96 7 100       37 my %param = ref($_[0]) eq 'HASH' ? %{$_[0]} : @_;
  6         37  
97              
98 7 100       28 my $min = exists $param{min} ? +$param{min} : 0;
99 7 100       21 my $max = exists $param{max} ? +$param{max} : 100;
100 7 100       23 my $period = exists $param{period} ? +$param{period} : 20;
101              
102 7 100       25 $period = 1 if $period == 0;
103              
104 7 100       594 $param{start_max} = delete $param{startmax} if exists $param{startmax};
105 7 100       30 $param{start_min} = delete $param{startmin} if exists $param{startmin};
106              
107 7 100       21 $param{at_max} = delete $param{atmax} if exists $param{atmax};
108 7 100       23 $param{at_min} = delete $param{atmin} if exists $param{atmin};
109              
110 7 100       31 my $start =
    100          
111             exists $param{start_max} ? PI / 2
112             : exists $param{start_min} ? PI / 2 * 3
113             : 0
114             ;
115              
116 7         37 my $self = {
117             min => $min,
118             max => $max,
119             angle => $start,
120             prev => $start,
121             period => $period,
122             };
123              
124 7 100 100     52 $self->{at_max} = $param{at_max} if exists $param{at_max} and ref($param{at_max}) eq 'CODE';
125 7 100 100     35 $self->{at_min} = $param{at_min} if exists $param{at_min} and ref($param{at_min}) eq 'CODE';
126              
127 7         20 $self = bless $self, $class;
128              
129 7         28 $self->_validate_min_max();
130 7         37 $self;
131             }
132              
133             sub FETCH {
134 76     76   6318 my $self = shift;
135 76         167 my $sin_prev = sin( $self->{prev} );
136 76         115 my $sin = sin( $self->{angle} );
137 76         103 my $delta = PI_2 / $self->{period};
138              
139 76         117 $self->{prev} = $self->{angle};
140 76         162 $self->{angle} += $delta;
141 76         462 my $sin_next = sin( $self->{angle} );
142              
143 76         100 my $prev_vs_curr = $sin_prev <=> $sin;
144 76         86 my $curr_vs_next = $sin <=> $sin_next;
145              
146 76 100 100     743 if( -1 == $prev_vs_curr and 1 == $curr_vs_next ) {
    100 100        
147             # the previous is smaller than the current,
148             # and the current is greater than the next,
149             # therefore we must be at the top of the wave.
150 4 100       20 exists $self->{at_max} and $self->{at_max}->($self);
151              
152             # Clamp the value to 0 < x < 2PI. For long running cycles this
153             # should improve accuracy (if P.J. Plauger it to be believed).
154 4 100       15 if( $self->{prev} > PI_2 ) {
155 3         6 $self->{prev} -= PI_2;
156 3         6 $self->{angle} -= PI_2;
157             }
158             }
159             elsif( 1 == $prev_vs_curr and -1 == $curr_vs_next ) {
160             # at the bottom (trough) of the wave
161 3 100       878 exists $self->{at_min} and $self->{at_min}->($self);
162             }
163              
164 76         331 (($sin + 1) / 2) * ($self->{max} - $self->{min}) + $self->{min};
165             }
166              
167             sub STORE {
168 1     1   9 my $self = shift;
169 1         5 $self->{angle} = $self->{prev} = $_[0];
170             }
171              
172             =head1 OBJECT METHODS
173              
174             You can call methods on the underlying object (which you access with the
175             C function). Have a look at the file C for an
176             example on what you might want to do with these.
177              
178             =over 4
179              
180             =item min
181              
182             When called without a parameter, returns the current minimum value. When
183             called with a (numeric) parameter, sets the new current minimum value.
184             The previous value is returned.
185              
186             my $min = (tied $cycle)->min();
187             (tied $cycle)->min($min - 20);
188              
189             =cut
190              
191             sub min {
192 5     5 1 640 my $self = shift;
193 5         12 my $old = $self->{min};
194 5 100       19 if( @_ ) {
195 1         3 $self->{min} = shift;
196 1         4 $self->_validate_min_max();
197             }
198 5         26 $old;
199             }
200              
201             =item max
202              
203             When called without a parameter, returns the current maximum value. When
204             called with a (numeric) parameter, sets the new current maximum value.
205             The previous value is returned.
206              
207             my $max = (tied $cycle)->max();
208             (tied $cycle)->max($max * 10);
209              
210             When C or C are modified, a consistency check is run to ensure
211             that C. If this check fails, the two values are quietly swapped
212             around.
213              
214             =cut
215              
216             sub max {
217 6     6 1 13 my $self = shift;
218 6         13 my $old = $self->{max};
219 6 100       19 if( @_ ) {
220 2         22 $self->{max} = shift;
221 2         7 $self->_validate_min_max();
222             }
223 6         28 $old;
224             }
225              
226             =item period
227              
228             When called without a parameter, returns the current period. When
229             called with a (numeric) parameter, sets the new current period.
230             The previous value is returned.
231              
232             =cut
233              
234             sub period {
235 6     6 1 19 my $self = shift;
236 6         105 my $old = $self->{period};
237 6 100       18 if( @_ ) {
238 2         6 $self->{period} = shift;
239 2 100       10 $self->{period} = 1 if $self->{period} == 0;
240             }
241 6         29 $old;
242             }
243              
244             sub _validate_min_max {
245 10 100   10   64 ($_[0]->{min}, $_[0]->{max}) = ($_[0]->{max}, $_[0]->{min}) if $_[0]->{max} < $_[0]->{min};
246             }
247              
248             =item angle
249              
250             Returns the current angle of the sine, which is guaranteed to be
251             in the range C< 0 <= angle <= 2*PI>.
252              
253             =back
254              
255             =cut
256              
257             sub angle {
258 2     2 1 8 my $self = shift;
259 2 50       8 if( $self->{prev} > PI_2 ) {
260 0         0 $self->{prev} -= PI_2;
261 0         0 $self->{angle} -= PI_2;
262             }
263 2         6 $self->{angle}
264             }
265              
266             =head1 AUTHOR
267              
268             David Landgren.
269              
270             =head1 SEE ALSO
271              
272             L
273             L
274              
275             =head1 BUGS
276              
277             Please report any bugs or feature requests to
278             C, or through the web interface at
279             L.
280              
281             =head1 COPYRIGHT & LICENSE
282              
283             Copyright 2005-2007 David Landgren, All Rights Reserved.
284              
285             This program is free software; you can redistribute it and/or modify it
286             under the same terms as Perl itself.
287              
288             =cut
289              
290             1; # End of Tie::Cycle::Sinewave