lygre 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/grely.rb +37 -0
- data/lib/grely.rb +13 -0
- data/lib/lygre/gabcpitchreader.rb +134 -0
- data/lib/lygre/gabcscore.rb +88 -0
- data/lib/lygre/gabcsemantics.rb +138 -0
- data/lib/lygre/lilypondconvertor.rb +165 -0
- data/spec/gabcgrammar_spec.rb +222 -0
- data/spec/gabcparser_spec.rb +44 -0
- data/spec/gabcpitchreader_spec.rb +116 -0
- data/spec/gabcscore_spec.rb +143 -0
- data/spec/lilypondconvertor_spec.rb +144 -0
- data/spec/matchers.rb +20 -0
- data/spec/spec_helper.rb +37 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 526592f951e4b589a6c55ddcbdee18ef7316fe81
|
4
|
+
data.tar.gz: 41375a347f0b43ebd94eb7d48b7eec8136b06870
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: be30c1c2c467349f386da0be4838753acd35b0b7769420a6752b36cd48046ed72c9bf71dd38077500016cd162d7621b0bfaa787da2d31107c01a3c879ba1caf1
|
7
|
+
data.tar.gz: 61b8761522c20c47a2c41d445a255fd1dfb344fbced567bee020b0aefe6f3c8c526f79beb6df06b0858a478861f7c927b95366df1d4512321aea9c2160202f18
|
data/bin/grely.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/bin/env ruby
|
2
|
+
|
3
|
+
# grely
|
4
|
+
|
5
|
+
# one day it will perform
|
6
|
+
# conversion gregorio (gabc) -> lilypond
|
7
|
+
|
8
|
+
# now it only
|
9
|
+
# says if it is able to parse the given input file
|
10
|
+
|
11
|
+
require 'grely'
|
12
|
+
|
13
|
+
parser = GabcParser.new
|
14
|
+
|
15
|
+
if ARGV.size >= 1 then
|
16
|
+
inputf = ARGV[0]
|
17
|
+
rf = File.open inputf
|
18
|
+
else
|
19
|
+
rf = STDIN
|
20
|
+
end
|
21
|
+
|
22
|
+
input = rf.read
|
23
|
+
|
24
|
+
result = parser.parse(input)
|
25
|
+
|
26
|
+
if result then
|
27
|
+
STDERR.puts 'grely thinks this is a valid gabc file.'
|
28
|
+
puts LilypondConvertor.new(cadenza: true).convert result.create_score
|
29
|
+
exit 0
|
30
|
+
else
|
31
|
+
STDERR.puts 'grely considers the input invalid gabc:'
|
32
|
+
STDERR.puts
|
33
|
+
STDERR.puts "'#{parser.failure_reason}' on line #{parser.failure_line} column #{parser.failure_column}:"
|
34
|
+
STDERR.puts input.split("\n")[parser.failure_line-1]
|
35
|
+
STDERR.puts (" " * parser.failure_column) + "^"
|
36
|
+
exit 1
|
37
|
+
end
|
data/lib/grely.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# libraries needed for the gregorio -> lilypond conversion
|
2
|
+
|
3
|
+
%w{
|
4
|
+
gabcscore
|
5
|
+
gabcsemantics
|
6
|
+
gabcpitchreader
|
7
|
+
lilypondconvertor
|
8
|
+
}.each {|f| require_relative File.join('lygre', f)}
|
9
|
+
|
10
|
+
# gabc parser
|
11
|
+
require 'polyglot'
|
12
|
+
require 'treetop'
|
13
|
+
Treetop.load File.expand_path('../lib/lygre/gabcgrammar', File.dirname(__FILE__))
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'rb-music-theory'
|
4
|
+
|
5
|
+
# responsible for converting the 'visual pitch' information
|
6
|
+
# contained in gabc to absolute musical pitch
|
7
|
+
class GabcPitchReader
|
8
|
+
CLEFS = {:c => "c''", :f => "f'"}
|
9
|
+
CLEF_POSITIONS = 1..4
|
10
|
+
|
11
|
+
def initialize(clef=:c, clef_position=4)
|
12
|
+
unless CLEFS.include? clef
|
13
|
+
raise ArgumentError.new "#{clef} is not a valid clef"
|
14
|
+
end
|
15
|
+
unless CLEF_POSITIONS.include? clef_position
|
16
|
+
raise ArgumentError.new "#{clef_position} is not a valid clef position"
|
17
|
+
end
|
18
|
+
|
19
|
+
@clef = clef
|
20
|
+
@clef_position = clef_position
|
21
|
+
|
22
|
+
init_base
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :clef, :clef_position, :base
|
26
|
+
|
27
|
+
# gets a gabc visual pitch, returns a RbMusicTheory::Note
|
28
|
+
def pitch(visual_note)
|
29
|
+
hnote = visual_note.to_s.ord - 'a'.ord # steps from a - the lowest writable gabc note
|
30
|
+
return @base.diatonic_steps(hnote)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def init_base
|
36
|
+
steps = -1 * (3 + # a is 3 steps under the first line
|
37
|
+
2 * (@clef_position - 1)) # one step for each line and space
|
38
|
+
@base = NoteFactory.create_note(CLEFS[@clef]).diatonic_steps(steps)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# an interface to create RBMusicTheory::Notes using lilypond notation
|
43
|
+
class NoteFactory
|
44
|
+
class << self
|
45
|
+
|
46
|
+
# notesym is a String - absolute pitch in the lilypond format.
|
47
|
+
# (currently alterations are not supported, as they are not necessary
|
48
|
+
# for our dealing with Gregorian chant.)
|
49
|
+
# returns a coresponding RBMusicTheory::Note
|
50
|
+
def create(notesym)
|
51
|
+
unless notesym =~ /^[a-g]('+|,+)?$/i
|
52
|
+
raise ArgumentError.new('#{notesym} is not a valid lilypond absolute pitch')
|
53
|
+
end
|
54
|
+
|
55
|
+
note = notesym[0]
|
56
|
+
octaves = notesym[1..-1]
|
57
|
+
|
58
|
+
n = RBMusicTheory::Note.new note.upcase
|
59
|
+
sign = 0
|
60
|
+
base_octave_shift = -1 # Note.new('C') returns c'=60, not c=48
|
61
|
+
if octaves then
|
62
|
+
sign = (octaves[0] == ',' ? -1 : 1)
|
63
|
+
octave_shift = (octaves.size * sign) + base_octave_shift
|
64
|
+
else
|
65
|
+
octave_shift = base_octave_shift
|
66
|
+
end
|
67
|
+
n += octave_shift * RBMusicTheory::NoteInterval.octave.value # strangely, NoteInterval cannot be multiplied
|
68
|
+
|
69
|
+
return n
|
70
|
+
end
|
71
|
+
|
72
|
+
alias :create_note :create
|
73
|
+
alias :[] :create
|
74
|
+
|
75
|
+
# returns a lilypond absolute pitch for the given RbMusicTheory::Note
|
76
|
+
#
|
77
|
+
# this method doesn't fit well in a *factory*, but
|
78
|
+
# #create translates lilypond pitch to Note and #lily_abs_pitch
|
79
|
+
# does the reverse translation, so maybe just the class should be renamed
|
80
|
+
def lily_abs_pitch(note)
|
81
|
+
octave_shifts = ''
|
82
|
+
octave_diff = note.value - create('c').value
|
83
|
+
|
84
|
+
octave_value = RBMusicTheory::NoteInterval.octave.value
|
85
|
+
octave_shift = octave_diff.abs / octave_value
|
86
|
+
if octave_diff < 0 and (octave_diff.abs % octave_value) > 0 then
|
87
|
+
octave_shift += 1
|
88
|
+
end
|
89
|
+
|
90
|
+
octave_signs = (octave_diff >= 0 ? "'" : ",") * octave_shift
|
91
|
+
note.name.downcase + octave_signs
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# monkey-patch Note to add step arithmetics
|
97
|
+
module RBMusicTheory
|
98
|
+
|
99
|
+
class Note
|
100
|
+
|
101
|
+
def diatonic_steps(steps, scale=nil)
|
102
|
+
if scale.nil? then
|
103
|
+
scale = self.class.new('C').major_scale
|
104
|
+
end
|
105
|
+
|
106
|
+
deg = self.degree_in(scale)
|
107
|
+
|
108
|
+
return scale.degree(deg + steps)
|
109
|
+
end
|
110
|
+
|
111
|
+
# note's degree in a scale
|
112
|
+
def degree_in(scale)
|
113
|
+
degree = scale.note_names.index(self.name)
|
114
|
+
if degree.nil? then
|
115
|
+
raise ArgumentError.new("#{name} is not a member of #{scale.note_names} scale")
|
116
|
+
end
|
117
|
+
degree += 1 # degrees start with 1
|
118
|
+
|
119
|
+
in_base_octave = scale.degree(degree)
|
120
|
+
octave_steps = scale.note_names.size
|
121
|
+
octave_value = RBMusicTheory::NoteInterval.octave.value
|
122
|
+
|
123
|
+
value_difference = self.value - in_base_octave.value
|
124
|
+
octave_difference = value_difference.abs / octave_value
|
125
|
+
if value_difference < 0 then
|
126
|
+
octave_difference *= -1
|
127
|
+
end
|
128
|
+
|
129
|
+
degree += octave_difference * octave_steps
|
130
|
+
|
131
|
+
return degree
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# todo: wrap in a module
|
4
|
+
|
5
|
+
# initialized by arguments or by assignment in a block;
|
6
|
+
# frozen thereafter
|
7
|
+
class Immutable
|
8
|
+
def initialize(args={})
|
9
|
+
args.each_pair do |k,v|
|
10
|
+
writer = (k.to_s + '=').to_sym
|
11
|
+
self.send(writer, v)
|
12
|
+
end
|
13
|
+
|
14
|
+
yield self if block_given?
|
15
|
+
|
16
|
+
freeze
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# clean and easy to use music data produced by
|
21
|
+
# Gabc::ScoreNode#create_score
|
22
|
+
# from the syntax tree created by GabcParser
|
23
|
+
class GabcScore < Immutable
|
24
|
+
|
25
|
+
# header fields as Hash
|
26
|
+
attr_accessor :header
|
27
|
+
|
28
|
+
# music information as GabcMusic
|
29
|
+
attr_accessor :music
|
30
|
+
end
|
31
|
+
|
32
|
+
class GabcMusic < Immutable
|
33
|
+
|
34
|
+
attr_accessor :clef
|
35
|
+
|
36
|
+
# Array of GabcWords
|
37
|
+
attr_accessor :words
|
38
|
+
end
|
39
|
+
|
40
|
+
class GabcClef < Immutable
|
41
|
+
|
42
|
+
# 'c' or 'f'
|
43
|
+
attr_accessor :pitch
|
44
|
+
|
45
|
+
# Integer 1...4
|
46
|
+
attr_accessor :line
|
47
|
+
|
48
|
+
# Boolean
|
49
|
+
attr_accessor :bemol
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
"#{pitch}#{bemol ? 'b' : ''}#{line}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# collection of syllables
|
57
|
+
class GabcWord < Array
|
58
|
+
|
59
|
+
def initialize(*args)
|
60
|
+
super(*args)
|
61
|
+
freeze
|
62
|
+
end
|
63
|
+
|
64
|
+
alias_method :each_syllable, :each
|
65
|
+
end
|
66
|
+
|
67
|
+
class GabcSyllable < Immutable
|
68
|
+
|
69
|
+
# String; may be empty
|
70
|
+
attr_accessor :lyrics
|
71
|
+
|
72
|
+
# Array of GabcNotes and other objects; may be empty
|
73
|
+
attr_accessor :notes
|
74
|
+
end
|
75
|
+
|
76
|
+
class GabcNote < Immutable
|
77
|
+
|
78
|
+
attr_accessor :pitch
|
79
|
+
attr_accessor :shape
|
80
|
+
attr_accessor :initio_debilis
|
81
|
+
attr_accessor :rhythmic_signs
|
82
|
+
attr_accessor :accent
|
83
|
+
end
|
84
|
+
|
85
|
+
class GabcDivisio < Immutable
|
86
|
+
|
87
|
+
attr_accessor :type
|
88
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# classes containing semantics of various nodes
|
4
|
+
# instantiated by the GabcParser
|
5
|
+
|
6
|
+
require 'treetop'
|
7
|
+
require_relative 'gabcscore'
|
8
|
+
|
9
|
+
# monkey-patch SyntaxNode
|
10
|
+
# to add a useful traversal method
|
11
|
+
class Treetop::Runtime::SyntaxNode
|
12
|
+
|
13
|
+
def each_element
|
14
|
+
return if elements.nil?
|
15
|
+
elements.each {|e| yield e }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Gabc
|
20
|
+
|
21
|
+
SyntaxNode = Treetop::Runtime::SyntaxNode
|
22
|
+
|
23
|
+
# root node
|
24
|
+
class ScoreNode < SyntaxNode
|
25
|
+
|
26
|
+
# creates and returns a GabcScore from the syntax tree
|
27
|
+
def create_score
|
28
|
+
return GabcScore.new do |s|
|
29
|
+
s.header = header.to_hash
|
30
|
+
s.music = body.create_music
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module HeaderNode
|
36
|
+
|
37
|
+
def to_hash
|
38
|
+
r = {}
|
39
|
+
|
40
|
+
each_element do |lvl1|
|
41
|
+
lvl1.each_element do |lvl2|
|
42
|
+
lvl2.each_element do |field|
|
43
|
+
if field.is_a? HeaderFieldNode then
|
44
|
+
r[field.field_id.text_value] = field.field_value.text_value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
return r
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class HeaderFieldNode < SyntaxNode
|
55
|
+
end
|
56
|
+
|
57
|
+
class BodyNode < SyntaxNode
|
58
|
+
|
59
|
+
def create_music
|
60
|
+
GabcMusic.new do |m|
|
61
|
+
|
62
|
+
clef = elements.find {|e| e.respond_to? :clef_symbol }
|
63
|
+
if clef != nil then
|
64
|
+
m.clef = GabcClef.new(pitch: clef.clef_symbol.text_value.to_sym,
|
65
|
+
line: clef.line_number.text_value.to_i,
|
66
|
+
bemol: (clef.bemol.text_value == 'b'))
|
67
|
+
end
|
68
|
+
|
69
|
+
words = []
|
70
|
+
each_element do |ele|
|
71
|
+
if ele.is_a? WordNode then
|
72
|
+
words << ele.create_word
|
73
|
+
else
|
74
|
+
ele.each_element do |elel|
|
75
|
+
elel.each_element do |elelel|
|
76
|
+
if elelel.is_a? WordNode then
|
77
|
+
words << elelel.create_word
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
m.words = words
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
module WordNode
|
90
|
+
|
91
|
+
def create_word
|
92
|
+
w = []
|
93
|
+
|
94
|
+
each_element do |ele|
|
95
|
+
next unless ele.is_a? SyllableNode
|
96
|
+
w << GabcSyllable.new do |s|
|
97
|
+
s.lyrics = ele.lyrics.text_value
|
98
|
+
s.notes = collect_notes ele
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
return GabcWord.new w
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# recursively collects notes from a node
|
108
|
+
def collect_notes(node, arr=[])
|
109
|
+
node.each_element do |ele|
|
110
|
+
if ele.is_a? NoteNode then
|
111
|
+
arr << GabcNote.new do |n|
|
112
|
+
n.pitch = ele.note_pitch.text_value.downcase.to_sym
|
113
|
+
end
|
114
|
+
elsif ele.is_a? DivisioNode then
|
115
|
+
arr << GabcDivisio.new do |d|
|
116
|
+
d.type = ele.text_value
|
117
|
+
end
|
118
|
+
else
|
119
|
+
collect_notes ele, arr
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
return arr
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
module SyllableNode
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
class NoteNode < SyntaxNode
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
module DivisioNode
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class LilypondConvertor
|
4
|
+
|
5
|
+
# true - print if given; false - ignore; 'always' - print even if empty
|
6
|
+
DEFAULT_SETTINGS = {
|
7
|
+
version: true,
|
8
|
+
notes: true,
|
9
|
+
lyrics: true,
|
10
|
+
header: true,
|
11
|
+
cadenza: false
|
12
|
+
}
|
13
|
+
|
14
|
+
DEFAULT_CLEF = GabcClef.new(pitch: :c, line: 4, bemol: false)
|
15
|
+
|
16
|
+
# maps gabc divisiones to lilypond bars
|
17
|
+
BARS = {
|
18
|
+
':' => '\bar "|"',
|
19
|
+
';' => '\bar "|"',
|
20
|
+
'::' => '\bar "||"',
|
21
|
+
',' => '\bar "\'"'
|
22
|
+
}
|
23
|
+
|
24
|
+
def initialize(settings={})
|
25
|
+
@settings = DEFAULT_SETTINGS.dup.update(settings)
|
26
|
+
|
27
|
+
# todo: make it possible to freely choose absolute c _pitch_
|
28
|
+
@c_pitch = NoteFactory["c''"]
|
29
|
+
|
30
|
+
@lily_scale = [:c, :d, :e, :f, :g, :a, :b]
|
31
|
+
@gabc_lines = ['', :d, :f, :h, :j]
|
32
|
+
end
|
33
|
+
|
34
|
+
# converts GabcScore to Lilypond source
|
35
|
+
def convert(score)
|
36
|
+
header = score.header.keys.sort.collect do |k|
|
37
|
+
" #{k} = \"#{score.header[k]}\""
|
38
|
+
end.join "\n"
|
39
|
+
|
40
|
+
notes = []
|
41
|
+
lyrics = []
|
42
|
+
|
43
|
+
clef = score.music.clef
|
44
|
+
if clef == nil then
|
45
|
+
clef = DEFAULT_CLEF
|
46
|
+
end
|
47
|
+
@gabc_reader = GabcPitchReader.new clef.pitch, clef.line
|
48
|
+
|
49
|
+
score.music.words.each do |word|
|
50
|
+
notes << word_notes(word, clef)
|
51
|
+
lyrics << word_lyrics(word)
|
52
|
+
end
|
53
|
+
|
54
|
+
r = ''
|
55
|
+
|
56
|
+
r += "\\version \"2.16.0\"\n\n" if @settings[:version]
|
57
|
+
r += "\\score {\n"
|
58
|
+
|
59
|
+
if @settings[:notes] and
|
60
|
+
(notes.size > 0 or @settings[:notes] == 'always') then
|
61
|
+
r += " \\absolute {\n"
|
62
|
+
|
63
|
+
if @settings[:cadenza] then
|
64
|
+
r += " \\cadenzaOn\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
r += " #{notes.join(" ")}\n" +
|
68
|
+
" }\n"
|
69
|
+
end
|
70
|
+
|
71
|
+
if @settings[:lyrics] and
|
72
|
+
(lyrics.size > 0 or @settings[:lyrics] == 'always') then
|
73
|
+
r += " \\addlyrics {\n" +
|
74
|
+
" #{lyrics.join(" ")}\n" +
|
75
|
+
" }\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
if @settings[:header] and
|
79
|
+
(header.size > 0 or @settings[:header] == 'always') then
|
80
|
+
r += " \\header {\n" +
|
81
|
+
" #{header}\n" +
|
82
|
+
" }\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
r += "}\n"
|
86
|
+
|
87
|
+
return r
|
88
|
+
end
|
89
|
+
|
90
|
+
# returns the output of #convert 'minimized', with whitespace reduced
|
91
|
+
# and normalized (useful for testing)
|
92
|
+
def convert_min(score)
|
93
|
+
convert(score).gsub(/\s+/, ' ').strip
|
94
|
+
end
|
95
|
+
|
96
|
+
# makes a melisma from a group of notes
|
97
|
+
def melisma(notes)
|
98
|
+
notes[0] = (notes[0].to_s + '(').to_sym
|
99
|
+
notes[-1] = (notes[-1].to_s + ')').to_sym
|
100
|
+
return notes
|
101
|
+
end
|
102
|
+
|
103
|
+
def word_notes(word, clef)
|
104
|
+
r = []
|
105
|
+
word.each_syllable do |syl|
|
106
|
+
notes = syl.notes
|
107
|
+
|
108
|
+
if notes.empty? then
|
109
|
+
r << 's'
|
110
|
+
else
|
111
|
+
sylnotes = notes.collect do |n|
|
112
|
+
if n.is_a? GabcNote then
|
113
|
+
NoteFactory.lily_abs_pitch(@gabc_reader.pitch(n.pitch))
|
114
|
+
|
115
|
+
elsif n.is_a? GabcDivisio then
|
116
|
+
divisio = n.type
|
117
|
+
unless BARS.has_key? divisio
|
118
|
+
raise RuntimeError.new "Unhandled bar type '#{n.type}'"
|
119
|
+
end
|
120
|
+
|
121
|
+
BARS[divisio].dup
|
122
|
+
|
123
|
+
else
|
124
|
+
raise RuntimeError.new "Unknown music content #{n}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
if notes.size >= 2 then
|
129
|
+
sylnotes = melisma sylnotes
|
130
|
+
end
|
131
|
+
r += sylnotes
|
132
|
+
end
|
133
|
+
end
|
134
|
+
return r.join ' '
|
135
|
+
end
|
136
|
+
|
137
|
+
def word_lyrics(word)
|
138
|
+
word.collect do |syll|
|
139
|
+
l = syll.lyrics
|
140
|
+
|
141
|
+
if syll.lyrics.start_with? '*' then
|
142
|
+
l = '"' + syll.lyrics + '"'
|
143
|
+
end
|
144
|
+
|
145
|
+
if syll.lyrics.include? '<' then
|
146
|
+
l = syll.lyrics.gsub(/<i>([^<]+)<\/i>/) do |m|
|
147
|
+
'\italic{' + $1 + '}'
|
148
|
+
end
|
149
|
+
l = '\markup{'+l+'}'
|
150
|
+
end
|
151
|
+
|
152
|
+
if syll.notes.size == 1 and
|
153
|
+
syll.notes.first.is_a? GabcDivisio and
|
154
|
+
syll.lyrics.size > 0 then
|
155
|
+
|
156
|
+
unless l.start_with? '\markup'
|
157
|
+
l = '\markup{'+l+'}'
|
158
|
+
end
|
159
|
+
l = '\set stanza = '+l
|
160
|
+
end
|
161
|
+
|
162
|
+
l
|
163
|
+
end.join ' -- '
|
164
|
+
end
|
165
|
+
end
|