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.
Files changed (45) hide show
  1. data/README.md +52 -0
  2. data/Rakefile +31 -0
  3. data/lib/mtk/chord.rb +47 -0
  4. data/lib/mtk/constants/dynamics.rb +56 -0
  5. data/lib/mtk/constants/intervals.rb +76 -0
  6. data/lib/mtk/constants/pitch_classes.rb +18 -0
  7. data/lib/mtk/constants/pitches.rb +24 -0
  8. data/lib/mtk/constants/pseudo_constants.rb +25 -0
  9. data/lib/mtk/event.rb +61 -0
  10. data/lib/mtk/midi/file.rb +179 -0
  11. data/lib/mtk/note.rb +44 -0
  12. data/lib/mtk/numeric_extensions.rb +61 -0
  13. data/lib/mtk/pattern/choice.rb +21 -0
  14. data/lib/mtk/pattern/note_sequence.rb +60 -0
  15. data/lib/mtk/pattern/pitch_sequence.rb +22 -0
  16. data/lib/mtk/pattern/sequence.rb +65 -0
  17. data/lib/mtk/patterns.rb +4 -0
  18. data/lib/mtk/pitch.rb +112 -0
  19. data/lib/mtk/pitch_class.rb +113 -0
  20. data/lib/mtk/pitch_class_set.rb +106 -0
  21. data/lib/mtk/pitch_set.rb +95 -0
  22. data/lib/mtk/timeline.rb +160 -0
  23. data/lib/mtk/util/mappable.rb +14 -0
  24. data/lib/mtk.rb +36 -0
  25. data/spec/mtk/chord_spec.rb +74 -0
  26. data/spec/mtk/constants/dynamics_spec.rb +94 -0
  27. data/spec/mtk/constants/intervals_spec.rb +140 -0
  28. data/spec/mtk/constants/pitch_classes_spec.rb +35 -0
  29. data/spec/mtk/constants/pitches_spec.rb +23 -0
  30. data/spec/mtk/event_spec.rb +120 -0
  31. data/spec/mtk/midi/file_spec.rb +208 -0
  32. data/spec/mtk/note_spec.rb +65 -0
  33. data/spec/mtk/numeric_extensions_spec.rb +102 -0
  34. data/spec/mtk/pattern/choice_spec.rb +21 -0
  35. data/spec/mtk/pattern/note_sequence_spec.rb +121 -0
  36. data/spec/mtk/pattern/pitch_sequence_spec.rb +47 -0
  37. data/spec/mtk/pattern/sequence_spec.rb +54 -0
  38. data/spec/mtk/pitch_class_set_spec.rb +103 -0
  39. data/spec/mtk/pitch_class_spec.rb +165 -0
  40. data/spec/mtk/pitch_set_spec.rb +163 -0
  41. data/spec/mtk/pitch_spec.rb +217 -0
  42. data/spec/mtk/timeline_spec.rb +234 -0
  43. data/spec/spec_helper.rb +7 -0
  44. data/spec/test.mid +0 -0
  45. metadata +97 -0
@@ -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