mtk 0.0.1
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.
- data/README.md +52 -0
- data/Rakefile +31 -0
- data/lib/mtk/chord.rb +47 -0
- data/lib/mtk/constants/dynamics.rb +56 -0
- data/lib/mtk/constants/intervals.rb +76 -0
- data/lib/mtk/constants/pitch_classes.rb +18 -0
- data/lib/mtk/constants/pitches.rb +24 -0
- data/lib/mtk/constants/pseudo_constants.rb +25 -0
- data/lib/mtk/event.rb +61 -0
- data/lib/mtk/midi/file.rb +179 -0
- data/lib/mtk/note.rb +44 -0
- data/lib/mtk/numeric_extensions.rb +61 -0
- data/lib/mtk/pattern/choice.rb +21 -0
- data/lib/mtk/pattern/note_sequence.rb +60 -0
- data/lib/mtk/pattern/pitch_sequence.rb +22 -0
- data/lib/mtk/pattern/sequence.rb +65 -0
- data/lib/mtk/patterns.rb +4 -0
- data/lib/mtk/pitch.rb +112 -0
- data/lib/mtk/pitch_class.rb +113 -0
- data/lib/mtk/pitch_class_set.rb +106 -0
- data/lib/mtk/pitch_set.rb +95 -0
- data/lib/mtk/timeline.rb +160 -0
- data/lib/mtk/util/mappable.rb +14 -0
- data/lib/mtk.rb +36 -0
- data/spec/mtk/chord_spec.rb +74 -0
- data/spec/mtk/constants/dynamics_spec.rb +94 -0
- data/spec/mtk/constants/intervals_spec.rb +140 -0
- data/spec/mtk/constants/pitch_classes_spec.rb +35 -0
- data/spec/mtk/constants/pitches_spec.rb +23 -0
- data/spec/mtk/event_spec.rb +120 -0
- data/spec/mtk/midi/file_spec.rb +208 -0
- data/spec/mtk/note_spec.rb +65 -0
- data/spec/mtk/numeric_extensions_spec.rb +102 -0
- data/spec/mtk/pattern/choice_spec.rb +21 -0
- data/spec/mtk/pattern/note_sequence_spec.rb +121 -0
- data/spec/mtk/pattern/pitch_sequence_spec.rb +47 -0
- data/spec/mtk/pattern/sequence_spec.rb +54 -0
- data/spec/mtk/pitch_class_set_spec.rb +103 -0
- data/spec/mtk/pitch_class_spec.rb +165 -0
- data/spec/mtk/pitch_set_spec.rb +163 -0
- data/spec/mtk/pitch_spec.rb +217 -0
- data/spec/mtk/timeline_spec.rb +234 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/test.mid +0 -0
- metadata +97 -0
data/lib/mtk/timeline.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
module MTK
|
2
|
+
|
3
|
+
# Maps sorted times to lists of events.
|
4
|
+
#
|
5
|
+
# Enumerable as |time,event| pairs.
|
6
|
+
#
|
7
|
+
class Timeline
|
8
|
+
|
9
|
+
include Mappable
|
10
|
+
|
11
|
+
def initialize()
|
12
|
+
@timeline = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def from_a(enumerable)
|
17
|
+
new.merge enumerable
|
18
|
+
end
|
19
|
+
alias from_hash from_a
|
20
|
+
end
|
21
|
+
|
22
|
+
def merge enumerable
|
23
|
+
for time,events in enumerable
|
24
|
+
add(time,events)
|
25
|
+
end
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear
|
30
|
+
@timeline.clear
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_hash
|
35
|
+
@timeline
|
36
|
+
end
|
37
|
+
|
38
|
+
def == other
|
39
|
+
other = other.to_hash unless other.is_a? Hash
|
40
|
+
@timeline == other
|
41
|
+
end
|
42
|
+
|
43
|
+
def [](time)
|
44
|
+
@timeline[time]
|
45
|
+
end
|
46
|
+
|
47
|
+
def []=(time, events)
|
48
|
+
time = time.to_f unless time.is_a? Numeric
|
49
|
+
case events
|
50
|
+
when nil?
|
51
|
+
@timeline.delete time
|
52
|
+
when Array
|
53
|
+
@timeline[time] = events
|
54
|
+
else
|
55
|
+
@timeline[time] = [events]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def add(time, event)
|
60
|
+
events = @timeline[time]
|
61
|
+
if events
|
62
|
+
if event.is_a? Array
|
63
|
+
events.concat event
|
64
|
+
else
|
65
|
+
events << event
|
66
|
+
end
|
67
|
+
else
|
68
|
+
self[time] = event
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def delete(time)
|
73
|
+
@timeline.delete(time)
|
74
|
+
end
|
75
|
+
|
76
|
+
def has_time? time
|
77
|
+
@timeline.has_key? time
|
78
|
+
end
|
79
|
+
|
80
|
+
def times
|
81
|
+
@timeline.keys.sort
|
82
|
+
end
|
83
|
+
|
84
|
+
def empty?
|
85
|
+
@timeline.empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
def events
|
89
|
+
times.map{|t| @timeline[t] }.flatten
|
90
|
+
end
|
91
|
+
|
92
|
+
def each
|
93
|
+
times.each do |time|
|
94
|
+
events = @timeline[time]
|
95
|
+
events.each do |event|
|
96
|
+
yield time,event
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def each_time
|
102
|
+
times.each do |time|
|
103
|
+
events = @timeline[time]
|
104
|
+
yield time,events
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def map! &block
|
109
|
+
mapped = enumerable_map &block
|
110
|
+
clear
|
111
|
+
merge mapped
|
112
|
+
end
|
113
|
+
|
114
|
+
def map_events
|
115
|
+
mapped_timeline = Timeline.new
|
116
|
+
each_time do |time,events|
|
117
|
+
mapped_timeline[time] = events.map{|event| yield event }
|
118
|
+
end
|
119
|
+
mapped_timeline
|
120
|
+
end
|
121
|
+
|
122
|
+
def map_events!
|
123
|
+
each_time do |time,events|
|
124
|
+
self[time] = events.map{|event| yield event }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def clone
|
129
|
+
self.class.from_hash(to_hash)
|
130
|
+
end
|
131
|
+
|
132
|
+
def compact!
|
133
|
+
@timeline.delete_if {|t,events| events.empty? }
|
134
|
+
end
|
135
|
+
|
136
|
+
def flatten
|
137
|
+
flattened = Timeline.new
|
138
|
+
for time,event in self
|
139
|
+
if event.is_a? Timeline
|
140
|
+
for subtime, subevent in event.flatten
|
141
|
+
flattened.add(time+subtime, subevent)
|
142
|
+
end
|
143
|
+
else
|
144
|
+
flattened.add(time,event)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
flattened
|
148
|
+
end
|
149
|
+
|
150
|
+
def to_s
|
151
|
+
times.map{|t| "#{t} => #{@timeline[t].join ', '}" }.join "\n"
|
152
|
+
end
|
153
|
+
|
154
|
+
def inspect
|
155
|
+
@timeline.inspect
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module MTK
|
2
|
+
|
3
|
+
# Similar to Enumerable, but relies on the including Class's from_a method to
|
4
|
+
# provide an implementation of #map which returns an object of the same type
|
5
|
+
module Mappable
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
alias enumerable_map map
|
9
|
+
def map &block
|
10
|
+
self.class.from_a(enumerable_map &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
data/lib/mtk.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'mtk/util/mappable'
|
2
|
+
require 'mtk/pitch_class'
|
3
|
+
require 'mtk/pitch_class_set'
|
4
|
+
require 'mtk/pitch'
|
5
|
+
require 'mtk/pitch_set'
|
6
|
+
|
7
|
+
require 'mtk/event'
|
8
|
+
require 'mtk/note'
|
9
|
+
require 'mtk/chord'
|
10
|
+
require 'mtk/timeline'
|
11
|
+
|
12
|
+
require 'mtk/constants/pseudo_constants'
|
13
|
+
require 'mtk/constants/pitch_classes'
|
14
|
+
require 'mtk/constants/pitches'
|
15
|
+
require 'mtk/constants/intervals'
|
16
|
+
require 'mtk/constants/dynamics'
|
17
|
+
|
18
|
+
require 'mtk/numeric_extensions'
|
19
|
+
|
20
|
+
|
21
|
+
##############################################
|
22
|
+
# Description of modules for documentation:
|
23
|
+
|
24
|
+
# The top level module for this library
|
25
|
+
module MTK
|
26
|
+
|
27
|
+
# Classes for MIDI input and output
|
28
|
+
module MIDI
|
29
|
+
end
|
30
|
+
|
31
|
+
# Classes that enumerate elements
|
32
|
+
module Pattern
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Chord do
|
4
|
+
|
5
|
+
let(:pitch_set) { PitchSet.new [C4, E4, G4] }
|
6
|
+
let(:intensity) { mf }
|
7
|
+
let(:duration) { 2.5 }
|
8
|
+
let(:chord) { Chord.new(pitch_set, intensity, duration) }
|
9
|
+
|
10
|
+
it "can be constructed with a PitchSet" do
|
11
|
+
pitch_set = PitchSet.new([C4])
|
12
|
+
Chord.new( pitch_set, intensity, duration ).pitch_set.should == pitch_set
|
13
|
+
end
|
14
|
+
|
15
|
+
it "can be constructed with an Array of Pitches" do
|
16
|
+
Chord.new( [C4], intensity, duration ).pitch_set.should == PitchSet.new([C4])
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#pitch_set" do
|
20
|
+
it "is the pitch_set used to create the Chord" do
|
21
|
+
chord.pitch_set.should == pitch_set
|
22
|
+
end
|
23
|
+
|
24
|
+
it "is a read-only attribute" do
|
25
|
+
lambda{ chord.pitch_set = PitchSet.new }.should raise_error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#pitches" do
|
30
|
+
it "is the list of pitches in the pitch_set" do
|
31
|
+
chord.pitches.should == chord.pitch_set.pitches
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "from_hash" do
|
36
|
+
it "constructs a Chord using a hash" do
|
37
|
+
Chord.from_hash({ :pitch_set => pitch_set, :intensity => intensity, :duration => duration }).should == chord
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#to_hash" do
|
42
|
+
it "is a hash containing all the attributes of the Chord" do
|
43
|
+
chord.to_hash.should == { :pitch_set => pitch_set, :intensity => intensity, :duration => duration }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#transpose' do
|
48
|
+
it 'adds the given interval to the @pitch_set' do
|
49
|
+
(chord.transpose 2.semitones).should == Chord.new(pitch_set+2, intensity, duration)
|
50
|
+
end
|
51
|
+
it 'does not affect the immutability of the Chord' do
|
52
|
+
(chord.transpose 2.semitones).should_not == chord
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#==" do
|
57
|
+
it "is true when the pitch_sets, intensities, and durations are equal" do
|
58
|
+
chord.should == Chord.new(pitch_set, intensity, duration)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "is false when the pitch_sets are not equal" do
|
62
|
+
chord.should_not == Chord.new(pitch_set + 1, intensity, duration)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "is false when the intensities are not equal" do
|
66
|
+
chord.should_not == Chord.new(pitch_set, intensity * 0.5, duration)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "is false when the durations are not equal" do
|
70
|
+
chord.should_not == Chord.new(pitch_set, intensity, duration * 2)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Dynamics do
|
4
|
+
|
5
|
+
describe 'ppp' do
|
6
|
+
it 'is equivalent to MIDI velocity 16' do
|
7
|
+
(ppp * 127).round.should == 16
|
8
|
+
end
|
9
|
+
it 'is available via a module property and via mixin' do
|
10
|
+
Dynamics::ppp.should == ppp
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'pp' do
|
15
|
+
it 'is equivalent to MIDI velocity 32' do
|
16
|
+
(pp * 127).round.should == 32
|
17
|
+
end
|
18
|
+
it 'is available via a module property and via mixin' do
|
19
|
+
Dynamics::pp.should == pp
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'p' do
|
24
|
+
it 'is equivalent to MIDI velocity 48' do
|
25
|
+
(p * 127).round.should == 48
|
26
|
+
end
|
27
|
+
it 'is available via a module property and via mixin' do
|
28
|
+
Dynamics::p.should == p
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'mp' do
|
33
|
+
it 'is equivalent to MIDI velocity 64' do
|
34
|
+
(mp * 127).round.should == 64
|
35
|
+
end
|
36
|
+
it 'is available via a module property and via mixin' do
|
37
|
+
Dynamics::mp.should == mp
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'mf' do
|
42
|
+
it 'is equivalent to MIDI velocity 79' do
|
43
|
+
(mf * 127).round.should == 79
|
44
|
+
end
|
45
|
+
it 'is available via a module property and via mixin' do
|
46
|
+
Dynamics::mf.should == mf
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'f' do
|
51
|
+
it 'is equivalent to MIDI velocity 95' do
|
52
|
+
(f * 127).round.should == 95
|
53
|
+
end
|
54
|
+
it 'is available via a module property and via mixin' do
|
55
|
+
Dynamics::f.should == f
|
56
|
+
end
|
57
|
+
it "does not overwrite the PitchClass constant 'F'" do
|
58
|
+
F.should be_a PitchClass
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'ff' do
|
63
|
+
it 'is equivalent to MIDI velocity 111' do
|
64
|
+
(ff * 127).round.should == 111
|
65
|
+
end
|
66
|
+
it 'is available via a module property and via mixin' do
|
67
|
+
Dynamics::ff.should == ff
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'fff' do
|
72
|
+
it 'is equivalent to MIDI velocity 127' do
|
73
|
+
(fff * 127).round.should == 127
|
74
|
+
end
|
75
|
+
it 'is available via a module property and via mixin' do
|
76
|
+
Dynamics::fff.should == fff
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe ".[]" do
|
81
|
+
it "looks up the constant by name" do
|
82
|
+
Dynamics['ppp'].should == ppp
|
83
|
+
Dynamics['pp'].should == pp
|
84
|
+
Dynamics['p'].should == p
|
85
|
+
Dynamics['mp'].should == mp
|
86
|
+
Dynamics['mf'].should == mf
|
87
|
+
Dynamics['f'].should == f
|
88
|
+
Dynamics['ff'].should == ff
|
89
|
+
Dynamics['fff'].should == fff
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Intervals do
|
4
|
+
|
5
|
+
describe 'P1' do
|
6
|
+
it 'is 0 semitones' do
|
7
|
+
P1.should == 0
|
8
|
+
end
|
9
|
+
it 'is available via a module property and via mixin' do
|
10
|
+
Intervals::P1.should == P1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'm2' do
|
15
|
+
it 'is 1 semitone' do
|
16
|
+
m2.should == 1
|
17
|
+
end
|
18
|
+
it 'is available via a module property and via mixin' do
|
19
|
+
Intervals::P1.should == P1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'M2' do
|
24
|
+
it 'is 2 semitones' do
|
25
|
+
M2.should == 2
|
26
|
+
end
|
27
|
+
it 'is available via a module property and via mixin' do
|
28
|
+
Intervals::P1.should == P1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'm3' do
|
33
|
+
it 'is 3 semitones' do
|
34
|
+
m3.should == 3
|
35
|
+
end
|
36
|
+
it 'is available via a module property and via mixin' do
|
37
|
+
Intervals::P1.should == P1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'M3' do
|
42
|
+
it 'is 4 semitones' do
|
43
|
+
M3.should == 4
|
44
|
+
end
|
45
|
+
it 'is available via a module property and via mixin' do
|
46
|
+
Intervals::P1.should == P1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'P4' do
|
51
|
+
it 'is 5 semitones' do
|
52
|
+
P4.should == 5
|
53
|
+
end
|
54
|
+
it 'is available via a module property and via mixin' do
|
55
|
+
Intervals::P1.should == P1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'TT' do
|
60
|
+
it 'is 6 semitones' do
|
61
|
+
TT.should == 6
|
62
|
+
end
|
63
|
+
it 'is available via a module property and via mixin' do
|
64
|
+
Intervals::P1.should == P1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'P5' do
|
69
|
+
it 'is 7 semitones' do
|
70
|
+
P5.should == 7
|
71
|
+
end
|
72
|
+
it 'is available via a module property and via mixin' do
|
73
|
+
Intervals::P1.should == P1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'm6' do
|
78
|
+
it 'is 8 semitones' do
|
79
|
+
m6.should == 8
|
80
|
+
end
|
81
|
+
it 'is available via a module property and via mixin' do
|
82
|
+
Intervals::P1.should == P1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'M6' do
|
87
|
+
it 'is 9 semitones' do
|
88
|
+
M6.should == 9
|
89
|
+
end
|
90
|
+
it 'is available via a module property and via mixin' do
|
91
|
+
Intervals::P1.should == P1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'm7' do
|
96
|
+
it 'is 10 semitones' do
|
97
|
+
m7.should == 10
|
98
|
+
end
|
99
|
+
it 'is available via a module property and via mixin' do
|
100
|
+
Intervals::P1.should == P1
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'M7' do
|
105
|
+
it 'is 11 semitones' do
|
106
|
+
M7.should == 11
|
107
|
+
end
|
108
|
+
it 'is available via a module property and via mixin' do
|
109
|
+
Intervals::P1.should == P1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'P8' do
|
114
|
+
it 'is 12 semitones' do
|
115
|
+
P8.should == 12
|
116
|
+
end
|
117
|
+
it 'is available via a module property and via mixin' do
|
118
|
+
Intervals::P1.should == P1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe ".[]" do
|
123
|
+
it "looks up the constant by name" do
|
124
|
+
Intervals['P1'].should == P1
|
125
|
+
Intervals['m2'].should == m2
|
126
|
+
Intervals['M2'].should == M2
|
127
|
+
Intervals['m3'].should == m3
|
128
|
+
Intervals['M3'].should == M3
|
129
|
+
Intervals['P4'].should == P4
|
130
|
+
Intervals['TT'].should == TT
|
131
|
+
Intervals['P5'].should == P5
|
132
|
+
Intervals['m6'].should == m6
|
133
|
+
Intervals['M6'].should == M6
|
134
|
+
Intervals['m7'].should == m7
|
135
|
+
Intervals['M7'].should == M7
|
136
|
+
Intervals['P8'].should == P8
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::PitchClasses do
|
4
|
+
let(:cases) {
|
5
|
+
[
|
6
|
+
[PitchClasses::C, 'C', 0],
|
7
|
+
[PitchClasses::Db, 'Db', 1],
|
8
|
+
[PitchClasses::D, 'D', 2],
|
9
|
+
[PitchClasses::Eb, 'Eb', 3],
|
10
|
+
[PitchClasses::E, 'E', 4],
|
11
|
+
[PitchClasses::F, 'F', 5],
|
12
|
+
[PitchClasses::Gb, 'Gb', 6],
|
13
|
+
[PitchClasses::G, 'G', 7],
|
14
|
+
[PitchClasses::Ab, 'Ab', 8],
|
15
|
+
[PitchClasses::A, 'A', 9],
|
16
|
+
[PitchClasses::Bb, 'Bb', 10],
|
17
|
+
[PitchClasses::B, 'B', 11],
|
18
|
+
]
|
19
|
+
}
|
20
|
+
|
21
|
+
it "defines constants for the 12 pitch classes in the twelve-tone octave" do
|
22
|
+
cases.length.should == 12
|
23
|
+
cases.each do |const, name, int_value|
|
24
|
+
const.name.should == name
|
25
|
+
const.to_i.should == int_value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "PITCH_CLASSES" do
|
30
|
+
it "contains the 12 pitch class constants" do
|
31
|
+
PitchClasses::PITCH_CLASSES.length.should == 12
|
32
|
+
PitchClasses::PITCH_CLASSES.should == cases.map{ |const,_,__| const }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Pitches do
|
4
|
+
|
5
|
+
it "defines constants for the 128 notes in MIDI" do
|
6
|
+
Pitches.constants.length.should == 129 # there's also the PITCHES constant
|
7
|
+
Pitches::C_1.should == Pitch.from_s('C-1')
|
8
|
+
Pitches::D0.should == Pitch.from_s('D0')
|
9
|
+
Pitches::Eb1.should == Pitch.from_s('Eb1')
|
10
|
+
Pitches::G9.should == Pitch.from_s('g9')
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "PITCHES" do
|
14
|
+
it "contains all 128 pitch constants" do
|
15
|
+
Pitches::PITCHES.length.should == 128
|
16
|
+
Pitches::PITCHES.should include Pitches::C_1
|
17
|
+
Pitches::PITCHES.should include Pitches::D0
|
18
|
+
Pitches::PITCHES.should include Pitches::Eb1
|
19
|
+
Pitches::PITCHES.should include Pitches::G9
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MTK::Event do
|
4
|
+
|
5
|
+
let(:intensity) { mf }
|
6
|
+
let(:duration) { 2.5 }
|
7
|
+
let(:event) { Event.new(intensity, duration) }
|
8
|
+
|
9
|
+
describe "#intensity" do
|
10
|
+
it "is the intensity used to create the Event" do
|
11
|
+
event.intensity.should == intensity
|
12
|
+
end
|
13
|
+
|
14
|
+
it "is a read-only attribute" do
|
15
|
+
lambda{ event.intensity = 0 }.should raise_error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#duration" do
|
20
|
+
it "is the duration used to create the Event" do
|
21
|
+
event.duration.should == duration
|
22
|
+
end
|
23
|
+
|
24
|
+
it "is a read-only attribute" do
|
25
|
+
lambda{ event.duration = 0 }.should raise_error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "from_hash" do
|
30
|
+
it "constructs an Event using a hash" do
|
31
|
+
Event.from_hash({ :intensity => intensity, :duration => duration }).should == event
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#to_hash" do
|
36
|
+
it "is a hash containing all the attributes of the Event" do
|
37
|
+
event.to_hash.should == { :intensity => intensity, :duration => duration }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#clone_with" do
|
42
|
+
it "clones the Event when given an empty hash" do
|
43
|
+
event.clone_with({}).should == event
|
44
|
+
end
|
45
|
+
|
46
|
+
it "creates an Event with the given :intensity, and the current Event's duration if not provided" do
|
47
|
+
event2 = event.clone_with :intensity => (intensity * 0.5)
|
48
|
+
event2.intensity.should == (intensity * 0.5)
|
49
|
+
event2.duration.should == duration
|
50
|
+
end
|
51
|
+
|
52
|
+
it "creates an Event with the given :duration, and the current Event's intensity if not provided" do
|
53
|
+
event2 = event.clone_with :duration => (duration * 2)
|
54
|
+
event2.intensity.should == intensity
|
55
|
+
event2.duration.should == (duration * 2)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#scale_intensity' do
|
60
|
+
it 'multiplies @intensity by the argument' do
|
61
|
+
(event.scale_intensity 0.5).should == Event.new(intensity * 0.5, duration)
|
62
|
+
end
|
63
|
+
it 'does not affect the immutability of the Evebt' do
|
64
|
+
(event.scale_intensity 0.5).should_not == event
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#scale_duration' do
|
69
|
+
it 'multiplies @duration by the argument' do
|
70
|
+
(event.scale_duration 2).should == Event.new(intensity, duration*2)
|
71
|
+
end
|
72
|
+
it 'does not affect the immutability of the Event' do
|
73
|
+
(event.scale_duration 0.5).should_not == event
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "#velocity" do
|
78
|
+
it "converts intensities in the range 0.0-1.0 to a MIDI velocity in the range 0-127" do
|
79
|
+
Event.new(0, 0).velocity.should == 0
|
80
|
+
Event.new(1, 0).velocity.should == 127
|
81
|
+
end
|
82
|
+
it "rounds to the nearest MIDI velocity" do
|
83
|
+
Event.new(0.5, 0).velocity.should == 64 # not be truncated to 63!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#duration_in_pulses" do
|
88
|
+
it "converts beats to pulses, given pulses_per_beat" do
|
89
|
+
Event.new(0,1).duration_in_pulses(60).should == 60
|
90
|
+
end
|
91
|
+
it "rounds to the nearest pulse" do
|
92
|
+
Event.new(0,1.5).duration_in_pulses(59).should == 89
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#==" do
|
97
|
+
it "is true when the intensities and durations are equal" do
|
98
|
+
event.should == Event.new(intensity, duration)
|
99
|
+
end
|
100
|
+
it "is false when the intensities are not equal" do
|
101
|
+
event.should_not == Event.new(intensity * 0.5, duration)
|
102
|
+
end
|
103
|
+
it "is false when the durations are not equal" do
|
104
|
+
event.should_not == Event.new(intensity, duration * 2)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "#to_s" do
|
109
|
+
it "is the intensity and duration to 2-decimal places" do
|
110
|
+
Event.new(0.454545, 0.789789).to_s.should == "0.45, 0.79"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "#inspect" do
|
115
|
+
it "is the string values of intensity and duration" do
|
116
|
+
Event.new(0.454545, 0.789789).inspect.should == "0.454545, 0.789789"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|