musicality 0.5.1 → 0.6.0
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 +4 -4
- data/ChangeLog.md +5 -0
- data/lib/musicality/composition/dsl/score_dsl.rb +30 -0
- data/lib/musicality/composition/dsl/score_methods.rb +77 -0
- data/lib/musicality/composition/note_generation.rb +15 -0
- data/lib/musicality/errors.rb +2 -0
- data/lib/musicality/notation/model/score.rb +18 -10
- data/lib/musicality/notation/packing/score_packing.rb +53 -37
- data/lib/musicality/printing/lilypond/score_engraver.rb +56 -29
- data/lib/musicality/version.rb +1 -1
- data/lib/musicality.rb +7 -0
- data/spec/notation/model/score_spec.rb +12 -3
- data/spec/notation/packing/score_packing_spec.rb +25 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6974b6669fd75f17166b07bf7a92e5e76298143
|
4
|
+
data.tar.gz: e8d4366d647aa439d3d63a93103578b355461292
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 231e327998bfbb3320ced367f4f71694e15b48f10380c42e3788868678c1c6c69f63001b3e43b3231ae48ef566db62ab52580c45e9b67eebccfd6d1951f72d5b
|
7
|
+
data.tar.gz: 673824837e850617754f9ea648e135516c27300b68d8d57568cf53ab5cee539d883f43d354e24eb3c4eccacd426fac9a64559f72d1b81e236353f64d136fca97
|
data/ChangeLog.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
### 0.6.0 / 2015-04-28
|
2
|
+
* Add Score DSL
|
3
|
+
* Add composition convenience methods `e`, `q`, and `dq` for generating eighth, quarter, and dotted quarter notes
|
4
|
+
* Add part selection and titling to Lilypond score engraver
|
5
|
+
|
1
6
|
### 0.5.0 / 2015-04-27
|
2
7
|
* Alter note syntax
|
3
8
|
* shift articulation placement to after pitches
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class ScoreDSL
|
4
|
+
def self.load fname
|
5
|
+
include Musicality
|
6
|
+
include Pitches
|
7
|
+
include Articulations
|
8
|
+
include Meters
|
9
|
+
include Dynamics
|
10
|
+
|
11
|
+
dsl = ScoreDSL.new
|
12
|
+
dsl.instance_eval(File.read(fname), fname)
|
13
|
+
dsl
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@score = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def score start_meter, start_tempo, &block
|
21
|
+
@score = Score::Measured.new(start_meter,start_tempo)
|
22
|
+
@score.instance_eval(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def score_yaml
|
26
|
+
@score.pack.to_yaml
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class Score
|
4
|
+
def section title, &block
|
5
|
+
a = duration
|
6
|
+
self.instance_eval(&block)
|
7
|
+
b = duration
|
8
|
+
@sections[title] = a...b
|
9
|
+
end
|
10
|
+
|
11
|
+
def repeat arg
|
12
|
+
case arg
|
13
|
+
when Range
|
14
|
+
program.push arg
|
15
|
+
when String,Symbol
|
16
|
+
program.push @sections.fetch(arg)
|
17
|
+
else
|
18
|
+
raise ArgumentError, "Arg is not a Range, String, or Symbol"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
DEFAULT_START_DYNAMIC = Dynamics::MF
|
23
|
+
def notes part_notes
|
24
|
+
raise ArgumentError, "No part notes given" if part_notes.empty?
|
25
|
+
|
26
|
+
durs = part_notes.values.map {|notes| notes.map {|n| n.duration }.inject(0,:+) }
|
27
|
+
durs_uniq = durs.uniq
|
28
|
+
raise DurationMismatchError, "New part note durations do not all match" if durs_uniq.size > 1
|
29
|
+
dur = durs_uniq.first
|
30
|
+
raise NonPositiveError, "Part note durations are not positive" if dur <= 0
|
31
|
+
|
32
|
+
a = self.duration
|
33
|
+
starting_part_dur = self.max_part_duration
|
34
|
+
part_notes.each do |part,notes|
|
35
|
+
unless parts.has_key? part
|
36
|
+
parts[part] = Part.new DEFAULT_START_DYNAMIC
|
37
|
+
if starting_part_dur > 0
|
38
|
+
parts[part].notes.push Note.new(starting_part_dur)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
parts[part].notes += notes
|
42
|
+
end
|
43
|
+
(parts.keys - part_notes.keys).each do |part|
|
44
|
+
parts[part].notes.push Note.new(dur)
|
45
|
+
end
|
46
|
+
|
47
|
+
b = self.duration
|
48
|
+
program.push a...b
|
49
|
+
a...b
|
50
|
+
end
|
51
|
+
|
52
|
+
def dynamic_change new_dynamic, transition_dur: 0, offset: 0
|
53
|
+
if transition_dur == 0
|
54
|
+
tempo_changes[self.duration + offset] = Change::Immediate.new(new_tempo)
|
55
|
+
else
|
56
|
+
tempo_changes[self.duration + offset] = Change::Gradual.linear(new_tempo, transition_dur)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class TempoBased < Score
|
61
|
+
def tempo_change new_tempo, transition_dur: 0, offset: 0
|
62
|
+
if transition_dur == 0
|
63
|
+
tempo_changes[self.duration + offset] = Change::Immediate.new(new_tempo)
|
64
|
+
else
|
65
|
+
tempo_changes[self.duration + offset] = Change::Gradual.linear(new_tempo, transition_dur)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Measured < Score::TempoBased
|
71
|
+
def meter_change new_meter, offset: 0
|
72
|
+
meter_changes[self.duration + offset] = Change::Immediate.new(new_meter)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -28,4 +28,19 @@ def make_notes rhythm, pitch_groups
|
|
28
28
|
end
|
29
29
|
module_function :make_notes
|
30
30
|
|
31
|
+
def e(pitch_groups)
|
32
|
+
pitch_groups.map {|pg| Note.eighth(pg) }
|
33
|
+
end
|
34
|
+
module_function :e
|
35
|
+
|
36
|
+
def q(pitch_groups)
|
37
|
+
pitch_groups.map {|pg| Note.quarter(pg) }
|
38
|
+
end
|
39
|
+
module_function :q
|
40
|
+
|
41
|
+
def dq(pitch_groups)
|
42
|
+
pitch_groups.map {|pg| Note.dotted_quarter(pg) }
|
43
|
+
end
|
44
|
+
module_function :dq
|
45
|
+
|
31
46
|
end
|
data/lib/musicality/errors.rb
CHANGED
@@ -2,11 +2,14 @@ module Musicality
|
|
2
2
|
|
3
3
|
class Score
|
4
4
|
include Validatable
|
5
|
-
attr_accessor :parts, :program
|
5
|
+
attr_accessor :parts, :program, :title, :composer
|
6
6
|
|
7
|
-
def initialize parts: {}, program: []
|
7
|
+
def initialize parts: {}, program: [], title: nil, composer: nil, sections: {}
|
8
8
|
@parts = parts
|
9
9
|
@program = program
|
10
|
+
@title = title
|
11
|
+
@composer = composer
|
12
|
+
@sections = sections
|
10
13
|
yield(self) if block_given?
|
11
14
|
end
|
12
15
|
|
@@ -21,7 +24,7 @@ class Score
|
|
21
24
|
return @parts == other.parts && @program == other.program
|
22
25
|
end
|
23
26
|
|
24
|
-
def
|
27
|
+
def max_part_duration
|
25
28
|
@parts.map {|name,part| part.duration }.max || 0.to_r
|
26
29
|
end
|
27
30
|
|
@@ -31,17 +34,19 @@ class Score
|
|
31
34
|
|
32
35
|
class Timed < Score
|
33
36
|
def seconds_long
|
34
|
-
|
37
|
+
max_part_duration
|
35
38
|
end
|
39
|
+
alias duration seconds_long
|
36
40
|
end
|
37
41
|
|
38
42
|
class TempoBased < Score
|
39
43
|
attr_accessor :start_tempo, :tempo_changes
|
40
44
|
|
41
|
-
|
45
|
+
# See Score#initialize for remaining kwargs
|
46
|
+
def initialize start_tempo, tempo_changes: {}, **kwargs
|
42
47
|
@start_tempo = start_tempo
|
43
48
|
@tempo_changes = tempo_changes
|
44
|
-
super(
|
49
|
+
super(**kwargs)
|
45
50
|
end
|
46
51
|
|
47
52
|
def check_methods
|
@@ -54,7 +59,7 @@ class Score
|
|
54
59
|
end
|
55
60
|
|
56
61
|
def notes_long
|
57
|
-
|
62
|
+
max_part_duration
|
58
63
|
end
|
59
64
|
|
60
65
|
private
|
@@ -81,6 +86,7 @@ class Score
|
|
81
86
|
# Tempo-based score without meter, bar lines, or fixed pulse (beat).
|
82
87
|
# Offsets are note-based, and tempo values are in quarter-notes-per-minute.
|
83
88
|
class Unmeasured < Score::TempoBased
|
89
|
+
alias duration notes_long
|
84
90
|
end
|
85
91
|
|
86
92
|
# Tempo-based score with meter, bar lines, and a fixed pulse (beat).
|
@@ -88,12 +94,12 @@ class Score
|
|
88
94
|
class Measured < Score::TempoBased
|
89
95
|
attr_accessor :start_meter, :meter_changes
|
90
96
|
|
91
|
-
|
97
|
+
# See Score::TempoBased#initialize for remaining kwargs
|
98
|
+
def initialize start_meter, start_tempo, meter_changes: {}, **kwargs
|
92
99
|
@start_meter = start_meter
|
93
100
|
@meter_changes = meter_changes
|
94
101
|
|
95
|
-
super(start_tempo,
|
96
|
-
program: program, parts: parts)
|
102
|
+
super(start_tempo, **kwargs)
|
97
103
|
end
|
98
104
|
|
99
105
|
def check_methods
|
@@ -129,6 +135,8 @@ class Score
|
|
129
135
|
end
|
130
136
|
return moff_prev + Rational(noff_end - noff_prev, mdur_prev)
|
131
137
|
end
|
138
|
+
|
139
|
+
alias duration measures_long
|
132
140
|
|
133
141
|
private
|
134
142
|
|
@@ -7,67 +7,79 @@ class Score
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.unpack packing
|
10
|
-
|
11
|
-
new(parts: score.parts, program: score.program)
|
10
|
+
new(**Score.unpack_common(packing))
|
12
11
|
end
|
13
12
|
end
|
14
13
|
|
15
14
|
class TempoBased < Score
|
16
15
|
def pack
|
17
|
-
packed_tcs = Hash[ tempo_changes.map do |offset,change|
|
18
|
-
[offset,change.pack]
|
19
|
-
end ]
|
20
|
-
|
21
16
|
pack_common.merge("start_tempo" => @start_tempo,
|
22
|
-
"tempo_changes" =>
|
17
|
+
"tempo_changes" => pack_tempo_changes)
|
23
18
|
end
|
24
19
|
|
20
|
+
def pack_tempo_changes
|
21
|
+
Hash[ tempo_changes.map do |offset,change|
|
22
|
+
[offset,change.pack]
|
23
|
+
end ]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.unpack_tempo_changes packing
|
27
|
+
Hash[ packing["tempo_changes"].map do |k,v|
|
28
|
+
[k, Change.unpack(v) ]
|
29
|
+
end ]
|
30
|
+
end
|
31
|
+
|
25
32
|
def self.unpack packing
|
26
|
-
score = Score.unpack_common(packing)
|
27
|
-
|
28
33
|
unpacked_tcs = Hash[ packing["tempo_changes"].map do |k,v|
|
29
34
|
[k, Change.unpack(v) ]
|
30
35
|
end ]
|
31
|
-
|
36
|
+
|
37
|
+
Score.unpack_common(packing)
|
32
38
|
new(packing["start_tempo"],
|
33
39
|
tempo_changes: unpacked_tcs,
|
34
|
-
|
35
|
-
|
36
|
-
)
|
37
|
-
end
|
40
|
+
**unpacked)
|
41
|
+
end
|
38
42
|
end
|
39
43
|
|
40
44
|
class Unmeasured < TempoBased
|
41
|
-
def pack
|
42
|
-
super()
|
43
|
-
end
|
44
|
-
|
45
45
|
def self.unpack packing
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
new(packing["start_tempo"],
|
47
|
+
tempo_changes: unpack_tempo_changes(packing),
|
48
|
+
**Score.unpack_common(packing))
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
52
|
class Measured < TempoBased
|
53
53
|
def pack
|
54
|
-
return super().merge("start_meter" =>
|
55
|
-
"meter_changes" =>
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
return super().merge("start_meter" => pack_start_meter,
|
55
|
+
"meter_changes" => pack_meter_changes)
|
56
|
+
end
|
57
|
+
|
58
|
+
def pack_meter_changes
|
59
|
+
Hash[ meter_changes.map do |off,change|
|
60
|
+
[off,change.pack(:with => :to_s)]
|
61
|
+
end ]
|
62
|
+
end
|
63
|
+
|
64
|
+
def pack_start_meter
|
65
|
+
start_meter.to_s
|
59
66
|
end
|
60
67
|
|
61
|
-
def self.
|
62
|
-
|
63
|
-
unpacked_start_meter = Meter.parse(packing["start_meter"])
|
64
|
-
unpacked_mcs = Hash[ packing["meter_changes"].map do |off,p|
|
68
|
+
def self.unpack_meter_changes packing
|
69
|
+
Hash[ packing["meter_changes"].map do |off,p|
|
65
70
|
[off, Change.unpack(p, :with => :to_meter) ]
|
66
71
|
end ]
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.unpack_start_meter packing
|
75
|
+
Meter.parse(packing["start_meter"])
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.unpack packing
|
79
|
+
new(unpack_start_meter(packing), packing["start_tempo"],
|
80
|
+
tempo_changes: unpack_tempo_changes(packing),
|
81
|
+
meter_changes: unpack_meter_changes(packing),
|
82
|
+
**Score.unpack_common(packing))
|
71
83
|
end
|
72
84
|
end
|
73
85
|
|
@@ -89,6 +101,8 @@ class Score
|
|
89
101
|
{ "type" => self.class.to_s.split("::")[-1],
|
90
102
|
"program" => packed_prog,
|
91
103
|
"parts" => packed_parts,
|
104
|
+
"title" => @title,
|
105
|
+
"composer" => @composer
|
92
106
|
}
|
93
107
|
end
|
94
108
|
|
@@ -98,9 +112,11 @@ class Score
|
|
98
112
|
end ]
|
99
113
|
unpacked_prog = packing["program"].map {|str| Segment.parse(str) }
|
100
114
|
|
101
|
-
|
102
|
-
parts
|
103
|
-
|
115
|
+
{ :program => unpacked_prog,
|
116
|
+
:parts => unpacked_parts,
|
117
|
+
:title => packing["title"],
|
118
|
+
:composer => packing["composer"]
|
119
|
+
}
|
104
120
|
end
|
105
121
|
end
|
106
122
|
|
@@ -15,41 +15,68 @@ class ScoreEngraver
|
|
15
15
|
end
|
16
16
|
|
17
17
|
@parts = score.collated? ? score.parts : ScoreCollator.new(score).collate_parts
|
18
|
+
@title = score.title
|
19
|
+
@composer = score.composer
|
18
20
|
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
output = "\\version \"#{LILYPOND_VERSION}\"\n
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
output += "
|
29
|
-
|
30
|
-
|
31
|
-
output += " \\time #{@start_meter.to_lilypond}\n"
|
32
|
-
end
|
22
|
+
# Generate a Lilypond header for the score
|
23
|
+
def header
|
24
|
+
output = "\\version \"#{LILYPOND_VERSION}\"\n"
|
25
|
+
output += "\\header {\n"
|
26
|
+
if @title
|
27
|
+
output += " title = \"#{@title}\"\n"
|
28
|
+
end
|
29
|
+
if @composer
|
30
|
+
output += " composer = \"#{@composer}\"\n"
|
31
|
+
end
|
32
|
+
output += "}\n"
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
note = part.notes[i]
|
37
|
-
begin
|
38
|
-
str = note.to_lilypond
|
39
|
-
rescue UnsupportedDurationError => e
|
40
|
-
binding.pry
|
41
|
-
end
|
34
|
+
return output
|
35
|
+
end
|
42
36
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
37
|
+
# Generate a Lilypond staff for the given part
|
38
|
+
def staff part, part_title, master: false
|
39
|
+
clef = ScoreEngraver.best_clef(part.notes)
|
40
|
+
output = " \\new Staff {\n"
|
41
|
+
output += " \\set Staff.instrumentName = \\markup { \"#{part_title}\" }\n"
|
42
|
+
output += " \\clef #{clef}\n"
|
43
|
+
if(master)
|
44
|
+
output += " \\time #{@start_meter.to_lilypond}\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
line = ""
|
48
|
+
part.notes.each_index do |i|
|
49
|
+
note = part.notes[i]
|
50
|
+
begin
|
51
|
+
str = note.to_lilypond + " "
|
52
|
+
rescue UnsupportedDurationError => e
|
53
|
+
binding.pry
|
48
54
|
end
|
49
|
-
output += " " + line
|
50
55
|
|
51
|
-
|
52
|
-
|
56
|
+
if (line.size + str.size) > MAX_LINE_LEN
|
57
|
+
output += " " + line
|
58
|
+
line = ""
|
59
|
+
end
|
60
|
+
line += str
|
61
|
+
end
|
62
|
+
output += " " + line
|
63
|
+
|
64
|
+
output += " }\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param [Hash] part_names A hash for titling parts differently than their names
|
68
|
+
# @param [Array] selected_parts The names of parts selected for engraving
|
69
|
+
def make_lilypond selected_parts: @parts.keys, part_titles: {}
|
70
|
+
(selected_parts - part_titles.keys).each do |part_name|
|
71
|
+
part_titles[part_name] = part_name
|
72
|
+
end
|
73
|
+
output = header
|
74
|
+
output += "{\n <<\n"
|
75
|
+
master = true
|
76
|
+
selected_parts.each do |part_name|
|
77
|
+
part = @parts[part_name]
|
78
|
+
part_title = part_titles[part_name]
|
79
|
+
output += staff part, part_title, master: master
|
53
80
|
master = false if master
|
54
81
|
end
|
55
82
|
output += " >>\n}\n"
|
data/lib/musicality/version.rb
CHANGED
data/lib/musicality.rb
CHANGED
@@ -82,6 +82,9 @@ require 'musicality/composition/transposition'
|
|
82
82
|
require 'musicality/composition/generation/counterpoint_generator'
|
83
83
|
require 'musicality/composition/generation/random_rhythm_generator'
|
84
84
|
|
85
|
+
require 'musicality/composition/dsl/score_methods'
|
86
|
+
require 'musicality/composition/dsl/score_dsl'
|
87
|
+
|
85
88
|
#
|
86
89
|
# Performance
|
87
90
|
#
|
@@ -104,6 +107,10 @@ require 'musicality/performance/midi/part_sequencer'
|
|
104
107
|
require 'musicality/performance/midi/score_sequencer'
|
105
108
|
require 'musicality/performance/midi/score_sequencing'
|
106
109
|
|
110
|
+
#
|
111
|
+
# Printing
|
112
|
+
#
|
113
|
+
|
107
114
|
require 'musicality/printing/lilypond/errors'
|
108
115
|
require 'musicality/printing/lilypond/pitch_engraving'
|
109
116
|
require 'musicality/printing/lilypond/note_engraving'
|
@@ -33,10 +33,10 @@ describe Score do
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
describe '#
|
36
|
+
describe '#max_part_duration' do
|
37
37
|
context 'no parts' do
|
38
38
|
it 'should return 0' do
|
39
|
-
Score.new.
|
39
|
+
Score.new.max_part_duration.should eq(0)
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
@@ -44,7 +44,16 @@ describe Score do
|
|
44
44
|
it 'should return the part duration' do
|
45
45
|
Score.new(parts: {"part1" => Part.new(Dynamics::PP,
|
46
46
|
notes: "/4 /4 /2 1".to_notes)
|
47
|
-
}).
|
47
|
+
}).max_part_duration.should eq(2)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'two parts' do
|
52
|
+
it 'should return the part duration of the longer part' do
|
53
|
+
Score.new(parts: {"part1" => Part.new(Dynamics::PP,
|
54
|
+
notes: "/4 /4 /2 1".to_notes), "part2" => Part.new(Dynamics::MP,
|
55
|
+
notes: "4".to_notes)
|
56
|
+
}).max_part_duration.should eq(4)
|
48
57
|
end
|
49
58
|
end
|
50
59
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
2
|
|
3
3
|
measured_score = Score::Measured.new(FOUR_FOUR,120) do |s|
|
4
|
+
s.title = "The best song ever"
|
5
|
+
s.composer = "James T."
|
6
|
+
|
4
7
|
s.meter_changes[1] = Change::Immediate.new(THREE_FOUR)
|
5
8
|
s.meter_changes[7] = Change::Immediate.new(SIX_EIGHT)
|
6
9
|
|
@@ -28,11 +31,15 @@ end
|
|
28
31
|
unmeasured_score = Score::Unmeasured.new(30) do |s|
|
29
32
|
s.program = measured_score.program
|
30
33
|
s.parts = measured_score.parts
|
34
|
+
s.title = measured_score.title
|
35
|
+
s.composer = measured_score.composer
|
31
36
|
end
|
32
37
|
|
33
38
|
timed_score = Score::Timed.new do |s|
|
34
39
|
s.program = measured_score.program
|
35
40
|
s.parts = measured_score.parts
|
41
|
+
s.title = measured_score.title
|
42
|
+
s.composer = measured_score.composer
|
36
43
|
end
|
37
44
|
|
38
45
|
[ measured_score, unmeasured_score, timed_score ].each do |score|
|
@@ -72,6 +79,16 @@ end
|
|
72
79
|
@h['parts'][name].should eq packing
|
73
80
|
end
|
74
81
|
end
|
82
|
+
|
83
|
+
it 'should add title to packing' do
|
84
|
+
@h.should have_key "title"
|
85
|
+
@h["title"].should eq @score.title
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should add composer to packing' do
|
89
|
+
@h.should have_key "composer"
|
90
|
+
@h["composer"].should eq @score.composer
|
91
|
+
end
|
75
92
|
end
|
76
93
|
|
77
94
|
describe '.unpack' do
|
@@ -90,6 +107,14 @@ end
|
|
90
107
|
it 'should successfuly unpack the program' do
|
91
108
|
@score2.program.should eq @score.program
|
92
109
|
end
|
110
|
+
|
111
|
+
it 'should successfuly unpack the title' do
|
112
|
+
@score2.title.should eq @score.title
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should successfuly unpack the composer' do
|
116
|
+
@score2.composer.should eq @score.composer
|
117
|
+
end
|
93
118
|
end
|
94
119
|
end
|
95
120
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: musicality
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Tunnell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -137,6 +137,8 @@ files:
|
|
137
137
|
- examples/notation/song1.rb
|
138
138
|
- examples/notation/song2.rb
|
139
139
|
- lib/musicality.rb
|
140
|
+
- lib/musicality/composition/dsl/score_dsl.rb
|
141
|
+
- lib/musicality/composition/dsl/score_methods.rb
|
140
142
|
- lib/musicality/composition/generation/counterpoint_generator.rb
|
141
143
|
- lib/musicality/composition/generation/random_rhythm_generator.rb
|
142
144
|
- lib/musicality/composition/model/pitch_class.rb
|