3 # Copyright (C) 2010 W.Trevor King (wking @ drexel.edu)
4 # 2005 Joshua Koo (joshuakoo @ myrealbox.com)
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 """Convert NoteWorthy Composer's `nwc` to LilyPond's `ly` fileformat.
22 Most infomation obtained about the `nwc` format is by using
23 noteworthycomposer and the somewhat like the `French cafe method`_.
25 .. _French cafe method: http://samba.org/ftp/tridge/misc/french_cafe.txt
31 * 07 april 2005. Initial hex parsing
35 * 13 april 2005. Added multiple staff, keysig, dots, durations
39 * 14 april 2005. Clef, key sig detection, absolute notes pitching
43 * 15 April 2005. Relative pitchs, durations, accidentals, stem
48 * 16 April 2005. Bug fixes, generate ly score, write to file, time
49 signature, triplets, experimental chords
53 * 17 April 2005. Compressed NWC file supported!
57 * 19 April 2005. Version detection, header
58 * 20 April 2005. Bug fixes, small syntax changes
59 * 21 April 2005. Still fixing aimlessly
60 * 23 April 2005. Chords improvement
61 * 24 April 2005. Staccato, accent, tenuto. dynamics, midi detection
66 * 24 April 2005. Experimental lyrics support
70 * 29 April 2005. Workround for ``\acciaccatura``, simple check full
75 * 30 November 2010. Cleanup to more Pythonic syntax.
79 * http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Line-breaking
95 # define useful structures and tools to make parsing easier
97 def _decode_string(string):
98 """Decode NWC text from ISO-8859-1 to Unicode.
100 Based on the contents of a few example files, NWC uses
101 `ISO-8859-1`_ to encode text with the private use character
102 `\\x92` representing the right single quote (see `ISO-6492`_).
104 .. _ISO-8859-1: http://en.wikipedia.org/wiki/ISO/IEC_8859
105 .. _ISO-6492: http://en.wikipedia.org/wiki/ISO_6429
107 >>> _decode_string('Someone\\x92s \\xc9tude in \\xa9')
108 u'Someone\u2019s \\xc9tude in \\xa9'
110 string = unicode(string, 'iso-8859-1')
111 string = string.replace(r'\r\n', '\n')
112 return string.replace(u'\x92', u'\u2019')
116 """An enumerated type (or any type requiring post-processing).
119 ... _list = ['A', 'B', 'C']
127 def process(self, value):
129 return self._list[value]
130 except IndexError: # if _list is a list
131 raise ValueError('index %d out of range for %s'
132 % (value, self._list))
133 except KeyError: # if _list is a dict
134 raise ValueError('key %s not in %s'
135 % (value, self._list))
138 class BoolEnum (Enum):
139 _list = [False, True]
142 class Pow2Enum (Enum):
143 _list = [1, 2, 4, 8, 16, 32, 64]
146 class BitField (list):
147 """Allow clean access into bit fields.
149 Initialize with a sequence of `(name, bit_size)` pairs.
151 When packing into a `Structure`, the type should be set to one of
152 the standard unsigned integer type characters. The total bit size
153 must match the size of the type specifier.
155 If you want, you may add an `Enum` subclass as a third tuple
156 element `(name, bit_size, enum)` to postprocess that bit field
157 value. The first bit fields will recieve the most significant
160 >>> b = BitField('B', ('a', 4), ('b', 3), ('c', 1, BoolEnum))
165 [('a', 2), ('b', 7), ('c', True)]
167 `BitField` instances will raise exceptions if you try to parse the
171 Traceback (most recent call last):
175 Or if the type bit size doesn't match the field total.
177 >>> b = BitField('B', ('a', 4), ('b', 3))
178 Traceback (most recent call last):
180 AssertionError: only assigned 7 of 8 bits
182 _uint_types = ['B', 'H', 'I', 'L', 'Q']
184 def __init__(self, type, *fields):
185 assert type in self._uint_types, type
187 self._fields = list(fields)
189 for i,field in enumerate(self._fields):
191 self._fields[i] = (field[0], field[1], None)
192 field_bits += field[1]
193 num_bytes = self._uint_types.index(type) + 1
194 self._num_bits = 8 * num_bytes
195 self._max_value = (1 << self._num_bits) - 1
196 assert field_bits == self._num_bits, (
197 'only assigned %d of %d bits' % (field_bits, self._num_bits))
199 def parse(self, type, value):
200 assert type == self._type, type
201 assert value >= 0, value
202 assert value <= self._max_value, value
204 top_offset = self._num_bits
205 for name,bits,enum in self._fields:
206 bottom_offset = top_offset - bits
207 mask = (1 << bits) - 1
208 v = (value >> bottom_offset) & mask
211 results.append((name, v))
213 assert top_offset == 0, top_offset
217 class Structure (dict):
218 """Extend `struct.Struct` to support additional types.
222 ==== ========================
223 `S` null-terminated string
225 - nested `Structure` class
226 ==== ========================
228 You can also use `BitField` instances as names.
231 ... _list = ['A', 'B', 'C', 'D', 'E', 'F']
232 >>> class S (Structure):
233 ... _fields = [(BitField('B',
236 ... ('bf3', 1, BoolEnum)), 'B'),
237 ... ('string1', 'S'), ('uint16', 'H'),
238 ... ('string3', 'S'), ('enum1', E)]
239 >>> buffer = '\\x2fHello\\x00\\x02\\x03World\\x00\\x04ABC'
241 >>> for n,t in s._fields:
242 ... if isinstance(n, BitField):
243 ... for n2,bits,enum in n._fields:
247 ... # doctest: +REPORT_UDIFF
260 >>> class N (Structure):
261 ... _fields = [('string1', 'S'), ('s', S), ('string2', 'S')]
262 >>> n_buffer = 'Fun\\x00%sDEF\\x00GHI' % buffer
273 'string2': u'ABCDEF'}
276 >>> n_buffer[n.size:]
279 Note that `ctypes.Structure` is similar to `struct.Struct`, but I
280 found it more difficult to work with. Neither one supports the
281 variable-length, null-terminated strings we need.
283 _byte_order = '>' # big-endian
284 _string_decoder = staticmethod(_decode_string)
285 _fields = [] # sequence of (name, type) pairs
287 def __init__(self, buffer=None, offset=0):
288 super(Structure, self).__init__()
290 self._post_processors = []
292 for name,_type in self._fields:
293 if self._is_subclass(_type, Enum):
295 if self._is_subclass(_type, Structure) or _type in ['S']:
297 self._parsers.append(
298 self._create_struct_parser(''.join(f)))
300 if self._is_subclass(_type, Structure):
301 self._parsers.append(_type)
303 self._parsers.append(self._string_parser)
307 self._parsers.append(self._create_struct_parser(''.join(f)))
310 self.unpack_from(buffer, offset)
312 def _is_subclass(self, obj, _class):
315 >>> s._is_subclass('c', Structure)
317 >>> s._is_subclass(Structure, Structure)
321 return issubclass(obj, _class)
325 def _create_struct_parser(self, format):
326 LOG.debug('%s: initialize struct parser for %s%s'
327 % (self.__class__.__name__, self._byte_order, format))
328 return struct.Struct('%s%s' % (self._byte_order, format))
330 def _string_parser(self, buffer, offset=0):
331 size = buffer[offset:].find('\x00')
332 string = buffer[offset:offset+size]
333 if self._string_decoder:
334 string = self._string_decoder(string)
335 return ((string,), size+1)
337 def unpack_from(self, buffer, offset=0):
340 for parser in self._parsers:
341 if self._is_subclass(parser, Structure):
343 if hasattr(parser, 'unpack_from'):
344 results = parser.unpack_from(buffer, offset)
347 results,size = parser(buffer, offset)
348 self._results.extend(results)
351 self._results = tuple(self._results)
352 for i,result in enumerate(self._results):
353 name,_type = self._fields[i]
354 if self._is_subclass(_type, Enum):
355 result = _type.process(result)
356 if isinstance(name, BitField):
357 for n,r in name.parse(_type, result):
358 LOG.debug("%s['%s'] = %s" % (
359 self.__class__.__name__, n, repr(r)))
362 LOG.debug("%s['%s'] = %s" % (
363 self.__class__.__name__, name, repr(result)))
368 # define the `nwc` file format
371 class NWCSongInfo (Structure):
375 #('lyricist, 'S'), # later versions? In example/2.0-1.nwctxt
383 http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Creating-titles#Creating-titles
385 lines = [r'\header {']
386 for element,field in [('title', 'title'), ('composer', 'author'),
387 ('copyright', 'copyright1')]:
389 lines.append('\t%s = "%s"' % (element, self[field]))
390 if self['copyright2']:
391 lines.append('\t%%{ %s %%}' % self['copyright2'])
393 lines.append('\t%%{ %s %%}' % self['comments'])
395 return '\n'.join(lines)
398 class NWCFontStyleEnum (Enum):
399 _list = ('regular', 'italic', 'bold', 'bold_italic')
402 class NWCTypefaceEnum (Enum):
409 class NWCFont (Structure):
414 ('style', 2, NWCFontStyleEnum)),
418 ('typeface', NWCTypefaceEnum),
422 class NWCPageSetup (Structure):
469 ('staff_italic', NWCFont),
470 ('staff_bold', NWCFont),
471 ('staff_lyric', NWCFont),
472 ('page_title', NWCFont),
473 ('page_text', NWCFont),
474 ('page_small', NWCFont),
484 class NWCFileHead (Structure):
490 ('minor_version', 'B'), ('major_version', 'B'),
492 ('unknown04', 'B'), #('num_saves', 'B'), ???
495 ('na', 'S'), # what does this mean?
496 ('name1', 'S'), # what does this mean?
507 ('song_info', NWCSongInfo),
508 ('page_setup', NWCPageSetup),
512 class NWCStaffSet (Structure):
523 class FinalBarEnum (Enum):
524 _list = ('section_close', 'master_repeat_close', 'single', 'double',
528 class StaffTypeEnum (Enum):
529 _list = ('standard', 'upper_grand', 'lower_grand', 'orchestra')
532 class NWCStaff (Structure):
538 ('final_bar', 3, FinalBarEnum)),
542 ('muted', 1, BoolEnum)),
545 ('playback_channel', 'B'), #+1
557 ('type', 2, StaffTypeEnum)),
560 ('vertical_size_upper', 'B'), # signed? 256 - ord(nwcData.read(1)) # - signed +1 )& 2^7-1 )
564 ('vertical_size_lower', 'B'),
565 ('unknown12', 'B'), # ww?
572 ('part_volume', 'B'),
582 #('boundary_top', 'B'),
583 #('boundary_bottom', 'B'),
587 #('playback_device', 'B'),
588 #('transposition', 'B'),
589 #('dynamic_velocity', 'S'),
590 #('patch_name', 'B'),
591 #('patch_list_type', 'B'),
592 #('bank_select', 'B'),
593 #('controller0', 'B'),
594 #('controller32', 'B'),
595 #('align_syllable_rule', 'B'),
596 #('staff_alignment', 'B'),
597 #('staff_offset', 'B'),
600 def _initialize_bar_items(self):
602 'bar_comment_interval': 5, # HACK
610 'previous_note': None,
613 def _add_bar_token(self, bar_items, token, token_index):
615 for t in self['tokens'][token_index+1:]:
616 if isinstance(t, (NWCToken_note,
620 if isinstance(token, (NWCToken_text)):
621 bar_items['bar'].append(token.ly_text())
623 bar_items['bar'].append(token.ly_text(
624 clef=bar_items['clef'],
625 key=bar_items['key_sig'],
626 previous_note=bar_items['previous_note'],
627 next_note=next_note))
628 bar_items['bar_beats'] += _nwc_duration_value(token)
629 bar_items['previous_note'] = token
631 def _format_bar(self, bar_items, bar_token=None):
633 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Writing-rhythms#Durations
636 bar_items['bar'].append(bar_token.ly_text())
637 elif bar_items['bar']:
639 bar_items['bar'].append(
640 b.ly_text(self['final_bar']))
641 else: # empty final bar
643 if bar_items['time_sig']:
644 expected_bar_beats = bar_items['time_sig'].expected_bar_beats()
646 expected_bar_beats = 1.0
647 LOG.warn('no time signature given for bar %d, guessing 4/4'
648 % bar_items['bar_index']+1)
649 bar_fraction = float(bar_items['bar_beats'])/expected_bar_beats
650 assert bar_fraction < 1.01, (
651 'too many beats (%f > %f) in bar %d' %
652 (bar_items['bar_beats'], expected_bar_beats,
653 bar_items['bar_index']+1))
654 if bar_fraction < 0.99:
655 bar_items['bar'].insert(0, self._partial(
656 bar_items, expected_bar_beats, bar_fraction))
657 line = '\t%s' % ' '.join(bar_items['bar'])
658 bar_items['last_bar'] = bar_items['bar']
659 bar_items['bar'] = []
660 bar_items['bar_index'] += 1
661 bar_items['bar_beats'] = 0
662 bar_items['previous_note'] = None
663 if bar_items['bar_index'] % bar_items['bar_comment_interval'] == 0:
664 line += (' %% bar %d' % bar_items['bar_index'])
667 def _partial(self, bar_items, expected_bar_beats, bar_fraction):
669 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Displaying-rhythms#Upbeats
671 remaining_fraction = 1 - bar_fraction
673 numerator = round(int(bar_fraction * expected_bar_beats * divisor))
674 while numerator % 2 == 0 and divisor >= 1:
677 LOG.debug('partial bar, %f < %f, %d/%d remaining' % (
678 bar_items['bar_beats'], expected_bar_beats,
681 duration = str(divisor)
683 duration = '%d*%d' % (divisor, numerator)
684 return r'\partial %s' % duration
686 def ly_lines(self, voice=None):
688 http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Multiple-staves#index-_005cnew-Staff
692 '%% %s' % self['name'],
697 # result+="\\relative c {"
702 lines[-1] = (r'\new Voice = "%s" ' % voice) + lines[-1]
704 LOG.info('format staff %s (%s)' % (self['name'], voice))
708 bar_items = self._initialize_bar_items()
709 for i,token in enumerate(self['tokens']):
710 LOG.info('format token %s' % type(token))
711 if isinstance(token, (NWCToken_clef,
715 if isinstance(token, NWCToken_clef):
716 bar_items['clef'] = token
717 elif isinstance(token, NWCToken_key_sig):
718 bar_items['key_sig'] = token
719 elif isinstance(token, NWCToken_time_sig):
720 bar_items['time_sig'] = token
721 lines.append('\t%s' % token.ly_text())
722 elif isinstance(token, NWCToken_bar):
723 lines.append(self._format_bar(bar_items, token))
724 elif isinstance(token, (NWCToken_note, # rests are note instances
727 self._add_bar_token(bar_items, token, i)
728 elif isinstance(token, NWCToken_fermata):
730 bar_items['bar'].append(token.ly_text())
731 else: # replace the previous \bar with a bar-fermata
732 # http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Writing-text#Text-marks
733 # except without an auto-generated bar, no line
734 # shows up. If we put the fermata before the bar,
735 # it still ends up over the clef on the next line.
736 # See bars 124 and 125 of example/1.75-2.nwc for
738 bar_items['last_bar'].insert(
740 r'\mark \markup { \musicglyph #"scripts.ufermata" }')
741 lines[-1] = ('\t%s' % ' '.join(bar_items['last_bar']))
742 lines.append(self._format_bar(bar_items))
746 for i,lyric_block in enumerate(self['lyrics']):
747 # http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Entering-lyrics
748 # http://kainhofer.com/~lilypond/ajax/user/lilypond/Stanzas.html
749 assert voice != None, 'lyrics must be attached to a named voice'
750 lines.append(r'\new Lyrics \lyricsto "%s" {' % voice)
751 first_line = lyric_block[0]
752 first_word = first_line[0]
753 if first_word[0].isdigit():
754 stanza,words = first_word.split(' ', 1)
755 first_line[0] = words
756 lines.append('\t\\set stanza = #"%s "' % stanza)
757 for line in lyric_block:
760 if not word.isalpha():
763 lines.append('\t%s' % ' '.join(L))
769 class NWCLyricProperties (Structure):
772 _fields.append(('unknown%d' % (i+1), 'B'))
775 class NWCLyricBlockProperties (Structure):
776 #'\x00\x00\x00\x00\x00'
777 #'\x00\x04\x9f'\x00\x00\x01
786 class NWCLyric (Structure):
792 class NWCStaffNoteProperties (Structure):
796 ('num_tokens', 'H'), # ?
799 _fields.append(('unknown%d' % (i+1), 'B'))
806 class NWCTokenEnum (Enum):
807 _list = ['clef', 'key_sig', 'bar', '3', 'instrument_patch', 'time_sig',
808 'tempo', 'dynamics1', 'note', 'rest', 'chord1', 'pedal', 12,
809 'midi_MPC', 'fermata', 'dynamics2', 'performance_style', 'text',
813 def process(self, value):
814 if value > len(self._list):
817 return self._list[value]
819 raise ValueError('index %d out of range for %s'
820 % (value, self._list))
823 class NWCToken (Structure):
825 ('token', NWCTokenEnum),
829 class NWCClefEnum (Enum):
830 _list = ['treble', 'bass', 'alto', 'tenor']
833 class NWCOctaveEnum (Enum):
837 class NWCToken_clef (Structure):
843 ('clef', 2, NWCClefEnum)),
847 ('octave', 2, NWCOctaveEnum)),
855 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Displaying-pitches#Clef
857 return r'\clef %s' % self['clef']
860 class NWCToken_key_sig (Structure):
874 '00000000': 'c \major % or a \minor',
875 '00000020': 'g \major % or e \minor',
876 '00000024': 'd \major % or b \minor',
877 '00000064': 'a \major % or fis \minor',
878 '0000006c': 'e \major % or cis \minor',
879 '0000006d': 'b \major % or gis \minor',
880 '0000007d': 'fis \major % or dis \minor',
881 '0000007f': 'cis \major % or ais \minor',
882 '00020000': 'f \major % or d \minor',
883 '00120000': 'bes \major % or g \minor',
884 '00130000': 'ees \major % or c \minor',
885 '001b0000': 'aes \major % or f \minor',
886 '005b0000': 'des \major % or bes \minor',
887 '005f0000': 'ges \major % or ees \minor',
888 '007f0000': 'ces \major % or a \minor',
893 http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Accidentals-and-key-signatures#index-_005ckey
895 sig = '%04x%04x' % (self['flat_bits'], self['sharp_bits'])
896 return '\key %s' % self._sigs[sig]
898 def accidental(self, pitch):
899 """Return the appropriate accidental for an in-key note.
901 index = ord(pitch[0].lower()) - ord('a')
902 if self['flat_bits'] >> index & 1:
904 elif self['sharp_bits'] >> index & 1:
909 class NWCToken_bar (Structure):
921 'section_open': '.|',
922 'section_close': '|.',
923 # use \repeat for repeats
926 def ly_text(self, type=None):
928 http://www.noteworthysoftware.com/composer/faq/102.htm
929 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Bars#Bar-lines
930 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Repeats
933 type = 'single' # TODO: detect bar type
934 return r'\bar "%s"' % self._bars[type]
935 #|Bar|Style:SectionClose|SysBreak:Y
938 class NWCToken_3 (Structure):
941 _fields.append(('unknown%d' % (i+1), 'B'))
944 class NWCToken_instrument_patch (Structure):
947 _fields.append(('unknown%d' % (i+1), 'B'))
951 http://kainhofer.com/~lilypond/ajax/user/lilypond/Creating-MIDI-files.html#index-instrument-names-1
952 http://kainhofer.com/~lilypond/ajax/user/lilypond/MIDI-instruments.html#MIDI-instruments
954 return r'\set Staff.midiInstrument = #"%s"' % 'cello'
957 class NWCToken_time_sig (Structure):
963 ('beat_value', Pow2Enum),
970 '4/4': '1', '3/4': '2.', '2/4': '2', '1/4': '1',
971 '1/8': '8', '2/8': '4', '3/8': '4.', '6/8': '2.',
972 '4/8': '2', '9/8': '12', '12/8': '1',
973 '2/2': '1', '4/2': '0', '1/2': '2',
978 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Displaying-rhythms#Time-signature
980 return r'\time %s/%s' % (self['beats'], self['beat_value'])
982 def expected_bar_beats(self):
983 return float(self['beats'])/self['beat_value']
986 class NWCToken_tempo (Structure):
992 ('beats_per_minute', 'B'),
998 def ly_text(self, time_sig=None):
1000 http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Writing-parts#Metronome-marks
1002 if time_sig == None:
1003 beats_per_measure = 4
1005 beats_per_measure = time_sig['']
1006 return r'\tempo "%s" %s = %s' % (
1007 self['text'], beats_per_measure, self['beats_per_minute'])
1010 class NWCToken_dynamics1 (Structure):
1024 class NWCAccidentalEnum (Enum):
1026 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Writing-pitches#Accidentals
1028 _list = ['sharp', 'flat', 'natural', 'double_sharp', 'double_flat', 'auto']
1031 def _nwc_duration_dot(struct):
1034 elif struct['ddot']:
1038 def _nwc_duration(struct):
1039 return '%s%s' % (struct['duration'], _nwc_duration_dot(struct))
1041 def _nwc_duration_value(struct):
1042 v = 1.0 / struct['duration']
1049 if struct['triplet'] != 'none':
1054 class NWCStemEnum (Enum):
1055 _list = ('neutral', 'up', 'down')
1056 _ly_text_list = ('\stemNeutral', '\stemUp', '\stemDown')
1059 class NWCTripletEnum (Enum):
1060 _list = ('none', 'start', 'inside', 'stop')
1063 class NWCBeamEnum (Enum):
1064 _list = ('none', 'start', 'inside', 'stop', 4, 5, 6, 7)
1067 class NWCSlurEnum (Enum):
1068 _list = ('none', 'start' , 'stop', 'inside')
1071 class NWCToken_note (Structure):
1075 ('duration', Pow2Enum),
1079 ('stem', 2, NWCStemEnum),
1080 ('triplet', 2, NWCTripletEnum),
1081 ('beam', 2, NWCBeamEnum)),
1086 ('accent', 1, BoolEnum),
1087 ('tie', 1, BoolEnum),
1089 ('dot', 1, BoolEnum),
1090 ('staccato', 1, BoolEnum),
1091 ('ddot', 1, BoolEnum)),
1097 ('tenuto', 1, BoolEnum),
1098 ('slur', 2, NWCSlurEnum),
1104 ('pitch1', 1, BoolEnum), # some kind of pitch adjustment?
1105 ('accidental', 3, NWCAccidentalEnum),
1110 _scale = ( # this list is taken from lilycomp
1111 "c,,,","d,,,","e,,,","f,,,","g,,,","a,,,","b,,,",
1112 "c,,","d,,","e,,","f,,","g,,","a,,","b,,",
1113 "c,","d,","e,","f,","g,","a,","b,",
1114 "c","d","e","f","g","a","b",
1115 "c'","d'","e'","f'","g'","a'","b'",
1116 "c''","d''","e''","f''","g''","a''","b''",
1117 "c'''","d'''","e'''","f'''","g'''","a'''","b'''",
1118 "c''''","d''''","e''''","f''''","g''''","a''''","b''''",
1132 'double_sharp': 'isis',
1133 'double_flat': 'eses',
1136 'sesqui_sharp': 'isih',
1137 'sesqui_flat': 'eseh'
1141 # http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Attached-to-notes#Articulations-and-ornamentations
1142 # http://lilypond.org/doc/v2.11/Documentation/user/lilypond/List-of-articulations
1143 'accent': '->', # r'\accent',
1144 'marcato': '-^', #r'\marcato',
1145 'staccatissimo': '-|', # r'\staccatissimo',
1146 'espressivo': r'\espressivo',
1147 'staccato': '-.', # r'\staccato',
1148 'tenuto': '--', # r'\tenuto',
1149 'portato': '-_', # r'\portato',
1151 'downbow': r'\downbow',
1152 'flageolet': r'\flageolet',
1159 'stopped': '-+', #r'\stopped',
1161 'reverseturn': r'\reverseturn',
1164 'mordent': r'\mordent',
1165 'prallprall': r'\prallprall',
1166 'prallmordent': r'\prallmordent',
1167 'upprall': r'\upprall',
1168 'downprall': r'\downprall',
1169 'upmordent': r'\upmordent',
1170 'downmordent': r'\downmordent',
1171 'pralldown': r'\pralldown',
1172 'prallup': r'\prallup',
1173 'lineprall': r'\lineprall',
1174 'signumconguruentiae': r'\signumconguruentiae',
1175 'shortfermata': r'\shortfermata',
1176 'fermata': r'\fermata',
1177 'longfermata': r'\longfermata',
1178 'verylongfermata': r'\verylongfermata',
1181 'varcoda': r'\varcoda',
1191 def ly_text(self, clef=None, key=None,
1192 previous_note=None, next_note=None,
1195 http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Curves#Phrasing-slurs
1196 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Writing-rhythms#Ties
1197 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Writing-rhythms#Tuplets
1198 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Special-rhythmic-concerns#Grace-notes
1201 clef_name = clef['clef']
1203 clef_name = 'treble'
1204 clef_offset = self._scale.index(self._clef_offsets[clef_name])
1205 pitch = self._scale[-self['pitch'] + clef_offset]
1207 if self['accidental'] == 'auto':
1209 accidental = key.accidental(pitch)
1211 accidental = 'natural'
1213 accidental = self['accidental']
1214 note = [pitch[0], self._accidentals[accidental], pitch[1:]]
1217 return ''.join(note)
1219 note.append(_nwc_duration(self))
1221 for key in ['accent', 'staccato', 'tenuto']:
1223 note.append(self._ornaments[key])
1225 note.append(self._slurs[self['slur']])
1230 if self['beam'] == 'start':
1232 elif self['beam'] == 'stop':
1235 if self['triplet'] == 'start':
1236 note.insert(0, r'\times 2/3 { ')
1237 elif self['triplet'] == 'stop':
1241 if previous_note == None or not previous_note['grace']:
1242 note.insert(0, r'\acciaccatura { ')
1243 if next_note == None or not next_note['grace']:
1246 return ''.join(note)
1249 class NWCToken_rest (NWCToken_note):
1250 def ly_text(self, **kwargs):
1251 rest = ['r%s' % _nwc_duration(self)]
1252 if self['triplet'] == 'start':
1253 rest.insert(0, r'\times 2/3 { ')
1254 elif self['triplet'] == 'stop':
1257 return ''.join(rest)
1260 class NWCToken_chord1 (Structure):
1261 _fields = NWCToken_note._fields + [
1266 def unpack_from(self, buffer, offset=0):
1267 super(NWCToken_chord1, self).unpack_from(buffer, offset)
1268 chords,size = self._unpack_chords_from(buffer, offset + self.size)
1269 self['chords'] = chords
1272 def _unpack_chords_from(self, buffer, offset=0):
1279 for i in range(self['num_notes']):
1280 token.unpack_from(buffer, offset + size)
1282 assert token['token'] in ['rest', 'note'], token
1284 structure = globals()['NWCToken_%s' % token['token']]
1285 t = structure(buffer, offset + size)
1288 if _nwc_duration(t) == _nwc_duration(self):
1295 chords.append(chord2)
1296 return (chords, size)
1298 def ly_text(self, **kwargs):
1300 http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Combining-notes-into-chords#Combining-notes-into-chords
1303 for chord in self['chords']:
1305 if isinstance(token, NWCToken_note):
1306 notes.append(token.ly_text(in_chord=True, **kwargs))
1307 chord = ['<%s>' % ' '.join(notes)]
1308 chord.append(_nwc_duration(self))
1312 if self['beam'] == 'start':
1314 elif self['beam'] == 'stop':
1317 if self['triplet'] == 'start':
1318 chord.insert(0, r'\times 2/3 { ')
1319 elif self['triplet'] == 'stop':
1322 return ''.join(chord)
1323 # if chord2: # 2 voices
1325 # for i in range(len(chord2)):
1326 # (pitch,accidental,duration ) = chord2[i]
1328 # note = SCALE[pitch]
1330 # if (accidental!='auto'):
1331 # currentKey[note[0]] = accidental
1332 # accidental = currentKey[note[0]]
1334 # if (relative_pitch):
1335 # octave = getRelativePitch(lastPitch, pitch)
1339 # result += note[0] + accidental + octave + duration + ' '
1341 # result += " \\\\ {"
1343 # for i in range(len(chord1)):
1344 # (pitch,accidental ) = chord1[i]
1346 # note = SCALE[pitch]
1348 # if (accidental!='auto'):
1349 # currentKey[note[0]] = accidental
1350 # accidental = currentKey[note[0]]
1352 # if (relative_pitch):
1353 # octave = getRelativePitch(lastPitch, pitch)
1357 # result += note[0] + accidental + octave +chordDur + ' '
1358 # if lastChord >0 : lastChord = _nwc_duration_value(duration) - _nwc_duration_value(chordDur)
1359 # if lastChord==0: result += ' } >> '
1361 # else: # block chord
1363 # for i in range(len(chord1)):
1364 # (pitch,accidental ) = chord1[i]
1366 # note = SCALE[pitch]
1368 # if (accidental!='auto'):
1369 # currentKey[note[0]] = accidental
1370 # accidental = currentKey[note[0]]
1372 # if (relative_pitch):
1373 # octave = getRelativePitch(lastPitch, pitch)
1377 # result += note[0] + accidental + octave + ' '
1378 # result += '>' +chordDur +' '
1379 # lastPitch = chord1[0][0] + lastClef
1380 # lastDuration = chordDur
1383 class NWCToken_pedal (Structure):
1386 _fields.append(('unknown%d' % (i+1), 'B'))
1389 class NWCToken_midi_MPC (Structure):
1392 _fields.append(('unknown%d' % (i+1), 'B'))
1395 class NWCToken_fermata (Structure):
1398 _fields.append(('unknown%d' % (i+1), 'B'))
1402 http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Other-uses-for-tweaks#index-fermata_002c-implementing-in-MIDI
1407 class NWCToken_dynamics2 (Structure):
1410 _fields.append(('unknown%d' % (i+1), 'B'))
1413 class NWCToken_performance_style (Structure):
1416 _fields.append(('unknown%d' % (i+1), 'B'))
1419 class NWCToken_text (Structure):
1431 http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Writing-text
1432 http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Writing-text#Text-marks
1434 #if text.isdigit() : # check numbers
1435 # text = "-\\markup -\\number "+ text
1436 # #text = "-\\markup {\\number "+ text +"}"
1438 # text = '-"' + text + '"'
1439 #extra += ' ' + text
1440 return r'\mark "%s"' % self['text']
1443 class NWCToken_chord2 (NWCToken_chord1):
1447 def _get_type_and_type_tail(nwc_buffer):
1449 >>> _get_type_and_type_tail('[NWZ]...\\n[bla bla]...')
1450 ('NWZ', '...\\n[bla bla]...')
1451 >>> _get_type_and_type_tail('[NoteWorthy ArtWare]...\\n[bla bla]...')
1452 ('NoteWorthy ArtWare', '...\\n[bla bla]...')
1454 m = re.match('\[([^]]*)\]', nwc_buffer)
1456 tail = nwc_buffer[len(m.group(0)):]
1457 return (_type, tail)
1459 def _normalize_nwc(nwc_buffer):
1461 Tested in `parse_nwc()` doctests.
1463 _type,tail = _get_type_and_type_tail(nwc_buffer)
1465 LOG.debug('compressed nwc detected, decompressing.')
1466 uncompressed_nwc_buffer = zlib.decompress(tail[1:])
1467 return uncompressed_nwc_buffer
1470 def _get_staff(nwc_buffer, offset, n):
1471 orig_offset = offset
1472 staff = NWCStaff(nwc_buffer, offset)
1473 offset += staff.size
1474 if staff['num_lyrics'] > 0:
1475 assert staff['has_lyrics'] == 1, pprint.pformat(staff)
1476 staff['lyric_properties'] = NWCLyricProperties(nwc_buffer, offset)
1477 offset += staff['lyric_properties'].size
1478 staff['lyrics'] = []
1479 for i in range(staff['num_lyrics']):
1480 p = NWCLyricBlockProperties(nwc_buffer, offset)
1482 length = 1024 * (p['num_blocks']/4) - 1
1486 _decode_string(nwc_buffer[offset:offset+length]))
1489 block.append([word.strip() for word in line.rstrip('\x00').split('\x00')])
1490 LOG.debug('lyric line %s' % block[-1])
1491 staff['lyrics'].append(block)
1493 # possible if lyric byte read
1494 staff['note_properties'] = NWCStaffNoteProperties(nwc_buffer, offset)
1495 offset += staff['note_properties'].size
1497 staff['tokens'] = []
1500 for i in range(10000000): # num_tokens
1502 token.unpack_from(nwc_buffer, offset)
1503 except struct.error:
1504 LOG.warn('ran off end of file')
1505 return (staff, offset-orig_offset)
1506 if token['token'] == 'STOP':
1508 offset += token.size
1509 LOG.debug('token: %s' % token['token'])
1510 if type(token['token']) == int:
1511 raise ValueError(token['token'])
1512 structure = globals()['NWCToken_%s' % token['token']]
1513 t = structure(nwc_buffer, offset)
1515 staff['tokens'].append(t)
1516 LOG.debug('%d tokens, %d bytes (chords count as one)' % (i, offset - oo))
1517 return (staff, offset-orig_offset)
1519 def parse_nwc(nwc_buffer):
1520 """Parse a NoteWorthy Composer `nwc` file.
1523 >>> with open(os.path.join('example', '1.75-1.nwc'), 'rb') as f:
1524 ... nwc_buffer = f.read()
1525 >>> nwc_buffer # doctest: +ELLIPSIS
1527 >>> n = parse_nwc(nwc_buffer)
1528 >>> n['_debug']['nwc_buffer'] # doctest: +ELLIPSIS
1529 '[NoteWorthy ArtWare]...'
1530 >>> d = n.pop('_debug')
1531 >>> pprint.pprint(n) # doctest: +ELLIPSIS, +REPORT_UDIFF
1532 {'major_version': 1,
1533 'minor_version': 75,
1536 'page_setup': {'f2': u'F2',
1537 'margins': u'1.00000000 1.00000000 1.00000000 1.00000000',
1539 'page_small': {'name': u'Times New Roman',
1544 'page_text': {'name': u'Times New Roman',
1549 'page_title': {'name': u'Times New Roman',
1554 'staff_bold': {'name': u'Times New Roman',
1559 'staff_italic': {'name': u'Times New Roman',
1564 'staff_lyric': {'name': u'Times New Roman',
1570 'user1': {'name': u'Times New Roman',
1575 'user2': {'name': u'Times New Roman',
1580 'user3': {'name': u'Times New Roman',
1585 'user4': {'name': u'Times New Roman',
1590 'user5': {'name': u'Times New Roman',
1595 'user6': {'name': u'Times New Roman',
1600 'product': u'[NoteWorthy Composer]',
1601 'song_info': {'author': u'George Job Elvey, 1858',
1602 'comments': u'Source: The United Methodist Hymnal (Nashville, Tennessee: The United Methodist Publishing House, 1989), # 694.',
1603 'copyright1': u'Public Domain',
1604 'copyright2': u'Courtesy of the Cyber Hymnal (http://www.cyberhymnal.org)',
1605 'title': u'St. George\u2019s Windsor, 77.77 D'},
1606 'staff': [{'ff': 255,
1607 'group': u'Standard',
1610 'name': u'Unnamed-000',
1611 'note_properties': {'color': 0, 'num_tokens': 0, 'unknown1': 0},
1613 'tokens': [{'flat_bits': 2,
1624 'type': u'[NoteWorthy ArtWare]',
1628 LOG.info('parsing nwc')
1630 nwc_buffer = _normalize_nwc(nwc_buffer)
1631 n['_debug']['nwc_buffer'] = nwc_buffer
1633 head = NWCFileHead(nwc_buffer)
1637 assert head['major_version'] == 1, head['major_version']
1638 assert head['minor_version'] == 75, head['minor_version']
1640 #o = nwc_buffer[offset:].find('\xff')
1642 # LOG.error('could not find staff section')
1643 # raise NotImplementedError
1645 #LOG.warn('skip to staves with offset %d (skipped %d, %s)'
1646 # % (offset, o, repr(nwc_buffer[offset:offset+10])))
1648 n['staff_set'] = NWCStaffSet(nwc_buffer, offset)
1649 offset += n['staff_set'].size
1652 for i in range(n['staff_set']['num_staves']):
1653 LOG.info('process staff %d' % i)
1654 staff,size = _get_staff(nwc_buffer, offset, n)
1658 n['staff'].append(staff)
1666 >>> with open(os.path.join('example', '1.75-1.nwc'), 'rb') as f:
1667 ... nwc_buffer = f.read()
1668 >>> nwc = parse_nwc(nwc_buffer)
1669 >>> print ly_text(nwc) # doctest: +ELLIPSIS
1670 '[NoteWorthy ArtWare]...'
1672 http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Creating-MIDI-files
1674 if nwc['type'] not in ['[NoteWorthy ArtWare]', '[NoteWorthy Composer]']:
1675 raise ValueError('unknown file type: %s' % nwc['type'])
1677 #LOG.debug(pprint.pformat(nwc, width=70))
1678 LOG.info('format header')
1681 '%% Generated with %s converter v%s' % ('nwc2ly.py', __version__),
1682 r'\version "2.12.2"', # http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Version-number#Version-number
1685 lines.extend([nwc['song_info'].ly_text(), ''])
1693 for i,staff in enumerate(nwc['staff']):
1694 LOG.info('format staff %d' % i)
1695 voice = chr(i+ord('a'))
1696 if i == 1 and False:
1697 for token in staff['tokens']:
1698 if isinstance(token, NWCToken_clef):
1699 token['clef'] = 'bass'
1700 lines.extend(['\t\t%s' % line for line in staff.ly_lines(voice=voice)])
1702 LOG.info('format footer')
1710 return '\n'.join(lines)
1715 return doctest.testmod()
1718 if __name__ == '__main__':
1719 from optparse import OptionParser
1721 usage = "nwc2ly.py [options] uncompressed.nwc test.ly > convert.log"
1722 p = OptionParser(usage=usage)
1723 p.add_option('--debug', dest='debug', action='store_true',
1724 help='Enable verbose logging')
1725 p.add_option('--absolute-pitch', dest='relative_pitch', default=True,
1726 action='store_false',
1728 p.add_option('--absolute-duration', dest='relative_duration', default=True,
1729 action='store_false',
1731 p.add_option('--bar-comments', dest='bar_comments', default=10,
1733 help='Comments for every x lines, section/line comment??')
1734 p.add_option('--no-beaming', dest='insert_beaming', default=True,
1735 action='store_false', help='')
1736 p.add_option('--no-stemming', dest='insert_stemming', default=True,
1737 action='store_false', help='')
1738 p.add_option('--no-text', dest='insert_text', default=True,
1739 action='store_false', help='Currently a no-op')
1740 p.add_option('--test', dest='test', action='store_true',
1741 help='Run internal tests and exit')
1743 options,args = p.parse_args()
1746 logging.basicConfig(level=logging.DEBUG)
1748 logging.basicConfig(level=logging.INFO)
1752 sys.exit(min(results.failed, 127))
1761 with open(nwc_file, 'rb') as f:
1762 nwc_buffer = f.read()
1763 nwc = parse_nwc(nwc_buffer)
1766 f = open(ly_file, 'w')
1770 f.write(ly_text(nwc).encode('utf-8'))