Improve MusicXML export, especially tuplets and ties

This commit is contained in:
Matthias Neeracher 2011-08-31 05:54:02 +02:00
parent 9d198f5cec
commit ec63fe8a7d
4 changed files with 229 additions and 59 deletions

View File

@ -221,7 +221,9 @@ class MusicXMLListener
'root-step', 'root-alter', 'bass-step', 'bass-alter', 'kind', 'root-step', 'root-alter', 'bass-step', 'bass-alter', 'kind',
'degree-value', 'degree-alter', 'degree-type', 'accidental' 'degree-value', 'degree-alter', 'degree-type', 'accidental'
@kind = tag @kind = tag
when 'tie' when 'notation'
@note['tied'] = 0
when 'tie', 'tied'
@note['tied'] ||= 0 @note['tied'] ||= 0
case attrs['type'] case attrs['type']
when 'start' when 'start'
@ -348,6 +350,10 @@ class MusicXMLListener
else else
@duration = @text.to_i @duration = @text.to_i
end end
when 'actual-notes'
@note['actualNotes'] = @text.to_i
when 'normal-notes'
@note['normalNotes'] = @text.to_i
when 'accidental' when 'accidental'
case @text case @text
when 'sharp' when 'sharp'

View File

@ -51,49 +51,68 @@ def _part_list
part_list = REXML::Element.new('part-list') part_list = REXML::Element.new('part-list')
melody = REXML::Element.new('score-part') melody = REXML::Element.new('score-part')
melody.add_attribute('id', 'MELO') melody.add_attribute('id', 'MELO')
melody.add_element newTextElement('part-name', 'Song') partname = newTextElement('part-name', 'Song')
partname.add_attribute('print-object', 'no')
melody.add_element(partname)
part_list.add_element(melody) part_list.add_element(melody)
return part_list return part_list
end end
$LAST_DIVISIONS = nil
$LAST_KEY = nil
$LAST_MODE = nil
$LAST_TIME_NUM = nil
$LAST_TIME_DENOM= nil
$SEEN_CLEF = false
def _attributes(prop) def _attributes(prop)
$DIVISIONS = prop['divisions'] $DIVISIONS = prop['divisions']
attr = REXML::Element.new('attributes') attr = REXML::Element.new('attributes')
attr.add_element newTextElement('divisions', prop['divisions']) if $LAST_DIVISIONS != prop['divisions']
attr.add_element newTextElement('divisions', $LAST_DIVISIONS = prop['divisions'])
end
if prop['key'] != $LAST_KEY || prop['mode'] != $LAST_MODE
key = REXML::Element.new('key') key = REXML::Element.new('key')
key.add_element newTextElement('fifths', prop['key']) key.add_element newTextElement('fifths', $LAST_KEY = prop['key'])
key.add_element newTextElement('mode', prop['mode'] > 0 ? "major" : "minor") key.add_element newTextElement('mode', ($LAST_MODE = prop['mode']) > 0 ? "major" : "minor")
attr.add_element(key) attr.add_element(key)
end
if prop['timeNum'] != $LAST_TIME_NUM || prop['timeDenom'] != $LAST_TIME_DENOM
time = REXML::Element.new('time') time = REXML::Element.new('time')
time.add_element newTextElement('beats', prop['timeNum']) time.add_element newTextElement('beats', $LAST_TIME_NUM = prop['timeNum'])
time.add_element newTextElement('beat-type', prop['timeDenom']) time.add_element newTextElement('beat-type', $LAST_TIME_DENOM = prop['timeDenom'])
attr.add_element(time) attr.add_element(time)
end
if !$SEEN_CLEF
$SEEN_CLEF = true
clef = REXML::Element.new('clef') clef = REXML::Element.new('clef')
clef.add_element newTextElement('sign', 'G') clef.add_element newTextElement('sign', 'G')
clef.add_element newTextElement('line', 2) clef.add_element newTextElement('line', 2)
attr.add_element(clef) attr.add_element(clef)
return attr end
end return attr.has_elements? ? attr : nil
def _groove(groove)
dir = REXML::Element.new('direction')
dty = REXML::Element.new('direction-type')
dty.add_element newTextElement('words', groove)
dir.add_element(dty)
return dir
end end
def _tempo(tempo) def _tempo(tempo)
dir = REXML::Element.new('direction') dir = REXML::Element.new('direction')
dty = REXML::Element.new('direction-type') dty = REXML::Element.new('direction-type')
metro = REXML::Element.new('metronome') metro = REXML::Element.new('metronome')
metro.add_element newTextElement('beat-unit', 'quarter') metro.add_element newTextElement('beat-unit', 'quarter')
metro.add_element newTextElement('per-minute', tempo) metro.add_element newTextElement('per-minute', tempo)
dty.add_element(metro) dty.add_element(metro)
dir.add_element(dty) dir.add_element(dty)
dir.add_element 'sound', {'tempo' => tempo} dir.add_element 'sound', {'tempo' => tempo}
return dir
end
def _groove(groove)
dir = REXML::Element.new('direction')
dir.add_attribute('placement', 'above')
dty = REXML::Element.new('direction-type')
dty.add_element newTextElement('words', groove)
dir.add_element(dty)
return dir return dir
end end
@ -106,14 +125,20 @@ def _pitch(name, pitch, accidental, prefix="")
stp = (pitch%12)+2 stp = (pitch%12)+2
step = STEPS[stp] step = STEPS[stp]
if step == 0x20 if step == 0x20
abort "Pitch #{pitch} not representable" if $LAST_KEY > 0
step = STEPS[stp-1]
accidental = 1
else
step = STEPS[stp+1]
accidental = -1
end
end end
if prefix.length > 0 if prefix.length > 0
prefix += "-" prefix += "-"
end end
pitch= REXML::Element.new(name) pitch= REXML::Element.new(name)
pitch.add_element newTextElement(prefix+'step', step.chr) pitch.add_element newTextElement(prefix+'step', step.chr)
if accidental != 0 if accidental && accidental != 0
pitch.add_element newTextElement(prefix+'alter', accidental) pitch.add_element newTextElement(prefix+'alter', accidental)
end end
if prefix.length == 0 if prefix.length == 0
@ -143,7 +168,7 @@ def _accidental(visual)
accidental accidental
end end
def _note(pitch, dur, visual, tied) def _note(pitch, dur, visual, tuplet, tied)
accidental = _accidental(visual) accidental = _accidental(visual)
note = REXML::Element.new('note') note = REXML::Element.new('note')
if pitch == VL::NoPitch if pitch == VL::NoPitch
@ -152,21 +177,49 @@ def _note(pitch, dur, visual, tied)
note.add_element(_pitch('pitch', pitch, accidental)) note.add_element(_pitch('pitch', pitch, accidental))
end end
note.add_element newTextElement('duration', dur) note.add_element newTextElement('duration', dur)
notations = nil
if (tied & VL::TiedWithPrev) != 0 if (tied & VL::TiedWithPrev) != 0
note.add_element 'tie', {'type' => 'stop' } note.add_element 'tie', {'type' => 'stop' }
unless notations
notations = REXML::Element.new('notations')
end
notations.add_element 'tied', {'type' => 'stop'}
end end
if (tied & VL::TiedWithNext) != 0 if (tied & VL::TiedWithNext) != 0
note.add_element 'tie', {'type' => 'start' } note.add_element 'tie', {'type' => 'start' }
unless notations
notations = REXML::Element.new('notations')
end
notations.add_element 'tied', {'type' => 'start'}
end end
note.add_element newTextElement('voice', 1) note.add_element newTextElement('voice', 1)
note.add_element newTextElement('type', TYPE[visual & 7]) note.add_element newTextElement('type', TYPE[visual & 7])
if accidental if accidental
note.add_element newTextElement('accidental', ACC[accidental+2]) note.add_element newTextElement('accidental', ACC[accidental+2])
end end
if tuplet
unless notations
notations = REXML::Element.new('notations')
end
notations.add_element 'tuplet', {'type' => (tuplet>0 ? 'start' : 'stop')}
end
if notations
note.add_element notations
end
return note return note
end end
def _timeMod(note)
timeMod = REXML::Element.new('time-modification')
timeMod.add_element newTextElement('actual-notes', note['actualNotes'])
timeMod.add_element newTextElement('normal-notes', note['normalNotes'])
if note['normalType']
timeMod.add_element newTextElement('normal-type', TYPE[note['normalType']])
end
return timeMod
end
CHORD = { CHORD = {
# #
# Triads # Triads
@ -350,27 +403,18 @@ def _melody
lastGroove = groove lastGroove = groove
m.add_element(_groove(lastGroove)) m.add_element(_groove(lastGroove))
end end
m.add_element(_attributes(INPUT['properties'][lastProp])) if attr = _attributes(INPUT['properties'][lastProp])
m.add_element(attr)
end
end end
if meas['new-page'] if meas['new-page']
m.add_element 'print', {'new-page' => 'yes'} m.add_element 'print', {'new-page' => 'yes'}
elsif meas['new-system'] elsif meas['new-system']
m.add_element 'print', {'new-system' => 'yes'} m.add_element 'print', {'new-system' => 'yes'}
end end
if meas['coda'] coda = meas['coda']
m.add_element 'sound', {'coda' => 'A'} eEnding = nil
end
if meas['begin-repeat']
barline = REXML::Element.new('barline')
barline.add_attribute('location', 'left')
barline.add_element newTextElement('bar-style',
(repeat[measNum-1] & 2) != 0 ? 'heavy-heavy' : 'heavy-light')
barline.add_element 'repeat', {'direction' => 'forward'}
m.add_element(barline)
end
if ending = meas['begin-ending'] if ending = meas['begin-ending']
barline = REXML::Element.new('barline')
barline.add_attribute('location', 'left')
volta = ending['volta'] volta = ending['volta']
num = nil num = nil
(0..7).each do |i| (0..7).each do |i|
@ -382,9 +426,37 @@ def _melody
end end
end end
end end
barline.add_element 'ending', {'type' => 'start', 'number' => num} eEnding = REXML::Element.new('ending')
eEnding.add_attributes({'type' => 'start', 'number' => num})
end
eBarstyle = nil
eRepeat = nil
if meas['begin-repeat']
eBarstyle = newTextElement('bar-style',
(repeat[measNum-1] & 2) != 0 ? 'heavy-heavy' : 'heavy-light')
eRepeat = REXML::Element.new('repeat')
eRepeat.add_attribute('direction', 'forward')
end
if eEnding || eBarstyle || eRepeat || coda
barline = REXML::Element.new('barline')
barline.add_attribute('location', 'left')
if eBarstyle
barline.add_element(eBarstyle)
end
if coda
barline.add_element 'coda'
end
if eEnding
barline.add_element(eEnding)
end
if eRepeat
barline.add_element(eRepeat)
end
m.add_element(barline) m.add_element(barline)
end end
if coda
m.add_element 'sound', {'coda' => 'A'}
end
noteAt = 0 noteAt = 0
chordAt= 0 chordAt= 0
chords = meas['chords'] chords = meas['chords']
@ -412,7 +484,10 @@ def _melody
m.add_element(bk) m.add_element(bk)
tempAt = noteAt tempAt = noteAt
end end
n = _note(note['pitch'], dur, note['visual'], note['tied'] || 0) n = _note(note['pitch'], dur, note['visual'], note['tuplet'], note['tied'] || 0)
if note['actualNotes']
n.add_element _timeMod(note)
end
stanza = 1 stanza = 1
note['lyrics'].each do |syll| note['lyrics'].each do |syll|
if syll['text'] if syll['text']
@ -427,19 +502,18 @@ def _melody
noteAt += dur noteAt += dur
m.add_element(n) m.add_element(n)
end end
eRepeat = nil
eBarstyle = nil
if r = meas['end-repeat'] if r = meas['end-repeat']
barline = REXML::Element.new('barline') eBarstyle = newTextElement('bar-style',
barline.add_attribute('location', 'right')
barline.add_element newTextElement('bar-style',
(repeat[measNum+1] & 1) != 0 ? 'heavy-heavy' : 'light-heavy') (repeat[measNum+1] & 1) != 0 ? 'heavy-heavy' : 'light-heavy')
barline.add_element 'repeat', {'direction' => 'backward', 'times' => r['times'].to_s} eRepeat = REXML::Element.new('repeat')
m.add_element(barline) eRepeat.add_attributes({'direction' => 'backward', 'times' => r['times'].to_s})
end end
eEnding = nil
if ending = meas['end-ending'] if ending = meas['end-ending']
barline = REXML::Element.new('barline') eBarstyle ||= newTextElement('bar-style',
barline.add_attribute('location', 'right') (repeat[measNum+1] & 1) != 0 ? 'heavy-heavy' : 'light-heavy')
barline.add_element newTextElement('bar-style',
(repeat[measNum+1] & 1) != 0 ? 'heavy-heavy' : 'light-heavy')
volta = ending['volta'] volta = ending['volta']
num = nil num = nil
(0..7).each do |i| (0..7).each do |i|
@ -452,13 +526,34 @@ def _melody
end end
end end
type = ending['last'] ? "discontinue" : "stop" type = ending['last'] ? "discontinue" : "stop"
barline.add_element 'ending', {'type', type, 'number', num} eEnding = REXML::Element.new('ending')
barline.add_element 'repeat', {'direction' => 'backward'} eEnding.add_attributes({'type', type, 'number', num})
m.add_element(barline) if !ending['last'] && !eRepeat
eRepeat = REXML::Element.new('repeat')
eRepeat.add_attribute('direction', 'backward')
end
end end
if meas['tocoda'] if coda = meas['tocoda']
m.add_element 'sound', {'tocoda' => 'A'} m.add_element 'sound', {'tocoda' => 'A'}
end end
if coda || eEnding || eRepeat || eBarstyle
barline = REXML::Element.new('barline')
barline.add_attribute('location', 'right')
if eBarstyle
barline.add_element(eBarstyle)
end
if coda
barline.add_element 'coda'
end
if eEnding
barline.add_element(eEnding)
end
if eRepeat
barline.add_element(eRepeat)
end
m.add_element(barline)
end
melody.add_element(m) melody.add_element(m)
end end

View File

@ -147,7 +147,8 @@ struct VLNote {
kTupletMask = 0xFF00 kTupletMask = 0xFF00
}; };
static int TupletNum(uint16_t visual) { return visual >> 12; } static int TupletNum(uint16_t visual) { return visual >> 12; }
static int TupletDenom(uint16_t visual) { return (visual >> 8) & 0x0F; } static int TupletDenom(uint16_t visual) { return (visual >> 8) & 0x0F; }
static uint16_t Tuplet(int num, int denom) { return (num << 12) | (denom << 8); }
VLNote(VLFraction dur=0, int pitch=kNoPitch, uint16_t visual=0); VLNote(VLFraction dur=0, int pitch=kNoPitch, uint16_t visual=0);
VLNote(std::string name); VLNote(std::string name);
std::string Name(uint16_t accidentals=0) const; std::string Name(uint16_t accidentals=0) const;

View File

@ -39,9 +39,14 @@ protected:
NSMutableArray * fMeasures; NSMutableArray * fMeasures;
NSMutableArray * fNotes; NSMutableArray * fNotes;
NSMutableArray * fChords; NSMutableArray * fChords;
NSMutableArray * fNotesInTuplet;
bool fPerfOrder; bool fPerfOrder;
const VLSong * fSong; const VLSong * fSong;
VLVisualFilter fVisFilter; VLVisualFilter fVisFilter;
int fInTuplet;
uint16_t fTuplet;
uint16_t fTupletNote;
VLFraction fTupletDur;
}; };
NSArray * VLPlistVisitor::EncodeProperties(const std::vector<VLProperties> & properties) NSArray * VLPlistVisitor::EncodeProperties(const std::vector<VLProperties> & properties)
@ -83,8 +88,11 @@ void VLPlistVisitor::VisitMeasure(size_t m, VLProperties & p, VLMeasure & meas)
fChords= [NSMutableArray arrayWithCapacity:1]; fChords= [NSMutableArray arrayWithCapacity:1];
fVisFilter.ResetWithKey(p.fKey); fVisFilter.ResetWithKey(p.fKey);
fInTuplet = 0;
VisitNotes(meas, p, true); VisitNotes(meas, p, true);
VisitChords(meas); VisitChords(meas);
if (fInTuplet)
[[fNotesInTuplet lastObject] setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
NSMutableDictionary * md = NSMutableDictionary * md =
[NSMutableDictionary dictionaryWithObjectsAndKeys: [NSMutableDictionary dictionaryWithObjectsAndKeys:
@ -142,8 +150,8 @@ void VLPlistVisitor::VisitNote(VLLyricsNote & n)
: [NSDictionary dictionary]]; : [NSDictionary dictionary]];
int grid = n.fPitch==VLNote::kNoPitch ? 0 : VLPitchToGrid(n.fPitch, n.fVisual, 0); int grid = n.fPitch==VLNote::kNoPitch ? 0 : VLPitchToGrid(n.fPitch, n.fVisual, 0);
NSDictionary * nd = NSMutableDictionary * nd =
[NSDictionary dictionaryWithObjectsAndKeys: [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:n.fDuration.fNum], @"durNum", [NSNumber numberWithInt:n.fDuration.fNum], @"durNum",
[NSNumber numberWithInt:n.fDuration.fDenom], @"durDenom", [NSNumber numberWithInt:n.fDuration.fDenom], @"durDenom",
[NSNumber numberWithInt:n.fPitch], @"pitch", [NSNumber numberWithInt:n.fPitch], @"pitch",
@ -151,6 +159,62 @@ void VLPlistVisitor::VisitNote(VLLyricsNote & n)
[NSNumber numberWithInt:fVisFilter(grid, n.fVisual)], @"visual", [NSNumber numberWithInt:fVisFilter(grid, n.fVisual)], @"visual",
ly, @"lyrics", ly, @"lyrics",
nil]; nil];
if (uint16_t newTuplet = n.fVisual & VLNote::kTupletMask) {
if (!fInTuplet || newTuplet != fTuplet) {
if (fInTuplet) {
[[fNotesInTuplet lastObject] setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
fInTuplet = 0;
}
fNotesInTuplet = [NSMutableArray array];
fTuplet = newTuplet;
fTupletNote = n.fVisual & VLNote::kNoteHeadMask;
fTupletDur = 0;
[nd setObject:[NSNumber numberWithInt:1] forKey:@"tuplet"];
}
int tupletNum = VLNote::TupletNum(fTuplet);
int tupletDenom = VLNote::TupletDenom(fTuplet);
[nd setObject:[NSNumber numberWithInt:tupletNum] forKey:@"actualNotes"];
[nd setObject:[NSNumber numberWithInt:tupletDenom] forKey:@"normalNotes"];
++fInTuplet;
fTupletDur += n.fDuration;
if (fTuplet == VLNote::kTriplet) {
uint16_t newNote = n.fVisual & VLNote::kNoteHeadMask;
if (newNote == fTupletNote) {
if (fInTuplet == 3)
fInTuplet = 0;
} else if (fInTuplet == 2 && newNote == fTupletNote-1) {
//
// 8th, 4th triplet
//
[nd setObject:[NSNumber numberWithInt:fTupletNote] forKey:@"normalType"];
fInTuplet = 0;
} else if (fInTuplet == 3 && newNote == fTupletNote-1) {
//
// 8th 8th 4th
//
for (NSMutableDictionary * prevNote in fNotesInTuplet) {
[prevNote setObject:[NSNumber numberWithInt:newNote] forKey:@"normalType"];
}
fInTuplet = 2;
fTupletNote = newNote;
} else {
//
// 4th 4th 8th
//
[nd setObject:[NSNumber numberWithInt:fTupletNote] forKey:@"normalType"];
if (fTupletDur.fNum == 1 && !(fTupletDur.fDenom&(fTupletDur.fDenom-1)))
fInTuplet = 0;
}
} else if (fInTuplet == tupletNum)
fInTuplet = 0;
if (!fInTuplet)
[nd setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
else
[fNotesInTuplet addObject:nd];
} else if (fInTuplet) {
[[fNotesInTuplet lastObject] setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
fInTuplet = 0;
}
[fNotes addObject:nd]; [fNotes addObject:nd];
} }
@ -255,9 +319,13 @@ enum {
[[ndict objectForKey:@"durDenom"] intValue], [[ndict objectForKey:@"durDenom"] intValue],
true); true);
note.fPitch = [[ndict objectForKey:@"pitch"] intValue]; note.fPitch = [[ndict objectForKey:@"pitch"] intValue];
note.fVisual |= [[ndict objectForKey:@"visual"] intValue] note.fVisual = [[ndict objectForKey:@"visual"] intValue]
& VLNote::kAccidentalsMask; & VLNote::kAccidentalsMask;
note.fTied = 0; note.fTied = 0;
if ([ndict objectForKey:@"actualNotes"])
note.fVisual |= VLNote::Tuplet([[ndict objectForKey:@"actualNotes"] intValue],
[[ndict objectForKey:@"normalNotes"] intValue]);
if ([[ndict objectForKey:@"tied"] intValue] & VLNote::kTiedWithPrev) { if ([[ndict objectForKey:@"tied"] intValue] & VLNote::kTiedWithPrev) {
if (at != 0) { if (at != 0) {