musicality 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|