-import binascii, sys, zlib, traceback
-from ConfigParser import SafeConfigParser
-
-shortcopyleft = """
-nwc2ly - Converts NWC(v 1.75) to LY fileformat
-Copyright (C) 2005 Joshua Koo (joshuakoo @ myrealbox.com)
-and Hans de Rijck (hans @ octet.nl)
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-"""
-
-##
-# most infomation obtained about the nwc format
-# is by using noteworthycomposer and the somewhat like the french cafe method
-# (http://samba.org/ftp/tridge/misc/french_cafe.txt)
+#!/usr/bin/env python
#
+# Copyright (C) 2010 W.Trevor King (wking @ drexel.edu)
++# 2005 Hans de Rijck (hans @ octet.nl)
+# 2005 Joshua Koo (joshuakoo @ myrealbox.com)
#
-##
-# Revisions
-# 0.1 07 april 2005 initial hex parsing;
-# 0.2 13 april 2005 added multiple staff, keysig, dots, durations
-# 0.3 14 april 2005 clef, key sig detection, absolute notes pitching
-# 0.4 15 April 2005 Relative Pitchs, Durations, Accidentals, Stem Up/Down, Beam, Tie
-# 0.5 16 April 2005 Bug fixes, Generate ly score , Write to file, Time Sig, Triplets, Experimental chords
-# 0.6 17 April 2005 Compressed NWC file Supported!
-# 0.7 19 April 2005 Version detection, Header
-# 20 April 2005 BUGS FiXes, small Syntax changes
-# 21 April 2005 Still fixing aimlessly
-# 23 April 2005 Chords Improvement
-# 24 April 2005 staccato, accent, tenuto. dynamics, midi detection (but unsupported)
-# 0.8 24 April 2005 Experimental Lyrics Support
-# 0.9 29 April 2005 Workround for \acciaccatura, simple Check full bar rest adjustment
-# 1.0 10 July 2005 Hans de Rijck,
-# added dynamics, dynamic variance, tempo, tempo variance,
-# performance style, page layout properties
-# corrected barlines, lyrics, slurs, natural accidentals
-# using inifile for settings
-# limited support for NWC version 2.0
-#
-##
-# TODO
-# Proper syntax and structure for staffs, lyrics, layering
-# version 1.7 Support
-# nwc2ly in lilytool
-#
-# Piano Staff
-# Chords
-# Midi Instruments
-# Visability
-# Lyrics
-# Context Staff
-# Staff layering / Merging
-##
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
#
-# BUGS text markups, chords
-######
-#
-# cd /cygdrive/c/development/nwc2ly
-# $ python nwc2ly.py lvb7th1\ uncompressed.nwc test.ly > convert.log
-#######
-
-#################################
-# Options #
-#################################
-
-cp = SafeConfigParser()
-cp.read( 'C:\development\nwc2ly\hans version\nwc2ly.ini' )
-#/cygdrive/C/development/nwc2ly/hans\ version/
-debug = int(cp.get('settings', 'debug'))
-relativePitch = int(cp.get('settings', 'relativePitch'))
-relativeDuration = int(cp.get('settings', 'relativeDuration'))
-barLinesComments = int(cp.get('settings', 'barLinesComments'))
-
-insertBeaming = int(cp.get('settings', 'insertBeaming'))
-insertSteming = int(cp.get('settings', 'insertSteming'))
-insertText = int(cp.get('settings', 'insertText'))
-
-paperSize = cp.get('settings', 'Papersize')
-fillLast = int(cp.get('settings', 'fillLast'))
-
-LilyPondVersion = cp.get('settings', 'LilyPondVersion')
-
-stdGrace = cp.get('settings', 'stdGrace')
-accidentalStyle = cp.get('settings', 'accidentalStyle')
-
-#################################
-# /Options #
-#################################
-
-global measureStart
-global nwcversion
-
-nwc2lyversion = '1.0'
-
-##############
-
-args = len(sys.argv)
-if args<2:
- print "Syntax: python nwc2ly.py nwcfile [lyfile]"
- sys.exit()
-nwcfile = sys.argv[1]
-
-if args<3:
- lyfile = ''
-else:
- lyfile = sys.argv[2]
-
-
-
-def getFileFormat(nwcData):
- global nwcversion
- #'[NoteWorthy ArtWare]'
- #'[NoteWorthy Composer]'
- nwcData.seek(0)
- company = readLn(nwcData)
- nwcData.seek(2,1) # pad
- product = readLn(nwcData)
- if debug: print "product: ", product
- #version = readLn(nwcData)
-
- version = nwcData.read(3)
- if debug: print " version: ", binascii.hexlify(version)
- huh = nwcData.read(1)
- version = ord(version[0]) * 0.01 + ord(version[1])
- nwcversion = version
- print 'NWC file version', nwcversion, 'detected!'
- pad(nwcData,2) # times saved
- name1 = readLn(nwcData)
- name2 = readLn(nwcData)
-
- pad(nwcData,8)
- huh = nwcData.read(1)
- pad(nwcData,1)
-
-def getFileInfo(nwcData):
- title = readLn(nwcData)
- author = readLn(nwcData)
- copyright1 = readLn(nwcData)
- copyright2 = readLn(nwcData)
- if nwcversion == 2.0 : whatsIt = readLn(nwcData)
- comments = readLn(nwcData)
-
- header = "\\header {"
- header += "\n\ttitle = \"%s\"" % title
- header += "\n\tcomposer = \"%s\"" % author
- header += "\n\tcopyright = \\markup \\teeny \"%s\"" % copyright1
- header += "\n\tfooter = \"%s\"" % copyright2
- header += "\n\t%%{ %s %%}" % comments
- header += "\n}"
-
- # TO ADD IN DEFAULT BLANK FIELDS TO KEY IN
- print 'title,author,copyright1,copyright2,comments: ', (title,author,copyright1,copyright2,comments)
- return header
-
-# Page Setup
-def getPageSetup(nwcData):
- # ??
- margins = getMargins(nwcData)
- #getContents(nwcData)
- #getOptions(nwcData)
- staffSize = getFonts(nwcData)
- barlineCount = 0
- return margins, staffSize
-
-def getMargins(nwcData):
- global measureStart
- #readTill(nwcData,'\x01') # not correct, should read 9 bytes (HdR)
- temp = nwcData.read(9)
- if debug : print "skipping ", binascii.hexlify(temp)
- measureStart = ord( nwcData.read(1) )
- pad(nwcData,1)
- # get string size 43
- margins = readLn(nwcData)
- print 'Start measure ', measureStart
- print 'margins ', margins
- #mirrorMargines
- #UOM
- return margins
-
-def getOptions(nwcData):
- # page numbering, from
- # title page info
- # extend last system
- # increase note spacing for larger note duration
- # staff size
- # staff labels (none, first systems, top systems, all systems
- # measure numbers none, plain, circled, boxed
- # measure start
- return
-
-def getFonts(nwcData):
- # 12 Times
- #readTill(nwcData,'\xFF') #not correct, should read 36 bytes (HdR)
- nwcData.read(36)
- n = nwcData.read(1)
- print "Staff size: ", ord(n)
- pad(nwcData,1)
- for i in range (12):
- # Font Name
- font = readLn (nwcData)
-
- # 00
-
- # Style 'Regular' 'Italic' 'Bold' 'Bold Italic'
- style = ord(nwcData.read(1)) & 3
-
- # Size
- size = ord(nwcData.read(1))
-
- ## 00
- nwcData.seek(1,1)
-
- # Typeface
- # 00 Western, b1 Hebrew
- typeface = nwcData.read(1)
-
- if debug: print 'Font detected' , font, 'size',size, 'style ', style, ' typeface',typeface
-
- return ord(n)
-
-def findNoOfStaff(nwcData):
- # Infomation on Staffs \x08 00 00 FF 00 00 n
- data = 0;
-
- readTill(nwcData,'\xFF')
- if debug: print "Where am I? ", nwcData.tell()
-
- nwcData.read(2)
-
- layering = nwcData.read(1) # FF or 00
-
- noOfStaffs = ord(nwcData.read(1))
- nwcData.read(1)
- if debug: print noOfStaffs, " noOfStaffs found"
- return noOfStaffs
-
-def findStaffInfo(nwcData):
- # Properties for Staff
- # General
- # Name.
- # Group
- # Ending Bar
- # Visual
- # Verticle Upper Size
- # Verticle Lower Size
- # Style
- # Layer Next Staff
- # Color
- # Midi
- # Part Volume
- # Stereo Pan
- # Transposition
- # Muted
- # PlayBack Device
- # Playback Channel
- # Instrument
- # Patch Name
- # Patch List Type
- # Bank Select
- # Controller0
- # Controller32
- # Staff Lyrics
- # LineCount
- # AlignSyllableRule
- # StaffPlacementAligment
- # StaffPlacementOffset
- # StaffPropertiesVerticleSizeUpper
- # StaffPropertiesVerticleSizeLower
-
- format = ''
- staffName = readLn(nwcData)
- format += "\\context Staff = %s " % staffName # or voice or lyrics
-
- groupName = readLn(nwcData) # HOW TO ORGANISE THEM??
-
- endbar = ord(nwcData.read(1)) & 7 # mask all but last bits
- print 'end ',endbar
- endingBar = ending[endbar] # 10 --> OC for lyrics? 10000 1100
-
- muted = ord(nwcData.read(1)) & 1
- nwcData.read(1)
-
- channel = ord(nwcData.read(1)) + 1
- nwcData.read(9)
-
- stafftype = staffType[ord(nwcData.read(1))&3]
- nwcData.read(1)
-
- uppersize = 256 - ord(nwcData.read(1)) # - signed +1 )& 2^7-1 )
- readTill(nwcData,'\xFF')
-
- lowersize = ord(nwcData.read(1))
- ww = nwcData.read(1)
- print '[uppersize,lowersize]',[uppersize,lowersize]
-
- noOfLines = ord(nwcData.read(1))
- print '[staffName,groupName,endingBar,stafftype,noOfLines]', [staffName,groupName,endingBar,stafftype,noOfLines]
-
- layer = ord(nwcData.read(1)) & 1
-
- # signed transposition
- # FF?
-
- partVolume = ord(nwcData.read(1))
- ord(nwcData.read(1))
-
- stereoPan = ord(nwcData.read(1))
- if nwcversion == 1.7:
- nwcData.read(2)
- else:
- nwcData.read(3)
-
- nwcData.read(2)
- #lyrics = ord(nwcData.read(1)) & 1
- lyrics = readInt(nwcData)
- noOfLyrics = readInt(nwcData)
-
- lyricsContent = ''
- if lyrics:
- lyricOptions = readInt(nwcData)
- nwcData.read(3)
- for i in range(noOfLyrics):
- print 'looping ',i, 'where', nwcData.tell(), 'NoOfLyrics:', noOfLyrics
- #lyricsContent += '\\ \lyricmode { ' # lyrics
- lyricsContent = str( getLyrics(nwcData) ) #
- #lyricsContent += '}'
- nwcData.read(1)
-
- nwcData.read(1)
- color = ord(nwcData.read(1)) & 3 #12
-
- noOfTokens = readInt(nwcData)
- print noOfTokens, " Tokens found", nwcData.tell()
- return staffName, endingBar, noOfTokens, format, lyricsContent
-
-def pad(nwcData, length):
- nwcData.seek(length,1)
-
-def readTill(nwcData, delimit):
- data = ''
- value = ''
- while data!=delimit:
- value += data
- data = nwcData.read(1)
-
- return value
-
-def readLn(nwcData):
- return readTill (nwcData,'\x00')
- # reads until 00 is hit
- # od oa == \n
-
-def readInt(nwcData):
- data = nwcData.read(2)
- no = ord(data[0])
- no += ord(data[1]) * 256
- return no
-
-
-def getLyrics(nwcData):
-
- data = ''
- print 'reach'
- data = nwcData.read(1)
- if data == '': return
- blocks = ord( data )
- if blocks==4: blocks = 1
- if blocks==8: blocks = 2
- if blocks == 0: return
- data = ''
- lyricsLen = readInt(nwcData)
-
- print 'blocks ',blocks, 'lyrics len', lyricsLen, 'at ', nwcData.tell()
-
- nwcData.read(1)
- for i in range (blocks):
- data += nwcData.read(1024)
-
- lyrics = data[1:lyricsLen-1]
-
- lyrics = lyrics.replace( "\x00", "_ " )
-
- print 'lyrics ', lyrics
- return lyrics
-
-def getDuration(data):
- durationBit = ord(data[2]) & 7
- durationDotBit = ord(data[6])
-
- duration = durations[durationBit]
- absDuration = revDurations[durationBit]
- if (durationDotBit & 1<<2):
- durationDot = '.'
- absDuration += revDurations[durationBit + 1]
- elif (durationDotBit & 1):
- durationDot = '..'
- absDuration += revDurations[durationBit + 1] + revDurations[durationBit + 1]
- else :
- durationDot = ''
- return duration + durationDot
-
-def getKey(data):
- data = binascii.hexlify(data)
-
- if (keysigs.has_key(data)):
- return '\key ' + keysigs[data]
- return '% unknown key'
-
-def getLocation(data):
- offset = ord(data[8]);
- if offset > 127 :
- return 256-offset
-
- #next statement doesn't work for me (HdR)
- #if (ord(data[9])>>3 & 1):
- # return -offset
-
- return -offset
-
- return offset
-
-def getAccidental(data):
- data = ord(data)
- data = (data & 7 )
- return acdts[data]
-
-def getDynVariance(data):
- data = ord(data)
- data = (data & 7 )
- return dynVariance[data]
-
-def getPerfStyle(data):
- data = ord(data)
- temp = '_\\markup {\\small \\italic \\bold {' + perfStyle[data] + '}}'
- return temp
-
-def getTempoVariance(data):
- data = ord(data)
- if data == 0 or data == 1 :
- temp = '\\' + tempoVariance[ data ]
- else:
- temp = '_\\markup {\\small \\italic \\bold {' + tempoVariance[data] + '}}'
- return temp
-
-def getNote(data):
-
- # pitch
- pitch = getLocation(data)
-
- # get Accidentals
- accidental = getAccidental(data[9])
-
- # get Relative Duration
- duration = getDuration(data)
-
- # check stems
- stem = ord(data[4])
- stem = (stem >> 4) & 3
-
- # check beam
- beam = ord(data[4]) & 3
-
- # triplets
- triplet = triplets [ord(data[4])>>2 & 3 ]
-
- # check tie
- tie = ''
- if ord(data[6]) >> 4 & 1:
- tie = '~'
-
- staccato = (ord(data[6]) >> 1) & 1
- accent = (ord(data[6]) >> 5) & 1
-
- tenuto = (ord(data[7]) >> 2) & 1
- grace = (ord(data[7]) >> 5) & 1
-
- # check slur
- slur = slurs[ord(data[7]) & 3 ]
- #if debug: print "Slur ", ord(data[7]), slur
-
- #TODO should use a dictionary
- return (pitch, accidental, duration, stem, beam, triplet, slur, tie, grace, staccato, accent, tenuto)
-
-def getRelativePitch(lastPitch, pitch):
- octave = ''
- diff = pitch - lastPitch
- if diff>3:
- for i in range((diff-4 + 7)/7):
- octave += "'"
- if octave == '':
- octave += "'"
- elif diff<-3:
- for i in range((-diff-4 + 7)/7):
- octave += ","
- if octave == '':
- octave += ","
- return octave
-
-def durVal(duration):
- where = duration.find('.')
-
- if where>-1:
- durVal = 128/int (duration[:where])
- nextVal = durVal
- for i in range(len(duration)-where):
- nextVal = nextVal / 2
- durVal += nextVal
-
- else:
- durVal = 128/int (duration)
- return durVal
-
-def processStaff(nwcData):
- keysigCount =0
- timesigCount=0
- noteCount=0
- restCount=0
- staffCount=0
- clefCount=0
- textCount=0
- tempoCount=0
- barlineCount = measureStart - 1
- dynamicCount = 0
-
- lastPitch = scale.index('c')
- lastDuration = 0
- lastClef = scale.index(clefs[0]) # index referenced to note of last clef
- lastStem = 0
- lastTimesig = 1
-
- lastKey = { 'c': '', #c
- 'd': '', 'e': '', 'f': '', 'g': '', 'a': '', 'b': '' }
- currentKey = lastKey.copy()
-
- data = 0
- token = 1
-
- result = ""
- result += "\n\t\\new Staff {\n\t" + "#(set-accidental-style '" + accidentalStyle + ") "
- lastChord = 0
- lastGrace = 0;
- lastSlur = ' ';
-
- (staffName, endingBar, noOfTokens, format, lyrics) = findStaffInfo(nwcData);
- if debug: print "staff info: name, endingbar, noOfTokens, format, lyrics-"
- if debug: print staffName, endingBar, noOfTokens, format, lyrics
-
- if relativePitch:
- result+="\\relative c {"
- else :
- result+=" {"
-
- result+= '\n\n\t% Staff ' + str(staff) + '\t\t(' + staffName + ')\n\n\t'
- result+= '\\set Staff.instrument = #\"' + staffName + '\"\n\t'
- result+= '\\set Score.skipBars = ##t\n\n\t'
-
- #print "00112233445566778899\n"
- extra = ''
- dynamic = ''
- style = ''
- # the juice
-
- dualVoice = 0
- while data!="":
-
- token += 1
- if token==noOfTokens:
- result += "\\bar \"" + endingBar + "\"\n\t\t"
-
- if dualVoice == 1: # no beams and slurs with two voices
- result += "%Todo: check for slurs and beams in the bar above\n\t\t"
- dualVoice = 0
-
- result += "}\n\t"
- if lyrics!='':
- result += '\n\t\t\\addlyrics{ ' + lyrics + '}'
- result += "\n\t}\n\t"
- print "going next staff! %s" % nwcData.tell()
- break
-
-
- if nwcversion==1.7:
- nwcData.seek(2,1)
-
- data = nwcData.read(1)
- #print 'test', data
-
- # clef
- if data=='\x00':
- data = nwcData.read(6)
- clefCount += 1
- if debug: print binascii.hexlify(data), ' = clef ',
-
- key = ord(data[2]) & 3
- octave = ord(data[4]) & 3
- # print binascii.hexlify(data) , "CLEF? "
- lastClef = scale.index(clefs[key]) + octaves[octave]
- #TODO check for octave shifts _8
- #lastClef += clefShift[octave] # on ottava don't shift, just print 8va
- if debug: print clefNames[key], clefOctave[octave]
- result += '\clef "' + clefNames[key] + clefOctave[octave]+ '"\n\t\t'
-
- # key signature
- elif data=='\x01':
- data = nwcData.read(12)
- keysigCount = keysigCount + 1
- if debug: print binascii.hexlify(data), ' = Key signature ',
-
- #
- flatBits = ord(data[2])
- sharpBits = ord(data[4])
-
- for note in lastKey.keys():
- noteInd = ['a','b','c','d','e','f','g'].index(note)
- if (flatBits >> noteInd & 1):
- lastKey[note] = 'es'
- elif (sharpBits >> noteInd & 1):
- lastKey[note] = 'is'
- else:
- lastKey[note] = ''
-
- currentKey = lastKey.copy()
- if debug: print getKey(data[1:5])
- result = result + getKey(data[1:5]) + "\n\t\t"
-
- #print "data", binascii.hexlify(data)
- #print "flat", binascii.hexlify(flatBits)
- #print "sharp", binascii.hexlify(data[4])
- #print getKey(data[1:5])
-
-
- # barline
- elif data=='\x02':
- data = nwcData.read(4)
- #if debug: print ' = Barline ', barlines[ ord(data[2]) ]
- #if debug: print binascii.hexlify(data)
- barlineCount += 1
- currentKey = lastKey.copy()
-
- if ( data[2] == '\x00' ):
- result += "|\n\t\t"
- else:
- result += "\\bar \"" + barlines[ ord(data[2]) ] + "\"\n\t\t"
-
- if dualVoice == 1: # no beams and slurs with two voices
- result += "%Todo: check for slurs and beams in the bar above\n\t\t"
-
- if (barlineCount % barLinesComments == 0):
- result += "\n\t\t% Bar " + str(barlineCount + 1) + "\n\t\t"
- #print '.',
- print 'Bar ', barlineCount, ' completed,'
-
- dualVoice = 0
- # Repeat
-
- elif data=='\x03':
- if debug: print "Repeat",
- data = nwcData.read(4)
- if debug: print binascii.hexlify(data),
- if debug: print " = repeat: ", ord( data[2] )
- result += "%Todo: place alternatives for \\repeat volta " + str(ord( data[2] )) + "\n\t\t"
-
- # Instrument Patch
- elif data=='\x04':
- if debug: print "Instrument Patch",
- data = nwcData.read(10)
- if debug: print binascii.hexlify(data)
- #readLn(nwcData)
- #readLn(nwcData)
- #readLn(nwcData)
-
- # timesig
- elif data=='\x05':
- data = nwcData.read(8)
- if debug: print binascii.hexlify(data),
- timesigCount = timesigCount + 1
- beats = ord(data[2])
- beatValues = [ 1, 2, 4, 8 ,6, 32 ]
- beatValue = beatValues[ord(data[4])]
- timesig = str(beats) + "/" + str(beatValue)
- if debug: print ' = Timesig', timesig
- lastTimesig = timesigValues[timesig]
- result += "\\time " + timesig + " "
-
- # Tempo
- elif data=='\x06':
- if debug: print "Tempo ",
- data = nwcData.read(7)
- if debug: print binascii.hexlify(data)
- tempo = readLn(nwcData)
-
- # byte 4 length, byte 6 note,
- tempoNote = data[6]
- tempoDuration = ord(data[4])
- if debug: print "duration ", tempoDuration
-
- if ( data[6] == '\x00' ):
- tempoNote = '8'
- tempoMultiply = int( tempoDuration * 2)
- elif ( data[6] == '\x01' ):
- tempoNote = '8.'
- tempoMultiply = int( tempoDuration / 0.75 )
- elif ( data[6] == '\x02' ):
- tempoNote = '4'
- tempoMultiply = tempoDuration
- elif ( data[6] == '\x03' ):
- tempoNote = '4.'
- tempoMultiply = int( tempoDuration / 1.5)
- elif ( data[6] == '\x04' ):
- tempoNote = '2'
- tempoMultiply = int( tempoDuration / 2)
- elif ( data[6] == '\x05' ):
- tempoNote = '2.'
- tempoMultiply = int( tempoDuration / 3)
-
- result += '\n\t\t\\tempo ' + tempoNote + '=' + str( tempoMultiply ) + ' '
- tempoCount = tempoCount + 1
-
- # dynamics
- elif data=='\x07':
- dynamicCount = dynamicCount + 1
- data = nwcData.read(9)
- if debug: print binascii.hexlify(data),
- dynamic = dynamics[ ord(data[4]) & 7 ]
- if debug: print ' = Dynamic ' + dynamic
-
- # note
- elif data=='\x08':
- data = nwcData.read(10)
- noteCount = noteCount + 1
-
- if debug: print 'note ', binascii.hexlify(data) , noteCount , nwcData.tell(),
-
- (pitch, accidental, duration, stem, beam, triplet, slur, tie, grace, staccato, accent, tenuto) = getNote(data)
-
- if debug: print pitch, accidental, duration, stem, beam, triplet, slur, tie, grace, staccato, accent, tenuto
-
-
- articulate = ''
- if staccato: articulate+= '-.'
- if accent: articulate+= '->'
- if tenuto: articulate+= '--'
-
- beam = beams[beam]
-
- chordMatters = ''
- if lastChord>0 and beam==']' :
- if debug: print 'Chord ] ', durVal(duration), lastChord
- chordMatters = ' } >> '
- lastChord = 0
- elif lastChord>0:
- if debug: print 'Chord last ', durVal(duration), lastChord
- dur = durVal(duration)
- #lastChord = 1.0/((1.0 / lastChord) - (1.0/durVal(duration)))
- lastChord -= dur
-
- if lastChord <= 0 :
- chordMatters = ' } >> '
- lastChord = 0
-
-
-
- if not insertBeaming: beam = ''
-
- # pitch
- pitch += lastClef
- note = scale[pitch]
-
- natural = ''
-
- # get Accidentals
- if ( accidental == '!' ) : # also do forced naturals
- natural = accidental
- accidental = ''
-
- if (accidental!='auto'):
- currentKey[note[0]] = accidental
-
- accidental = currentKey[note[0]]
-
- if (relativePitch):
- octave = getRelativePitch(lastPitch, pitch)
- lastPitch = pitch
- else:
- octave = note[1:]
- pitch = note[0] + accidental + octave
-
- # get Relative Duration
- if (relativeDuration):
- if (lastDuration==duration):
- duration = ''
- else:
- lastDuration = duration
-
- # check stems
- if insertSteming and (stem!= lastStem) :
- lastStem = stem
- stem = stems[stem]
- else :
- stem = ''
-
- # normal note
- if extra!='':
- extra = '-"' + extra + '"'
-
- if grace and not lastGrace: result += '\\' + stdGrace + " { "
-
- if nwcversion == 2.0:
- if ( slur == ' ' or slur == '' ) and lastSlur == '(' :
- slur = ')'
- lastSlur = ' '
- if debug : print ' slur closed'
- elif slur == '(' and lastSlur == '(' :
- slur = ' '
- lastSlur = '('
- if debug : print ' slur continued'
- elif slur == '(' and lastSlur == ' ' :
- slur = '('
- lastSlur = '('
- if debug : print ' slur opened'
-
- if dualVoice == 1: # no beams and slurs with two voices
- beam = ''
- slur = ''
-
- if not grace and lastGrace: result += " } "
- result += triplet[0] + stem + pitch + natural + duration + articulate + dynamic + style + extra
- result += slur + tie + beam + triplet[1] +chordMatters + " "
-
-
- # reset
- lastGrace = grace
- extra = ''
- dynamic = ''
- style = ''
- # rest
- elif data=='\x09':
- data = nwcData.read(10)
- if debug: print 'rest ', binascii.hexlify(data) , noteCount , nwcData.tell()
- (pitch, accidental, duration, stem, beam, triplet, slur, tie, grace, staccato, accent, tenuto) = getNote(data)
-
- restCount = restCount + 1
-
- # get Relative Duration
- duration = getDuration(data)
- if duration == '1':
- duration = lastTimesig
- if (relativeDuration):
- if (lastDuration==duration):
- duration = ''
- else:
- lastDuration = duration
-
- result = result + triplet[0] + 'r' + str(duration) + dynamic + triplet[1] + " "
- dynamic = ''
-
- #chord starting with a rest
- elif data == '\x12':
- data = nwcData.read(12) # rest info now in data
- print binascii.hexlify(data), "Chord started with rest. Rest skipped for now"
- (pitch, accidental, duration, stem, beam, triplet, slur, tie, grace, staccato, accent, tenuto) = getNote(data)
- result += "\n\t\t\\mark \\markup {r" + duration + "}\n\t\t%Todo: Above rest must be added to next chord\n\t\t"
- token -= 1 #doesn't count as token
-
- # chord
- elif data=='\x0A' : # or data=='\x12':
- if data == '\x0a' :
- data = nwcData.read(12)
-
- chordAmt = ord(data[10])
- chords = []
- chordDur = getDuration(data)
- #print 'duration',chordDur
-
- print binascii.hexlify(data), "Chord ", chordDur, chordAmt
- #print 'no. of notes in chord' , chordAmt
-
- chord1 = []
- chord2 = []
-
- for i in range(chordAmt):
- # rest or note
- what = nwcData.read(1)
- data = nwcData.read(10)
- ha = getNote(data)
-
- noteCount = noteCount + 1
- if debug: print 'chord ', binascii.hexlify(data) , noteCount , nwcData.tell(),
- (pitch, accidental, duration, stem, beam, triplet, slur, tie, grace, staccato, accent, tenuto) = ha
- if debug: print pitch, accidental, duration, stem, beam, triplet, slur, tie, grace, staccato, accent, tenuto
-
- # add to list
- if ha[2] == chordDur or len(chord2) > 0:
- chord1.append( (pitch,accidental ) )
- if beam==0 or beam ==4 :
- lastChord = 0
- else : # 2 voices
- chord2.append( (pitch,accidental, duration) )
- lastChord = durVal(duration)
-
- if len(chord2)==0: # block chord
- result += triplet[0] + ' <'
- for i in range(len(chord1)):
- (pitch,accidental ) = chord1[i]
- pitch += lastClef
- note = scale[pitch]
-
- natural = ''
-
- # get Accidentals
- if ( accidental == '!' ) : # also do forced naturals
- natural = accidental
- accidental = ''
-
- if (accidental!='auto'):
- currentKey[note[0]] = accidental
-
- accidental = currentKey[note[0]]
-
- if (relativePitch):
- octave = getRelativePitch(lastPitch, pitch)
- lastPitch = pitch
- else:
- octave = note[1:]
- result += note[0] + accidental + octave + natural + ' '
-
- if dualVoice == 1: # no beams and slurs with two voices
- slur = ''
-
- result += '>' + chordDur + slur + triplet[1] + ' ' # added slur (HdR)
- lastPitch = chord1[0][0] + lastClef
- else: # 2 voices Beware, this is experimental and not working good.
- dualVoice = 1
- result += ' << '
- for i in range(len(chord2)):
- (pitch,accidental,duration ) = chord2[i]
- pitch += lastClef
- note = scale[pitch]
-
- if ( accidental == '!' ) : # also do forced naturals
- natural = accidental
- accidental = ''
-
- if (accidental!='auto'):
- currentKey[note[0]] = accidental
-
- accidental = currentKey[note[0]]
-
- if (relativePitch):
- octave = getRelativePitch(lastPitch, pitch)
- lastPitch = pitch
- else:
- octave = note[1:]
- result += note[0] + accidental + octave + duration + ' ' #no slur, must be done manually
-
- result += " \\\\ {"
-
- for i in range(len(chord1)):
- (pitch,accidental ) = chord1[i]
- pitch += lastClef
- note = scale[pitch]
-
- if ( accidental == '!' ) : # also do forced naturals
- natural = accidental
- accidental = ''
-
- if (accidental!='auto'):
- currentKey[note[0]] = accidental
-
- accidental = currentKey[note[0]]
-
- if (relativePitch):
- octave = getRelativePitch(lastPitch, pitch)
- lastPitch = pitch
- else:
- octave = note[1:]
- result += note[0] + accidental + octave + chordDur + ' ' # no slur, must be done manually
- if lastChord >0 : lastChord = durVal(duration) - durVal(chordDur)
- if lastChord==0: result += ' } >> '
- # end 2 voices
- lastDuration = chordDur
- # check if duration / stem / same
- # < >duration ties,beam, slurs
-
- # Pedal
- elif data=='\x0b':
- data = data = nwcData.read(5)
- if data[4] == '\x01':
- style += '\\sustainDown'
- elif data[4] == '\x00':
- style += '\\sustainUp'
- if debug: print binascii.hexlify(data),
- if debug: print ' = Pedal, style: ', style
-
- # midi control instruction / MPC
- elif data=='\x0d':
- if debug: print 'midi control instruction',
- data = data = nwcData.read(36)
- if debug: print binascii.hexlify(data)
-
- # fermata / Breath mark
- elif data=='\x0e':
- data = nwcData.read(6)
- style += getTempoVariance( data[4] )
- if debug: print binascii.hexlify(data),
- if debug: print " = tempo variance, style: ", style
-
- # Dynamic variance
- elif data=='\x0f':
- data = nwcData.read(5)
- style += getDynVariance( data[4] )
- if debug: print binascii.hexlify(data),
- if debug: print " = Dynamic variance, style: ", style
-
- # Performance Style
- elif data=='\x10':
- data = nwcData.read(5)
- style += getPerfStyle( data[4] )
- if debug: print binascii.hexlify(data),
- if debug: print " = Performance Style, style: ", style
-
- # text
- elif data=='\x11':
- textCount = textCount + 1
- data = nwcData.read(2) #pad
- textpos = nwcData.read(1)
- data = nwcData.read(2) #pad
- text = ''
- data = nwcData.read(1)
- while data!='\x00':
- text += data
- data = nwcData.read(1)
-
- #if text.isdigit() : # check numbers
- # text = "-\\markup -\\number "+ text
- # #text = "-\\markup {\\number "+ text +"}"
- #else :
- # text = '-"' + text + '"'
-
- if text == 'tr':
- style += '\\trill'
- else :
- if insertText :
- extra += ' ' + text
-
-
- # todo
- else :
- print "WARNING: Unrecognised token ",binascii.hexlify(data), " at #", nwcData.tell(), " at Token", token
-
-
-
- # output converted file?
- print "\nStats"
- print keysigCount, " keysigCount found"
- print noteCount, " notes found"
- print staffCount, " staffCount found"
- print clefCount, " clefCount found"
- print barlineCount, " barlineCount found"
- print timesigCount, " timesigCount found"
- print textCount, " textCount found"
- print tempoCount, " tempoCount found"
- print dynamicCount, " dynamicCount found"
- print restCount, " restCount found"
-
- return result
-
-# Variables
-keysigs = {
-'00000000' : 'c \major % or a \minor' ,
-'00000020' : 'g \major % or e \minor' ,
-'00000024': 'd \major % or b \minor' ,
-'00000064' : 'a \major % or fis \minor' ,
-'0000006c' : 'e \major % or cis \minor' ,
-'0000006d' : 'b \major % or gis \minor' ,
-'0000007d' : 'fis \major % or dis \minor' ,
-'0000007f' : 'cis \major % or ais \minor' ,
-'00020000' : 'f \major % or d \minor' ,
-'00120000' : 'bes \major % or g \minor' ,
-'00130000' : 'ees \major % or c \minor' ,
-'001b0000' : 'aes \major % or f \minor' ,
-'005b0000' : 'des \major % or bes \minor' ,
-'005f0000' : 'ges \major % or ees \minor' ,
-'007f0000' : 'ces \major % or a \minor'
-}
-
-acdts = ( 'is', 'es', '!' ,'isis', 'eses', 'auto' )
-
-dynVariance = ( '\\<', # crescendo
- '\\>', # decrescendo
- '\\>', # diminuendo
- '\\rfz', # rinforzando
- '\\sfz' # sforzando
- )
-
-perfStyle = ( 'ad lib.',
- 'animato',
- 'cantabile',
- 'con brio',
- 'dolce',
- 'espressivo',
- 'grazioso',
- 'legato',
- 'maestoso',
- 'marcato',
- 'meno mosso',
- 'poco a poco',
- 'piu mosso',
- 'semplice',
- 'simile',
- 'solo',
- 'sostenuto',
- 'sotto voce',
- 'staccato',
- 'subito',
- 'tenuto',
- 'tutti',
- 'volta subito'
- )
-
-clefs = { 0 : "b'",
- 1 : "d",
- 2 : "c'",
- 3 : "a'",
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+"""Convert NoteWorthy Composer's `nwc` to LilyPond's `ly` fileformat.
+
+Most infomation obtained about the `nwc` format is by using
+noteworthycomposer and the somewhat like the `French cafe method`_.
+
+.. _French cafe method: http://samba.org/ftp/tridge/misc/french_cafe.txt
+
+Revisions:
+
+* 0.1
+
+ * 07 april 2005. Initial hex parsing
+
+* 0.2
+
+ * 13 april 2005. Added multiple staff, keysig, dots, durations
+
+* 0.3
+
+ * 14 april 2005. Clef, key sig detection, absolute notes pitching
+
+* 0.4
+
+ * 15 April 2005. Relative pitchs, durations, accidentals, stem
+ up/down, beam, tie
+
+* 0.5
+
+ * 16 April 2005. Bug fixes, generate ly score, write to file, time
+ signature, triplets, experimental chords
+
+* 0.6
+
+ * 17 April 2005. Compressed NWC file supported!
+
+* 0.7
+
+ * 19 April 2005. Version detection, header
+ * 20 April 2005. Bug fixes, small syntax changes
+ * 21 April 2005. Still fixing aimlessly
+ * 23 April 2005. Chords improvement
+ * 24 April 2005. Staccato, accent, tenuto. dynamics, midi detection
+ (but unsupported)
+
+* 0.8
+
+ * 24 April 2005. Experimental lyrics support
+
+* 0.9
+
+ * 29 April 2005. Workround for ``\acciaccatura``, simple check full
+ bar rest adjustment
+
- * 0.10
++* 1.0
++
++ * 10 July 2005 Hans de Rijck,
++
++ * added dynamics, dynamic variance, tempo, tempo variance
++ * performance style, page layout properties
++ * corrected barlines, lyrics, slurs, natural accidentals
++ * using inifile for settings
++ * limited support for NWC version 2.0
++
++* 1.1
+
+ * 30 November 2010. Cleanup to more Pythonic syntax.
++ * 01 December 2010. Merged fields & enums from Hans' 1.0 version.
+
+TODO:
+
+* http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Line-breaking
+"""
+
+import logging
+import pprint
+import re
+import struct
+import sys
+import zlib
+
+
- __version__ = '0.10'
++__version__ = '1.1'
+
+LOG = logging
+
+
+# define useful structures and tools to make parsing easier
+
+def _decode_string(string):
+ """Decode NWC text from ISO-8859-1 to Unicode.
+
+ Based on the contents of a few example files, NWC uses
+ `ISO-8859-1`_ to encode text with the private use character
+ `\\x92` representing the right single quote (see `ISO-6492`_).
+
+ .. _ISO-8859-1: http://en.wikipedia.org/wiki/ISO/IEC_8859
+ .. _ISO-6492: http://en.wikipedia.org/wiki/ISO_6429
+
+ >>> _decode_string('Someone\\x92s \\xc9tude in \\xa9')
+ u'Someone\u2019s \\xc9tude in \\xa9'
+ """
+ string = unicode(string, 'iso-8859-1')
+ string = string.replace(r'\r\n', '\n')
+ return string.replace(u'\x92', u'\u2019')
+
+
+class Enum (object):
+ """An enumerated type (or any type requiring post-processing).
+
+ >>> class E (Enum):
+ ... _list = ['A', 'B', 'C']
+ >>> E.process(1)
+ 'B'
+ """
+ type = 'B'
+ _list = []
+
+ @classmethod
+ def process(self, value):
+ try:
+ return self._list[value]
+ except IndexError: # if _list is a list
+ raise ValueError('index %d out of range for %s'
+ % (value, self._list))
+ except KeyError: # if _list is a dict
+ raise ValueError('key %s not in %s'
+ % (value, self._list))
+
+
+class BoolEnum (Enum):
+ _list = [False, True]
+
+
+class Pow2Enum (Enum):
+ _list = [1, 2, 4, 8, 16, 32, 64]
+
+
+class BitField (list):
+ """Allow clean access into bit fields.
+
+ Initialize with a sequence of `(name, bit_size)` pairs.
+
+ When packing into a `Structure`, the type should be set to one of
+ the standard unsigned integer type characters. The total bit size
+ must match the size of the type specifier.
+
+ If you want, you may add an `Enum` subclass as a third tuple
+ element `(name, bit_size, enum)` to postprocess that bit field
+ value. The first bit fields will recieve the most significant
+ bits.
+
+ >>> b = BitField('B', ('a', 4), ('b', 3), ('c', 1, BoolEnum))
+ >>> a = ord('\\x2f')
+ >>> bin(a)
+ '0b101111'
+ >>> b.parse('B', a)
+ [('a', 2), ('b', 7), ('c', True)]
+
+ `BitField` instances will raise exceptions if you try to parse the
+ wrong type.
+
+ >>> b.parse('C', a)
+ Traceback (most recent call last):
+ ...
+ AssertionError: C
+
+ Or if the type bit size doesn't match the field total.
+
+ >>> b = BitField('B', ('a', 4), ('b', 3))
+ Traceback (most recent call last):
+ ...
+ AssertionError: only assigned 7 of 8 bits
+ """
+ _uint_types = ['B', 'H', 'I', 'L', 'Q']
+
+ def __init__(self, type, *fields):
+ assert type in self._uint_types, type
+ self._type = type
+ self._fields = list(fields)
+ field_bits = 0
+ for i,field in enumerate(self._fields):
+ if len(field) == 2:
+ self._fields[i] = (field[0], field[1], None)
+ field_bits += field[1]
+ num_bytes = self._uint_types.index(type) + 1
+ self._num_bits = 8 * num_bytes
+ self._max_value = (1 << self._num_bits) - 1
+ assert field_bits == self._num_bits, (
+ 'only assigned %d of %d bits' % (field_bits, self._num_bits))
+
+ def parse(self, type, value):
+ assert type == self._type, type
+ assert value >= 0, value
+ assert value <= self._max_value, value
+ results = []
+ top_offset = self._num_bits
+ for name,bits,enum in self._fields:
+ bottom_offset = top_offset - bits
+ mask = (1 << bits) - 1
+ v = (value >> bottom_offset) & mask
+ if enum:
+ v = enum.process(v)
+ results.append((name, v))
+ top_offset -= bits
+ assert top_offset == 0, top_offset
+ return results
+
+
+class Structure (dict):
+ """Extend `struct.Struct` to support additional types.
+
+ Additional types:
+
+ ==== ========================
+ `S` null-terminated string
+ - `Enum` instance
+ - nested `Structure` class
+ ==== ========================
+
+ You can also use `BitField` instances as names.
+
+ >>> class E (Enum):
+ ... _list = ['A', 'B', 'C', 'D', 'E', 'F']
+ >>> class S (Structure):
+ ... _fields = [(BitField('B',
+ ... ('bf1', 4),
+ ... ('bf2', 3),
+ ... ('bf3', 1, BoolEnum)), 'B'),
+ ... ('string1', 'S'), ('uint16', 'H'),
+ ... ('string3', 'S'), ('enum1', E)]
+ >>> buffer = '\\x2fHello\\x00\\x02\\x03World\\x00\\x04ABC'
+ >>> s = S(buffer)
+ >>> for n,t in s._fields:
+ ... if isinstance(n, BitField):
+ ... for n2,bits,enum in n._fields:
+ ... print n2, s[n2]
+ ... else:
+ ... print n, s[n]
+ ... # doctest: +REPORT_UDIFF
+ bf1 2
+ bf2 7
+ bf3 True
+ string1 Hello
+ uint16 515
+ string3 World
+ enum1 E
+ >>> s.size
+ 16
+ >>> buffer[s.size:]
+ 'ABC'
+
+ >>> class N (Structure):
+ ... _fields = [('string1', 'S'), ('s', S), ('string2', 'S')]
+ >>> n_buffer = 'Fun\\x00%sDEF\\x00GHI' % buffer
+ >>> n = N(n_buffer)
+ >>> pprint.pprint(n)
+ {'s': {'bf1': 2,
+ 'bf2': 7,
+ 'bf3': True,
+ 'enum1': 'E',
+ 'string1': u'Hello',
+ 'string3': u'World',
+ 'uint16': 515},
+ 'string1': u'Fun',
+ 'string2': u'ABCDEF'}
+ >>> n.size
+ 27
+ >>> n_buffer[n.size:]
+ 'GHI'
+
+ Note that `ctypes.Structure` is similar to `struct.Struct`, but I
+ found it more difficult to work with. Neither one supports the
+ variable-length, null-terminated strings we need.
+ """
+ _byte_order = '>' # big-endian
+ _string_decoder = staticmethod(_decode_string)
+ _fields = [] # sequence of (name, type) pairs
+
+ def __init__(self, buffer=None, offset=0):
+ super(Structure, self).__init__()
+ self._parsers = []
+ self._post_processors = []
+ f = []
+ for name,_type in self._fields:
+ if self._is_subclass(_type, Enum):
+ _type = _type.type
+ if self._is_subclass(_type, Structure) or _type in ['S']:
+ if f:
+ self._parsers.append(
+ self._create_struct_parser(''.join(f)))
+ f = []
+ if self._is_subclass(_type, Structure):
+ self._parsers.append(_type)
+ elif _type == 'S':
+ self._parsers.append(self._string_parser)
+ else:
+ f.append(_type)
+ if f:
+ self._parsers.append(self._create_struct_parser(''.join(f)))
+
+ if buffer:
+ self.unpack_from(buffer, offset)
+
+ def _is_subclass(self, obj, _class):
+ """
+ >>> s = Structure()
+ >>> s._is_subclass('c', Structure)
+ False
+ >>> s._is_subclass(Structure, Structure)
+ True
+ """
+ try:
+ return issubclass(obj, _class)
+ except TypeError:
+ return False
+
+ def _create_struct_parser(self, format):
+ LOG.debug('%s: initialize struct parser for %s%s'
+ % (self.__class__.__name__, self._byte_order, format))
+ return struct.Struct('%s%s' % (self._byte_order, format))
+
+ def _string_parser(self, buffer, offset=0):
+ size = buffer[offset:].find('\x00')
+ string = buffer[offset:offset+size]
+ if self._string_decoder:
+ string = self._string_decoder(string)
+ return ((string,), size+1)
+
+ def unpack_from(self, buffer, offset=0):
+ self._results = []
+ self.size = 0
+ for parser in self._parsers:
+ if self._is_subclass(parser, Structure):
+ parser = parser()
+ if hasattr(parser, 'unpack_from'):
+ results = parser.unpack_from(buffer, offset)
+ size = parser.size
+ else:
+ results,size = parser(buffer, offset)
+ self._results.extend(results)
+ self.size += size
+ offset += size
+ self._results = tuple(self._results)
+ for i,result in enumerate(self._results):
+ name,_type = self._fields[i]
+ if self._is_subclass(_type, Enum):
+ result = _type.process(result)
+ if isinstance(name, BitField):
+ for n,r in name.parse(_type, result):
+ LOG.debug("%s['%s'] = %s" % (
+ self.__class__.__name__, n, repr(r)))
+ self[n] = r
+ else:
+ LOG.debug("%s['%s'] = %s" % (
+ self.__class__.__name__, name, repr(result)))
+ self[name] = result
+ return (self,)
+
+
+# define the `nwc` file format
+
+
+class NWCSongInfo (Structure):
+ _fields = [
+ ('title', 'S'),
+ ('author', 'S'),
+ #('lyricist, 'S'), # later versions? In example/2.0-1.nwctxt
+ ('copyright1', 'S'),
+ ('copyright2', 'S'),
+ ('comments', 'S'),
+ ]
+
+ def ly_text(self):
+ """
+ http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Creating-titles#Creating-titles
+ """
+ lines = [r'\header {']
+ for element,field in [('title', 'title'), ('composer', 'author'),
+ ('copyright', 'copyright1')]:
+ if self[field]:
+ lines.append('\t%s = "%s"' % (element, self[field]))
+ if self['copyright2']:
+ lines.append('\t%%{ %s %%}' % self['copyright2'])
+ if self['comments']:
+ lines.append('\t%%{ %s %%}' % self['comments'])
+ lines.append('}')
+ return '\n'.join(lines)
+
+
+class NWCFontStyleEnum (Enum):
+ _list = ('regular', 'italic', 'bold', 'bold_italic')
+
+
+class NWCTypefaceEnum (Enum):
+ _list = {
+ 0: 'western',
+ 1: 'unknown',
+ 177: 'hebrew',}
+
+
+class NWCFont (Structure):
+ _fields = [
+ ('name', 'S'),
+ (BitField('B',
+ ('unknown2', 6),
+ ('style', 2, NWCFontStyleEnum)),
+ 'B'),
+ ('size', 'B'),
+ ('unknown3', 'B'),
+ ('typeface', NWCTypefaceEnum),
+ ]
+
+
+class NWCPageSetup (Structure):
+ _fields = [
+ ('ny_', 'S'),
+ ('f2', 'S'),
+ ('unknown01', 'B'),
+ ('unknown02', 'B'),
- ('unknown03', 'B'),
++ ('measure_start', 'B'),
+ ('unknown04', 'B'),
+ ('margins', 'S'),
+ ('unknown05', 'B'),
+ ('unknown06', 'B'),
+ ('unknown07', 'B'),
+ ('unknown08', 'B'),
+ ('unknown09', 'B'),
+ ('unknown10', 'B'),
+ ('unknown11', 'B'),
+ ('unknown12', 'B'),
+ ('unknown13', 'B'),
+ ('unknown14', 'B'),
+ ('unknown15', 'B'),
+ ('unknown16', 'B'),
+ ('unknown17', 'B'),
+ ('unknown18', 'B'),
+ ('unknown19', 'B'),
+ ('unknown20', 'B'),
+ ('unknown21', 'B'),
+ ('unknown22', 'B'),
+ ('unknown23', 'B'),
+ ('unknown24', 'B'),
+ ('unknown25', 'B'),
+ ('unknown26', 'B'),
+ ('unknown27', 'B'),
+ ('unknown28', 'B'),
+ ('unknown29', 'B'),
+ ('unknown30', 'B'),
+ ('unknown31', 'B'),
+ ('unknown32', 'B'),
+ ('unknown33', 'B'),
+ ('unknown34', 'B'),
+ ('unknown35', 'B'),
+ ('unknown36', 'B'),
+ ('unknown37', 'B'),
+ ('unknown38', 'B'),
+ ('unknown39', 'B'),
+ ('unknown40', 'B'),
- ('unknown41', 'B'),
++ ('staff_size', 'B'),
+ ('unknown42', 'B'),
+ ('staff_italic', NWCFont),
+ ('staff_bold', NWCFont),
+ ('staff_lyric', NWCFont),
+ ('page_title', NWCFont),
+ ('page_text', NWCFont),
+ ('page_small', NWCFont),
+ ('user1', NWCFont),
+ ('user2', NWCFont),
+ ('user3', NWCFont),
+ ('user4', NWCFont),
+ ('user5', NWCFont),
+ ('user6', NWCFont),
+ ]
+
+
+class NWCFileHead (Structure):
+ _fields = [
+ ('type', 'S'),
+ ('unknown01', 'B'),
+ ('unknown02', 'B'),
+ ('product', 'S'),
+ ('minor_version', 'B'), ('major_version', 'B'),
+ ('unknown03', 'B'),
+ ('unknown04', 'B'), #('num_saves', 'B'), ???
+ ('unknown05', 'B'),
+ ('unknown06', 'B'),
+ ('na', 'S'), # what does this mean?
+ ('name1', 'S'), # what does this mean?
+ ('unknown07', 'B'),
+ ('unknown08', 'B'),
+ ('unknown09', 'B'),
+ ('unknown10', 'B'),
+ ('unknown11', 'B'),
+ ('unknown12', 'B'),
+ ('unknown13', 'B'),
+ ('unknown14', 'B'),
+ ('unknown15', 'B'),
+ ('unknown16', 'B'),
+ ('song_info', NWCSongInfo),
+ ('page_setup', NWCPageSetup),
+ ]
+
+
+class NWCStaffSet (Structure):
+ _fields = [
+ ('ff', 'B'),
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ ('unknown3', 'B'),
+ ('num_staves', 'B'),
+ ('unknown5', 'B'),
+ ]
+
+
+class FinalBarEnum (Enum):
+ _list = ('section_close', 'master_repeat_close', 'single', 'double',
+ 'invisible')
+
+
+class StaffTypeEnum (Enum):
+ _list = ('standard', 'upper_grand', 'lower_grand', 'orchestra')
+
+
+class NWCStaff (Structure):
+ _fields = [
+ ('name', 'S'),
+ ('group', 'S'),
+ (BitField('B',
+ ('unknown01', 5),
+ ('final_bar', 3, FinalBarEnum)),
+ 'B'),
+ (BitField('B',
+ ('unknown02', 7),
+ ('muted', 1, BoolEnum)),
+ 'B'),
+ ('unknown01', 'B'),
+ ('playback_channel', 'B'), #+1
+ ('unknown02', 'B'),
+ ('unknown03', 'B'),
+ ('unknown04', 'B'),
+ ('unknown05', 'B'),
+ ('unknown06', 'B'),
+ ('unknown07', 'B'),
+ ('unknown08', 'B'),
+ ('unknown09', 'B'),
+ ('unknown10', 'B'),
+ (BitField('B',
+ ('unknown11', 6),
+ ('type', 2, StaffTypeEnum)),
+ 'B'),
+ ('unknown11', 'B'),
+ ('vertical_size_upper', 'B'), # signed? 256 - ord(nwcData.read(1)) # - signed +1 )& 2^7-1 )
+
+ #... to ff
+
+ ('vertical_size_lower', 'B'),
+ ('unknown12', 'B'), # ww?
+ ('line_count', 'B'),
+ ('layer', 'B'), # &1
+ ('unknown12', 'B'),
+ ('unknown13', 'B'),
+ ('unknown14', 'B'),
+ ('unknown15', 'B'),
+ ('part_volume', 'B'),
+ ('unknown16', 'B'),
+ ('stereo_pan', 'B'),
+ ('unknown17', 'B'),
+ ('unknown18', 'B'),
+ ('has_lyrics', 'B'),
+ ('num_lyrics', 'H'),
+
+
+ #('visible', 'B'),
+ #('boundary_top', 'B'),
+ #('boundary_bottom', 'B'),
+ #('lines', 'H'),
+ #('style', 'B'),
+ #('color', 'B'),
+ #('playback_device', 'B'),
+ #('transposition', 'B'),
+ #('dynamic_velocity', 'S'),
+ #('patch_name', 'B'),
+ #('patch_list_type', 'B'),
+ #('bank_select', 'B'),
+ #('controller0', 'B'),
+ #('controller32', 'B'),
+ #('align_syllable_rule', 'B'),
+ #('staff_alignment', 'B'),
+ #('staff_offset', 'B'),
+ ]
+
+ def _initialize_bar_items(self):
+ return {
+ 'bar_comment_interval': 5, # HACK
+ 'clef': None,
+ 'key_sig': None,
+ 'time_sig': None,
+ 'last_bar': None,
+ 'bar_index': 0,
+ 'bar': [],
+ 'bar_beats': 0,
+ 'previous_note': None,
++ 'note_qualifiers': [],
+ }
+
+ def _add_bar_token(self, bar_items, token, token_index):
+ next_note = None
+ for t in self['tokens'][token_index+1:]:
+ if isinstance(t, (NWCToken_note,
- NWCToken_chord1)):
++ NWCToken_chord)):
+ next_note = t
+ break
- if isinstance(token, (NWCToken_text)):
++ if isinstance(token, (NWCToken_dynamics,
++ NWCToken_dynamic_variance)):
++ bar_items['note_qualifiers'].append(token.ly_text())
++ return
++ if isinstance(token, (NWCToken_pedal,
++ NWCToken_text)):
+ bar_items['bar'].append(token.ly_text())
+ return
+ bar_items['bar'].append(token.ly_text(
+ clef=bar_items['clef'],
+ key=bar_items['key_sig'],
+ previous_note=bar_items['previous_note'],
- next_note=next_note))
++ next_note=next_note,
++ note_qualifiers=bar_items['note_qualifiers']))
+ bar_items['bar_beats'] += _nwc_duration_value(token)
+ bar_items['previous_note'] = token
+
+ def _format_bar(self, bar_items, bar_token=None):
+ """
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Writing-rhythms#Durations
+ """
+ if bar_token:
+ bar_items['bar'].append(bar_token.ly_text())
+ elif bar_items['bar']:
+ b = NWCToken_bar()
+ bar_items['bar'].append(
+ b.ly_text(self['final_bar']))
+ else: # empty final bar
+ return ''
+ if bar_items['time_sig']:
+ expected_bar_beats = bar_items['time_sig'].expected_bar_beats()
+ else:
+ expected_bar_beats = 1.0
+ LOG.warn('no time signature given for bar %d, guessing 4/4'
+ % bar_items['bar_index']+1)
+ bar_fraction = float(bar_items['bar_beats'])/expected_bar_beats
+ assert bar_fraction < 1.01, (
+ 'too many beats (%f > %f) in bar %d' %
+ (bar_items['bar_beats'], expected_bar_beats,
+ bar_items['bar_index']+1))
+ if bar_fraction < 0.99:
+ bar_items['bar'].insert(0, self._partial(
+ bar_items, expected_bar_beats, bar_fraction))
+ line = '\t%s' % ' '.join(bar_items['bar'])
+ bar_items['last_bar'] = bar_items['bar']
+ bar_items['bar'] = []
+ bar_items['bar_index'] += 1
+ bar_items['bar_beats'] = 0
+ bar_items['previous_note'] = None
++ # bar_items['note_qualifiers'] = [] # fall through to next bar
+ if bar_items['bar_index'] % bar_items['bar_comment_interval'] == 0:
+ line += (' %% bar %d' % bar_items['bar_index'])
+ return line
+
+ def _partial(self, bar_items, expected_bar_beats, bar_fraction):
+ """
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Displaying-rhythms#Upbeats
+ """
+ remaining_fraction = 1 - bar_fraction
+ divisor = 128
+ numerator = round(int(bar_fraction * expected_bar_beats * divisor))
+ while numerator % 2 == 0 and divisor >= 1:
+ numerator /= 2
+ divisor /= 2
+ LOG.debug('partial bar, %f < %f, %d/%d remaining' % (
+ bar_items['bar_beats'], expected_bar_beats,
+ numerator, divisor))
+ if numerator == 1:
+ duration = str(divisor)
+ else:
+ duration = '%d*%d' % (divisor, numerator)
+ return r'\partial %s' % duration
+
+ def ly_lines(self, voice=None):
+ """
+ http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Multiple-staves#index-_005cnew-Staff
+ """
+
+ lines = [
+ '%% %s' % self['name'],
+ r'\new Staff',
+ ]
+
+ #if relative_pitch:
+ # result+="\\relative c {"
+ #else :
+ lines.append('{')
+
+ if voice:
+ lines[-1] = (r'\new Voice = "%s" ' % voice) + lines[-1]
+
+ LOG.info('format staff %s (%s)' % (self['name'], voice))
+ lines.extend([
+ '\t\\autoBeamOff',
+ ])
+ bar_items = self._initialize_bar_items()
+ for i,token in enumerate(self['tokens']):
+ LOG.info('format token %s' % type(token))
+ if isinstance(token, (NWCToken_clef,
+ NWCToken_key_sig,
+ NWCToken_time_sig,
+ NWCToken_tempo)):
+ if isinstance(token, NWCToken_clef):
+ bar_items['clef'] = token
+ elif isinstance(token, NWCToken_key_sig):
+ bar_items['key_sig'] = token
+ elif isinstance(token, NWCToken_time_sig):
+ bar_items['time_sig'] = token
+ lines.append('\t%s' % token.ly_text())
+ elif isinstance(token, NWCToken_bar):
+ lines.append(self._format_bar(bar_items, token))
- elif isinstance(token, (NWCToken_note, # rests are note instances
- NWCToken_chord1,
++ elif isinstance(token, (NWCToken_dynamics,
++ NWCToken_note, # rests are note instances
++ NWCToken_chord,
++ NWCToken_pedal,
+ NWCToken_text)):
+ self._add_bar_token(bar_items, token, i)
+ elif isinstance(token, NWCToken_fermata):
+ if bar_items['bar']:
+ bar_items['bar'].append(token.ly_text())
+ else: # replace the previous \bar with a bar-fermata
+ # http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Writing-text#Text-marks
+ # except without an auto-generated bar, no line
+ # shows up. If we put the fermata before the bar,
+ # it still ends up over the clef on the next line.
+ # See bars 124 and 125 of example/1.75-2.nwc for
+ # an example.
+ bar_items['last_bar'].insert(
+ -1,
+ r'\mark \markup { \musicglyph #"scripts.ufermata" }')
+ lines[-1] = ('\t%s' % ' '.join(bar_items['last_bar']))
+ lines.append(self._format_bar(bar_items))
+
+ lines.append('}')
+
+ for i,lyric_block in enumerate(self['lyrics']):
+ # http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Entering-lyrics
+ # http://kainhofer.com/~lilypond/ajax/user/lilypond/Stanzas.html
+ assert voice != None, 'lyrics must be attached to a named voice'
+ lines.append(r'\new Lyrics \lyricsto "%s" {' % voice)
+ first_line = lyric_block[0]
+ first_word = first_line[0]
+ if first_word[0].isdigit():
+ stanza,words = first_word.split(' ', 1)
+ first_line[0] = words
+ lines.append('\t\\set stanza = #"%s "' % stanza)
+ for line in lyric_block:
+ L = []
+ for word in line:
+ if not word.isalpha():
+ word = '"%s"' % word
+ L.append(word)
+ lines.append('\t%s' % ' '.join(L))
+ lines.append('}')
+
+ return lines
+
+
+class NWCLyricProperties (Structure):
+ _fields = []
+ for i in range(6):
+ _fields.append(('unknown%d' % (i+1), 'B'))
+
+
+class NWCLyricBlockProperties (Structure):
+ #'\x00\x00\x00\x00\x00'
+ #'\x00\x04\x9f'\x00\x00\x01
+ _fields = [
+ ('num_blocks', 'B'),
+ ('len', 'B'),
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ ('unknown3', 'B'),
+ ]
+
+class NWCLyric (Structure):
+ _fields = [
+ ('lyric', 'S'),
+ ]
+
+
+class NWCStaffNoteProperties (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('color', 'B'),
+ ('num_tokens', 'H'), # ?
+ ]
+ for i in range(1):
+ _fields.append(('unknown%d' % (i+1), 'B'))
+
+
+
+# tokens
+
+
+class NWCTokenEnum (Enum):
- _list = ['clef', 'key_sig', 'bar', '3', 'instrument_patch', 'time_sig',
- 'tempo', 'dynamics1', 'note', 'rest', 'chord1', 'pedal', 12,
- 'midi_MPC', 'fermata', 'dynamics2', 'performance_style', 'text',
- 'chord2']
++ _list = ['clef', 'key_sig', 'bar', 'repeat', 'instrument_patch',
++ 'time_sig', 'tempo', 'dynamics', 'note', 'rest', 'chord',
++ 'pedal', 12, 'midi_MPC', 'fermata', 'dynamic_variance',
++ 'performance_style', 'text', 'rest_chord']
+
+ @classmethod
+ def process(self, value):
+ if value > len(self._list):
+ return 'STOP'
+ try:
+ return self._list[value]
+ except IndexError:
+ raise ValueError('index %d out of range for %s'
+ % (value, self._list))
+
+
+class NWCToken (Structure):
+ _fields = [
+ ('token', NWCTokenEnum),
+ ]
+
+
+class NWCClefEnum (Enum):
+ _list = ['treble', 'bass', 'alto', 'tenor']
+
+
+class NWCOctaveEnum (Enum):
- _list = [0, 7, -7]
++ _list = ['standard', 'transpose_up', 'transpose_down']
+
+
+class NWCToken_clef (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ (BitField('B',
+ ('unknown3', 6),
+ ('clef', 2, NWCClefEnum)),
+ 'B'),
+ (BitField('B',
+ ('unknown4', 6),
+ ('octave', 2, NWCOctaveEnum)),
+ 'B'),
+ ('unknown4', 'B'),
+ ('unknown5', 'B'),
+ ]
+
++ _octaves = {
++ 'standard': '',
++ 'transpose_up': '^8',
++ 'transpose_down': '_8',
+ }
+
-clefNames = { 0: 'treble',
- 1: 'bass',
- 2: 'alto',
- 3: 'tenor',
+ def ly_text(self):
+ """
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Displaying-pitches#Clef
+ """
- return r'\clef %s' % self['clef']
++
++ clef = '%s%s' % (self['clef'], self._octaves[self['octave']])
++ if not clef.isalpha():
++ clef = '"%s"' % clef
++ return r'\clef %s' % clef
+
+
+class NWCToken_key_sig (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('flat_bits', 'H'),
+ ('sharp_bits', 'H'),
+ ('unknown2', 'B'),
+ ('unknown3', 'B'),
+ ('unknown4', 'B'),
+ ('unknown5', 'B'),
+ ('unknown6', 'B'),
+ ('unknown7', 'B'),
+ ('unknown8', 'B'),
+ ]
+ _sigs = {
+ '00000000': 'c \major % or a \minor',
+ '00000020': 'g \major % or e \minor',
+ '00000024': 'd \major % or b \minor',
+ '00000064': 'a \major % or fis \minor',
+ '0000006c': 'e \major % or cis \minor',
+ '0000006d': 'b \major % or gis \minor',
+ '0000007d': 'fis \major % or dis \minor',
+ '0000007f': 'cis \major % or ais \minor',
+ '00020000': 'f \major % or d \minor',
+ '00120000': 'bes \major % or g \minor',
+ '00130000': 'ees \major % or c \minor',
+ '001b0000': 'aes \major % or f \minor',
+ '005b0000': 'des \major % or bes \minor',
+ '005f0000': 'ges \major % or ees \minor',
+ '007f0000': 'ces \major % or a \minor',
+ }
+
+ def ly_text(self):
+ """
+ http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Accidentals-and-key-signatures#index-_005ckey
+ """
+ sig = '%04x%04x' % (self['flat_bits'], self['sharp_bits'])
+ return '\key %s' % self._sigs[sig]
+
+ def accidental(self, pitch):
+ """Return the appropriate accidental for an in-key note.
+ """
+ index = ord(pitch[0].lower()) - ord('a')
+ if self['flat_bits'] >> index & 1:
+ return 'flat'
+ elif self['sharp_bits'] >> index & 1:
+ return 'sharp'
+ return 'natural'
+
+
++class NWCBarStyleEnum (Enum):
++ _list = ('single', 'double', 'section_open', 'section_close',
++ 'master_repeat_open', 'master_repeat_close',
++ 'local_repeat_open', 'local_repeat_close')
++
++
+class NWCToken_bar (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
- ('unknown3', 'B'),
++ ('type', NWCBarStyleEnum),
+ ('unknown4', 'B'),
+ ]
+
+ _bars = {
+ 'invisible': '',
+ 'single': '|',
+ 'double': '||',
+ 'section_open': '.|',
+ 'section_close': '|.',
+ # use \repeat for repeats
+ }
+
+ def ly_text(self, type=None):
+ """
+ http://www.noteworthysoftware.com/composer/faq/102.htm
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Bars#Bar-lines
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Repeats
+ """
+ if type == None:
- type = 'single' # TODO: detect bar type
++ type = self['type']
+ return r'\bar "%s"' % self._bars[type]
- #|Bar|Style:SectionClose|SysBreak:Y
++ # TODO: repeats via \repeat
+
+
- class NWCToken_3 (Structure):
++class NWCToken_repeat (Structure):
+ _fields = []
+ for i in range(4):
+ _fields.append(('unknown%d' % (i+1), 'B'))
+
++ # TODO: place alternatives for r'\repeat volta %d' % self['unknown3']
++
+
+class NWCToken_instrument_patch (Structure):
+ _fields = []
+ for i in range(10):
+ _fields.append(('unknown%d' % (i+1), 'B'))
+
+ def ly_text(self):
+ """
+ http://kainhofer.com/~lilypond/ajax/user/lilypond/Creating-MIDI-files.html#index-instrument-names-1
+ http://kainhofer.com/~lilypond/ajax/user/lilypond/MIDI-instruments.html#MIDI-instruments
+ """
+ return r'\set Staff.midiInstrument = #"%s"' % 'cello'
+
+
+class NWCToken_time_sig (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ ('beats', 'B'),
+ ('unknown3', 'B'),
+ ('beat_value', Pow2Enum),
+ ('unknown6', 'B'),
+ ('unknown7', 'B'),
+ ('unknown8', 'B'),
+ ]
+
+ def ly_text(self):
+ """
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Displaying-rhythms#Time-signature
+ """
+ return r'\time %s/%s' % (self['beats'], self['beat_value'])
+
+ def expected_bar_beats(self):
+ return float(self['beats'])/self['beat_value']
+
+
++class NWCTempoDurationEnum (Enum):
++ _list = ('8', '8.', '4', '4.', '2', '2.')
++
++
+class NWCToken_tempo (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ ('unknown3', 'B'),
+ ('unknown4', 'B'),
- ('beats_per_minute', 'B'),
++ ('notes_per_minute', 'B'),
+ ('unknown6', 'B'),
- ('unknown7', 'B'),
++ ('note_duration', NWCTempoDurationEnum),
+ ('text', 'S'),
+ ]
+
- def ly_text(self, time_sig=None):
++ _multipliers = {
++ '8': 2, '8.': 4./3, '4': 1, '4.': 2./3, '2': 0.5, '2.': 1./3}
++
++ def ly_text(self):
+ """
+ http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Writing-parts#Metronome-marks
+ """
- if time_sig == None:
- beats_per_measure = 4
- else:
- beats_per_measure = time_sig['']
++ multiplier = self._multipliers[self['note_duration']]
+ return r'\tempo "%s" %s = %s' % (
- self['text'], beats_per_measure, self['beats_per_minute'])
++ self['text'], self['note_duration'],
++ int(self['notes_per_minute'] * multiplier))
+
+
- class NWCToken_dynamics1 (Structure):
++class NWCDynamicsEnum (Enum):
++ _list = ('ppp', 'pp', 'p', 'mp', 'mf', 'f', 'ff', 'fff')
++
++
++class NWCToken_dynamics (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ ('unknown3', 'B'),
+ ('unknown4', 'B'),
- ('unknown5', 'B'),
++ (BitField('B',
++ ('unknown5', 5),
++ ('type', 3, NWCDynamicsEnum)),
++ 'B'),
+ ('unknown6', 'B'),
+ ('unknown7', 'B'),
+ ('unknown8', 'B'),
+ ('unknown9', 'B'),
+ ]
+
++ def ly_text(self):
++ """
++ # http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Articulation-and-dynamics#Dynamics-1
++ """
++ return r'\%s' % self['type']
++
+
+class NWCAccidentalEnum (Enum):
+ """
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Writing-pitches#Accidentals
+ """
+ _list = ['sharp', 'flat', 'natural', 'double_sharp', 'double_flat', 'auto']
+
+
+def _nwc_duration_dot(struct):
+ if struct['dot']:
+ return '.'
+ elif struct['ddot']:
+ return '..'
+ return ''
+
+def _nwc_duration(struct):
+ return '%s%s' % (struct['duration'], _nwc_duration_dot(struct))
+
+def _nwc_duration_value(struct):
+ v = 1.0 / struct['duration']
+ if struct['grace']:
+ return 0
+ if struct['dot']:
+ v *= 1.5
+ if struct['ddot']:
+ v *= 1.75
+ if struct['triplet'] != 'none':
+ v *= 2./3
+ return v
+
+
+class NWCStemEnum (Enum):
+ _list = ('neutral', 'up', 'down')
+ _ly_text_list = ('\stemNeutral', '\stemUp', '\stemDown')
+
+
+class NWCTripletEnum (Enum):
+ _list = ('none', 'start', 'inside', 'stop')
+
+
+class NWCBeamEnum (Enum):
+ _list = ('none', 'start', 'inside', 'stop', 4, 5, 6, 7)
+
+
+class NWCSlurEnum (Enum):
+ _list = ('none', 'start' , 'stop', 'inside')
+
+
++class _NotePitch (object):
++ _clef_offsets = {
++ 'treble': "b'",
++ 'bass': 'd',
++ 'alto': "c'",
++ 'tenor': "a'",
+ }
-octaves = { 0: 0, 1:7, 2:-7 }
-scale = [ # this list is taken from lilycomp
++
++ _scale = ( # this list is taken from lilycomp
+ "c,,,","d,,,","e,,,","f,,,","g,,,","a,,,","b,,,",
+ "c,,","d,,","e,,","f,,","g,,","a,,","b,,",
+ "c,","d,","e,","f,","g,","a,","b,",
+ "c","d","e","f","g","a","b",
+ "c'","d'","e'","f'","g'","a'","b'",
+ "c''","d''","e''","f''","g''","a''","b''",
+ "c'''","d'''","e'''","f'''","g'''","a'''","b'''",
+ "c''''","d''''","e''''","f''''","g''''","a''''","b''''",
+ "c'''''","d'''''","e'''''","f'''''","g'''''","a'''''","b'''''",
++ )
++
++ _accidentals = {
++ 'natural': '',
++ 'sharp': 'is',
++ 'flat': 'es',
++ 'double_sharp': 'isis',
++ 'double_flat': 'eses',
++ 'semi_sharp': 'ih',
++ 'semi_flat': 'eh',
++ 'sesqui_sharp': 'isih',
++ 'sesqui_flat': 'eseh'
++ }
++
++ @classmethod
++ def pitch(self, note, clef=None, key=None, **kwargs):
++ if clef:
++ clef_name = clef['clef']
++ clef_off = self._clef_offsets[clef_name]
++ #if clef['octave'] == 'transpose_up':
++ # clef_off +=
++ else:
++ clef_off = self._clef_offsets['treble']
++ clef_offset = self._scale.index(clef_off)
++ pitch = self._scale[-note['pitch'] + clef_offset]
++
++ if note['accidental'] == 'auto':
++ if key:
++ accidental = key.accidental(pitch)
++ else:
++ accidental = 'natural'
++ else:
++ accidental = note['accidental']
++ note = [pitch[0], self._accidentals[accidental], pitch[1:]]
++ return ''.join(note)
++
++
+class NWCToken_note (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ ('duration', Pow2Enum),
+ ('unknown3', 'B'),
+ (BitField('B',
+ ('unknown4', 2),
+ ('stem', 2, NWCStemEnum),
+ ('triplet', 2, NWCTripletEnum),
+ ('beam', 2, NWCBeamEnum)),
+ 'B'),
+ ('unknown6', 'B'),
+ (BitField('B',
+ ('unknown5', 2),
+ ('accent', 1, BoolEnum),
+ ('tie', 1, BoolEnum),
+ ('unknown6', 1),
+ ('dot', 1, BoolEnum),
+ ('staccato', 1, BoolEnum),
+ ('ddot', 1, BoolEnum)),
+ 'B'),
+ (BitField('B',
+ ('unknown7', 2),
+ ('grace', 1),
+ ('unknown8', 2),
+ ('tenuto', 1, BoolEnum),
+ ('slur', 2, NWCSlurEnum),
+ ),
+ 'B'),
+ ('pitch', 'b'),
+ (BitField('B',
+ ('unknown9', 4),
+ ('pitch1', 1, BoolEnum), # some kind of pitch adjustment?
+ ('accidental', 3, NWCAccidentalEnum),
+ ),
+ 'B'),
+ ]
+
- _scale = ( # this list is taken from lilycomp
- "c,,,","d,,,","e,,,","f,,,","g,,,","a,,,","b,,,",
- "c,,","d,,","e,,","f,,","g,,","a,,","b,,",
- "c,","d,","e,","f,","g,","a,","b,",
- "c","d","e","f","g","a","b",
- "c'","d'","e'","f'","g'","a'","b'",
- "c''","d''","e''","f''","g''","a''","b''",
- "c'''","d'''","e'''","f'''","g'''","a'''","b'''",
- "c''''","d''''","e''''","f''''","g''''","a''''","b''''",
- )
-
- _clef_offsets = {
- 'treble': "b'",
- 'bass': 'd',
- 'alto': "c'",
- 'tenor': "a'",
- }
-
- _accidentals = {
- 'natural': '',
- 'sharp': 'is',
- 'flat': 'es',
- 'double_sharp': 'isis',
- 'double_flat': 'eses',
- 'semi_sharp': 'ih',
- 'semi_flat': 'eh',
- 'sesqui_sharp': 'isih',
- 'sesqui_flat': 'eseh'
- }
-
+ _ornaments = {
+ # http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Attached-to-notes#Articulations-and-ornamentations
+ # http://lilypond.org/doc/v2.11/Documentation/user/lilypond/List-of-articulations
+ 'accent': '->', # r'\accent',
+ 'marcato': '-^', #r'\marcato',
+ 'staccatissimo': '-|', # r'\staccatissimo',
+ 'espressivo': r'\espressivo',
+ 'staccato': '-.', # r'\staccato',
+ 'tenuto': '--', # r'\tenuto',
+ 'portato': '-_', # r'\portato',
+ 'upbow': r'\upbow',
+ 'downbow': r'\downbow',
+ 'flageolet': r'\flageolet',
+ 'thumb': r'\thumb',
+ 'lheel': r'\lheel',
+ 'rheel': r'\rheel',
+ 'ltoe': r'\ltoe',
+ 'rtoe': r'\rtoe',
+ 'open': r'\open',
+ 'stopped': '-+', #r'\stopped',
+ 'turn': r'\turn',
+ 'reverseturn': r'\reverseturn',
+ 'trill': r'\trill',
+ 'prall': r'\prall',
+ 'mordent': r'\mordent',
+ 'prallprall': r'\prallprall',
+ 'prallmordent': r'\prallmordent',
+ 'upprall': r'\upprall',
+ 'downprall': r'\downprall',
+ 'upmordent': r'\upmordent',
+ 'downmordent': r'\downmordent',
+ 'pralldown': r'\pralldown',
+ 'prallup': r'\prallup',
+ 'lineprall': r'\lineprall',
+ 'signumconguruentiae': r'\signumconguruentiae',
+ 'shortfermata': r'\shortfermata',
+ 'fermata': r'\fermata',
+ 'longfermata': r'\longfermata',
+ 'verylongfermata': r'\verylongfermata',
+ 'segno': r'\segno',
+ 'coda': r'\coda',
+ 'varcoda': r'\varcoda',
++ # http://lilypond.org/doc/v2.12/input/lsr/lilypond-snippets/Expressive-marks#Expressive-marks
++ 'breathe': r'\breathe',
+ }
+
+ _slurs = {
+ 'none': '',
+ 'start': r'\(',
+ 'stop': r'\)',
+ 'inside': '',
+ }
+
- def ly_text(self, clef=None, key=None,
- previous_note=None, next_note=None,
- in_chord=False):
++ def ly_text(self, clef=None, key=None, in_chord=False, **kwargs):
++ """
++ http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Simple-notation#Simple-notation
++ http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Accidentals-and-key-signatures
++ """
++ note = [_NotePitch.pitch(self, clef=clef, key=key)]
++ if in_chord:
++ return ''.join(note)
++ return self._decorate(note, **kwargs)
++
++ def _decorate(self, note, previous_note=None, next_note=None,
++ note_qualifiers=tuple(), **kwargs):
+ """
+ http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Curves#Phrasing-slurs
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Writing-rhythms#Ties
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Writing-rhythms#Tuplets
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Special-rhythmic-concerns#Grace-notes
+ """
- if clef:
- clef_name = clef['clef']
- else:
- clef_name = 'treble'
- clef_offset = self._scale.index(self._clef_offsets[clef_name])
- pitch = self._scale[-self['pitch'] + clef_offset]
-
- if self['accidental'] == 'auto':
- if key:
- accidental = key.accidental(pitch)
- else:
- accidental = 'natural'
- else:
- accidental = self['accidental']
- note = [pitch[0], self._accidentals[accidental], pitch[1:]]
-
- if in_chord:
- return ''.join(note)
-
+ note.append(_nwc_duration(self))
+
+ for key in ['accent', 'staccato', 'tenuto']:
+ if self[key]:
+ note.append(self._ornaments[key])
+
++ while len(note_qualifiers) > 0:
++ note.append(note_qualifiers.pop(0))
++
+ note.append(self._slurs[self['slur']])
+
+ if self['tie']:
+ note.append('~')
+
+ if self['beam'] == 'start':
+ note.append('[')
+ elif self['beam'] == 'stop':
+ note.append(']')
+
+ if self['triplet'] == 'start':
+ note.insert(0, r'\times 2/3 { ')
+ elif self['triplet'] == 'stop':
+ note.append(' }')
+
+ if self['grace']:
+ if previous_note == None or not previous_note['grace']:
+ note.insert(0, r'\acciaccatura { ')
+ if next_note == None or not next_note['grace']:
+ note.append(' }')
+
+ return ''.join(note)
+
+
+class NWCToken_rest (NWCToken_note):
+ def ly_text(self, **kwargs):
- rest = ['r%s' % _nwc_duration(self)]
- if self['triplet'] == 'start':
- rest.insert(0, r'\times 2/3 { ')
- elif self['triplet'] == 'stop':
- rest.append(' }')
-
- return ''.join(rest)
++ rest = ['r']
++ return self._decorate(rest, **kwargs)
+
+
- class NWCToken_chord1 (Structure):
++class NWCToken_chord (NWCToken_note):
+ _fields = NWCToken_note._fields + [
+ ('num_notes', 'B'),
+ ('unknown12', 'B'),
+ ]
+
+ def unpack_from(self, buffer, offset=0):
- super(NWCToken_chord1, self).unpack_from(buffer, offset)
++ super(NWCToken_chord, self).unpack_from(buffer, offset)
+ chords,size = self._unpack_chords_from(buffer, offset + self.size)
+ self['chords'] = chords
+ self.size += size
+
+ def _unpack_chords_from(self, buffer, offset=0):
+ size = 0
+ chord1 = []
+ chord2 = []
+
+ token = NWCToken()
+ size = 0
+ for i in range(self['num_notes']):
+ token.unpack_from(buffer, offset + size)
+ size += token.size
+ assert token['token'] in ['rest', 'note'], token
+
+ structure = globals()['NWCToken_%s' % token['token']]
+ t = structure(buffer, offset + size)
+ size += t.size
+
+ if _nwc_duration(t) == _nwc_duration(self):
+ chord1.append(t)
+ else : # 2 voices
+ chord2.append(t)
+
+ chords = [chord1]
+ if chord2:
+ chords.append(chord2)
+ return (chords, size)
+
- def ly_text(self, **kwargs):
- """
- http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Combining-notes-into-chords#Combining-notes-into-chords
++ def _initial_note(self, **kwargs):
++ """The chord token itself encodes the first note.
++
++ Actually, the chord's note seems to duplicate one of the chord's
++ note tokens.
+ """
++ return None #_NotePitch.pitch(self, **kwargs)
++
++ def _notes(self, **kwargs):
+ notes = []
++ initial_note = self._initial_note(**kwargs)
++ if initial_note:
++ notes.append(initial_note)
+ for chord in self['chords']:
+ for token in chord:
+ if isinstance(token, NWCToken_note):
+ notes.append(token.ly_text(in_chord=True, **kwargs))
- chord = ['<%s>' % ' '.join(notes)]
- chord.append(_nwc_duration(self))
- if self['tie']:
- chord.append('~')
-
- if self['beam'] == 'start':
- chord.append('[')
- elif self['beam'] == 'stop':
- chord.append(']')
++ return notes
+
- if self['triplet'] == 'start':
- chord.insert(0, r'\times 2/3 { ')
- elif self['triplet'] == 'stop':
- chord.append(' }')
++ def ly_text(self, **kwargs):
++ """
++ http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Combining-notes-into-chords#Combining-notes-into-chords
++ """
++ chord = ['<%s>' % ' '.join(self._notes(**kwargs))]
++ return self._decorate(chord, **kwargs)
+
- return ''.join(chord)
+# if chord2: # 2 voices
+# result += ' << '
+# for i in range(len(chord2)):
+# (pitch,accidental,duration ) = chord2[i]
+# pitch += lastClef
+# note = SCALE[pitch]
+#
+# if (accidental!='auto'):
+# currentKey[note[0]] = accidental
+# accidental = currentKey[note[0]]
+#
+# if (relative_pitch):
+# octave = getRelativePitch(lastPitch, pitch)
+# lastPitch = pitch
+# else:
+# octave = note[1:]
+# result += note[0] + accidental + octave + duration + ' '
+#
+# result += " \\\\ {"
+#
+# for i in range(len(chord1)):
+# (pitch,accidental ) = chord1[i]
+# pitch += lastClef
+# note = SCALE[pitch]
+#
+# if (accidental!='auto'):
+# currentKey[note[0]] = accidental
+# accidental = currentKey[note[0]]
+#
+# if (relative_pitch):
+# octave = getRelativePitch(lastPitch, pitch)
+# lastPitch = pitch
+# else:
+# octave = note[1:]
+# result += note[0] + accidental + octave +chordDur + ' '
+# if lastChord >0 : lastChord = _nwc_duration_value(duration) - _nwc_duration_value(chordDur)
+# if lastChord==0: result += ' } >> '
+# # end 2 voices
+# else: # block chord
+# result += ' <'
+# for i in range(len(chord1)):
+# (pitch,accidental ) = chord1[i]
+# pitch += lastClef
+# note = SCALE[pitch]
+#
+# if (accidental!='auto'):
+# currentKey[note[0]] = accidental
+# accidental = currentKey[note[0]]
+#
+# if (relative_pitch):
+# octave = getRelativePitch(lastPitch, pitch)
+# lastPitch = pitch
+# else:
+# octave = note[1:]
+# result += note[0] + accidental + octave + ' '
+# result += '>' +chordDur +' '
+# lastPitch = chord1[0][0] + lastClef
+# lastDuration = chordDur
+
+
+class NWCToken_pedal (Structure):
- _fields = []
- for i in range(5):
- _fields.append(('unknown%d' % (i+1), 'B'))
++ _fields = [
++ ('unknown1', 'B'),
++ ('unknown2', 'B'),
++ ('unknown3', 'B'),
++ ('unknown4', 'B'),
++ ('sustain', BoolEnum),
++ ]
++
++ def ly_text(self):
++ """
++ http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Piano-templates#Piano-templates
++ http://www.kainhofer.com/~lilypond/ajax/lilypond/Piano.html#Piano-pedals
++
++ TODO: remove %{/%} comment wrapper once we upgrade to a version
++ of LilyPond that supports piano pedals natively.
++ """
++ if self['sustain']:
++ sustain = 'On'
++ else:
++ sustain = 'Off'
++ return r'%%{ \sustain%s %%}' % sustain
+
+
+class NWCToken_midi_MPC (Structure):
+ _fields = []
+ for i in range(36):
+ _fields.append(('unknown%d' % (i+1), 'B'))
+
+
++class NWCFermataTempoVarianceEnum (Enum):
++ _list = ['breathe', 'fermata', 'accel.', 'allarg.', 'rall.', 'ritard.',
++ 'rit.', 'rubato', 'string.']
++
+class NWCToken_fermata (Structure):
- _fields = []
- for i in range(6):
- _fields.append(('unknown%d' % (i+1), 'B'))
++ _fields = [
++ ('unknown1', 'B'),
++ ('unknown2', 'B'),
++ ('unknown3', 'B'),
++ ('unknown4', 'B'),
++ ('tempo_variance', NWCFermataTempoVarianceEnum),
++ ('unknown6', 'B'),
++ ]
+
+ def ly_text(self):
+ """
+ http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Other-uses-for-tweaks#index-fermata_002c-implementing-in-MIDI
++ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/List-of-articulations
++ http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Adding-text
+ """
- return r'\fermata'
++ if self['tempo_variance'] in NWCToken_note._ornaments:
++ return NWCToken_note._ornaments[self['tempo_variance']]
++ return r'_\markup { \small \italic \bold { %s } }' % self['tempo_variance']
+
+
- class NWCToken_dynamics2 (Structure):
- _fields = []
- for i in range(5):
- _fields.append(('unknown%d' % (i+1), 'B'))
++class NWCDynamicVarianceEnum (Enum):
++ _list = (
++ 'crescendo', 'decrescendo', 'diminuendo', 'rinforzando', 'sforzando')
++
++class NWCToken_dynamic_variance (Structure):
++ _fields = [
++ ('unknown1', 'B'),
++ ('unknown2', 'B'),
++ ('unknown3', 'B'),
++ ('unknown4', 'B'),
++ (BitField('B',
++ ('unknown5', 5),
++ ('type', 3, NWCDynamicVarianceEnum)),
++ 'B'),
+ ]
+
-stems = [ '\stemNeutral ', '\stemUp ', '\stemDown ']
-
-# what is slur 3? continuation of existing slur? (HdR)
-slurs = [ '', '(' , ')', '' ]
-
-tempoVariance = ['breathe',
- 'fermata',
- 'accel.',
- 'allarg.',
- 'rall.',
- 'ritard.',
- 'rit.',
- 'rubato',
- 'string.'
- ]
-dynamics = [ '\\ppp ',
- '\\pp ',
- '\\p ',
- '\\mp ',
- '\\mf ',
- '\\f ',
- '\f ',
- '\\fff '
- ]
-
-triplets = [
- ('' , '' ),
- ( '\\times 2/3 { ', '') ,
- ('' , '' ),
- ('' , ' }' ),
- ]
-
-durations = ( '1','2','4','8','16','32','64' )
-revDurations = ( 64, 32, 16, 8, 4, 2, 1 )
-
-barlines = (
- '|', # 'Single'
- '||', # 'Double'
- '.|', # SectionOpen
- '|.', # SectionClose
- '|:', # MasterRepeatOpen
- ':|', # MasterRepeatClose
- '|:', # LocalRepeatOpen
- ':|', # LocalRepeatClose
-)
-
-ending = (
-'|.', # SectionClose
-':|', # MasterRepeatClose
-'|', # 'Single'
-'||', # 'Double'
-'' # Open hidden
-)
-
-staffType = (
-'Standard' , # Standard
-'Upper Grand Staff' , # Upper Grand Staff
-'Lower Grand Staff' , # Lower Grand Staff
-'Orchestra' , # Orchestra
-)
-
-
-beams = [ '', '[', '',']' ]
-
-
-# end ' \bar "|."'
-
-# Notation Properties
-# extra accidental spacing
-# extra note spacing
-# muted
-# no ledger lines
-# slurdirection
-# tiedirection
-# lyricsyllable
-# visability
-# show printed
-# item color
-
-#dynamics
-# cmd = DynamicVariance
-# Decrescendo \setTextCresc \<
-# setTextCresc Crescendo \> setHairpinCresc
-# Dynamics stop '\! '
-# style = ff pp
-
-clefOctave = [ '' , '^8', '_8' , '' ]
-clefShift = [0,7,-7, 0]
-
-
-# '#(set-accidental-style '#39'modern-cautionary)'
-#(ly:set-point-and-click 'line-column)
-#(set-global-staff-size 20)
-
-timesigValues = {
- '4/4' : '1', '3/4' : '2.', '2/4' : '2', '1/4' : '1', '6/4' : '2.', '5/4' : 1,
- '1/8' : '8', '2/8' : '4', '3/8' : '4.', '6/8' : '2.',
- '4/8' : '2', '9/8' : '12', '12/8' : '1',
- '2/2' : '1', '4/2' : '0', '1/2' : '2',
++ _typess = {
++ 'crescendo': r'\<',
++ 'decrescendo': r'\>',
++ 'diminuendo': r'\>',
++ 'rinforzando': r'\rfz',
++ 'sforzando': r'\sfz',
+ }
+
-print "python nwc2ly is running..."
-try:
-
- nwcData = open( nwcfile,'rb')
-
- # check if its a readable nwc format
- # compressed - [NWZ]
- # uncompressed - [NoteWorthy ArtWare] [NoteWorthy Composer]
- format = nwcData.read(5)
- if format== '[NWZ]':
- nwcData.seek(1,1)
- print 'Compressed NWC detected!'
- print 'Dumping to uncompressed NWC format and attemping conversion soon...'
- uncompress = open ('uncompressed.nwc','wb')
- uncompress.write(zlib.decompress(nwcData.read()))
- uncompress.close()
- print 'Inflating done. Now opening new file...'
- nwcData.close()
- nwcData = open( 'uncompressed.nwc','rb')
- nwcData.seek(6)
- elif format!= '[Note':
- print 'Unknown format, please use an uncompress NWC format and try again.'
- sys.exit()
-
-
- if debug: print "Getting file format"
- getFileFormat(nwcData)
- if debug: print "Getting file info"
- fileInfo = getFileInfo(nwcData)
- if debug: print "Getting page setup"
- (margins, staffSize) = getPageSetup(nwcData)
-
- #0.09850000 0.20094000 0.29944000 0.50037998
- #0123456789012345678901234567890123456789012345
- topMargin = margins[0:9]
- leftMargin = margins[11:20]
- rightMargin = margins[22:31]
- bottomMargin = margins[33:42]
-
- topMargin = round( float(topMargin), 2 )
- bottomMargin = round( float(bottomMargin), 2)
- leftMargin = round( float(leftMargin), 2 )
- rightMargin = round( float(rightMargin), 2 )
- if debug: print "top ", str(topMargin)
- if debug: print "bottom ", str(bottomMargin)
- if debug: print "left ", str(leftMargin)
- if debug: print "right ", str(rightMargin)
-
- resultFile = '%% Generated from python nwc2ly converter v%s by Joshua Koo (joshuakoo@myrealbox.com)' % nwc2lyversion
- resultFile += '\n\\paper \n{\n'
- resultFile += '\t#(set-paper-size "' + paperSize + '")\n'
- resultFile += '\t#(set-global-staff-size ' + str(staffSize) + ')\n'
- resultFile += '\ttopmargin = ' + str(topMargin) + '\\in\n'
- resultFile += '\tleftmargin = ' + str(leftMargin) + '\\in\n'
- resultFile += '\trightmargin = ' + str(rightMargin) + '\\in\n'
- resultFile += '\tbottommargin = ' + str(bottomMargin) + '\\in\n'
- resultFile += '\traggedlastbottom = ##'
- if fillLast:
- resultFile += 'f\n'
- else:
- resultFile += 't\n'
- resultFile += '}'
- resultFile += '\n\n\\version "' + LilyPondVersion + '"'
- resultFile += "\n"
-
- # START WORK
- resultFile+= fileInfo
- resultFile+= "\n\n\\score {"
- resultFile+= "\n\t<<\n\t\t"
-
++ def ly_text(self):
++ return self._types[self['type']]
++
++
++class NWCPerformanceStyleEnum (Enum):
++ _list = (
++ 'ad lib.', 'animato', 'cantabile', 'con brio', 'dolce', 'espressivo',
++ 'grazioso', 'legato', 'maestoso', 'marcato', 'meno mosso',
++ 'poco a poco', 'piu mosso', 'semplice', 'simile', 'solo', 'sostenuto',
++ 'sotto voce', 'staccato', 'subito', 'tenuto', 'tutti', 'volta subito')
+
+
+class NWCToken_performance_style (Structure):
- _fields = []
- for i in range(5):
- _fields.append(('unknown%d' % (i+1), 'B'))
++ _fields = [
++ ('unknown1', 'B'),
++ ('unknown2', 'B'),
++ ('unknown3', 'B'),
++ ('unknown4', 'B'),
++ ('style', 'B'),
++ ]
++
++ def ly_text(self):
++ return r'_\markup { \small \italic \bold { %s } }' % self['style']
+
+
+class NWCToken_text (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ ('textpos', 'B'),
+ ('unknown3', 'B'),
+ ('unknown4', 'B'),
+ ('text', 'S'),
+ ]
+
+ def ly_text(self):
+ """
+ http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Writing-text
+ http://lilypond.org/doc/v2.12/Documentation/user/lilypond/Writing-text#Text-marks
+ """
+ #if text.isdigit() : # check numbers
+ # text = "-\\markup -\\number "+ text
+ # #text = "-\\markup {\\number "+ text +"}"
+ #else :
+ # text = '-"' + text + '"'
+ #extra += ' ' + text
++ if text == 'tr':
++ return r'\trill'
+ return r'\mark "%s"' % self['text']
+
+
- class NWCToken_chord2 (NWCToken_chord1):
++class NWCToken_rest_chord (NWCToken_chord):
++ """A chord starting with a rest?"""
++ #def _initial_note(self, **kwargs):
++ # return 'r'
+ pass
+
+
+def _get_type_and_type_tail(nwc_buffer):
+ """
+ >>> _get_type_and_type_tail('[NWZ]...\\n[bla bla]...')
+ ('NWZ', '...\\n[bla bla]...')
+ >>> _get_type_and_type_tail('[NoteWorthy ArtWare]...\\n[bla bla]...')
+ ('NoteWorthy ArtWare', '...\\n[bla bla]...')
+ """
+ m = re.match('\[([^]]*)\]', nwc_buffer)
+ _type = m.group(1)
+ tail = nwc_buffer[len(m.group(0)):]
+ return (_type, tail)
+
+def _normalize_nwc(nwc_buffer):
+ """
+ Tested in `parse_nwc()` doctests.
+ """
+ _type,tail = _get_type_and_type_tail(nwc_buffer)
+ if _type == 'NWZ':
+ LOG.debug('compressed nwc detected, decompressing.')
+ uncompressed_nwc_buffer = zlib.decompress(tail[1:])
+ return uncompressed_nwc_buffer
+ return nwc_buffer
+
+def _get_staff(nwc_buffer, offset, n):
+ orig_offset = offset
+ staff = NWCStaff(nwc_buffer, offset)
+ offset += staff.size
+ if staff['num_lyrics'] > 0:
+ assert staff['has_lyrics'] == 1, pprint.pformat(staff)
+ staff['lyric_properties'] = NWCLyricProperties(nwc_buffer, offset)
+ offset += staff['lyric_properties'].size
+ staff['lyrics'] = []
+ for i in range(staff['num_lyrics']):
+ p = NWCLyricBlockProperties(nwc_buffer, offset)
+ offset += p.size
+ length = 1024 * (p['num_blocks']/4) - 1
+ #p['len']
+ lines = re.split(
+ '\r|\n',
+ _decode_string(nwc_buffer[offset:offset+length]))
+ block = []
+ for line in lines:
+ block.append([word.strip() for word in line.rstrip('\x00').split('\x00')])
+ LOG.debug('lyric line %s' % block[-1])
+ staff['lyrics'].append(block)
+ offset += length
+ # possible if lyric byte read
+ staff['note_properties'] = NWCStaffNoteProperties(nwc_buffer, offset)
+ offset += staff['note_properties'].size
+
+ staff['tokens'] = []
+ token = NWCToken()
+ oo = offset
+ for i in range(10000000): # num_tokens
+ try:
+ token.unpack_from(nwc_buffer, offset)
+ except struct.error:
+ LOG.warn('ran off end of file')
+ return (staff, offset-orig_offset)
+ if token['token'] == 'STOP':
+ break
+ offset += token.size
+ LOG.debug('token: %s' % token['token'])
+ if type(token['token']) == int:
+ raise ValueError(token['token'])
+ structure = globals()['NWCToken_%s' % token['token']]
+ t = structure(nwc_buffer, offset)
+ offset += t.size
+ staff['tokens'].append(t)
+ LOG.debug('%d tokens, %d bytes (chords count as one)' % (i, offset - oo))
+ return (staff, offset-orig_offset)
+
+def parse_nwc(nwc_buffer):
+ """Parse a NoteWorthy Composer `nwc` file.
+
+ >>> import os.path
+ >>> with open(os.path.join('example', '1.75-1.nwc'), 'rb') as f:
+ ... nwc_buffer = f.read()
+ >>> nwc_buffer # doctest: +ELLIPSIS
+ '[NWZ]...'
+ >>> n = parse_nwc(nwc_buffer)
+ >>> n['_debug']['nwc_buffer'] # doctest: +ELLIPSIS
+ '[NoteWorthy ArtWare]...'
+ >>> d = n.pop('_debug')
+ >>> pprint.pprint(n) # doctest: +ELLIPSIS, +REPORT_UDIFF
+ {'major_version': 1,
+ 'minor_version': 75,
+ 'na': u'N/A',
+ 'name1': u'Abwhir',
+ 'page_setup': {'f2': u'F2',
+ 'margins': u'1.00000000 1.00000000 1.00000000 1.00000000',
+ 'ny_': u'NN_',
+ 'page_small': {'name': u'Times New Roman',
+ 'size': 10,
+ 'style': 0,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ 'page_text': {'name': u'Times New Roman',
+ 'size': 12,
+ 'style': 0,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ 'page_title': {'name': u'Times New Roman',
+ 'size': 24,
+ 'style': 1,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ 'staff_bold': {'name': u'Times New Roman',
+ 'size': 10,
+ 'style': 1,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ 'staff_italic': {'name': u'Times New Roman',
+ 'size': 12,
+ 'style': 3,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ 'staff_lyric': {'name': u'Times New Roman',
+ 'size': 15,
+ 'style': 0,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ ...,
+ 'user1': {'name': u'Times New Roman',
+ 'size': 12,
+ 'style': 0,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ 'user2': {'name': u'Times New Roman',
+ 'size': 12,
+ 'style': 0,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ 'user3': {'name': u'Times New Roman',
+ 'size': 12,
+ 'style': 0,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ 'user4': {'name': u'Times New Roman',
+ 'size': 12,
+ 'style': 0,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ 'user5': {'name': u'Times New Roman',
+ 'size': 8,
+ 'style': 0,
+ 'unknown3': 0,
+ 'unknown4': 0},
+ 'user6': {'name': u'Times New Roman',
+ 'size': 8,
+ 'style': 0,
+ 'unknown3': 0,
+ 'unknown4': 0}},
+ 'product': u'[NoteWorthy Composer]',
+ 'song_info': {'author': u'George Job Elvey, 1858',
+ 'comments': u'Source: The United Methodist Hymnal (Nashville, Tennessee: The United Methodist Publishing House, 1989), # 694.',
+ 'copyright1': u'Public Domain',
+ 'copyright2': u'Courtesy of the Cyber Hymnal (http://www.cyberhymnal.org)',
+ 'title': u'St. George\u2019s Windsor, 77.77 D'},
+ 'staff': [{'ff': 255,
+ 'group': u'Standard',
+ 'has_lyrics': 0,
+ 'lyrics': [],
+ 'name': u'Unnamed-000',
+ 'note_properties': {'color': 0, 'num_tokens': 0, 'unknown1': 0},
+ 'num_lyrics': 0,
+ 'tokens': [{'flat_bits': 2,
+ 'sharp_bits': 0,
+ ...},
+ {'tempo': u'',
+ ...},
+ {'beat_value': 4,
+ 'beats': 4,
+ ...},
+ ...],
+ 'unknown01': 0,
+ ...}],
+ 'type': u'[NoteWorthy ArtWare]',
+ 'unknown01': 0,
+ ...}
+ """
+ LOG.info('parsing nwc')
+ n = {'_debug': {}}
+ nwc_buffer = _normalize_nwc(nwc_buffer)
+ n['_debug']['nwc_buffer'] = nwc_buffer
+
+ head = NWCFileHead(nwc_buffer)
+ offset = head.size
+ n.update(head)
+
+ assert head['major_version'] == 1, head['major_version']
+ assert head['minor_version'] == 75, head['minor_version']
+
+ #o = nwc_buffer[offset:].find('\xff')
+ #if o < 0:
+ # LOG.error('could not find staff section')
+ # raise NotImplementedError
+ #offset += o
+ #LOG.warn('skip to staves with offset %d (skipped %d, %s)'
+ # % (offset, o, repr(nwc_buffer[offset:offset+10])))
+
+ n['staff_set'] = NWCStaffSet(nwc_buffer, offset)
+ offset += n['staff_set'].size
+
+ n['staff'] = []
+ for i in range(n['staff_set']['num_staves']):
+ LOG.info('process staff %d' % i)
+ staff,size = _get_staff(nwc_buffer, offset, n)
+ offset += size
+ if staff == None:
+ break
+ n['staff'].append(staff)
+
+ return n
+
+
+def ly_text(nwc):
+ """
+ >>> import os.path
+ >>> with open(os.path.join('example', '1.75-1.nwc'), 'rb') as f:
+ ... nwc_buffer = f.read()
+ >>> nwc = parse_nwc(nwc_buffer)
+ >>> print ly_text(nwc) # doctest: +ELLIPSIS
+ '[NoteWorthy ArtWare]...'
+
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Creating-MIDI-files
+ """
+ if nwc['type'] not in ['[NoteWorthy ArtWare]', '[NoteWorthy Composer]']:
+ raise ValueError('unknown file type: %s' % nwc['type'])
+
+ #LOG.debug(pprint.pformat(nwc, width=70))
+ LOG.info('format header')
+
+ lines = [
+ '%% Generated with %s converter v%s' % ('nwc2ly.py', __version__),
+ r'\version "2.12.2"', # http://lilypond.org/doc/v2.12/Documentation/user/lilypond-learning/Version-number#Version-number
+ ]
+
+ lines.extend([nwc['song_info'].ly_text(), ''])
+
+
- noOfStaffs = findNoOfStaff(nwcData);
-
- for staff in range(1,noOfStaffs+1):
- print "\n\nWorking on Staff", staff
- result = processStaff(nwcData)
- #print result
- resultFile += result
-
- resultFile+= "\n\t>>"
-# resultFile+= "\n\t\layout {}"
-# resultFile+= "\n\t\midi {}"
- resultFile+= "\n}"
- nwcData.close()
-
- if lyfile=='':
- print 'Dumping output file to screen'
- print resultFile
- else :
- write = open( lyfile ,'w')
- write.write (resultFile)
- write.close()
-
-except IOError:
- print 'File does not exist or an IO error occurred'
-except Exception, e: #KeyError
- print "Error while reading data at ", nwcData.tell() ,"\n"
- print 'Dumping whatever result first'
- print resultFile
- print Exception, e
- traceback.print_exc()
-
-print
-print
-print "Please send all bugs and requests to joshuakoo@myrealbox.com"
+ lines.extend([
+ '',
+ r'\score {',
+ '\t<<',
+ ])
+
+ for i,staff in enumerate(nwc['staff']):
+ LOG.info('format staff %d' % i)
+ voice = chr(i+ord('a'))
+ if i == 1 and False:
+ for token in staff['tokens']:
+ if isinstance(token, NWCToken_clef):
+ token['clef'] = 'bass'
+ lines.extend(['\t\t%s' % line for line in staff.ly_lines(voice=voice)])
+
+ LOG.info('format footer')
+ lines.extend([
+ '\t>>',
- ''
- '\t\layout {}',
- '\t\midi {}',
++ '',
++ '\t\\layout {}',
++ '\t\\midi {}',
+ '}',
+ ''])
+ return '\n'.join(lines)
+
+
+def test():
+ import doctest
+ return doctest.testmod()
+
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ usage = "nwc2ly.py [options] uncompressed.nwc test.ly > convert.log"
+ p = OptionParser(usage=usage)
+ p.add_option('--debug', dest='debug', action='store_true',
+ help='Enable verbose logging')
+ p.add_option('--absolute-pitch', dest='relative_pitch', default=True,
+ action='store_false',
+ help='')
+ p.add_option('--absolute-duration', dest='relative_duration', default=True,
+ action='store_false',
+ help='')
+ p.add_option('--bar-comments', dest='bar_comments', default=10,
+ type='int',
+ help='Comments for every x lines, section/line comment??')
+ p.add_option('--no-beaming', dest='insert_beaming', default=True,
+ action='store_false', help='')
+ p.add_option('--no-stemming', dest='insert_stemming', default=True,
+ action='store_false', help='')
+ p.add_option('--no-text', dest='insert_text', default=True,
+ action='store_false', help='Currently a no-op')
+ p.add_option('--test', dest='test', action='store_true',
+ help='Run internal tests and exit')
+
+ options,args = p.parse_args()
+
+ if options.debug:
+ logging.basicConfig(level=logging.DEBUG)
+ else:
+ logging.basicConfig(level=logging.INFO)
+
+ if options.test:
+ results = test()
+ sys.exit(min(results.failed, 127))
+
+ #options? lvb7th1
+ nwc_file = args[0]
+ if len(args) > 1:
+ ly_file = args[1]
+ else:
+ ly_file = None
+
+ with open(nwc_file, 'rb') as f:
+ nwc_buffer = f.read()
+ nwc = parse_nwc(nwc_buffer)
+
+ if ly_file:
+ f = open(ly_file, 'w')
+ else:
+ f = sys.stdout
+
+ f.write(ly_text(nwc).encode('utf-8'))
+
+ if ly_file:
+ f.close()