music-transcription 0.7.1 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/music-transcription.rb +3 -2
- data/lib/music-transcription/change.rb +18 -4
- data/lib/music-transcription/dynamics.rb +9 -9
- data/lib/music-transcription/errors.rb +7 -2
- data/lib/music-transcription/meter.rb +15 -0
- data/lib/music-transcription/meters.rb +11 -0
- data/lib/music-transcription/note.rb +16 -11
- data/lib/music-transcription/part.rb +30 -5
- data/lib/music-transcription/program.rb +12 -64
- data/lib/music-transcription/score.rb +51 -1
- data/lib/music-transcription/validatable.rb +31 -0
- data/lib/music-transcription/version.rb +1 -1
- data/spec/meter_spec.rb +29 -0
- data/spec/note_spec.rb +22 -0
- data/spec/part_spec.rb +43 -0
- data/spec/program_spec.rb +14 -19
- data/spec/score_spec.rb +38 -3
- data/spec/spec_helper.rb +15 -2
- metadata +4 -6
- data/lib/music-transcription/dynamic.rb +0 -27
- data/lib/music-transcription/profile.rb +0 -148
- data/spec/profile_spec.rb +0 -163
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47821dcba8bd951fa6052d201b49c4253bf6a8c0
|
4
|
+
data.tar.gz: fc84a5d2ea369f1d17fcdf52a82e8d9a61bef9da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20284ea1e670062928ef8dc4ea86a342392e6746c7b511ced9ea2e4843a5b9705ffee13302ce984d09d12a01a32ccd2a23e4a25ae8787597afe2e97df3740928
|
7
|
+
data.tar.gz: 0c9083eb4e7cc5841db15f0f873f3f0835b27b403f1cd428d7536dd9113f5c630e41a7af306f6e2c468d82c4bfe47911681c4eff5c47d6aa69309ca0d05d710f
|
data/lib/music-transcription.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# basic core classes
|
2
2
|
require 'music-transcription/version'
|
3
|
+
require 'music-transcription/validatable'
|
4
|
+
require 'music-transcription/errors'
|
3
5
|
|
4
6
|
# code for transcribing (representing) music
|
5
7
|
require 'music-transcription/pitch'
|
@@ -9,11 +11,10 @@ require 'music-transcription/accent'
|
|
9
11
|
require 'music-transcription/accents'
|
10
12
|
require 'music-transcription/change'
|
11
13
|
require 'music-transcription/note'
|
12
|
-
require 'music-transcription/profile'
|
13
|
-
require 'music-transcription/dynamic'
|
14
14
|
require 'music-transcription/dynamics'
|
15
15
|
require 'music-transcription/part'
|
16
16
|
require 'music-transcription/program'
|
17
17
|
require 'music-transcription/tempo'
|
18
18
|
require 'music-transcription/meter'
|
19
|
+
require 'music-transcription/meters'
|
19
20
|
require 'music-transcription/score'
|
@@ -7,10 +7,6 @@ class Change
|
|
7
7
|
def initialize value, duration
|
8
8
|
@value = value
|
9
9
|
@duration = duration
|
10
|
-
|
11
|
-
unless duration >= 0
|
12
|
-
raise ArgumentError, "duration #{duration} must be >= 0"
|
13
|
-
end
|
14
10
|
end
|
15
11
|
|
16
12
|
def ==(other)
|
@@ -20,15 +16,33 @@ class Change
|
|
20
16
|
end
|
21
17
|
|
22
18
|
class Immediate < Change
|
19
|
+
include Validatable
|
20
|
+
|
23
21
|
def initialize value
|
22
|
+
@check_methods = [ :ensure_zero_duration ]
|
24
23
|
super(value,0)
|
25
24
|
end
|
25
|
+
|
26
|
+
def ensure_zero_duration
|
27
|
+
unless @duration == 0
|
28
|
+
raise ValueNotZeroError, "immediate change duration #{self.duration} must be 0"
|
29
|
+
end
|
30
|
+
end
|
26
31
|
end
|
27
32
|
|
28
33
|
class Gradual < Change
|
34
|
+
include Validatable
|
35
|
+
|
29
36
|
def initialize value, transition_duration
|
37
|
+
@check_methods = [ :ensure_positive_duration ]
|
30
38
|
super(value, transition_duration)
|
31
39
|
end
|
40
|
+
|
41
|
+
def ensure_positive_duration
|
42
|
+
if @duration < 0
|
43
|
+
raise ValueNotPositiveError, "gradual change duration #{self.duration} must be >= 0"
|
44
|
+
end
|
45
|
+
end
|
32
46
|
end
|
33
47
|
end
|
34
48
|
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Music
|
2
2
|
module Transcription
|
3
|
-
module Dynamics
|
4
|
-
PPP =
|
5
|
-
PP =
|
6
|
-
P =
|
7
|
-
MP =
|
8
|
-
MF =
|
9
|
-
F =
|
10
|
-
FF =
|
11
|
-
FFF =
|
3
|
+
module Dynamics
|
4
|
+
PPP = 0.125
|
5
|
+
PP = 0.25
|
6
|
+
P = 0.375
|
7
|
+
MP = 0.5
|
8
|
+
MF = 0.625
|
9
|
+
F = 0.75
|
10
|
+
FF = 0.875
|
11
|
+
FFF = 1.0
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -1,5 +1,10 @@
|
|
1
1
|
module Music
|
2
2
|
module Transcription
|
3
|
-
class ValueNotPositiveError <
|
4
|
-
class ValueOutOfRangeError <
|
3
|
+
class ValueNotPositiveError < StandardError; end
|
4
|
+
class ValueOutOfRangeError < StandardError; end
|
5
|
+
class ValueNotZeroError < StandardError; end
|
6
|
+
class NotPositiveIntegerError < StandardError; end
|
7
|
+
class SegmentNotIncreasingError < StandardError; end
|
8
|
+
class SegmentNegativeError < StandardError; end
|
5
9
|
end
|
10
|
+
end
|
@@ -2,12 +2,27 @@ module Music
|
|
2
2
|
module Transcription
|
3
3
|
|
4
4
|
class Meter
|
5
|
+
include Validatable
|
5
6
|
|
6
7
|
attr_reader :measure_duration, :beat_duration, :beats_per_measure
|
7
8
|
def initialize beats_per_measure, beat_duration
|
8
9
|
@beats_per_measure = beats_per_measure
|
9
10
|
@beat_duration = beat_duration
|
10
11
|
@measure_duration = beats_per_measure * beat_duration
|
12
|
+
|
13
|
+
@check_methods = [ :check_beats_per_measure, :check_beat_duration ]
|
14
|
+
end
|
15
|
+
|
16
|
+
def check_beats_per_measure
|
17
|
+
unless @beats_per_measure.is_a?(Integer) && @beats_per_measure > 0
|
18
|
+
raise NotPositiveIntegerError, "beats per measure #{@beats_per_measure} is not a positive integer"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_beat_duration
|
23
|
+
unless @beat_duration > 0
|
24
|
+
raise ValueNotPositiveError, "beat duration #{@beat_duration} is not positive"
|
25
|
+
end
|
11
26
|
end
|
12
27
|
|
13
28
|
def ==(other)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Music
|
2
|
+
module Transcription
|
3
|
+
module Meters
|
4
|
+
TWO_TWO = Meter.new(2,"1/2".to_r)
|
5
|
+
TWO_FOUR = Meter.new(2,"1/4".to_r)
|
6
|
+
THREE_FOUR = Meter.new(3,"1/4".to_r)
|
7
|
+
FOUR_FOUR = Meter.new(4,"1/4".to_r)
|
8
|
+
SIX_EIGHT = Meter.new(6,"1/8".to_r)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -4,14 +4,23 @@ module Transcription
|
|
4
4
|
require 'set'
|
5
5
|
|
6
6
|
class Note
|
7
|
-
|
8
|
-
|
7
|
+
include Validatable
|
8
|
+
|
9
|
+
attr_reader :pitches, :links
|
10
|
+
attr_accessor :accent, :duration
|
9
11
|
|
10
12
|
def initialize duration, pitches = [], links: {}, accent: Accents::NONE
|
11
13
|
self.duration = duration
|
12
14
|
@pitches = Set.new(pitches).sort
|
13
15
|
@links = links
|
14
|
-
|
16
|
+
@duration = duration
|
17
|
+
@accent = accent
|
18
|
+
|
19
|
+
@check_methods = [ :ensure_positive_duration ]
|
20
|
+
end
|
21
|
+
|
22
|
+
def ensure_positive_duration
|
23
|
+
raise ValueNotPositiveError, "duration #{@duration} is not positive" if @duration <= 0
|
15
24
|
end
|
16
25
|
|
17
26
|
def == other
|
@@ -20,14 +29,6 @@ class Note
|
|
20
29
|
(@links.to_a.sort == other.links.to_a.sort) &&
|
21
30
|
(@accent == other.accent)
|
22
31
|
end
|
23
|
-
|
24
|
-
# Set the note duration.
|
25
|
-
# @param [Numeric] duration The duration to use.
|
26
|
-
# @raise [ArgumentError] if duration is not greater than 0.
|
27
|
-
def duration= duration
|
28
|
-
raise ValueNotPositiveError if duration <= 0
|
29
|
-
@duration = duration
|
30
|
-
end
|
31
32
|
|
32
33
|
def clone
|
33
34
|
Marshal.load(Marshal.dump(self))
|
@@ -61,6 +62,10 @@ class Note
|
|
61
62
|
return self
|
62
63
|
end
|
63
64
|
|
65
|
+
def valid?
|
66
|
+
@duration > 0
|
67
|
+
end
|
68
|
+
|
64
69
|
class Sixteenth < Note
|
65
70
|
def initialize pitches = [], links: {}, accent: Accents::NONE
|
66
71
|
super(Rational(1,16),pitches,links:links,accent:accent)
|
@@ -4,6 +4,8 @@ module Music
|
|
4
4
|
module Transcription
|
5
5
|
|
6
6
|
class Part
|
7
|
+
include Validatable
|
8
|
+
|
7
9
|
attr_reader :start_dynamic, :dynamic_changes, :notes
|
8
10
|
|
9
11
|
def initialize start_dynamic, notes: [], dynamic_changes: {}
|
@@ -11,11 +13,13 @@ class Part
|
|
11
13
|
@start_dynamic = start_dynamic
|
12
14
|
@dynamic_changes = dynamic_changes
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
@check_methods = [:ensure_start_dynamic, :ensure_dynamic_change_values_range
|
17
|
+
#:ensure_dynamic_change_offsets
|
18
|
+
]
|
19
|
+
end
|
20
|
+
|
21
|
+
def validatables
|
22
|
+
@notes + @dynamic_changes.values
|
19
23
|
end
|
20
24
|
|
21
25
|
def clone
|
@@ -31,6 +35,27 @@ class Part
|
|
31
35
|
def duration
|
32
36
|
return @notes.inject(0) { |sum, note| sum + note.duration }
|
33
37
|
end
|
38
|
+
|
39
|
+
def ensure_start_dynamic
|
40
|
+
unless @start_dynamic.between?(0,1)
|
41
|
+
raise RangeError, "start dynamic #{@start_dynamic} is not between 0 and 1"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
#def ensure_dynamic_change_offsets
|
46
|
+
# d = self.duration
|
47
|
+
# outofrange = @dynamic_changes.keys.select {|k| !k.between?(0,d) }
|
48
|
+
# if outofrange.any?
|
49
|
+
# raise RangeError, "dynamic change offsets #{outofrange} are not between 0 and #{d}"
|
50
|
+
# end
|
51
|
+
#end
|
52
|
+
|
53
|
+
def ensure_dynamic_change_values_range
|
54
|
+
outofrange = @dynamic_changes.values.select {|v| !v.value.between?(0,1) }
|
55
|
+
if outofrange.any?
|
56
|
+
raise RangeError, "dynamic change values #{outofrange} are not between 0 and 1"
|
57
|
+
end
|
58
|
+
end
|
34
59
|
end
|
35
60
|
|
36
61
|
end
|
@@ -6,12 +6,15 @@ module Transcription
|
|
6
6
|
# @author James Tunnell
|
7
7
|
#
|
8
8
|
class Program
|
9
|
+
include Validatable
|
10
|
+
|
9
11
|
attr_accessor :segments
|
10
12
|
|
11
13
|
# A new instance of Program.
|
12
14
|
# @param [Hash] args Hashed arguments. Required key is :segments.
|
13
15
|
def initialize segments = []
|
14
16
|
@segments = segments
|
17
|
+
@check_methods = [:ensure_increasing_segments, :ensure_nonnegative_segments]
|
15
18
|
end
|
16
19
|
|
17
20
|
# @return [Float] the sum of all program segment lengths
|
@@ -21,8 +24,7 @@ class Program
|
|
21
24
|
|
22
25
|
# compare to another Program
|
23
26
|
def == other
|
24
|
-
|
25
|
-
return @segments == other.segments
|
27
|
+
return other.respond_to?(:segments) && @segments == other.segments
|
26
28
|
end
|
27
29
|
|
28
30
|
def include? offset
|
@@ -33,74 +35,20 @@ class Program
|
|
33
35
|
end
|
34
36
|
return false
|
35
37
|
end
|
36
|
-
|
37
|
-
# For the given note elapsed, what will the note offset be?
|
38
|
-
#
|
39
|
-
def note_offset_for elapsed
|
40
|
-
raise ArgumentError, "elapsed #{elapsed} is less than 0.0" if elapsed < 0.0
|
41
|
-
raise ArgumentError, "elapsed #{elapsed} is greater than program length" if elapsed > self.length
|
42
|
-
|
43
|
-
so_far = 0.0
|
44
|
-
|
45
|
-
@segments.each do |segment|
|
46
|
-
segment_length = segment.last - segment.first
|
47
|
-
|
48
|
-
if (segment_length + so_far) > elapsed
|
49
|
-
return segment.first + (elapsed - so_far)
|
50
|
-
else
|
51
|
-
so_far += segment_length
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
raise "offset not determined even though the given elapsed is less than program length!"
|
56
|
-
end
|
57
38
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
raise ArgumentError, "offset #{offset} is not included in program" if !self.include?(offset)
|
63
|
-
|
64
|
-
elapsed = 0.0
|
65
|
-
|
66
|
-
@segments.each do |segment|
|
67
|
-
if segment.include?(offset)
|
68
|
-
elapsed += (offset - segment.first)
|
69
|
-
break
|
70
|
-
else
|
71
|
-
elapsed += (segment.last - segment.first)
|
72
|
-
end
|
39
|
+
def ensure_increasing_segments
|
40
|
+
non_increasing = @segments.select {|seg| seg.first >= seg.last }
|
41
|
+
if non_increasing.any?
|
42
|
+
raise SegmentNotIncreasingError, "Non-increasing segments found #{non_increasing}"
|
73
43
|
end
|
74
|
-
|
75
|
-
return elapsed
|
76
44
|
end
|
77
45
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
raise ArgumentError, "offset #{offset} is not included in program" if !self.include?(offset)
|
83
|
-
|
84
|
-
elapsed = 0.0
|
85
|
-
|
86
|
-
@segments.each do |segment|
|
87
|
-
if segment.include?(offset)
|
88
|
-
elapsed += note_time_converter.time_elapsed(segment.first, offset)
|
89
|
-
break
|
90
|
-
else
|
91
|
-
elapsed += note_time_converter.time_elapsed(segment.first, segment.last)
|
92
|
-
end
|
46
|
+
def ensure_nonnegative_segments
|
47
|
+
negative = @segments.select {|seg| seg.first < 0 || seg.last < 0 }
|
48
|
+
if negative.any?
|
49
|
+
raise SegmentNegativeError, "Negative segments found #{negative}"
|
93
50
|
end
|
94
|
-
|
95
|
-
return elapsed
|
96
51
|
end
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
module_function
|
101
|
-
|
102
|
-
def program segments
|
103
|
-
Program.new(:segments => segments)
|
104
52
|
end
|
105
53
|
|
106
54
|
end
|
@@ -2,6 +2,8 @@ module Music
|
|
2
2
|
module Transcription
|
3
3
|
|
4
4
|
class Score
|
5
|
+
include Validatable
|
6
|
+
|
5
7
|
attr_reader :start_meter, :start_tempo, :parts, :program, :meter_changes, :tempo_changes
|
6
8
|
|
7
9
|
def initialize start_meter, start_tempo, meter_changes: {}, tempo_changes: {}, parts: {}, program: Program.new
|
@@ -11,6 +13,54 @@ class Score
|
|
11
13
|
@tempo_changes = tempo_changes
|
12
14
|
@parts = parts
|
13
15
|
@program = program
|
16
|
+
|
17
|
+
@check_methods = [ :check_start_tempo, :check_tempo_changes,
|
18
|
+
#:check_tempo_change_offsets, :check_meter_change_offsets,
|
19
|
+
:check_meter_changes ]
|
20
|
+
end
|
21
|
+
|
22
|
+
def validatables
|
23
|
+
return [ @program, @start_meter ] +
|
24
|
+
@tempo_changes.values +
|
25
|
+
@meter_changes.values +
|
26
|
+
@meter_changes.values.map {|v| v.value} +
|
27
|
+
@parts.values
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_start_tempo
|
31
|
+
unless @start_tempo > 0
|
32
|
+
raise ValueNotPositiveError, "start tempo #{@start_tempo} is not positive"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_tempo_changes
|
37
|
+
negative = @tempo_changes.select {|k,v| v.value <= 0}
|
38
|
+
if negative.any?
|
39
|
+
raise ValueNotPositiveError, "tempo changes #{negative} are not positive"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#def check_tempo_change_offsets
|
44
|
+
# d = self.duration
|
45
|
+
# outofrange = @tempo_changes.keys.select {|k| !k.between?(0,d) }
|
46
|
+
# if outofrange.any?
|
47
|
+
# raise RangeError, "tempo change offsets #{outofrange} are not between 0 and #{d}"
|
48
|
+
# end
|
49
|
+
#end
|
50
|
+
#
|
51
|
+
#def check_meter_change_offsets
|
52
|
+
# d = self.duration
|
53
|
+
# outofrange = @meter_changes.keys.select {|k| !k.between?(0,d) }
|
54
|
+
# if outofrange.any?
|
55
|
+
# raise RangeError, "meter change offsets #{outofrange} are not between 0 and #{d}"
|
56
|
+
# end
|
57
|
+
#end
|
58
|
+
|
59
|
+
def check_meter_changes
|
60
|
+
nonzero_duration = @meter_changes.select {|k,v| v.duration != 0 }
|
61
|
+
if nonzero_duration.any?
|
62
|
+
raise ValueNotZeroError, "meter changes #{nonzero_duration} have non-zero duration"
|
63
|
+
end
|
14
64
|
end
|
15
65
|
|
16
66
|
def clone
|
@@ -28,7 +78,7 @@ class Score
|
|
28
78
|
|
29
79
|
def duration
|
30
80
|
@parts.map {|p| p.duration }.max
|
31
|
-
end
|
81
|
+
end
|
32
82
|
end
|
33
83
|
|
34
84
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# assumes that @checks is defined as an array of no-arg lambdas, each
|
2
|
+
# lambda raising an error (with useful msg) when check fails
|
3
|
+
module Validatable
|
4
|
+
attr_reader :errors
|
5
|
+
|
6
|
+
def validate
|
7
|
+
@errors = []
|
8
|
+
@check_methods.each do |check_method|
|
9
|
+
begin
|
10
|
+
send(check_method)
|
11
|
+
rescue StandardError => e
|
12
|
+
@errors.push e
|
13
|
+
end
|
14
|
+
end
|
15
|
+
if respond_to?(:validatables)
|
16
|
+
validatables.each do |v|
|
17
|
+
@errors += v.validate
|
18
|
+
end
|
19
|
+
end
|
20
|
+
return @errors
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
self.validate
|
25
|
+
@errors.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def invalid?
|
29
|
+
!self.valid?
|
30
|
+
end
|
31
|
+
end
|
data/spec/meter_spec.rb
CHANGED
@@ -47,4 +47,33 @@ describe Meter do
|
|
47
47
|
YAML.load(m.to_yaml).should eq m
|
48
48
|
end
|
49
49
|
end
|
50
|
+
|
51
|
+
describe '#valid?' do
|
52
|
+
{
|
53
|
+
'4/4 meter' => [4,'1/4'.to_r],
|
54
|
+
'2/4 meter' => [2,'1/4'.to_r],
|
55
|
+
'3/4 meter' => [2,'1/4'.to_r],
|
56
|
+
'6/8 meter' => [6,'1/8'.to_r],
|
57
|
+
'12/8 meter' => [12,'1/8'.to_r],
|
58
|
+
}.each do |context_str,args|
|
59
|
+
context context_str do
|
60
|
+
it 'should return true' do
|
61
|
+
Score.new(*args).should be_valid
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
{
|
67
|
+
'non-integer positive beats per measure' => [4.0,"1/4".to_r],
|
68
|
+
'integer negative beats per measure' => [-1,"1/4".to_r],
|
69
|
+
'zero beat duration' => [4,0.to_r],
|
70
|
+
'negative beat duration' => [4,-1.to_r],
|
71
|
+
}.each do |context_str,args|
|
72
|
+
context context_str do
|
73
|
+
it 'should return false' do
|
74
|
+
Score.new(*args).should be_invalid
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
50
79
|
end
|
data/spec/note_spec.rb
CHANGED
@@ -102,4 +102,26 @@ describe Note do
|
|
102
102
|
YAML.load(n.to_yaml).should eq n
|
103
103
|
end
|
104
104
|
end
|
105
|
+
|
106
|
+
describe '#valid?' do
|
107
|
+
context 'note with positive duration' do
|
108
|
+
it 'should return true' do
|
109
|
+
Note.new(1,[C2]).should be_valid
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'note with 0 duration' do
|
114
|
+
it 'should return false' do
|
115
|
+
Note.new(0,[C2]).should be_invalid
|
116
|
+
require 'pry'
|
117
|
+
binding.pry
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'note with negative duration' do
|
122
|
+
it 'should be invalid' do
|
123
|
+
Note.new(-1,[C2]).should be_invalid
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
105
127
|
end
|
data/spec/part_spec.rb
CHANGED
@@ -26,4 +26,47 @@ describe Part do
|
|
26
26
|
YAML.load(p.to_yaml).should eq p
|
27
27
|
end
|
28
28
|
end
|
29
|
+
|
30
|
+
describe '#valid?' do
|
31
|
+
{ 'negative start dynamic' => [-0.01],
|
32
|
+
'start dynamic > 1' => [1.01],
|
33
|
+
#'dynamic change offsets outside 0..d' => [
|
34
|
+
# 0.5, :notes => [ Note::Whole.new ],
|
35
|
+
# :dynamic_changes => { 1.2 => Change::Immediate.new(0.5) }],
|
36
|
+
#'dynamic change offsets outside 0..d' => [
|
37
|
+
# 0.5, :notes => [ Note::Whole.new ],
|
38
|
+
# :dynamic_changes => { -0.2 => Change::Immediate.new(0.5) }],
|
39
|
+
'dynamic change values outside 0..1' => [
|
40
|
+
0.5, :notes => [ Note::Whole.new ],
|
41
|
+
:dynamic_changes => { 0.2 => Change::Immediate.new(-0.01), 0.3 => Change::Gradual.new(1.01,0.2) }],
|
42
|
+
'notes with 0 duration' => [ 0.5, :notes => [ Note.new(0) ]],
|
43
|
+
'notes with negative duration' => [ 0.5, :notes => [ Note.new(-1) ]],
|
44
|
+
'gradual change with negative duration' => [
|
45
|
+
0.5, :notes => [ Note.new(1) ],
|
46
|
+
:dynamic_changes => { 0.2 => Change::Gradual.new(0.6,-0.1) }]
|
47
|
+
}.each do |context_str, args|
|
48
|
+
context context_str do
|
49
|
+
it 'should return false' do
|
50
|
+
Part.new(*args).should be_invalid
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
{
|
56
|
+
'valid notes' => [ Dynamics::PP,
|
57
|
+
:notes => [ Note::Whole.new, Note::Quarter.new([C5]) ]],
|
58
|
+
'valid dynamic values' => [ Dynamics::MF,
|
59
|
+
:notes => [ Note::Whole.new([C4]), Note::Quarter.new ],
|
60
|
+
:dynamic_changes => {
|
61
|
+
0.5 => Change::Immediate.new(Dynamics::MP),
|
62
|
+
1.2 => Change::Gradual.new(Dynamics::FF, 0.05) } ],
|
63
|
+
}.each do |context_str, args|
|
64
|
+
context context_str do
|
65
|
+
it 'should return true' do
|
66
|
+
part = Part.new(*args)
|
67
|
+
part.should be_valid
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
29
72
|
end
|
data/spec/program_spec.rb
CHANGED
@@ -28,28 +28,23 @@ describe Program do
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
describe
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
it "should return 0.0 at program start" do
|
38
|
-
@program.note_elapsed_at(@program.segments.first.first).should eq(0.0)
|
39
|
-
end
|
40
|
-
|
41
|
-
it "should return program length at program stop" do
|
42
|
-
@program.note_elapsed_at(@program.segments.last.last).should eq(@program.length)
|
31
|
+
describe '#valid?' do
|
32
|
+
context 'increasing, positive segments' do
|
33
|
+
it 'should return true' do
|
34
|
+
Program.new([0..2,1..2,0..4]).should be_valid
|
35
|
+
end
|
43
36
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
37
|
+
|
38
|
+
context 'decreasing, positive segments' do
|
39
|
+
it 'should return false' do
|
40
|
+
Program.new([2..0,2..1,04..0]).should be_invalid
|
41
|
+
end
|
48
42
|
end
|
49
43
|
|
50
|
-
|
51
|
-
|
52
|
-
|
44
|
+
context 'increasing, negative segments' do
|
45
|
+
it 'should return false' do
|
46
|
+
Program.new([-1..2,-2..0,-2..2]).should be_invalid
|
47
|
+
end
|
53
48
|
end
|
54
49
|
end
|
55
50
|
end
|
data/spec/score_spec.rb
CHANGED
@@ -3,20 +3,20 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
3
3
|
describe Score do
|
4
4
|
describe '#initialize' do
|
5
5
|
it 'should use empty containers for parameters not given' do
|
6
|
-
s = Score.new(
|
6
|
+
s = Score.new(FOUR_FOUR,120)
|
7
7
|
s.parts.should be_empty
|
8
8
|
s.program.segments.should be_empty
|
9
9
|
end
|
10
10
|
|
11
11
|
it 'should assign given parameters' do
|
12
|
-
m =
|
12
|
+
m = FOUR_FOUR
|
13
13
|
s = Score.new(m,120)
|
14
14
|
s.start_meter.should eq m
|
15
15
|
s.start_tempo.should eq 120
|
16
16
|
|
17
17
|
parts = { "piano (LH)" => Samples::SAMPLE_PART }
|
18
18
|
program = Program.new [0...0.75, 0...0.75]
|
19
|
-
mcs = { 1 => Change::Immediate.new(
|
19
|
+
mcs = { 1 => Change::Immediate.new(THREE_FOUR) }
|
20
20
|
tcs = { 1 => Change::Immediate.new(100) }
|
21
21
|
|
22
22
|
s = Score.new(m,120,
|
@@ -31,4 +31,39 @@ describe Score do
|
|
31
31
|
s.tempo_changes.should eq tcs
|
32
32
|
end
|
33
33
|
end
|
34
|
+
|
35
|
+
describe '#valid?' do
|
36
|
+
{
|
37
|
+
'just valid start meter and tempo' => [ FOUR_FOUR, 120 ],
|
38
|
+
'valid meter changes' => [ FOUR_FOUR, 120,
|
39
|
+
:meter_changes => { 1 => Change::Immediate.new(TWO_FOUR) } ],
|
40
|
+
'valid tempo changes' => [ FOUR_FOUR, 120,
|
41
|
+
:tempo_changes => { 1 => Change::Gradual.new(200, 2), 2 => Change::Immediate.new(300) } ],
|
42
|
+
'valid part' => [ FOUR_FOUR, 120, :parts => { "piano" => Samples::SAMPLE_PART }],
|
43
|
+
'valid program' => [ FOUR_FOUR, 120, :program => Program.new([0..2,0..2]) ]
|
44
|
+
}.each do |context_str,args|
|
45
|
+
context context_str do
|
46
|
+
it 'should return true' do
|
47
|
+
Score.new(*args).should be_valid
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
{
|
53
|
+
'invalid start tempo' => [ FOUR_FOUR, -1],
|
54
|
+
'invalid start meter' => [ Meter.new(-1,"1/4".to_r), 120],
|
55
|
+
'invalid meter in change' => [ FOUR_FOUR, 120,
|
56
|
+
:meter_changes => { 1 => Change::Immediate.new(Meter.new(-2,"1/4".to_r)) } ],
|
57
|
+
'non-immediate meter change' => [ FOUR_FOUR, 120,
|
58
|
+
:meter_changes => { 1 => Change::Gradual.new(TWO_FOUR,1) } ],
|
59
|
+
'invalid part' => [ FOUR_FOUR, 120, :parts => { "piano" => Part.new(-0.1) }],
|
60
|
+
'invalid program' => [ FOUR_FOUR, 120, :program => Program.new([2..0]) ],
|
61
|
+
}.each do |context_str,args|
|
62
|
+
context context_str do
|
63
|
+
it 'should return false' do
|
64
|
+
Score.new(*args).should be_invalid
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
34
69
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,7 +2,8 @@ require 'rspec'
|
|
2
2
|
require 'music-transcription'
|
3
3
|
|
4
4
|
include Music::Transcription
|
5
|
-
include
|
5
|
+
include Pitches
|
6
|
+
include Meters
|
6
7
|
|
7
8
|
class Samples
|
8
9
|
SAMPLE_PART = Part.new(
|
@@ -14,4 +15,16 @@ class Samples
|
|
14
15
|
],
|
15
16
|
dynamic_changes: {1.0 => Change::Immediate.new(Dynamics::MP)}
|
16
17
|
)
|
17
|
-
end
|
18
|
+
end
|
19
|
+
|
20
|
+
RSpec::Matchers.define :be_valid do
|
21
|
+
match do |obj|
|
22
|
+
obj.valid?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
RSpec::Matchers.define :be_invalid do
|
27
|
+
match do |obj|
|
28
|
+
obj.invalid?
|
29
|
+
end
|
30
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: music-transcription
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Tunnell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -154,19 +154,19 @@ files:
|
|
154
154
|
- lib/music-transcription/accent.rb
|
155
155
|
- lib/music-transcription/accents.rb
|
156
156
|
- lib/music-transcription/change.rb
|
157
|
-
- lib/music-transcription/dynamic.rb
|
158
157
|
- lib/music-transcription/dynamics.rb
|
159
158
|
- lib/music-transcription/errors.rb
|
160
159
|
- lib/music-transcription/link.rb
|
161
160
|
- lib/music-transcription/meter.rb
|
161
|
+
- lib/music-transcription/meters.rb
|
162
162
|
- lib/music-transcription/note.rb
|
163
163
|
- lib/music-transcription/part.rb
|
164
164
|
- lib/music-transcription/pitch.rb
|
165
165
|
- lib/music-transcription/pitches.rb
|
166
|
-
- lib/music-transcription/profile.rb
|
167
166
|
- lib/music-transcription/program.rb
|
168
167
|
- lib/music-transcription/score.rb
|
169
168
|
- lib/music-transcription/tempo.rb
|
169
|
+
- lib/music-transcription/validatable.rb
|
170
170
|
- lib/music-transcription/version.rb
|
171
171
|
- music-transcription.gemspec
|
172
172
|
- spec/change_spec.rb
|
@@ -176,7 +176,6 @@ files:
|
|
176
176
|
- spec/note_spec.rb
|
177
177
|
- spec/part_spec.rb
|
178
178
|
- spec/pitch_spec.rb
|
179
|
-
- spec/profile_spec.rb
|
180
179
|
- spec/program_spec.rb
|
181
180
|
- spec/score_spec.rb
|
182
181
|
- spec/spec_helper.rb
|
@@ -214,7 +213,6 @@ test_files:
|
|
214
213
|
- spec/note_spec.rb
|
215
214
|
- spec/part_spec.rb
|
216
215
|
- spec/pitch_spec.rb
|
217
|
-
- spec/profile_spec.rb
|
218
216
|
- spec/program_spec.rb
|
219
217
|
- spec/score_spec.rb
|
220
218
|
- spec/spec_helper.rb
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module Music
|
2
|
-
module Transcription
|
3
|
-
|
4
|
-
# Defines a dynamic level
|
5
|
-
#
|
6
|
-
# @author James Tunnell
|
7
|
-
#
|
8
|
-
class Dynamic
|
9
|
-
def ==(other)
|
10
|
-
self.class == other.class
|
11
|
-
end
|
12
|
-
|
13
|
-
def clone
|
14
|
-
self.class.new
|
15
|
-
end
|
16
|
-
|
17
|
-
[
|
18
|
-
:Piano, :Pianissimo, :Pianississimo,
|
19
|
-
:MezzoPiano, :MezzoForte,
|
20
|
-
:Forte, :Fortissimo, :Fortississimo
|
21
|
-
].each do |name|
|
22
|
-
Dynamic.const_set(name, Class.new(Dynamic))
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
end
|
@@ -1,148 +0,0 @@
|
|
1
|
-
module Music
|
2
|
-
module Transcription
|
3
|
-
|
4
|
-
# Represent a setting that can change over time.
|
5
|
-
#
|
6
|
-
# @author James Tunnell
|
7
|
-
#
|
8
|
-
class Profile
|
9
|
-
attr_accessor :start_value, :value_changes
|
10
|
-
|
11
|
-
def initialize start_value, value_changes = {}
|
12
|
-
@start_value = start_value
|
13
|
-
@value_changes = value_changes
|
14
|
-
end
|
15
|
-
|
16
|
-
# Compare to another Profile object.
|
17
|
-
def == other
|
18
|
-
(self.start_value == other.start_value) &&
|
19
|
-
(self.value_changes == other.value_changes)
|
20
|
-
end
|
21
|
-
|
22
|
-
# Produce an identical Profile object.
|
23
|
-
def clone
|
24
|
-
Marshal.load(Marshal.dump(self))
|
25
|
-
end
|
26
|
-
|
27
|
-
def last_value
|
28
|
-
if @value_changes.empty?
|
29
|
-
return @start_value
|
30
|
-
else
|
31
|
-
return @value_changes[@value_changes.keys.max].value
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def changes_before? offset
|
36
|
-
@value_changes.count {|k,v| k < offset } > 0
|
37
|
-
end
|
38
|
-
|
39
|
-
def changes_after? offset
|
40
|
-
@value_changes.count {|k,v| k > offset } > 0
|
41
|
-
end
|
42
|
-
|
43
|
-
# move changes forward or back by some offset
|
44
|
-
def shift amt
|
45
|
-
self.clone.shift! amt
|
46
|
-
end
|
47
|
-
|
48
|
-
# move changes forward or back by some offset
|
49
|
-
def shift! amt
|
50
|
-
@value_changes = Hash[@value_changes.map {|k,v| [k+amt,v]}]
|
51
|
-
return self
|
52
|
-
end
|
53
|
-
|
54
|
-
def stretch ratio
|
55
|
-
self.clone.stretch! ratio
|
56
|
-
end
|
57
|
-
|
58
|
-
def stretch! ratio
|
59
|
-
@value_changes = Hash[ @value_changes.map {|k,v| [k*ratio,v] }]
|
60
|
-
return self
|
61
|
-
end
|
62
|
-
|
63
|
-
def append profile, offset
|
64
|
-
self.clone.append! profile
|
65
|
-
end
|
66
|
-
|
67
|
-
def append! profile, start_offset
|
68
|
-
if @value_changes.any? && start_offset < @value_changes.keys.max
|
69
|
-
raise ArgumentError, "appending profile overlaps"
|
70
|
-
end
|
71
|
-
|
72
|
-
lv = self.last_value
|
73
|
-
unless lv == profile.start_value
|
74
|
-
@value_changes[start_offset] = Change::Immediate.new(profile.start_value)
|
75
|
-
lv = profile.start_value
|
76
|
-
end
|
77
|
-
|
78
|
-
shifted = profile.shift(start_offset)
|
79
|
-
shifted.value_changes.sort.each do |offset,value_change|
|
80
|
-
unless value_change.value == lv
|
81
|
-
@value_changes[offset] = value_change
|
82
|
-
lv = value_change.value
|
83
|
-
end
|
84
|
-
end
|
85
|
-
return self
|
86
|
-
end
|
87
|
-
|
88
|
-
# Returns true if start value and value changes all are between given A and B.
|
89
|
-
def values_between? a, b
|
90
|
-
is_ok = self.start_value.between?(a,b)
|
91
|
-
|
92
|
-
if is_ok
|
93
|
-
self.value_changes.each do |offset, setting|
|
94
|
-
setting.value.between?(a,b)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
return is_ok
|
98
|
-
end
|
99
|
-
|
100
|
-
# Returns true if start value and value changes all are greater than zero.
|
101
|
-
def values_positive?
|
102
|
-
is_ok = self.start_value > 0.0
|
103
|
-
|
104
|
-
if is_ok
|
105
|
-
self.value_changes.each do |offset, setting|
|
106
|
-
setting.value > 0.0
|
107
|
-
end
|
108
|
-
end
|
109
|
-
return is_ok
|
110
|
-
end
|
111
|
-
|
112
|
-
def clone_and_collate computer_class, program_segments
|
113
|
-
new_profile = Profile.new start_value
|
114
|
-
|
115
|
-
segment_start_offset = 0.0
|
116
|
-
comp = computer_class.new(self)
|
117
|
-
|
118
|
-
program_segments.each do |seg|
|
119
|
-
# figure which dynamics to keep/modify
|
120
|
-
changes = Marshal.load(Marshal.dump(value_changes))
|
121
|
-
changes.keep_if {|offset,change| seg.include?(offset) }
|
122
|
-
changes.each do |offset, change|
|
123
|
-
if(offset + change.transition.duration) > seg.last
|
124
|
-
change.transition.duration = seg.last - offset
|
125
|
-
change.value = comp.value_at seg.last
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
# find & add segment start value first
|
130
|
-
value = comp.value_at seg.first
|
131
|
-
offset = segment_start_offset
|
132
|
-
new_profile.value_changes[offset] = value_change(value)
|
133
|
-
|
134
|
-
# add changes to part, adjusting for segment start offset
|
135
|
-
changes.each do |offset2, change|
|
136
|
-
offset3 = (offset2 - seg.first) + segment_start_offset
|
137
|
-
new_profile.value_changes[offset3] = change
|
138
|
-
end
|
139
|
-
|
140
|
-
segment_start_offset += (seg.last - seg.first)
|
141
|
-
end
|
142
|
-
|
143
|
-
return new_profile
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
end
|
148
|
-
end
|
data/spec/profile_spec.rb
DELETED
@@ -1,163 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
|
3
|
-
describe Profile do
|
4
|
-
|
5
|
-
context '.new' do
|
6
|
-
it "should assign start value given during construction" do
|
7
|
-
p = Profile.new(0.2)
|
8
|
-
p.start_value.should eq(0.2)
|
9
|
-
end
|
10
|
-
|
11
|
-
it "should assign settings given during construction" do
|
12
|
-
p = Profile.new(0.2,
|
13
|
-
1.0 => Change::Immediate.new(0.5),
|
14
|
-
1.5 => Change::Immediate.new(1.0)
|
15
|
-
)
|
16
|
-
p.value_changes[1.0].value.should eq(0.5)
|
17
|
-
p.value_changes[1.5].value.should eq(1.0)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
describe '#last_value' do
|
22
|
-
context 'no value changes' do
|
23
|
-
it 'should return the start value' do
|
24
|
-
p = Profile.new(0.5)
|
25
|
-
p.last_value.should eq 0.5
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
context 'with value changes' do
|
30
|
-
it 'should return the value with highest key' do
|
31
|
-
p = Profile.new(0.5, 1.0 => Change::Immediate.new(0.6), 2.0 => Change::Immediate.new(0.7))
|
32
|
-
p.last_value.should eq 0.7
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
describe '#changes_before?' do
|
38
|
-
context 'no value changes' do
|
39
|
-
it 'should return false' do
|
40
|
-
p = Profile.new(0.0)
|
41
|
-
p.changes_before?(0).should be false
|
42
|
-
p.changes_before?(-10000000000000).should be false
|
43
|
-
p.changes_before?(10000000000000).should be false
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
context 'with value changes' do
|
48
|
-
context 'with changes before given offset' do
|
49
|
-
it 'should return true' do
|
50
|
-
p = Profile.new(0.0, 5.0 => Change::Immediate.new(0.1), 7.5 => Change::Immediate.new(0.2))
|
51
|
-
p.changes_before?(10.0).should be true
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
context 'with no changes before given offset' do
|
56
|
-
it 'should return false' do
|
57
|
-
p = Profile.new(0.0, 5.0 => Change::Immediate.new(0.1), 7.5 => Change::Immediate.new(0.2))
|
58
|
-
p.changes_before?(5.0).should be false
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
describe '#changes_after?' do
|
65
|
-
context 'no value changes' do
|
66
|
-
it 'should return false' do
|
67
|
-
p = Profile.new(0.0)
|
68
|
-
p.changes_after?(0).should be false
|
69
|
-
p.changes_after?(-10000000000000).should be false
|
70
|
-
p.changes_after?(10000000000000).should be false
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
context 'with value changes' do
|
75
|
-
context 'with changes after given offset' do
|
76
|
-
it 'should return true' do
|
77
|
-
p = Profile.new(0.0, 5.0 => Change::Immediate.new(0.1), 7.5 => Change::Immediate.new(0.2))
|
78
|
-
p.changes_after?(0.0).should be true
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
context 'with no changes after given offset' do
|
83
|
-
it 'should return false' do
|
84
|
-
p = Profile.new(0.0, 5.0 => Change::Immediate.new(0.1), 7.5 => Change::Immediate.new(0.2))
|
85
|
-
p.changes_after?(7.5).should be false
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
describe '#shift!' do
|
92
|
-
it 'should add shift amount to all change offsets' do
|
93
|
-
p = Profile.new(0.0, 5.0 => Change::Immediate.new(0.1), 7.5 => Change::Immediate.new(0.2))
|
94
|
-
p.shift!(1.0)
|
95
|
-
p.value_changes[6.0].value.should eq(0.1)
|
96
|
-
p.value_changes[8.5].value.should eq(0.2)
|
97
|
-
p.shift!(-1.0)
|
98
|
-
p.value_changes[5.0].value.should eq(0.1)
|
99
|
-
p.value_changes[7.5].value.should eq(0.2)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
describe '#stretch!' do
|
104
|
-
it 'should multiply change offsets by ratio' do
|
105
|
-
p = Profile.new(0.0, 5.0 => Change::Immediate.new(0.1), 7.5 => Change::Immediate.new(0.2))
|
106
|
-
p.stretch!(1)
|
107
|
-
p.value_changes[5.0].value.should eq(0.1)
|
108
|
-
p.value_changes[7.5].value.should eq(0.2)
|
109
|
-
p.stretch!("3/2".to_r)
|
110
|
-
p.value_changes[7.5].value.should eq(0.1)
|
111
|
-
p.value_changes[11.25].value.should eq(0.2)
|
112
|
-
p.stretch!("2/3".to_r)
|
113
|
-
p.value_changes[5.0].value.should eq(0.1)
|
114
|
-
p.value_changes[7.5].value.should eq(0.2)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
describe '#append!' do
|
119
|
-
before :each do
|
120
|
-
@p1 = Profile.new(0.0, 5.0 => Change::Immediate.new(0.1), 7.5 => Change::Immediate.new(0.2))
|
121
|
-
@p2 = Profile.new(0.2, 1.0 => Change::Immediate.new(0.0), 2.0 => Change::Gradual.new(100.0,1.0))
|
122
|
-
@p3 = Profile.new(0.1, 1.0 => Change::Immediate.new(0.0))
|
123
|
-
end
|
124
|
-
|
125
|
-
context 'offset less than last value change offset' do
|
126
|
-
it' should raise ArgumentError' do
|
127
|
-
expect { @p1.append!(@p2,7.0) }.to raise_error(ArgumentError)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
context 'offset equal to last value change offset' do
|
132
|
-
it' should not raise ArgumentError' do
|
133
|
-
expect { @p1.append!(@p2,7.5) }.not_to raise_error
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
context 'offset greater than last value change offset' do
|
138
|
-
it' should not raise ArgumentError' do
|
139
|
-
expect { @p1.append!(@p2, 7.6) }.not_to raise_error
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'should add on shifted value changes from second profile' do
|
143
|
-
@p1.append!(@p2,10.0)
|
144
|
-
@p1.value_changes[11.0].value.should eq 0.0
|
145
|
-
@p1.value_changes[12.0].value.should eq 100.0
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
context 'second profile start value equal to first profile last value' do
|
150
|
-
it 'should not add value change at offset' do
|
151
|
-
@p1.append!(@p2, 10.0)
|
152
|
-
@p1.value_changes[10.0].should be nil
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
context 'second profile start value not equal to first profile last value' do
|
157
|
-
it 'should add value change at offset' do
|
158
|
-
@p1.append!(@p3, 10.0)
|
159
|
-
@p1.value_changes[10.0].should_not be nil
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|