music_player 0.9.0-universal-darwin-9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ #include <ruby.h>
2
+ #include <CoreFoundation/CoreFoundation.h>
3
+
4
+ /* Test whether a VALUE is a primitive number type. */
5
+ #define PRIM_NUM_P(num) (T_FIXNUM == TYPE(num) || \
6
+ T_FLOAT == TYPE(num) || \
7
+ T_BIGNUM == TYPE(num))
8
+
9
+ /* Convert a C string to a Ruby Symbol. */
10
+ #define CSTR2SYM(str) (ID2SYM(rb_intern(str)))
11
+
12
+ /* Call ruby's === operator on the given lhs and rhs. */
13
+ #define THRQL(lhs, rhs) (rb_funcall(lhs, rb_intern("==="), 1, rhs))
14
+
15
+ /* Convert a Ruby String to a CFURLRef. */
16
+ #define PATH2CFURL(path) (rb_path_to_cfurl(path))
17
+
18
+ static CFURLRef
19
+ rb_path_to_cfurl (VALUE rb_path)
20
+ {
21
+ VALUE rb_abs_path = rb_file_expand_path(rb_path, Qnil);
22
+ char *path = StringValueCStr(rb_abs_path);
23
+ CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *) path, strlen(path), false);
24
+ return url;
25
+ }
@@ -0,0 +1,216 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '../ext/music_player')
2
+ require 'thread'
3
+ require 'music_player.bundle'
4
+
5
+ module AudioToolbox
6
+ class MusicSequence
7
+ attr :tracks
8
+
9
+ def load(path)
10
+ @tracks.lock.synchronize do
11
+ load_internal(path)
12
+ end
13
+ end
14
+ end
15
+
16
+ class MusicTrackCollection
17
+ include Enumerable
18
+
19
+ attr :lock # :nodoc:
20
+
21
+ def initialize(sequence)
22
+ @sequence = sequence
23
+ @tracks = []
24
+ @lock = Mutex.new
25
+ end
26
+
27
+ def each
28
+ 0.upto(size-1) { |i| yield self[i] }
29
+ end
30
+
31
+ def new(options=nil)
32
+ @lock.synchronize do
33
+ track = MusicTrack.send(:new, @sequence, options)
34
+ @tracks << track
35
+ track
36
+ end
37
+ end
38
+
39
+ def [](index)
40
+ @lock.synchronize do
41
+ @tracks[index] ||= ind_internal(index)
42
+ end
43
+ end
44
+
45
+ def delete(track)
46
+ @lock.synchronize do
47
+ delete_internal(track)
48
+ @tracks.delete(track)
49
+ track.freeze
50
+ end
51
+ nil
52
+ end
53
+
54
+ def tempo
55
+ @tempo ||= tempo_internal
56
+ end
57
+ end
58
+
59
+ class MusicTrack
60
+ class << self
61
+ private :new
62
+ end
63
+
64
+ def add(time, message)
65
+ message.add(time, self)
66
+ end
67
+
68
+ def iterator
69
+ MusicEventIterator.new(self)
70
+ end
71
+
72
+ include Enumerable
73
+
74
+ def each
75
+ i = iterator
76
+ while i.current?
77
+ yield i.event
78
+ i.next
79
+ end
80
+ nil
81
+ end
82
+
83
+ def each_with_time
84
+ i = iterator
85
+ while i.current?
86
+ yield [i.event, i.time]
87
+ i.next
88
+ end
89
+ nil
90
+ end
91
+ end
92
+
93
+ class MIDINoteMessage
94
+ def ==(msg)
95
+ self.class == msg.class &&
96
+ channel == msg.channel &&
97
+ note == msg.note &&
98
+ velocity == msg.velocity &&
99
+ release_velocity == msg.release_velocity &&
100
+ duration == msg.duration
101
+ end
102
+
103
+ def add(time, track)
104
+ track.add_midi_note_message(time, self)
105
+ end
106
+ end
107
+
108
+ class MIDIChannelMessage
109
+ def ==(msg)
110
+ self.class == msg.class &&
111
+ status == msg.status &&
112
+ data1 == msg.data1 &&
113
+ data2 == msg.data2
114
+ end
115
+
116
+ def channel
117
+ status ^ mask
118
+ end
119
+
120
+ def mask
121
+ raise NotImplementedError, "Subclass responsibility."
122
+ end
123
+
124
+ def add(time, track)
125
+ track.add_midi_channel_message(time, self)
126
+ end
127
+
128
+ protected
129
+ def required_opts(opts, *keys)
130
+ keys.map do |key|
131
+ opts[key] or raise ArgumentError, "%s is required." % key.inspect
132
+ end
133
+ end
134
+ end
135
+
136
+ class MIDIKeyPressureMessage < MIDIChannelMessage
137
+ alias :note :data1
138
+ alias :pressure :data2
139
+
140
+ def mask; 0xA0 end
141
+
142
+ def initialize(opts)
143
+ channel, note, pressure = required_opts(opts, :channel, :note, :pressure)
144
+ super(:status => mask | channel,
145
+ :data1 => note,
146
+ :data2 => pressure)
147
+ end
148
+ end
149
+
150
+ class MIDIControlChangeMessage < MIDIChannelMessage
151
+ alias :number :data1
152
+ alias :value :data2
153
+
154
+ def mask; 0xB0 end
155
+
156
+ def initialize(opts)
157
+ channel, number, value = required_opts(opts, :channel, :number, :value)
158
+ super(:status => mask | channel,
159
+ :data1 => number,
160
+ :data2 => value)
161
+ end
162
+ end
163
+
164
+ class MIDIProgramChangeMessage < MIDIChannelMessage
165
+ alias :program :data1
166
+
167
+ def mask; 0xC0 end
168
+
169
+ def initialize(opts)
170
+ channel, program = required_opts(opts, :channel, :program)
171
+ super(:status => mask | channel,
172
+ :data1 => program)
173
+ end
174
+ end
175
+
176
+ class MIDIChannelPressureMessage < MIDIChannelMessage
177
+ alias :pressure :data1
178
+
179
+ def mask; 0xD0 end
180
+
181
+ def initialize(opts)
182
+ channel, pressure = required_opts(opts, :channel, :pressure)
183
+ super(:status => mask | channel,
184
+ :data1 => pressure)
185
+ end
186
+ end
187
+
188
+ class MIDIPitchBendMessage < MIDIChannelMessage
189
+ alias :value :data1
190
+
191
+ def mask; 0xE0 end
192
+
193
+ def initialize(opts)
194
+ channel, value = required_opts(opts, :channel, :value)
195
+ super(:status => mask | channel,
196
+ :data1 => value)
197
+ end
198
+ end
199
+
200
+ class ExtendedTempoEvent
201
+ attr :bpm
202
+
203
+ def add(time, track)
204
+ track.add_extended_tempo_event(time, @bpm)
205
+ end
206
+
207
+ def initialize(opts)
208
+ @bpm = opts[:bpm]
209
+ end
210
+
211
+ def ==(other)
212
+ self.class == other.class &&
213
+ bpm == other.bpm
214
+ end
215
+ end
216
+ end
Binary file
@@ -0,0 +1,15 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper.rb')
2
+
3
+ class ExtendedTempoEventTest < Test::Unit::TestCase
4
+ def test_initialization
5
+ event = ExtendedTempoEvent.new(:bpm => 120)
6
+ assert_equal 120, event.bpm
7
+ end
8
+
9
+ def test_add
10
+ sequence = MusicSequence.new
11
+ tempo = sequence.tracks.tempo
12
+ event = ExtendedTempoEvent.new(:bpm => 120)
13
+ assert_nothing_raised { event.add(0.0, tempo) }
14
+ end
15
+ end
@@ -0,0 +1,93 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper.rb')
2
+
3
+ class MIDIChannelMessageTest < Test::Unit::TestCase
4
+ def test_initialization
5
+ assert_raise(ArgumentError) { MIDIChannelMessage.new }
6
+ assert_raise(ArgumentError) { MIDIChannelMessage.new({}) }
7
+ assert_nothing_raised { MIDIChannelMessage.new :status => 42 }
8
+ end
9
+
10
+ def test_eq
11
+ msg1 = make_msg
12
+ msg2 = make_msg
13
+ # Demonstrates that separate Ruby MIDIChannelMessage objects may be
14
+ # compared by value.
15
+ assert msg1.object_id != msg2.object_id
16
+ assert msg1 == msg2
17
+ end
18
+
19
+ def test_accessors
20
+ msg = make_msg
21
+ assert_equal 42, msg.status
22
+ assert_equal 43, msg.data1
23
+ assert_equal 44, msg.data2
24
+ end
25
+
26
+ def make_msg
27
+ MIDIChannelMessage.new(:status => 42, :data1 => 43, :data2 => 44)
28
+ end
29
+ end
30
+
31
+ class MIDIKeyPressureMessageTest < Test::Unit::TestCase
32
+ def test_initialization
33
+ msg = MIDIKeyPressureMessage.new(
34
+ :channel => 1,
35
+ :note => 60,
36
+ :pressure => 64)
37
+
38
+ assert_equal 1, msg.channel
39
+ assert_equal 60, msg.note
40
+ assert_equal 64, msg.pressure
41
+ assert_equal msg.mask | msg.channel, msg.status
42
+ end
43
+ end
44
+
45
+ class MIDIControlChangeMessageTest < Test::Unit::TestCase
46
+ def test_initialization
47
+ msg = MIDIControlChangeMessage.new(
48
+ :channel => 1,
49
+ :number => 8,
50
+ :value => 64)
51
+
52
+ assert_equal 1, msg.channel
53
+ assert_equal 8, msg.number
54
+ assert_equal 64, msg.value
55
+ assert_equal msg.mask | msg.channel, msg.status
56
+ end
57
+ end
58
+
59
+ class MIDIProgramChangeMessageTest < Test::Unit::TestCase
60
+ def test_initialization
61
+ msg = MIDIProgramChangeMessage.new(
62
+ :channel => 1,
63
+ :program => 2)
64
+
65
+ assert_equal 1, msg.channel
66
+ assert_equal 2, msg.program
67
+ assert_equal msg.mask | msg.channel, msg.status
68
+ end
69
+ end
70
+
71
+ class MIDIChannelPressureMessageTest < Test::Unit::TestCase
72
+ def test_initialization
73
+ msg = MIDIChannelPressureMessage.new(
74
+ :channel => 1,
75
+ :pressure => 64)
76
+
77
+ assert_equal 1, msg.channel
78
+ assert_equal 64, msg.pressure
79
+ assert_equal msg.mask | msg.channel, msg.status
80
+ end
81
+ end
82
+
83
+ class MIDIPitchBendMessageTest < Test::Unit::TestCase
84
+ def test_initialization
85
+ msg = MIDIPitchBendMessage.new(
86
+ :channel => 1,
87
+ :value => 64)
88
+
89
+ assert_equal 1, msg.channel
90
+ assert_equal 64, msg.value
91
+ assert_equal msg.mask | msg.channel, msg.status
92
+ end
93
+ end
@@ -0,0 +1,50 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper.rb')
2
+
3
+ class MIDINoteMessageTest < Test::Unit::TestCase
4
+ include AudioToolbox
5
+
6
+ def test_initialization
7
+ assert_raise(ArgumentError) { MIDINoteMessage.new }
8
+ assert_raise(ArgumentError) { MIDINoteMessage.new({}) }
9
+ assert_nothing_raised { MIDINoteMessage.new :note => 60 }
10
+ assert_nothing_raised do
11
+ # Do your own range-checking. This is harmless.
12
+ oob = rand(10000)
13
+ msg = MIDINoteMessage.new :note => oob
14
+ assert_equal (oob%256), msg.note
15
+ end
16
+ end
17
+
18
+ def test_defaults
19
+ msg = MIDINoteMessage.new :note => 60 # No default for :note.
20
+ assert_equal 1, msg.channel
21
+ assert_equal 64, msg.velocity
22
+ assert_equal 0, msg.release_velocity
23
+ assert_equal 1.0, msg.duration
24
+ end
25
+
26
+ def test_accessors
27
+ msg = make_msg
28
+ assert_equal 50, msg.note
29
+ assert_equal 40, msg.velocity
30
+ assert_equal 30, msg.release_velocity
31
+ assert_equal 2.0, msg.duration
32
+ end
33
+
34
+ def test_eq
35
+ msg1 = make_msg
36
+ msg2 = make_msg
37
+ # Demonstrates that separate Ruby MIDIChannelMessage objects may be
38
+ # compared by value.
39
+ assert msg1.object_id != msg2.object_id
40
+ assert msg1 == msg2
41
+ end
42
+
43
+ def make_msg
44
+ MIDINoteMessage.new(
45
+ :note => 50,
46
+ :velocity => 40,
47
+ :release_velocity => 30,
48
+ :duration => 2.0)
49
+ end
50
+ end
@@ -0,0 +1,160 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper.rb')
2
+
3
+ class MusicEventIteratorTest < Test::Unit::TestCase
4
+ def setup
5
+ @sequence = MusicSequence.new
6
+ @track = @sequence.tracks.new
7
+ @track.add 0, @ev1=MIDINoteMessage.new(:note => 60)
8
+ @track.add 1, @ev2=MIDINoteMessage.new(:note => 67)
9
+ @iter = @track.iterator
10
+ end
11
+
12
+ def test_seek
13
+ assert_nothing_raised { @iter.seek(0) }
14
+ assert_equal @ev1, @iter.event
15
+ # Seeking to a point between event onsets causes
16
+ # the iterator to advance to the next onset.
17
+ @iter.seek(0.1)
18
+ assert_equal @ev2, @iter.event
19
+ end
20
+
21
+ def test_current?
22
+ assert @iter.current?
23
+ @iter.next
24
+ assert @iter.current?
25
+ @iter.next
26
+ assert !@iter.current?
27
+ end
28
+
29
+ def test_next
30
+ assert_nothing_raised do
31
+ @iter.next; @iter.next
32
+ end
33
+ assert_raise(EndOfTrack) { @iter.next }
34
+ end
35
+
36
+ def test_next?
37
+ assert @iter.next?
38
+ @iter.next
39
+ assert !@iter.next?
40
+ @iter.next # We may advance once beyond the last event.
41
+ assert_raise(EndOfTrack) { @iter.next }
42
+ end
43
+
44
+ def test_prev
45
+ assert_nothing_raised do
46
+ @iter.next; @iter.prev
47
+ end
48
+ assert_raise(StartOfTrack) { @iter.prev }
49
+ end
50
+
51
+ def test_prev?
52
+ assert !@iter.prev?
53
+ @iter.next
54
+ assert @iter.prev?
55
+ end
56
+
57
+ def test_time
58
+ assert_equal 0.0, @iter.time
59
+ @iter.next
60
+ assert_equal 1.0, @iter.time
61
+ @iter.next
62
+ assert_raise(EndOfTrack) { @iter.time }
63
+ @iter.seek(0.0)
64
+ assert_equal 0.0, @iter.time
65
+ end
66
+
67
+ def test_set_time
68
+ # swap note onsets
69
+ assert_equal 0, @iter.time
70
+ @iter.time = 1
71
+ assert_equal 1, @iter.time
72
+ @iter.next
73
+ assert_equal 1, @iter.time
74
+ @iter.time = 0
75
+
76
+ @iter.seek 0
77
+ assert_equal @ev2, @iter.event
78
+ @iter.next
79
+ assert_equal @ev1, @iter.event
80
+ end
81
+
82
+ def test_event__note
83
+ assert_nothing_raised do
84
+ assert_equal @ev1, @iter.event
85
+ @iter.next
86
+ assert_equal @ev2, @iter.event
87
+ end
88
+ end
89
+
90
+ def test_event__channel
91
+ track = @sequence.tracks.new
92
+ track.add 0, ev1=MIDIKeyPressureMessage.new(:channel => 1, :note => 60, :pressure => 64)
93
+ track.add 0, ev2=MIDIControlChangeMessage.new(:channel => 1, :number => 1, :value => 127)
94
+ track.add 0, ev3=MIDIProgramChangeMessage.new(:channel => 1, :program => 42)
95
+ track.add 0, ev4=MIDIChannelPressureMessage.new(:channel => 1, :pressure => 37)
96
+ track.add 0, ev5=MIDIPitchBendMessage.new(:channel => 1, :value => 84)
97
+ iter = track.iterator
98
+
99
+ assert_equal ev1, iter.event
100
+ iter.next
101
+ assert_equal ev2, iter.event
102
+ iter.next
103
+ assert_equal ev3, iter.event
104
+ iter.next
105
+ assert_equal ev4, iter.event
106
+ iter.next
107
+ assert_equal ev5, iter.event
108
+ end
109
+
110
+ def test_event__tempo
111
+ track = @sequence.tracks.tempo
112
+ track.add 0, ev=ExtendedTempoEvent.new(:bpm => 120)
113
+ iter = track.iterator
114
+ assert_equal ev, iter.event
115
+ end
116
+
117
+ def test_set_event
118
+ # swapping notes
119
+ @iter.seek(0)
120
+ @iter.event = @ev2
121
+ @iter.next
122
+ @iter.event = @ev1
123
+
124
+ @iter.seek(0)
125
+ assert_equal @ev2, @iter.event
126
+ @iter.next
127
+ assert_equal @ev1, @iter.event
128
+
129
+ # Event types may be altered.
130
+ @iter.seek(0)
131
+ @iter.event = cc=MIDIControlChangeMessage.new(:channel => 1, :number => 2, :value => 3)
132
+ assert_equal cc, @iter.event
133
+
134
+ # Assigning when there is no current event raises an exception.
135
+ # Use MusicTrack#add to add new events.
136
+ @iter.next until !@iter.current?
137
+ assert_raise(EndOfTrack) do
138
+ @iter.event = MIDINoteMessage.new(:note => 60)
139
+ end
140
+
141
+ # Tempo tracks are iterable as well.
142
+ tempo = @sequence.tracks.tempo
143
+ tempo.add 0, ExtendedTempoEvent.new(:bpm => 120)
144
+
145
+ iter = @sequence.tracks.tempo.iterator
146
+ assert_equal 120, iter.event.bpm
147
+ iter.event = ExtendedTempoEvent.new(:bpm => 60)
148
+ assert_equal 60, iter.event.bpm
149
+ end
150
+
151
+ def test_delete
152
+ assert_equal @ev1, @iter.event
153
+ @iter.delete
154
+ assert_equal @ev2, @iter.event
155
+ @iter.delete
156
+
157
+ assert !@iter.current?
158
+ assert_nothing_raised { @iter.delete }
159
+ end
160
+ end