The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

our $AUTHORITY = 'cpan:GENE';
# ABSTRACT: Control-change based RtController filters
our $VERSION = '0.0103';
use v5.36;
use strictures 2;
use Moo;
use Time::HiRes qw(usleep);
use Types::MIDI qw(Channel Velocity);
use Types::Common::Numeric qw(PositiveNum);
use Types::Standard qw(Bool Num);
has rtc => (
is => 'ro',
isa => sub { die 'Invalid rtc' unless ref($_[0]) eq 'MIDI::RtController' },
required => 1,
);
has channel => (
is => 'rw',
isa => Channel,
default => 0,
);
has control => (
is => 'rw',
isa => Velocity, # no CC# in Types::MIDI yet
default => 1,
);
has range_bottom => (
is => 'rw',
isa => Num,
default => 0,
);
has range_top => (
is => 'rw',
isa => Num,
default => 127,
);
has range_step => (
is => 'rw',
isa => PositiveNum,
default => 1,
);
has time_step => (
is => 'rw',
isa => PositiveNum,
default => 250_000,
);
has running => (
is => 'rw',
isa => Bool,
default => 0,
);
has stop => (
is => 'rw',
isa => Bool,
default => 0,
);
sub breathe ($self, $device, $dt, $event) {
return 0 if $self->running;
my ($ev, $chan, $ctl, $val) = $event->@*;
my $it = Iterator::Breathe->new(
bottom => $self->range_bottom,
top => $self->range_top,
step => $self->range_step,
);
$self->running(1);
while (!$self->stop) {
$it->iterate;
my $cc = [ 'control_change', $self->channel, $self->control, $it->i ];
$self->rtc->send_it($cc);
usleep $self->time_step;
}
return 0;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
MIDI::RtController::Filter::CC - Control-change based RtController filters
=head1 VERSION
version 0.0103
=head1 SYNOPSIS
use curry;
use MIDI::RtController ();
use MIDI::RtController::Filter::CC ();
my $control = MIDI::RtController->new(
input => 'keyboard',
output => 'usb',
);
my $filter = MIDI::RtController::Filter::CC->new(rtc => $control);
$filter->control(1); # CC#01 = mod-wheel
$filter->channel(0);
$filter->range_bottom(10);
$filter->range_top(100);
$filter->range_step(2);
$filter->time_step(125_000);
$control->add_filter('breathe', all => $filter->curry::breathe);
$control->run;
=head1 DESCRIPTION
C<MIDI::RtController::Filter::CC> is a (growing) collection of
control-change based L<MIDI::RtController> filters.
=head2 Making filters
All filter methods must accept the object, a MIDI device name, a
delta-time, and a MIDI event ARRAY reference, like:
sub breathe ($self, $device, $delta, $event) {
my ($event_type, $chan, $note, $value) = $event->@*;
...
return $boolean;
}
A filter also must return a boolean value. This tells
L<MIDI::RtController> to continue processing other known filters or
not.
=head1 ATTRIBUTES
=head2 rtc
$rtc = $filter->rtc;
The required L<MIDI::RtController> instance provided in the
constructor.
=head2 channel
$channel = $filter->channel;
$filter->channel($number);
The current MIDI channel value between C<0> and C<15>.
Default: C<0>
=head2 control
$control = $filter->control;
$filter->control($number);
Return or set the control change number.
Default: C<1> (mod-wheel)
=head2 range_bottom
$range_bottom = $filter->range_bottom;
$filter->range_bottom($number);
The current iteration lowest number value.
Default: C<0>
=head2 range_top
$range_top = $filter->range_top;
$filter->range_top($number);
The current iteration highest number value.
Default: C<127>
=head2 range_step
$range_step = $filter->range_step;
$filter->range_step($number);
A number greater than zero representing the current iteration step
size between B<bottom> and B<top>.
Default: C<1>
=head2 time_step
$time_step = $filter->time_step;
$filter->time_step($number);
The current iteration step in microseconds (where
C<1,000,000> = C<1> second).
Default: C<250_000> (a quarter of a second)
=head2 running
$running = $filter->running;
$filter->running($boolean);
Are we running a filter?
Default: C<0>
=head2 stop
$stop = $filter->stop;
$filter->stop($boolean);
Stop running a filter.
Default: C<0>
=head1 METHODS
=head2 new
$filter = MIDI::RtController::Filter::CC->new(%arguments);
Return a new C<MIDI::RtController::Filter::CC> object.
=head2 breathe
This filter sets the B<running> flag and then iterates between the
B<range_bottom> and B<range_top> by B<range_step> increments, sending
a B<control> change message, over the MIDI B<channel> every iteration,
until B<stop> is seen.
=head1 SEE ALSO
L<Iterator::Breathe>
L<Moo>
L<Time::HiRes>
L<Types::Common::Numeric>
L<Types::MIDI>
L<Types::Standard>
=head1 AUTHOR
Gene Boggs <gene.boggs@gmail.com>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2025 by Gene Boggs.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut