Browse Source

add read support for simple macro language

fix minor bugs
(second attempt at commit)
master
Ivan Holmes 5 years ago
parent
commit
5aac931f51
  1. 96
      chordsheet/document.py
  2. 3
      examples/kissoflife.cma
  3. 2
      examples/kissoflife.xml
  4. 14
      gui.py
  5. 37
      linux.spec

96
chordsheet/document.py

@ -119,7 +119,7 @@ class Document:
blockChord = c blockChord = c
break break
if blockChord is None: if blockChord is None:
exit("Chord {c} does not match any chord in {l}.".format(
raise ValueError("Chord {c} does not match any chord in {l}.".format(
c=blockChordName, l=self.chordList)) c=blockChordName, l=self.chordList))
else: else:
blockChord = None blockChord = None
@ -144,11 +144,12 @@ class Document:
self.tempo = (root.find('tempo').text if root.find( self.tempo = (root.find('tempo').text if root.find(
'tempo') is not None else None) 'tempo') is not None else None)
def newFromXML(filepath):
@classmethod
def newFromXML(cls, filepath):
""" """
Create a new Document object directly from an XML file. Create a new Document object directly from an XML file.
""" """
doc = Document()
doc = cls()
doc.loadXML(filepath) doc.loadXML(filepath)
return doc return doc
@ -200,3 +201,92 @@ class Document:
tree = ET.ElementTree(root) tree = ET.ElementTree(root)
tree.write(filepath) tree.write(filepath)
def loadCSMacro(self, filepath):
"""
Read a Chordsheet Macro file and import its contents.
"""
self.chordList = []
self.sectionList = []
aliasTable = {}
def chord(args):
argList = args.split(" ")
chordName = argList.pop(0)
self.chordList.append(Chord(parseName(chordName)))
argIter = iter(argList)
subCmdsArgs = list(zip(argIter, argIter))
for subCmd, arg in subCmdsArgs:
if subCmd == "alias":
aliasTable[arg] = chordName
else:
self.chordList[-1].voicings[subCmd] = parseFingering(arg, subCmd)
def section(args):
blockList = []
sectionName, blocks = [arg.strip() for arg in args.split("\n", 1)]
self.sectionList.append(Section(name=sectionName))
for b in blocks.split():
blockParams = b.split(",")
blockLength = float(blockParams[1])
if blockParams[0] in aliasTable:
blockChordName = aliasTable[blockParams[0]]
else:
blockChordName = blockParams[0]
blockChordName = parseName(blockChordName) if blockChordName not in ["NC", "X"] else None
blockChord = None
if blockChordName:
for c in self.chordList:
if c.name == blockChordName:
blockChord = c
break
if blockChord is None:
raise ValueError("Chord {c} does not match any chord in {l}.".format(
c=blockChordName, l=self.chordList))
blockList.append(Block(blockLength, chord=blockChord))
self.sectionList[-1].blockList = blockList
with open(filepath, 'r') as f:
cmatext = f.read()
cmaCmdsArgs = [statement.split(" ", 1) for statement in \
(rawStatement.strip() for rawStatement in cmatext.split("\\")[1:])]
for cmd, args in cmaCmdsArgs:
if cmd == "chordsheet":
# There's only one version so no need to do anything with this
pass
elif cmd == "title":
self.title = args
elif cmd == "subtitle":
self.subtitle = args
elif cmd == "arranger":
self.arranger = args
elif cmd == "composer":
self.composer = args
elif cmd == "timesig":
self.timeSignature = int(args)
elif cmd == "tempo":
self.tempo = args
elif cmd == "chord":
chord(args)
elif cmd == "section":
section(args)
elif cmd in ["!", "rem"]:
# Simply ignore comments
pass
else:
raise ValueError(f"Command {cmd} not understood.")

3
examples/kissoflife.cma

@ -1,4 +1,5 @@
\chordsheet 1 \chordsheet 1
\rem This is a comment to say that the first line isn't really needed (as there's only one version of Chordsheet Macro so far)
\title Kiss of Life \title Kiss of Life
\subtitle Sade \subtitle Sade
@ -17,5 +18,7 @@
AM9,8 F#m11,8 DM7,1.5 AM9,8 F#m11,8 DM7,1.5
C#m7,2 Bm7,4.5 F#m11,8 C#m7,2 Bm7,4.5 F#m11,8
\! This is another comment. Comments may not exist in the middle of other statements.
\section Chorus and bridge \section Chorus and bridge
Bm7,3.5 F,4.5 Bm7,3.5 F#m11,4.5 Bm7,3.5 F,4.5 Bm7,3.5 F#m11,4.5

2
examples/kissoflife.xml

