VocalEasel/Filters/org.lilypond.lilypond-source.reader

590 lines
14 KiB
Plaintext
Raw Normal View History

2007-09-06 00:43:10 +00:00
#!/usr/bin/ruby
#
# VLLilypondType.reader - Import lilypond files
#
2008-01-27 21:06:35 +00:00
$KCODE = 'u'
2007-09-06 00:43:10 +00:00
require File.dirname($0)+'/plistWriter'
require File.dirname($0)+'/vl'
OUTPUT = {'measures' => []}
2007-10-21 22:06:06 +00:00
CHORDS = []
NOTES = []
STANZAS= []
MEAS = []
2007-09-06 00:43:10 +00:00
$RELPITCH = 0
2007-10-21 22:06:06 +00:00
$timeNum = 4
$timeDenom= 4
$key = 0
$mode = '\major'
2007-09-06 00:43:10 +00:00
PITCH = {
?c => 0,
?d => 2,
?e => 4,
?f => 5,
?g => 7,
?a => 9,
?b => 11
}
2007-09-06 18:06:51 +00:00
def lyPitch(pitch, base=-1)
if !pitch || pitch =~ /^[rs]/
2007-09-06 00:43:10 +00:00
return VL::NoPitch
end
p = PITCH[pitch[0]] || 0
2007-09-06 18:06:51 +00:00
if base > -1
2007-09-06 00:43:10 +00:00
p += base
elsif $RELPITCH > 0
while $RELPITCH-p > 5
2007-10-21 21:25:41 +00:00
if $RELPITCH-p == 6
#
# f -> b choose upward interval, b -> f choose downward
#
break if p%12 == PITCH[?f]
end
2007-09-06 00:43:10 +00:00
p += 12
end
else
p += 48
2007-09-06 00:43:10 +00:00
end
pitch.scan(/'/) {|c| p += 12}
pitch.scan(/,/) {|c| p -= 12}
2007-10-21 21:25:41 +00:00
puts "#{pitch}<>#{$RELPITCH} -> #{p}" if $DEBUG
2007-09-11 15:35:39 +00:00
if base == -1 && $RELPITCH > 0
$RELPITCH = p
end
2007-09-06 00:43:10 +00:00
if pitch =~ /^[ea]s/
p -= 1
pitch[0..1] = ""
end
pitch.scan('is') { |x| p += 1 }
pitch.scan('es') { |x| p -= 1 }
return p
end
$timesNum = 1
$timesDen = 1
2007-09-06 00:43:10 +00:00
def lyDur(dur)
dur =~ /^(\d+)(\.*)(?:\*(\d+).(\d+))?/
2007-09-06 00:43:10 +00:00
num = 1
den = $1.to_i
2007-09-11 15:35:39 +00:00
if $2
(0...$2.length).each do |x|
den = 2*den
num = 2*num+1
end
end
if $3
num *= $3.to_i
den *= $4.to_i
2007-09-06 00:43:10 +00:00
end
return [num*$timesNum,den*$timesDen]
2007-09-06 00:43:10 +00:00
end
STEPS = {
'' => VL::Chord::Maj,
'm' => VL::Chord::Min,
'maj' => VL::Chord::Maj7,
'dim7' => VL::Chord::Dim7,
'dim' => VL::Chord::Dim,
'aug' => VL::Chord::Aug,
'sus4' => VL::Chord::Sus4,
'sus2' => VL::Chord::Sus2,
'sus' => VL::Chord::Sus4
}
DEGREE = [
[VL::Unison, VL::Unison],
[VL::Min2nd+VL::Maj2nd, VL::Maj2nd],
[VL::Min3rd+VL::Maj3rd, VL::Maj3rd],
[VL::Fourth, VL::Fourth],
[VL::Fifth, VL::Fifth],
[VL::Aug5th+VL::Dim7th, VL::Dim7th],
[VL::Min7th+VL::Maj7th, VL::Min7th],
[VL::Octave, VL::Octave],
[VL::Min9th+VL::Maj9th, VL::Maj9th],
[VL::Aug9th+VL::Dim11th, VL::Dim11th],
[VL::Eleventh, VL::Eleventh],
[VL::Aug11th+VL::Dim13th, VL::Dim13th],
[VL::Min13th+VL::Maj13th, VL::Maj13th]
];
2007-09-06 18:06:51 +00:00
MAJORKEY = [
0, # C
-5, # Db
2, # D
-3, # Eb
4, # E
-1, # F
-6, # Gb
1, # G
-4, # Ab
3, # A
-2, # Bb
5, # B
];
MINORKEY = [
-3, # Cm -> Eb
4, # Dbm -> E
-1, # Dm -> F
-6, # Ebm -> Gb
1, # Em -> G
-4, # Fm -> Ab
3, # F#m -> A
-2, # Gm -> Bb
5, # G#m -> B
0, # Am -> C
-5, # Bbm -> Db
2, # Bm -> D
];
2007-09-06 00:43:10 +00:00
def lySteps(steps)
steps =~ /^(maj|dim7?|aug|sus[42]?|m|)/
2007-09-06 00:43:10 +00:00
s = STEPS[$1]
steps = $'
if !($1 =~ /\d$/) && steps =~ /^(7|9|11|13)/
if (s & VL::Maj7th) == 0
2007-09-06 00:43:10 +00:00
s |= VL::Min7th
end
case $1
when '9'
s |= VL::Maj9th
when '11'
s |= VL::Maj9th+VL::Eleventh
2007-09-06 00:43:10 +00:00
when '13'
s |= VL::Maj9th+VL::Eleventh+VL::Maj13th
2007-09-06 00:43:10 +00:00
end
steps = $'
end
steps.scan(/(\^)?(\d+)([-+])?/) do |ext|
degree = DEGREE[$2.to_i-1]
if $1 == '^'
s &= ~degree[0]
else
step = degree[1]
if $3 == '+'
step <<= 1
elsif $3 == '-'
step >>= 1
end
s = (s & ~degree[0]) | step
end
end
return s
end
2007-10-21 22:06:06 +00:00
def parseLilypond
2007-09-06 00:43:10 +00:00
#
2007-10-21 22:06:06 +00:00
# Lex
2007-09-06 00:43:10 +00:00
#
2007-10-21 22:06:06 +00:00
tokens = []
INFILE.each do |line|
line.chomp!.sub!(/%.*/, "")
2008-04-22 00:32:36 +00:00
line.gsub!(/\\breve/, "1*8/4")
2008-08-16 20:42:59 +00:00
line.scan(%r$\G\s*(\{|\}|\(|\)|\||=|~|<<|>>|--|#'|#\(|##t|##f|\\\w+|\".*?\"|(\w|'|`)[-+^\w\d.'`,:*/?!]+|.)$) do |token|
2007-10-21 22:06:06 +00:00
tokens.push(token[0])
2007-09-06 00:43:10 +00:00
end
end
2007-10-21 22:06:06 +00:00
#
# Parse
#
nestLevel = 0
block = nil
level = -1
stack = []
repeats = []
lyrics = []
lastDur = 1
tied = false
repeat = 0
lyricFlags= 0
2008-08-15 13:27:43 +00:00
slur = false
2007-09-06 00:43:10 +00:00
2007-10-21 22:06:06 +00:00
while tokens.length > 0
token = tokens.shift
2007-09-06 00:43:10 +00:00
#
2007-10-21 22:06:06 +00:00
# Title, composer, etc.
2007-09-06 00:43:10 +00:00
#
2007-10-21 22:06:06 +00:00
if tokens[0] == '='
case token
when 'title','composer','poet'
key = token=='poet' ? 'lyricist' : token
value = tokens[1]
value.sub!(/"(.*)"/, '\1')
OUTPUT[key] = value
tokens[0..1]= nil
redo
end
end
case block
when '\header', '\paper'
# Ignore
when '\chords', '\chordmode'
#
# Possibly chords
#
if token.downcase =~ %r{^
2007-09-06 18:06:51 +00:00
([rs] | # Rest
2007-09-06 00:43:10 +00:00
[a-g](?:[ei]?s)? # g, ges, fis, es, as
)
(\d+ # 1, 2, 4, 8, 16 ...
\.*(?:\*\d+/\d+)? # ., *3/4
2007-09-06 00:43:10 +00:00
)?
(?:\:([-+^:.a-z\d]*))? # :maj9.7-^2
(?:/\+?( # /+
2007-09-06 00:43:10 +00:00
[a-g](?:[ei]?s)? # Root: a, bes, fis, as
))?
$}x
2007-10-21 22:06:06 +00:00
pitch = lyPitch($1, 60)
dur = $2 || lastDur
ext = $3 ? lySteps($3) : 0
root = lyPitch($4, 48)
lastDur = dur
d = lyDur(dur)
2007-09-06 00:43:10 +00:00
2007-10-21 22:06:06 +00:00
chord = {'pitch' => pitch, 'root' => root, 'steps' => ext,
'durNum'=> d[0], 'durDenom' => d[1]}
p token, chord if $DEBUG
CHORDS.push(chord)
redo
end
when 'voice'
#
# Possibly notes
#
if token.downcase =~ %r{^
2007-09-06 18:06:51 +00:00
([rs] | # Rest
[a-g](?:[ei]?s)? # g, ges, fis, es, as
[',]* # g'''
)
(\d+\.* # 1, 2, 4, 8, 16 ...
2007-09-06 18:06:51 +00:00
(?:\*\d+/\d+)? # *3/4
)?
$}x
2007-10-21 22:06:06 +00:00
pitch = lyPitch($1)
dur = $2 || lastDur
lastDur = dur
d = lyDur(dur)
2007-09-06 18:06:51 +00:00
2008-08-15 13:27:43 +00:00
if slur
#
# We don't support slurs, so we turn them into tied notes at the
# final pitch
#
ix = NOTES.size
tie= true
while tie do
break if ix == 0
note = NOTES[ix -= 1]
note['pitch'] = pitch;
note['tied'] ||= 0
note['tied'] |= VL::TiedWithNext
tie = (note['tied'] & VL::TiedWithPrev) != 0
end
tied = true
end
2007-10-21 22:06:06 +00:00
note = {'pitch' => pitch, 'durNum'=> d[0], 'durDenom' => d[1]}
note['tied'] = VL::TiedWithPrev if tied
p token, note if $DEBUG
tied = false
2008-08-15 13:27:43 +00:00
NOTES.push(note)
2007-10-21 22:06:06 +00:00
redo
elsif token == '~'
if note = NOTES.last
note['tied'] ||= 0
note['tied'] |= VL::TiedWithNext
end
tied = true
2008-08-15 13:27:43 +00:00
elsif token == '('
slur = true
elsif token == ')'
slur = false
2007-10-21 22:06:06 +00:00
elsif token == '\repeat' && (tokens[0] == 'volta' || tokens[0] == fold) &&
tokens[1] =~ /^\d+$/
stack.push([block, level, "repeat"])
level = nestLevel
repeats.push(repeat)
repeat = tokens[1].to_i
NOTES.push({'begin-repeat' => true, 'times' => repeat})
tokens[0..1] = nil
redo
elsif token == '\alternative'
inEndings = true
stack.push([block, level, "endings"])
level = nestLevel+1
voltas = 0
curVoltas = nil
NOTES.push({'begin-ending' => true})
elsif token == '\times' && tokens[0] =~ %r|^(\d+)/(\d+)|
$timesNum = $1.to_i
$timesDen = $2.to_i
stack.push([block, level, "times"])
level = nestLevel
2007-09-06 18:06:51 +00:00
end
2007-10-21 22:06:06 +00:00
when '\lyricmode'
if token == '--'
lyrics.last[1] |= VL::TiedWithNext if lyrics.size > 0
lyricFlags = VL::TiedWithPrev
elsif token == '\skip'
p ["", 0] if $DEBUG
lyrics.push ["", 0]
lyricFlags = 0
if tokens[0] =~ /\d+/
tokens[0..0] = nil
end
elsif token =~ /\\skip\d+/
p ["", 0] if $DEBUG
lyrics.push ["", 0]
lyricFlags = 0
elsif token =~ /"(.*)"/
p [$1, lyricFlags] if $DEBUG
lyrics.push [$1, lyricFlags]
lyricFlags = 0
2008-08-16 20:42:59 +00:00
elsif token =~ /^(\w|'|`).*/
#
# Handle smart quotes
#
token.gsub!(/``/, "\xE2\x80\x9C");
token.gsub!(/''/, "\xE2\x80\x9D");
token.gsub!(/'/, "\xE2\x80\x99");
2007-10-21 22:06:06 +00:00
p [token, lyricFlags] if $DEBUG
lyrics.push [token, lyricFlags]
lyricFlags = 0
2007-09-11 15:35:39 +00:00
end
end
2007-10-21 22:06:06 +00:00
#
# Nesting levels
#
case token
when '{', '<<'
nestLevel += 1
when '}', '>>'
nestLevel -= 1
if nestLevel <= level
if lv = stack.pop
block = lv[0]
level = lv[1]
type = lv[2]
else
block = nil
level = -1
2007-09-13 23:18:19 +00:00
end
2007-10-21 22:06:06 +00:00
if type == "repeat"
if tokens[0] != '\alternative'
NOTES.push({'end-repeat' => true})
repeat = repeats.pop
2007-09-13 23:18:19 +00:00
end
2007-10-21 22:06:06 +00:00
elsif type == "endings"
last = tokens[0] == '}'
if last
curVoltas = ((1<<repeat) - 1) & ~voltas
elsif !curVoltas
curVoltas = 1
while (voltas&curVoltas) != 0
curVoltas <<= 1
end
end
NOTES.push({'end-ending' => true, 'volta' => curVoltas,
'last'=>last})
voltas |= curVoltas
curVoltas = 0
if last
repeat = repeats.pop
else
NOTES.push({'begin-ending' => true})
stack.push([block, level, "endings"])
level = nestLevel
end
elsif type == "times"
$timesNum = 1
$timesDen = 1
2007-09-13 23:18:19 +00:00
end
end
2007-10-21 22:06:06 +00:00
when '\chords', '\header', '\paper', '\lyricmode'
2007-09-13 23:18:19 +00:00
stack.push([block, level, ""])
2007-10-21 22:06:06 +00:00
block = token
2007-09-11 15:35:39 +00:00
level = nestLevel
2007-10-21 22:06:06 +00:00
STANZAS.push(lyrics= []) if block == '\lyricmode'
when '\chordmode'
2007-09-13 23:18:19 +00:00
stack.push([block, level, ""])
2007-10-21 22:06:06 +00:00
block = '\chords'
level = nestLevel
when '\lyricsto'
2008-01-27 20:20:37 +00:00
tokens[0] = nil
2007-10-21 22:06:06 +00:00
when '\new'
if tokens[0] == "Lyrics"
2008-01-27 21:06:35 +00:00
if tokens[1] =~ /^\\/
tokens[0..1] = nil
else
stack.push([block, level, ""])
block = '\lyricmode'
level = nestLevel
STANZAS.push(lyrics= [])
tokens[0..0] = nil
end
2007-10-21 22:06:06 +00:00
end
when '\relative'
2007-09-13 23:18:19 +00:00
stack.push([block, level, ""])
2007-10-21 22:06:06 +00:00
if tokens[0] =~ /[a-g](?:[ei]?s)?[',]*/
$RELPITCH = lyPitch(tokens[0], 48)
tokens[0..0] = nil
else
$RELPITCH = 60
end
2007-09-06 18:06:51 +00:00
block = 'voice'
2007-10-21 22:06:06 +00:00
level = nestLevel
when '\time'
if tokens[0] =~ %r{(\d+)/(\d+)}
$timeNum = $1.to_i
$timeDenom = $2.to_i
tokens[0..0] = nil
end
if block != 'voice'
stack.push([block, level, ""])
block = 'voice'
level = nestLevel-1
end
when '\key'
p = lyPitch(tokens[0], 0)
$mode = tokens[1]
$key = $mode == '\minor' ? MINORKEY[p] : MAJORKEY[p]
tokens[0..1] = nil
if block != 'voice'
stack.push([block, level, ""])
block = 'voice'
level = nestLevel-1
end
when '\repeat'
tokens[0..1] = nil
when '\alternative'
2007-09-06 18:06:51 +00:00
end
2007-09-06 00:43:10 +00:00
end
end
2007-10-21 22:06:06 +00:00
2007-09-13 23:18:19 +00:00
def peek(where, what)
return where.first && where.first[what]
end
2007-10-21 22:06:06 +00:00
def makeMeasures
measureLen = VL::Fract.new($timeNum, $timeDenom)
#
# Make measures
#
measCount= -1
while NOTES.size > 0 || CHORDS.size > 0
measCount += 1
meas = {}
meas['measure'] = measCount
meas['properties'] = 0
if peek(NOTES, 'begin-repeat')
rep = NOTES.shift
meas['begin-repeat'] = {'times' => rep['times']}
end
2007-10-21 22:06:06 +00:00
if peek(NOTES, 'begin-ending')
NOTES.shift
meas['begin-ending'] = {}
end
if CHORDS.size > 0
mchords = []
len = VL::Fract.new(0, 1)
while len < measureLen && CHORDS.size > 0
chord = CHORDS.shift
chordLen = VL::Fract.new(chord['durNum'], chord['durDenom'])
if len+chordLen > measureLen
remLen = len+chordLen-measureLen
chordLen -= remLen
remChord = {
'pitch' => VL::NoPitch, 'root' => VL::NoPitch,
'durNum' => remLen.num, 'durDenom' => remLen.denom}
2007-10-21 22:06:06 +00:00
CHORDS.unshift(remChord)
end
mchords.push(chord)
len += chordLen
end
2007-10-21 22:06:06 +00:00
meas['chords'] = mchords
end
if NOTES.size > 0
mnotes = []
len = VL::Fract.new(0, 1)
while len < measureLen && NOTES.size > 0
note = NOTES.shift
noteLen = VL::Fract.new(note['durNum'], note['durDenom'])
if len+noteLen > measureLen
remLen = len+noteLen-measureLen
noteLen -= remLen
remNote = note.dup
remNote['durNum'] = remLen.num
remNote['durDenom'] = remLen.denom
remNote['tied'] = (remNote['tied'] || 0) | VL::TiedWithPrev
note['tied'] = (note['tied'] || 0) | VL::TiedWithNext
NOTES.unshift(remNote)
2007-09-11 15:35:39 +00:00
end
2007-10-21 22:06:06 +00:00
if note['pitch'] != VL::NoPitch &&
(!note['tied'] || (note['tied'] & VL::TiedWithPrev) == 0)
ly = []
stanza = 0
STANZAS.each_index do |i|
lyrics = STANZAS[i]
if lyrics.size > 0
stanza = i+1
syll = lyrics.shift
ly.push({'text' => syll[0].gsub('_', ' '), 'kind' => syll[1]})
else
ly.push({'text' => '', 'kind' => 0})
end
end
if stanza < ly.size
ly[stanza..-1] = nil
end
note['lyrics'] = ly if stanza > 0
2007-09-11 15:35:39 +00:00
end
2007-10-21 22:06:06 +00:00
mnotes.push(note)
len += noteLen
2007-09-11 15:35:39 +00:00
end
2007-10-21 22:06:06 +00:00
meas['melody'] = mnotes
end
2007-10-21 22:06:06 +00:00
if peek(NOTES, 'end-ending')
ending = NOTES.shift
meas['end-ending'] = {'last' => ending['last'], 'volta' => ending['volta']}
end
if peek(NOTES, 'end-repeat')
NOTES.shift
meas['end-repeat'] = {}
end
MEAS.push(meas)
2007-09-13 23:18:19 +00:00
end
end
2007-10-21 22:06:06 +00:00
begin
parseLilypond
makeMeasures
2007-09-06 18:06:51 +00:00
2007-10-21 22:06:06 +00:00
OUTPUT['measures'] = MEAS
OUTPUT['properties'] = [{
'key' => $key,
'mode' => $mode == '\minor' ? -1 : 1,
'timeNum' => $timeNum,
'timeDenom' => $timeDenom
}]
writePlist($stdout, OUTPUT)
rescue => except
$stderr.print except.message, "\n", except.backtrace.join("\n"), "\n"
end
2007-09-06 00:43:10 +00:00
# Local Variables:
# mode:ruby
# End: