beeps 0.3.16 → 0.5.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.
- checksums.yaml +4 -4
- data/.doc/ext/beeps/analyser.cpp +4 -4
- data/.doc/ext/beeps/envelope.cpp +1 -1
- data/.doc/ext/beeps/file_in.cpp +1 -1
- data/.doc/ext/beeps/gain.cpp +1 -1
- data/.doc/ext/beeps/high_pass.cpp +1 -1
- data/.doc/ext/beeps/low_pass.cpp +1 -1
- data/.doc/ext/beeps/mic_in.cpp +1 -1
- data/.doc/ext/beeps/mixer.cpp +2 -2
- data/.doc/ext/beeps/oscillator.cpp +3 -3
- data/.doc/ext/beeps/pitch_shift.cpp +1 -1
- data/.doc/ext/beeps/processor.cpp +1 -1
- data/.doc/ext/beeps/reverb.cpp +1 -1
- data/.doc/ext/beeps/sequencer.cpp +2 -2
- data/.doc/ext/beeps/signals.cpp +3 -3
- data/.doc/ext/beeps/sound.cpp +16 -8
- data/.doc/ext/beeps/sound_player.cpp +13 -5
- data/.doc/ext/beeps/text_in.cpp +1 -1
- data/.doc/ext/beeps/time_stretch.cpp +1 -1
- data/.doc/ext/beeps/value.cpp +2 -2
- data/.github/workflows/release-gem.yml +2 -40
- data/.github/workflows/tag.yml +5 -28
- data/.github/workflows/test.yml +1 -28
- data/ChangeLog.md +28 -0
- data/Gemfile.lock +13 -8
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/beeps.gemspec +3 -3
- data/ext/beeps/analyser.cpp +4 -4
- data/ext/beeps/envelope.cpp +1 -1
- data/ext/beeps/file_in.cpp +1 -1
- data/ext/beeps/gain.cpp +1 -1
- data/ext/beeps/high_pass.cpp +1 -1
- data/ext/beeps/low_pass.cpp +1 -1
- data/ext/beeps/mic_in.cpp +1 -1
- data/ext/beeps/mixer.cpp +2 -2
- data/ext/beeps/oscillator.cpp +3 -3
- data/ext/beeps/pitch_shift.cpp +1 -1
- data/ext/beeps/processor.cpp +1 -1
- data/ext/beeps/reverb.cpp +1 -1
- data/ext/beeps/sequencer.cpp +2 -2
- data/ext/beeps/signals.cpp +3 -3
- data/ext/beeps/sound.cpp +17 -8
- data/ext/beeps/sound_player.cpp +14 -5
- data/ext/beeps/text_in.cpp +1 -1
- data/ext/beeps/time_stretch.cpp +1 -1
- data/ext/beeps/value.cpp +2 -2
- data/include/beeps/defs.h +14 -4
- data/include/beeps/signals.h +2 -2
- data/include/beeps/sound.h +9 -3
- data/lib/beeps/beeps.rb +4 -4
- data/lib/beeps/extension.rb +1 -1
- data/lib/beeps/sound.rb +2 -2
- data/src/file_in.cpp +2 -0
- data/src/mic_in.cpp +10 -9
- data/src/openal.cpp +66 -8
- data/src/openal.h +5 -1
- data/src/oscillator.cpp +1 -1
- data/src/signals.cpp +6 -6
- data/src/sound.cpp +382 -160
- data/src/win32/beeps.cpp +25 -2
- data/src/win32/signals.cpp +103 -29
- data/test/test_file_in.rb +38 -0
- data/test/test_sound.rb +6 -6
- data/test/test_sound_player.rb +86 -20
- metadata +7 -8
- data/.github/workflows/utils.rb +0 -127
data/src/win32/beeps.cpp
CHANGED
|
@@ -10,12 +10,27 @@ namespace Beeps
|
|
|
10
10
|
{
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
namespace global
|
|
14
|
+
{
|
|
15
|
+
|
|
16
|
+
static bool initialized = false;
|
|
17
|
+
|
|
18
|
+
static bool com_initialized = false;
|
|
19
|
+
|
|
20
|
+
}// global
|
|
21
|
+
|
|
22
|
+
|
|
13
23
|
void
|
|
14
24
|
Beeps_init ()
|
|
15
25
|
{
|
|
26
|
+
if (global::initialized)
|
|
27
|
+
beeps_error(__FILE__, __LINE__, "already initialized.");
|
|
28
|
+
global::initialized = true;
|
|
29
|
+
|
|
16
30
|
check_media_foundation_error(
|
|
17
|
-
CoInitializeEx(NULL,
|
|
31
|
+
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED),
|
|
18
32
|
__FILE__, __LINE__);
|
|
33
|
+
global::com_initialized = true;
|
|
19
34
|
|
|
20
35
|
check_media_foundation_error(
|
|
21
36
|
MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET),
|
|
@@ -25,11 +40,19 @@ namespace Beeps
|
|
|
25
40
|
void
|
|
26
41
|
Beeps_fin ()
|
|
27
42
|
{
|
|
43
|
+
if (!global::initialized)
|
|
44
|
+
beeps_error(__FILE__, __LINE__, "not initialized.");
|
|
45
|
+
global::initialized = false;
|
|
46
|
+
|
|
28
47
|
check_media_foundation_error(
|
|
29
48
|
MFShutdown(),
|
|
30
49
|
__FILE__, __LINE__);
|
|
31
50
|
|
|
32
|
-
|
|
51
|
+
if (global::com_initialized)
|
|
52
|
+
{
|
|
53
|
+
CoUninitialize();
|
|
54
|
+
global::com_initialized = false;
|
|
55
|
+
}
|
|
33
56
|
}
|
|
34
57
|
|
|
35
58
|
|
data/src/win32/signals.cpp
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#include "../signals.h"
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
#include <stdint.h>
|
|
4
5
|
#include <mfapi.h>
|
|
5
6
|
#include <mfidl.h>
|
|
6
7
|
#include <mfreadwrite.h>
|
|
7
8
|
#include <xot/noncopyable.h>
|
|
8
9
|
#include <xot/string.h>
|
|
9
10
|
#include <xot/windows.h>
|
|
11
|
+
#include <mmreg.h>// must be after windows.h
|
|
10
12
|
#include "exception.h"
|
|
11
13
|
|
|
12
14
|
|
|
@@ -55,41 +57,72 @@ namespace Beeps
|
|
|
55
57
|
|
|
56
58
|
static bool is_file_exist (const char* path)
|
|
57
59
|
{
|
|
58
|
-
|
|
60
|
+
if (!path) return false;
|
|
61
|
+
|
|
62
|
+
DWORD attribs = GetFileAttributesW(Xot::String(path).to_wstr().c_str());
|
|
59
63
|
return
|
|
60
64
|
attribs != INVALID_FILE_ATTRIBUTES &&
|
|
61
65
|
!(attribs & FILE_ATTRIBUTE_DIRECTORY);
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
static
|
|
65
|
-
|
|
68
|
+
static void
|
|
69
|
+
set_decoded_media_type (IMFMediaType* decoded, IMFSourceReader* source_reader)
|
|
70
|
+
{
|
|
71
|
+
ReleasePtr<IMFMediaType> native;
|
|
72
|
+
check_media_foundation_error(
|
|
73
|
+
source_reader->GetNativeMediaType(
|
|
74
|
+
MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, &native.ptr),
|
|
75
|
+
__FILE__, __LINE__);
|
|
76
|
+
|
|
77
|
+
GUID subtype = {};// all zeros, as GUID_NULL, which needs libuuid
|
|
78
|
+
UINT32 bits = 0;
|
|
79
|
+
native->GetGUID(MF_MT_SUBTYPE, &subtype);
|
|
80
|
+
native->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &bits);
|
|
81
|
+
|
|
82
|
+
bool is_float = subtype == MFAudioFormat_Float;
|
|
83
|
+
if (is_float)
|
|
84
|
+
bits = 32;
|
|
85
|
+
else if (subtype != MFAudioFormat_PCM || (bits != 8 && bits != 24 && bits != 32))
|
|
86
|
+
bits = 16;
|
|
87
|
+
|
|
88
|
+
decoded->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
|
|
89
|
+
decoded->SetGUID(MF_MT_SUBTYPE, is_float ? MFAudioFormat_Float : MFAudioFormat_PCM);
|
|
90
|
+
decoded->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bits);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static bool
|
|
94
|
+
is_float_format (const WAVEFORMATEX& format)
|
|
66
95
|
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
96
|
+
if (format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
|
|
97
|
+
return true;
|
|
98
|
+
if (format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
|
|
99
|
+
return false;
|
|
100
|
+
|
|
101
|
+
const auto& extensible = (const WAVEFORMATEXTENSIBLE&) format;
|
|
102
|
+
return extensible.SubFormat.Data1 == WAVE_FORMAT_IEEE_FLOAT;
|
|
71
103
|
}
|
|
72
104
|
|
|
73
105
|
static void
|
|
74
|
-
load_bytes (
|
|
106
|
+
load_bytes (
|
|
107
|
+
WAVEFORMATEX* format, bool* is_float, std::vector<BYTE>* bytes,
|
|
108
|
+
const char* path)
|
|
75
109
|
{
|
|
76
|
-
std::wstring wpath =
|
|
110
|
+
std::wstring wpath = Xot::String(path).to_wstr();
|
|
77
111
|
|
|
78
|
-
ReleasePtr<
|
|
112
|
+
ReleasePtr<IMFSourceReader> source_reader;
|
|
79
113
|
check_media_foundation_error(
|
|
80
|
-
|
|
114
|
+
MFCreateSourceReaderFromURL(wpath.c_str(), NULL, &source_reader.ptr),
|
|
81
115
|
__FILE__, __LINE__);
|
|
82
|
-
pcm_media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
|
|
83
|
-
pcm_media_type->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
|
|
84
116
|
|
|
85
|
-
ReleasePtr<
|
|
117
|
+
ReleasePtr<IMFMediaType> decoded_media_type;
|
|
86
118
|
check_media_foundation_error(
|
|
87
|
-
|
|
119
|
+
MFCreateMediaType(&decoded_media_type.ptr),
|
|
88
120
|
__FILE__, __LINE__);
|
|
121
|
+
set_decoded_media_type(decoded_media_type.ptr, source_reader.ptr);
|
|
89
122
|
|
|
90
123
|
check_media_foundation_error(
|
|
91
124
|
source_reader->SetCurrentMediaType(
|
|
92
|
-
MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL,
|
|
125
|
+
MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, decoded_media_type.ptr),
|
|
93
126
|
__FILE__, __LINE__);
|
|
94
127
|
|
|
95
128
|
ReleasePtr<IMFMediaType> media_type;
|
|
@@ -103,9 +136,15 @@ namespace Beeps
|
|
|
103
136
|
MFCreateWaveFormatExFromMFMediaType(media_type.ptr, &format_.ptr, NULL),
|
|
104
137
|
__FILE__, __LINE__);
|
|
105
138
|
|
|
106
|
-
*format
|
|
107
|
-
|
|
139
|
+
*format = *format_.ptr;
|
|
140
|
+
*is_float = is_float_format(*format_.ptr);
|
|
141
|
+
if (
|
|
142
|
+
format->wFormatTag != WAVE_FORMAT_PCM &&
|
|
143
|
+
format->wFormatTag != WAVE_FORMAT_IEEE_FLOAT &&
|
|
144
|
+
format->wFormatTag != WAVE_FORMAT_EXTENSIBLE)
|
|
145
|
+
{
|
|
108
146
|
beeps_error(__FILE__, __LINE__, "'%s' is not a PCM file.", path);
|
|
147
|
+
}
|
|
109
148
|
|
|
110
149
|
while (true)
|
|
111
150
|
{
|
|
@@ -130,7 +169,7 @@ namespace Beeps
|
|
|
130
169
|
__FILE__, __LINE__);
|
|
131
170
|
|
|
132
171
|
bytes->resize(bytes->size() + size);
|
|
133
|
-
memcpy(
|
|
172
|
+
memcpy(bytes->data() + bytes->size() - size, data, size);
|
|
134
173
|
|
|
135
174
|
check_media_foundation_error(
|
|
136
175
|
buffer->Unlock(),
|
|
@@ -145,35 +184,70 @@ namespace Beeps
|
|
|
145
184
|
beeps_error(__FILE__, __LINE__, "'%s' not found.", path);
|
|
146
185
|
|
|
147
186
|
WAVEFORMATEX format = {0};
|
|
187
|
+
bool is_float = false;
|
|
148
188
|
std::vector<BYTE> bytes;
|
|
149
|
-
load_bytes(&format, &bytes, path);
|
|
189
|
+
load_bytes(&format, &is_float, &bytes, path);
|
|
150
190
|
if (bytes.empty())
|
|
151
191
|
beeps_error(__FILE__, __LINE__, "failed to read bytes: '%s'", path);
|
|
152
192
|
|
|
153
193
|
uint Bps = format.wBitsPerSample / 8;
|
|
154
194
|
uint nchannels = format.nChannels;
|
|
155
|
-
|
|
195
|
+
if (is_float ? Bps != 4 : (Bps < 1 || 4 < Bps))
|
|
196
|
+
{
|
|
197
|
+
beeps_error(
|
|
198
|
+
__FILE__, __LINE__, "'%s' has samples of %d bits.",
|
|
199
|
+
path, format.wBitsPerSample);
|
|
200
|
+
}
|
|
201
|
+
if (nchannels == 0)
|
|
202
|
+
beeps_error(__FILE__, __LINE__, "'%s' has no channels.", path);
|
|
203
|
+
|
|
204
|
+
uint nsamples = bytes.size() / Bps / nchannels;
|
|
156
205
|
Signals signals(nsamples, nchannels, format.nSamplesPerSec);
|
|
157
206
|
|
|
207
|
+
uint step = nchannels * Bps;
|
|
158
208
|
for (uint ch = 0; ch < nchannels; ++ch)
|
|
159
209
|
{
|
|
210
|
+
Sample* to_p = Signals_at(&signals, 0, ch);
|
|
211
|
+
const uchar* from_p = ((uchar*) bytes.data()) + ch * Bps;
|
|
160
212
|
switch (Bps)
|
|
161
213
|
{
|
|
162
214
|
case 1:
|
|
163
215
|
{
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
for (uint i = 0; i < nsamples; ++i, to_p += nchannels, from_p += nchannels)
|
|
167
|
-
*to_p = (*to_p - 128) / 128.f;
|
|
216
|
+
for (uint i = 0; i < nsamples; ++i, to_p += nchannels, from_p += step)
|
|
217
|
+
*to_p = (*from_p - 128) / 128.f;
|
|
168
218
|
break;
|
|
169
219
|
}
|
|
170
220
|
|
|
171
221
|
case 2:
|
|
172
222
|
{
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
223
|
+
for (uint i = 0; i < nsamples; ++i, to_p += nchannels, from_p += step)
|
|
224
|
+
*to_p = *(const short*) from_p / 32768.f;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
case 3:
|
|
229
|
+
{
|
|
230
|
+
for (uint i = 0; i < nsamples; ++i, to_p += nchannels, from_p += step)
|
|
231
|
+
{
|
|
232
|
+
int32_t value = from_p[0] | (from_p[1] << 8) | (from_p[2] << 16);
|
|
233
|
+
if (value & 0x800000) value -= 0x1000000;// sign extension
|
|
234
|
+
*to_p = value / 8388608.f;
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
case 4:
|
|
240
|
+
{
|
|
241
|
+
if (is_float)
|
|
242
|
+
{
|
|
243
|
+
for (uint i = 0; i < nsamples; ++i, to_p += nchannels, from_p += step)
|
|
244
|
+
*to_p = *(const float*) from_p;
|
|
245
|
+
}
|
|
246
|
+
else
|
|
247
|
+
{
|
|
248
|
+
for (uint i = 0; i < nsamples; ++i, to_p += nchannels, from_p += step)
|
|
249
|
+
*to_p = *(const int32_t*) from_p / 2147483648.f;
|
|
250
|
+
}
|
|
177
251
|
break;
|
|
178
252
|
}
|
|
179
253
|
}
|
data/test/test_file_in.rb
CHANGED
|
@@ -14,6 +14,22 @@ class TestFileIn < Test::Unit::TestCase
|
|
|
14
14
|
B::Oscillator.new(...)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
def wav(tag, bits, rate, data)
|
|
18
|
+
align = bits / 8
|
|
19
|
+
fmt = [tag, 1, rate, rate * align, align, bits].pack('vvVVvv')
|
|
20
|
+
['RIFF', 36 + data.bytesize, 'WAVE', 'fmt ', 16].pack('a4Va4a4V') + fmt +
|
|
21
|
+
['data', data.bytesize].pack('a4V') + data
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def save_wav(filename, *wav_args, &block)
|
|
25
|
+
Tempfile.create ["#{filename}", '.wav'] do |file|
|
|
26
|
+
file.binmode
|
|
27
|
+
file.write wav(*wav_args)
|
|
28
|
+
file.close
|
|
29
|
+
block.call file.path
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
17
33
|
def test_load_file()
|
|
18
34
|
Tempfile.create 'tmp.wav' do |file|
|
|
19
35
|
file.close
|
|
@@ -26,6 +42,28 @@ class TestFileIn < Test::Unit::TestCase
|
|
|
26
42
|
end
|
|
27
43
|
end
|
|
28
44
|
|
|
45
|
+
def test_load_formats()
|
|
46
|
+
rate = B.sample_rate
|
|
47
|
+
values = (0...rate / 10).map {Math.sin(2 * Math::PI * _1 / 100) * 0.5}
|
|
48
|
+
{
|
|
49
|
+
pcm8: [1, 8, 0.01, values.map {(_1 * 128 + 128).round}.pack('C*')],
|
|
50
|
+
pcm16: [1, 16, 0.0001, values.map {(_1 * 32768).round}.pack('s<*')],
|
|
51
|
+
pcm24: [1, 24, 0.0001, values.map {(_1 * 8388608).round}
|
|
52
|
+
.flat_map {[_1, _1 >> 8, _1 >> 16]}
|
|
53
|
+
.map {_1 & 0xff}.pack('C*')],
|
|
54
|
+
pcm32: [1, 32, 0.0001, values.map {(_1 * 2147483648).round}.pack('l<*')],
|
|
55
|
+
float32: [3, 32, 0.0001, values.pack('e*')]
|
|
56
|
+
}.each do |name, (tag, bits, delta, data)|
|
|
57
|
+
save_wav name, tag, bits, rate, data do |path|
|
|
58
|
+
samples = B::Processor
|
|
59
|
+
.get_signals(filein(path), seconds: values.size.to_f / rate)
|
|
60
|
+
.to_a
|
|
61
|
+
assert_equal values.size, samples.size, name
|
|
62
|
+
assert_each_in_delta values, samples, delta, name
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
29
67
|
def test_file_not_found()
|
|
30
68
|
assert_raise(B::BeepsError) {B::FileIn.new 'nofile.wav'}
|
|
31
69
|
end
|
data/test/test_sound.rb
CHANGED
|
@@ -38,17 +38,17 @@ class TestSound < Test::Unit::TestCase
|
|
|
38
38
|
assert_nothing_raised {B::Sound.load PATH}
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
def test_nchannels()
|
|
42
|
+
assert_equal 1, sound .nchannels
|
|
43
|
+
assert_equal 2, sound(nchannels: 2).nchannels
|
|
44
|
+
end
|
|
45
|
+
|
|
41
46
|
def test_sample_rate()
|
|
42
47
|
assert_in_epsilon 44100, sound .sample_rate
|
|
43
48
|
assert_in_epsilon 48000, sound(sample_rate: 48000).sample_rate
|
|
44
49
|
assert_in_epsilon 96000, sound(sample_rate: 96000).sample_rate
|
|
45
50
|
end
|
|
46
51
|
|
|
47
|
-
def test_nchannels()
|
|
48
|
-
assert_equal 1, sound .nchannels
|
|
49
|
-
assert_equal 2, sound(nchannels: 2).nchannels
|
|
50
|
-
end
|
|
51
|
-
|
|
52
52
|
def test_seconds()
|
|
53
53
|
assert_in_epsilon 0.2, sound(0.2).seconds
|
|
54
54
|
assert_in_epsilon (-1), sound(0) .seconds
|
|
@@ -90,8 +90,8 @@ class TestSound < Test::Unit::TestCase
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
s = B::Sound.load PATH
|
|
93
|
-
assert_in_epsilon 96000, s.sample_rate
|
|
94
93
|
assert_equal 2, s.nchannels
|
|
94
|
+
assert_in_epsilon 96000, s.sample_rate
|
|
95
95
|
assert_in_epsilon 0.1, s.seconds
|
|
96
96
|
assert_nothing_raised {s.play}
|
|
97
97
|
end
|
data/test/test_sound_player.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'tempfile'
|
|
1
2
|
require_relative 'helper'
|
|
2
3
|
|
|
3
4
|
|
|
@@ -5,23 +6,20 @@ class TestSoundPlayer < Test::Unit::TestCase
|
|
|
5
6
|
|
|
6
7
|
B = Beeps
|
|
7
8
|
|
|
8
|
-
PATH = 'test.wav'
|
|
9
|
-
|
|
10
9
|
def sound(seconds = 0.1, processor: B::Oscillator.new, **kwargs)
|
|
11
10
|
B::Sound.new processor >> B::Gain.new(gain: 0), seconds, **kwargs
|
|
12
11
|
end
|
|
13
12
|
|
|
14
|
-
def stream_sound(**kwargs)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
def stream_sound(seconds = 0.1, **kwargs)
|
|
14
|
+
Tempfile.create ['stream', '.wav'] do |file|
|
|
15
|
+
file.close
|
|
16
|
+
sound(seconds).save file.path
|
|
17
|
+
sound 0, processor: B::FileIn.new(file.path), **kwargs
|
|
18
|
+
end
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
def teardown()
|
|
23
22
|
B::SoundPlayer.stop_all
|
|
24
|
-
File.delete PATH if File.exist?(PATH)
|
|
25
23
|
end
|
|
26
24
|
|
|
27
25
|
def test_initialize()
|
|
@@ -66,7 +64,7 @@ class TestSoundPlayer < Test::Unit::TestCase
|
|
|
66
64
|
sleep sec * 2
|
|
67
65
|
assert_equal :stopped, p.state
|
|
68
66
|
|
|
69
|
-
s = stream_sound
|
|
67
|
+
s = stream_sound sec
|
|
70
68
|
p = s.play
|
|
71
69
|
assert_equal :playing, p.state
|
|
72
70
|
sleep sec * 2
|
|
@@ -87,6 +85,18 @@ class TestSoundPlayer < Test::Unit::TestCase
|
|
|
87
85
|
assert_equal :playing, p.state
|
|
88
86
|
end
|
|
89
87
|
|
|
88
|
+
def test_play_after_stop()
|
|
89
|
+
p = sound.play
|
|
90
|
+
p.stop
|
|
91
|
+
p.play
|
|
92
|
+
assert_equal :playing, p.state
|
|
93
|
+
|
|
94
|
+
p = stream_sound.play
|
|
95
|
+
p.stop
|
|
96
|
+
p.play
|
|
97
|
+
assert_equal :playing, p.state
|
|
98
|
+
end
|
|
99
|
+
|
|
90
100
|
def test_stop_after_pause()
|
|
91
101
|
p = sound.play
|
|
92
102
|
p.pause
|
|
@@ -102,6 +112,8 @@ class TestSoundPlayer < Test::Unit::TestCase
|
|
|
102
112
|
end
|
|
103
113
|
|
|
104
114
|
def test_state()
|
|
115
|
+
assert_equal :none, B::SoundPlayer.new.state
|
|
116
|
+
|
|
105
117
|
p = sound.play
|
|
106
118
|
assert_equal :playing, p.state
|
|
107
119
|
assert_equal [true, false, false], [p.playing?, p.paused?, p.stopped?]
|
|
@@ -155,32 +167,67 @@ class TestSoundPlayer < Test::Unit::TestCase
|
|
|
155
167
|
assert_equal 200, p.position
|
|
156
168
|
end
|
|
157
169
|
|
|
170
|
+
def test_play_from_position_after_stop()
|
|
171
|
+
p = sound(1).play
|
|
172
|
+
p.stop
|
|
173
|
+
p.position = 22050
|
|
174
|
+
assert_equal 22050, p.position
|
|
175
|
+
p.play
|
|
176
|
+
sleep 0.05
|
|
177
|
+
assert_equal :playing, p.state
|
|
178
|
+
assert_operator p.position, :>=, 22050
|
|
179
|
+
|
|
180
|
+
p = stream_sound(1).play
|
|
181
|
+
p.stop
|
|
182
|
+
p.position = 1000
|
|
183
|
+
assert_equal 1000, p.position
|
|
184
|
+
p.play
|
|
185
|
+
sleep 0.05
|
|
186
|
+
assert_equal :playing, p.state
|
|
187
|
+
assert_operator p.position, :>=, 1000
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def test_position_over_length()
|
|
191
|
+
p = sound.play
|
|
192
|
+
assert_raise(ArgumentError) {p.position = 100_000}
|
|
193
|
+
p.stop
|
|
194
|
+
assert_raise(ArgumentError) {p.position = 100_000}
|
|
195
|
+
p.play
|
|
196
|
+
assert_equal :playing, p.state
|
|
197
|
+
|
|
198
|
+
p = stream_sound.play
|
|
199
|
+
p.stop
|
|
200
|
+
p.position = 100_000
|
|
201
|
+
p.play
|
|
202
|
+
assert_equal :stopped, p.state
|
|
203
|
+
end
|
|
204
|
+
|
|
158
205
|
def test_time()
|
|
159
206
|
p = sound.play
|
|
160
207
|
p.pause
|
|
161
|
-
assert_in_epsilon 0,
|
|
208
|
+
assert_in_epsilon 0, p.time
|
|
162
209
|
|
|
163
|
-
p.time = 0.
|
|
210
|
+
p.time = 0.025
|
|
164
211
|
sleep 0.05
|
|
165
|
-
assert_in_epsilon 0.
|
|
212
|
+
assert_in_epsilon 0.025, p.time
|
|
166
213
|
|
|
167
|
-
p.time 0.
|
|
214
|
+
p.time 0.05
|
|
168
215
|
sleep 0.05
|
|
169
|
-
assert_in_epsilon 0.
|
|
216
|
+
assert_in_epsilon 0.05, p.time
|
|
170
217
|
end
|
|
171
218
|
|
|
172
219
|
def test_time_stream()
|
|
173
220
|
p = stream_sound.play
|
|
174
221
|
p.pause
|
|
175
|
-
assert_in_epsilon 0,
|
|
222
|
+
assert_in_epsilon 0, p.time
|
|
176
223
|
|
|
177
|
-
p.time = 0.
|
|
224
|
+
p.time = 0.025
|
|
178
225
|
sleep 0.05
|
|
179
|
-
assert_in_epsilon 0.
|
|
226
|
+
assert_in_epsilon 0.025, p.time
|
|
180
227
|
|
|
181
|
-
p.time 0.
|
|
228
|
+
p.time 0.05
|
|
182
229
|
sleep 0.05
|
|
183
|
-
assert_in_epsilon 0.
|
|
230
|
+
assert_in_epsilon 0.05, p.time
|
|
184
231
|
end
|
|
185
232
|
|
|
186
233
|
def test_time_scale()
|
|
@@ -228,4 +275,23 @@ class TestSoundPlayer < Test::Unit::TestCase
|
|
|
228
275
|
assert_true players.all? {|p| p.stopped?}
|
|
229
276
|
end
|
|
230
277
|
|
|
278
|
+
def test_players_over_limit()
|
|
279
|
+
s = sound 1
|
|
280
|
+
|
|
281
|
+
paused = s.play
|
|
282
|
+
paused.pause
|
|
283
|
+
paused.time = 0.5
|
|
284
|
+
|
|
285
|
+
players = 300.times.map {s.play}
|
|
286
|
+
assert_true players.none? {|p| p.state == :none}
|
|
287
|
+
assert_equal :paused, paused.state
|
|
288
|
+
assert_in_epsilon 0.5, paused.time
|
|
289
|
+
|
|
290
|
+
paused.play
|
|
291
|
+
assert_equal :playing, paused.state
|
|
292
|
+
|
|
293
|
+
players.first.play
|
|
294
|
+
assert_equal :playing, players.first.state
|
|
295
|
+
end
|
|
296
|
+
|
|
231
297
|
end# TestSoundPlayer
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: beeps
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- xordog
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: xot
|
|
@@ -16,28 +16,28 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 0.
|
|
19
|
+
version: 0.5.0
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.
|
|
26
|
+
version: 0.5.0
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: rucy
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 0.
|
|
33
|
+
version: 0.5.0
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 0.
|
|
40
|
+
version: 0.5.0
|
|
41
41
|
description: Synthesize and play beep sounds.
|
|
42
42
|
email: xordog@gmail.com
|
|
43
43
|
executables: []
|
|
@@ -93,7 +93,6 @@ files:
|
|
|
93
93
|
- ".github/workflows/release-gem.yml"
|
|
94
94
|
- ".github/workflows/tag.yml"
|
|
95
95
|
- ".github/workflows/test.yml"
|
|
96
|
-
- ".github/workflows/utils.rb"
|
|
97
96
|
- CLAUDE.md
|
|
98
97
|
- CONTRIBUTING.md
|
|
99
98
|
- ChangeLog.md
|
|
@@ -233,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
233
232
|
- !ruby/object:Gem::Version
|
|
234
233
|
version: '0'
|
|
235
234
|
requirements: []
|
|
236
|
-
rubygems_version: 3.
|
|
235
|
+
rubygems_version: 3.5.22
|
|
237
236
|
signing_key:
|
|
238
237
|
specification_version: 4
|
|
239
238
|
summary: Plays beep sound.
|