music-transcription 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby-version +1 -0
- data/bin/transcribe +0 -1
- data/lib/music-transcription.rb +1 -5
- data/lib/music-transcription/accent.rb +43 -0
- data/lib/music-transcription/errors.rb +5 -0
- data/lib/music-transcription/link.rb +46 -89
- data/lib/music-transcription/note.rb +66 -110
- data/lib/music-transcription/part.rb +14 -88
- data/lib/music-transcription/pitch.rb +39 -41
- data/lib/music-transcription/pitch_constants.rb +88 -88
- data/lib/music-transcription/profile.rb +5 -20
- data/lib/music-transcription/program.rb +2 -31
- data/lib/music-transcription/score.rb +28 -65
- data/lib/music-transcription/tempo.rb +3 -15
- data/lib/music-transcription/transition.rb +37 -53
- data/lib/music-transcription/value_change.rb +9 -35
- data/lib/music-transcription/version.rb +1 -1
- data/music-transcription.gemspec +5 -10
- data/spec/link_spec.rb +14 -10
- data/spec/note_spec.rb +13 -40
- data/spec/part_spec.rb +4 -72
- data/spec/pitch_spec.rb +39 -39
- data/spec/profile_spec.rb +4 -7
- data/spec/program_spec.rb +6 -6
- data/spec/score_spec.rb +9 -14
- data/spec/spec_helper.rb +2 -14
- data/spec/transition_spec.rb +25 -7
- data/spec/value_change_spec.rb +4 -4
- metadata +21 -25
- data/lib/music-transcription/arrangement.rb +0 -31
- data/lib/music-transcription/instrument_config.rb +0 -38
- data/lib/music-transcription/interval.rb +0 -66
- data/spec/instrument_config_spec.rb +0 -47
- data/spec/interval_spec.rb +0 -38
@@ -6,33 +6,25 @@ module Transcription
|
|
6
6
|
# @author James Tunnell
|
7
7
|
#
|
8
8
|
class Profile
|
9
|
-
include Hashmake::HashMakeable
|
10
|
-
|
11
9
|
attr_accessor :start_value, :value_changes
|
12
|
-
|
13
|
-
# hashed-arg specs (for hash-makeable idiom)
|
14
|
-
ARG_SPECS = {
|
15
|
-
:start_value => arg_spec(:reqd => true),
|
16
|
-
:value_changes => arg_spec_hash(:reqd => false, :type => ValueChange)
|
17
|
-
}
|
18
10
|
|
19
11
|
# A new instance of Profile.
|
20
12
|
#
|
21
13
|
# @param [Hash] args Hashed args. Required key is :start_value. Optional key is :value_changes.
|
22
|
-
def initialize
|
23
|
-
|
14
|
+
def initialize start_value, value_changes = {}
|
15
|
+
@start_value = start_value
|
16
|
+
@value_changes = value_changes
|
24
17
|
end
|
25
18
|
|
26
19
|
# Compare to another Profile object.
|
27
20
|
def == other
|
28
|
-
(self.class == other.class) &&
|
29
21
|
(self.start_value == other.start_value) &&
|
30
22
|
(self.value_changes == other.value_changes)
|
31
23
|
end
|
32
24
|
|
33
25
|
# Produce an identical Profile object.
|
34
26
|
def clone
|
35
|
-
Profile.new(
|
27
|
+
Profile.new(@start_value, @value_changes.clone)
|
36
28
|
end
|
37
29
|
|
38
30
|
# Returns true if start value and value changes all are between given A and B.
|
@@ -60,7 +52,7 @@ class Profile
|
|
60
52
|
end
|
61
53
|
|
62
54
|
def clone_and_collate computer_class, program_segments
|
63
|
-
new_profile = Profile.new
|
55
|
+
new_profile = Profile.new start_value
|
64
56
|
|
65
57
|
segment_start_offset = 0.0
|
66
58
|
comp = computer_class.new(self)
|
@@ -94,12 +86,5 @@ class Profile
|
|
94
86
|
end
|
95
87
|
end
|
96
88
|
|
97
|
-
module_function
|
98
|
-
|
99
|
-
# Create a Profile object
|
100
|
-
def profile start_value, value_changes = {}
|
101
|
-
return Profile.new(:start_value => start_value, :value_changes => value_changes)
|
102
|
-
end
|
103
|
-
|
104
89
|
end
|
105
90
|
end
|
@@ -6,43 +6,14 @@ module Transcription
|
|
6
6
|
# @author James Tunnell
|
7
7
|
#
|
8
8
|
class Program
|
9
|
-
|
10
|
-
attr_reader :segments
|
11
|
-
|
12
|
-
# hashed-arg specs (for hash-makeable idiom)
|
13
|
-
ARG_SPECS = {
|
14
|
-
:segments => arg_spec_array(:reqd => false, :type => Range)
|
15
|
-
}
|
9
|
+
attr_accessor :segments
|
16
10
|
|
17
11
|
# A new instance of Program.
|
18
12
|
# @param [Hash] args Hashed arguments. Required key is :segments.
|
19
|
-
def initialize
|
20
|
-
hash_make args
|
21
|
-
end
|
22
|
-
|
23
|
-
# Assign program segments. Each segment is a Range to specify which range of
|
24
|
-
# notes from a score should be played.
|
25
|
-
#
|
26
|
-
# @param [Array] segments An array of program segements. Each segment is a
|
27
|
-
# Range to specify which range of
|
28
|
-
# @raise [ArgumentError] if segments is not an Array.
|
29
|
-
# @raise [ArgumentError] if segments contains a non-Range
|
30
|
-
#
|
31
|
-
def segments= segments
|
32
|
-
ARG_SPECS[:segments].validate_value segments
|
13
|
+
def initialize segments = []
|
33
14
|
@segments = segments
|
34
15
|
end
|
35
16
|
|
36
|
-
# @return [Float] the starting note offset for the program
|
37
|
-
def start
|
38
|
-
@segments.first.first
|
39
|
-
end
|
40
|
-
|
41
|
-
# @return [Float] the ending note offset for the program
|
42
|
-
def stop
|
43
|
-
@segments.last.last
|
44
|
-
end
|
45
|
-
|
46
17
|
# @return [Float] the sum of all program segment lengths
|
47
18
|
def length
|
48
19
|
segments.inject(0.0) { |length, segment| length + (segment.last - segment.first) }
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Music
|
2
2
|
module Transcription
|
3
3
|
|
4
|
-
#
|
4
|
+
# Score, containing parts and a program.
|
5
5
|
#
|
6
6
|
# @author James Tunnell
|
7
7
|
#
|
@@ -12,49 +12,13 @@ module Transcription
|
|
12
12
|
# @return [Array] Score program.
|
13
13
|
#
|
14
14
|
class Score
|
15
|
-
include Hashmake::HashMakeable
|
16
15
|
attr_reader :parts, :program
|
17
|
-
|
18
|
-
# hashed-arg specs (for hash-makeable idiom)
|
19
|
-
ARG_SPECS = {
|
20
|
-
:parts => arg_spec_hash(:reqd => false, :type => Part),
|
21
|
-
:program => arg_spec(:reqd => false, :type => Program, :default => ->(){ Program.new }),
|
22
|
-
}
|
23
|
-
|
24
|
-
# A new instance of Score.
|
25
|
-
# @param [Hash] args Hashed arguments. Optional keys are :program and :parts.
|
26
|
-
def initialize args={}
|
27
|
-
hash_make args, ARG_SPECS
|
28
|
-
end
|
29
16
|
|
30
|
-
def
|
31
|
-
Marshal.load(Marshal.dump(self))
|
32
|
-
end
|
33
|
-
|
34
|
-
# Compare the equality of another Score object.
|
35
|
-
def ==(other)
|
36
|
-
return (@program == other.program) &&
|
37
|
-
(@parts == other.parts)
|
38
|
-
end
|
39
|
-
|
40
|
-
# Set the score parts.
|
41
|
-
# @param [Hash] parts The score parts, mapped to IDs.
|
42
|
-
# @raise [ArgumentError] if notes is not a Hash.
|
43
|
-
# @raise [ArgumentError] if parts contain a non-Part object.
|
44
|
-
def parts= parts
|
45
|
-
Score::ARG_SPECS[:parts].validate_value parts
|
17
|
+
def initialize parts: {}, program: Program.new
|
46
18
|
@parts = parts
|
47
|
-
end
|
48
|
-
|
49
|
-
# Set the score program, which determines which defines sections and how they
|
50
|
-
# are played.
|
51
|
-
# @param [Program] program The score program.
|
52
|
-
# @raise [ArgumentError] if tempos is not a Program.
|
53
|
-
def program= program
|
54
|
-
Score::ARG_SPECS[:program].validate_value program
|
55
19
|
@program = program
|
56
20
|
end
|
57
|
-
|
21
|
+
|
58
22
|
# Find the start of a score. The start will be at then start of whichever part begins
|
59
23
|
# first, or 0 if no parts have been added.
|
60
24
|
def start
|
@@ -82,39 +46,38 @@ class Score
|
|
82
46
|
end
|
83
47
|
end
|
84
48
|
|
85
|
-
# Score
|
86
|
-
|
87
|
-
|
88
|
-
#
|
89
|
-
# @!attribute [rw] tempo_profile
|
90
|
-
# @return [Profile] The tempo profile.
|
91
|
-
#
|
92
|
-
class TempoScore < Score
|
93
|
-
include Hashmake::HashMakeable
|
94
|
-
attr_reader :tempo_profile
|
49
|
+
# Score where time is based on absolute time in seconds
|
50
|
+
class TimeScore < Score
|
51
|
+
attr_reader :program, :parts
|
95
52
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
}
|
53
|
+
def clone
|
54
|
+
TimeScore.new @parts, @programs
|
55
|
+
end
|
100
56
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
57
|
+
def ==(other)
|
58
|
+
return (@program == other.program) &&
|
59
|
+
(@parts == other.parts)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Score where time is based on notes and tempo.
|
64
|
+
class TempoScore < Score
|
65
|
+
attr_reader :tempo_profile, :program, :parts
|
66
|
+
|
67
|
+
def initialize tempo_profile, parts: {}, program: Program.new
|
68
|
+
@tempo_profile = tempo_profile
|
69
|
+
raise ValueNotPositiveError unless @tempo_profile.values_positive?
|
70
|
+
super(parts: parts, program: program)
|
106
71
|
end
|
107
72
|
|
108
73
|
def clone
|
109
|
-
|
74
|
+
TempoScore.new @tempo_profile.clone, @parts.clone, @program.clone
|
110
75
|
end
|
111
76
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
TempoScore::ARG_SPECS[:tempo_profile].validate_value tempo_profile
|
117
|
-
@tempo_profile = tempo_profile
|
77
|
+
def ==(other)
|
78
|
+
return (@tempo_profile == other.tempo_profile) &&
|
79
|
+
(@program == other.program) &&
|
80
|
+
(@parts == other.parts)
|
118
81
|
end
|
119
82
|
end
|
120
83
|
|
@@ -4,17 +4,11 @@ module Transcription
|
|
4
4
|
# Represent the musical tempo, with beats ber minute and beat duration.
|
5
5
|
class Tempo
|
6
6
|
include Comparable
|
7
|
-
include Hashmake::HashMakeable
|
8
|
-
|
9
7
|
attr_reader :beats_per_minute, :beat_duration
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
}
|
15
|
-
|
16
|
-
def initialize args
|
17
|
-
hash_make args
|
9
|
+
def initialize beats_per_minute, beat_duration: Rational(1,4)
|
10
|
+
@beats_per_minute = beats_per_minute
|
11
|
+
@beat_duration = beat_duration
|
18
12
|
end
|
19
13
|
|
20
14
|
def notes_per_second
|
@@ -34,11 +28,5 @@ class Tempo
|
|
34
28
|
end
|
35
29
|
end
|
36
30
|
|
37
|
-
module_function
|
38
|
-
|
39
|
-
def tempo beats_per_minute, beat_duration = Tempo::ARG_SPECS[:beat_duration].default
|
40
|
-
Tempo.new(:beats_per_minute => beats_per_minute, :beat_duration => beat_duration)
|
41
|
-
end
|
42
|
-
|
43
31
|
end
|
44
32
|
end
|
@@ -3,69 +3,53 @@ module Transcription
|
|
3
3
|
|
4
4
|
# Describes how to transition from one value to another.
|
5
5
|
class Transition
|
6
|
-
|
6
|
+
attr_reader :duration
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
TYPES = [ IMMEDIATE, LINEAR, SIGMOID ] # the transitions which are valid and expected
|
12
|
-
|
13
|
-
# hashed-arg specs (for hash-makeable idiom)
|
14
|
-
ARG_SPECS = {
|
15
|
-
:duration => arg_spec(:reqd => false, :type => Numeric, :default => 0.0, :validator => ->(a){ a >= 0.0 } ),
|
16
|
-
:type => arg_spec(:reqd => false, :type => Symbol, :default => IMMEDIATE, :validator => ->(a) { Transition::TYPES.include?(a)}),
|
17
|
-
:abruptness => arg_spec(:reqd => false, :type => Numeric, :default => 0.5, :validator => ->(a){ a.between?(0,1) })
|
18
|
-
}
|
19
|
-
|
20
|
-
attr_reader :type, :duration, :abruptness
|
8
|
+
def initialize duration
|
9
|
+
@duration = duration
|
10
|
+
end
|
21
11
|
|
22
|
-
def
|
23
|
-
|
12
|
+
def ==(other)
|
13
|
+
@duration == other.duration
|
24
14
|
end
|
25
15
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
16
|
+
class Immediate < Transition
|
17
|
+
def initialize
|
18
|
+
super(0)
|
19
|
+
end
|
20
|
+
|
21
|
+
def clone
|
22
|
+
Immediate.new
|
23
|
+
end
|
31
24
|
end
|
32
25
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
class Linear < Transition
|
27
|
+
def initialize duration
|
28
|
+
super(duration)
|
29
|
+
end
|
30
|
+
|
31
|
+
def clone
|
32
|
+
Linear.new @duration
|
33
|
+
end
|
37
34
|
end
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
36
|
+
class Sigmoid < Transition
|
37
|
+
attr_reader :abruptness
|
38
|
+
def initialize duration, abruptness = 0.5
|
39
|
+
@abruptness = abruptness
|
40
|
+
super(duration)
|
41
|
+
end
|
42
|
+
|
43
|
+
def clone
|
44
|
+
Sigmoid.new @duration, @abruptness
|
45
|
+
end
|
46
|
+
|
47
|
+
def == other
|
48
|
+
@abruptness == other.abruptness &&
|
49
|
+
@duration == other.duration
|
50
|
+
end
|
48
51
|
end
|
49
52
|
end
|
50
53
|
|
51
|
-
module_function
|
52
|
-
|
53
|
-
# Create a Transition object with 0 duration and of IMMEDIATE type.
|
54
|
-
def immediate
|
55
|
-
Transition.new(:duration => 0.0, :type => Transition::IMMEDIATE)
|
56
|
-
end
|
57
|
-
|
58
|
-
|
59
|
-
# Create a Transition object of IMMEDIATE type, with the given duration.
|
60
|
-
def linear duration
|
61
|
-
Transition.new(:duration => duration, :type => Transition::LINEAR)
|
62
|
-
end
|
63
|
-
|
64
|
-
|
65
|
-
# Create a Transition object of SIGMOID type, with the given duration.
|
66
|
-
def sigmoid duration, abruptness = Transition::ARG_SPECS[:abruptness].default
|
67
|
-
Transition.new(:duration => duration, :type => Transition::SIGMOID, :abruptness => abruptness)
|
68
|
-
end
|
69
|
-
|
70
54
|
end
|
71
55
|
end
|
@@ -12,20 +12,13 @@ module Transcription
|
|
12
12
|
# @return [Numeric] The value of the event.
|
13
13
|
#
|
14
14
|
class ValueChange
|
15
|
-
|
16
|
-
|
17
|
-
attr_reader :value, :transition
|
18
|
-
|
19
|
-
# hashed-arg specs (for hash-makeable idiom)
|
20
|
-
ARG_SPECS = {
|
21
|
-
:value => arg_spec(:reqd => true),
|
22
|
-
:transition => arg_spec(:reqd => false, :type => Transition, :default => ->(){ immediate() })
|
23
|
-
}
|
15
|
+
attr_accessor :value, :transition
|
24
16
|
|
25
17
|
# New instance of ValueChange.
|
26
18
|
# @param [Hash] args Hashed arguments for initialization.
|
27
|
-
def initialize
|
28
|
-
|
19
|
+
def initialize value, transition = Transition::Immediate.new
|
20
|
+
@value = value
|
21
|
+
@transition = transition
|
29
22
|
end
|
30
23
|
|
31
24
|
# Compare the equality of another ValueChange object.
|
@@ -36,49 +29,30 @@ class ValueChange
|
|
36
29
|
|
37
30
|
# Produce an identical ValueChange object
|
38
31
|
def clone
|
39
|
-
return ValueChange.new(
|
40
|
-
end
|
41
|
-
|
42
|
-
# Set the event value. Can be any object.
|
43
|
-
def value= value
|
44
|
-
ValueChange::ARG_SPECS[:value].validate_value value
|
45
|
-
@value = value
|
46
|
-
end
|
47
|
-
|
48
|
-
# Set the transition.
|
49
|
-
def transition= transition
|
50
|
-
ValueChange::ARG_SPECS[:transition].validate_value transition
|
51
|
-
@transition = transition
|
32
|
+
return ValueChange.new(@value, @transition.clone)
|
52
33
|
end
|
53
34
|
end
|
54
35
|
|
55
36
|
module_function
|
56
37
|
|
57
|
-
# Creates a ValueChange object
|
58
|
-
# @param [Object] value
|
59
|
-
# @param [Transition] transition
|
60
|
-
def value_change(value, transition = immediate())
|
61
|
-
return ValueChange.new(:value => value, :transition => transition)
|
62
|
-
end
|
63
|
-
|
64
38
|
# Creates a ValueChange object using an immediate transition.
|
65
39
|
# @param [Object] value
|
66
40
|
def immediate_change(value)
|
67
|
-
return ValueChange.new(
|
41
|
+
return ValueChange.new(value, Transition::Immediate.new)
|
68
42
|
end
|
69
43
|
|
70
44
|
# Creates a ValueChange object using a linear transition.
|
71
45
|
# @param [Object] value
|
72
46
|
# @param [Transition] transition_duration Length of the transition
|
73
47
|
def linear_change(value, transition_duration = 0.0)
|
74
|
-
return ValueChange.new(
|
48
|
+
return ValueChange.new(value, Transition::Linear.new(transition_duration))
|
75
49
|
end
|
76
50
|
|
77
51
|
# Creates a ValueChange object using a sigmoid transition.
|
78
52
|
# @param [Object] value
|
79
53
|
# @param [Transition] transition_duration Length of the transition
|
80
|
-
def sigmoid_change(value, transition_duration = 0.0, abruptness =
|
81
|
-
return ValueChange.new(
|
54
|
+
def sigmoid_change(value, transition_duration = 0.0, abruptness = 0.5)
|
55
|
+
return ValueChange.new(value, Transition::Sigmoid.new(transition_duration, abruptness))
|
82
56
|
end
|
83
57
|
|
84
58
|
end
|