The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Music::RhythmSet::Voice - a rhythmic line

SYNOPSIS

  use Math::Random::Discrete;
  use Music::RhythmSet::Util qw(write_midi);
  use Music::RhythmSet::Voice;
  
  # different selection odds for three patterns
  my $pat = Math::Random::Discrete->new(
      [ 15,
        30,
        20, ],
      [ [qw/1 0 1 0 0 1/],
        [qw/1 0 1 0 0 0/],
        [qw/1 0 0 0 1 0/] ]);
  # and three ttl
  my $ttl = Math::Random::Discrete->new(
      [ 25, 45, 15, ],
      [ 1,  2,  4 ]);

  # callback: pick a random pattern and ttl
  sub newpat { $pat->rand, $ttl->rand }
  
  my $voice = Music::RhythmSet::Voice->new(
      next => \&newpat
  );
  
  # generate 32 measures of (probably) noise
  $voice->advance(32);

  # export
  my $track = $voice->to_midi(sustain => 1);
  write_midi('noise.midi', $track);

DESCRIPTION

This module encapsulates a single rhythmic voice (or track) and has various methods to advance and change the rhythm over time. Rhythms can be exported in various formats. Music::RhythmSet can store multiple voices, but most of the work is done by this module for each voice.

See eg/beatinator and eg/texty in the distribution for this module for various ways to generate MIDI, import from string form, etc.

Various calls will throw exceptions if something goes awry.

CONSTRUCTOR

The new method accepts any of the "ATTRIBUTES". If both a pattern and a ttl are given they will be automatically added to the replay log. Another option is to only build out the replay log via next callback through the advance method, or to set the replay log manually. measure may need to be set manually if the replay log is changed manually.

BUILD

Constructor helper subroutine. See Moo.

ATTRIBUTES

id

An ID for the voice. This is set automatically by Music::RhythmSet but could be set manually. Must not be changed when the voice belongs to a Music::RhythmSet object. Otherwise it should ideally be a small non-negative integer.

next code-reference

A callback that runs when the ttl expires. This routine must return a new pattern and TTL. The callback is passed a reference to the Music::RhythmSet::Voice object, and a set of parameters with various metadata. It may help to log what is going on:

  use Data::Dumper;
  use Music::RhythmSet::Voice;

  my $voice = Music::RhythmSet::Voice->new(
      next => sub {
          my ( $self, %param ) = @_;
          warn "CALLBACK\n", Dumper \%param;
          return [ 1, 0, 0 ], 8;
      }
  );

  $voice->advance( 16, _foo => 'bar' );

  warn "REPLAY\n", Dumper $voice->replay;

If no callback function is set advance calls may throw an error.

The parameters may optionally be passed in through advance by the caller; certain parameters are set by code in this module. In particular the measure number (counting from 0, not 1) and the current pattern are set by advance.

The advance method of Music::RhythmSet will add a set parameter so that callback code can access the set object that contains the voices.

Callers may want to prefix any custom parameters with _ to minimize potential conflicts with future versions of this module.

measure

