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
break
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))
else:
blockChord = None
@ -144,11 +144,12 @@ class Document:
self.tempo = (root.find('tempo').text if root.find(
'tempo') is not None else None)
def newFromXML(filepath):
@classmethod
def newFromXML(cls, filepath):
"""
Create a new Document object directly from an XML file.
"""
doc = Document()
doc = cls()
doc.loadXML(filepath)
return doc
@ -200,3 +201,92 @@ class Document:
tree = ET.ElementTree(root)
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
\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
\subtitle Sade
@ -17,5 +18,7 @@
AM9,8 F#m11,8 DM7,1.5
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
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):
if self.saveWarning(): # ask the user if they want to save
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:
self.openFile(filePath)
@ -323,7 +323,14 @@ class DocumentWindow(QMainWindow):
Opens a file from a file path and sets up the window accordingly.
"""
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.setPath("workingPath", self.currentFilePath)
self.UIInitDocument()
@ -570,7 +577,8 @@ class DocumentWindow(QMainWindow):
if b.chord:
if b.chord.name == oldName:
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()
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