sound 0.0.6 → 0.0.7

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MmUyZGIzMWJmMGRmZTA5MmVlMjAwNWEyOTc3M2I4NmVkZGRkOTBkOA==
4
+ MjJmMzc0MDMzOTQ5ZjdjNDk4NzI0ZGMyYmM3NWE0NjdhZjA4YTI0Yw==
5
5
  data.tar.gz: !binary |-
6
- NWU1Zjk0Yjk4ZGQ1OTdiOTRkY2MyZTY2NmQ3NmY0YzUyOTBmOTFhMA==
6
+ YjIxMDAwMTgwMDMzNTdhOTZiNzQzNTk1MTNkMDEwYjAxMGZkNmQ5OQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZDdkNjVjZmVhOTFiZWE3ZWRiMDIyMDljMGU5ZTFkZmJlMzYzOTcxYjVmYTRh
10
- OGE0ZTcyNGNlODM5NmFiYjNkZWY5NjM5NjBkNDE4NzgyY2RlM2YyMTA5ODA0
11
- NDI4YjFjNGI5YmU5MGJkNDg5Mjc4OTE0YTA3MDRmNDdjNmEyNmQ=
9
+ MjJlYjk0NDc5NTdiOWNlYjhjMGE2NTIxODNjZDE3YjU2OWYxMGUyOTZlMTU5
10
+ MTU1Y2UzNjRhNTA1MGZhNzgxYzlmOTAwZDBjMjBjMzhmMThhYmMzOTkxYzQy
11
+ OGNhN2U5ZTRkZTYwNGE2ZmE4OThjYmU4MWJlNzgyMDQzZDEwY2M=
12
12
  data.tar.gz: !binary |-
13
- N2UxOWQxZjRlMTEwYTJlNDZhNjgwZTNlNzNlMDY1YTc4YjFmNWRhODE3NDRl
14
- MDlhMTM5NjUwYzJlOWFhNzI1NjQ0YjdkZTM1NzBhY2U0OGQxNjg3OGM3MWNk
15
- NzU4YzYwY2MxODBlY2E3NDY3MmIyYWJmMjIzOTBlMTZiNTdkMzA=
13
+ ZTJjZjJmN2EyMzcyN2RkNDdiN2VlNDNhZDMxM2E2OGRkNTgwMzQ3NDE2ZTQ0
14
+ NzE3MWYwNzYyMGM1NzA3YWJmOWNkY2MxYjU1ODY3NGI2NzhkNzQyODRiYzMz
15
+ NTRmMmY4MDM3YzE0MGYwYjE0NzI4YzE1YWQzZGI2OWQ0YmI1Nzg=
@@ -1,21 +1,154 @@
1
1
  require 'sound'
2
- Sound.verbose = true
3
2
 
3
+ # this will alert you when various events occur
4
+ # such as when a device is opened or closed, when
5
+ # data is added to the queue, etc.
6
+ Sound.verbose = true
4
7
  # Don't trust these examples. The readme is more up to date.
5
8
 
6
9
  # Example of opening a device, writing to it, and closing it up
7
10
 
11
+ # opens a device for writing
12
+ device = Sound::Device.new("w")
13
+
14
+ # creates a PCM format object
15
+ format = Sound::Format.new(Sound::Format::PCM)
16
+
17
+ # creates a new Sound Data object with a PCM format
18
+ data = Sound::Data.new(format)
19
+
20
+ # writes a sine wave to the data object with
21
+ # a frequency of 440 Hz, a duration of 500 ms
22
+ # and a volume of 1 (full volume)
23
+ data.generate_sine_wave(880, 500, 1)
24
+
25
+ # writes data to the message queue
26
+ device.write data
27
+
28
+ # flushes the message queue
29
+ device.flush
30
+
31
+ # responsibly closes the device
32
+ device.close
33
+
34
+ # with sensible defaults, you can ignore some of this
35
+
8
36
  device = Sound::Device.new
9
- data = Sound::Data.new(device.format)
37
+ data = Sound::Data.new
10
38
  data.generate_sine_wave(440, 500, 1)
11
- device.write data
39
+ device.play data
12
40
  device.close
13
41
 
