musicality 0.2.0 → 0.3.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 +30 -0
- data/lib/musicality/errors.rb +1 -0
- data/lib/musicality/notation/conversion/change_conversion.rb +63 -3
- data/lib/musicality/notation/conversion/note_time_converter.rb +23 -5
- data/lib/musicality/notation/conversion/score_conversion.rb +60 -0
- data/lib/musicality/notation/conversion/score_converter.rb +105 -0
- data/lib/musicality/notation/model/change.rb +98 -28
- data/lib/musicality/notation/model/part.rb +1 -1
- data/lib/musicality/notation/model/score.rb +4 -4
- data/lib/musicality/notation/packing/change_packing.rb +35 -25
- data/lib/musicality/notation/packing/score_packing.rb +2 -2
- data/lib/musicality/notation/util/function.rb +99 -0
- data/lib/musicality/notation/util/piecewise_function.rb +79 -99
- data/lib/musicality/notation/util/transition.rb +12 -0
- data/lib/musicality/notation/util/value_computer.rb +12 -152
- data/lib/musicality/performance/conversion/score_collator.rb +35 -20
- data/lib/musicality/performance/midi/part_sequencer.rb +2 -5
- data/lib/musicality/validatable.rb +6 -1
- data/lib/musicality/version.rb +1 -1
- data/lib/musicality.rb +4 -4
- data/musicality.gemspec +1 -0
- data/spec/notation/conversion/change_conversion_spec.rb +216 -9
- data/spec/notation/conversion/measure_note_map_spec.rb +2 -2
- data/spec/notation/conversion/note_time_converter_spec.rb +91 -9
- data/spec/notation/conversion/{measured_score_conversion_spec.rb → score_conversion_spec.rb} +44 -9
- data/spec/notation/conversion/score_converter_spec.rb +246 -0
- data/spec/notation/model/change_spec.rb +139 -36
- data/spec/notation/model/part_spec.rb +3 -3
- data/spec/notation/model/score_spec.rb +4 -4
- data/spec/notation/packing/change_packing_spec.rb +222 -71
- data/spec/notation/packing/part_packing_spec.rb +1 -1
- data/spec/notation/packing/score_packing_spec.rb +3 -2
- data/spec/notation/util/function_spec.rb +43 -0
- data/spec/notation/util/transition_spec.rb +51 -0
- data/spec/notation/util/value_computer_spec.rb +43 -87
- data/spec/performance/conversion/score_collator_spec.rb +46 -7
- data/spec/performance/midi/part_sequencer_spec.rb +2 -1
- metadata +29 -14
- data/lib/musicality/notation/conversion/measured_score_conversion.rb +0 -70
- data/lib/musicality/notation/conversion/measured_score_converter.rb +0 -95
- data/lib/musicality/notation/conversion/unmeasured_score_conversion.rb +0 -47
- data/lib/musicality/notation/conversion/unmeasured_score_converter.rb +0 -64
- data/spec/notation/conversion/measured_score_converter_spec.rb +0 -329
- data/spec/notation/conversion/unmeasured_score_conversion_spec.rb +0 -71
- data/spec/notation/conversion/unmeasured_score_converter_spec.rb +0 -116
@@ -0,0 +1,99 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class Function
|
4
|
+
DOMAIN_MIN, DOMAIN_MAX = -Float::INFINITY, Float::INFINITY
|
5
|
+
|
6
|
+
def sample xrange, srate
|
7
|
+
xrange.step(Rational(1,srate)).map { |x| at(x) }
|
8
|
+
end
|
9
|
+
|
10
|
+
# x in start domain, transformed to x in end domain
|
11
|
+
def self.transform_domains start_domain, end_domain, x
|
12
|
+
perc = (x - start_domain.first) / (start_domain.last - start_domain.first).to_f
|
13
|
+
return perc * (end_domain.last - end_domain.first) + end_domain.first
|
14
|
+
end
|
15
|
+
|
16
|
+
class Constant < Function
|
17
|
+
attr_reader :value
|
18
|
+
|
19
|
+
def initialize value
|
20
|
+
@value = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def at(x)
|
24
|
+
@value
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
super(other) && @value == other.value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Linear < Function
|
33
|
+
attr_reader :slope, :intercept
|
34
|
+
|
35
|
+
def initialize p1,p2
|
36
|
+
@slope = (p2[1] - p1[1])/(p2[0] - p1[0]).to_f
|
37
|
+
@intercept = p1[1] - @slope * p1[0]
|
38
|
+
end
|
39
|
+
|
40
|
+
def at(x)
|
41
|
+
x * @slope + @intercept
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
super(other) && @slope == other.slope && @intercept == other.intercept
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Sigmoid < Function
|
50
|
+
def self.sigm x
|
51
|
+
1.0 / (1 + Math::exp(-x))
|
52
|
+
end
|
53
|
+
|
54
|
+
#def self.inv_sigm y
|
55
|
+
# -Math::log((1-y)/y.to_f)
|
56
|
+
#end
|
57
|
+
|
58
|
+
SIGM_DOMAIN = -5..5
|
59
|
+
SIGM_RANGE = Sigmoid.sigm(SIGM_DOMAIN.first)..Sigmoid.sigm(SIGM_DOMAIN.last)
|
60
|
+
SIGM_SPAN = SIGM_RANGE.last - SIGM_RANGE.first
|
61
|
+
|
62
|
+
attr_reader :y0, :dy, :domain
|
63
|
+
def initialize p0, p1
|
64
|
+
@y0, y1 = p0[1], p1[1]
|
65
|
+
@dy = y1 - @y0
|
66
|
+
@domain = p0[0]..p1[0]
|
67
|
+
end
|
68
|
+
|
69
|
+
def at(x)
|
70
|
+
x_ = Function.transform_domains(@domain, SIGM_DOMAIN, x)
|
71
|
+
y_ = (Sigmoid.sigm(x_) - SIGM_RANGE.first) / SIGM_SPAN
|
72
|
+
y = @y0 + y_ * @dy
|
73
|
+
return y
|
74
|
+
end
|
75
|
+
|
76
|
+
#def from(y)
|
77
|
+
# y2 = (y - @y0) / @dy
|
78
|
+
# x2 = Sigmoid.inv_sigm(y2 * SIGM_SPAN + SIGM_RANGE.first)
|
79
|
+
# x = Function.transform_domains(SIGM_DOMAIN, @domain, x2)
|
80
|
+
# return x
|
81
|
+
#end
|
82
|
+
|
83
|
+
# Given a domain, an xy-point in that domain, and the y-value at
|
84
|
+
# the end of the domain, find the y-value at the start of the domain,
|
85
|
+
# assuming the the function is sigmoid.
|
86
|
+
def self.find_y0 domain, pt, y1
|
87
|
+
x,y = pt
|
88
|
+
x_ = Function.transform_domains(domain, SIGM_DOMAIN, x)
|
89
|
+
y_ = (sigm(x_) - SIGM_RANGE.first) / SIGM_SPAN
|
90
|
+
return Function::Linear.new([y_,y],[1,y1]).at(0)
|
91
|
+
end
|
92
|
+
|
93
|
+
def ==(other)
|
94
|
+
super(other) && @y0 == other.y0 && @dy == other.dy
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -1,121 +1,101 @@
|
|
1
1
|
module Musicality
|
2
2
|
|
3
|
-
|
4
|
-
#
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# Take an array of points (each point is a two-element array pair) and
|
10
|
-
# create a piecewise function to calculate values in-between.
|
11
|
-
def initialize points = []
|
12
|
-
@pieces = { }
|
3
|
+
class Function
|
4
|
+
# Combine functions that are each applicable for a non-overlapping domain.
|
5
|
+
#
|
6
|
+
# @author James Tunnell
|
7
|
+
class Piecewise < Function
|
8
|
+
attr_reader :pieces
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
points = points.to_a
|
19
|
-
end
|
20
|
-
|
21
|
-
for i in 1...points.count
|
22
|
-
add_points points[i-1], points[i]
|
23
|
-
end
|
10
|
+
# Take an array of points (each point is a two-element array pair) and
|
11
|
+
# create a piecewise function to calculate values in-between.
|
12
|
+
def initialize
|
13
|
+
@pieces = { }
|
24
14
|
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def add_points prev_point, point
|
28
|
-
domain = prev_point[0]..point[0]
|
29
|
-
func = lambda do |x|
|
30
|
-
perc = (x - domain.min).to_f / (domain.max - domain.min)
|
31
|
-
y = Interpolation.linear prev_point[1], point[1], perc
|
32
|
-
return y
|
33
|
-
end
|
34
|
-
add_piece(domain, func)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Add a function piece, which covers the given domain (includes domain start
|
38
|
-
# but not the end).
|
39
|
-
# @param [Range] domain The function domain. If this overlaps an existing domain,
|
40
|
-
# the existing domain will be split with the non-
|
41
|
-
# overlapping pieces kept and the overlapping old piece
|
42
|
-
# discarded.
|
43
|
-
def add_piece domain, func
|
44
15
|
|
45
|
-
|
46
|
-
|
16
|
+
def ==(other)
|
17
|
+
@pieces == other.pieces
|
18
|
+
end
|
47
19
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
20
|
+
# Add a function piece, which covers the given domain (includes domain start
|
21
|
+
# but not the end).
|
22
|
+
# @param [Range] domain The function domain. If this overlaps an existing domain,
|
23
|
+
# the existing domain will be split with the non-
|
24
|
+
# overlapping pieces kept and the overlapping old piece
|
25
|
+
# discarded.
|
26
|
+
def add_piece domain, func
|
27
|
+
|
28
|
+
raise ArgumentError, "domain is not a Range" unless domain.is_a? Range
|
29
|
+
raise ArgumentError, "func is not a Function" unless func.is_a? Function
|
30
|
+
|
31
|
+
contains_domain_completely = @pieces.select { |d,f| d.include?(domain.begin) && d.include?(domain.end) }
|
32
|
+
if contains_domain_completely.any?
|
33
|
+
contains_domain_completely.each do |d,f|
|
34
|
+
l = d.begin...domain.begin
|
35
|
+
if d.exclude_end?
|
36
|
+
r = domain.end...d.end
|
37
|
+
else
|
38
|
+
r = domain.end..d.end
|
39
|
+
end
|
40
|
+
|
41
|
+
@pieces.delete d
|
42
|
+
|
43
|
+
if domain.begin != d.begin
|
44
|
+
@pieces[l] = f
|
45
|
+
end
|
46
|
+
if domain.end == d.end
|
47
|
+
@pieces[domain.begin..domain.end] = func
|
48
|
+
else
|
49
|
+
@pieces[domain.begin...domain.end] = func
|
50
|
+
@pieces[r] = f
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
delete_completely = @pieces.select { |d,f| domain.include?(d.begin) && domain.include?(d.end) }
|
55
|
+
delete_completely.each do |d,f|
|
56
|
+
@pieces.delete d
|
56
57
|
end
|
57
58
|
|
58
|
-
|
59
|
+
# should only be one
|
60
|
+
move_end = @pieces.select { |d,f| domain.include?(d.end) }
|
61
|
+
move_end.each do |d,f|
|
62
|
+
@pieces.delete d
|
63
|
+
@pieces[d.begin...domain.begin] = f
|
64
|
+
end
|
59
65
|
|
60
|
-
|
61
|
-
|
66
|
+
# should only be one
|
67
|
+
move_begin = @pieces.select { |d,f| domain.include?(d.begin) }
|
68
|
+
move_begin.each do |d,f|
|
69
|
+
@pieces.delete d
|
70
|
+
if d.exclude_end?
|
71
|
+
@pieces[domain.end...d.end] = f
|
72
|
+
else
|
73
|
+
@pieces[domain.end..d.end] = f
|
74
|
+
end
|
62
75
|
end
|
63
|
-
|
64
|
-
|
65
|
-
else
|
76
|
+
|
77
|
+
if move_begin.any?
|
66
78
|
@pieces[domain.begin...domain.end] = func
|
67
|
-
@pieces[r] = f
|
68
|
-
end
|
69
|
-
end
|
70
|
-
else
|
71
|
-
delete_completely = @pieces.select { |d,f| domain.include?(d.begin) && domain.include?(d.end) }
|
72
|
-
delete_completely.each do |d,f|
|
73
|
-
@pieces.delete d
|
74
|
-
end
|
75
|
-
|
76
|
-
# should only be one
|
77
|
-
move_end = @pieces.select { |d,f| domain.include?(d.end) }
|
78
|
-
move_end.each do |d,f|
|
79
|
-
@pieces.delete d
|
80
|
-
@pieces[d.begin...domain.begin] = f
|
81
|
-
end
|
82
|
-
|
83
|
-
# should only be one
|
84
|
-
move_begin = @pieces.select { |d,f| domain.include?(d.begin) }
|
85
|
-
move_begin.each do |d,f|
|
86
|
-
@pieces.delete d
|
87
|
-
if d.exclude_end?
|
88
|
-
@pieces[domain.end...d.end] = f
|
89
79
|
else
|
90
|
-
@pieces[domain
|
80
|
+
@pieces[domain] = func
|
91
81
|
end
|
92
82
|
end
|
93
|
-
|
94
|
-
if move_begin.any?
|
95
|
-
@pieces[domain.begin...domain.end] = func
|
96
|
-
else
|
97
|
-
@pieces[domain] = func
|
98
|
-
end
|
99
83
|
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# Evaluate the piecewise function by finding a function piece whose domain
|
103
|
-
# includes the given independent value.
|
104
|
-
def eval x
|
105
|
-
y = nil
|
106
84
|
|
107
|
-
|
108
|
-
|
109
|
-
y = func.call x
|
110
|
-
break
|
111
|
-
end
|
85
|
+
def domain_include? x
|
86
|
+
!@pieces.keys.detect {|domain| domain.include?(x)}.nil?
|
112
87
|
end
|
113
88
|
|
114
|
-
|
115
|
-
|
89
|
+
# Evaluate the piecewise function by finding a function piece whose domain
|
90
|
+
# includes the given independent value.
|
91
|
+
def at x
|
92
|
+
@pieces.each do |domain, func|
|
93
|
+
if domain.include? x
|
94
|
+
return func.at(x)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
raise DomainError, "The input #{x} is not in the domain."
|
116
98
|
end
|
117
|
-
|
118
|
-
return y
|
119
99
|
end
|
120
100
|
end
|
121
101
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class Transition < Function::Piecewise
|
4
|
+
def initialize func, transition_domain
|
5
|
+
super()
|
6
|
+
add_piece(transition_domain, func)
|
7
|
+
add_piece(transition_domain.last..DOMAIN_MAX,
|
8
|
+
Function::Constant.new(func.at(transition_domain.last)))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -1,169 +1,29 @@
|
|
1
1
|
module Musicality
|
2
2
|
|
3
3
|
# Given a start value, and value changes, compute the value at any offset.
|
4
|
-
class ValueComputer
|
4
|
+
class ValueComputer < Function::Piecewise
|
5
5
|
attr_reader :piecewise_function
|
6
|
-
|
7
6
|
def initialize start_value, value_changes = {}
|
8
|
-
|
9
|
-
set_default_value
|
10
|
-
|
7
|
+
super()
|
8
|
+
set_default_value(start_value)
|
11
9
|
if value_changes.any?
|
12
10
|
value_changes.sort.each do |offset,change|
|
13
|
-
|
14
|
-
case change
|
15
|
-
when Change::Immediate
|
16
|
-
add_immediate_change change, offset
|
17
|
-
when Change::Gradual
|
18
|
-
add_linear_change change, offset
|
19
|
-
# add_sigmoid_change change, offset
|
20
|
-
end
|
21
|
-
|
11
|
+
add_change(offset, change)
|
22
12
|
end
|
23
13
|
end
|
24
14
|
end
|
25
|
-
|
26
|
-
# Compute the value at the given offset.
|
27
|
-
# @param [Numeric] offset The given offset to compute value at.
|
28
|
-
def value_at offset
|
29
|
-
@piecewise_function.eval offset
|
30
|
-
end
|
31
|
-
|
32
|
-
def sample xmin, xmax, srate
|
33
|
-
sample_period = Rational(1,srate)
|
34
|
-
((xmin.to_r)..(xmax.to_r)).step(sample_period).map do |x|
|
35
|
-
value_at(x)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# finds the minimum domain value
|
40
|
-
def domain_min
|
41
|
-
-Float::INFINITY
|
42
|
-
end
|
43
|
-
|
44
|
-
# finds the maximum domain value
|
45
|
-
def domain_max
|
46
|
-
Float::INFINITY
|
47
|
-
end
|
48
|
-
|
49
|
-
# finds the minimum domain value
|
50
|
-
def self.domain_min
|
51
|
-
-Float::INFINITY
|
52
|
-
end
|
53
|
-
|
54
|
-
# finds the maximum domain value
|
55
|
-
def self.domain_max
|
56
|
-
Float::INFINITY
|
57
|
-
end
|
58
|
-
|
15
|
+
|
59
16
|
private
|
60
17
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
18
|
+
def add_change offset, change
|
19
|
+
start_value = at(offset)
|
20
|
+
trans = change.to_transition(offset, start_value)
|
21
|
+
add_piece(offset..Function::DOMAIN_MAX, trans)
|
64
22
|
end
|
65
23
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
#
|
70
|
-
# @param [ValueChange] value_change An event with information about the new value.
|
71
|
-
# @param [Numeric] offset
|
72
|
-
def add_immediate_change value_change, offset
|
73
|
-
func = nil
|
74
|
-
value = value_change.value
|
75
|
-
domain = offset..domain_max
|
76
|
-
func = lambda {|x| value }
|
77
|
-
|
78
|
-
@piecewise_function.add_piece domain, func
|
79
|
-
end
|
80
|
-
|
81
|
-
# Add a function piece to the piecewise function, which will to compute value
|
82
|
-
# for a matching note offset. If the dynamic event duration is non-zero, a
|
83
|
-
# linear transition function is created.
|
84
|
-
#
|
85
|
-
# @param [ValueChange] value_change An event with information about the new value.
|
86
|
-
# @param [Numeric] offset
|
87
|
-
def add_linear_change value_change, offset
|
88
|
-
|
89
|
-
func = nil
|
90
|
-
value = value_change.value
|
91
|
-
duration = value_change.duration
|
92
|
-
domain = offset..domain_max
|
93
|
-
|
94
|
-
if duration == 0
|
95
|
-
add_immediate_change(value_change, offset)
|
96
|
-
else
|
97
|
-
b = @piecewise_function.eval domain.first
|
98
|
-
m = (value.to_f - b.to_f) / duration.to_f
|
99
|
-
|
100
|
-
func = lambda do |x|
|
101
|
-
raise RangeError, "#{x} is not in the domain" if !domain.include?(x)
|
102
|
-
|
103
|
-
if x < (domain.first + duration)
|
104
|
-
(m * (x - domain.first)) + b
|
105
|
-
else
|
106
|
-
value
|
107
|
-
end
|
108
|
-
end
|
109
|
-
@piecewise_function.add_piece domain, func
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# Add a function piece to the piecewise function, which will to compute value
|
114
|
-
# for a matching note offset. If the dynamic event duration is non-zero, a
|
115
|
-
# linear transition function is created.
|
116
|
-
#
|
117
|
-
# @param [ValueChange] value_change An event with information about the new value.
|
118
|
-
# @param [Numeric] offset
|
119
|
-
def add_sigmoid_change value_change, offset
|
120
|
-
|
121
|
-
func = nil
|
122
|
-
start_value = @piecewise_function.eval offset
|
123
|
-
end_value = value_change.value
|
124
|
-
value_diff = end_value - start_value
|
125
|
-
duration = value_change.duration
|
126
|
-
domain = offset.to_f..domain_max
|
127
|
-
abruptness = 0.7 # value_change.transition.abruptness.to_f
|
128
|
-
|
129
|
-
if duration == 0
|
130
|
-
add_immediate_change(value_change,offset)
|
131
|
-
else
|
132
|
-
raise ArgumentError, "abruptness is not between 0 and 1" unless abruptness.between?(0,1)
|
133
|
-
|
134
|
-
min_magn = 2
|
135
|
-
max_magn = 6
|
136
|
-
tanh_domain_magn = abruptness * (max_magn - min_magn) + min_magn
|
137
|
-
tanh_domain = -tanh_domain_magn..tanh_domain_magn
|
138
|
-
|
139
|
-
tanh_range = Math::tanh(tanh_domain.first)..Math::tanh(tanh_domain.last)
|
140
|
-
tanh_span = tanh_range.last - tanh_range.first
|
141
|
-
|
142
|
-
func = lambda do |x|
|
143
|
-
raise RangeError, "#{x} is not in the domain" if !domain.include?(x)
|
144
|
-
if x < (domain.first + duration)
|
145
|
-
start_domain = domain.first...(domain.first + duration)
|
146
|
-
x2 = transform_domains(start_domain, tanh_domain, x)
|
147
|
-
y = Math::tanh x2
|
148
|
-
z = (y / tanh_span) + 0.5 # ranges from 0 to 1
|
149
|
-
start_value + (z * value_diff)
|
150
|
-
else
|
151
|
-
end_value
|
152
|
-
end
|
153
|
-
end
|
154
|
-
@piecewise_function.add_piece domain, func
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
# x should be in the start domain
|
159
|
-
def transform_domains start_domain, end_domain, x
|
160
|
-
perc = (x - start_domain.first) / (start_domain.last - start_domain.first).to_f
|
161
|
-
x2 = perc * (end_domain.last - end_domain.first) + end_domain.first
|
162
|
-
end
|
163
|
-
|
164
|
-
# 0 to 1
|
165
|
-
def logistic x
|
166
|
-
1.0 / (1 + Math::exp(-x))
|
24
|
+
def set_default_value value
|
25
|
+
func = Function::Constant.new(value)
|
26
|
+
add_piece(Function::DOMAIN_MIN..Function::DOMAIN_MAX, func)
|
167
27
|
end
|
168
28
|
end
|
169
29
|
|
@@ -48,30 +48,45 @@ class ScoreCollator
|
|
48
48
|
seg = seg.first...seg.last
|
49
49
|
|
50
50
|
# add segment start value
|
51
|
-
value = comp.
|
51
|
+
value = comp.at seg.first
|
52
52
|
new_changes[segment_start_offset] = Change::Immediate.new(value)
|
53
53
|
|
54
|
-
|
55
|
-
changes.select {|o,c| c.is_a?(Change::Immediate) && seg.include?(o) }.each do |off,c|
|
56
|
-
new_changes[(off - seg.first) + segment_start_offset] = c.clone
|
57
|
-
end
|
58
|
-
|
59
|
-
# add gradual changes
|
60
|
-
changes.select {|o,c| c.is_a?(Change::Gradual)}.each do |off, change|
|
54
|
+
changes.each do |off,change|
|
61
55
|
adj_start_off = (off - seg.first) + segment_start_offset
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
56
|
+
|
57
|
+
new_change = case change
|
58
|
+
when Change::Immediate
|
59
|
+
change.clone if seg.include?(off)
|
60
|
+
when Change::Gradual::Trimmed
|
61
|
+
end_off = off + change.remaining
|
62
|
+
if off < seg.last && end_off > seg.first
|
63
|
+
add_preceding = seg.first > off ? seg.first - off : 0
|
64
|
+
add_trailing = end_off > seg.last ? end_off - seg.last : 0
|
65
|
+
|
66
|
+
if add_preceding == 0 && add_trailing == 0
|
67
|
+
change.clone
|
68
|
+
else
|
69
|
+
adj_start_off += add_preceding
|
70
|
+
change.untrim.trim(change.preceding + add_preceding,
|
71
|
+
change.trailing + add_trailing)
|
72
|
+
end
|
70
73
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
74
|
+
when Change::Gradual
|
75
|
+
end_off = off + change.duration
|
76
|
+
if off < seg.last && end_off > seg.first
|
77
|
+
preceding = seg.first > off ? seg.first - off : 0
|
78
|
+
trailing = end_off > seg.last ? end_off - seg.last : 0
|
79
|
+
if preceding == 0 && trailing == 0
|
80
|
+
change.clone
|
81
|
+
else
|
82
|
+
adj_start_off += preceding
|
83
|
+
change.trim(preceding, trailing)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
unless new_change.nil?
|
89
|
+
new_changes[adj_start_off] = new_change
|
75
90
|
end
|
76
91
|
end
|
77
92
|
end
|
@@ -81,12 +81,9 @@ class PartSequencer
|
|
81
81
|
dyn_comp = ValueComputer.new(start_dyn,dyn_changes)
|
82
82
|
finish = 0
|
83
83
|
if dyn_changes.any?
|
84
|
-
finish
|
85
|
-
if change.is_a? Change::Gradual
|
86
|
-
finish += change.duration
|
87
|
-
end
|
84
|
+
finish = dyn_changes.map {|off,ch| ch.offsets(off).max }.max
|
88
85
|
end
|
89
|
-
samples = dyn_comp.sample(0
|
86
|
+
samples = dyn_comp.sample(0..finish, sample_rate)
|
90
87
|
|
91
88
|
prev = nil
|
92
89
|
samples.each_index do |i|
|
@@ -1,7 +1,12 @@
|
|
1
1
|
# assumes that @checks is defined as an array of no-arg lambdas, each
|
2
2
|
# lambda raising an error (with useful msg) when check fails
|
3
3
|
module Validatable
|
4
|
-
|
4
|
+
def errors
|
5
|
+
if @errors.nil?
|
6
|
+
self.validate
|
7
|
+
end
|
8
|
+
return @errors
|
9
|
+
end
|
5
10
|
|
6
11
|
def check_methods; []; end
|
7
12
|
def validatables; []; end
|
data/lib/musicality/version.rb
CHANGED
data/lib/musicality.rb
CHANGED
@@ -47,17 +47,17 @@ require 'musicality/notation/packing/program_packing'
|
|
47
47
|
require 'musicality/notation/packing/score_packing'
|
48
48
|
|
49
49
|
require 'musicality/notation/util/interpolation'
|
50
|
+
require 'musicality/notation/util/function'
|
50
51
|
require 'musicality/notation/util/piecewise_function'
|
52
|
+
require 'musicality/notation/util/transition'
|
51
53
|
require 'musicality/notation/util/value_computer'
|
52
54
|
|
53
55
|
require 'musicality/notation/conversion/tempo_conversion'
|
54
56
|
require 'musicality/notation/conversion/change_conversion'
|
55
57
|
require 'musicality/notation/conversion/note_time_converter'
|
56
|
-
require 'musicality/notation/conversion/unmeasured_score_converter'
|
57
|
-
require 'musicality/notation/conversion/unmeasured_score_conversion'
|
58
58
|
require 'musicality/notation/conversion/measure_note_map'
|
59
|
-
require 'musicality/notation/conversion/
|
60
|
-
require 'musicality/notation/conversion/
|
59
|
+
require 'musicality/notation/conversion/score_converter'
|
60
|
+
require 'musicality/notation/conversion/score_conversion'
|
61
61
|
|
62
62
|
#
|
63
63
|
# Performance
|
data/musicality.gemspec
CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.7"
|
24
24
|
spec.add_development_dependency "rake", "~> 10.0"
|
25
25
|
spec.add_development_dependency "rspec", "~> 2.9"
|
26
|
+
spec.add_development_dependency "pry"
|
26
27
|
|
27
28
|
spec.add_dependency "treetop", "~> 1.5"
|
28
29
|
spec.add_dependency 'midilib', '~> 2.0'
|