beeps 0.1.31 → 0.1.33

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.doc/ext/beeps/adsr.cpp +139 -0
  3. data/.doc/ext/beeps/analyser.cpp +128 -0
  4. data/.doc/ext/beeps/beeps.cpp +9 -1
  5. data/.doc/ext/beeps/file_in.cpp +55 -9
  6. data/.doc/ext/beeps/gain.cpp +64 -0
  7. data/.doc/ext/beeps/mic_in.cpp +83 -0
  8. data/.doc/ext/beeps/native.cpp +20 -8
  9. data/.doc/ext/beeps/oscillator.cpp +88 -0
  10. data/.doc/ext/beeps/pitch_shift.cpp +64 -0
  11. data/.doc/ext/beeps/processor.cpp +31 -2
  12. data/.doc/ext/beeps/sound.cpp +90 -7
  13. data/.doc/ext/beeps/sound_player.cpp +156 -0
  14. data/.doc/ext/beeps/time_stretch.cpp +64 -0
  15. data/.github/workflows/release-gem.yml +62 -0
  16. data/.github/workflows/test.yml +0 -6
  17. data/.github/workflows/utils.rb +13 -5
  18. data/ChangeLog.md +13 -0
  19. data/Rakefile +27 -6
  20. data/VERSION +1 -1
  21. data/beeps.gemspec +2 -2
  22. data/ext/beeps/adsr.cpp +150 -0
  23. data/ext/beeps/analyser.cpp +134 -0
  24. data/ext/beeps/beeps.cpp +10 -1
  25. data/ext/beeps/extconf.rb +1 -2
  26. data/ext/beeps/file_in.cpp +60 -9
  27. data/ext/beeps/gain.cpp +67 -0
  28. data/ext/beeps/mic_in.cpp +88 -0
  29. data/ext/beeps/native.cpp +20 -8
  30. data/ext/beeps/oscillator.cpp +93 -0
  31. data/ext/beeps/pitch_shift.cpp +67 -0
  32. data/ext/beeps/processor.cpp +34 -2
  33. data/ext/beeps/sound.cpp +99 -7
  34. data/ext/beeps/sound_player.cpp +169 -0
  35. data/ext/beeps/time_stretch.cpp +67 -0
  36. data/include/beeps/beeps.h +2 -1
  37. data/include/beeps/filter.h +179 -0
  38. data/include/beeps/generator.h +120 -0
  39. data/include/beeps/processor.h +37 -68
  40. data/include/beeps/ruby/filter.h +78 -0
  41. data/include/beeps/ruby/generator.h +60 -0
  42. data/include/beeps/ruby/processor.h +5 -45
  43. data/include/beeps/ruby/sound.h +14 -3
  44. data/include/beeps/signals.h +10 -4
  45. data/include/beeps/sound.h +67 -2
  46. data/lib/beeps/beeps.rb +6 -1
  47. data/lib/beeps/processor.rb +95 -15
  48. data/lib/beeps/sound.rb +29 -2
  49. data/src/adsr.cpp +245 -0
  50. data/src/analyser.cpp +254 -0
  51. data/src/beeps.cpp +11 -2
  52. data/src/file_in.cpp +94 -0
  53. data/src/gain.cpp +55 -0
  54. data/src/mic_in.cpp +262 -0
  55. data/src/mic_in.h +20 -0
  56. data/src/openal.cpp +2 -1
  57. data/src/oscillator.cpp +145 -0
  58. data/src/osx/signals.mm +83 -0
  59. data/src/pitch_shift.cpp +82 -0
  60. data/src/processor.cpp +202 -88
  61. data/src/processor.h +98 -0
  62. data/src/signals.cpp +326 -20
  63. data/src/signals.h +192 -2
  64. data/src/sound.cpp +735 -113
  65. data/src/sound.h +6 -1
  66. data/src/time_stretch.cpp +91 -0
  67. data/test/helper.rb +2 -1
  68. data/test/test_beeps.rb +10 -7
  69. data/test/test_beeps_init.rb +18 -0
  70. data/test/test_file_in.rb +15 -0
  71. data/test/test_processor.rb +50 -0
  72. data/test/test_sound.rb +87 -11
  73. data/test/test_sound_player.rb +134 -0
  74. metadata +55 -17
  75. data/.doc/ext/beeps/sawtooth_wave.cpp +0 -61
  76. data/.doc/ext/beeps/sine_wave.cpp +0 -61
  77. data/.doc/ext/beeps/square_wave.cpp +0 -61
  78. data/.github/workflows/release.yml +0 -34
  79. data/ext/beeps/sawtooth_wave.cpp +0 -64
  80. data/ext/beeps/sine_wave.cpp +0 -64
  81. data/ext/beeps/square_wave.cpp +0 -64
