Browse Source

various lilypond stuff

master
Nils 4 years ago
parent
commit
f1ebbb01bc
  1. 27
      engine/api.py
  2. 70
      engine/items.py
  3. 16
      engine/main.py
  4. 17
      qtgui/designer/mainwindow.py
  5. 19
      qtgui/designer/mainwindow.ui
  6. 10
      qtgui/items.py
  7. 8
      qtgui/menu.py
  8. 2
      qtgui/scoreview.py
  9. 45
      qtgui/submenus.py

27
engine/api.py

@ -25,6 +25,7 @@ import logging; logging.info("import {}".format(__file__))
#Python Standard Library
import sys
import random
from typing import Iterable, Callable, Tuple
#Third Party Modules
@ -2325,6 +2326,11 @@ def midiRelativeChannelReset():
_applyToSelection("midiRelativeChannelReset")
else:
_applyToItem("midiRelativeChannelReset")
#Lilypond
def lilypondText(text):
insertItem(items.LilypondText(text))
def exportLilypond(absoluteFilePath):
lilypond.saveAsLilypond(session.data, absoluteFilePath)
@ -2332,6 +2338,27 @@ def exportLilypond(absoluteFilePath):
def showPDF():
lilypond.saveAsLilypondPDF(session.data, openPDF = True)
def getLilypondBarlineList()->Iterable[Tuple[str, Callable]]:
"""Return a list of very similar functions for a convenience menu in a GUI.
They are trivial so we don't need to create seperate api function for them individually.
the qt gui submenus ChooseOne uses this format."""
return [
#("Bold", lambda: lilypondText('\\bar "."')),
("Double", lambda: lilypondText('\\bar "||"')),
#("Bold Double", lambda: lilypondText('\\bar ".."')),
("Open", lambda: lilypondText('\\bar ".|"')),
("End/Open", lambda: lilypondText('\\bar "|.|"')),
("End", lambda: lilypondText('\\bar "|."')),
]
def getLilypondRepeatList()->Iterable[Tuple[str, Callable]]:
return [
("Open", lambda: lilypondText('\\bar ".|:"')),
("Close/Open", lambda: lilypondText('\\bar ":|.|:"')),
("Close", lambda: lilypondText('\\bar ":|."')),
]
#Debug
def printPitches():
track = session.data.currentTrack()

70
engine/items.py

