From: W. Trevor King Date: Wed, 1 Dec 2010 12:18:36 +0000 (-0500) Subject: Merge Hans' 1.0 version of nwc2ly.py into my new architecture. X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=9d7c0b8c70a353c9cabec08f686ccea8b360175b;p=nwc2ly.git Merge Hans' 1.0 version of nwc2ly.py into my new architecture. --- 9d7c0b8c70a353c9cabec08f686ccea8b360175b diff --cc nwc2ly.py index ee29a1f,3dc8db9..6f2e58e mode 100755,100644..100755 --- a/nwc2ly.py +++ b/nwc2ly.py @@@ -1,1766 -1,1362 +1,1914 @@@ -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()