File Coverage

File:blib/lib/Music/NWC2MusicXML/Staff.pm
Coverage:97.2%

linestmtbrancondsubtimecode
1package Music::NWC2MusicXML::Staff;
2
3
16
16
16
17
13
56
use strict;
4
16
16
16
17
2
161
use warnings;
5
6our $VERSION = '0.002.4';
7
8
16
16
16
13
9
116
use Carp qw(croak carp);
9
16
16
16
10
6
116
use Readonly;
10
16
16
16
14
8
107
use Scalar::Util qw(blessed);
11
16
16
16
7
12
96
use Params::Validate::Strict qw(validate_strict);
12
16
16
16
14
5
91
use Params::Get;
13
16
16
16
1596
12
3025
use Music::NWC2MusicXML::Event;
14
15Readonly::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.
22Readonly::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.002.4

=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
57sub new {
58
575
53346
        my ($class, %input) = @_;
59
575
2646
        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
575
71915
        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
575
1436
                _events          => [],
86        }, $class;
87
88
575
539
        return $self;
89}
90
91# ---------------------------------------------------------------------------
92# Accessors
93# ---------------------------------------------------------------------------
94
95 - 99
=head2 name

Return the staff name string.

=cut
100
101
440
615
sub name            { return $_[0]->{_name} }
102
103 - 107
=head2 group

Return the group name string.

=cut
108
109
5
7
sub group           { return $_[0]->{_group} }
110
111 - 115
=head2 lines

Return the number of staff lines (usually 5).

=cut
116
117
6
87
sub lines           { return $_[0]->{_lines} }
118
119 - 123
=head2 visible

Return 1 if the staff is visible, 0 otherwise.

=cut
124
125
6
7
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
928
869
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
801
1170
sub initial_clef    { return $_[0]->{_initial_clef} }
144
145 - 149
=head2 set_initial_clef

Set the initial clef.

=cut
150
151sub set_initial_clef {
152
289
338
        my ($self, $clef) = @_;
153
289
202
        $self->{_initial_clef} = $clef;
154
289
241
        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
800
970
sub initial_key     { return $_[0]->{_initial_key} }
164
165 - 169
=head2 set_initial_key

Set the initial key.

=cut
170
171sub set_initial_key {
172
255
277
        my ($self, $key) = @_;
173
255
164
        $self->{_initial_key} = $key;
174
255
228
        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
414
418
sub initial_timesig { return $_[0]->{_initial_timesig} }
184
185 - 189
=head2 set_initial_timesig

Set the initial time signature.

=cut
190
191sub set_initial_timesig {
192
254
258
        my ($self, $ts) = @_;
193
254
154
        $self->{_initial_timesig} = $ts;
194
254
214
        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
260sub add_event {
261
1216321
335114
        my ($self, $event) = @_;
262
1216321
827133
        croak _fmt_msg('error_bad_event', ref($event) // 'SCALAR')
263                unless blessed($event) && $event->isa('Music::NWC2MusicXML::Event');
264
1216308
1216308
334340
342593
        push @{ $self->{_events} }, $event;
265
1216308
773800
        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
288sub events {
289
2427
1010
        my ($self) = @_;
290
2427
2271
        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
300sub musical_events {
301
5
116
        my ($self) = @_;
302
5
10
5
4
15
4
        return [ grep { $_->is_musical_event } @{ $self->{_events} } ];
303}
304
305 - 309
=head2 event_count

Return the total number of events (musical + metadata).

=cut
310
311sub event_count {
312
38
760
        my ($self) = @_;
313
38
38
22
46
        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
324sub has_notes {
325
3819
2052
        my ($self) = @_;
326
3819
3819
1332
1782
        for my $ev (@{ $self->{_events} }) {
327
4828
4594
                return 1 if $SOUNDING_EVENT_TYPES{ $ev->type };
328        }
329
589
510
        return 0;
330}
331
332# ---------------------------------------------------------------------------
333# Private
334# ---------------------------------------------------------------------------
335
336sub _fmt_msg {
337
13
10
        my ($key, @args) = @_;
338
13
17
        croak "Unknown message key: $key" unless exists $MESSAGES{$key};
339
13
35
        return sprintf $MESSAGES{$key}, @args;
340}
341
3421;
343