Merge Hans' 1.0 version of nwc2ly.py into my new architecture. master
authorW. Trevor King <wking@drexel.edu>
Wed, 1 Dec 2010 12:18:36 +0000 (07:18 -0500)
committerW. Trevor King <wking@drexel.edu>
Wed, 1 Dec 2010 12:18:36 +0000 (07:18 -0500)
1  2 
nwc2ly.py

diff --cc nwc2ly.py
index ee29a1f0fd73ba992705efb157a7c6d0617ba758,3dc8db97fd0a97a36c01f48c5379832d7e648b19..6f2e58ecd09196271f8595823014c6ec9f7a62e1
mode 100755,100644..100755
+++ b/nwc2ly.py
 -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()