data/src/file_in.cpp ADDED
@@ -0,0 +1,94 @@
1
+ #include "beeps/generator.h"
2
+
3
+
4
+ #include "beeps/exception.h"
5
+ #include "signals.h"
6
+
7
+
8
+ namespace Beeps
9
+ {
10
+
11
+
12
+ struct FileIn::Data
13
+ {
14
+
15
+ String path;
16
+
17
+ Signals signals;
18
+
19
+ };// FileIn::Data
20
+
21
+
22
+ FileIn::FileIn (const char* path)
23
+ {
24
+ if (path) set_path(path);
25
+ }
26
+
27
+ FileIn::~FileIn ()
28
+ {
29
+ }
30
+
31
+ void
32
+ FileIn::reset ()
33
+ {
34
+ Super::reset();
35
+ }
36
+
37
+ void
38
+ FileIn::set_path (const char* path)
39
+ {
40
+ if (*this)
41
+ invalid_state_error(__FILE__, __LINE__, "path is already set");
42
+
43
+ self->signals = Signals_load(path);
44
+ self->path = path;
45
+
46
+ set_updated();
47
+ }
48
+
49
+ const char*
50
+ FileIn::path () const
51
+ {
52
+ return self->path;
53
+ }
54
+
55
+ double
56
+ FileIn::sample_rate () const
57
+ {
58
+ return self->signals.sample_rate();
59
+ }
60
+
61
+ uint
62
+ FileIn::nchannels () const
63
+ {
64
+ return self->signals.nchannels();
65
+ }
66
+
67
+ uint
68
+ FileIn::nsamples () const
69
+ {
70
+ return self->signals.nsamples();
71
+ }
72
+
73
+ float
74
+ FileIn::seconds () const
75
+ {
76
+ return Signals_get_seconds(self->signals);
77
+ }
78
+
79
+ void
80
+ FileIn::generate (Context* context, Signals* signals, uint* offset)
81
+ {
82
+ Super::generate(context, signals, offset);
83
+
84
+ *offset += Signals_copy(signals, self->signals, *offset);
85
+ }
86
+
87
+ FileIn::operator bool () const
88
+ {
89
+ if (!Super::operator bool()) return false;
90
+ return self->signals;
91
+ }
92
+
93
+
94
+ }// Beeps
data/src/gain.cpp ADDED
@@ -0,0 +1,55 @@
1
+ #include "beeps/filter.h"
2
+
3
+
4
+ #include "signals.h"
5
+
6
+
7
+ namespace Beeps
8
+ {
9
+
10
+
11
+ struct Gain::Data
12
+ {
13
+
14
+ float gain = 1;
15
+
16
+ };// Gain::Data
17
+
18
+
19
+ Gain::Gain (Processor* input)
20
+ : Super(input)
21
+ {
22
+ }
23
+
24
+ Gain::~Gain ()
25
+ {
26
+ }
27
+
28
+ void
29
+ Gain::set_gain (float gain)
30
+ {
31
+ self->gain = gain;
32
+
33
+ set_updated();
34
+ }
35
+
36
+ float
37
+ Gain::gain () const
38
+ {
39
+ return self->gain;
40
+ }
41
+
42
+ void
43
+ Gain::filter (Context* context, Signals* signals, uint* offset)
44
+ {
45
+ Super::filter(context, signals, offset);
46
+
47
+ Frames* frames = Signals_get_frames(signals);
48
+ if (!frames)
49
+ argument_error(__FILE__, __LINE__);
50
+
51
+ *frames *= self->gain;
52
+ }
53
+
54
+
55
+ }// Beeps
data/src/mic_in.cpp ADDED
@@ -0,0 +1,262 @@
1
+ #include "mic_in.h"
2
+
3
+
4
+ #include <limits.h>
5
+ #include <vector>
6
+ #include <memory>
7
+ #include <algorithm>
8
+ #include "beeps/beeps.h"
9
+ #include "beeps/exception.h"
10
+ #include "openal.h"
11
+ #include "signals.h"
12
+
13
+
14
+ namespace Beeps
15
+ {
16
+
17
+
18
+ struct Mic
19
+ {
20
+
21
+ void start ()
22
+ {
23
+ if (self->started) return;
24
+
25
+ self->create();
26
+ if (!is_valid())
27
+ invalid_state_error(__FILE__, __LINE__);
28
+
29
+ alcCaptureStart(self->device);
30
+ OpenAL_check_error(__FILE__, __LINE__);
31
+
32
+ self->started = true;
33
+ }
34
+
35
+ void stop ()
36
+ {
37
+ if (!self->started) return;
38
+
39
+ if (!is_valid())
40
+ invalid_state_error(__FILE__, __LINE__);
41
+
42
+ alcCaptureStop(self->device);
43
+ OpenAL_check_error(__FILE__, __LINE__);
44
+
45
+ self->started = false;
46
+ }
47
+
48
+ bool is_started () const
49
+ {
50
+ return self->started;
51
+ }
52
+
53
+ void buffer_samples ()
54
+ {
55
+ if (!is_valid())
56
+ invalid_state_error(__FILE__, __LINE__);
57
+
58
+ ALint nsamples = 0;
59
+ alcGetIntegerv(
60
+ self->device, ALC_CAPTURE_SAMPLES, sizeof(nsamples), &nsamples);
61
+ OpenAL_check_error(__FILE__, __LINE__);
62
+ if (nsamples <= 0) return;
63
+
64
+ auto& buffer = self->buffer;
65
+ if (buffer.size() < (size_t) nsamples) buffer.resize(nsamples);
66
+
67
+ alcCaptureSamples(self->device, (ALCvoid*) &buffer[0], nsamples);
68
+ OpenAL_check_error(__FILE__, __LINE__);
69
+
70
+ auto& samples = self->samples;
71
+ samples.reserve(samples.size() + nsamples);
72
+ samples.insert(samples.end(), &buffer[0], &buffer[0] + nsamples);
73
+
74
+ if (samples.size() > samples_size_max())
75
+ {
76
+ size_t size = samples.size() - samples_size_max();
77
+ samples.erase(samples.begin(), samples.begin() + size);
78
+ self->offset += (uint) size;
79
+ }
80
+ }
81
+
82
+ void get_signals (Signals* signals, uint* gen_offset)
83
+ {
84
+ assert(signals && signals->empty() && generate_offset);
85
+
86
+ if (!is_valid()) return;
87
+
88
+ Frames* frames = Signals_get_frames(signals);
89
+ assert(frames);
90
+
91
+ auto& samples = self->samples;
92
+ uint nchannels = self->nchannels;
93
+ uint nsamples = (uint) (samples.size() / nchannels);
94
+ uint offset = *gen_offset < self->offset ? 0 : *gen_offset - self->offset;
95
+ if (offset >= nsamples) return;
96
+
97
+ nsamples = std::min(frames->nframes(), nsamples - offset);
98
+ for (uint ch = 0; ch < frames->nchannels(); ++ch)
99
+ {
100
+ uint samples_ch = ch < nchannels ? ch : 0;
101
+ float div = 1.0f / SHRT_MAX;
102
+ for (uint i = 0; i < nsamples; ++i)
103
+ (*frames)(i, ch) = samples[(offset + i) * nchannels + samples_ch] * div;
104
+ }
105
+ Signals_set_nsamples(signals, nsamples);
106
+
107
+ *gen_offset = self->offset + offset + nsamples;
108
+ }
109
+
110
+ uint samples_size_max () const
111
+ {
112
+ assert(self->sample_rate > 0);
113
+
114
+ return self->sample_rate * 1;
115
+ }
116
+
117
+ bool is_valid () const
118
+ {
119
+ return self->sample_rate > 0 && self->nchannels > 0 && self->device;
120
+ }
121
+
122
+ struct Data
123
+ {
124
+
125
+ double sample_rate = 0;
126
+
127
+ uint nchannels = 0;
128
+
129
+ uint offset = 0;
130
+
131
+ bool started = false;
132
+
133
+ ALCdevice* device = NULL;
134
+
135
+ std::vector<ALshort> samples, buffer;
136
+
137
+ ~Data ()
138
+ {
139
+ clear();
140
+ }
141
+
142
+ void create ()
143
+ {
144
+ assert(sample_rate > 0 && nchannels > 0);
145
+
146
+ if (device) return;
147
+
148
+ device = alcCaptureOpenDevice(
149
+ NULL,
150
+ sample_rate,
151
+ nchannels == 2 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16,
152
+ sample_rate / 10);
153
+ if (!device)
154
+ openal_error(__FILE__, __LINE__, "failed to create a mic device");
155
+ }
156
+
157
+ void clear ()
158
+ {
159
+ if (!device) return;
160
+
161
+ if (alcCaptureCloseDevice(device) == ALC_FALSE)
162
+ openal_error(__FILE__, __LINE__, "failed to close the mic device");
163
+
164
+ device = NULL;
165
+ }
166
+
167
+ };// Data
168
+
169
+ Xot::PSharedImpl<Data> self;
170
+
171
+ };// Mic
172
+
173
+
174
+ struct MicIn::Data
175
+ {
176
+
177
+ Mic mic;
178
+
179
+ };// MicIn::Data
180
+
181
+
182
+ namespace global
183
+ {
184
+
185
+ static std::vector<Mic> mics;
186
+
187
+ };// global
188
+
189
+
190
+ void
191
+ MicIn_process_streams ()
192
+ {
193
+ for (Mic& mic : global::mics)
194
+ mic.buffer_samples();
195
+ }
196
+
197
+
198
+ MicIn::MicIn (uint nchannels, double sample_rate)
199
+ {
200
+ if (nchannels > 2)
201
+ argument_error(__FILE__, __LINE__);
202
+
203
+ if (sample_rate <= 0) sample_rate = Beeps::sample_rate();
204
+
205
+ self->mic.self->sample_rate = sample_rate;
206
+ self->mic.self->nchannels = nchannels;
207
+ }
208
+
209
+ MicIn::~MicIn ()
210
+ {
211
+ }
212
+
213
+ void
214
+ MicIn::start ()
215
+ {
216
+ if (self->mic.is_started()) return;
217
+
218
+ self->mic.start();
219
+
220
+ global::mics.emplace_back(self->mic);
221
+ }
222
+
223
+ void
224
+ MicIn::stop ()
225
+ {
226
+ if (!self->mic.is_started()) return;
227
+
228
+ std::erase_if(
229
+ global::mics,
230
+ [&](auto& mic) {return mic.self.get() == self->mic.self.get();});
231
+
232
+ self->mic.stop();
233
+ }
234
+
235
+ double
236
+ MicIn::sample_rate () const
237
+ {
238
+ return self->mic.self->sample_rate;
239
+ }
240
+
241
+ uint
242
+ MicIn::nchannels () const
243
+ {
244
+ return self->mic.self->nchannels;
245
+ }
246
+
247
+ void
248
+ MicIn::generate (Context* context, Signals* signals, uint* offset)
249
+ {
250
+ Super::generate(context, signals, offset);
251
+
252
+ self->mic.get_signals(signals, offset);
253
+ }
254
+
255
+ MicIn::operator bool () const
256
+ {
257
+ if (!Super::operator bool()) return false;
258
+ return self->mic.is_valid();
259
+ }
260
+
261
+
262
+ }// Beeps
data/src/mic_in.h ADDED
@@ -0,0 +1,20 @@
1
+ // -*- c++ -*-
2
+ #pragma once
3
+ #ifndef __BEEPS_SRC_MIC_IN_H__
4
+ #define __BEEPS_SRC_MIC_IN_H__
5
+
6
+
7
+ #include <beeps/generator.h>
8
+
9
+
10
+ namespace Beeps
11
+ {
12
+
13
+
14
+ void MicIn_process_streams ();
15
+
16
+
17
+ }// Beeps
18
+
19
+
20
+ #endif//EOH
data/src/openal.cpp CHANGED
@@ -23,7 +23,7 @@ namespace Beeps
23
23
  static void