The current measure number of the voice. The first measure is 0, not 1, though measure will be 1 following the first advance(1) call. The next callback can make use of this to make decisions based on the measure number, as measure is passed in as a parameter:

  ... = Music::RhythmSet::Voice->new(
      next => sub {
          my ($self, %param) = @_;
          if ($param{measure} == 0) {   # first measure
              ...

The length of a measure will change if the length of the pattern used varies. This may complicate various things that rely on measure numbers, especially if there are multiple voices that use different pattern lengths. See changes in the code for Music::RhythmSet for one way to handle such a case. Another approach would be to resize all the patterns to be the "least common multiple" length so that the pattern length does not vary; see upsize in Music::RhythmSet::Util.

pattern

The current rhythmic pattern, an array reference of zeros and ones; these might be called "beats" where a 1 represents an onset, and 0 silence. A pattern may be considered as a single measure of music (of some number of beats which is the length of the pattern), though measure is used for something else in this code.

replay

An array reference of pattern and ttl pairs, usually created by calling advance for some number of measures with a suitable next callback set.

stash

A place for the caller to store whatever. For example, a voice could vary between a rhythm for seven measures and silence for one using the stash:

  sub silence {
      my ( $self, %param ) = @_;
      $self->next( $self->stash );  # restore previous
      return [ (0) x 16 ], 1;
  }

  sub voice {
      my ( $self, %param ) = @_;
      $self->stash( $self->next );  # save current method
      $self->next( \&silence );     # go quiet
      return [qw/1 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0/], 7;
  }

  Music::RhythmSet::Voice->new( next => \&voice );

The above code uses the stash as a scalar; a hash reference would make more sense if multiple values need be passed around. The above could also be done in a single function that keeps track of how many times it has been called

  sub voice {
      state $yesno = 0;
      $yesno ^= 1;

      if ($yesno) {
          return [qw/1 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0/], 7;
      } else {
          return [ (0) x 16 ], 1;
      }
  }

though changing the callback function may suit more complicated arrangements.

The stash attribute is not used by code in this distribution.

ttl

Time-to-live of the current pattern. Probably should not be changed manually.

METHODS

advance count [ param ]

Step the voice forward by count measures. This may trigger the next attribute callback code and may result in new entries in the replay log.

The various to_* methods will fail if there is nothing in the replay log; this can happen when the replay log is generated only from next calls and you forget to call advance to make those calls happen.

clone [ newid => new-id ]

Clones the object with a new-id that if unset will be the same id as the current object. The ID is optional because the Music::RhythmSet clone method must preserve the ID as that value must track the array index in the voices list; other uses may need different ID values.

from_string string [ param ]

Attempts to parse and push the string (presumably from to_string or of compatible form) onto the replay log. The ID parameter is ignored; all events are assumed to belong to this voice. The events are assumed to be in sequential order; the beat-count field is ignored. Same parameters as to_string. A default split on whitespace delimits the fields.

Lines that only contain whitespace, are empty, or start with a # that may have whitespace before it will be skipped. Trailing whitespace and # comments on lines are ignored.

to_ly [ param ]

Returns the replay log formatted for LilyPond (a text string). Parameters include dur for the note duration (16 or 4 or such), the note (a, b, etc), and rest (probably should be r or s). time can be specified to add \time ... statements to the LilyPond output; assuming the patterns represent 16th notes

  $voice->to_ly(time => 16);

will for a pattern 12 beats in length prefix those notes with \time 12/16. This is limited: there is no way to turn 12/16 into the more common 6/8 or 3/4 forms.

maxm will limit the number of measures produced from the replay log.

The LilyPond "Notation Reference" documentation may be helpful.

to_midi [ param ]
  my $track = $v->to_midi(
    chan         => 0,         # MIDI channel (drumtrack == 9)
    patch_change => 105,       # Banjo
    note         => 42,        # MIDI number
    sustain      => 1,         # hold notes open until next onset
    tempo        => 640e3,     # ought to be fast enough?
    velo         => 99,        # MIDI velocity (loudness)
  );

Encodes the replay log as a MIDI::Track object and returns that. Parameters include chan, dur, note, tempo, velo (see MIDI::Event) as well as the sustain and notext booleans.

sustain holds notes open until the next onset while notext removes the text_event that document where each new pattern,ttl pair begins. sustain may result in the final note of the track having a different duration than in other repeats of the same measure.

Enabling notext will increase the end-of-track raggedness; a MIDI text_event is used to demark where the track ends that notext will remove.

maxm will limit the number of measures produced from the replay log.

Note that the MIDI events are by default duplicated to save memory. If the track events are adjusted via the events_r method of MIDI::Track the individual events must be made unique prior to modification so that a change is not replicated into the repeats of that event:

  my $track = $v->to_midi( ...
  my $tref  = $track->events_r;
  my $i     = 0;
  while (...) {
    ...
    $tref->[$i]    = [ $tref->[$i]->@* ]; # make unique
    $tref->[$i][3] = next_pitch();
    # be sure to also modify the relevant note_off ...

With embig enabled the MIDI events will be de-duplicated. This was added in module version 0.04.

to_string [ param ]

Converts the replay log of the voice (if any) into a custom text format with the fields:

  beat-count voice-id beatstring ttl

This allows a numeric sort on the first column to order the records for multiple voices together in a timeline view. The voice-id must be kept sorted in ascending order if from_string will be used.

eg/texty in the distribution for this module uses this method.

Parameters:

divisor

will divide the beat-count by that value.

maxm

will limit the number of measures produced from the replay log.

rs

record separator, default \n.

sep

field separator, default \t.

BUGS

None known.

SEE ALSO

MIDI, Music::AtonalUtil, Music::RecRhythm

"The Geometry of Musical Rhythm" by Godfried T. Toussaint.

COPYRIGHT AND LICENSE

Copyright 2021 Jeremy Mates

This program is distributed under the (Revised) BSD License: https://opensource.org/licenses/BSD-3-Clause