File Coverage

blib/lib/Music/LilyPondUtil.pm
Criterion Covered Total %
statement 200 215 93.0
branch 122 160 76.2
condition 40 52 76.9
subroutine 28 30 93.3
pod 17 17 100.0
total 407 474 85.8


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # http://www.lilypond.org/ related utility code (mostly to transition
4             # between Perl processing integers and the related appropriate letter
5             # names for the black dots in lilypond). See also Music::PitchNum.
6              
7             package Music::LilyPondUtil;
8              
9 2     2   169494 use 5.010000;
  2         8  
10 2     2   10 use strict;
  2         4  
  2         40  
11 2     2   9 use warnings;
  2         8  
  2         60  
12 2     2   9 use Carp qw/croak/;
  2         4  
  2         96  
13 2     2   10 use Scalar::Util qw/blessed looks_like_number/;
  2         3  
  2         109  
14 2     2   1673 use Try::Tiny;
  2         3051  
  2         6887  
15              
16             our $VERSION = '0.56';
17              
18             # Since dealing with lilypond, assume 12 pitch material
19             my $DEG_IN_SCALE = 12;
20             my $TRITONE = 6;
21              
22             # Default register - due to "c" in lilypond absolute notation mapping to
23             # the fourth register, or MIDI pitch number 48. Used by the reg_*
24             # utility subs.
25             my $REL_DEF_REG = 4;
26              
27             # Just the note and register information - the 0,6 bit grants perhaps
28             # too much leeway for relative motion (silly things like c,,,,,,,
29             # relative to the top note on a piano) but there are other bounds on the
30             # results so that they lie within the span of the MIDI note numbers.
31             my $LY_NOTE_RE = qr/(([a-g])(?:eses|isis|es|is)?)(([,'])\g{-1}{0,6})?/;
32              
33             my %N2P = (
34             qw/bis 0 c 0 deses 0 bisis 1 cis 1 des 1 cisis 2 d 2 eeses 2 dis 3 ees 3 feses 3 disis 4 e 4 fes 4 eis 5 f 5 geses 5 eisis 6 fis 6 ges 6 fisis 7 g 7 aeses 7 gis 8 aes 8 gisis 9 a 9 beses 9 ais 10 bes 10 ceses 10 aisis 11 b 11 ces 11/
35             );
36             # mixing flats and sharps not supported, either one or other right now
37             my %P2N = (
38             flats => {qw/0 c 1 des 2 d 3 ees 4 e 5 f 6 ges 7 g 8 aes 9 a 10 bes 11 b/},
39             sharps => {qw/0 c 1 cis 2 d 3 dis 4 e 5 f 6 fis 7 g 8 gis 9 a 10 ais 11 b/},
40             );
41              
42             # Diabolus in Musica, indeed (direction tritone heads in relative mode)
43             my %TTDIR = (
44             flats => {qw/0 -1 1 1 2 -1 3 1 4 -1 5 1 6 1 7 -1 8 1 9 -1 10 1 11 -1/},
45             sharps => {qw/0 1 1 -1 2 1 3 -1 4 1 5 1 6 -1 7 1 8 -1 9 1 10 -1 11 -1/},
46             );
47              
48             # on loan from scm/midi.scm of lilypond fame (and possibly also some
49             # MIDI specification somewhere?)
50             my %PATCH2INSTRUMENT = (
51             0 => "acoustic grand",
52             1 => "bright acoustic",
53             2 => "electric grand",
54             3 => "honky-tonk",
55             4 => "electric piano 1",
56             5 => "electric piano 2",
57             6 => "harpsichord",
58             7 => "clav",
59             8 => "celesta",
60             9 => "glockenspiel",
61             10 => "music box",
62             11 => "vibraphone",
63             12 => "marimba",
64             13 => "xylophone",
65             14 => "tubular bells",
66             15 => "dulcimer",
67             16 => "drawbar organ",
68             17 => "percussive organ",
69             18 => "rock organ",
70             19 => "church organ",
71             20 => "reed organ",
72             21 => "accordion",
73             22 => "harmonica",
74             23 => "concertina",
75             24 => "acoustic guitar (nylon)",
76             25 => "acoustic guitar (steel)",
77             26 => "electric guitar (jazz)",
78             27 => "electric guitar (clean)",
79             28 => "electric guitar (muted)",
80             29 => "overdriven guitar",
81             30 => "distorted guitar",
82             31 => "guitar harmonics",
83             32 => "acoustic bass",
84             33 => "electric bass (finger)",
85             34 => "electric bass (pick)",
86             35 => "fretless bass",
87             36 => "slap bass 1",
88             37 => "slap bass 2",
89             38 => "synth bass 1",
90             39 => "synth bass 2",
91             40 => "violin",
92             41 => "viola",
93             42 => "cello",
94             43 => "contrabass",
95             44 => "tremolo strings",
96             45 => "pizzicato strings",
97             46 => "orchestral harp",
98             47 => "timpani",
99             48 => "string ensemble 1",
100             49 => "string ensemble 2",
101             50 => "synthstrings 1",
102             51 => "synthstrings 2",
103             52 => "choir aahs",
104             53 => "voice oohs",
105             54 => "synth voice",
106             55 => "orchestra hit",
107             56 => "trumpet",
108             57 => "trombone",
109             58 => "tuba",
110             59 => "muted trumpet",
111             60 => "french horn",
112             61 => "brass section",
113             62 => "synthbrass 1",
114             63 => "synthbrass 2",
115             64 => "soprano sax",
116             65 => "alto sax",
117             66 => "tenor sax",
118             67 => "baritone sax",
119             68 => "oboe",
120             69 => "english horn",
121             70 => "bassoon",
122             71 => "clarinet",
123             72 => "piccolo",
124             73 => "flute",
125             74 => "recorder",
126             75 => "pan flute",
127             76 => "blown bottle",
128             77 => "shakuhachi",
129             78 => "whistle",
130             79 => "ocarina",
131             80 => "lead 1 (square)",
132             81 => "lead 2 (sawtooth)",
133             82 => "lead 3 (calliope)",
134             83 => "lead 4 (chiff)",
135             84 => "lead 5 (charang)",
136             85 => "lead 6 (voice)",
137             86 => "lead 7 (fifths)",
138             87 => "lead 8 (bass+lead)",
139             88 => "pad 1 (new age)",
140             89 => "pad 2 (warm)",
141             90 => "pad 3 (polysynth)",
142             91 => "pad 4 (choir)",
143             92 => "pad 5 (bowed)",
144             93 => "pad 6 (metallic)",
145             94 => "pad 7 (halo)",
146             95 => "pad 8 (sweep)",
147             96 => "fx 1 (rain)",
148             97 => "fx 2 (soundtrack)",
149             98 => "fx 3 (crystal)",
150             99 => "fx 4 (atmosphere)",
151             100 => "fx 5 (brightness)",
152             101 => "fx 6 (goblins)",
153             102 => "fx 7 (echoes)",
154             103 => "fx 8 (sci-fi)",
155             104 => "sitar",
156             105 => "banjo",
157             106 => "shamisen",
158             107 => "koto",
159             108 => "kalimba",
160             109 => "bagpipe",
161             110 => "fiddle",
162             111 => "shanai",
163             112 => "tinkle bell",
164             113 => "agogo",
165             114 => "steel drums",
166             115 => "woodblock",
167             116 => "taiko drum",
168             117 => "melodic tom",
169             118 => "synth drum",
170             119 => "reverse cymbal",
171             120 => "guitar fret noise",
172             121 => "breath noise",
173             122 => "seashore",
174             123 => "bird tweet",
175             124 => "telephone ring",
176             125 => "helicopter",
177             126 => "applause",
178             127 => "gunshot",
179             );
180              
181             ########################################################################
182             #
183             # SUBROUTINES
184              
185             sub _range_check {
186 330     330   597 my ( $self, $pitch ) = @_;
187 330 100       1137 if ( $pitch < $self->{_min_pitch} ) {
    100          
188 3 100       11 if ( exists $self->{_min_pitch_hook} ) {
189             return $self->{_min_pitch_hook}
190 1         5 ( $pitch, $self->{_min_pitch}, $self->{_max_pitch}, $self );
191             } else {
192 2         13 die "pitch $pitch is too low\n";
193             }
194              
195             } elsif ( $pitch > $self->{_max_pitch} ) {
196 3 100       12 if ( exists $self->{_max_pitch_hook} ) {
197             return $self->{_max_pitch_hook}
198 1         5 ( $pitch, $self->{_min_pitch}, $self->{_max_pitch}, $self );
199             } else {
200 2         12 die "pitch $pitch is too high\n";
201             }
202             }
203              
204 324         896 return;
205             }
206              
207             sub _symbol2relreg {
208 107     107   160 my ($symbol) = @_;
209 107   100     290 $symbol ||= q{};
210              
211             # no leap, within three stave lines of previous note
212 107 100       252 return 0 if length $symbol == 0;
213              
214 51 50       181 die "invalid register symbol $symbol\n"
215             if $symbol !~ m/^(([,'])\g{-1}*)$/;
216              
217 51         112 my $count = length $1;
218 51 100       108 $count *= $2 eq q{'} ? 1 : -1;
219              
220 51         83 return $count;
221             }
222              
223             sub chrome {
224 5     5 1 61 my ( $self, $chrome ) = @_;
225 5 100       22 if ( defined $chrome ) {
226 4 50       16 croak q{chrome must be 'sharps' or 'flats'} unless exists $P2N{$chrome};
227 4         14 $self->{_chrome} = $chrome;
228             }
229 5         28 return $self->{_chrome};
230             }
231              
232             sub clear_prev_note {
233 0     0 1 0 my ($self) = @_;
234 0         0 undef $self->{prev_note};
235             }
236              
237             sub clear_prev_pitch {
238 1     1 1 3 my ($self) = @_;
239 1         3 undef $self->{prev_pitch};
240             }
241              
242             # diatonic (piano white key) pitch number for a given input note (like
243             # prev_note() below except without side-effects).
244             sub diatonic_pitch {
245 3     3 1 7 my ( $self, $note ) = @_;
246              
247 3 50       8 croak 'note not defined' unless defined $note;
248              
249 3         4 my $pitch;
250 3 50       74 if ( $note =~ m/^$LY_NOTE_RE/ ) {
251             # TODO duplicates (portions of) same code, below
252 3         8 my $real_note = $1;
253 3         6 my $diatonic_note = $2;
254 3   50     12 my $reg_symbol = $3 // '';
255              
256 3 50       12 croak "unknown lilypond note $note" unless exists $N2P{$real_note};
257              
258 3         33 $pitch = $N2P{$diatonic_note} + $self->reg_sym2num($reg_symbol) * $DEG_IN_SCALE;
259 3 50       13 $pitch %= $DEG_IN_SCALE if $self->{_ignore_register};
260              
261             } else {
262 0         0 croak "unknown note $note";
263             }
264              
265 3         12 return $pitch;
266             }
267              
268             sub ignore_register {
269 2     2 1 12 my ( $self, $state ) = @_;
270 2 100       7 $self->{_ignore_register} = $state if defined $state;
271 2         9 return $self->{_ignore_register};
272             }
273              
274             sub keep_state {
275 3     3 1 21 my ( $self, $state ) = @_;
276 3 100       14 $self->{_keep_state} = $state if defined $state;
277 3         59 return $self->{_keep_state};
278             }
279              
280             sub mode {
281 5     5 1 30 my ( $self, $mode ) = @_;
282 5 100       23 if ( defined $mode ) {
283 4 50 66     42 croak q{mode must be 'absolute' or 'relative'}
284             if $mode ne 'absolute' and $mode ne 'relative';
285 4         12 $self->{_mode} = $mode;
286             }
287 5         21 return $self->{_mode};
288             }
289              
290             sub new {
291 11     11 1 1095 my ( $class, %param ) = @_;
292 11         32 my $self = {};
293              
294 11   100     93 $self->{_chrome} = $param{chrome} || 'sharps';
295             croak q{chrome must be 'sharps' or 'flats'}
296 11 50       49 unless exists $P2N{ $self->{_chrome} };
297              
298 11   100     64 $self->{_keep_state} = $param{keep_state} // 1;
299 11   100     62 $self->{_ignore_register} = $param{ignore_register} // 0;
300              
301             # Default min_pitch of 21 causes too many problems for existing code,
302             # so minimum defaults to 0, which is a bit beyond the bottom of 88-key
303             # pianos. 108 is the top of a standard 88-key piano.
304 11   100     48 $self->{_min_pitch} = $param{min_pitch} // 0;
305 11   100     56 $self->{_max_pitch} = $param{max_pitch} // 108;
306              
307 11 100       35 if ( exists $param{min_pitch_hook} ) {
308             croak 'min_pitch_hook must be code ref'
309 1 50       5 unless ref $param{min_pitch_hook} eq 'CODE';
310 1         3 $self->{_min_pitch_hook} = $param{min_pitch_hook};
311             }
312 11 100       32 if ( exists $param{max_pitch_hook} ) {
313             croak 'max_pitch_hook must be code ref'
314 1 50       5 unless ref $param{max_pitch_hook} eq 'CODE';
315 1         2 $self->{_max_pitch_hook} = $param{max_pitch_hook};
316             }
317              
318 11   100     55 $self->{_mode} = $param{mode} || 'absolute';
319             croak q{'mode' must be 'absolute' or 'relative'}
320 11 50 66     51 if $self->{_mode} ne 'absolute' and $self->{_mode} ne 'relative';
321              
322             $self->{_p2n_hook} = $param{p2n_hook}
323 11   50 324   94 || sub { $P2N{ $_[1] }->{ $_[0] % $DEG_IN_SCALE } };
  324         994  
324             croak q{'p2n_hook' must be code ref}
325 11 50       51 unless ref $self->{_p2n_hook} eq 'CODE';
326              
327 11   100     65 $self->{_sticky_state} = $param{sticky_state} // 0;
328 11   100     47 $self->{_strip_rests} = $param{strip_rests} // 0;
329              
330 11         22 bless $self, $class;
331 11         43 return $self;
332             }
333              
334             sub notes2pitches {
335 15     15 1 59 my $self = shift;
336 15         22 my @pitches;
337              
338 15 50       60 for my $n ( ref $_[0] eq 'ARRAY' ? @{ $_[0] } : @_ ) {
  0         0  
339             # pass through what hopefully are raw pitch numbers, otherwise parse
340             # note from subset of the lilypond note format
341 144 50 66     1673 if ( !defined $n ) {
    100          
    100          
    100          
342             # might instead blow up? or have option to blow up...
343 0 0       0 push @pitches, undef unless $self->{_strip_rests};
344              
345             } elsif ( $n =~ m/^(-?\d+)$/ ) {
346 3         7 push @pitches, $n;
347              
348             } elsif ( $n =~ m/^(?i)[rs]/ or $n =~ m/\\rest/ ) {
349             # rests or lilypond 'silent' bits
350 3 100       17 push @pitches, undef unless $self->{_strip_rests};
351              
352             } elsif ( $n =~ m/^$LY_NOTE_RE/ ) {
353             # "diatonic" (here, the white notes of a piano) are necessary
354             # for leap calculations in relative mode, as "cisis" goes down
355             # to "aeses" despite the real notes ("d" and "g," in absolute
356             # mode) being a fifth apart. Another way to think of it: the
357             # diatonic "c" and "a" of "cisis" and "aeses" are within three
358             # stave lines of one another; anything involving three or more
359             # stave lines is a leap.
360 137         241 my $real_note = $1;
361 137         224 my $diatonic_note = $2;
362 137   100     455 my $reg_symbol = $3 // '';
363              
364 137 50       315 croak "unknown lilypond note $n" unless exists $N2P{$real_note};
365              
366 137         154 my ( $diatonic_pitch, $real_pitch );
367 137 100       340 if ( $self->{_mode} ne 'relative' ) { # absolute
368             # TODO see if can do this code regardless of mode, and still
369             # sanity check the register for absolute/relative-no-previous,
370             # but not for relative-with-previous, to avoid code
371             # duplication in abs/r-no-p blocks - or call subs with
372             # appropriate register numbers.
373             ( $diatonic_pitch, $real_pitch ) =
374 28         45 map { $N2P{$_} + $self->reg_sym2num($reg_symbol) * $DEG_IN_SCALE }
  56         147  
375             $diatonic_note, $real_note;
376              
377             # Account for edge cases of ces and bis and the like
378 28         49 my $delta = $diatonic_pitch - $real_pitch;
379 28 100       78 if ( abs($delta) > $TRITONE ) {
380 3 100       9 $real_pitch += $delta > 0 ? $DEG_IN_SCALE : -$DEG_IN_SCALE;
381             }
382              
383             } else { # relatively more complicated
384              
385 109 100       230 if ( !defined $self->{prev_note} ) { # absolute if nothing prior
386             ( $diatonic_pitch, $real_pitch ) =
387 2         5 map { $N2P{$_} + $self->reg_sym2num($reg_symbol) * $DEG_IN_SCALE }
  4         11  
388             $diatonic_note, $real_note;
389              
390             # Account for edge cases of ces and bis and the like
391 2         4 my $delta = $diatonic_pitch - $real_pitch;
392 2 50       6 if ( abs($delta) > $TRITONE ) {
393 0 0       0 $real_pitch += $delta > 0 ? $DEG_IN_SCALE : -$DEG_IN_SCALE;
394             }
395              
396             } else { # meat of relativity
397 107         205 my $reg_number = int( $self->{prev_note} / $DEG_IN_SCALE ) * $DEG_IN_SCALE;
398              
399 107         187 my $reg_delta = $self->{prev_note} % $DEG_IN_SCALE - $N2P{$diatonic_note};
400 107 100       223 if ( abs($reg_delta) > $TRITONE ) {
401 31 100       76 $reg_number += $reg_delta > 0 ? $DEG_IN_SCALE : -$DEG_IN_SCALE;
402             }
403              
404             # adjust register by the required relative offset
405 107         178 my $reg_offset = _symbol2relreg($reg_symbol);
406 107 100       237 if ( $reg_offset != 0 ) {
407 51         75 $reg_number += $reg_offset * $DEG_IN_SCALE;
408             }
409              
410             ( $diatonic_pitch, $real_pitch ) =
411 107         188 map { $reg_number + $N2P{$_} } $diatonic_note, $real_note;
  214         439  
412              
413 107         157 my $delta = $diatonic_pitch - $real_pitch;
414 107 100       243 if ( abs($delta) > $TRITONE ) {
415 10 100       24 $real_pitch += $delta > 0 ? $DEG_IN_SCALE : -$DEG_IN_SCALE;
416             }
417             }
418              
419 109 50       292 $self->{prev_note} = $diatonic_pitch if $self->{_keep_state};
420             }
421              
422 137         334 push @pitches, $real_pitch;
423              
424             } else {
425 1         23 croak "unknown note '$n'";
426             }
427             }
428              
429 14 100       41 if ( $self->{_ignore_register} ) {
430 1         3 for my $p (@pitches) {
431 4 50       18 $p %= $DEG_IN_SCALE if defined $p;
432             }
433             }
434              
435 14 50       42 undef $self->{prev_note} unless $self->{_sticky_state};
436              
437 14 100       169 return @pitches > 1 ? @pitches : $pitches[0];
438             }
439              
440             # Converts pitches to lilypond names
441             sub p2ly {
442 36     36 1 2419 my $self = shift;
443              
444 36         55 my @notes;
445 36 50       142 for my $obj ( ref $_[0] eq 'ARRAY' ? @{ $_[0] } : @_ ) {
  0         0  
446 330         413 my $pitch;
447 330 50 33     1848 if ( !defined $obj ) {
    50          
    50          
448 0         0 croak "cannot convert undefined value to lilypond element";
449             } elsif ( blessed $obj and $obj->can("pitch") ) {
450 0         0 $pitch = $obj->pitch;
451             } elsif ( looks_like_number $obj) {
452 330         695 $pitch = int $obj;
453             } else {
454             # pass through on unknowns (could be rests or who knows what)
455 0         0 push @notes, $obj;
456 0         0 next;
457             }
458              
459             # Response handling on range check:
460             # * exception - out of bounds, default die() handler tripped
461             # * defined return value - got something from a hook function, use that
462             # * undefined - pitch is within bounds, continue with code below
463 330         362 my $range_result;
464 330     330   13718 try { $range_result = $self->_range_check($pitch) }
465             catch {
466 4     4   79 croak $_;
467 330         1861 };
468 326 100       4100 if ( defined $range_result ) {
469 2         4 push @notes, $range_result;
470 2         5 next;
471             }
472              
473 324         729 my $note = $self->{_p2n_hook}( $pitch, $self->{_chrome} );
474 324 50       741 croak "could not lookup note for pitch '$pitch'" unless defined $note;
475              
476 324         382 my $register;
477 324 100       734 if ( $self->{_mode} ne 'relative' ) {
478 20         72 $register = $self->reg_num2sym( $pitch / $DEG_IN_SCALE );
479              
480             } else { # relatively more complicated
481 304         433 my $rel_reg = $REL_DEF_REG;
482 304 100       677 if ( defined $self->{prev_pitch} ) {
483 290         537 my $delta = int( $pitch - $self->{prev_pitch} );
484 290 100       723 if ( abs($delta) >= $TRITONE ) { # leaps need , or ' variously
485 197 100       390 if ( $delta % $DEG_IN_SCALE == $TRITONE ) {
486 152         262 $rel_reg += int( $delta / $DEG_IN_SCALE );
487              
488             # Adjust for tricky changing tritone default direction
489             my $default_dir =
490 152         370 $TTDIR{ $self->{_chrome} }->{ $self->{prev_pitch} % $DEG_IN_SCALE };
491 152 100 100     950 if ( $delta > 0 and $default_dir < 0 ) {
    100 100        
492 36         65 $rel_reg++;
493             } elsif ( $delta < 0 and $default_dir > 0 ) {
494 36         65 $rel_reg--;
495             }
496              
497             } else { # not tritone, but leap
498             # TT adjust is to push <1 leaps out so become 1
499 45 100       149 $rel_reg +=
500             int( ( $delta + ( $delta > 0 ? $TRITONE : -$TRITONE ) ) / $DEG_IN_SCALE );
501             }
502             }
503             }
504 304         685 $register = $self->reg_num2sym($rel_reg);
505 304 50       974 $self->{prev_pitch} = $pitch if $self->{_keep_state};
506             }
507              
508             # Do not care about register (even in absolute mode) if keeping state
509 324 100       673 if ( $self->{_keep_state} ) {
510 316 50       690 croak "register out of range for pitch '$pitch'"
511             unless defined $register;
512             } else {
513 8         19 $register = '';
514             }
515 324         935 push @notes, $note . $register;
516             }
517              
518 32 100       93 undef $self->{prev_pitch} unless $self->{_sticky_state};
519 32 100       438 return @_ > 1 ? @notes : $notes[0];
520             }
521              
522             # Class method, patch number to instrument name utility function
523             sub patch2instrument {
524 2     2 1 539 my ( $class, $patchnum ) = @_;
525              
526 2   100     20 return $PATCH2INSTRUMENT{$patchnum} // '';
527             }
528              
529             # MUST NOT accept raw pitch numbers, as who knows if "61" is a "cis"
530             # or "des" or the like, which will in turn affect the relative
531             # calculations!
532             sub prev_note {
533 2     2 1 5 my ( $self, $pitch ) = @_;
534 2 50       8 if ( defined $pitch ) {
535 2 50       63 if ( $pitch =~ m/^$LY_NOTE_RE/ ) {
536             # TODO duplicates (portions of) same code, below
537 2         5 my $real_note = $1;
538 2         5 my $diatonic_note = $2;
539 2   50     7 my $reg_symbol = $3 // '';
540              
541 2 50       6 croak "unknown lilypond note $pitch" unless exists $N2P{$real_note};
542              
543             # for relative-to-this just need the diatonic
544             $self->{prev_note} =
545 2         6 $N2P{$diatonic_note} + $self->reg_sym2num($reg_symbol) * $DEG_IN_SCALE;
546              
547             } else {
548 0         0 croak "unknown pitch '$pitch'";
549             }
550             }
551 2         11 return $self->{prev_note};
552             }
553              
554             sub prev_pitch {
555 3     3 1 677 my ( $self, $pitch ) = @_;
556 3 100       10 if ( defined $pitch ) {
557 1 50 33     11 if ( blessed $pitch and $pitch->can("pitch") ) {
    50          
558 0         0 $self->{prev_pitch} = $pitch->pitch;
559             } elsif ( looks_like_number $pitch) {
560 0         0 $self->{prev_pitch} = int $pitch;
561             } else {
562 1     1   42 try { $self->{prev_pitch} = $self->diatonic_pitch($pitch) }
563             catch {
564 0     0   0 croak $_;
565 1         7 };
566             }
567             }
568 3         28 return $self->{prev_pitch};
569             }
570              
571             # Utility, converts arbitrary numbers into lilypond register notation
572             sub reg_num2sym {
573 327     327 1 504 my ( $self, $number ) = @_;
574 327 50 33     1482 croak 'register number must be numeric'
575             if !defined $number
576             or !looks_like_number $number;
577              
578 327         403 $number = int $number;
579 327         458 my $symbol = q{};
580 327 100       850 if ( $number < $REL_DEF_REG ) {
    100          
581 77         148 $symbol = q{,} x ( $REL_DEF_REG - $number );
582             } elsif ( $number > $REL_DEF_REG ) {
583 77         141 $symbol = q{'} x ( $number - $REL_DEF_REG );
584             }
585 327         836 return $symbol;
586             }
587              
588             # Utility, converts arbitrary ,, or ''' into appropriate register number
589             sub reg_sym2num {
590 68     68 1 139 my ( $self, $symbol ) = @_;
591 68 50       156 croak 'undefined register symbol' unless defined $symbol;
592 68 50       253 croak 'invalid register symbol' unless $symbol =~ m/^(,|')*$/;
593              
594 68 100       151 my $dir = $symbol =~ m/[,]/ ? -1 : 1;
595              
596 68         258 return $REL_DEF_REG + $dir * length $symbol;
597             }
598              
599             sub sticky_state {
600 3     3 1 11 my ( $self, $state ) = @_;
601 3 100       12 $self->{_sticky_state} = $state if defined $state;
602 3         9 return $self->{_sticky_state};
603             }
604              
605             sub strip_rests {
606 2     2 1 27 my ( $self, $state ) = @_;
607 2 100       14 $self->{_strip_rests} = $state if defined $state;
608 2         19 return $self->{_strip_rests};
609             }
610              
611             1;
612             __END__