24
24
  cleanup ()
25
25
  {
26
- Sound_cleanup_sources();
26
+ SoundPlayer_clear_all();
27
27
 
28
28
  alcMakeContextCurrent(NULL);
29
29
 
@@ -94,6 +94,7 @@ namespace Beeps
94
94
  case ALC_INVALID_DEVICE: return "ALC_INVALID_DEVICE";
95
95
  case ALC_INVALID_CONTEXT: return "ALC_INVALID_CONTEXT";
96
96
  case ALC_INVALID_ENUM: return "ALC_INVALID_ENUM";
97
+ case ALC_INVALID_VALUE: return "ALC_INVALID_VALUE";
97
98
  case ALC_OUT_OF_MEMORY: return "ALC_OUT_OF_MEMORY";
98
99
  default: return "UNKNOWN ERROR";
99
100
  }
@@ -0,0 +1,145 @@
1
+ #include "beeps/generator.h"
2
+
3
+
4
+ #include "SineWave.h"
5
+ #include "BlitSquare.h"
6
+ #include "BlitSaw.h"
7
+ #include "beeps/exception.h"
8
+ #include "signals.h"
9
+
10
+
11
+ namespace Beeps
12
+ {
13
+
14
+
15
+ struct Oscillator::Data
16
+ {
17
+
18
+ Type type = NONE;
19
+
20
+ float frequency = 440;
21
+
22
+ std::unique_ptr<stk::SineWave> sine;
23
+
24
+ std::unique_ptr<stk::BlitSquare> square;
25
+
26
+ std::unique_ptr<stk::BlitSaw> saw;
27
+
28
+ };// Oscillator::Data
29
+
30
+
31
+ Oscillator::Oscillator (Type type)
32
+ {
33
+ set_type(type);
34
+ }
35
+
36
+ Oscillator::~Oscillator ()
37
+ {
38
+ }
39
+
40
+ void
41
+ Oscillator::reset ()
42
+ {
43
+ Super::reset();
44
+
45
+ if (self->sine) self->sine->reset();
46
+ if (self->square) self->square->reset();
47
+ if (self->saw) self->saw->reset();
48
+ }
49
+
50
+ void
51
+ Oscillator::set_type (Type type)
52
+ {
53
+ if (type == self->type) return;
54
+
55
+ self->type = type;
56
+
57
+ self->sine .reset();
58
+ self->square.reset();
59
+ self->saw .reset();
60
+
61
+ switch (self->type)
62
+ {
63
+ case SINE: self->sine .reset(new stk::SineWave()); break;
64
+ case SQUARE: self->square.reset(new stk::BlitSquare()); break;
65
+ case SAWTOOTH: self->saw .reset(new stk::BlitSaw()); break;
66
+ default:
67
+ argument_error(
68
+ __FILE__, __LINE__, "unknown oscilator type '%d'", self->type);
69
+ break;
70
+ }
71
+
72
+ set_updated();
73
+ }
74
+
75
+ Oscillator::Type
76
+ Oscillator::type () const
77
+ {
78
+ return self->type;
79
+ }
80
+
81
+ void
82
+ Oscillator::set_frequency (float frequency)
83
+ {
84
+ if (frequency <= 0)
85
+ argument_error(__FILE__, __LINE__);
86
+
87
+ self->frequency = frequency;
88
+
89
+ set_updated();
90
+ }
91
+
92
+ float
93
+ Oscillator::frequency () const
94
+ {
95
+ return self->frequency;
96
+ }
97
+
98
+ void
99
+ Oscillator::generate (Context* context, Signals* signals, uint* offset)
100
+ {
101
+ Super::generate(context, signals, offset);
102
+
103
+ Frames* frames = Signals_get_frames(signals);
104
+ if (!frames)
105
+ argument_error(__FILE__, __LINE__);
106
+
107
+ switch (self->type)
108
+ {
109
+ case SINE:
110
+ assert(self->sine);
111
+ self->sine->setFrequency(self->frequency);
112
+ self->sine->tick(*frames);
113
+ break;
114
+
115
+ case SQUARE:
116
+ assert(self->square);
117
+ self->square->setFrequency(self->frequency);
118
+ self->square->tick(*frames);
119
+ break;
120
+
121
+ case SAWTOOTH:
122
+ assert(self->saw);
123
+ self->saw->setFrequency(self->frequency);
124
+ self->saw->tick(*frames);
125
+ break;
126
+
127
+ default:
128
+ invalid_state_error(
129
+ __FILE__, __LINE__, "unknown oscilator type '%d'", self->type);
130
+ break;
131
+ }
132
+
133
+ Signals_set_nsamples(signals, frames->nframes());
134
+ }
135
+
136
+ Oscillator::operator bool () const
137
+ {
138
+ if (!Super::operator bool()) return false;
139
+ return
140
+ self->type != NONE && self->frequency > 0 &&
141
+ (self->sine || self->square || self->saw);
142
+ }
143
+
144
+
145
+ }// Beeps
@@ -0,0 +1,83 @@
1
+ // -*- c++ -*-
2
+ #include "../signals.h"
3
+
4
+
5
+ #import <AVFoundation/AVFoundation.h>
6
+ #include <beeps/exception.h>
7
+
8
+
9
+ namespace Beeps
10
+ {
11
+
12
+
13
+ [[noreturn]]
14
+ void
15
+ objc_error (const char* file, int line, NSError* error, const char* format = NULL, ...)
16
+ {
17
+ if (!error)
18
+ argument_error(__FILE__, __LINE__);
19
+
20
+ XOT_STRINGF(format, s);
21
+ beeps_error(
22
+ file, line,
23
+ Xot::stringf("%s (%s)", s.c_str(), error.localizedDescription.UTF8String));
24
+ }
25
+
26
+
27
+ static AVAudioPCMBuffer*
28
+ load_buffer (const char* path)
29
+ {
30
+ if (!path)
31
+ argument_error(__FILE__, __LINE__);
32
+
33
+ NSURL* url = [NSURL fileURLWithPath: [NSString stringWithUTF8String:path]];
34
+ if (!url)
35
+ system_error(__FILE__, __LINE__, "failed to get url object for path");
36
+
37
+ NSError* error = nil;
38
+
39
+ AVAudioFile* file =
40
+ [[[AVAudioFile alloc] initForReading:url error:&error] autorelease];
41
+ if (error)
42
+ objc_error(__FILE__, __LINE__, error, "failed to open file '%s'", path);
43
+
44
+ AVAudioFormat* format = file.processingFormat;
45
+ if (format.channelCount <= 0)
46
+ beeps_error(__FILE__, __LINE__, "invalid channel count %d", format.channelCount);
47
+
48
+ AVAudioPCMBuffer* buffer = [[[AVAudioPCMBuffer alloc]
49
+ initWithPCMFormat:format frameCapacity:(AVAudioFrameCount) file.length]
50
+ autorelease];
51
+
52
+ BOOL result = [file readIntoBuffer:buffer error:&error];
53
+ if (error)
54
+ objc_error(__FILE__, __LINE__, error, "failed to read into the buffer");
55
+ if (!result)
56
+ system_error(__FILE__, __LINE__, "failed to read into the buffer");
57
+
58
+ return buffer;
59
+ }
60
+
61
+ Signals
62
+ Signals_load (const char* path)
63
+ {
64
+ AVAudioPCMBuffer* buffer = load_buffer(path);
65
+ if (!buffer) return Signals();
66
+
67
+ uint nchannels = buffer.format.channelCount;
68
+ if (nchannels <= 0)
69
+ beeps_error(__FILE__, __LINE__, "invalid nchannels %d", nchannels);
70
+
71
+ uint len = buffer.frameLength;
72
+ if (len <= 0)
73
+ beeps_error(__FILE__, __LINE__, "invalid buffer length %d", len);
74
+
75
+ const float* const* data = buffer.floatChannelData;
76
+ if (!data)
77
+ beeps_error(__FILE__, __LINE__, "failed to get channel data");
78
+
79
+ return Signals_create(data, len, nchannels, (uint) buffer.format.sampleRate);
80
+ }
81
+
82
+
83
+ }// Beeps