14
- # This will close the device on its own after the block finishes
42
+ # note the lack of specifying a format or explicitly writing the data
43
+ # to the message queue. the #play method writes the data to the queue
44
+ # and flushes it automatically.
45
+
46
+ # with the block form, which uses #open instead of #new, it's even more
47
+ # cogent, since it will close the device for you
48
+
49
+ Sound::Device.new {|d| d.play Sound::Data.new.sine_wave(440, 500, 1)}
50
+
51
+ # Let's play a little melody! It starts to get out of sync about halfway through
52
+
53
+ threads = []
54
+
55
+ threads << Thread.new do
56
+ Sound::Device.new do |device|
57
+ device.write Sound::Data.new.sine_wave(440, 1200, 0.4)
58
+ device.write Sound::Data.new.sine_wave(440*2**(4/12.0), 600, 0.4)
59
+ device.write Sound::Data.new.sine_wave(440*2**(7/12.0), 600, 0.4)
60
+ device.write Sound::Data.new.sine_wave(440*2**(-1/12.0), 900, 0.4)
61
+ device.write Sound::Data.new.sine_wave(440*2**(0/12.0), 150, 0.4)
62
+ device.write Sound::Data.new.sine_wave(440*2**(2/12.0), 150, 0.4)
63
+ device.write Sound::Data.new.sine_wave(440*2**(0/12.0), 1200, 0.4)
64
+ device.write Sound::Data.new.sine_wave(440*2**(9/12.0), 1200, 0.4)
65
+ device.write Sound::Data.new.sine_wave(440*2**(7/12.0), 600, 0.4)
66
+ device.write Sound::Data.new.sine_wave(440*2**(12/12.0), 600, 0.4)
67
+ device.write Sound::Data.new.sine_wave(440*2**(7/12.0), 600, 0.4)
68
+ device.write Sound::Data.new.sine_wave(440*2**(5/12.0), 70, 0.4)
69
+ device.write Sound::Data.new.sine_wave(440*2**(7/12.0), 70, 0.4)
70
+ device.write Sound::Data.new.sine_wave(440*2**(5/12.0), 70, 0.4)
71
+ device.write Sound::Data.new.sine_wave(440*2**(7/12.0), 70, 0.4)
72
+ device.write Sound::Data.new.sine_wave(440*2**(5/12.0), 170, 0.4)
73
+ device.write Sound::Data.new.sine_wave(440*2**(4/12.0), 150, 0.4)
74
+ device.write Sound::Data.new.sine_wave(440*2**(4/12.0), 1200, 0.4)
75
+ Thread.pass
76
+ Thread.pass
77
+ end
78
+ end
79
+
80
+ threads << Thread.new do
81
+ Sound::Device.new do |device|
82
+ device.write Sound::Data.new.sine_wave(220*2**(0/12.0), 300, 0.4)
83
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
84
+ device.write Sound::Data.new.sine_wave(220*2**(4/12.0), 300, 0.4)
85
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
86
+
87
+ device.write Sound::Data.new.sine_wave(220*2**(0/12.0), 300, 0.4)
88
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
89
+ device.write Sound::Data.new.sine_wave(220*2**(4/12.0), 300, 0.4)
90
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
91
+
92
+ device.write Sound::Data.new.sine_wave(220*2**(-1/12.0), 300, 0.4)
93
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
94
+ device.write Sound::Data.new.sine_wave(220*2**(5/12.0), 300, 0.4)
95
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
96
+
97
+ device.write Sound::Data.new.sine_wave(220*2**(0/12.0), 300, 0.4)
98
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
99
+ device.write Sound::Data.new.sine_wave(220*2**(4/12.0), 300, 0.4)
100
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
101
+
102
+ device.write Sound::Data.new.sine_wave(220*2**(0/12.0), 300, 0.4)
103
+ device.write Sound::Data.new.sine_wave(220*2**(9/12.0), 300, 0.4)
104
+ device.write Sound::Data.new.sine_wave(220*2**(5/12.0), 300, 0.4)
105
+ device.write Sound::Data.new.sine_wave(220*2**(9/12.0), 300, 0.4)
106
+
107
+ device.write Sound::Data.new.sine_wave(220*2**(0/12.0), 300, 0.4)
108
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
109
+ device.write Sound::Data.new.sine_wave(220*2**(4/12.0), 300, 0.4)
110
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
111
+
112
+ device.write Sound::Data.new.sine_wave(220*2**(-1/12.0), 300, 0.4)
113
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
114
+ device.write Sound::Data.new.sine_wave(220*2**(5/12.0), 300, 0.4)
115
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
116
+
117
+ device.write Sound::Data.new.sine_wave(220*2**(0/12.0), 300, 0.4)
118
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
119
+ device.write Sound::Data.new.sine_wave(220*2**(4/12.0), 300, 0.4)
120
+ device.write Sound::Data.new.sine_wave(220*2**(7/12.0), 300, 0.4)
121
+ Thread.pass
122
+ end
123
+ end
124
+
125
+ threads.each {|t| t.join}
126
+ #example of asynchronous playback
127
+
128
+ Sound::Device.new do |device|
129
+ device.write_async Sound::Data.new.sine_wave(440, 300, 1)
130
+ device.write_async Sound::Data.new.sine_wave(660, 300, 1)
131
+ device.write Sound::Data.new.sine_wave(660, 300, 1)
132
+ device.write_async Sound::Data.new.sine_wave(440, 300, 1), true
133
+ device.write_async Sound::Data.new.sine_wave(880, 300, 1)
134
+ device.write_async Sound::Data.new.sine_wave(440, 300, 1), true
135
+ device.write_async Sound::Data.new.sine_wave(880*2**(4/12.0), 300, 1)
136
+
137
+ end
138
+
139
+ threads = []
15
140
 
141
+ threads << Thread.new do
142
+ Sound::Device.new do |device|
143
+ device.write Sound::Data.new.sine_wave(440, 500, 1)
144
+ Thread.pass
145
+ end
146
+ end
147
+ threads << Thread.new do
148
+ Sound::Device.new do |device|
149
+ device.write Sound::Data.new.sine_wave(660, 500, 1)
150
+ Thread.pass
151
+ end
152
+ end
16
153
 
