musicality 0.2.0 → 0.3.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 +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'
|