File Coverage

blib/lib/MIDI/RtController/Filter/CC.pm
Criterion Covered Total %
statement 29 224 12.9
branch 0 68 0.0
condition 0 49 0.0
subroutine 10 26 38.4
pod 9 9 100.0
total 48 376 12.7


line stmt bran cond sub pod time code
1             package MIDI::RtController::Filter::CC;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Control-change based RtController filters
5              
6             our $VERSION = '0.1200';
7              
8 2     2   563687 use v5.36;
  2         6  
9              
10 2     2   792 use strictures 2;
  2         2771  
  2         84  
11 2     2   2067 use curry;
  2         630  
  2         69  
12 2     2   798 use IO::Async::Timer::Countdown ();
  2         32504  
  2         46  
13 2     2   815 use IO::Async::Timer::Periodic ();
  2         1663  
  2         67  
14 2     2   854 use Iterator::Breathe ();
  2         133669  
  2         90  
15 2     2   39 use Moo;
  2         5  
  2         11  
16 2     2   2429 use Types::MIDI qw(Velocity);
  2         601697  
  2         31  
17 2     2   5626 use Types::Common::Numeric qw(PositiveNum);
  2         6  
  2         21  
18 2     2   2161 use namespace::clean;
  2         6  
  2         24  
19              
20             extends 'MIDI::RtController::Filter';
21              
22              
23             has control => (
24             is => 'rw',
25             isa => Velocity, # no CC# in Types::MIDI yet
26             default => 1,
27             );
28              
29              
30             has initial_point => (
31             is => 'rw',
32             isa => Velocity, # no CC# msg value in Types::MIDI yet
33             default => 0,
34             );
35              
36              
37             has range_bottom => (
38             is => 'rw',
39             isa => Velocity, # no CC# msg value in Types::MIDI yet
40             default => 0,
41             );
42              
43              
44             has range_top => (
45             is => 'rw',
46             isa => Velocity, # no CC# msg value in Types::MIDI yet
47             default => 127,
48             );
49              
50              
51             has range_step => (
52             is => 'rw',
53             isa => Velocity, # no CC# msg value in Types::MIDI yet
54             default => 1,
55             );
56              
57              
58             has time_step => (
59             is => 'rw',
60             isa => PositiveNum,
61             default => 0.25,
62             );
63              
64              
65             has step_up => (
66             is => 'rw',
67             isa => Velocity, # no CC# in Types::MIDI yet
68             default => 2,
69             );
70              
71              
72             has step_down => (
73             is => 'rw',
74             isa => Velocity, # no CC# in Types::MIDI yet
75             default => 1,
76             );
77              
78              
79 0     0 1   sub add_filters ($filters, $controllers) {
  0            
  0            
  0            
80 0           for my $params (@$filters) {
81 0           my $port = delete $params->{port};
82             # skip unnamed and unknown entries
83 0 0 0       next if !$port || !exists $controllers->{$port};
84 0   0       my $type = delete $params->{type} || 'single';
85 0   0       my $event = delete $params->{event} || 'all';
86             my $filter = __PACKAGE__->new(
87 0           rtc => $controllers->{$port}
88             );
89             # assume all remaining key/values are module attributes
90 0           for my $param (keys %$params) {
91 0           $filter->$param($params->{$param});
92             }
93 0           my $method = "curry::$type";
94 0           $controllers->{$port}->add_filter($type, $event => $filter->$method);
95             }
96             }
97              
98              
99 0     0 1   sub single ($self, $device, $dt, $event) {
  0            
  0            
  0            
  0            
  0            
100 0           my ($ev, $chan, $note, $val) = $event->@*;
101 0 0 0       return 0 if defined $self->trigger && $note != $self->trigger;
102              
103 0   0       my $value = $self->value // $val;
104 0           my $cc = [ 'control_change', $self->channel, $self->control, $value ];
105 0           $self->rtc->send_it($cc);
106              
107 0           return $self->continue;
108             }
109              
110              
111 0     0 1   sub clock_it ($self, $device, $dt, $event) {
  0            
  0            
  0            
  0            
  0            
112 0 0         return 0 if $self->running;
113              
114 0           $self->running(1);
115              
116 0           $self->rtc->send_it(['start']);
117              
118             $self->rtc->loop->add(
119             IO::Async::Timer::Periodic->new(
120             interval => $self->time_step,
121             on_tick => sub {
122 0     0     my ($c) = @_;
123 0 0         if ($self->halt) {
124 0           $self->rtc->send_it(['stop']);
125 0           $c->stop;
126 0           $self->running(0);
127 0           $self->halt(0);
128             }
129             else {
130 0           $self->rtc->send_it(['clock']);
131             }
132             },
133 0           )->start
134             );
135              
136 0           return $self->continue;
137             }
138              
139              
140 0     0 1   sub breathe ($self, $device, $dt, $event) {
  0            
  0            
  0            
  0            
  0            
141 0 0         return 0 if $self->running;
142              
143 0           my ($ev, $chan, $note, $val) = $event->@*;
144 0 0 0       return 0 if defined $self->trigger && $note != $self->trigger;
145 0 0 0       return 0 if defined $self->value && $val != $self->value;
146              
147 0           $self->running(1);
148              
149 0           my $it = Iterator::Breathe->new(
150             bottom => $self->range_bottom,
151             top => $self->range_top,
152             step => $self->range_step,
153             );
154              
155             $self->rtc->loop->add(
156             IO::Async::Timer::Periodic->new(
157             interval => $self->time_step,
158             on_tick => sub {
159 0     0     my ($c) = @_;
160 0 0         if ($self->halt) {
161 0           $c->stop;
162 0           $self->running(0);
163 0           $self->halt(0);
164             }
165             else {
166 0           $it->iterate;
167 0           my $cc = [ 'control_change', $self->channel, $self->control, $it->i ];
168 0           $self->rtc->send_it($cc);
169             }
170             },
171 0           )->start
172             );
173              
174 0           return $self->continue;
175             }
176              
177              
178 0     0 1   sub scatter ($self, $device, $dt, $event) {
  0            
  0            
  0            
  0            
  0            
179 0 0         return 0 if $self->running;
180              
181 0           my ($ev, $chan, $note, $val) = $event->@*;
182 0 0 0       return 0 if defined $self->trigger && $note != $self->trigger;
183 0 0 0       return 0 if defined $self->value && $val != $self->value;
184              
185 0           $self->running(1);
186              
187 0           my $value = $self->initial_point;
188 0           my @values = ($self->range_bottom .. $self->range_top);
189              
190             $self->rtc->loop->add(
191             IO::Async::Timer::Periodic->new(
192             interval => $self->time_step,
193             on_tick => sub {
194 0     0     my ($c) = @_;
195 0 0         if ($self->halt) {
196 0           $c->stop;
197 0           $self->running(0);
198 0           $self->halt(0);
199             }
200             else {
201 0           my $cc = [ 'control_change', $self->channel, $self->control, $value ];
202 0           $self->rtc->send_it($cc);
203 0           $value = $values[ int rand @values ];
204             }
205             },
206 0           )->start
207             );
208              
209 0           return $self->continue;
210             }
211              
212              
213 0     0 1   sub stair_step ($self, $device, $dt, $event) {
  0            
  0            
  0            
  0            
  0            
214 0 0         return 0 if $self->running;
215              
216 0           my ($ev, $chan, $note, $val) = $event->@*;
217 0 0 0       return 0 if defined $self->trigger && $note != $self->trigger;
218 0 0 0       return 0 if defined $self->value && $val != $self->value;
219              
220 0           $self->running(1);
221              
222 0           my $it = Iterator::Breathe->new(
223             bottom => $self->range_bottom,
224             top => $self->range_top,
225             );
226              
227 0           my $value = $self->initial_point;
228 0           my $direction = 1; # up
229              
230             $self->rtc->loop->add(
231             IO::Async::Timer::Periodic->new(
232             interval => $self->time_step,
233             on_tick => sub {
234 0     0     my ($c) = @_;
235 0 0         if ($self->halt) {
236 0           $c->stop;
237 0           $self->running(0);
238 0           $self->halt(0);
239             }
240             else {
241 0           my $cc = [ 'control_change', $self->channel, $self->control, $value ];
242 0           $self->rtc->send_it($cc);
243              
244             # compute the stair-stepping
245 0 0         if ($direction) {
246 0           $it->step($self->step_up);
247             }
248             else {
249 0           $it->step(- $self->step_down);
250             }
251              
252             # toggle the stair-step direction
253 0           $direction = !$direction;
254              
255             # iterate the breathing
256 0           $it->iterate;
257 0           $value = $it->i;
258 0 0         $value = $self->range_top if $value >= $self->range_top;
259 0 0         $value = $self->range_bottom if $value <= $self->range_bottom;
260             }
261             },
262 0           )->start
263             );
264 0           return $self->continue;
265             }
266              
267              
268 0     0 1   sub ramp_up ($self, $device, $dt, $event) {
  0            
  0            
  0            
  0            
  0            
269 0 0         return 0 if $self->running;
270              
271 0           my ($ev, $chan, $note, $val) = $event->@*;
272 0 0 0       return 0 if defined $self->trigger && $note != $self->trigger;
273 0 0 0       return 0 if defined $self->value && $val != $self->value;
274              
275 0           $self->running(1);
276              
277 0           my $value = $self->initial_point;
278              
279             $self->rtc->loop->add(
280             IO::Async::Timer::Countdown->new(
281             delay => $self->time_step,
282             on_expire => sub {
283 0     0     my ($c) = @_;
284 0 0         if ($self->halt) {
285 0           $c->stop;
286 0           $self->running(0);
287 0           $self->halt(0);
288             }
289             else {
290 0           my $cc = [ 'control_change', $self->channel, $self->control, $value ];
291 0           $self->rtc->send_it($cc);
292              
293 0           $value += $self->range_step;
294              
295 0 0         if ($value > $self->range_top) {
296 0           $c->stop;
297 0           $self->running(0);
298             }
299             else {
300 0           $c->start;
301             }
302             }
303             },
304 0           )->start
305             );
306              
307 0           return $self->continue;
308             }
309              
310              
311 0     0 1   sub ramp_down ($self, $device, $dt, $event) {
  0            
  0            
  0            
  0            
  0            
312 0 0         return 0 if $self->running;
313              
314 0           my ($ev, $chan, $note, $val) = $event->@*;
315 0 0 0       return 0 if defined $self->trigger && $note != $self->trigger;
316 0 0 0       return 0 if defined $self->value && $val != $self->value;
317              
318 0           $self->running(1);
319              
320 0           my $value = $self->initial_point;
321              
322             $self->rtc->loop->add(
323             IO::Async::Timer::Countdown->new(
324             delay => $self->time_step,
325             on_expire => sub {
326 0     0     my ($c) = @_;
327 0 0         if ($self->halt) {
328 0           $c->stop;
329 0           $self->running(0);
330 0           $self->halt(0);
331             }
332             else {
333 0           my $cc = [ 'control_change', $self->channel, $self->control, $value ];
334 0           $self->rtc->send_it($cc);
335              
336 0           $value -= $self->range_step;
337              
338 0 0         if ($value < $self->range_bottom) {
339 0           $c->stop;
340 0           $self->running(0);
341             }
342             else {
343 0           $c->start;
344             }
345             }
346             },
347 0           )->start
348             );
349              
350 0           return $self->continue;
351             }
352              
353              
354 0     0 1   sub flicker ($self, $device, $dt, $event) {
  0            
  0            
  0            
  0            
  0            
355 0 0         return 0 if $self->running;
356              
357 0           my ($ev, $chan, $note, $val) = $event->@*;
358 0 0 0       return 0 if defined $self->trigger && $note != $self->trigger;
359 0 0 0       return 0 if defined $self->value && $val != $self->value;
360              
361 0           $self->running(1);
362              
363 0           my $value = $self->range_bottom;
364              
365             $self->rtc->loop->add(
366             IO::Async::Timer::Countdown->new(
367             delay => $self->time_step,
368             on_expire => sub {
369 0     0     my ($c) = @_;
370 0 0         if ($self->halt) {
371 0           $c->stop;
372 0           $self->running(0);
373 0           $self->halt(0);
374             }
375             else {
376 0           my $cc = [ 'control_change', $self->channel, $self->control, $value ];
377 0           $self->rtc->send_it($cc);
378 0 0         $value = $value == $self->range_top ? $self->range_bottom : $self->range_top;
379 0           $c->start;
380             }
381             },
382 0           )->start
383             );
384              
385 0           return $self->continue;
386             }
387              
388             1;
389              
390             __END__