17
- #data = WaveData.new
18
- #
19
- #device = Device.open
20
- #device.write(data)
21
- #device.close
154
+ threads.each {|t| t.join}
@@ -3,14 +3,45 @@ require 'ffi'
3
3
  require 'pry'
4
4
  require 'os/os'
5
5
 
6
+ module Sound
7
+
8
+ @verbose = false
9
+ @no_device = false
10
+ @platform_supported = false
11
+
12
+ class << self
13
+ attr_accessor :verbose, :no_device, :platform_supported
14
+ end
15
+
16
+ class NoDeviceError < RuntimeError; end
17
+ class NoDependencyError < RuntimeError; end
18
+
19
+ end
20
+
6
21
  if OS.windows?
7
- require 'sound/win32/sound'
22
+ require 'sound/win32'
23
+ module Sound
24
+ class Device
25
+ include Win32
26
+ end
27
+ end
28
+ Sound.platform_supported = true
8
29
  elsif OS.linux?
9
- require 'sound/linux/sound'
30
+ libasound_present = !(`which aplay`.eql? "")
31
+ unless libasound_present
32
+ warn("warning: sound output requires libasound2, libasound2-dev, and alsa-utils packages")
33
+ end
34
+ require 'sound/alsa'
35
+ module Sound
36
+ class Device
37
+ include ALSA
38
+ end
39
+ end
40
+ Sound.platform_supported = true
10
41
  else
11
42
  warn("warning: Sound output not yet implemented for this platform: #{OS.os}")
12
43
  end
13
44
 
45
+ require 'sound/device'
14
46
  require 'sound/data'
15
47
  require 'sound/format'
