-import binascii, sys, zlib, traceback \r
-\r
-shortcopyleft = """\r
-nwc2ly - Converts NWC(v 1.75) to LY fileformat\r
-Copyright (C) 2005 Joshua Koo (joshuakoo @ myrealbox.com)\r
-\r
-This program is free software; you can redistribute it and/or\r
-modify it under the terms of the GNU General Public License\r
-as published by the Free Software Foundation; either version 2\r
-of the License, or (at your option) any later version.\r
-\r
-This program is distributed in the hope that it will be useful,\r
-but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
-GNU General Public License for more details.\r
-\r
-You should have received a copy of the GNU General Public License\r
-along with this program; if not, write to the Free Software\r
-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\r
-"""\r
-\r
-##\r
-# most infomation obtained about the nwc format \r
-# is by using noteworthycomposer and the somewhat like the french cafe method\r
-# (http://samba.org/ftp/tridge/misc/french_cafe.txt)\r
-#\r
-#\r
-## \r
-# Revisions\r
-# 0.1 07 april 2005 initial hex parsing;\r
-# 0.2 13 april 2005 added multiple staff, keysig, dots, durations\r
-# 0.3 14 april 2005 clef, key sig detection, absolute notes pitching\r
-# 0.4 15 April 2005 Relative Pitchs, Durations, Accidentals, Stem Up/Down, Beam, Tie\r
-# 0.5 16 April 2005 Bug fixes, Generate ly score , Write to file, Time Sig, Triplets, Experimental chords\r
-# 0.6 17 April 2005 Compressed NWC file Supported!\r
-# 0.7 19 April 2005 Version detection, Header \r
-# 20 April 2005 BUGS FiXes, small Syntax changes\r
-# 21 April 2005 Still fixing aimlessly \r
-# 23 April 2005 Chords Improvement\r
-# 24 April 2005 staccato, accent, tenuto. dynamics, midi detection (but unsupported)\r
-# 0.8 24 April 2005 Experimental Lyrics Support\r
-# 0.9 29 April 2005 Workround for \acciaccatura, simple Check full bar rest adjustment\r
-# \r
-## \r
-# TODO\r
-# Proper syntax and structure for staffs, lyrics, layering\r
-# version 1.7 Support\r
-# nwc2ly in lilytool\r
-# \r
-# Piano Staff\r
-# Chords\r
-# Pedals\r
-# Midi Instruments\r
-# Visability\r
-# Lyrics\r
-# Context Staff\r
-# Staff layering / Merging\r
-##\r
-#\r
-# BUGS text markups, chords, fermata\r
-######\r
-# \r
-# cd /cygdrive/c/development/nwc2ly\r
-# $ python nwc2ly.py lvb7th1\ uncompressed.nwc test.ly > convert.log\r
-#######\r
-\r
-nwc2lyversion = '0.9.0'\r
-############\r
-# Options #\r
-############\r
-debug = 0 \r
- # You can use 0 for False and 1 for True\r
-relativePitch = 1\r
-relativeDuration = 1\r
-barLinesComments = 10 # Comments for every x lines # section/line comment\r
-\r
-insertBeaming = 1\r
-insertSteming = 1\r
-#insertText = 0\r
-\r
-nwcversion = 1.75\r
-##############\r
-\r
-args = len(sys.argv)\r
-if args<2:\r
- print "Syntax: python nwc2ly.py nwcfile [lyfile]"\r
- sys.exit()\r
-nwcfile = sys.argv[1] # 'simple1.nwc' #'simple2.nwc' 'simple3.nwc' 'bbc5-1-org.nwc' bbc5-1-nop.\r
-\r
-if args<3:\r
- lyfile = '' # 'test.ly'\r
-else:\r
- lyfile = sys.argv[2]\r
-\r
-\r
-\r
-def getFileFormat(nwcData):\r
- #'[NoteWorthy ArtWare]'\r
- #'[NoteWorthy Composer]'\r
- nwcData.seek(0)\r
- company = readLn(nwcData)\r
- nwcData.seek(2,1) # pad\r
- product = readLn(nwcData)\r
- version = readLn(nwcData)\r
- version = ord(version[0]) * 0.01 + ord(version[1])\r
- nwcversion = version\r
- print 'NWC file version', nwcversion, 'detected!'\r
- pad(nwcData,2) # times saved\r
- name1 = readLn(nwcData)\r
- name2 = readLn(nwcData)\r
- \r
- pad(nwcData,8)\r
- huh = nwcData.read(1)\r
- pad(nwcData,1)\r
- \r
-def getFileInfo(nwcData):\r
- title = readLn(nwcData)\r
- author = readLn(nwcData)\r
- copyright1 = readLn(nwcData)\r
- copyright2 = readLn(nwcData)\r
- comments = readLn(nwcData)\r
- \r
- header = "\\header {"\r
- header += "\n\ttitle = \"%s\"" % title\r
- header += "\n\tenteredby = \"%s\"" % author\r
- header += "\n\tcopyright = \"%s\"" % copyright1\r
- header += "\n\tfooter = \"%s\"" % copyright2\r
- header += "\n\t%%{ %s %%}" % comments\r
- header += "\n}"\r
- \r
- # TO ADD IN DEFAULT BLANK FEILDS TO KEY IN\r
- print 'title,author,copyright1,copyright2,comments: ', (title,author,copyright1,copyright2,comments)\r
- return header\r
-\r
-# Page Setup\r
-def getPageSetup(nwcData):\r
- # ??\r
- getMargins(nwcData)\r
- #getContents(nwcData)\r
- #getOptions(nwcData)\r
- getFonts(nwcData)\r
-\r
- \r
-def getMargins(nwcData):\r
- readTill(nwcData,'\x01')\r
- pad(nwcData,1)\r
- # get string size 43\r
- margins = readLn(nwcData)\r
- print 'margins ', margins\r
- #mirrorMargines\r
- #UOM\r
- return \r
-\r
-def getOptions(nwcData):\r
- # page numbering, from\r
- # title page info\r
- # extend last system\r
- # incrase note spacing for larger note duration\r
- # staff size\r
- # staff labels (none, first systems, top systems, all systems\r
- # measure numbers none, plain, circled, boxed\r
- # measure start\r
- return\r
-\r
-def getFonts(nwcData):\r
- # 12 Times\r
- readTill(nwcData,'\xFF')\r
- n = nwcData.read(1)\r
- pad(nwcData,1)\r
- for i in range (12):\r
- # Font Name\r
- font = readLn (nwcData)\r
- \r
- # 00\r
- \r
- # Style 'Regular' 'Italic' 'Bold' 'Bold Italic'\r
- style = ord(nwcData.read(1)) & 3\r
- \r
- # Size\r
- size = ord(nwcData.read(1))\r
- \r
- ## 00\r
- nwcData.seek(1,1)\r
- \r
- # Typeface\r
- # 00 Western, b1 Hebrew\r
- typeface = nwcData.read(1)\r
- \r
- print 'Font detected' , font, 'size',size, 'style ', style, ' typeface',typeface\r
- \r
-\r
-def findNoOfStaff(nwcData):\r
- # Infomation on Staffs \x08 00 00 FF 00 00 n\r
- data = 0;\r
- \r
- readTill(nwcData,'\xFF')\r
- print "Where am I? ", nwcData.tell()\r
- \r
- \r
- nwcData.read(2)\r
- \r
- layering = nwcData.read(1) # FF or 00\r
- \r
- noOfStaffs = ord(nwcData.read(1))\r
- nwcData.read(1)\r
- print noOfStaffs, " noOfStaffs found"\r
- return noOfStaffs\r
-\r
-def findStaffInfo(nwcData):\r
- # Properties for Staff\r
- # General \r
- # Name.\r
- # Group\r
- # Ending Bar\r
- # Visual\r
- # Verticle Upper Size\r
- # Verticle Lower Size\r
- # Style\r
- # Layer Next Staff\r
- # Color\r
- # Midi\r
- # Part Volume\r
- # Stereo Pan\r
- # Transposition\r
- # Muted\r
- # PlayBack Device\r
- # Playback Channel\r
- # Instrument\r
- # Patch Name\r
- # Patch List Type\r
- # Bank Select \r
- # Controller0\r
- # Controller32\r
- # Staff Lyrics\r
- # LineCount\r
- # AlignSyllableRule\r
- # StaffPlacementAligment\r
- # StaffPlacementOffset\r
- # StaffPropertiesVerticleSizeUpper\r
- # StaffPropertiesVerticleSizeLower\r
- \r
- format = ''\r
- staffName = readLn(nwcData)\r
- format += "\\context Staff = %s " % staffName # or voice or lyrics \r
- \r
- groupName = readLn(nwcData) # HOW TO ORGANISE THEM??\r
- \r
- endbar = ord(nwcData.read(1)) # & (2^3-1)\r
- #print 'end ',endbar\r
- endingBar = ending[endbar] # 10 --> OC for lyrics? 10000 1100\r
- \r
- muted = ord(nwcData.read(1)) & 1\r
- nwcData.read(1)\r
- \r
- channel = ord(nwcData.read(1)) + 1\r
- nwcData.read(9)\r
- \r
- stafftype = staffType[ord(nwcData.read(1))&3]\r
- nwcData.read(1)\r
- \r
- uppersize = 256 - ord(nwcData.read(1)) # - signed +1 )& 2^7-1 )\r
- readTill(nwcData,'\xFF')\r
- \r
- lowersize = ord(nwcData.read(1))\r
- ww = nwcData.read(1) \r
- print '[uppersize,lowersize]',[uppersize,lowersize]\r
- \r
- noOfLines = ord(nwcData.read(1))\r
- print '[staffName,groupName,endingBar,stafftype,noOfLines]', [staffName,groupName,endingBar,stafftype,noOfLines]\r
- \r
- layer = ord(nwcData.read(1)) & 1\r
- \r
- # signed transposition\r
- # FF?\r
- \r
- partVolume = ord(nwcData.read(1))\r
- ord(nwcData.read(1))\r
- \r
- stereoPan = ord(nwcData.read(1))\r
- if nwcversion == 1.7:\r
- nwcData.read(2)\r
- else:\r
- nwcData.read(3)\r
- \r
- nwcData.read(2)\r
- #lyrics = ord(nwcData.read(1)) & 1\r
- lyrics = readInt(nwcData)\r
- noOfLyrics = readInt(nwcData)\r
- \r
- lyricsContent = ''\r
- if lyrics:\r
- lyricOptions = readInt(nwcData)\r
- nwcData.read(3)\r
- for i in range(noOfLyrics):\r
- print 'looping ',i, 'where', nwcData.tell()\r
- lyricsContent += '\\ \lyricmode { ' # lyrics\r
- lyricsContent += getLyrics(nwcData) #\r
- lyricsContent += '}'\r
- nwcData.read(1)\r
- \r
- nwcData.read(1)\r
- color = ord(nwcData.read(1)) & 3 #12\r
- \r
- noOfTokens = readInt(nwcData)\r
- print noOfTokens, " Tokens found", nwcData.tell()\r
- return noOfTokens, format, lyricsContent\r
- \r
-\r
-\r
-\r
-\r
-def pad(nwcData, length):\r
- nwcData.seek(length,1)\r
-\r
-def readTill(nwcData, delimit):\r
- data = ''\r
- value = ''\r
- while data!=delimit:\r
- value += data\r
- data = nwcData.read(1)\r
- \r
- return value\r
- \r
-def readLn(nwcData):\r
- return readTill (nwcData,'\x00')\r
- # reads until 00 is hit\r
- # od oa == \n\r
-\r
-def readInt(nwcData):\r
- data = nwcData.read(2)\r
- no = ord(data[0])\r
- no += ord(data[1]) * 256\r
- return no\r
-\r
-\r
-def getLyrics(nwcData):\r
- \r
- data = ''\r
- print 'reach'\r
- blocks = ord(nwcData.read(1))\r
- if blocks==4: blocks = 1\r
- if blocks==8: blocks = 2\r
- if blocks == 0: return \r
- \r
- lyricsLen = readInt(nwcData)\r
- \r
- print 'blocks ',blocks, 'lyrics len', lyricsLen, 'at ', nwcData.tell()\r
- \r
- nwcData.read(1)\r
- for i in range (blocks):\r
- data += nwcData.read(1024)\r
- \r
- lyrics = data[1:lyricsLen-1]\r
- print 'lyrics ', lyrics\r
- return lyrics\r
- \r
-def getDuration(data):\r
- durationBit = ord(data[2]) & 7\r
- durationDotBit = ord(data[6]) \r
- \r
- duration = durations[durationBit]\r
- if (durationDotBit & 1<<2):\r
- durationDot = '.'\r
- elif (durationDotBit & 1):\r
- durationDot = '..'\r
- else :\r
- durationDot = ''\r
- return duration + durationDot\r
-\r
-def getKey(data):\r
- data = binascii.hexlify(data)\r
- \r
- if (keysigs.has_key(data)):\r
- return '\key ' + keysigs[data]\r
- return '% unknown key'\r
-\r
-def getLocation(data):\r
- offset = ord(data[8]);\r
- if offset > 127 :\r
- return 256-offset\r
- if (ord(data[9])>>3 & 1):\r
- return -offset\r
- \r
- return offset\r
- #print 'offset ', offset\r
- #print binascii.hexlify(data[8:9])\r
-\r
-def getAccidental(data):\r
- data = ord(data)\r
- data = (data & 7 )\r
- return acdts[data]\r
-\r
-def getNote(data):\r
- \r
- # pitch\r
- pitch = getLocation(data)\r
- \r
- # get Accidentals\r
- accidental = getAccidental(data[9])\r
- \r
- # get Relative Duration\r
- duration = getDuration(data)\r
- \r
- # check stems\r
- stem = ord(data[4])\r
- stem = (stem >> 4) & 3\r
- \r
- # check beam\r
- beam = ord(data[4]) & 3\r
- \r
- # triplets \r
- triplet = triplets [ord(data[4])>>2 & 3 ]\r
- \r
- # check tie\r
- tie = ''\r
- if ord(data[6]) >> 4 & 1:\r
- tie = '~'\r
- \r
- staccato = (ord(data[6]) >> 1) & 1\r
- accent = (ord(data[6]) >> 5) & 1\r
- \r
- tenuto = (ord(data[7]) >> 2) & 1\r
- slur = slurs[ord(data[7]) & 3 ]\r
- grace = (ord(data[7]) >> 5) & 1\r
- \r
- # check slur\r
- slur = slurs[ord(data[7]) & 3 ]\r
- \r
- \r
- #TODO should use a dictionary\r
- return (pitch, accidental, duration, stem, beam, triplet, slur, tie, grace, staccato, accent, tenuto)\r
-\r
-def getRelativePitch(lastPitch, pitch):\r
- octave = ''\r
- diff = pitch - lastPitch\r
- if diff>3:\r
- for i in range((diff-4 + 7)/7):\r
- octave += "'"\r
- if octave == '':\r
- octave += "'"\r
- elif diff<-3:\r
- for i in range((-diff-4 + 7)/7):\r
- octave += ","\r
- if octave == '':\r
- octave += ","\r
- return octave\r
- \r
-def durVal(duration):\r
- where = duration.find('.')\r
- \r
- if where>-1:\r
- durVal = 128/int (duration[:where])\r
- nextVal = durVal\r
- for i in range(len(duration)-where):\r
- nextVal = nextVal / 2\r
- durVal += nextVal\r
- \r
- else:\r
- durVal = 128/int (duration)\r
- return durVal\r
- \r
-def processStaff(nwcData):\r
- keysigCount =0\r
- timesigCount=0\r
- noteCount=0\r
- restCount=0\r
- staffCount=0\r
- barlineCount=0\r
- clefCount=0\r
- textCount =0\r
- dynamicCount = 0\r
- \r
- lastPitch = scale.index('c')\r
- lastDuration = 0\r
- lastClef = scale.index(clefs[0]) # index referenced to note of last clef\r
- lastStem = 0\r
- lastTimesig = 1\r
- \r
- lastKey = { 'c': '', #c \r
- 'd': '', 'e': '', 'f': '', 'g': '', 'a': '', 'b': '' }\r
- currentKey = lastKey.copy()\r
- \r
- data = 0\r
- token = 1\r
- \r
- result = ""\r
- result += "\n\t\\new Staff {\n\t\t"\r
- lastChord = 0\r
- lastGrace = 0;\r
- \r
- (noOfTokens,format, lyrics) = findStaffInfo(nwcData);\r
- if lyrics!='':\r
- #result += '\\lyricmode { ' + lyrics + '}'\r
- result += '%%Lyrics %%{ ' + lyrics + '%%}'\r
- \r
- if relativePitch: \r
- result+="\\relative c {"\r
- else :\r
- result+=" {"\r
- result+="\n\t\t\n\n\t\t"\r
- result+= '\n\t %% Staff %s \n\t' % staff \r
- \r
- #print "00112233445566778899\n"\r
- extra = ''\r
- # the juice\r
- \r
- while data!="":\r
- \r
- token += 1\r
- if token==noOfTokens:\r
- result += "\n\t\t}\n\t}\n\t"\r
- print "going next staff! %s" % nwcData.tell()\r
- break\r
- \r
- \r
- if nwcversion==1.7:\r
- nwcData.seek(2,1)\r
- \r
- data = nwcData.read(1)\r
- #print 'test', data\r
- \r
- # clef\r
- if data=='\x00':\r
- data = nwcData.read(6)\r
- clefCount += 1\r
- \r
- key = ord(data[2]) & 3 \r
- octave = ord(data[4]) & 3\r
- # print binascii.hexlify(data) , "CLEF? "\r
- lastClef = scale.index(clefs[key]) + octaves[octave]\r
- #TODO check for octave shifts _8\r
- lastClef += clefShift[octave] \r
- result += '\clef "' + clefNames[key] + clefOctave[octave]+ '"\n\t\t'\r
- \r
- # key signature\r
- elif data=='\x01':\r
- data = nwcData.read(12)\r
- keysigCount = keysigCount + 1\r
- \r
- #\r
- flatBits = ord(data[2])\r
- sharpBits = ord(data[4])\r
- \r
- for note in lastKey.keys():\r
- noteInd = ['a','b','c','d','e','f','g'].index(note)\r
- if (flatBits >> noteInd & 1):\r
- lastKey[note] = 'es'\r
- elif (sharpBits >> noteInd & 1):\r
- lastKey[note] = 'is'\r
- else:\r
- lastKey[note] = ''\r
- \r
- currentKey = lastKey.copy()\r
- result = result + getKey(data[1:5]) + "\n\t\t"\r
- \r
- #print "data", binascii.hexlify(data)\r
- #print "flat", binascii.hexlify(flatBits)\r
- #print "sharp", binascii.hexlify(data[4])\r
- #print getKey(data[1:5])\r
- \r
- \r
- # barline\r
- elif data=='\x02':\r
- data = nwcData.read(4)\r
- barlineCount += 1\r
- \r
- currentKey = lastKey.copy()\r
- \r
- result += "|\n\t\t"\r
- if (barlineCount % barLinesComments == 0):\r
- result += "\n\t\t% Bar " + str(barlineCount + 1) + "\n\t\t" \r
- #print '.',\r
- print "Bar ",barlineCount, " completed, "\r
- \r
- # timesig\r
- elif data=='\x05':\r
- data = nwcData.read(8)\r
- timesigCount = timesigCount + 1 \r
- beats = ord(data[2])\r
- beatValues = [ 1, 2, 4, 8 ,6, 32 ]\r
- beatValue = beatValues[ord(data[4])]\r
- timesig = str(beats) + "/" + str(beatValue)\r
- lastTimesig = timesigValues[timesig]\r
- result += "\\time " + timesig + " "\r
- # Tempo\r
- elif data=='\x06':\r
- print "Tempo"\r
- data = nwcData.read(7)\r
- tempo = readLn(nwcData)\r
- result += '\n\t\t%%tempo %s\n\t\t' % tempo\r
- #tempoCount = tempoCount + 1 \r
- \r
- # note\r
- elif data=='\x08':\r
- data = nwcData.read(10)\r
- noteCount = noteCount + 1\r
- \r
- if debug: print binascii.hexlify(data) , noteCount , nwcData.tell()\r
- (pitch, accidental, duration, stem, beam, triplet, slur, tie, grace, staccato, accent, tenuto) = getNote(data)\r
- \r
- \r
- articulate = ''\r
- if staccato: articulate+= '-.'\r
- if accent: articulate+= '->'\r
- if tenuto: articulate+= '--'\r
- \r
- beam = beams[beam]\r
- \r
- chordMatters = ''\r
- if lastChord>0 and beam==']' :\r
- chordMatters = ' } >> '\r
- lastChord = 0\r
- elif lastChord>0:\r
- \r
- print 'CHORd', durVal(duration), lastChord\r
- dur = durVal(duration)\r
- #lastChord = 1.0/((1.0 / lastChord) - (1.0/durVal(duration)))\r
- lastChord -= dur \r
- \r
- if lastChord <= 0 :\r
- chordMatters = ' } >> '\r
- lastChord = 0\r
- \r
- \r
- \r
- if not insertBeaming: beam = ''\r
- \r
- # pitch\r
- pitch += lastClef\r
- note = scale[pitch]\r
- \r
- \r
- # get Accidentals\r
- #print 'accidental',accidental\r
- if (accidental!='auto'):\r
- currentKey[note[0]] = accidental\r
- accidental = currentKey[note[0]]\r
- \r
- if (relativePitch):\r
- octave = getRelativePitch(lastPitch, pitch)\r
- lastPitch = pitch\r
- else:\r
- octave = note[1:]\r
- pitch = note[0] + accidental + octave\r
- \r
- # get Relative Duration\r
- if (relativeDuration):\r
- if (lastDuration==duration):\r
- duration = ''\r
- else:\r
- lastDuration = duration\r
- \r
- # check stems\r
- if insertSteming and (stem!= lastStem) :\r
- lastStem = stem\r
- stem = stems[stem]\r
- else :\r
- stem = ''\r
- \r
- # normal note\r
- if extra!='':\r
- extra = '-"' + extra + '"'\r
- \r
- if grace and not lastGrace: result += "\\acciaccatura { "\r
- \r
- if not grace and lastGrace: result += " } "\r
- result += triplet[0] + stem + pitch + duration + articulate + extra \r
- result += slur + tie + beam + triplet[1] +chordMatters + " "\r
- \r
- \r
- # reset\r
- lastGrace = grace\r
- extra = ''\r
- # rest\r
- elif data=='\x09':\r
- data = nwcData.read(10)\r
- restCount = restCount + 1 \r
- \r
- # get Relative Duration\r
- duration = getDuration(data)\r
- if duration == '1':\r
- duration = lastTimesig\r
- if (relativeDuration):\r
- if (lastDuration==duration):\r
- duration = ''\r
- else:\r
- lastDuration = duration\r
- \r
- result = result + 'r' + duration + " "\r
-\r
- # text\r
- elif data=='\x11':\r
- textCount = textCount + 1\r
- data = nwcData.read(2) #pad\r
- textpos = nwcData.read(1)\r
- data = nwcData.read(2) #pad\r
- text = ''\r
- data = nwcData.read(1)\r
- while data!='\x00':\r
- text += data\r
- data = nwcData.read(1)\r
- \r
- #if text.isdigit() : # check numbers\r
- # text = "-\\markup -\\number "+ text \r
- # #text = "-\\markup {\\number "+ text +"}"\r
- #else :\r
- # text = '-"' + text + '"'\r
- \r
- extra += ' ' + text\r
- \r
- # dynamics\r
- elif data=='\x07':\r
- dynamicCount = dynamicCount + 1\r
- data = nwcData.read(9)\r
- # chord\r
- elif data=='\x0A' or data=='\x12':\r
- \r
- data = nwcData.read(12)\r
- \r
- chordAmt = ord(data[10])\r
- chords = []\r
- chordDur = getDuration(data)\r
- #print 'duration',chordDur\r
- \r
- #print "WARNING Chord support is experimental", chordDur, chordAmt\r
- #print binascii.hexlify(data), 'barlines ', barlineCount\r
- #print 'no. of notes in chord' , chordAmt\r
- \r
- chord1 = []\r
- chord2 = []\r
- \r
- for i in range(chordAmt):\r
- # rest or note\r
- what = nwcData.read(1)\r
- \r
- data = nwcData.read(10)\r
- ha = getNote(data)\r
- #print 'data ', ha\r
- (pitch, accidental, duration, stem, beam, triplet, slur, tie, grace, staccato, accent, tenuto) = ha\r
- # add to list\r
- if ha[2] == chordDur:\r
- chord1.append( (pitch,accidental ) )\r
- if beam==0 or beam ==4 :\r
- lastChord = 0\r
- \r
- \r
- else : # 2 voices\r
- chord2.append( (pitch,accidental, duration) )\r
- lastChord = durVal(duration) \r
- \r
- \r
- if len(chord2)==0: # block chord\r
- result += ' <'\r
- for i in range(len(chord1)):\r
- (pitch,accidental ) = chord1[i]\r
- pitch += lastClef\r
- note = scale[pitch]\r
- \r
- if (accidental!='auto'):\r
- currentKey[note[0]] = accidental\r
- accidental = currentKey[note[0]]\r
- \r
- if (relativePitch):\r
- octave = getRelativePitch(lastPitch, pitch)\r
- lastPitch = pitch\r
- else:\r
- octave = note[1:]\r
- result += note[0] + accidental + octave + ' '\r
- result += '>' +chordDur +' '\r
- lastPitch = chord1[0][0] + lastClef\r
- else: # 2 voices\r
- result += ' << ' \r
- for i in range(len(chord2)):\r
- (pitch,accidental,duration ) = chord2[i]\r
- pitch += lastClef\r
- note = scale[pitch]\r
- \r
- if (accidental!='auto'):\r
- currentKey[note[0]] = accidental\r
- accidental = currentKey[note[0]]\r
- \r
- if (relativePitch):\r
- octave = getRelativePitch(lastPitch, pitch)\r
- lastPitch = pitch\r
- else:\r
- octave = note[1:]\r
- result += note[0] + accidental + octave + duration + ' '\r
- \r
- result += " \\\\ {"\r
- \r
- for i in range(len(chord1)):\r
- (pitch,accidental ) = chord1[i]\r
- pitch += lastClef\r
- note = scale[pitch]\r
- \r
- if (accidental!='auto'):\r
- currentKey[note[0]] = accidental\r
- accidental = currentKey[note[0]]\r
- \r
- if (relativePitch):\r
- octave = getRelativePitch(lastPitch, pitch)\r
- lastPitch = pitch\r
- else:\r
- octave = note[1:]\r
- result += note[0] + accidental + octave +chordDur + ' '\r
- if lastChord >0 : lastChord = durVal(duration) - durVal(chordDur)\r
- if lastChord==0: result += ' } >> '\r
- # end 2 voices\r
- lastDuration = chordDur\r
- \r
- # check if duration / stem / same \r
- # < >duration ties,beam, slurs\r
- \r
- # Pedal\r
- elif data=='\x0b':\r
- print 'Ped '\r
- data = data = nwcData.read(5)\r
- # midi control instruction / MPC\r
- elif data=='\x0d':\r
- print 'midi control instruction'\r
- data = data = nwcData.read(36)\r
- # fermata / Breath mark\r
- elif data=='\x0E':\r
- print "Fermata"\r
- data = nwcData.read(6)\r
- extra += "\\fermata" \r
- \r
- # Dynamics\r
- elif data=='\x0f':\r
- print "Dynamics"\r
- data = nwcData.read(5)\r
- \r
- # Performance Style\r
- elif data=='\x10':\r
- print "Performance Style"\r
- data = nwcData.read(5)\r
- \r
- # Instrument Patch\r
- elif data=='\x04':\r
- print "Instrument Patch"\r
- data = nwcData.read(10)\r
- \r
- \r
- # todo\r
- else :\r
- print "WARNING: Unrecognised token ",binascii.hexlify(data), " at #", nwcData.tell(), " at Token", token\r
- \r
- \r
- \r
- # output converted file?\r
- print "\nStats"\r
- print keysigCount, " keysigCount found"\r
- print noteCount, " notes found"\r
- print staffCount, " staffCount found"\r
- print clefCount, " clefCount found"\r
- print barlineCount, " barlineCount found"\r
- print timesigCount, " timesigCount found"\r
- print textCount, " textCount found"\r
- print dynamicCount, " dynamicCount found"\r
- print restCount, " restCount found"\r
- \r
- #print "\nLilypond format:\n"\r
- return result\r
- \r
-# Variables\r
-keysigs = {\r
-'00000000' : 'c \major % or a \minor' ,\r
-'00000020' : 'g \major % or e \minor' ,\r
-'00000024': 'd \major % or b \minor' ,\r
-'00000064' : 'a \major % or fis \minor' ,\r
-'0000006c' : 'e \major % or cis \minor' ,\r
-'0000006d' : 'b \major % or gis \minor' ,\r
-'0000007d' : 'fis \major % or dis \minor' ,\r
-'0000007f' : 'cis \major % or ais \minor' ,\r
-'00020000' : 'f \major % or d \minor' ,\r
-'00120000' : 'bes \major % or g \minor' ,\r
-'00130000' : 'ees \major % or c \minor' ,\r
-'001b0000' : 'aes \major % or f \minor' ,\r
-'005b0000' : 'des \major % or bes \minor' ,\r
-'005f0000' : 'ges \major % or ees \minor' ,\r
-'007f0000' : 'ces \major % or a \minor'\r
-}\r
-\r
-acdts = ( 'is', 'es', '' ,'isis', 'eses', 'auto' ) \r
-\r
-clefs = { 0 : "b'",\r
- 1 : "d",\r
- 2 : "c'",\r
- 3 : "a'",\r
- }\r
-\r
-clefNames = { 0: 'treble',\r
- 1: 'bass',\r
- 2: 'alto',\r
- 3: 'tenor',\r
- }\r
-octaves = { 0: 0, 1:7, 2:-7 }\r
-scale = [ # this list is taken from lilycomp\r
- "c,,,","d,,,","e,,,","f,,,","g,,,","a,,,","b,,,",\r
- "c,,","d,,","e,,","f,,","g,,","a,,","b,,",\r
- "c,","d,","e,","f,","g,","a,","b,",\r
- "c","d","e","f","g","a","b",\r
- "c'","d'","e'","f'","g'","a'","b'",\r
- "c''","d''","e''","f''","g''","a''","b''",\r
- "c'''","d'''","e'''","f'''","g'''","a'''","b'''",\r
- "c''''","d''''","e''''","f''''","g''''","a''''","b''''",\r
- ]\r
-\r
-stems = [ '\stemNeutral ', '\stemUp ', '\stemDown ']\r
-\r
-slurs = [ '', '(' , ')', '' ]\r
-\r
-triplets = [ \r
- ('' , '' ),\r
- ( '\\times 2/3 { ', '') ,\r
- ('' , '' ),\r
- ('' , ' }' ),\r
- ]\r
-\r
-durations = ( '1','2','4','8','16','32','64' ) \r
-\r
-barlines = (\r
- '|', # 'Single'\r
- '||', # 'Double'\r
- '.|', # SectionOpen\r
- '|.', # SectionClose\r
- '|:', # MasterRepeatOpen\r
- ':|', # MasterRepeatClose\r
- '|:', # LocalRepeatOpen\r
- ':|', # LocalRepeatClose\r
-)\r
-\r
-ending = (\r
-'|.', # SectionClose\r
-':|', # MasterRepeatClose\r
-'|', # 'Single'\r
-'||', # 'Double'\r
-'' # Open hidden\r
-)\r
-\r
-staffType = (\r
-'Standard' , # Standard\r
-'Upper Grand Staff' , # Upper Grand Staff\r
-'Lower Grand Staff' , # Lower Grand Staff\r
-'Orchestra' , # Orchestra\r
-)\r
-\r
-\r
-beams = [ '', '[', '',']' ]\r
-\r
-\r
-# end ' \bar "|."'\r
-\r
-# Notation Properties\r
-# extra accidental spacing\r
-# extra note spacing\r
-# muted\r
-# no ledger lines\r
-# slurdirection\r
-# tiedirection\r
-# lyricsyllable\r
-# visability \r
-# show printed\r
-# item color\r
-\r
-#dynamics\r
-# cmd = DynamicVariance\r
-# Decrescendo \setTextCresc \<\r
-# setTextCresc Crescendo \> setHairpinCresc\r
-# Dynamics stop '\! '\r
-# style = ff pp\r
-\r
-clefOctave = [ '' , '^8', '_8' , '' ]\r
-clefShift = [0,7,-7, 0]\r
-\r
-\r
-# '#(set-accidental-style '#39'modern-cautionary)'\r
-#(ly:set-point-and-click 'line-column)\r
-#(set-global-staff-size 20)\r
-\r
-timesigValues = { \r
- '4/4' : '1', '3/4' : '2.', '2/4' : '2', '1/4' : '1',\r
- '1/8' : '8', '2/8' : '4', '3/8' : '4.', '6/8' : '2.',\r
- '4/8' : '2', '9/8' : '12', '12/8' : '1',\r
- '2/2' : '1', '4/2' : '0', '1/2' : '2', \r
- }\r
-\r
-print "python nwc2ly is running..."\r
-try:\r
-\r
- nwcData = open( nwcfile,'rb')\r
- \r
- # check if its a readable nwc format\r
- # compressed - [NWZ]\r
- # uncompressed - [NoteWorthy ArtWare] [NoteWorthy Composer]\r
- format = nwcData.read(5)\r
- if format== '[NWZ]':\r
- nwcData.seek(1,1)\r
- print 'Compressed NWC detected!'\r
- print 'Dumping to uncompressed NWC format and attemping conversion soon...'\r
- uncompress = open ('uncompressed.nwc','wb')\r
- uncompress.write(zlib.decompress(nwcData.read()))\r
- uncompress.close()\r
- print 'Inflating done. Now opening new file...'\r
- nwcData.close()\r
- nwcData = open( 'uncompressed.nwc','rb')\r
- nwcData.seek(6)\r
- elif format!= '[Note':\r
- print 'Unknown format, please use an uncompress NWC format and try again.' \r
- sys.exit()\r
- \r
- \r
- \r
- resultFile = '%% Generated from python nwc2ly converter v%s by Joshua Koo (joshuakoo@myrealbox.com)' % nwc2lyversion\r
- resultFile += '\n\n\\version "2.4.0"'\r
- resultFile += "\n"\r
- \r
- \r
- # START WORK\r
- getFileFormat(nwcData)\r
- resultFile+=getFileInfo(nwcData)\r
- resultFile+= "\n\n\\score {"\r
- resultFile+= "\n\t<<\n\t\t"\r
- \r
- getPageSetup(nwcData)\r
- \r
- noOfStaffs = findNoOfStaff(nwcData);\r
- \r
- for staff in range(1,noOfStaffs+1):\r
- print "\n\nWorking on Staff", staff\r
- result = processStaff(nwcData)\r
- #print result\r
- resultFile += result\r
- \r
- resultFile+= "\n\t>>"\r
- resultFile+= "\n\t\layout {}"\r
- resultFile+= "\n\t\midi {}"\r
- resultFile+= "\n}"\r
- nwcData.close()\r
- \r
- if lyfile=='':\r
- print 'Dumping output file to screen'\r
- print resultFile\r
- else :\r
- write = open( lyfile ,'w')\r
- write.write (resultFile)\r
- write.close()\r
-\r
-except IOError:\r
- print 'File does not exist or an IO error occurred'\r
-except Exception, e: #KeyError\r
- print "Error while reading data at ", nwcData.tell() ,"\n"\r
- print 'Dumping whatever result first'\r
- print resultFile\r
- print result\r
- print\r
- traceback.print_exc()\r
-\r
-print\r
-print\r
-print "Please send all bugs and requests to joshuakoo@myrealbox.com"\r
+#!/usr/bin/env python
+#
+# Copyright (C) 2010 W.Trevor King (wking @ drexel.edu)
+# 2005 Joshua Koo (joshuakoo @ myrealbox.com)
+#
+# 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., 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
+
+ * 30 November 2010. Cleanup to more Pythonic syntax.
+
+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'
+
+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'),
+ ('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'),
+ ('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,
+ }
+
+ 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)):
+ next_note = t
+ break
+ if isinstance(token, (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))
+ 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
+ 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,
+ 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']
+
+ @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]
+
+
+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'),
+ ]
+
+ def ly_text(self):
+ """
+ http://lilypond.org/doc/v2.11/Documentation/user/lilypond/Displaying-pitches#Clef
+ """
+ return r'\clef %s' % self['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 NWCToken_bar (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ ('unknown3', 'B'),
+ ('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
+ return r'\bar "%s"' % self._bars[type]
+ #|Bar|Style:SectionClose|SysBreak:Y
+
+
+class NWCToken_3 (Structure):
+ _fields = []
+ for i in range(4):
+ _fields.append(('unknown%d' % (i+1), 'B'))
+
+
+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'),
+ ]
+
+ _sigs = {
+ '4/4': '1', '3/4': '2.', '2/4': '2', '1/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',
+ }
+
+ 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 NWCToken_tempo (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ ('unknown3', 'B'),
+ ('unknown4', 'B'),
+ ('beats_per_minute', 'B'),
+ ('unknown6', 'B'),
+ ('unknown7', 'B'),
+ ('text', 'S'),
+ ]
+
+ def ly_text(self, time_sig=None):
+ """
+ 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['']
+ return r'\tempo "%s" %s = %s' % (
+ self['text'], beats_per_measure, self['beats_per_minute'])
+
+
+class NWCToken_dynamics1 (Structure):
+ _fields = [
+ ('unknown1', 'B'),
+ ('unknown2', 'B'),
+ ('unknown3', 'B'),
+ ('unknown4', 'B'),
+ ('unknown5', 'B'),
+ ('unknown6', 'B'),
+ ('unknown7', 'B'),
+ ('unknown8', 'B'),
+ ('unknown9', 'B'),
+ ]
+
+
+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 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',
+ }
+
+ _slurs = {
+ 'none': '',
+ 'start': r'\(',
+ 'stop': r'\)',
+ 'inside': '',
+ }
+
+ def ly_text(self, clef=None, key=None,
+ previous_note=None, next_note=None,
+ in_chord=False):
+ """
+ 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])
+
+ 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)
+
+
+class NWCToken_chord1 (Structure):
+ _fields = NWCToken_note._fields + [
+ ('num_notes', 'B'),
+ ('unknown12', 'B'),
+ ]
+
+ def unpack_from(self, buffer, offset=0):
+ super(NWCToken_chord1, 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
+ """
+ notes = []
+ 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(']')
+
+ if self['triplet'] == 'start':
+ chord.insert(0, r'\times 2/3 { ')
+ elif self['triplet'] == 'stop':
+ chord.append(' }')
+
+ 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'))
+
+
+class NWCToken_midi_MPC (Structure):
+ _fields = []
+ for i in range(36):
+ _fields.append(('unknown%d' % (i+1), 'B'))
+
+
+class NWCToken_fermata (Structure):
+ _fields = []
+ for i in range(6):
+ _fields.append(('unknown%d' % (i+1), '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
+ """
+ return r'\fermata'
+
+
+class NWCToken_dynamics2 (Structure):
+ _fields = []
+ for i in range(5):
+ _fields.append(('unknown%d' % (i+1), 'B'))
+
+
+class NWCToken_performance_style (Structure):
+ _fields = []
+ for i in range(5):
+ _fields.append(('unknown%d' % (i+1), 'B'))
+
+
+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
+ return r'\mark "%s"' % self['text']
+
+
+class NWCToken_chord2 (NWCToken_chord1):
+ 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(), ''])
+
+ 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 {}',
+ '}',
+ ''])
+ 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()