# ------------------------------------------------------------------------------
# Name: noteworthy/translate.py
# Purpose: translates Noteworthy Composer's NWCTXT format
#
# Authors: Jordi Bartolome
# Michael Scott Asato Cuthbert
#
# Copyright: Copyright © 2011-2012 Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# ------------------------------------------------------------------------------
'''
Module to translate Noteworthy Composer's NWCTXT format to music21.
'''
# to do:
# |SongInfo|Title:"<FileTitle>"|Author:"<Author>"|Lyricist:"<Lyricist>"|
# Copyright1:"<Copyright1>"|Copyright2:"<Copyright2>"|Comments:"<Comments>"
# |StaffProperties|Muted:N|Volume:127|StereoPan:64|Device:0|Channel:2
# |StaffInstrument|Name:"Lead 6 (voice)"|Patch:85|Trans:0|DynVel:10,30,45,60,75,92,108,127
#
# UnderscoreAsSpace? N or Y
# |Lyrics|Placement:Bottom|Align:Standard Rules|Offset:0|UnderscoreAsSpace:N
#
# strip \\r and \\n
# |Lyric1|Text:"Ahoy________\\r\\n"
#
# support lyric 2
# |Lyric2|Text:"2_1 2_2 2_3 2_4\r\n2_5 2_6 2_7 2_8\r\n"
#
# |Ending|Endings:1
# |Ending|Endings:2,D
#
# As expression
# |TempoVariance|Style:Fermata|Pause:3|Pos:-4
#
# As spanner
# |TempoVariance|Style:Accelerando|Pos:-6
#
#
# Beams:
# |Note|Dur:8th|Pos:0|Opts:Stem=Down,Beam=First
# |Note|Dur:8th|Pos:0|Opts:Stem=Down,Beam
# |Note|Dur:8th|Pos:0|Opts:Stem=Down,Beam
# |Dynamic|Style:ppp|Pos:-8
# |Rest|Dur:8th
# |Note|Dur:8th|Pos:0|Opts:Stem=Down,Beam
# |Note|Dur:8th|Pos:0|Opts:Stem=Down,Beam
# |Note|Dur:8th|Pos:0|Opts:Stem=Down,Beam
# |Note|Dur:8th|Pos:0|Opts:Stem=Down,Beam=End
#
# performance style
# |PerformanceStyle|Style:Animato|Pos:-7
#
# low priority:
# |MPC|Controller:vol|Style:Linear Sweep|TimeRes:Whole|SweepRes:1|
# Pt1:0,127|Pt2:8,30|Pos:8|Wide:Y|Placement:BestFitForward
#
# Pos2? Dur2?
# |Chord|Dur:8th|Pos:-4,n-3,b-2,#-1,x0,v1,2x|Opts:Stem=Down,Crescendo|Dur2:8th,DblDotted|Pos2:3x
from __future__ import annotations
import unittest
from music21 import bar
from music21 import chord
from music21 import clef
from music21 import common
from music21 import duration
from music21 import dynamics
from music21 import environment
from music21 import expressions
from music21.exceptions21 import Music21Exception
from music21 import instrument
from music21 import interval
from music21 import key
from music21 import metadata
from music21 import meter
from music21 import note
from music21 import pitch
from music21 import repeat
from music21 import spanner
from music21 import stream
from music21 import tempo
from music21 import tie
from music21.noteworthy.dictionaries import dictionaries
environLocal = environment.Environment('noteworthy.translate')
# initializations
# file = open("Part_OWeisheit.nwctxt")
[docs]
class NoteworthyTranslator:
'''
stores all the data about the current parse context (whether we're in a slur, tuplet, etc.)
'''
def __init__(self):
self.currentPart = None
self.currentMeasure = None
self.measureNumber = 0
self.currentEnding = 1
self.repeatedMeasures = []
self.score = stream.Score()
self.currentClef = 'TREBLE'
self.currentKey = key.KeySignature(0)
self.withinSlur = False
self.beginningSlurNote = None
self.withinTie = False
self.lyricPosition = 0
self.lyrics = []
self.activeAccidentals = {}
[docs]
def parseFile(self, filePath):
try:
data = common.readFileEncodingSafe(filePath)
dataList = data.split('\n')
return self.parseList(dataList)
except (OSError, FileNotFoundError):
raise NoteworthyTranslateException(f'cannot open {filePath}: ')
[docs]
def parseString(self, data):
dataList = data.splitlines()
return self.parseList(dataList)
[docs]
def parseList(self, dataList):
r'''
Parses a list where each element is a line from a nwctxt file.
Returns a :class:`~music21.stream.Score` object
>>> data = []
>>> data.append('!NoteWorthyComposer(2.0)\n')
>>> data.append('|AddStaff|\n')
>>> data.append('|Clef|Type:Bass\n')
>>> data.append('|TimeSig|Signature:4/4\n')
>>> data.append('|Note|Dur:Whole|Pos:1\n')
>>>
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> s = nwt.parseList(data)
>>> s.show('text')
{0.0} <music21.stream.Part ...>
{0.0} <music21.stream.Measure 0 offset=0.0>
{0.0} <music21.clef.BassClef>
{0.0} <music21.meter.TimeSignature 4/4>
{0.0} <music21.note.Note E>
'''
# Main
for pi in dataList:
pi = pi.rstrip()
if pi.startswith('|') is False:
continue
sections = pi.split('|')
command = sections[1]
attributes = {}
for attribute in sections[2:]:
try:
(name, value) = attribute.split(':', 1)
if command == 'Chord' and (name in ('Dur', 'Pos')):
if attributes.get(name) is None:
attributes[name] = [value]
else:
attributes[name].append(value)
else:
attributes[name] = value
except ValueError:
if attribute.strip() == '':
pass
else:
raise NoteworthyTranslateException(
f'Cannot unpack value from {attribute} in {pi}')
try:
if command == 'AddStaff':
self.createStaff(attributes)
elif command == 'Bar':
self.createBarlines(attributes)
elif command == 'Chord':
self.translateChord(attributes)
self.lyricPosition += 1
elif command == 'Clef':
self.createClef(attributes)
elif command == 'Dynamic':
self.createDynamics(attributes)
elif command == 'DynamicVariance':
self.createDynamicVariance(attributes)
elif command == 'Ending':
self.createEnding(attributes)
elif command == 'Flow':
self.createOtherRepetitions(attributes)
elif command == 'Key':
self.createKey(attributes)
elif command == 'Lyric1':
self.lyrics = self.createLyrics(attributes)
elif command == 'Note':
self.translateNote(attributes)
self.lyricPosition += 1
elif command == 'Rest':
self.translateRest(attributes)
elif command == 'SongInfo':
self.createSongInfo(attributes)
elif command == 'StaffInstrument':
self.createStaffInstrument(attributes)
elif command == 'Tempo':
self.createTempo(attributes)
elif command == 'Text':
self.createText(attributes)
elif command == 'TimeSig':
self.createTimeSignature(attributes)
except Exception as e:
print(f'Cannot create object from "{attributes}"')
raise NoteworthyTranslateException from e
# Add the last Stuff
if self.currentMeasure:
self.currentPart.append(self.currentMeasure)
self.score.insert(0, self.currentPart)
# print('SHOW')
# totalscore.show('text')
# totalscore.show()
return self.score
[docs]
def setDurationForObject(self, generalNote, durationInfo):
'''
generalNote could be a Note, Chord, or Rest
DurationInfo is a string like:
Whole,Dotted,Slur
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> n = note.Note()
>>> nwt.setDurationForObject(n, 'Half')
>>> n.duration
<music21.duration.Duration 2.0>
'''
parts = durationInfo.split(',')
lengthNote = parts[0]
thisNoteIsSlurred = False
durationObject = duration.Duration(dictionaries['dictionaryNoteLength'][lengthNote])
for kk in parts:
if kk == 'Grace':
durationObject = durationObject.getGraceDuration()
elif kk == 'Slur':
if not self.withinSlur:
self.beginningSlurNote = generalNote
thisNoteIsSlurred = True
elif kk == 'Dotted':
durationObject.dots = 1
elif kk == 'DblDotted':
durationObject.dots = 2
elif kk in ('Triplet', 'Triplet=First', 'Triplet=End'):
tup = duration.Tuplet(3, 2, durationObject.type)
durationObject.appendTuplet(tup)
generalNote.duration = durationObject
# if Slur
if self.withinSlur and not thisNoteIsSlurred:
music21SlurObj = spanner.Slur(self.beginningSlurNote, generalNote)
self.currentMeasure.append(music21SlurObj)
self.withinSlur = False
elif thisNoteIsSlurred:
self.withinSlur = True
else:
self.withinSlur = False
[docs]
def setTieFromPitchInfo(self, noteOrChord, pitchInfo):
'''
sets the tie status for a noteOrChord from the pitchInfo
'''
thisNoteBeginsATie = False
thisNoteIsTied = False
if pitchInfo[-1] == '^':
if self.withinTie is False:
thisNoteBeginsATie = True
thisNoteIsTied = True
self.withinTie = True
achord = noteOrChord if isinstance(noteOrChord, chord.Chord) else None
# if Tied
if thisNoteBeginsATie:
if achord is not None:
for n in achord:
n.tie = tie.Tie('start')
else:
noteOrChord.tie = tie.Tie('start')
if self.withinTie is True and thisNoteIsTied is False:
if achord is not None:
for n in achord:
n.tie = tie.Tie('stop')
else:
noteOrChord.tie = tie.Tie('stop')
self.withinTie = False
[docs]
def getPitchFromPositionInfo(self, posInfo):
# noinspection PyShadowingNames
'''
returns a pitch object given the Pos: info
removes ties and alteration signs. Otherwise
is same as getOnePitchFromPosition()
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentClef = 'BASS'
>>> p = nwt.getPitchFromPositionInfo('b3^') # removes ties
>>> p
<music21.pitch.Pitch G-3>
'''
pos = posInfo.rstrip('^') # remove any tie
# What does this do???
pos = pos.rstrip('x')
pos = pos.rstrip('X')
pos = pos.rstrip('z')
p = self.getOnePitchFromPosition(pos)
return p
[docs]
def getMultiplePitchesFromPositionInfo(self, posInfo):
'''
returns a list of pitch objects given the Pos:... info
for a chord.
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentClef = 'BASS'
>>> pList = nwt.getMultiplePitchesFromPositionInfo('1,b3,5^')
>>> pList
[<music21.pitch.Pitch E3>, <music21.pitch.Pitch G-3>, <music21.pitch.Pitch B3>]
'''
pitchList = []
for thisPos in posInfo.split(','):
p = self.getPitchFromPositionInfo(thisPos)
pitchList.append(p)
return pitchList
[docs]
def getOnePitchFromPosition(self, pos):
# noinspection PyShadowingNames
'''
Get one pitch from a position:
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentClef = 'BASS'
>>> p = nwt.getOnePitchFromPosition('b3')
>>> p
<music21.pitch.Pitch G-3>
>>> p.ps
54.0
'''
accidental = ''
if pos[0] in ['n', 'b', '#', 'x', 'v']:
accidental = pos[0]
pos = pos[1:]
if accidental == 'b':
accidental = '-'
elif accidental == 'x':
accidental = '##'
elif accidental == 'v':
accidental = '--'
positionNote = int(pos)
(noteStep, octave) = self.getStepAndOctaveFromPosition(positionNote)
p = pitch.Pitch()
p.step = noteStep
p.octave = octave
pName = p.nameWithOctave
if accidental != '':
p.accidental = pitch.Accidental(accidental)
self.activeAccidentals[pName] = accidental
# previous accidental in same bar that is still active
elif pName in self.activeAccidentals:
p.accidental = pitch.Accidental(self.activeAccidentals[pName])
else:
stepAccidental = self.currentKey.accidentalByStep(noteStep)
if stepAccidental is not None:
p.accidental = stepAccidental
return p
[docs]
def getStepAndOctaveFromPosition(self, positionNote):
# noinspection PyShadowingNames
'''
Given an int representing the position on the staff for the
current clef,
returns a string for the step and an int for the octave
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentClef = 'BASS'
>>> (step, octave) = nwt.getStepAndOctaveFromPosition(3)
>>> (step, octave)
('G', 3)
'''
currentClef = self.currentClef
if currentClef == 'TREBLE8dw':
octave = 4
minPosition = 1
dictionary = dictionaries['dictionaryTreble']
elif currentClef == 'TREBLE8up':
octave = 6
minPosition = 1
dictionary = dictionaries['dictionaryTreble']
elif currentClef in ('BASS', 'PERCUSSION'):
octave = 3
minPosition = -1
dictionary = dictionaries['dictionaryBass']
elif currentClef == 'BASS8dw':
octave = 2
minPosition = -1
dictionary = dictionaries['dictionaryBass']
elif currentClef == 'BASS8up':
octave = 4
minPosition = -1
dictionary = dictionaries['dictionaryBass']
elif currentClef == 'ALTO':
octave = 4
minPosition = 0
dictionary = dictionaries['dictionaryAlto']
elif currentClef == 'TENOR':
octave = 3
minPosition = -5
dictionary = dictionaries['dictionaryTenor']
else: # 'TREBLE':
octave = 5
minPosition = 1
dictionary = dictionaries['dictionaryTreble']
while positionNote < minPosition or positionNote > (minPosition + 6):
if positionNote < minPosition:
positionNote = positionNote + 7
octave = octave - 1
if positionNote > (minPosition + 6):
positionNote = positionNote - 7
octave = octave + 1
noteName = dictionary[positionNote]
return (noteName, octave)
[docs]
def translateNote(self, attributes):
r'''
Translation of a music21 note from a NWC note.
>>> measure = stream.Measure()
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentMeasure = measure
>>> nwt.translateNote({'Dur': 'Half', 'Pos': '#-3'})
>>> measure[0]
<music21.note.Note F#>
Note that the next note in the measure with the same position should
inherit the last position's accidental:
>>> nwt.translateNote({'Dur': 'Half', 'Pos': '-3'})
>>> measure[1]
<music21.note.Note F#>
'''
durationInfo = attributes['Dur']
pitchInfo = attributes['Pos']
p = self.getPitchFromPositionInfo(pitchInfo)
if self.currentClef == 'PERCUSSION':
n = note.Unpitched()
n.displayStep = p.step
n.displayOctave = p.octave
else:
n = note.Note(p) # note!
# durationInfo
self.setDurationForObject(n, durationInfo)
# pitchInfo
self.setTieFromPitchInfo(n, pitchInfo)
# if Lyrics
if self.lyrics and self.lyricPosition < len(self.lyrics):
n.addLyric(self.lyrics[self.lyricPosition])
self.getShortestStream().append(n)
# Returns the shortest stream. It can be the shortest Voice
# or current measure if no voice.
[docs]
def getShortestStream(self):
if self.currentMeasure.isFlat:
# O(1) in most cases.
return self.currentMeasure
# O(n) only if voices already exist.
return min(
self.currentMeasure.getElementsByClass(stream.Voice),
key=lambda v: v.quarterLength
)
[docs]
def translateChord(self, attributes):
r'''
Translation of a music21 chord from a NWC one.
>>> measure = stream.Measure()
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentMeasure = measure
>>> nwt.translateChord({'Dur': ['Half'], 'Pos': ['1,3,#5']})
>>> measure[0]
<music21.chord.Chord C5 E5 G#5>
Chords also inherit accidentals:
>>> nwt.translateChord({'Dur': ['Half'], 'Pos': ['1,3,5']})
>>> measure[1]
<music21.chord.Chord C5 E5 G#5>
'''
durationInfos = attributes['Dur']
pitchInfos = attributes['Pos']
currentDuration = self.getShortestStream().duration
isRestChord = 'Dur2' in attributes
i = 0
def getVoiceAtDuration(inner_self, voiceId, dur):
# first check if voice already exists in measure
voice = None
voices = inner_self.currentMeasure.getElementsByClass(stream.Voice)
voice = voices.getElementById(voiceId)
# otherwise create it
if voice is None:
voice = stream.Voice()
voice.id = voiceId
# if creating the first voice, add current measure contents to it
if len(voices) == 0:
notes = []
for item in inner_self.currentMeasure:
if isinstance(item, note.GeneralNote):
notes.append(item)
if notes:
voice.append(notes)
inner_self.currentMeasure.remove(notes)
inner_self.currentMeasure.append(voice)
cd = dur.quarterLength
vd = voice.quarterLength
# if current voice is late, add rest
if cd - vd > 0:
rest = note.Rest()
rest.quarterLength = cd - vd
rest.stepShift = 3
voice.append(rest)
return voice
for d in durationInfos:
c = chord.Chord() # note!
# durationInfo
self.setDurationForObject(c, d)
# pitchInfo
p = pitchInfos[durationInfos.index(d)]
c.pitches = self.getMultiplePitchesFromPositionInfo(p)
self.setTieFromPitchInfo(c, p)
# if Lyrics
if self.lyrics and self.lyricPosition < len(self.lyrics):
c.addLyric(self.lyrics[self.lyricPosition])
if len(durationInfos) == 1 and isRestChord is not None:
self.getShortestStream().append(c)
else:
v = getVoiceAtDuration(self, i, currentDuration)
v.append(c)
i += 1
if isRestChord:
restDurInfo = attributes['Dur2']
r = note.Rest()
r.stepShift = 3
self.setDurationForObject(r, restDurInfo)
v = getVoiceAtDuration(self, i, currentDuration)
v.append(r)
[docs]
def translateRest(self, attributes):
r'''
Translation of a music21 rest. Adds the rest to the given measure.
>>> measureIn = stream.Measure()
>>> measureIn.append(note.Note('C#4', type='half'))
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentMeasure = measureIn
>>> nwt.translateRest({'Dur': '8th,Dotted'})
>>> nwt.translateRest({'Dur': '4th'})
>>> measureIn.show('text')
{0.0} <music21.note.Note C#>
{2.0} <music21.note.Rest dotted-eighth>
{2.75} <music21.note.Rest quarter>
'''
durationInfo = attributes['Dur']
r = note.Rest()
self.setDurationForObject(r, durationInfo)
self.getShortestStream().append(r)
[docs]
def createClef(self, attributes):
r'''
Add a new clef to the current measure and return the currentClef.
Clef lines should look like: \|Clef\|Type:ClefType or
\|Clef\|Type:ClefType\|OctaveShift:Octave Down (or Up)
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentMeasure = stream.Measure()
>>> nwt.createClef({'Type': 'Treble'})
>>> nwt.currentMeasure.show('text')
{0.0} <music21.clef.TrebleClef>
>>> nwt.currentClef
'TREBLE'
>>> nwt.createClef({'Type' : 'Bass', 'OctaveShift' : 'Octave Down'})
>>> nwt.currentMeasure.show('text')
{0.0} <music21.clef.TrebleClef>
{0.0} <music21.clef.Bass8vbClef>
>>> nwt.createClef({'Type' : 'Percussion'})
>>> nwt.currentMeasure.show('text')
{0.0} <music21.clef.TrebleClef>
{0.0} <music21.clef.Bass8vbClef>
{0.0} <music21.clef.PercussionClef>
If no clef can be found then it raises a NoteworthyTranslate exception
>>> nwt.createClef({'Type' : 'OrangeClef'})
Traceback (most recent call last):
music21.noteworthy.translate.NoteworthyTranslateException: Did
not find a proper clef in type, OrangeClef
'''
currentClef = None
if 'OctaveShift' in attributes:
if attributes['OctaveShift'] == 'Octave Down':
octaveShift = -1
elif attributes['OctaveShift'] == 'Octave Up':
octaveShift = 1
else:
raise NoteworthyTranslateException(
f'Did not get a proper octave shift from {attributes[3]}')
else:
octaveShift = 0
cl = attributes['Type']
if cl == 'Treble':
if octaveShift == 0:
self.currentMeasure.append(clef.TrebleClef())
currentClef = 'TREBLE'
elif octaveShift == -1:
self.currentMeasure.append(clef.Treble8vbClef())
currentClef = 'TREBLE8dw'
elif octaveShift == 1:
self.currentMeasure.append(clef.Treble8vaClef())
currentClef = 'TREBLE8up'
elif cl == 'Bass':
if octaveShift == 0:
self.currentMeasure.append(clef.BassClef())
currentClef = 'BASS'
elif octaveShift == -1:
self.currentMeasure.append(clef.Bass8vbClef())
currentClef = 'BASS8dw'
elif octaveShift == 1:
self.currentMeasure.append(clef.Bass8vaClef())
currentClef = 'BASS8up'
elif cl == 'Alto':
if octaveShift != 0:
raise NoteworthyTranslateException('cannot shift octaves on an alto clef')
self.currentMeasure.append(clef.AltoClef())
currentClef = 'ALTO'
elif cl == 'Tenor':
if octaveShift != 0:
raise NoteworthyTranslateException('cannot shift octaves on a tenor clef')
self.currentMeasure.append(clef.TenorClef())
currentClef = 'TENOR'
elif cl == 'Percussion':
c = clef.PercussionClef()
c.line = 2
self.currentMeasure.append(c)
currentClef = 'PERCUSSION'
if currentClef is None:
raise NoteworthyTranslateException(f'Did not find a proper clef in type, {cl}')
self.currentClef = currentClef
[docs]
def createKey(self, attributes):
r'''
Adds a new key signature to the given measure.
Returns the number of sharps (negative for flats)
>>> measureIn = stream.Measure()
>>> measureIn.append(note.Rest(quarterLength=3.0))
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentMeasure = measureIn
>>> nwt.createKey({'Signature':'F#,C#,G#,D#'})
>>> nwt.currentKey.sharps
4
>>> measureIn.show('text')
{0.0} <music21.note.Rest dotted-half>
{3.0} <music21.key.KeySignature of 4 sharps>
'''
currentSharps = 0
for attribute in attributes['Signature']:
if attribute == '#':
currentSharps += 1
elif attribute == 'b':
currentSharps -= 1
currentKey = key.KeySignature(currentSharps)
self.currentMeasure.append(currentKey)
self.currentKey = currentKey
[docs]
def createTimeSignature(self, attributes):
r'''
Adding a time signature in the score.
>>> measure = stream.Measure()
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentMeasure = measure
>>> nwt.createTimeSignature({'Signature':'4/4'})
>>> measure[0]
<music21.meter.TimeSignature 4/4>
'''
times = attributes['Signature']
if times == 'AllaBreve': # These are strange cases
times = '2/2'
elif times == 'Common':
times = '4/4'
m = meter.TimeSignature(times)
self.currentMeasure.append(m)
[docs]
def createPart(self):
'''
Add a new part to the score.
'''
if self.currentPart is not None:
self.currentPart.append(self.currentMeasure)
self.score.insert(0, self.currentPart)
self.currentPart = stream.Part()
self.currentMeasure = stream.Measure()
self.measureNumber = 0
[docs]
def createBarlines(self, attributes):
r'''
Translates bar lines into music21.
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentPart = stream.Part()
>>> nwt.currentMeasure = stream.Measure()
>>> nwt.createBarlines({'Style':'MasterRepeatOpen'})
>>> nwt.currentMeasure
<music21.stream.Measure 0 offset=0.0>
>>> nwt.currentMeasure.leftBarline
<music21.bar.Repeat direction=start>
'''
self.activeAccidentals = {}
if 'Style' not in attributes:
# pure barline
self.currentPart.append(self.currentMeasure)
self.currentMeasure = stream.Measure(number=self.measureNumber)
if len(self.repeatedMeasures) > 0:
self.repeatedMeasures.append(self.currentMeasure)
self.measureNumber += 1
return
if len(self.repeatedMeasures) > 0:
rbSpanner = spanner.RepeatBracket(self.repeatedMeasures, number=self.currentEnding)
self.currentPart.append(rbSpanner)
self.repeatedMeasures = []
style = attributes['Style']
if style == 'MasterRepeatOpen':
self.currentPart.append(self.currentMeasure)
self.currentMeasure = stream.Measure()
self.currentMeasure.leftBarline = bar.Repeat(direction='start')
elif style == 'MasterRepeatClose':
self.currentMeasure.rightBarline = bar.Repeat(direction='end')
self.currentPart.append(self.currentMeasure)
self.currentMeasure = stream.Measure()
elif style == 'LocalRepeatOpen':
self.currentPart.append(self.currentMeasure)
self.currentMeasure = stream.Measure()
self.currentMeasure.leftBarline = bar.Repeat(direction='start')
elif style == 'LocalRepeatClose':
self.currentMeasure.rightBarline = bar.Repeat(direction='end')
self.currentPart.append(self.currentMeasure)
self.currentMeasure = stream.Measure()
elif style == 'Double':
self.currentMeasure.rightBarline = bar.Barline('double')
self.currentPart.append(self.currentMeasure)
self.currentMeasure = stream.Measure()
elif style == 'SectionOpen':
self.currentMeasure.rightBarline = bar.Barline('heavy-light')
self.currentPart.append(self.currentMeasure)
self.currentMeasure = stream.Measure()
elif style == 'SectionClose':
self.currentMeasure.rightBarline = bar.Barline('final')
self.currentPart.append(self.currentMeasure)
self.currentMeasure = stream.Measure()
else:
raise NoteworthyTranslateException(f'cannot find a style {style} in our list')
self.currentMeasure.number = self.measureNumber
self.measureNumber += 1
[docs]
def createOtherRepetitions(self, attributes):
r'''
Repetitions like 'Coda', 'Segno' and some others.
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentMeasure = stream.Measure()
>>> nwt.createOtherRepetitions({'Style' : 'ToCoda', 'Pos': '8',
... 'Wide':'Y', 'Placement': 'BestFitForward'})
>>> isinstance(nwt.currentMeasure[0], repeat.Coda)
True
'''
# DaCapoAlFine - Coda - Segno - ToCoda
style = attributes['Style']
if style == 'DCalFine':
g = repeat.DaCapoAlFine()
elif style == 'Coda':
g = repeat.Coda()
elif style == 'ToCoda':
g = repeat.Coda()
elif style == 'Segno':
g = repeat.Segno()
elif style == 'DSalCoda':
g = repeat.DalSegnoAlCoda()
elif style == 'Fine':
g = repeat.Fine()
else:
raise NoteworthyTranslateException(f'Cannot get style from {str(attributes)}')
self.currentMeasure.append(g)
[docs]
def createDynamicVariance(self, attributes):
r'''
Adding dynamics like "crescendo" to the measure.
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentMeasure = stream.Measure()
>>> nwt.createDynamicVariance({'Style' : 'Crescendo', 'Pos': '-6'})
>>> nwt.currentMeasure.show('text')
{0.0} <music21.dynamics.Crescendo>
'''
style = attributes['Style']
g = None
if style == 'Crescendo':
g = dynamics.Crescendo()
elif style == 'Decrescendo':
g = dynamics.Diminuendo()
else:
pass
# raise NoteworthyTranslateException(f'Cannot get style from {attributes}')
if g is not None:
self.currentMeasure.append(g)
[docs]
def createDynamics(self, attributes):
r'''
Adding dynamics like "fff", "pp", ... to the measure.
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> nwt.currentMeasure = stream.Measure()
>>> nwt.createDynamics({'Style': 'fff', 'Pos': '-8'})
>>> nwt.currentMeasure[0]
<music21.dynamics.Dynamic fff>
'''
# Dynamic case
if 'Style' in attributes:
g = dynamics.Dynamic(attributes['Style'])
self.currentMeasure.append(g)
[docs]
def createLyrics(self, attributes):
r'''
Get a list of lyrics from a Lyric line
>>> nwt = noteworthy.translate.NoteworthyTranslator()
>>> lyricsList = nwt.createLyrics({'Text': '"Hello world"'})
>>> lyricsList[0]
'Hello'
'''
lyrics = []
space = 0
allText = attributes['Text']
allText = allText.strip('"')
allText = allText.replace('\r\n', ' ')
allText = allText.replace('\r', ' ')
allText = allText.replace('\n', ' ')
for word in allText.split(' '):
nou = 1
for wordPart in word.split('-'):
if space == 1:
nou = 0
space = 0
for w in wordPart.split('\n'):
if nou != 1:
ll = f' -{w}'
else:
ll = w
nou = 0
if w == '':
space = 0 # if 'space=1', it will appear a '-' before the next syllable
ll = ' - '
lyrics.append(ll)
return lyrics
[docs]
def createStaff(self, attributes):
self.createPart()
self.currentKey = key.KeySignature(0)
self.activeAccidentals = {}
self.lyrics = []
self.lyricPosition = 0
self.currentPart.partName = attributes.get('Name')
self.currentPart.partAbbreviation = attributes.get('Name')
[docs]
def createStaffInstrument(self, attributes):
patch = int(attributes.get('Patch', 0))
staffInstru = instrument.instrumentFromMidiProgram(patch)
readTranspo = int(attributes.get('Trans', 0))
staffInstru.transposition = interval.Interval(readTranspo)
self.currentPart.append(staffInstru)
[docs]
def createText(self, attributes):
text = attributes['Text']
# Rehearsal is associated to the score while TextExpression is staff-associated.
expr = expressions.TextExpression(text)
expr.placement = 'above' if int(attributes.get('Pos')) < 0 else 'below'
expr.style.relativeY = attributes.get('Pos')
self.currentMeasure.append(expr)
[docs]
def createSongInfo(self, attributes):
title = attributes.get('Title')
author = attributes.get('Author')
md = metadata.Metadata()
md.title = title
md.author = author
self.score.append(md)
[docs]
def createEnding(self, attributes):
end = attributes['Endings']
if self.currentMeasure.leftBarline is None:
self.currentMeasure.leftBarline = bar.Barline()
self.repeatedMeasures = [self.currentMeasure]
endingType = end.split(',')[0]
self.currentEnding = int(endingType)
[docs]
def createTempo(self, attributes):
tempo_value = attributes['Tempo']
mm = tempo.MetronomeMark(number=int(tempo_value))
self.currentMeasure.insert(0, mm)
class NoteworthyTranslateException(Music21Exception):
pass
class Test(unittest.TestCase):
def testBasic(self):
nwcTranslatePath = common.getSourceFilePath() / 'noteworthy'
simplePath = nwcTranslatePath / 'verySimple.nwctxt'
myScore = NoteworthyTranslator().parseFile(simplePath)
self.assertEqual(len(myScore[note.Note]), 1)
self.assertEqual(str(myScore[note.Note].first().name), 'E')
self.assertEqual(str(myScore[clef.Clef].first()),
'<music21.clef.BassClef>')
def testKeySignatureAtBeginning(self):
'''
test a problem with accidentals at the end of one staff not
being cleared at the beginning of the next
showed up in Morley, "Since my tears and lamenting" where
Staff 1 ended with a B-natural Picardy, and Staff
2 began with a B in a flat key, but was showing up as B-natural also
'''
info = '''!NoteWorthyComposer(2.0)
|AddStaff|
|Clef|Type:Treble
|Key|Signature:Bb
|TimeSig|Signature:Common
|Tempo|Base:Half|Tempo:60|Pos:7|Visibility:Never
|Note|Dur:Half|Pos:2^|Opts:Stem=Down
|Bar
|Note|Dur:Half|Pos:2|Opts:Stem=Down
|Note|Dur:Half|Pos:n0|Opts:Stem=Down
|Bar
|AddStaff|
|Clef|Type:Treble
|Key|Signature:Bb
|TimeSig|Signature:Common
|Note|Dur:Half|Pos:0^|Opts:Stem=Down
|Bar
|Note|Dur:Half|Pos:0|Opts:Stem=Down
|Note|Dur:Half|Pos:0|Opts:Stem=Down
|Bar
!NoteWorthyComposer-End'''
nwt = NoteworthyTranslator()
s = nwt.parseString(info)
# s.show('text')
n1 = s.parts[1].getElementsByClass(stream.Measure).first().notes.first()
self.assertEqual(n1.pitch.accidental.alter, -1.0)
class TestExternal(unittest.TestCase):
show = True
def testComplete(self):
nwcTranslatePath = common.getSourceFilePath() / 'noteworthy'
complete = nwcTranslatePath / 'NWCTEXT_Really_complete_example_file.nwctxt'
# 'Part_OWeisheit.nwctxt' #
myScore = NoteworthyTranslator().parseFile(complete)
if self.show:
myScore.show()
if __name__ == '__main__':
import music21
music21.mainTest(Test) # , TestExternal)