16
- require 'sound/device'
@@ -0,0 +1,211 @@
1
+ require 'ffi'
2
+
3
+ module Sound
4
+ module ALSA
5
+
6
+ class Handle
7
+ def initialize
8
+ @handle = FFI::MemoryPointer.new(:pointer)
9
+ end
10
+ def pointer
11
+ @handle
12
+ end
13
+ def id
14
+ @handle.read_pointer
15
+ end
16
+ end
17
+
18
+ SND_PCM_STREAM_PLAYBACK = 0
19
+ SND_PCM_STREAM_CAPTURE = 1
20
+ DEFAULT_DEVICE_ID = "default"
21
+
22
+ extend FFI::Library
23
+ ffi_lib 'asound'
24
+ ffi_convention :stdcall
25
+
26
+ attach_function :snd_pcm_open, [:pointer, :string, :int, :int], :int
27
+ attach_function :snd_pcm_close, [:pointer], :int
28
+ attach_function :snd_pcm_drain, [:pointer], :int
29
+ attach_function :snd_pcm_prepare, [:pointer], :int
30
+ attach_function :write_noninterleaved, :snd_pcm_writen, [:pointer, :pointer, :ulong], :long
31
+ attach_function :snd_pcm_writei, [:pointer, :pointer, :ulong], :long
32
+ attach_function :snd_pcm_hw_params_malloc, [:pointer], :int
33
+ attach_function :snd_pcm_hw_params_any, [:pointer, :pointer], :int
34
+ attach_function :snd_pcm_hw_params_set_access, [:pointer, :pointer, :int], :int
35
+ attach_function :snd_pcm_hw_params_set_format, [:pointer, :pointer, :int], :int
36
+ attach_function :snd_pcm_hw_params_set_rate, [:pointer, :pointer, :uint, :int], :int
37
+ attach_function :snd_pcm_hw_params_set_channels, [:pointer, :pointer, :int], :int
38
+ attach_function :snd_pcm_hw_params, [:pointer, :pointer], :int
39
+ attach_function :snd_pcm_hw_params_free, [:pointer], :void
40
+
41
+ def snd_pcm_open(*args)
42
+ output = `aplay -l 2>&1`
43
+ if output.match(/no soundcard/m)
44
+ raise NoDeviceError, "No sound devices present"
45
+ elsif output.match(/not found/m)
46
+ raise NoDependencyError, "aplay is not present in your environment. Install alsa-utils package for audio playback."
47
+ else
48
+ snd_pcm_open(*args)
49
+ end
50
+ end
51
+
52
+ def open_device
53
+ begin
54
+ snd_pcm_open(handle.pointer, id, 0, ASYNC)
55
+ rescue NoDeviceError
56
+ Sound.no_device = true
57
+ end
58
+ end
59
+
60
+ def prepare_buffer
61
+
62
+ unless Sound.no_device
63
+ buffer_length
64
+
65
+ snd_pcm_hw_params_malloc(params_handle.pointer)
66
+ snd_pcm_hw_params_any(handle.id, params_handle.id)
67
+
68
+ snd_pcm_hw_params_set_access(handle.id, params_handle.id, SND_PCM_ACCESS_RW_INTERLEAVED)
69
+ set_formsnd_pcm_hw_params_set_formatat(handle.id, params_handle.id, SND_PCM_FORMAT_S16_LE)
70
+ # need to change this to set_rate_near at some point
71
+ snd_pcm_hw_params_set_rate(handle.id, params_handle.id, data.format.sample_rate, 0)
72
+ snd_pcm_hw_params_set_channels(handle.id, params_handle.id, 1)
73
+
74
+ snd_pcm_hw_params(handle.id, params_handle.id)
75
+ snd_pcm_hw_params_free(params_handle.id)
76
+
77
+ snd_pcm_prepare(handle.id)
78
+ end
79
+
80
+ end
81
+
82
+ def write_to_device
83
+ snd_pcm_writei(handle.id, data_buffer, buffer_length) unless Sound.no_device
84
+ end
85
+
86
+ def unprepare_buffer
87
+ snd_pcm_drain(handle.id) unless Sound.no_device
88
+ end
89
+
90
+ def close_device
91
+ snd_pcm_close(handle.id) unless Sound.no_device
92
+ end
93
+
94
+ def handle
95
+ Thread.current[:handle] ||= Handle.new
96
+ end
97
+
98
+ def params_handle
99
+ Thread.current[:params_handle] ||= Handle.new
100
+ end
101
+
102
+ def data
103
+ Thread.current[:data]
104
+ end
105
+
106
+ def data_buffer
107
+ Thread.current[:data_buffer] ||= FFI::MemoryPointer.new(:int, data.pcm_data.size).write_array_of_int data.pcm_data
108
+ end
109
+
110
+ def buffer_length
111
+ Thread.current[:buffer_length] ||= data_buffer.size/2
112
+ end
113
+
114
+ SND_PCM_ASYNC = 2
115
+ ASYNC = SND_PCM_ASYNC
116
+ #snd_pcm formats
117
+ # Unknown
118
+ SND_PCM_FORMAT_UNKNOWN = -1
119
+ # Signed 8 bit
120
+ SND_PCM_FORMAT_S8 = 0
121
+ # Unsigned 8 bit
122
+ SND_PCM_FORMAT_U8 = 1
123
+ # Signed 16 bit Little Endian
124
+ SND_PCM_FORMAT_S16_LE = 2
125
+ # Signed 16 bit Big Endian
126
+ SND_PCM_FORMAT_S16_BE = 3
127
+ # Unsigned 16 bit Little Endian
128
+ SND_PCM_FORMAT_U16_LE = 4
129
+ # Unsigned 16 bit Big Endian
130
+ SND_PCM_FORMAT_U16_BE = 5
131
+ # Signed 24 bit Little Endian using low three bytes in 32-bit word
132
+ SND_PCM_FORMAT_S24_LE = 6
133
+ # Signed 24 bit Big Endian using low three bytes in 32-bit word
134
+ SND_PCM_FORMAT_S24_BE = 7
135
+ # Unsigned 24 bit Little Endian using low three bytes in 32-bit word
136
+ SND_PCM_FORMAT_U24_LE = 8
137
+ # Unsigned 24 bit Big Endian using low three bytes in 32-bit word
138
+ SND_PCM_FORMAT_U24_BE = 9
139
+ # Signed 32 bit Little Endian
140
+ SND_PCM_FORMAT_S32_LE = 10
141
+ # Signed 32 bit Big Endian
142
+ SND_PCM_FORMAT_S32_BE = 11
143
+ # Unsigned 32 bit Little Endian
144
+ SND_PCM_FORMAT_U32_LE = 12
145
+ # Unsigned 32 bit Big Endian
146
+ SND_PCM_FORMAT_U32_BE = 13
147
+ # Float 32 bit Little Endian, Range -1.0 to 1.0
148
+ SND_PCM_FORMAT_FLOAT_LE = 14
149
+ # Float 32 bit Big Endian, Range -1.0 to 1.0
150
+ SND_PCM_FORMAT_FLOAT_BE = 15
151
+ # Float 64 bit Little Endian, Range -1.0 to 1.0
152
+ SND_PCM_FORMAT_FLOAT64_LE = 16
153
+ # Float 64 bit Big Endian, Range -1.0 to 1.0
154
+ SND_PCM_FORMAT_FLOAT64_BE = 17
155
+ # IEC-958 Little Endian
156
+ SND_PCM_FORMAT_IEC958_SUBFRAME_LE = 18
157
+ # IEC-958 Big Endian
158
+ SND_PCM_FORMAT_IEC958_SUBFRAME_BE = 19
159
+ # Mu-Law
160
+ SND_PCM_FORMAT_MU_LAW = 20
161
+ # A-Law
162
+ SND_PCM_FORMAT_A_LAW = 21
163
+ # Ima-ADPCM
164
+ SND_PCM_FORMAT_IMA_ADPCM = 22
165
+ # MPEG
166
+ SND_PCM_FORMAT_MPEG = 23
167
+ # GSM
168
+ SND_PCM_FORMAT_GSM = 24
169
+ # Special
170
+ SND_PCM_FORMAT_SPECIAL = 31
171
+ # Signed 24bit Little Endian in 3bytes format
172
+ SND_PCM_FORMAT_S24_3LE = 32
173
+ # Signed 24bit Big Endian in 3bytes format
174
+ SND_PCM_FORMAT_S24_3BE = 33
175
+ # Unsigned 24bit Little Endian in 3bytes format
176
+ SND_PCM_FORMAT_U24_3LE = 34
177
+ # Unsigned 24bit Big Endian in 3bytes format
178
+ SND_PCM_FORMAT_U24_3BE = 35
179
+ # Signed 20bit Little Endian in 3bytes format
180
+ SND_PCM_FORMAT_S20_3LE = 36
181
+ # Signed 20bit Big Endian in 3bytes format
182
+ SND_PCM_FORMAT_S20_3BE = 37
183
+ # Unsigned 20bit Little Endian in 3bytes format
184
+ SND_PCM_FORMAT_U20_3LE = 38
185
+ # Unsigned 20bit Big Endian in 3bytes format
186
+ SND_PCM_FORMAT_U20_3BE = 39
187
+ # Signed 18bit Little Endian in 3bytes format
188
+ SND_PCM_FORMAT_S18_3LE = 40
189
+ # Signed 18bit Big Endian in 3bytes format
190
+ SND_PCM_FORMAT_S18_3BE = 41
191
+ # Unsigned 18bit Little Endian in 3bytes format
192
+ SND_PCM_FORMAT_U18_3LE = 42
193
+ # Unsigned 18bit Big Endian in 3bytes format
194
+ SND_PCM_FORMAT_U18_3BE = 43
195
+ SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_U18_3BE
196
+
197
+ #snd_pcm access
198
+ # mmap access with simple interleaved channels
199
+ SND_PCM_ACCESS_MMAP_INTERLEAVED = 0
200
+ # mmap access with simple non interleaved channels
201
+ SND_PCM_ACCESS_MMAP_NONINTERLEAVED = 1
202
+ # mmap access with complex placement
203
+ SND_PCM_ACCESS_MMAP_COMPLEX = 2
204
+ # snd_pcm_readi/snd_pcm_writei access
205
+ SND_PCM_ACCESS_RW_INTERLEAVED = 3
206
+ # snd_pcm_readn/snd_pcm_writen access
207
+ SND_PCM_ACCESS_RW_NONINTERLEAVED = 4
208
+ SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED
209
+
210
+ end
211
+ end
@@ -2,13 +2,16 @@
2
2
  module Sound
