roborabb 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.
data/HISTORY.md ADDED
@@ -0,0 +1,5 @@
1
+ # History
2
+
3
+ ## 0.0.1 / 10 December 2011
4
+
5
+ * Initial release
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ Roborabb
2
+ ========
3
+
4
+ Generates drumming practice charts in [lilypond][lilypond] notation.
5
+
6
+ <img
7
+ src="https://img.skitch.com/20111210-n7ey6x4jrmiaq11tjj1j8qqd4u.jpg"
8
+ alt='example score' />
9
+
10
+ Example
11
+ -------
12
+
13
+ Install the gem:
14
+
15
+ gem install roborabb
16
+
17
+ Then use it:
18
+
19
+ require 'roborabb'
20
+
21
+ rock_1 = Roborabb.construct(
22
+ title: "Rock",
23
+ subdivisions: 8,
24
+ unit: 8,
25
+ time_signature: "4/4",
26
+ notes: {
27
+ hihat: L{|env| true },
28
+ kick: L{|env| (env.subdivision + 0) % 4 == 0 },
29
+ snare: L{|env| (env.subdivision + 2) % 4 == 0 },
30
+ }
31
+ )
32
+
33
+ puts Roborabb::Lilypond.new(rock_1, bars: 16).to_lilypond
34
+
35
+ The resulting file is immediately compilable with [lilypond][lilypond]:
36
+
37
+ ruby examples/rock.rb > rock.ly && lilypond rock.ly # Generates rock.pdf
38
+
39
+ See `examples` directory for more.
40
+
41
+ [lilypond]: http://lilypond.org/
42
+
43
+ Compatibility
44
+ -------------
45
+
46
+ Only tested on ruby 1.9.3. Require 1.9, since it uses new style hashes.
47
+
48
+ Developing
49
+ ----------
50
+
51
+ git clone git://github.com/xaviershay/roborabb.git
52
+ bundle # Install development dependencies
53
+ bundle exec rake # Runs the specs
54
+
55
+ Any big new features require an acceptance test, bug fixes should only require
56
+ unit tests. Follow the conventions already present.
57
+
58
+ Status
59
+ ------
60
+
61
+ New, but complete.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task :default => :spec
10
+ rescue LoadError
11
+ $stderr.puts "rspec not available, spec task not provided"
12
+ end
data/lib/roborabb.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'roborabb/version'
2
+
3
+ require 'roborabb/core_ext'
4
+ require 'roborabb/builder'
5
+ require 'roborabb/lilypond'
6
+
7
+ module Roborabb
8
+ def construct(plan)
9
+ unless plan.has_key?(:notes)
10
+ raise(ArgumentError, "Plan does not contain :notes")
11
+ end
12
+ Builder.new(plan)
13
+ end
14
+ module_function :construct
15
+ end
16
+
@@ -0,0 +1,23 @@
1
+ module Roborabb
2
+ class Bar
3
+ ATTRIBUTES = [
4
+ :beat_structure,
5
+ :notes,
6
+ :subdivisions,
7
+ :time_signature,
8
+ :title,
9
+ :unit,
10
+ ]
11
+ attr_reader *ATTRIBUTES
12
+
13
+ def initialize(attributes)
14
+ ATTRIBUTES.each do |x|
15
+ send("#{x}=", attributes[x])
16
+ end
17
+ end
18
+
19
+ protected
20
+
21
+ attr_writer *ATTRIBUTES
22
+ end
23
+ end
@@ -0,0 +1,77 @@
1
+ require 'ostruct'
2
+
3
+ require 'roborabb/bar'
4
+
5
+ module Roborabb
6
+ class Builder
7
+ attr_reader :plan
8
+
9
+ def initialize(plan_hash)
10
+ self.plan = OpenStruct.new(plan_hash)
11
+ self.bar_env = OpenStruct.new(index: 0)
12
+ self.enumerator = Enumerator.new do |yielder|
13
+ loop do
14
+ yielder.yield(generate_bar)
15
+ bar_env.index += 1
16
+ end
17
+ end
18
+ end
19
+
20
+ def next
21
+ enumerator.next
22
+ end
23
+
24
+ protected
25
+
26
+ def generate_bar
27
+ notes = subdivisions.inject(empty_notes) do |notes, subdivision|
28
+ env = build_env(subdivision)
29
+
30
+ plan.notes.map do |name, f|
31
+ notes[name] << resolve(f, env)
32
+ end
33
+
34
+ notes
35
+ end
36
+
37
+ Bar.new(
38
+ subdivisions: subdivisions.max + 1,
39
+ unit: resolve(plan.unit, bar_env),
40
+ time_signature: resolve(plan.time_signature, bar_env),
41
+ beat_structure: resolve(plan.beat_structure, bar_env),
42
+ title: resolve(plan.title, bar_env),
43
+ notes: notes
44
+ )
45
+ end
46
+
47
+ def resolve(f, env)
48
+ if f.respond_to?(:call)
49
+ f.call(env)
50
+ else
51
+ f
52
+ end
53
+ end
54
+
55
+ def subdivisions
56
+ (0...resolve(plan.subdivisions, bar_env))
57
+ end
58
+
59
+ def empty_notes
60
+ x = plan.notes.keys.map do |name|
61
+ [name, []]
62
+ end
63
+ Hash[x]
64
+ end
65
+
66
+ def build_env(subdivision)
67
+ OpenStruct.new(
68
+ subdivision: subdivision,
69
+ bar: bar_env
70
+ )
71
+ end
72
+
73
+ attr_writer :plan
74
+ attr_accessor :enumerator
75
+ attr_accessor :bar_env
76
+ end
77
+ end
@@ -0,0 +1 @@
1
+ alias :L :lambda
@@ -0,0 +1,149 @@
1
+ require 'roborabb/bar'
2
+
3
+ module Roborabb
4
+ class Lilypond
5
+ def initialize(generator, opts)
6
+ self.generator = generator
7
+ self.opts = opts
8
+ end
9
+
10
+ def to_lilypond
11
+ score = opts[:bars].times.map do
12
+ bar = generator.next
13
+
14
+ format_bar(bar)
15
+ end
16
+
17
+ lilypond do
18
+ voice(:up) { format_bars(score, :upper) } +
19
+ voice(:down) { format_bars(score, :lower) }
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ attr_accessor :generator, :opts, :title
26
+
27
+ def format_bars(bars, voice)
28
+ last_plan = Bar.new({})
29
+ bars.map do |bar|
30
+ plan = bar[:bar]
31
+
32
+ preamble = ""
33
+ if last_plan.time_signature != plan.time_signature
34
+ preamble += %(\\time #{plan.time_signature}\n)
35
+ end
36
+
37
+ if last_plan.beat_structure != plan.beat_structure && plan.beat_structure
38
+ preamble += %(\\set Staff.beatStructure = #'(%s)\n) % [
39
+ plan.beat_structure.join(' ')
40
+ ]
41
+ end
42
+ last_plan = plan
43
+ self.title = plan.title
44
+
45
+ preamble + bar[voice]
46
+ end.join(' | ') + ' \\bar "|."'
47
+ end
48
+
49
+ def lilypond
50
+ # Evaluating the content first is necessary to infer the title.
51
+ content = yield
52
+
53
+ <<-LP
54
+ \\version "2.14.2"
55
+ \\header {
56
+ title = "#{title}"
57
+ subtitle = " "
58
+ }
59
+ \\new DrumStaff <<
60
+ #{content}
61
+ >>
62
+ LP
63
+ end
64
+
65
+ def voice(direction)
66
+ result = <<-LP
67
+ \\new DrumVoice {
68
+ \\override Rest #'direction = ##{direction}
69
+ \\stem#{direction == :up ? "Up" : "Down"} \\drummode {
70
+ #{yield}
71
+ }
72
+ }
73
+ LP
74
+ end
75
+
76
+ def format_bar(bar)
77
+ {
78
+ bar: bar,
79
+ upper: format_notes(bar, expand(hashslice(bar.notes, :hihat))),
80
+ lower: format_notes(bar, expand(hashslice(bar.notes, :kick, :snare)))
81
+ }
82
+ end
83
+
84
+ def format_notes(bar, notes)
85
+ notes.map do |note|
86
+ if note[0].length == 1
87
+ mappings[note[0][0]] + duration(bar, note[1]).to_s
88
+ elsif note[0].length > 1
89
+ "<%s>%s" % [
90
+ note[0].map {|x| mappings[x] }.join(' '),
91
+ duration(bar, note[1])
92
+ ]
93
+ else
94
+ "r%s" % duration(bar, note[1])
95
+ end
96
+ end.join(" ")
97
+ end
98
+
99
+ def mappings
100
+ {
101
+ kick: 'bd',
102
+ snare: 'sn',
103
+ hihat: 'hh'
104
+ }
105
+ end
106
+
107
+ def duration(bar, x)
108
+ unit = bar.unit
109
+ [
110
+ unit,
111
+ unit / 2,
112
+ (unit / 2).to_s + ".",
113
+ unit / 4
114
+ ].map(&:to_s)[x-1] || raise("Unsupported duration: #{x}")
115
+ end
116
+
117
+ def hashslice(hash, *keep_keys)
118
+ h = {}
119
+ keep_keys.each do |key|
120
+ h[key] = hash[key] if hash.has_key?(key)
121
+ end
122
+ h
123
+ end
124
+
125
+ def expand(notes)
126
+ accum = []
127
+ time = 0
128
+ out = notes.values.transpose.inject([]) do |out, on_notes|
129
+ on = [*on_notes].map.with_index do |x, i|
130
+ notes.keys[i] if x
131
+ end.compact
132
+
133
+ if !on.empty? || time >= 4
134
+ if time > 0
135
+ out << [accum, time]
136
+ end
137
+ accum = on
138
+ time = 0
139
+ end
140
+ time += 1
141
+
142
+ out
143
+ end
144
+
145
+ out << [accum, time] if time > 0
146
+ out
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,3 @@
1
+ module Roborabb
2
+ VERSION = "0.0.1"
3
+ end
data/roborabb.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/roborabb/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Xavier Shay"]
6
+ gem.email = ["hello@xaviershay.com"]
7
+ gem.description = %q{Algorithmically generate practice drum scores}
8
+ gem.summary = %q{
9
+ Algorithmically generate practice drum scores. Customize algorithms with
10
+ ruby with an archaeopteryx-inspired style, output to lilypond format.
11
+ }
12
+ gem.homepage = "http://github.com/xaviershay/roborabb"
13
+
14
+ gem.executables = []
15
+ gem.files = Dir.glob("{spec,lib}/**/*.rb") + %w(
16
+ README.md
17
+ HISTORY.md
18
+ Rakefile
19
+ roborabb.gemspec
20
+ )
21
+ gem.test_files = Dir.glob("spec/**/*.rb")
22
+ gem.name = "roborabb"
23
+ gem.require_paths = ["lib"]
24
+ gem.version = Roborabb::VERSION
25
+ gem.has_rdoc = false
26
+ gem.add_development_dependency 'rspec', '~> 2.0'
27
+ gem.add_development_dependency 'rake'
28
+ end
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
2
+ require 'roborabb'
3
+
4
+ describe 'outputting to lilypond' do
5
+ it 'outputs a basic rock beat' do
6
+ rabb = Roborabb.construct(
7
+ subdivisions: 8,
8
+ unit: 8,
9
+ time_signature: "4/4",
10
+ notes: {
11
+ hihat: L{|env| true },
12
+ kick: L{|env| (env.subdivision + 0) % 4 == 0 },
13
+ snare: L{|env| (env.subdivision + 2) % 4 == 0 },
14
+ }
15
+ )
16
+ ly = Roborabb::Lilypond.new(rabb, bars: 2)
17
+ output = ly.to_lilypond.lines.map(&:chomp).join
18
+ output.should include('\\time 4/4')
19
+ output.should include('hh8 hh8 hh8 hh8 | hh8 hh8 hh8 hh8')
20
+ output.should include('bd4 sn4 | bd4 sn4')
21
+ end
22
+ end
data/spec/unit_spec.rb ADDED
@@ -0,0 +1,259 @@
1
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
2
+ require 'roborabb'
3
+
4
+ describe Roborabb do
5
+ def notes(rabb)
6
+ rabb.next.notes
7
+ end
8
+
9
+ def default_attributes
10
+ {
11
+ subdivisions: 2,
12
+ unit: 8,
13
+ time_signature: '1/4',
14
+ notes: {}
15
+ }
16
+ end
17
+
18
+ def construct(attributes)
19
+ Roborabb.construct(default_attributes.merge(attributes))
20
+ end
21
+
22
+ describe '#construct' do
23
+ it 'raises Argument error when no :notes given' do
24
+ lambda {
25
+ Roborabb.construct(default_attributes.delete_if {|k, _| k == :notes })
26
+ }.should raise_error(ArgumentError)
27
+ end
28
+ end
29
+
30
+ describe '#next' do
31
+ it 'allows a value for notes' do
32
+ rabb = construct(
33
+ subdivisions: 2,
34
+ notes: { a: 'A' }
35
+ )
36
+
37
+ notes(rabb).should == { a: ['A', 'A'] }
38
+ end
39
+
40
+ it 'includes subdivision in env yielded to notes' do
41
+ rabb = construct(
42
+ subdivisions: 3,
43
+ notes: {
44
+ a: :subdivision.to_proc
45
+ }
46
+ )
47
+
48
+ notes(rabb).should == { a: [0, 1, 2] }
49
+ end
50
+
51
+ it 'includes bar number in env yielded to config' do
52
+ rabb = construct(
53
+ subdivisions: L{|e| e.index + 1 },
54
+ notes: { a: 1 }
55
+ )
56
+
57
+ 3.times.map { notes(rabb) }.should == [
58
+ { a: [1] },
59
+ { a: [1, 1] },
60
+ { a: [1, 1, 1] }
61
+ ]
62
+ end
63
+
64
+ it 'includes bar number in env yielded to notes' do
65
+ rabb = construct(
66
+ subdivisions: 2,
67
+ notes: { a: L{|e| e.bar.index } }
68
+ )
69
+
70
+ 3.times.map { notes(rabb) }.should == [
71
+ { a: [0, 0] },
72
+ { a: [1, 1] },
73
+ { a: [2, 2] }
74
+ ]
75
+ end
76
+
77
+ it 'includes subdivisons in returned object' do
78
+ rabb = construct(subdivisions: 2)
79
+ rabb.next.subdivisions.should == 2
80
+ end
81
+
82
+ it 'includes unit in returned object' do
83
+ rabb = construct(unit: 8)
84
+ rabb.next.unit.should == 8
85
+ end
86
+
87
+ it 'includes generated unit in returned object' do
88
+ rabb = construct(unit: L{|e| 8 })
89
+ rabb.next.unit.should == 8
90
+ end
91
+
92
+ it 'includes time_signature in returned object' do
93
+ rabb = construct(time_signature: "7/8")
94
+ rabb.next.time_signature.should == "7/8"
95
+ end
96
+
97
+ it 'includes generated time_signature in returned object' do
98
+ rabb = construct(time_signature: L{|e| "7/8" })
99
+ rabb.next.time_signature.should == "7/8"
100
+ end
101
+
102
+ it 'includes beat_structure in returned object' do
103
+ rabb = construct(beat_structure: [3, 2, 2])
104
+ rabb.next.beat_structure.should == [3, 2, 2]
105
+ end
106
+
107
+ it 'includes generated beat_structure in returned object' do
108
+ rabb = construct(beat_structure: L{|e| [3, 2, 2] })
109
+ rabb.next.beat_structure.should == [3, 2, 2]
110
+ end
111
+
112
+ it 'includes title in returned object' do
113
+ rabb = construct(title: "Hello")
114
+ rabb.next.title.should == "Hello"
115
+ end
116
+
117
+ it 'includes generated title in returned object' do
118
+ rabb = construct(title: L{|e| "Hello" })
119
+ rabb.next.title.should == "Hello"
120
+ end
121
+ end
122
+
123
+ end
124
+
125
+ describe Roborabb::Lilypond do
126
+ describe '#to_lilypond' do
127
+ def bar(attributes = {})
128
+ double("Bar", {
129
+ title: nil,
130
+ unit: 8,
131
+ notes: {hihat: [true]},
132
+ time_signature: "4/4",
133
+ beat_structure: [4, 4]
134
+ }.merge(attributes))
135
+ end
136
+
137
+ def output(generator, opts = {bars: 1})
138
+ formatter = described_class.new(generator.each, opts)
139
+ formatter.to_lilypond
140
+ end
141
+
142
+ it 'outputs rests' do
143
+ generator = [bar(notes: {hihat: [false]})]
144
+ output(generator).should include("r")
145
+ end
146
+
147
+ it 'outputs hihats' do
148
+ generator = [bar(notes: {hihat: [true]})]
149
+ output(generator).should include("hh")
150
+ end
151
+
152
+ it 'calculates durations correctly to a maximum of four units' do
153
+ generator = [bar(unit: 32, notes: {hihat:
154
+ [true] +
155
+ [true] + [false] * 1 +
156
+ [true] + [false] * 2 +
157
+ [true] + [false] * 3 +
158
+ [true] + [false] * 4
159
+ })]
160
+
161
+ output(generator).should include("hh32 hh16 hh16. hh8 hh8 r32")
162
+ end
163
+
164
+ it 'outputs kicks and snares' do
165
+ generator = [bar(unit: 4, notes: {
166
+ kick: [true, false],
167
+ snare: [false, true]
168
+ })]
169
+ output(generator).should include("bd4 sn4")
170
+ end
171
+
172
+ it 'can output two notes at the same time' do
173
+ generator = [bar(unit: 4, notes: {kick: [true], snare: [true]})]
174
+ output(generator).should include("<bd sn>4")
175
+ end
176
+
177
+ it 'can output a rest before a note' do
178
+ generator = [bar(unit: 8, notes: {hihat: [false, true]})]
179
+ output(generator).should include("r8 hh8")
180
+ end
181
+
182
+ it 'includes lilypond preamble' do
183
+ lilypond = output([bar])
184
+ lilypond.should include("\\version")
185
+ lilypond.should include("\\new DrumStaff")
186
+ end
187
+
188
+ it 'places hihats and kick/snare in different voices' do
189
+ generator = [bar(unit: 8, notes: {
190
+ hihat: [true, true],
191
+ kick: [true, false],
192
+ snare: [false, true]
193
+ })]
194
+ voices = output(generator).split("\\new DrumVoice")[1..-1]
195
+ voices[0].should include("hh8 hh8")
196
+ voices[0].should include("\\override Rest #'direction = #up")
197
+ voices[0].should include("\\stemUp")
198
+ voices[1].should include("bd8 sn8")
199
+ voices[1].should include("\\stemDown")
200
+ end
201
+
202
+ it 'includes bar lines' do
203
+ generator = [
204
+ bar(notes: {hihat: [true] }),
205
+ bar(notes: {hihat: [false] }),
206
+ ]
207
+ bars = output(generator, bars: 2).split('|')
208
+ bars[0].should include('hh')
209
+ bars[1].should include('r')
210
+ end
211
+
212
+ it 'includes time signature changes per bar' do
213
+ generator = [
214
+ bar(time_signature: "1/8"),
215
+ bar(time_signature: "1/8"),
216
+ bar(time_signature: "1/4"),
217
+ ]
218
+ bars = output(generator, bars: 3).split('|')
219
+ bars[0].should include(%(\\time 1/8))
220
+ bars[1].should_not include(%(\\time))
221
+ bars[2].should include(%(\\time 1/4))
222
+ end
223
+
224
+ it 'includes beat structure changes per bar' do
225
+ generator = [
226
+ bar(beat_structure: [3, 2]),
227
+ bar(beat_structure: [3, 2]),
228
+ bar(beat_structure: [2, 3]),
229
+ ]
230
+ bars = output(generator, bars: 3).split('|')
231
+ bars[0].should include(%(\\set Staff.beatStructure = #'(3 2)))
232
+ bars[1].should_not include(%(\\set Staff.beatStructure))
233
+ bars[2].should include(%(\\set Staff.beatStructure = #'(2 3)))
234
+ end
235
+
236
+ it 'does not include beat structure if none provided' do
237
+ generator = [
238
+ bar(beat_structure: [3, 2]),
239
+ bar(beat_structure: nil)
240
+ ]
241
+ bars = output(generator, bars: 2).split('|')
242
+ bars[0].should include(%(\\set Staff.beatStructure = #'(3 2)))
243
+ bars[1].should_not include(%(\\set Staff.beatStructure))
244
+ end
245
+
246
+ it 'includes a final double bar line' do
247
+ output([bar]).should include(' \\bar "|."')
248
+ end
249
+
250
+ it "includes the final bar's title as the document title" do
251
+ lilypond = output([
252
+ bar(title: 'Wrong'),
253
+ bar(title: 'Hello'),
254
+ ], bars: 2)
255
+ lilypond.should include(%(title = "Hello"))
256
+ lilypond.should_not include("Wrong")
257
+ end
258
+ end
259
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roborabb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Xavier Shay
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &2156657040 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2156657040
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &2156656460 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2156656460
36
+ description: Algorithmically generate practice drum scores
37
+ email:
38
+ - hello@xaviershay.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - spec/acceptance_spec.rb
44
+ - spec/unit_spec.rb
45
+ - lib/roborabb/bar.rb
46
+ - lib/roborabb/builder.rb
47
+ - lib/roborabb/core_ext.rb
48
+ - lib/roborabb/lilypond.rb
49
+ - lib/roborabb/version.rb
50
+ - lib/roborabb.rb
51
+ - README.md
52
+ - HISTORY.md
53
+ - Rakefile
54
+ - roborabb.gemspec
55
+ homepage: http://github.com/xaviershay/roborabb
56
+ licenses: []
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 1.8.6
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: Algorithmically generate practice drum scores. Customize algorithms with
79
+ ruby with an archaeopteryx-inspired style, output to lilypond format.
80
+ test_files:
81
+ - spec/acceptance_spec.rb
82
+ - spec/unit_spec.rb