| File: | blib/lib/Music/NWC2MusicXML/Staff.pm |
| Coverage: | 97.2% |
| line | stmt | bran | cond | sub | time | code |
|---|---|---|---|---|---|---|
| 1 | package Music::NWC2MusicXML::Staff; | |||||
| 2 | ||||||
| 3 | 15 15 15 | 12 8 59 | use strict; | |||
| 4 | 15 15 15 | 10 6 163 | use warnings; | |||
| 5 | ||||||
| 6 | our $VERSION = '0.001.1'; | |||||
| 7 | ||||||
| 8 | 15 15 15 | 14 15 123 | use Carp qw(croak carp); | |||
| 9 | 15 15 15 | 8 7 109 | use Readonly; | |||
| 10 | 15 15 15 | 8 9 118 | use Scalar::Util qw(blessed); | |||
| 11 | 15 15 15 | 13 4 109 | use Params::Validate::Strict qw(validate_strict); | |||
| 12 | 15 15 15 | 13 8 80 | use Params::Get; | |||
| 13 | 15 15 15 | 986 13 3275 | use Music::NWC2MusicXML::Event; | |||
| 14 | ||||||
| 15 | Readonly::Hash my %MESSAGES => ( | |||||
| 16 | error_bad_event => 'add_event: argument must be a Music::NWC2MusicXML::Event, got: %s', | |||||
| 17 | error_internal => 'Internal error: %s', | |||||
| 18 | ); | |||||
| 19 | ||||||
| 20 | # Event types that constitute a sounding (or structurally significant) event | |||||
| 21 | # for the purpose of has_notes(). Hash lookup is O(1); avoids regex in a loop. | |||||
| 22 | Readonly::Hash my %SOUNDING_EVENT_TYPES => map { $_ => 1 } qw(Note Rest Chord Bar); | |||||
| 23 | ||||||
| 24 - 55 | =head1 NAME
Music::NWC2MusicXML::Staff - Internal representation of a single NWC staff.
=head1 VERSION
0.001.1
=head1 SYNOPSIS
use Music::NWC2MusicXML::Staff;
my $staff = Music::NWC2MusicXML::Staff->new(
name => 'Violin I',
group => 'Standard',
instrument => { name => 'String Ensemble 1', patch => 48 },
);
$staff->add_event($note_event);
my $events = $staff->events;
=head1 DESCRIPTION
C<Music::NWC2MusicXML::Staff> holds all information about one NWC staff: its
properties (name, visibility, number of lines), instrument data, and the
ordered sequence of C<Music::NWC2MusicXML::Event> objects that constitute its
musical content.
The event list is in parse order. MusicXML generation iterates over it
to produce C<< <measure> >> elements.
=cut | |||||
| 56 | ||||||
| 57 | sub new { | |||||
| 58 | 550 | 61696 | my ($class, %input) = @_; | |||
| 59 | 550 | 3309 | my $args = validate_strict( | |||
| 60 | schema => { | |||||
| 61 | name => { type => 'scalar', optional => 1, default => 'Staff' }, | |||||
| 62 | group => { type => 'scalar', optional => 1, default => 'Standard' }, | |||||
| 63 | lines => { type => 'scalar', optional => 1, default => 5 }, | |||||
| 64 | visible => { type => 'scalar', optional => 1, default => 1 }, | |||||
| 65 | ending_bar => { type => 'scalar', optional => 1 }, | |||||
| 66 | instrument => { type => 'hashref', optional => 1, default => {} }, | |||||
| 67 | initial_clef => { type => 'scalar', optional => 1 }, | |||||
| 68 | initial_key => { type => 'hashref', optional => 1 }, | |||||
| 69 | initial_timesig => { type => 'hashref', optional => 1 }, | |||||
| 70 | }, | |||||
| 71 | input => \%input, | |||||
| 72 | ); | |||||
| 73 | 550 | 61988 | croak $@ unless defined $args; | |||
| 74 | ||||||
| 75 | my $self = bless { | |||||
| 76 | _name => $args->{name}, | |||||
| 77 | _group => $args->{group}, | |||||
| 78 | _lines => $args->{lines}, | |||||
| 79 | _visible => $args->{visible}, | |||||
| 80 | _ending_bar => $args->{ending_bar}, | |||||
| 81 | _instrument => $args->{instrument}, | |||||
| 82 | _initial_clef => $args->{initial_clef}, | |||||
| 83 | _initial_key => $args->{initial_key}, | |||||
| 84 | _initial_timesig => $args->{initial_timesig}, | |||||
| 85 | 550 | 1419 | _events => [], | |||
| 86 | }, $class; | |||||
| 87 | ||||||
| 88 | 550 | 565 | return $self; | |||
| 89 | } | |||||
| 90 | ||||||
| 91 | # --------------------------------------------------------------------------- | |||||
| 92 | # Accessors | |||||
| 93 | # --------------------------------------------------------------------------- | |||||
| 94 | ||||||
| 95 - 99 | =head2 name Return the staff name string. =cut | |||||
| 100 | ||||||
| 101 | 417 | 668 | sub name { return $_[0]->{_name} } | |||
| 102 | ||||||
| 103 - 107 | =head2 group Return the group name string. =cut | |||||
| 108 | ||||||
| 109 | 5 | 6 | sub group { return $_[0]->{_group} } | |||
| 110 | ||||||
| 111 - 115 | =head2 lines Return the number of staff lines (usually 5). =cut | |||||
| 116 | ||||||
| 117 | 6 | 115 | sub lines { return $_[0]->{_lines} } | |||
| 118 | ||||||
| 119 - 123 | =head2 visible Return 1 if the staff is visible, 0 otherwise. =cut | |||||
| 124 | ||||||
| 125 | 6 | 10 | sub visible { return $_[0]->{_visible} } | |||
| 126 | ||||||
| 127 - 133 | =head2 instrument Return the instrument information hashref. Keys: C<name> (string), C<patch> (MIDI patch number 0-127). =cut | |||||
| 134 | ||||||
| 135 | 902 | 805 | sub instrument { return $_[0]->{_instrument} } | |||
| 136 | ||||||
| 137 - 141 | =head2 initial_clef Return the initial clef string (e.g. C<Treble>), or undef if none recorded. =cut | |||||
| 142 | ||||||
| 143 | 775 | 1238 | sub initial_clef { return $_[0]->{_initial_clef} } | |||
| 144 | ||||||
| 145 - 149 | =head2 set_initial_clef Set the initial clef. =cut | |||||
| 150 | ||||||
| 151 | sub set_initial_clef { | |||||
| 152 | 264 | 275 | my ($self, $clef) = @_; | |||
| 153 | 264 | 171 | $self->{_initial_clef} = $clef; | |||
| 154 | 264 | 254 | return $self; | |||
| 155 | } | |||||
| 156 | ||||||
| 157 - 161 | =head2 initial_key Return the initial key hashref (C<signature>, C<tonic>, C<mode>), or undef. =cut | |||||
| 162 | ||||||
| 163 | 774 | 936 | sub initial_key { return $_[0]->{_initial_key} } | |||
| 164 | ||||||
| 165 - 169 | =head2 set_initial_key Set the initial key. =cut | |||||
| 170 | ||||||
| 171 | sub set_initial_key { | |||||
| 172 | 230 | 229 | my ($self, $key) = @_; | |||
| 173 | 230 | 124 | $self->{_initial_key} = $key; | |||
| 174 | 230 | 237 | return $self; | |||
| 175 | } | |||||
| 176 | ||||||
| 177 - 181 | =head2 initial_timesig Return the initial time-signature hashref (C<beats>, C<beat_type>), or undef. =cut | |||||
| 182 | ||||||
| 183 | 392 | 330 | sub initial_timesig { return $_[0]->{_initial_timesig} } | |||
| 184 | ||||||
| 185 - 189 | =head2 set_initial_timesig Set the initial time signature. =cut | |||||
| 190 | ||||||
| 191 | sub set_initial_timesig { | |||||
| 192 | 229 | 197 | my ($self, $ts) = @_; | |||
| 193 | 229 | 102 | $self->{_initial_timesig} = $ts; | |||
| 194 | 229 | 208 | return $self; | |||
| 195 | } | |||||
| 196 | ||||||
| 197 | # --------------------------------------------------------------------------- | |||||
| 198 | # Event management | |||||
| 199 | # --------------------------------------------------------------------------- | |||||
| 200 | ||||||
| 201 - 258 | =head2 add_event Append a C<Music::NWC2MusicXML::Event> to this staff's event list. =head3 Purpose Used by the parser to build the ordered event sequence as it processes NWCTXT records belonging to this staff. =head3 Arguments =over 4 =item C<$event> -- a blessed C<Music::NWC2MusicXML::Event> object (required). =back =head3 Returns C<$self> (for chaining). =head3 Side Effects Appends to C<_events> array. =head3 Usage Example $staff->add_event( Music::NWC2MusicXML::Event->new(type => 'Note', ...) ); =head3 API SPECIFICATION =head4 Input $event : Music::NWC2MusicXML::Event (required) =head4 Output $self (Music::NWC2MusicXML::Staff) =head3 MESSAGES | Code | Meaning | Resolution | |----------------|----------------------------------------|---------------------------------| | error_bad_event| Argument is not a Music::NWC2MusicXML::Event | Construct event before adding | =head3 FORMAL SPECIFICATION [AddEvent] DeltaStaff event? : Event --------- events' = events ^ <event?> (placeholder) =cut | |||||
| 259 | ||||||
| 260 | sub add_event { | |||||
| 261 | 1216248 | 380020 | my ($self, $event) = @_; | |||
| 262 | 1216248 | 987000 | croak _fmt_msg('error_bad_event', ref($event) // 'SCALAR') | |||
| 263 | unless blessed($event) && $event->isa('Music::NWC2MusicXML::Event'); | |||||
| 264 | 1216235 1216235 | 351325 405597 | push @{ $self->{_events} }, $event; | |||
| 265 | 1216235 | 1039190 | return $self; | |||
| 266 | } | |||||
| 267 | ||||||
| 268 - 286 | =head2 events Return an arrayref of all C<Music::NWC2MusicXML::Event> objects in parse order. =head3 Returns Arrayref of C<Music::NWC2MusicXML::Event>. =head3 API SPECIFICATION =head4 Input (none) =head4 Output ARRAYREF of Music::NWC2MusicXML::Event =cut | |||||
| 287 | ||||||
| 288 | sub events { | |||||
| 289 | 1947 | 980 | my ($self) = @_; | |||
| 290 | 1947 | 1878 | return $self->{_events}; | |||
| 291 | } | |||||
| 292 | ||||||
| 293 - 298 | =head2 musical_events Return an arrayref containing only the events where C<is_musical_event> is true. Metadata records are excluded. =cut | |||||
| 299 | ||||||
| 300 | sub musical_events { | |||||
| 301 | 5 | 138 | my ($self) = @_; | |||
| 302 | 5 10 5 | 3 18 3 | return [ grep { $_->is_musical_event } @{ $self->{_events} } ]; | |||
| 303 | } | |||||
| 304 | ||||||
| 305 - 309 | =head2 event_count Return the total number of events (musical + metadata). =cut | |||||
| 310 | ||||||
| 311 | sub event_count { | |||||
| 312 | 28 | 924 | my ($self) = @_; | |||
| 313 | 28 28 | 16 31 | return scalar @{ $self->{_events} }; | |||
| 314 | } | |||||
| 315 | ||||||
| 316 - 322 | =head2 has_notes Return true if any note, rest, chord, or bar event has been added to this staff. Used by the parser to distinguish staff-header records (before any sounding content) from mid-staff change records. =cut | |||||
| 323 | ||||||
| 324 | sub has_notes { | |||||
| 325 | 3780 | 1756 | my ($self) = @_; | |||
| 326 | 3780 3780 | 1146 1842 | for my $ev (@{ $self->{_events} }) { | |||
| 327 | 4828 | 4764 | return 1 if $SOUNDING_EVENT_TYPES{ $ev->type }; | |||
| 328 | } | |||||
| 329 | 550 | 453 | return 0; | |||
| 330 | } | |||||
| 331 | ||||||
| 332 | # --------------------------------------------------------------------------- | |||||
| 333 | # Private | |||||
| 334 | # --------------------------------------------------------------------------- | |||||
| 335 | ||||||
| 336 | sub _fmt_msg { | |||||
| 337 | 13 | 10 | my ($key, @args) = @_; | |||
| 338 | 13 | 20 | croak "Unknown message key: $key" unless exists $MESSAGES{$key}; | |||
| 339 | 13 | 39 | return sprintf $MESSAGES{$key}, @args; | |||
| 340 | } | |||||
| 341 | ||||||
| 342 | 1; | |||||
| 343 | ||||||