3
3
 
4
4
  class Data
5
- attr_reader :data, :duration
6
- def initialize(format)
5
+ attr_reader :pcm_data, :duration, :format
6
+ def initialize(format = Format.new)
7
7
  @format = format
8
- @data = []
8
+ @pcm_data = []
9
+ end
10
+ def to_s
11
+ puts "#<Sound::Data:#{object_id}>"
9
12
  end
10
13
  def generate_sine_wave(freq, duration, volume)
11
- @data = []
14
+ @pcm_data = []
12
15
  @duration = duration
13
16
  ramp = 200.0
14
17
  samples = (@format.sample_rate/2*duration/1000.0).floor
@@ -26,11 +29,12 @@ module Sound
26
29
  x *= (samples - sample)/ramp
27
30
  end
28
31
 
29
- @data << x.floor
32
+ @pcm_data << x.floor
30
33
  end
31
34
 
32
35
  self
33
36
  end
37
+ alias :sine_wave :generate_sine_wave
34
38
  end
35
39
 
36
40
  end
@@ -1,55 +1,50 @@
1
1
 
2
2
  module Sound
3
-
4
- @verbose = false
5
-
6
- class << self
7
- attr_accessor :verbose
8
- end
9
-
10
- WAVE_MAPPER = -1
11
3
 
12
4
  class Device
13
5
 
14
- class Handle
15
- def initialize
16
- if OS.windows?
17
- @handle = Win32::HWAVEOUT.new
18
- elsif OS.linux?
19
- @handle = FFI::MemoryPointer.new(:pointer)
20
- end
21
- end
22
- def pointer
23
- if OS.windows?
24
- @handle.pointer
25
- elsif OS.linux?
26
- @handle
27
- end
28
- end
29
- def id
30
- if OS.windows?
31
- @handle[:i]
32
- elsif OS.linux?
33
- @handle.read_pointer
34
- end
35
- end
36
- end
6
+ class Buffer < Array; end
37
7
 
38
- attr_accessor :closed, :id, :handle, :format
8
+ attr_reader :status, :id
39
9
 
40
- def initialize(format = Format::PCM, direction = "w", id = nil)
41
- if OS.windows?
42
- id ||= WAVE_MAPPER
43
- elsif OS.linux?
44
- id ||= "default"
45
- end
10
+ # creates a new device for writing by default. default id is set by
11
+ # whatever device interface was included (Win32 or ALSA, e.g.)
12
+ # if a block is passed, it executes the code in the block, passing
13
+ # the newly created device, and then closes the device.
14
+ #
15
+ def initialize(direction = "w", id = DEFAULT_DEVICE_ID, &block)
16
+
46
17
  @id = id
47
- closed = false
48
- @queue = []
18
+ @status = :open
19
+ @queue = Device::Buffer.new
49
20
  @mutex = Mutex.new
