mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 11:14:00 +00:00
Properly read and write harmony info
This commit is contained in:
parent
0cc5fb11b4
commit
7d8a579907
|
@ -108,39 +108,40 @@ class MusicXMLListener
|
||||||
@kind = nil # Ignore all tags not recognized here
|
@kind = nil # Ignore all tags not recognized here
|
||||||
@text = ""
|
@text = ""
|
||||||
case tag
|
case tag
|
||||||
when 'score-timewise' then
|
when 'score-timewise'
|
||||||
$stderr.puts "Can't read timewise MusicXML files yet"
|
$stderr.puts "Can't read timewise MusicXML files yet"
|
||||||
exit 1
|
exit 1
|
||||||
when 'work-title' then
|
when 'work-title'
|
||||||
@kind = 'textProp'
|
@kind = 'textProp'
|
||||||
@key = 'title'
|
@key = 'title'
|
||||||
when 'creator' then
|
when 'creator'
|
||||||
case attrs['type']
|
case attrs['type']
|
||||||
when 'composer' then
|
when 'composer'
|
||||||
@kind = 'textProp'
|
@kind = 'textProp'
|
||||||
@key = 'composer'
|
@key = 'composer'
|
||||||
when 'poet' then
|
when 'poet'
|
||||||
@kind = 'textProp'
|
@kind = 'textProp'
|
||||||
@key = 'lyricist'
|
@key = 'lyricist'
|
||||||
end
|
end
|
||||||
when 'miscellaneous-field' then
|
when 'miscellaneous-field'
|
||||||
if attrs['name'] == 'VocalEasel-groove'
|
if attrs['name'] == 'VocalEasel-groove'
|
||||||
@kind = 'textProp'
|
@kind = 'textProp'
|
||||||
@key = 'groove'
|
@key = 'groove'
|
||||||
end
|
end
|
||||||
when 'encoding-date' then
|
when 'encoding-date'
|
||||||
@kind = 'dateProp'
|
@kind = 'dateProp'
|
||||||
@key = 'saved'
|
@key = 'saved'
|
||||||
when 'software' then
|
when 'software'
|
||||||
@kind = 'textProp'
|
@kind = 'textProp'
|
||||||
@key = 'software'
|
@key = 'software'
|
||||||
when 'part' then
|
when 'part'
|
||||||
@part = attrs['id'] || ""
|
@part = attrs['id'] || ""
|
||||||
@prop = {}
|
@prop = {}
|
||||||
@props = []
|
@props = []
|
||||||
@measNo = -1
|
@measNo = -1
|
||||||
when 'measure' then
|
when 'measure'
|
||||||
@notes= []
|
@notes= []
|
||||||
|
@note = nil
|
||||||
@harm = []
|
@harm = []
|
||||||
@chord= false
|
@chord= false
|
||||||
@at = 0
|
@at = 0
|
||||||
|
@ -157,20 +158,20 @@ class MusicXMLListener
|
||||||
'melody' => []
|
'melody' => []
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
when 'barline' then
|
when 'barline'
|
||||||
@times = nil
|
@times = nil
|
||||||
@type = nil
|
@type = nil
|
||||||
@number = ""
|
@number = ""
|
||||||
when 'repeat' then
|
when 'repeat'
|
||||||
if attrs['direction'] == 'backward' && attrs['times']
|
if attrs['direction'] == 'backward' && attrs['times']
|
||||||
@times = attrs['times'].to_i
|
@times = attrs['times'].to_i
|
||||||
elsif attrs['direction'] == 'forward'
|
elsif attrs['direction'] == 'forward'
|
||||||
@times = 0
|
@times = 0
|
||||||
end
|
end
|
||||||
when 'ending' then
|
when 'ending'
|
||||||
@type = attrs['type']
|
@type = attrs['type']
|
||||||
@number = attrs['number']
|
@number = attrs['number']
|
||||||
when 'sound' then
|
when 'sound'
|
||||||
if attrs['tocoda']
|
if attrs['tocoda']
|
||||||
@meas['tocoda'] = true
|
@meas['tocoda'] = true
|
||||||
elsif attrs['coda']
|
elsif attrs['coda']
|
||||||
|
@ -179,47 +180,46 @@ class MusicXMLListener
|
||||||
if attrs['tempo']
|
if attrs['tempo']
|
||||||
OUTPUT['tempo'] = attrs['tempo'].to_i
|
OUTPUT['tempo'] = attrs['tempo'].to_i
|
||||||
end
|
end
|
||||||
when 'divisions' then
|
when 'divisions'
|
||||||
@kind = 'prop'
|
@kind = 'prop'
|
||||||
@key = 'divisions'
|
@key = 'divisions'
|
||||||
when 'fifths' then
|
when 'fifths'
|
||||||
@kind = 'prop'
|
@kind = 'prop'
|
||||||
@key = 'key'
|
@key = 'key'
|
||||||
when 'beats' then
|
when 'beats'
|
||||||
@kind = 'prop'
|
@kind = 'prop'
|
||||||
@key = 'timeNum'
|
@key = 'timeNum'
|
||||||
when 'beat-type' then
|
when 'beat-type'
|
||||||
@kind = 'prop'
|
@kind = 'prop'
|
||||||
@key = 'timeDenom'
|
@key = 'timeDenom'
|
||||||
when 'harmony' then
|
when 'harmony'
|
||||||
@note = { 'pitch' => VL::NoPitch, 'steps' => 0, 'root' => VL::NoPitch,
|
@note = { 'pitch' => VL::NoPitch, 'steps' => 0, 'root' => VL::NoPitch,
|
||||||
'at' => @at }
|
'at' => @at }
|
||||||
when 'degree' then
|
when 'degree'
|
||||||
@degree_value = 0
|
@degree_value = 0
|
||||||
@degree_alter = 0
|
@degree_alter = 0
|
||||||
@degree_type = "alter"
|
@degree_type = "alter"
|
||||||
when 'note' then
|
when 'note'
|
||||||
@note = { 'pitch' => 0, 'durNum' => 0, 'durDenom' => 0 }
|
@note = { 'pitch' => 0, 'durNum' => 0, 'durDenom' => 0 }
|
||||||
when 'rest' then
|
when 'rest'
|
||||||
@note['pitch'] = VL::NoPitch
|
@note['pitch'] = VL::NoPitch
|
||||||
when 'mode', 'step', 'alter', 'octave', 'duration', 'syllabic', 'text',
|
when 'mode', 'step', 'alter', 'octave', 'duration', 'syllabic', 'text',
|
||||||
'root-step', 'root-alter', 'bass-step', 'bass-alter', 'kind',
|
'root-step', 'root-alter', 'bass-step', 'bass-alter', 'kind',
|
||||||
'degree-value', 'degree-alter', 'degree-type'
|
'degree-value', 'degree-alter', 'degree-type'
|
||||||
then
|
|
||||||
@kind = tag
|
@kind = tag
|
||||||
when 'tie' then
|
when 'tie'
|
||||||
@note['tied'] ||= 0
|
@note['tied'] ||= 0
|
||||||
case attrs['type']
|
case attrs['type']
|
||||||
when 'start' then
|
when 'start'
|
||||||
@note['tied'] |= VL::TiedWithNext
|
@note['tied'] |= VL::TiedWithNext
|
||||||
when 'stop' then
|
when 'stop'
|
||||||
@note['tied'] |= VL::TiedWithPrev
|
@note['tied'] |= VL::TiedWithPrev
|
||||||
end
|
end
|
||||||
when 'lyric' then
|
when 'lyric'
|
||||||
num = (attrs['number'] || "1").to_i-1
|
num = (attrs['number'] || "1").to_i-1
|
||||||
@note['lyrics'] ||= []
|
@note['lyrics'] ||= []
|
||||||
@note['lyrics'][num] = @lyric = {}
|
@note['lyrics'][num] = @lyric = {}
|
||||||
when 'chord' then
|
when 'chord'
|
||||||
@chord = true
|
@chord = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -283,44 +283,48 @@ class MusicXMLListener
|
||||||
#
|
#
|
||||||
if @kind
|
if @kind
|
||||||
case @kind
|
case @kind
|
||||||
when 'textProp' then
|
when 'textProp'
|
||||||
OUTPUT[@key] = @text
|
OUTPUT[@key] = @text
|
||||||
when 'dateProp' then
|
when 'dateProp'
|
||||||
begin
|
begin
|
||||||
OUTPUT[@key] = Time.parse(@text)
|
OUTPUT[@key] = Time.parse(@text)
|
||||||
rescue
|
rescue
|
||||||
OUTPUT[@key] = Time.now
|
OUTPUT[@key] = Time.now
|
||||||
end
|
end
|
||||||
when 'prop' then
|
when 'prop'
|
||||||
@prop[@key] = @text.to_i
|
@prop[@key] = @text.to_i
|
||||||
when 'mode' then
|
when 'mode'
|
||||||
@prop['mode'] = @text == 'minor' ? -1 : 1
|
@prop['mode'] = @text == 'minor' ? -1 : 1
|
||||||
when 'step' then
|
when 'step'
|
||||||
@note['pitch'] += PITCH[@text]
|
@note['pitch'] += PITCH[@text]
|
||||||
when 'alter', 'root-alter' then
|
when 'alter', 'root-alter'
|
||||||
@note['pitch'] += @text.to_i
|
@note['pitch'] += @text.to_i
|
||||||
when 'octave' then
|
when 'octave'
|
||||||
@note['pitch'] += (@text.to_i+1)*12
|
@note['pitch'] += (@text.to_i+1)*12
|
||||||
when 'duration' then
|
when 'duration'
|
||||||
@note['durNum'] = @text.to_i
|
if @note
|
||||||
@note['durDenom'] = @prop['divisions']*4
|
@note['durNum'] = @text.to_i
|
||||||
when 'root-step' then
|
@note['durDenom'] = @prop['divisions']*4
|
||||||
|
else
|
||||||
|
@duration = @text.to_i
|
||||||
|
end
|
||||||
|
when 'root-step'
|
||||||
@note['pitch'] = PITCH[@text]
|
@note['pitch'] = PITCH[@text]
|
||||||
when 'bass-step' then
|
when 'bass-step'
|
||||||
@note['root'] = PITCH[@text]
|
@note['root'] = PITCH[@text]
|
||||||
when 'bass-alter' then
|
when 'bass-alter'
|
||||||
@note['root'] += @text.to_i
|
@note['root'] += @text.to_i
|
||||||
when 'kind' then
|
when 'kind'
|
||||||
@note['steps'] = CHORD[@text]
|
@note['steps'] = CHORD[@text]
|
||||||
when 'degree-value' then
|
when 'degree-value'
|
||||||
@degree_value = @text.to_i-1
|
@degree_value = @text.to_i-1
|
||||||
when 'degree-alter' then
|
when 'degree-alter'
|
||||||
@degree_alter = @text.to_i
|
@degree_alter = @text.to_i
|
||||||
when 'degree-type' then
|
when 'degree-type'
|
||||||
@degree_type = @text
|
@degree_type = @text
|
||||||
when 'syllabic' then
|
when 'syllabic'
|
||||||
@lyric['kind'] = SYLL[@text]
|
@lyric['kind'] = SYLL[@text]
|
||||||
when 'text' then
|
when 'text'
|
||||||
@lyric['text'] = @text
|
@lyric['text'] = @text
|
||||||
end
|
end
|
||||||
@kind = nil
|
@kind = nil
|
||||||
|
@ -329,11 +333,11 @@ class MusicXMLListener
|
||||||
# Structures are distinguished by tag
|
# Structures are distinguished by tag
|
||||||
#
|
#
|
||||||
case tag
|
case tag
|
||||||
when 'attributes' then
|
when 'attributes'
|
||||||
if @prop != @props.last
|
if @prop != @props.last
|
||||||
@props.push(@prop)
|
@props.push(@prop)
|
||||||
end
|
end
|
||||||
when 'note' then
|
when 'note'
|
||||||
if @chord
|
if @chord
|
||||||
note = @notes.last
|
note = @notes.last
|
||||||
note['pitch'] = [*note['pitch']] << @note['pitch']
|
note['pitch'] = [*note['pitch']] << @note['pitch']
|
||||||
|
@ -342,13 +346,18 @@ class MusicXMLListener
|
||||||
@notes.push(@note)
|
@notes.push(@note)
|
||||||
@at += @note['durNum']
|
@at += @note['durNum']
|
||||||
end
|
end
|
||||||
when 'degree' then
|
@note = nil
|
||||||
|
when 'forward'
|
||||||
|
@at += @duration
|
||||||
|
when 'backup'
|
||||||
|
@at -= @duration
|
||||||
|
when 'degree'
|
||||||
oldSteps = @note['steps']
|
oldSteps = @note['steps']
|
||||||
oldValue = @degree_value
|
oldValue = @degree_value
|
||||||
case @degree_type
|
case @degree_type
|
||||||
when 'subtract' then
|
when 'subtract'
|
||||||
@note['steps'] &= ~DEGREE[@degree_value][0]
|
@note['steps'] &= ~DEGREE[@degree_value][0]
|
||||||
when 'add' then
|
when 'add'
|
||||||
@degree_value = DEGREE[@degree_value][1]
|
@degree_value = DEGREE[@degree_value][1]
|
||||||
if @degree_alter < 0
|
if @degree_alter < 0
|
||||||
@degree_value >>= -@degree_alter
|
@degree_value >>= -@degree_alter
|
||||||
|
@ -356,7 +365,7 @@ class MusicXMLListener
|
||||||
@degree_value <<= @degree_alter
|
@degree_value <<= @degree_alter
|
||||||
end
|
end
|
||||||
@note['steps'] |= @degree_value
|
@note['steps'] |= @degree_value
|
||||||
when 'alter' then
|
when 'alter'
|
||||||
@degree_value = @note['steps'] & DEGREE[@degree_value][0]
|
@degree_value = @note['steps'] & DEGREE[@degree_value][0]
|
||||||
@note['steps'] ^= @degree_value
|
@note['steps'] ^= @degree_value
|
||||||
if @degree_alter < 0
|
if @degree_alter < 0
|
||||||
|
@ -366,16 +375,16 @@ class MusicXMLListener
|
||||||
end
|
end
|
||||||
@note['steps'] |= @degree_value
|
@note['steps'] |= @degree_value
|
||||||
end
|
end
|
||||||
# $stderr.puts "#{@degree_type} #{oldValue} #{@degree_alter} (#{oldSteps} -> #{@note['steps']})"
|
when 'harmony'
|
||||||
when 'harmony' then
|
|
||||||
@harm.push(@note)
|
@harm.push(@note)
|
||||||
when 'barline' then
|
@note = nil
|
||||||
|
when 'barline'
|
||||||
case @type
|
case @type
|
||||||
when 'start' then
|
when 'start'
|
||||||
@meas['begin-ending'] = {
|
@meas['begin-ending'] = {
|
||||||
'volta' => makeVolta(@number)
|
'volta' => makeVolta(@number)
|
||||||
}
|
}
|
||||||
when 'stop', 'discontinue' then
|
when 'stop', 'discontinue'
|
||||||
@meas['end-ending'] = {
|
@meas['end-ending'] = {
|
||||||
'volta' => makeVolta(@number),
|
'volta' => makeVolta(@number),
|
||||||
'last' => @type == 'discontinue'
|
'last' => @type == 'discontinue'
|
||||||
|
@ -391,7 +400,7 @@ class MusicXMLListener
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
when 'measure' then
|
when 'measure'
|
||||||
@meas['properties'] = @props.length-1 unless @props.empty?
|
@meas['properties'] = @props.length-1 unless @props.empty?
|
||||||
if @part == 'HARM'
|
if @part == 'HARM'
|
||||||
@meas['chords'] = makeChords(@notes)
|
@meas['chords'] = makeChords(@notes)
|
||||||
|
@ -416,7 +425,7 @@ class MusicXMLListener
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
when 'part' then
|
when 'part'
|
||||||
OUTPUT['properties'] = @props unless @part == 'HARM'
|
OUTPUT['properties'] = @props unless @part == 'HARM'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -50,13 +50,9 @@ end
|
||||||
|
|
||||||
def _part_list
|
def _part_list
|
||||||
part_list = REXML::Element.new('part-list')
|
part_list = REXML::Element.new('part-list')
|
||||||
chords = REXML::Element.new('score-part')
|
|
||||||
chords.add_attribute('id', 'HARM')
|
|
||||||
chords.add_element newTextElement('part-name', 'Chords')
|
|
||||||
part_list.add_element(chords)
|
|
||||||
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', 'Melody')
|
melody.add_element newTextElement('part-name', 'Song')
|
||||||
part_list.add_element(melody)
|
part_list.add_element(melody)
|
||||||
|
|
||||||
return part_list
|
return part_list
|
||||||
|
@ -85,33 +81,43 @@ end
|
||||||
|
|
||||||
STEPS = 'C DbD EbE F GbG AbA BbB '
|
STEPS = 'C DbD EbE F GbG AbA BbB '
|
||||||
|
|
||||||
|
def _pitch(name, pitch, prefix="")
|
||||||
|
oct = pitch/12 - 1
|
||||||
|
stp = 2*(pitch%12)
|
||||||
|
step = STEPS[stp]
|
||||||
|
alt = STEPS[stp+1] == ?b
|
||||||
|
if alt
|
||||||
|
if $USE_FLATS
|
||||||
|
alt = -1
|
||||||
|
else
|
||||||
|
step = step == ?A ? ?G : step-1
|
||||||
|
alt = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if prefix.length > 0
|
||||||
|
prefix += "-"
|
||||||
|
end
|
||||||
|
pitch= REXML::Element.new(name)
|
||||||
|
pitch.add_element newTextElement(prefix+'step', step.chr)
|
||||||
|
if alt
|
||||||
|
pitch.add_element newTextElement(prefix+'alter', alt)
|
||||||
|
end
|
||||||
|
if prefix.length == 0
|
||||||
|
pitch.add_element newTextElement('octave', oct)
|
||||||
|
end
|
||||||
|
|
||||||
|
return pitch
|
||||||
|
end
|
||||||
|
|
||||||
def _note(pitch, dur, tied=0)
|
def _note(pitch, dur, tied=0)
|
||||||
note = REXML::Element.new('note')
|
note = REXML::Element.new('note')
|
||||||
if pitch == VL::NoPitch
|
if pitch == VL::NoPitch
|
||||||
note.add_element(REXML::Element.new('rest'))
|
note.add_element(REXML::Element.new('rest'))
|
||||||
else
|
else
|
||||||
oct = pitch/12 - 1
|
|
||||||
stp = 2*(pitch%12)
|
|
||||||
step = STEPS[stp]
|
|
||||||
alt = STEPS[stp+1] == ?b
|
|
||||||
if alt
|
|
||||||
if $USE_FLATS
|
|
||||||
alt = -1
|
|
||||||
else
|
|
||||||
step = step == ?A ? ?G : step-1
|
|
||||||
alt = 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if (tied & VL::InChord) != 0
|
if (tied & VL::InChord) != 0
|
||||||
note.add_element 'chord'
|
note.add_element 'chord'
|
||||||
end
|
end
|
||||||
pitch= REXML::Element.new('pitch')
|
note.add_element(_pitch('pitch', pitch))
|
||||||
pitch.add_element newTextElement('step', step.chr)
|
|
||||||
if alt
|
|
||||||
pitch.add_element newTextElement('alter', alt)
|
|
||||||
end
|
|
||||||
pitch.add_element newTextElement('octave', oct)
|
|
||||||
note.add_element(pitch)
|
|
||||||
end
|
end
|
||||||
note.add_element newTextElement('duration', dur)
|
note.add_element newTextElement('duration', dur)
|
||||||
if (tied & VL::TiedWithPrev) != 0
|
if (tied & VL::TiedWithPrev) != 0
|
||||||
|
@ -124,45 +130,152 @@ def _note(pitch, dur, tied=0)
|
||||||
|
|
||||||
return note
|
return note
|
||||||
end
|
end
|
||||||
|
|
||||||
def _chords
|
CHORD = {
|
||||||
chords = REXML::Element.new('part')
|
#
|
||||||
chords.add_attribute('id', 'HARM')
|
# Triads
|
||||||
|
#
|
||||||
lastProp = -1
|
'major' => VL::Chord::Maj,
|
||||||
measNum = 0
|
'minor' => VL::Chord::Min,
|
||||||
INPUT['measures'].each do |meas|
|
'augmented' => VL::Chord::Aug,
|
||||||
measNum += 1
|
'diminished' => VL::Chord::Dim,
|
||||||
m = REXML::Element.new('measure')
|
#
|
||||||
m.add_attribute('number', measNum.to_s);
|
# 7ths
|
||||||
if meas['properties'] != lastProp
|
#
|
||||||
lastProp = meas['properties']
|
'dominant' => VL::Chord::Dom7,
|
||||||
m.add_element(_attributes(INPUT['properties'][lastProp]))
|
'major-seventh' => VL::Chord::Maj7,
|
||||||
|
'minor-seventh' => VL::Chord::Min7,
|
||||||
|
'diminished-seventh' => VL::Chord::Dim7,
|
||||||
|
'augmented-seventh' => VL::Chord::Aug7,
|
||||||
|
'half-diminished' => VL::Chord::M7b5,
|
||||||
|
'major-minor' => VL::Chord::MMin7,
|
||||||
|
#
|
||||||
|
# 6ths
|
||||||
|
#
|
||||||
|
'major-sixth' => VL::Chord::Maj6,
|
||||||
|
'minor-sixth' => VL::Chord::Min6,
|
||||||
|
#
|
||||||
|
# 9ths
|
||||||
|
#
|
||||||
|
'dominant-ninth' => VL::Chord::Dom9,
|
||||||
|
'major-ninth' => VL::Chord::Maj9,
|
||||||
|
'minor-ninth' => VL::Chord::Min9,
|
||||||
|
#
|
||||||
|
# 11ths
|
||||||
|
#
|
||||||
|
'dominant-11th' => VL::Chord::Dom11,
|
||||||
|
'major-11th' => VL::Chord::Maj11,
|
||||||
|
'minor-11th' => VL::Chord::Min11,
|
||||||
|
#
|
||||||
|
# 13ths
|
||||||
|
#
|
||||||
|
'dominant-13th' => VL::Chord::Dom13,
|
||||||
|
'major-13th' => VL::Chord::Maj13,
|
||||||
|
'minor-13th' => VL::Chord::Min13,
|
||||||
|
#
|
||||||
|
# Suspended
|
||||||
|
#
|
||||||
|
'suspended-second' => VL::Chord::Sus2,
|
||||||
|
'suspended-fourth' => VL::Chord::Sus4,
|
||||||
|
#
|
||||||
|
# Varia
|
||||||
|
#
|
||||||
|
'other' => VL::Unison,
|
||||||
|
'none' => 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# This list differs a bit from the list in .reader, as we prefer
|
||||||
|
# certain degrees, i.e. #9 rather than b10
|
||||||
|
#
|
||||||
|
DEGREE = [
|
||||||
|
[VL::Unison, VL::Unison],
|
||||||
|
[VL::Min2nd+VL::Maj2nd, VL::Maj2nd],
|
||||||
|
[VL::Min3rd+VL::Maj3rd, VL::Maj3rd],
|
||||||
|
[VL::Fourth, VL::Fourth],
|
||||||
|
[VL::Dim5th+VL::Fifth+VL::Aug5th, VL::Fifth],
|
||||||
|
[VL::Dim7th, VL::Dim7th],
|
||||||
|
[VL::Min7th+VL::Maj7th, VL::Min7th],
|
||||||
|
[VL::Octave, VL::Octave],
|
||||||
|
[VL::Min9th+VL::Maj9th+VL::Aug9th, VL::Maj9th],
|
||||||
|
[0, VL::Dim11th],
|
||||||
|
[VL::Dim11th+VL::Eleventh+VL::Aug11th, VL::Eleventh],
|
||||||
|
[0, VL::Dim13th],
|
||||||
|
[VL::Dim13th+VL::Min13th+VL::Maj13th, VL::Maj13th]
|
||||||
|
];
|
||||||
|
|
||||||
|
def _chord(pitch, steps, root)
|
||||||
|
#
|
||||||
|
# Pick kind. sus takes precedence
|
||||||
|
#
|
||||||
|
if (steps & (VL::Min3rd|VL::Maj3rd)) == 0 and (steps & (VL::Maj2nd|VL::Fourth))!= 0
|
||||||
|
if (steps & VL::Fourth) != 0
|
||||||
|
kind = 'suspended-fourth'
|
||||||
|
else
|
||||||
|
kind = 'suspended-second'
|
||||||
end
|
end
|
||||||
meas['chords'].each do |chord|
|
else
|
||||||
dur = (chord['durNum'] * $DIVISIONS * 4) / chord['durDenom']
|
#
|
||||||
if chord['pitch'] == VL::NoPitch
|
# It's hard to score alterations properly, so rank purely by correct steps
|
||||||
m.add_element _note(VL::NoPitch, dur)
|
#
|
||||||
else
|
best = 0
|
||||||
seenNote = 0
|
kind = 'none'
|
||||||
if chord['root'] != VL::NoPitch
|
CHORD.each do |k,mask|
|
||||||
m.add_element _note(chord['root'], dur)
|
mask &= steps
|
||||||
seenNote = VL::InChord
|
score = 0
|
||||||
end
|
while mask > 0
|
||||||
pitch = chord['pitch']
|
score += 1
|
||||||
steps = chord['steps']
|
mask &= mask-1
|
||||||
(0..25).each do |step|
|
end
|
||||||
if (steps & (1<<step)) != 0
|
if score > best
|
||||||
m.add_element _note(pitch+step, dur, seenNote)
|
kind = k
|
||||||
seenNote = VL::InChord
|
best = score
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
harm = REXML::Element.new('harmony')
|
||||||
|
harm.add_element(_pitch('root', pitch, 'root'))
|
||||||
|
harm.add_element newTextElement('kind', kind)
|
||||||
|
if root != VL::NoPitch
|
||||||
|
harm.add_element(_pitch('bass', root, 'bass'))
|
||||||
|
end
|
||||||
|
needSteps = steps & ~CHORD[kind]
|
||||||
|
extraSteps= CHORD[kind] & ~steps
|
||||||
|
if (needSteps+extraSteps) > 0
|
||||||
|
DEGREE.each_index do |deg|
|
||||||
|
mask = DEGREE[deg][0]
|
||||||
|
type = nil
|
||||||
|
need = needSteps & mask
|
||||||
|
extra= extraSteps & mask
|
||||||
|
if need != 0
|
||||||
|
if extra != 0
|
||||||
|
type = 'alter'
|
||||||
|
alter= extra > need ? -1 : 1
|
||||||
|
else
|
||||||
|
type = 'add'
|
||||||
|
step = DEGREE[deg][1]
|
||||||
|
alter= step > need ? -1 : (step < need ? 1 : 0)
|
||||||
|
end
|
||||||
|
elsif extra != 0
|
||||||
|
type = 'subtract'
|
||||||
|
alter= 0
|
||||||
|
end
|
||||||
|
if type
|
||||||
|
degree = REXML::Element.new('degree')
|
||||||
|
degree.add_element newTextElement('degree-value', (deg+1).to_s)
|
||||||
|
degree.add_element newTextElement('degree-alter', alter.to_s)
|
||||||
|
degree.add_element newTextElement('degree-type', type)
|
||||||
|
harm.add_element(degree)
|
||||||
|
|
||||||
|
needSteps &= ~need
|
||||||
|
extraSteps &= ~extra
|
||||||
|
|
||||||
|
break if (needSteps+extraSteps) == 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
chords.add_element(m)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return chords
|
return harm
|
||||||
end
|
end
|
||||||
|
|
||||||
SYLLABLE = %w[single begin end middle]
|
SYLLABLE = %w[single begin end middle]
|
||||||
|
@ -217,8 +330,33 @@ def _melody
|
||||||
barline.add_element 'ending', {'type' => 'start', 'number' => num}
|
barline.add_element 'ending', {'type' => 'start', 'number' => num}
|
||||||
m.add_element(barline)
|
m.add_element(barline)
|
||||||
end
|
end
|
||||||
|
noteAt = 0
|
||||||
|
chordAt= 0
|
||||||
|
chords = meas['chords']
|
||||||
|
chordIx= 0
|
||||||
meas['melody'].each do |note|
|
meas['melody'].each do |note|
|
||||||
dur = (note['durNum'] * $DIVISIONS * 4) / note['durDenom']
|
dur = (note['durNum'] * $DIVISIONS * 4) / note['durDenom']
|
||||||
|
tempAt = noteAt
|
||||||
|
while chordIx < chords.length && chordAt < noteAt+dur
|
||||||
|
chord = chords[chordIx]
|
||||||
|
if chord['pitch'] != VL::NoPitch
|
||||||
|
if chordAt > tempAt
|
||||||
|
fw = REXML::Element.new('forward')
|
||||||
|
fw.add_element newTextElement('duration', (chordAt-tempAt).to_s)
|
||||||
|
m.add_element(fw)
|
||||||
|
tempAt = chordAt
|
||||||
|
end
|
||||||
|
m.add_element(_chord(chord['pitch'], chord['steps'], chord['root']))
|
||||||
|
end
|
||||||
|
chordAt += (chord['durNum'] * $DIVISIONS * 4) / chord['durDenom']
|
||||||
|
chordIx += 1
|
||||||
|
end
|
||||||
|
if tempAt > noteAt
|
||||||
|
bk = REXML::Element.new('backup')
|
||||||
|
bk.add_element newTextElement('duration', (tempAt-noteAt).to_s)
|
||||||
|
m.add_element(bk)
|
||||||
|
tempAt = noteAt
|
||||||
|
end
|
||||||
n = _note(note['pitch'], dur, note['tied'] || 0)
|
n = _note(note['pitch'], dur, note['tied'] || 0)
|
||||||
stanza = 1
|
stanza = 1
|
||||||
note['lyrics'].each do |syll|
|
note['lyrics'].each do |syll|
|
||||||
|
@ -231,6 +369,7 @@ def _melody
|
||||||
end
|
end
|
||||||
stanza += 1
|
stanza += 1
|
||||||
end if note['lyrics']
|
end if note['lyrics']
|
||||||
|
noteAt += dur
|
||||||
m.add_element(n)
|
m.add_element(n)
|
||||||
end
|
end
|
||||||
if r = meas['end-repeat']
|
if r = meas['end-repeat']
|
||||||
|
@ -246,7 +385,6 @@ def _melody
|
||||||
barline.add_attribute('location', 'right')
|
barline.add_attribute('location', 'right')
|
||||||
barline.add_element newTextElement('bar-style',
|
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'}
|
|
||||||
volta = ending['volta']
|
volta = ending['volta']
|
||||||
num = nil
|
num = nil
|
||||||
(0..7).each do |i|
|
(0..7).each do |i|
|
||||||
|
@ -260,6 +398,7 @@ def _melody
|
||||||
end
|
end
|
||||||
type = ending['last'] ? "discontinue" : "stop"
|
type = ending['last'] ? "discontinue" : "stop"
|
||||||
barline.add_element 'ending', {'type', type, 'number', num}
|
barline.add_element 'ending', {'type', type, 'number', num}
|
||||||
|
barline.add_element 'repeat', {'direction' => 'backward'}
|
||||||
m.add_element(barline)
|
m.add_element(barline)
|
||||||
end
|
end
|
||||||
if meas['tocoda']
|
if meas['tocoda']
|
||||||
|
@ -277,7 +416,6 @@ def _score
|
||||||
score.add_element(_work)
|
score.add_element(_work)
|
||||||
score.add_element(_identification)
|
score.add_element(_identification)
|
||||||
score.add_element(_part_list)
|
score.add_element(_part_list)
|
||||||
score.add_element(_chords)
|
|
||||||
score.add_element(_melody)
|
score.add_element(_melody)
|
||||||
|
|
||||||
return score
|
return score
|
||||||
|
|
Loading…
Reference in New Issue
Block a user