@ -1 +1 @@
<chordsheet><title>Kiss of Life</title><subtitle>Sade</subtitle><arranger>Ivan Holmes</arranger><composer>Sade Adu, Paul S. Denman, Andrew Hale, Stuart Matthewman</composer><timesignature>4</timesignature><chords><chord><name>AM9</name><voicing instrument="piano">A,B,C&#9839;,E</voicing></chord><chord><name>F&#9839;m11</name><voicing instrument="piano">A,B,C&#9839;,E</voicing></chord><chord><name>DM7</name><voicing instrument="piano">A,C&#9839;,F&#9839;</voicing></chord><chord><name>C&#9839;m7</name><voicing instrument="piano">G&#9839;,B,E</voicing></chord><chord><name>Bm7</name><voicing instrument="piano">A,D,F&#9839;</voicing></chord></chords><section name="Intro and verse"><block><length>8.0</length><chord>AM9</chord></block><block><length>8.0</length><chord>F&#9839;m11</chord></block><block><length>1.5</length><chord>DM7</chord></block><block><length>2.0</length><chord>C&#9839;m7</chord></block><block><length>4.5</length><chord>Bm7</chord></block><block><length>8.0</length><chord>F&#9839;m11</chord></block></section><section name="Chorus and bridge"><block><length>3.5</length><chord>Bm7</chord></block><block><length>4.5</length><chord>F&#9839;m11</chord></block><block><length>3.5</length><chord>Bm7</chord></block><block><length>4.5</length><chord>F&#9839;m11</chord></block></section></chordsheet>
<chordsheet><title>Kiss of Life</title><subtitle>Sade</subtitle><arranger>Ivan Holmes</arranger><composer>Sade Adu, Paul S. Denman, Andrew Hale, Stuart Matthewman</composer><timesignature>4</timesignature><tempo>120</tempo><chords><chord><name>AM9</name><voicing instrument="piano">A,B,C&#9839;,E</voicing></chord><chord><name>F&#9839;m11</name><voicing instrument="piano">A,B,C&#9839;,E</voicing></chord><chord><name>DM7</name><voicing instrument="piano">A,C&#9839;,F&#9839;</voicing></chord><chord><name>C&#9839;m7</name><voicing instrument="piano">G&#9839;,B,E</voicing></chord><chord><name>Bm7</name><voicing instrument="piano">A,D,F&#9839;</voicing></chord></chords><section name="Intro and verse"><block><length>8.0</length><chord>AM9</chord></block><block><length>8.0</length><chord>F&#9839;m11</chord></block><block><length>1.5</length><chord>DM7</chord></block><block><length>2.0</length><chord>C&#9839;m7</chord></block><block><length>4.5</length><chord>Bm7</chord></block><block><length>8.0</length><chord>F&#9839;m11</chord></block></section><section name="Chorus and bridge"><block><length>3.5</length><chord>Bm7</chord></block><block><length>4.5</length><chord>F&#9839;m11</chord></block><block><length>3.5</length><chord>Bm7</chord></block><block><length>4.5</length><chord>F&#9839;m11</chord></block></section></chordsheet>

14
gui.py

@ -314,7 +314,7 @@ class DocumentWindow(QMainWindow):
def menuFileOpenAction(self): def menuFileOpenAction(self):
if self.saveWarning(): # ask the user if they want to save if self.saveWarning(): # ask the user if they want to save
filePath = QFileDialog.getOpenFileName(self.window.tabWidget, 'Open file', self.getPath( filePath = QFileDialog.getOpenFileName(self.window.tabWidget, 'Open file', self.getPath(
"workingPath"), "Chordsheet ML files (*.xml *.cml)")[0]
"workingPath"), "Chordsheet Markup Language files (*.xml *.cml);;Chordsheet Macro files (*.cma)")[0]
if filePath: if filePath:
self.openFile(filePath) self.openFile(filePath)
@ -323,7 +323,14 @@ class DocumentWindow(QMainWindow):
Opens a file from a file path and sets up the window accordingly. Opens a file from a file path and sets up the window accordingly.
""" """
self.currentFilePath = filePath self.currentFilePath = filePath
self.doc.loadXML(self.currentFilePath)
fileExt = os.path.splitext(self.currentFilePath)[1].lower()
if fileExt == ".cma":
self.doc.loadCSMacro(self.currentFilePath)
else: # if fileExt in [".xml", ".cml"]:
self.doc.loadXML(self.currentFilePath)
self.lastDoc = copy(self.doc) self.lastDoc = copy(self.doc)
self.setPath("workingPath", self.currentFilePath) self.setPath("workingPath", self.currentFilePath)
self.UIInitDocument() self.UIInitDocument()
@ -570,7 +577,8 @@ class DocumentWindow(QMainWindow):
if b.chord: if b.chord:
if b.chord.name == oldName: if b.chord.name == oldName:
b.chord.name = cName b.chord.name = cName
self.window.blockTableView.populate(self.currentSection.blockList)
if self.currentSection and self.currentSection.blockList:
self.window.blockTableView.populate(self.currentSection.blockList)
self.clearChordLineEdits() self.clearChordLineEdits()
def removeSectionAction(self): def removeSectionAction(self):

37
linux.spec

@ -0,0 +1,37 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['gui.py'],
pathex=['/home/ivan/Code/chordsheet'],
binaries=[],
datas=[
('fonts', 'fonts'),
('ui', 'ui')
],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='chordsheet',
icon='ui/icon.ico',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False )
Loading…
Cancel
Save