50
- @handle = Device::Handle.new
51
- @format = format
52
21
  @direction = direction
22
+
23
+ puts "opening device: '#{id}'" if Sound.verbose
24
+
25
+ if block_given?
26
+ block.call(self)
27
+ close
28
+ end
29
+
30
+ end
31
+
32
+ # checks if the current status of the device is :open
33
+ #
34
+ def open?
35
+ status == :open
36
+ end
37
+
38
+ # checks if the current status of the device is :closed
39
+ #
40
+ def closed?
41
+ status == :closed
42
+ end
43
+
44
+ # returns the current queue. should be used for debugging only.
45
+ #
46
+ def queue
47
+ @queue.dup.freeze
53
48
  end
54
49
 
55
50
  class << self
@@ -58,114 +53,132 @@ module Sound
58
53
  # direction is reading or writing or both
59
54
  # format is MIDI vs PCM or others
60
55
  # this method can take a block and if so closes the device after execution
61
- def open(device = Device::DEFAULT, direction = "w", format = Format::PCM, &block)
62
- device.format = format
63
- puts "opening device_#{device.id}" if Sound.verbose
56
+ #
57
+ def open(device = Device.new, direction = "w", &block)
58
+ puts "opening device: '#{device.id}'" if Sound.verbose
64
59
  if block_given?
65
60
  block.call(device)
66
61
  device.close
67
62
  else
68
- device.closed = false
63
+ device.open
69
64
  device
70
65
  end
71
66
  end
72
67
  end
73
68
 
74
- def play(data = "beep boop")
69
+ # opens the device.
70
+ #
71
+ def open
72
+ @status = :open
73
+ self
74
+ end
75
+
76
+ # writes data to the queue and immediately flushes the queue.
77
+ #
78
+ def play(data = Sound::Data.new)
75
79
  write(data)
76
80
  flush
77
81
  end
78
-
79
- def write(data = "beep boop")
82
+
83
+ # writes given Data to the queue as a data block thread. Threads pause
84
+ # after preperation, and during flushing they get started back up. If a
85
+ # thread isn't done preparing when flushed, it finished preparing and
86
+ # immediately writes data to the device.
87
+ #
88
+ def write(data = Sound::Data.new)
80
89
  if closed?
81
90
  puts "cannot write to a closed device"
82
91
  else
83
92
  @mutex.lock
84
93
  @queue << Thread.new do
85
- write_thread(data)
94
+ Thread.current[:async] = false
95
+ Thread.current[:data] = data
96
+ write_thread
86
97
  end
87
98
  @mutex.unlock
88
- puts "writing to device_#{id}_queue: #{data.class}" if Sound.verbose
99
+ puts "writing to queue of device '#{id}': #{data}" if Sound.verbose
89
100
  end
101
+ self
90
102
  end
91
103
 
104
+ # starts up data block threads that get played back at the same time. Need
105
+ # to make all threads wait until others are finished preparing the buffer.
106
+ #
107
+ def write_async(data = Sound::Data.new, new_queue_elem = false)
108
+ if closed?
109
+ puts "cannot write to a closed device"
110
+ else
111
+ @mutex.lock
112
+ if new_queue_elem || @queue.empty? || @queue.last.kind_of?(Thread)
113
+ threads = []
114
+ threads << Thread.new do
115
+ Thread.current[:async] = true
116
+ Thread.current[:data] = data
117
+ write_thread
118
+ end
119
+ @queue << threads
120
+ else
121
+ @queue.last << Thread.new do
122
+ Thread.current[:async] = true
123
+ Thread.current[:data] = data
124
+ write_thread
125
+ end
126
+ end
127
+ @mutex.unlock
128
+ puts "writing async to queue of device '#{id}': #{data}" if Sound.verbose
129
+ end
130
+ end
131
+
132
+ # should make a close! method that ignores any pending queue data blocks,
133
+ # but still safely closes the device as quickly as possible.
134
+ #
92
135
  def close
93
136
  if closed?
94
137
  puts "cannot close a closed device"
95
138
  else
96
139
  flush
97
- puts "device is closing now" if Sound.verbose
98
- closed = true
140
+ puts "device '#{id}' is closing now" if Sound.verbose
141
+ @status = :closed
99
142
  end
100
143
  end
101
144
 
145
+ # flushes each block after previous finishes. Should make other options,
146
+ # like flush each block after a specified amount of time.
147
+ #
102
148
  def flush
103
149
  until @queue.empty?
104
150
  output = @queue.shift
105
- output[:stop] = false
106
- puts "writing to device_#{id}: #{output}" if Sound.verbose
107
- output.run.join
151
+ if output.kind_of? Thread
152
+ output[:stop] = false
153
+ puts "writing to device '#{id}': #{output[:data].class}" if Sound.verbose #this may be NilClass if parent thread is too fast
154
+ output.run.join
155
+ else
156
+ output.each do |thread|
157
+ thread[:stop] = false
158
+ puts "writing to device '#{id}': #{thread[:data].class}" if Sound.verbose
159
+ thread.run
160
+ end
161
+ output.last.join if output.last.alive?
162
+ end
108
163
  end
109
164
  end
