music_player 0.9.0-universal-darwin-9

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.
@@ -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