@ -1,4 +1,4 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2017, Nils Hilbricht, Germany ( https://www.hilbricht.net )
@ -565,15 +565,21 @@ class Duration(object):
def lilypond(self):
"""Called by note.lilypond(), See Item.lilypond for the general docstring.
returns a number as string."""
if self.durationKeyword == D_TIE:
append = "~"
else:
append = ""
n = self.genericNumber
if n == 0:
return "\\breve"
return "\\breve" + append
elif n == -1:
return "\\longa"
return "\\longa" + append
elif n == -2:
return "\\maxima"
return "\\maxima" + append
else:
return str(n) + self.dots*"."
return str(n) + self.dots*"." + append
class DurationGroup(object):
"""Holds several durations and returns values meant for chords.
@ -661,9 +667,8 @@ class Item(object):
"explicit" : False,
}
def _secondInit(self, parentBlock):
"""see Score._secondInit"""
"""see Score._secondInit"""
def deserializeDurationAndNotelistInPlace(self, serializedObject):
"""Part of instanceFromSerializedData.
@ -686,7 +691,7 @@ class Item(object):
every child class!"""
assert cls.__name__ == serializedObject["class"]
self = cls.__new__(cls)
self.deserializeDurationAndNotelistInPlace(serializedObject)
self.deserializeDurationAndNotelistInPlace(serializedObject) #creates parentBlocks
self._secondInit(parentTrack = parentObject)
return self
@ -2822,3 +2827,52 @@ class RecordedNote(Item):
def _lilypond(self):
"""absolutely not"""
return ""
class LilypondText(Item):
"""lilypond text as a last resort"""
def __init__(self, text):
super().__init__()
self.text = text
self._secondInit(parentBlock = None) #On item creation there is no parentBlock. The block adds itself to the item during insert.
def _secondInit(self, parentBlock):
"""see Item._secondInit"""
super()._secondInit(parentBlock) #Item._secondInit
def _lilypond(self):
"""called by block.lilypond(), returns a string.
Don't create white-spaces yourself, this is done by the structures.
When in doubt prefer functionality and robustness over 'beautiful' lilypond syntax."""
return self.text
@classmethod
def instanceFromSerializedData(cls, serializedObject, parentObject):
"""see Score.instanceFromSerializedData"""
assert cls.__name__ == serializedObject["class"]
self = cls.__new__(cls)
self.text = serializedObject["text"]
self.deserializeDurationAndNotelistInPlace(serializedObject) #creates parentBlocks
self._secondInit(parentBlock = parentObject)
return self
def serialize(self):
result = super().serialize() #call this in child classes
result["text"] = self.text
return result
def _copy(self):
"""return an independent copy of self"""
new = LilypondText(self.text)
return new
def _exportObject(self, trackState):
return {
"type" : "LilypondText",
"completeDuration" : 0,
"tickindex" : trackState.tickindex, #we parse the tickindex after we stepped over the item.
"midiBytes" : [],
"text" : self.text,
"UIstring" : self.text, #this is for a UI, possibly a text UI, maybe for simple items of a GUI. Make it as short and unambigious as possible.
}

16
engine/main.py

@ -73,6 +73,7 @@ class Data(template.engine.sequencer.Score):
return tr
def trackById(self, trackId):
"""Also looks up hidden and deleted tracks"""
try:
ret = Track.allTracks[trackId]
except:
@ -835,10 +836,11 @@ class Data(template.engine.sequencer.Score):
def allItems(self, hidden = True, removeContentLinkedData = False):
seenItems = set()
if hidden:
what = Track.allTracks.values() #this includes hidden tracks
what = list(self.hiddenTracks.keys()) + self.tracks
#what = Track.allTracks.values() #this includes deleted tracks
else:
what = self.tracks
for track in what:
for block in track.blocks:
for item in block.data:
@ -1001,8 +1003,10 @@ class Data(template.engine.sequencer.Score):
def removeEmptyBlocks(self):
dictOfTrackIdsWithListOfBlockIds = {} # [trackId] = [listOfBlockIds]
for trId, track in Track.allTracks.items():
#for trId, track in Track.allTracks.items():
for track in list(self.hiddenTracks.keys()) + self.tracks:
trId = id(track)
listOfBlockIds = track.asListOfBlockIds()
for block in track.blocks:
if not block.data and len(track.blocks) > 1:
@ -1039,8 +1043,8 @@ class Data(template.engine.sequencer.Score):
assert cls.__name__ == serializedData["class"]
self = cls.__new__(cls)
super().copyFromSerializedData(parentSession, serializedData, self) #Tracks, parentSession and tempoMap
self.parentSession = parentSession
self.hiddenTracks = {Track.instanceFromSerializedData(track, parentData = self):originalIndex for track, originalIndex in serializedData["hiddenTracks"]}
self.parentSession = parentSession
self.hiddenTracks = {Track.instanceFromSerializedData(parentData=self, serializedData=track):originalIndex for track, originalIndex in serializedData["hiddenTracks"]}
self.tempoTrack = TempoTrack.instanceFromSerializedData(serializedData["tempoTrack"], parentData = self)
self.metaData = serializedData["metaData"]
self.currentMetronomeTrack = self.tracks[serializedData["currentMetronomeTrackIndex"]]

17
qtgui/designer/mainwindow.py

@ -2,12 +2,13 @@
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
# Created by: PyQt5 UI code generator 5.12.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
@ -437,6 +438,12 @@ class Ui_MainWindow(object):
self.actionRandom_in_scale_in_octave_around_cursor.setObjectName("actionRandom_in_scale_in_octave_around_cursor")
self.actionCreate_pool_from_selection = QtWidgets.QAction(MainWindow)
self.actionCreate_pool_from_selection.setObjectName("actionCreate_pool_from_selection")
self.actionLyBarline = QtWidgets.QAction(MainWindow)
self.actionLyBarline.setObjectName("actionLyBarline")
self.actionLyFree_Instruction = QtWidgets.QAction(MainWindow)
self.actionLyFree_Instruction.setObjectName("actionLyFree_Instruction")
self.actionLyRepeat = QtWidgets.QAction(MainWindow)
self.actionLyRepeat.setObjectName("actionLyRepeat")
self.menuObjects.addAction(self.actionMetrical_Instruction)
self.menuObjects.addAction(self.actionClef)
self.menuObjects.addAction(self.actionKey_Signature)
@ -612,6 +619,10 @@ class Ui_MainWindow(object):
self.menuToolbox.addAction(self.menuMIDI.menuAction())
self.menuLilypond.addAction(self.actionShow_PDF)
self.menuLilypond.addAction(self.actionExport_to_Ly)
self.menuLilypond.addSeparator()
self.menuLilypond.addAction(self.actionLyBarline)
self.menuLilypond.addAction(self.actionLyRepeat)
self.menuLilypond.addAction(self.actionLyFree_Instruction)
self.menubar.addAction(self.menuView.menuAction())
self.menubar.addAction(self.menuEdit_2.menuAction())
self.menubar.addAction(self.menu.menuAction())
@ -953,4 +964,8 @@ class Ui_MainWindow(object):
self.actionRandom_in_scale_in_cursor_plus_octave.setText(_translate("MainWindow", "Random in-scale in cursor plus octave (authentic mode)"))
self.actionRandom_in_scale_in_octave_around_cursor.setText(_translate("MainWindow", "Random in-scale in octave around cursor (hypo mode)"))
self.actionCreate_pool_from_selection.setText(_translate("MainWindow", "Create pool from selection"))
self.actionLyBarline.setText(_translate("MainWindow", "Barline"))
self.actionLyFree_Instruction.setText(_translate("MainWindow", "Free Instruction"))
self.actionLyRepeat.setText(_translate("MainWindow", "Repeat"))

19
qtgui/designer/mainwindow.ui

@ -315,6 +315,10 @@
</property>
<addaction name="actionShow_PDF"/>
<addaction name="actionExport_to_Ly"/>
<addaction name="separator"/>
<addaction name="actionLyBarline"/>
<addaction name="actionLyRepeat"/>
<addaction name="actionLyFree_Instruction"/>
</widget>
<addaction name="menuView"/>
<addaction name="menuEdit_2"/>
@ -1669,6 +1673,21 @@
<string>Create pool from selection</string>
</property>
</action>
<action name="actionLyBarline">
<property name="text">
<string>Barline</string>
</property>
</action>
<action name="actionLyFree_Instruction">
<property name="text">
<string>Free Instruction</string>
</property>
</action>
<action name="actionLyRepeat">
<property name="text">
<string>Repeat</string>
</property>
</action>
</widget>
<resources/>
<connections/>

10
qtgui/items.py

@ -28,6 +28,10 @@ from .constantsAndConfigs import constantsAndConfigs
from template.qtgui.helper import stretchRect
import engine.api as api
from template.engine import pitch
"""All svg items (execept pitch-related) graphics are expected to be
already on the correct position and will be inserter ON the middle line
That means g-clef needs to be 14 pxiels below the y=0 coordinate since
@ -667,7 +671,7 @@ class GuiKeySignature(GuiItem):
self.bigNatural.setParentItem(self)
self.bigNatural.setPos(constantsAndConfigs.magicPixel, constantsAndConfigs.stafflineGap * -1)
self.rootGlyph = QtWidgets.QGraphicsSimpleTextItem(constantsAndConfigs.baseNotes[self.staticItem["root"]])
self.rootGlyph = QtWidgets.QGraphicsSimpleTextItem(pitch.baseNotesToBaseNames[self.staticItem["root"]])
self.rootGlyph.setParentItem(self)
self.rootGlyph.setPos(constantsAndConfigs.negativeMagicPixel, 2*constantsAndConfigs.stafflineGap)
@ -916,7 +920,7 @@ def staticItem2Item(staticItem):
return GuiMetricalInstruction(staticItem)
elif typ is "BlockEndMarker":
return GuiBlockEndMarker(staticItem)
elif typ in ("InstrumentChange", "ChannelChange",):
return GuiGenericText(staticItem)
elif typ in ("InstrumentChange", "ChannelChange", "LilypondText"):
return GuiGenericText(staticItem)
else:
raise ValueError("Unknown Item Type:", staticItem)

8
qtgui/menu.py

@ -33,7 +33,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
import engine.api as api
from midiinput.stepmidiinput import stepMidiInput #singleton instance
from .constantsAndConfigs import constantsAndConfigs
from .submenus import SecondaryClefMenu, SecondaryKeySignatureMenu, SecondaryDynamicsMenu, SecondaryMetricalInstructionMenu, SecondaryTempoChangeMenu, SecondaryTemporaryTempoChangeMenu, SecondarySplitMenu, TransposeMenu, pedalNoteChooser, SecondaryProperties, SecondaryProgramChangeMenu, SecondaryChannelChangeMenu
from .submenus import SecondaryClefMenu, SecondaryKeySignatureMenu, SecondaryDynamicsMenu, SecondaryMetricalInstructionMenu, SecondaryTempoChangeMenu, SecondaryTemporaryTempoChangeMenu, SecondarySplitMenu, TransposeMenu, pedalNoteChooser, SecondaryProperties, SecondaryProgramChangeMenu, SecondaryChannelChangeMenu, ChooseOne, forwardText
class ModalKeys(object):
def __init__(self):
@ -294,6 +294,12 @@ class MenuActionDatabase(object):
#Midi
self.mainWindow.ui.actionInstrument_Change: SecondaryProgramChangeMenu(self.mainWindow), #no lambda for submenus. They get created here once and have a __call__ option that executes them. There is no internal state in these menus.
self.mainWindow.ui.actionChannel_Change: SecondaryChannelChangeMenu(self.mainWindow), #no lambda for submenus. They get created here once and have a __call__ option that executes them. There is no internal state in these menus.
#Lilypond
#Print and Export is in self.actions
self.mainWindow.ui.actionLyBarline: ChooseOne(self.mainWindow, "Choose a Barline", api.getLilypondBarlineList()),
self.mainWindow.ui.actionLyRepeat: ChooseOne(self.mainWindow, "Choose a Repeat", api.getLilypondRepeatList()),
self.mainWindow.ui.actionLyFree_Instruction: lambda: forwardText(self.mainWindow, "Enter Instruction", api.lilypondText),
}
self.modalActions = { #these are only available in Note Edit Mode, not in CC Edit Mode etc.

2
qtgui/scoreview.py

@ -31,7 +31,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets, QtOpenGL
from template.helper import onlyOne
#Our Modules
from .submenus import GridRhytmEdit
from .constantsAndConfigs import constantsAndConfigs
from .structures import GuiScore
import engine.api as api

45
qtgui/submenus.py

@ -20,6 +20,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from typing import Iterable, Callable, Tuple
from PyQt5 import QtCore, QtGui, QtWidgets
import engine.api as api
@ -149,10 +151,10 @@ class TickWidget(QtWidgets.QDialog):
class Submenu(QtWidgets.QDialog):
#TODO: instead of using a QDialog we could use a QWidget and use it as proxy widget on the graphic scene, placing the menu where the input cursor is.
def __init__(self, mainWindow, labelString):
super().__init__(mainWindow) #if you don't set the parent to the main window the whole screen will be the root and the dialog pops up in the middle of it.
super().__init__(mainWindow) #if you don't set the parent to the main window the whole screen will be the root and the dialog pops up in the middle of it.
#self.setModal(True) #we don't need this when called with self.exec() instead of self.show()
self.layout = QtWidgets.QFormLayout()
#self.layout = QtWidgets.QVBoxLayout()
#self.layout = QtWidgets.QVBoxLayout()
self.setLayout(self.layout)
label = QtWidgets.QLabel(labelString) #"Choose a clef" or so.
@ -180,14 +182,20 @@ class Submenu(QtWidgets.QDialog):
except AttributeError:
super().keyPressEvent(event)
def showEvent(self, event):
#TODO: not optimal but better than nothing.
super().showEvent(event)
#self.resize(self.layout.geometry().width(), self.layout.geometry().height())
self.resize(self.childrenRect().height(), self.childrenRect().width())
self.updateGeometry()
def abortHandler(self):
pass
def __call__(self):
"""This instance can be called like a function"""
"""This instance can be called like a function"""
self.exec() #blocks until the dialog gets closed
"""
Most submenus have the line "lambda, r, value=value"...
the r is the return value we get automatically from the Qt buttons which need to be handled.
@ -280,6 +288,22 @@ class SecondaryMetricalInstructionMenu(Submenu):
button.clicked.connect(function)
button.clicked.connect(self.done)
class ChooseOne(Submenu):
"""A generic submenu that presents a list of options to the users.
Only supports up to ten entries, for number shortcuts"""
def __init__(self, mainWindow, title:str, lst:Iterable[Tuple[str, Callable]]):
if len(lst) > 9:
raise ValueError(f"ChooseOne submenu supports up to nine entries. You have {len(lst)}")
super().__init__(mainWindow, title)
for number, (prettyname, function) in enumerate(lst):
button = QtWidgets.QPushButton(f"[{number+1}] {prettyname}")
button.setShortcut(QtGui.QKeySequence(str(number+1)))
button.setStyleSheet("Text-align:left; padding: 5px;");
self.layout.addWidget(button)
button.clicked.connect(function)
button.clicked.connect(self.done)
class SecondaryTempoChangeMenu(Submenu):
"""A single tempo change where the user can decide which reference unit and how many of them
per minute.
@ -610,3 +634,16 @@ def pedalNoteChooser(mainWindow):
for baseDuration, v in constantsAndConfigs.commonNotes:
if v == rhythmString[0]:
api.pedalNotes(baseDuration)
def forwardText(mainWindow, title, function):
text, status = QtWidgets.QInputDialog.getText(mainWindow, title, title)
if status:
function(text)

Loading…
Cancel
Save