110
165
 
111
- def closed?
112
- closed
113
- end
166
+ private
114
167
 
115
- def write_thread(data)
168
+ def write_thread
116
169
  Thread.current[:stop] = true if Thread.current[:stop].nil?
117
- if OS.windows?
118
- windows_write_thread(data)
119
- elsif OS.linux?
120
- linux_write_thread(data)
170
+ if Sound.platform_supported
171
+ open_device
172
+ prepare_buffer
173
+ Thread.stop if Thread.current[:stop]
174
+ Thread.pass if Thread.current[:async]
175
+ write_to_device
176
+ unprepare_buffer
177
+ close_device
121
178
  else
122
179
  warn("warning: playback is not yet supported on this platform")
123
180
  end
124
181
  end
125
-
126
- def windows_write_thread(data)
127
- handle = Handle.new
128
- Win32::Sound.waveOutOpen(handle.pointer, id, format.pointer, 0, 0, 0)
129
- data_buffer = FFI::MemoryPointer.new(:int, data.data.size)
130
- data_buffer.write_array_of_int data.data
131
- buffer_length = format.avg_bps*data.duration/1000
132
- header = Win32::WAVEHDR.new(data_buffer, buffer_length)
133
- Win32::Sound.waveOutPrepareHeader(handle.id, header.pointer, header.size)
134
- Thread.stop if Thread.current[:stop]
135
- Win32::Sound.waveOutWrite(handle.id, header.pointer, header.size)
136
- while Win32::Sound.waveOutUnprepareHeader(handle.id, header.pointer, header.size) == 33
137
- sleep 0.001
138
- end
139
- Win32::Sound.waveOutClose(handle.id)
140
- end
141
-
142
- def linux_write_thread(data)
143
- handle = Handle.new
144
- AlsaPCM::Sound.snd_pcm_open(handle.pointer, id, 0, 0)
145
- data_buffer = FFI::MemoryPointer.new(:int, data.data.size)
146
- data_buffer.write_array_of_int data.data
147
- buffer_length = data_buffer.size/2
148
- params = FFI::MemoryPointer.new(:pointer)
149
- AlsaPCM::Sound.snd_pcm_hw_params_malloc(params)
150
- AlsaPCM::Sound.snd_pcm_hw_params_any(handle.id, params.read_pointer)
151
-
152
- AlsaPCM::Sound.snd_pcm_hw_params_set_access(handle.id, params.read_pointer, 3)
153
- AlsaPCM::Sound.snd_pcm_hw_params_set_format(handle.id, params.read_pointer, 2)
154
- AlsaPCM::Sound.snd_pcm_hw_params_set_rate(handle.id, params.read_pointer, 44100, 0)
155
- AlsaPCM::Sound.snd_pcm_hw_params_set_channels(handle.id, params.read_pointer, 1)
156
-
157
- AlsaPCM::Sound.snd_pcm_hw_params(handle.id, params.read_pointer)
158
- AlsaPCM::Sound.snd_pcm_hw_params_free(params.read_pointer)
159
-
160
- AlsaPCM::Sound.snd_pcm_prepare(handle.id)
161
- Thread.stop if Thread.current[:stop]
162
- AlsaPCM::Sound.snd_pcm_writei(handle.id, data_buffer, buffer_length)
163
-
164
-
165
- AlsaPCM::Sound.snd_pcm_drain(handle.id)
166
- AlsaPCM::Sound.snd_pcm_close(handle.id)
167
-
168
- end
169
182
  end
170
183
 
171
184
  end
@@ -5,8 +5,10 @@ module Sound
5
5
  WAVE_FORMAT_PCM = 1
6
6
 
7
7
  class Format
8
- attr_accessor :channels, :sample_rate, :bps
9
- def initialize(format_type = WAVE_FORMAT_PCM)
8
+
9
+ PCM = WAVE_FORMAT_PCM
10
+ attr_accessor :channels, :sample_rate, :bps, :alsa_format
11
+ def initialize(format_type = PCM)
10
12
  @channels = 1
11
13
  @sample_rate = 44100
12
14
  @bps = 16
@@ -32,7 +34,6 @@ module Sound
32
34
  @wfx.pointer
33
35
  end
34
36
  end
35
- PCM = self.new
36
37
  end
37
38
 
38
39
  end
@@ -1,24 +1,79 @@
1
+ require 'ffi'
1
2
 
2
3
  module Sound
3
4
  module Win32
5
+
6
+ class Handle
7
+ def initialize
8
+ @handle = HWAVEOUT.new
9
+ end
10
+ def pointer
11
+ @handle.pointer
12
+ end
13
+ def id
14
+ @handle[:i]
15
+ end
16
+ end
4
17
 
5
- class Sound
6
- extend FFI::Library
18
+ extend FFI::Library
7
19
 
8
- typedef :ulong, :dword
9
- typedef :uintptr_t, :hwaveout
10
- typedef :uint, :mmresult
11
-
12
- ffi_lib :winmm
20
+ typedef :ulong, :dword
21
+ typedef :uintptr_t, :hwaveout
22
+ typedef :uint, :mmresult
13
23
 
14
- attach_function :waveOutOpen, [:pointer, :uint, :pointer, :dword, :dword, :dword], :mmresult
15
- attach_function :waveOutPrepareHeader, [:hwaveout, :pointer, :uint], :mmresult
16
- attach_function :waveOutWrite, [:hwaveout, :pointer, :uint], :mmresult
17
- attach_function :waveOutUnprepareHeader, [:hwaveout, :pointer, :uint], :mmresult
18
- attach_function :waveOutClose, [:hwaveout], :mmresult
19
- end
24
+ ffi_lib :winmm
25
+
26
+ attach_function :waveOutOpen, [:pointer, :uint, :pointer, :dword, :dword, :dword], :mmresult
27
+ attach_function :waveOutPrepareHeader, [:hwaveout, :pointer, :uint], :mmresult
28
+ attach_function :waveOutWrite, [:hwaveout, :pointer, :uint], :mmresult
29
+ attach_function :waveOutUnprepareHeader, [:hwaveout, :pointer, :uint], :mmresult
30
+ attach_function :waveOutClose, [:hwaveout], :mmresult
20
31
 
21
32
  WAVE_FORMAT_PCM = 1
33
+ WAVE_MAPPER = -1
34
+ DEFAULT_DEVICE_ID = WAVE_MAPPER
35
+
36
+ def open_device
37
+ waveOutOpen(handle.pointer, id, data.format.pointer, 0, 0, 0)
38
+ end
39
+
40
+ def prepare_buffer
41
+ waveOutPrepareHeader(handle.id, header.pointer, header.size)
42
+ end
43
+
44
+ def write_to_device
45
+ waveOutWrite(handle.id, header.pointer, header.size)
46
+ end
47
+
48
+ def unprepare_buffer
49
+ while waveOutUnprepareHeader(handle.id, header.pointer, header.size) == 33
50
+ sleep 0.001
51
+ end
52
+ end
53
+
54
+ def close_device
55
+ waveOutClose(handle.id)
56
+ end
57
+
58
+ def handle
59
+ Thread.current[:handle] ||= Handle.new
60
+ end
61
+
62
+ def data
63
+ Thread.current[:data]
64
+ end
65
+
66
+ def header
67
+ Thread.current[:header] ||= WAVEHDR.new(data_buffer, buffer_length)
68
+ end
69
+
70
+ def data_buffer
71
+ Thread.current[:data_buffer] ||= FFI::MemoryPointer.new(:int, data.pcm_data.size).write_array_of_int data.pcm_data
72
+ end
73
+
74
+ def buffer_length
75
+ Thread.current[:buffer_length] ||= data.format.avg_bps*data.duration/1000
76
+ end
22
77
 
23
78
  # Define an HWAVEOUT struct for use by all the waveOut functions.
24
79
  # It is a handle to a waveOut stream, so starting up multiple
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sound
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominic Muller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-10 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2014-08-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  description: Allows for effiecent cross-platform sound libraries in pure Ruby by tapping
14
42
  into native libraries.
15
43
  email: nicklink483@gmail.com
@@ -20,11 +48,11 @@ files:
20
48
  - examples/example.rb
21
49
  - lib/os/os.rb
22
50
  - lib/sound.rb
51
+ - lib/sound/alsa.rb
23
52
  - lib/sound/data.rb
24
53
  - lib/sound/device.rb
25
54
  - lib/sound/format.rb
26
- - lib/sound/linux/sound.rb
27
- - lib/sound/win32/sound.rb
55
+ - lib/sound/win32.rb
28
56
  homepage: https://github.com/RSMP/sound
29
57
  licenses:
30
58
  - MIT
@@ -1,25 +0,0 @@
1
- require 'ffi'
2
-
3
- module AlsaPCM
4
- class Sound
5
- extend FFI::Library
6
- ffi_lib 'asound'
7
- ffi_convention :stdcall
8
-
9
- attach_function :snd_pcm_open, [:pointer, :string, :int, :int], :int
10
- attach_function :snd_pcm_close, [:pointer], :int
11
- attach_function :snd_pcm_drain, [:pointer], :int
12
- attach_function :snd_pcm_prepare, [:pointer], :int
13
- attach_function :snd_pcm_writen, [:pointer, :pointer, :ulong], :long
14
- attach_function :snd_pcm_writei, [:pointer, :pointer, :ulong], :long
15
- attach_function :snd_pcm_hw_params_malloc, [:pointer], :int
16
- attach_function :snd_pcm_hw_params_any, [:pointer, :pointer], :int
17
- attach_function :snd_pcm_hw_params_set_access, [:pointer, :pointer, :int], :int
18
- attach_function :snd_pcm_hw_params_set_format, [:pointer, :pointer, :int], :int
19
- attach_function :snd_pcm_hw_params_set_rate, [:pointer, :pointer, :uint, :int], :int
20
- attach_function :snd_pcm_hw_params_set_channels, [:pointer, :pointer, :int], :int
21
- attach_function :snd_pcm_hw_params, [:pointer, :pointer], :int
22
- attach_function :snd_pcm_hw_params_free, [:pointer], :void
23
-
24
- end
25
- end