ruby_dsp 0.0.5 → 0.0.6
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/README.md +61 -20
- data/ext/ruby_dsp/ruby_dsp.cpp +107 -30
- data/lib/ruby_dsp/version.rb +1 -1
- data/ruby_dsp.gemspec +2 -2
- data/stubs/ruby_dsp/audio_track.rb +31 -14
- metadata +6 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c8994eadc28b1923b14a4979f435f18973a83afb09e73d77f709b2f4cef81b8
|
|
4
|
+
data.tar.gz: 1516af206f7b1010f871729f00fd5427b522baacbef2fc516090dc0759f4e1b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f34843f736e5454ed4130d98f2cce6ff62dac04d250291222ce31201f4a4f9f986bfdeb413d1c2c89fab55dc1dceb094dd6b062bb7e1b978c020698f2f0d0f10
|
|
7
|
+
data.tar.gz: f801c956425c34f6092d40cb3c820c22025aac7aa7a5d9916efd6f8048cccee42a04cebd4b808db7b27940a7360430369ad194dba1f765f313c0ac8aa30a9e6f
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# RubyDSP | [Documentation](https://www.rubydoc.info/gems/ruby_dsp/0.0.
|
|
1
|
+
# RubyDSP | [Documentation](https://www.rubydoc.info/gems/ruby_dsp/0.0.6)
|
|
2
2
|
|
|
3
3
|
[](https://github.com/cichrrad/rubyDSP/actions/workflows/test.yml)
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
**RubyDSP** is an audio processing and DSP Ruby gem
|
|
11
|
+
**RubyDSP** is an audio processing/synthesis and DSP Ruby gem made mostly for fun. It uses C++ under the hood, utilizing [miniaudio](https://miniaud.io/) and [Rice](https://github.com/jasonroelofs/rice).
|
|
12
12
|
|
|
13
13
|
I made this gem to try [Rice](https://github.com/jasonroelofs/rice), as I would like to be able to bring some C++ speed to Ruby, oh my beloved....
|
|
14
14
|
|
|
@@ -16,7 +16,12 @@ I made this gem to try [Rice](https://github.com/jasonroelofs/rice), as I would
|
|
|
16
16
|
|
|
17
17
|
* **Fast:** Basically all of the code is written in C++. While not extremely optimized currently, it still absolutely shreds native Ruby.
|
|
18
18
|
|
|
19
|
+
* **Fluent API:** Mutating methods return `self`, allowing for beautiful, highly readable method chaining (e.g., `track.to_mono!.normalize!.fade_in!(0.5)`).
|
|
20
|
+
|
|
21
|
+
* **Audio Synthesis & Sequencing:** Build multitrack, polyphonic audio from scratch using a (very limited) set of built-in mathematically generated waveforms (sine, square, sawtooth, and white noise).
|
|
22
|
+
|
|
19
23
|
* **Format Agnostic Loading:** Automatically decodes standard audio formats (WAV, MP3, FLAC) via `miniaudio`.
|
|
24
|
+
|
|
20
25
|
> **Note:** While the loading of these formats is supported, `miniaudio` encodes only in `.wav`. While other encodings might be considered in the future, they would require more dependencies and thus are not available right now.
|
|
21
26
|
|
|
22
27
|
* **Zero-Dependency Native Build:** No need to install `ffmpeg` or `libsndfile` on your system.
|
|
@@ -29,24 +34,28 @@ Add this line to your application's `Gemfile`:
|
|
|
29
34
|
|
|
30
35
|
```ruby
|
|
31
36
|
gem 'ruby_dsp'
|
|
37
|
+
|
|
32
38
|
```
|
|
33
39
|
|
|
34
40
|
And then execute:
|
|
35
41
|
|
|
36
42
|
```bash
|
|
37
43
|
$ bundle install
|
|
44
|
+
|
|
38
45
|
```
|
|
39
46
|
|
|
40
47
|
Or install it yourself directly via:
|
|
41
48
|
|
|
42
49
|
```bash
|
|
43
50
|
$ gem install ruby_dsp
|
|
51
|
+
|
|
44
52
|
```
|
|
53
|
+
|
|
45
54
|
*(Note: Installing this gem requires a modern C++ compiler, as it builds the native extensions directly on your machine upon installation. It requires Ruby 3.0+).*
|
|
46
55
|
|
|
47
|
-
## Quick Start
|
|
56
|
+
## Quick Start: Audio Processing
|
|
48
57
|
|
|
49
|
-
Here is a quick look at what you can do with a loaded `AudioTrack
|
|
58
|
+
Here is a quick look at what you can do with a loaded `AudioTrack`. Thanks to the fluent API, you can process audio in a single readable chain:
|
|
50
59
|
|
|
51
60
|
```ruby
|
|
52
61
|
require 'ruby_dsp'
|
|
@@ -57,32 +66,63 @@ track = RubyDSP::AudioTrack.new("raw_vocals.wav")
|
|
|
57
66
|
puts track
|
|
58
67
|
# => ['raw_vocals.wav', 12.450s duration, 2 channel(s), 48000Hz sample rate]
|
|
59
68
|
|
|
60
|
-
#
|
|
61
|
-
track.to_mono!
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# Analysis & Math
|
|
69
|
+
# Process, edit, and save in one chain
|
|
70
|
+
track.to_mono! # Averages channels into mono
|
|
71
|
+
.resample!(44100) # Linearly resamples to target rate
|
|
72
|
+
.trim_silence!(-60.0) # Strips leading/trailing silence below -60dB
|
|
73
|
+
.normalize!(-1.0) # Scales audio to target peak dBFS
|
|
74
|
+
.pad_to_duration!(15.0) # Centers audio evenly into a 15s window
|
|
75
|
+
.fade_in!(0.5) # Adds a 0.5s linear fade-in
|
|
76
|
+
.fade_out!(0.5) # Adds a 0.5s linear fade-out
|
|
77
|
+
.save_track("processed.wav") # Export the final result
|
|
78
|
+
|
|
79
|
+
# Analysis & Math (Still works!)
|
|
73
80
|
puts "Peak Amp: #{track.peak_amp}"
|
|
74
81
|
puts "Overall RMS: #{track.rms}"
|
|
75
82
|
puts "Overall ZCR: #{track.zcr}"
|
|
76
83
|
|
|
77
84
|
# You can also get framed analysis for time-series data:
|
|
78
|
-
# framed_rms_data = track.framed_rms(2048, 512) also works
|
|
79
85
|
framed_rms_data = track.framed_rms(frame_length: 2048, hop_length: 512)
|
|
80
86
|
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Quick Start: Synthesis & Sequencing
|
|
90
|
+
|
|
91
|
+
Initialize an empty track and generate your own jam using `add_wave!`:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
require 'ruby_dsp'
|
|
95
|
+
|
|
96
|
+
# Create a blank canvas (Mono, 44.1kHz)
|
|
97
|
+
track = RubyDSP::AudioTrack.new("", 1, 44100)
|
|
98
|
+
|
|
99
|
+
track.add_wave!("sine", 261.63, 3.0, 0.0)
|
|
100
|
+
.add_wave!("sine", 329.63, 3.0, 1.0)
|
|
101
|
+
.add_wave!("sine", 392.00, 3.0, 2.0)
|
|
102
|
+
|
|
103
|
+
# Polish and export
|
|
104
|
+
track.normalize!(-30.0) # This goes a long way for your hearing
|
|
105
|
+
.fade_out!(0.5) # Smooth tail
|
|
106
|
+
.save_track("c_major_chord.wav")
|
|
81
107
|
|
|
82
|
-
# Save the results
|
|
83
|
-
track.save_track("processed_vocals.wav")
|
|
84
108
|
```
|
|
85
109
|
|
|
110
|
+
### Demos
|
|
111
|
+
|
|
112
|
+
> You can find scripts for these in `/demo` directory. Outputs were converted from `.wav` to `.mp4` to trick Github to show it in this README.
|
|
113
|
+
|
|
114
|
+
#### **TWINKLE**
|
|
115
|
+
|
|
116
|
+
* <video src="./demo/twinkle_twinkle.mp4" controls title="Twinkle"></video>
|
|
117
|
+
|
|
118
|
+
#### **TETRIS**
|
|
119
|
+
|
|
120
|
+
* <video src="./demo/tetris_theme.mp4" controls title="Tetris"></video>
|
|
121
|
+
|
|
122
|
+
#### **SUPER MARIO BROS**
|
|
123
|
+
|
|
124
|
+
* <video src="./demo/mario_intro.mp4" controls title="SMB"></video>
|
|
125
|
+
|
|
86
126
|
## Development
|
|
87
127
|
|
|
88
128
|
If you want to clone the repo and work on C++ guts, start with:
|
|
@@ -96,4 +136,5 @@ If you want to clone the repo and work on C++ guts, start with:
|
|
|
96
136
|
The gem is available as open source under the terms of the **MIT License**.
|
|
97
137
|
|
|
98
138
|
---
|
|
139
|
+
|
|
99
140
|
> Cheers! - RC
|
data/ext/ruby_dsp/ruby_dsp.cpp
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#include <sstream>
|
|
8
8
|
#include <iomanip>
|
|
9
9
|
#include <algorithm>
|
|
10
|
+
#include <cstdlib>
|
|
10
11
|
|
|
11
12
|
#define MINIAUDIO_IMPLEMENTATION
|
|
12
13
|
#include "vendor/miniaudio.h"
|
|
@@ -35,6 +36,17 @@ struct AudioTrack
|
|
|
35
36
|
|
|
36
37
|
AudioTrack(std::string f, unsigned int target_channels = 0, unsigned int target_sample_rate = 0) : filename(f)
|
|
37
38
|
{
|
|
39
|
+
|
|
40
|
+
// empty constructor
|
|
41
|
+
if (filename.empty())
|
|
42
|
+
{
|
|
43
|
+
sample_rate = target_sample_rate > 0 ? target_sample_rate : 44100;
|
|
44
|
+
channels = target_channels > 0 ? target_channels : 1;
|
|
45
|
+
is_mono = (channels == 1);
|
|
46
|
+
sample_count = 0;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
ma_decoder decoder;
|
|
39
51
|
ma_result result;
|
|
40
52
|
|
|
@@ -163,11 +175,11 @@ struct AudioTrack
|
|
|
163
175
|
return max_val;
|
|
164
176
|
}
|
|
165
177
|
|
|
166
|
-
|
|
178
|
+
AudioTrack &to_mono_bang()
|
|
167
179
|
{
|
|
168
180
|
if (is_mono)
|
|
169
181
|
{
|
|
170
|
-
return
|
|
182
|
+
return *this; // no-op
|
|
171
183
|
}
|
|
172
184
|
|
|
173
185
|
if (channels < 1)
|
|
@@ -196,14 +208,14 @@ struct AudioTrack
|
|
|
196
208
|
channels = 1;
|
|
197
209
|
is_mono = true;
|
|
198
210
|
sample_count = samples.size();
|
|
199
|
-
return
|
|
211
|
+
return *this;
|
|
200
212
|
}
|
|
201
213
|
|
|
202
|
-
|
|
214
|
+
AudioTrack &resample_bang(unsigned int target_rate = 0)
|
|
203
215
|
{
|
|
204
216
|
if (target_rate == 0 || target_rate == sample_rate)
|
|
205
217
|
{
|
|
206
|
-
return
|
|
218
|
+
return *this; // no-op
|
|
207
219
|
}
|
|
208
220
|
|
|
209
221
|
// TODO: add better, linear will have to do for now
|
|
@@ -252,7 +264,7 @@ struct AudioTrack
|
|
|
252
264
|
sample_rate = target_rate;
|
|
253
265
|
sample_count = samples.size();
|
|
254
266
|
|
|
255
|
-
return
|
|
267
|
+
return *this;
|
|
256
268
|
}
|
|
257
269
|
|
|
258
270
|
std::vector<float> rms()
|
|
@@ -532,10 +544,10 @@ struct AudioTrack
|
|
|
532
544
|
return {start_sample, end_sample};
|
|
533
545
|
}
|
|
534
546
|
|
|
535
|
-
|
|
547
|
+
AudioTrack &trim_silence_bang(float threshold_db = -60.0f, unsigned int frame_length = 2048, unsigned int hop_length = 512)
|
|
536
548
|
{
|
|
537
549
|
if (samples.empty())
|
|
538
|
-
return
|
|
550
|
+
return *this;
|
|
539
551
|
|
|
540
552
|
std::vector<unsigned long long> bounds = silence_bounds(threshold_db, frame_length, hop_length);
|
|
541
553
|
unsigned long long start_sample = bounds[0];
|
|
@@ -545,14 +557,14 @@ struct AudioTrack
|
|
|
545
557
|
|
|
546
558
|
// No-op checks
|
|
547
559
|
if (start_sample == 0 && end_sample >= per_channel_samples)
|
|
548
|
-
return
|
|
560
|
+
return *this;
|
|
549
561
|
|
|
550
562
|
// If the file is entirely silent, clear everything
|
|
551
563
|
if (start_sample == 0 && end_sample == 0)
|
|
552
564
|
{
|
|
553
565
|
samples.clear();
|
|
554
566
|
sample_count = 0;
|
|
555
|
-
return
|
|
567
|
+
return *this;
|
|
556
568
|
}
|
|
557
569
|
|
|
558
570
|
// Slice the interleaved sample array
|
|
@@ -563,17 +575,17 @@ struct AudioTrack
|
|
|
563
575
|
samples = std::move(trimmed_samples);
|
|
564
576
|
sample_count = samples.size();
|
|
565
577
|
|
|
566
|
-
return
|
|
578
|
+
return *this;
|
|
567
579
|
}
|
|
568
580
|
|
|
569
|
-
|
|
581
|
+
AudioTrack &normalize_bang(float target_db = -1.0f)
|
|
570
582
|
{
|
|
571
583
|
if (samples.empty())
|
|
572
|
-
return
|
|
584
|
+
return *this;
|
|
573
585
|
|
|
574
586
|
float current_peak = peak_amplitude();
|
|
575
587
|
if (current_peak <= 0.0f)
|
|
576
|
-
return
|
|
588
|
+
return *this; // silent track, nothing to scale
|
|
577
589
|
|
|
578
590
|
// convert target dB to a linear multiplier
|
|
579
591
|
float target_linear = std::pow(10.0f, target_db / 20.0f);
|
|
@@ -581,20 +593,20 @@ struct AudioTrack
|
|
|
581
593
|
|
|
582
594
|
// already at the target peak -- do nothing
|
|
583
595
|
if (std::abs(scale_factor - 1.0f) < 1e-5f)
|
|
584
|
-
return
|
|
596
|
+
return *this;
|
|
585
597
|
|
|
586
598
|
for (auto &sample : samples)
|
|
587
599
|
{
|
|
588
600
|
sample *= scale_factor;
|
|
589
601
|
}
|
|
590
602
|
|
|
591
|
-
return
|
|
603
|
+
return *this;
|
|
592
604
|
}
|
|
593
605
|
|
|
594
|
-
|
|
606
|
+
AudioTrack &fade_in_bang(float duration_sec)
|
|
595
607
|
{
|
|
596
608
|
if (samples.empty() || duration_sec <= 0.0f)
|
|
597
|
-
return
|
|
609
|
+
return *this;
|
|
598
610
|
|
|
599
611
|
unsigned long long fade_frames = (unsigned long long)(duration_sec * sample_rate);
|
|
600
612
|
unsigned long long total_frames = sample_count / channels;
|
|
@@ -611,13 +623,13 @@ struct AudioTrack
|
|
|
611
623
|
samples[i * channels + c] *= multiplier;
|
|
612
624
|
}
|
|
613
625
|
}
|
|
614
|
-
return
|
|
626
|
+
return *this;
|
|
615
627
|
}
|
|
616
628
|
|
|
617
|
-
|
|
629
|
+
AudioTrack &fade_out_bang(float duration_sec)
|
|
618
630
|
{
|
|
619
631
|
if (samples.empty() || duration_sec <= 0.0f)
|
|
620
|
-
return
|
|
632
|
+
return *this;
|
|
621
633
|
|
|
622
634
|
unsigned long long fade_frames = (unsigned long long)(duration_sec * sample_rate);
|
|
623
635
|
unsigned long long total_frames = sample_count / channels;
|
|
@@ -636,13 +648,13 @@ struct AudioTrack
|
|
|
636
648
|
samples[frame_idx * channels + c] *= multiplier;
|
|
637
649
|
}
|
|
638
650
|
}
|
|
639
|
-
return
|
|
651
|
+
return *this;
|
|
640
652
|
}
|
|
641
653
|
|
|
642
|
-
|
|
654
|
+
AudioTrack &pad_bang(float head_sec = 0.0f, float tail_sec = 0.0f)
|
|
643
655
|
{
|
|
644
656
|
if (head_sec <= 0.0f && tail_sec <= 0.0f)
|
|
645
|
-
return
|
|
657
|
+
return *this;
|
|
646
658
|
|
|
647
659
|
unsigned long long head_frames = (unsigned long long)(head_sec * sample_rate);
|
|
648
660
|
unsigned long long tail_frames = (unsigned long long)(tail_sec * sample_rate);
|
|
@@ -664,20 +676,20 @@ struct AudioTrack
|
|
|
664
676
|
|
|
665
677
|
sample_count = samples.size();
|
|
666
678
|
|
|
667
|
-
return
|
|
679
|
+
return *this;
|
|
668
680
|
}
|
|
669
681
|
|
|
670
|
-
|
|
682
|
+
AudioTrack &pad_to_duration_bang(float target_duration_sec)
|
|
671
683
|
{
|
|
672
684
|
if (target_duration_sec <= 0.0f)
|
|
673
|
-
return
|
|
685
|
+
return *this;
|
|
674
686
|
|
|
675
687
|
unsigned long long current_frames = sample_count / channels;
|
|
676
688
|
unsigned long long target_frames = (unsigned long long)(target_duration_sec * sample_rate);
|
|
677
689
|
|
|
678
690
|
// track is already long enough, do nothing
|
|
679
691
|
if (target_frames <= current_frames)
|
|
680
|
-
return
|
|
692
|
+
return *this;
|
|
681
693
|
|
|
682
694
|
unsigned long long diff_frames = target_frames - current_frames;
|
|
683
695
|
|
|
@@ -702,7 +714,66 @@ struct AudioTrack
|
|
|
702
714
|
|
|
703
715
|
sample_count = samples.size();
|
|
704
716
|
|
|
705
|
-
return
|
|
717
|
+
return *this;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
AudioTrack &add_wave_bang(std::string wave_type, float frequency, float duration_sec, float start_sec = -1.0f, float amplitude = 1.0f)
|
|
721
|
+
{
|
|
722
|
+
if (duration_sec <= 0.0f)
|
|
723
|
+
return *this;
|
|
724
|
+
|
|
725
|
+
// If no start time is provided (-1.0), append to the very end of the track
|
|
726
|
+
if (start_sec < 0.0f)
|
|
727
|
+
{
|
|
728
|
+
start_sec = duration();
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
unsigned long long start_sample = (unsigned long long)(start_sec * sample_rate);
|
|
732
|
+
unsigned long long wave_samples = (unsigned long long)(duration_sec * sample_rate);
|
|
733
|
+
unsigned long long end_sample = start_sample + wave_samples;
|
|
734
|
+
|
|
735
|
+
// Dynamically grow the track if this wave pushes past the current end
|
|
736
|
+
unsigned long long required_samples = end_sample * channels;
|
|
737
|
+
if (required_samples > samples.size())
|
|
738
|
+
{
|
|
739
|
+
samples.resize(required_samples, 0.0f);
|
|
740
|
+
sample_count = samples.size();
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Generate and MIX the wave
|
|
744
|
+
for (unsigned long long i = 0; i < wave_samples; ++i)
|
|
745
|
+
{
|
|
746
|
+
float t = (float)i / sample_rate; // Time in seconds
|
|
747
|
+
float sample_val = 0.0f;
|
|
748
|
+
|
|
749
|
+
if (wave_type == "sine")
|
|
750
|
+
{
|
|
751
|
+
sample_val = std::sin(2.0f * M_PI * frequency * t);
|
|
752
|
+
}
|
|
753
|
+
else if (wave_type == "square")
|
|
754
|
+
{
|
|
755
|
+
sample_val = std::sin(2.0f * M_PI * frequency * t) >= 0.0f ? 1.0f : -1.0f;
|
|
756
|
+
}
|
|
757
|
+
else if (wave_type == "sawtooth")
|
|
758
|
+
{
|
|
759
|
+
sample_val = 2.0f * std::fmod(t * frequency, 1.0f) - 1.0f;
|
|
760
|
+
}
|
|
761
|
+
else if (wave_type == "noise")
|
|
762
|
+
{
|
|
763
|
+
sample_val = ((float)std::rand() / RAND_MAX) * 2.0f - 1.0f;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
sample_val *= amplitude;
|
|
767
|
+
|
|
768
|
+
// Mix into all channels at the correct offset
|
|
769
|
+
unsigned long long current_idx = start_sample + i;
|
|
770
|
+
for (int c = 0; c < channels; ++c)
|
|
771
|
+
{
|
|
772
|
+
samples[current_idx * channels + c] += sample_val;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return *this;
|
|
706
777
|
}
|
|
707
778
|
|
|
708
779
|
std::string to_s()
|
|
@@ -727,7 +798,7 @@ extern "C"
|
|
|
727
798
|
Module rb_mRubyDSP = define_module("RubyDSP");
|
|
728
799
|
Data_Type<AudioTrack> rb_cAudioTrack = define_class_under<AudioTrack>(rb_mRubyDSP, "AudioTrack")
|
|
729
800
|
.define_constructor(Constructor<AudioTrack, std::string, unsigned int, unsigned int>(),
|
|
730
|
-
Arg("file_name") = (std::string) "
|
|
801
|
+
Arg("file_name") = (std::string) "",
|
|
731
802
|
Arg("target_channels") = (unsigned int)0,
|
|
732
803
|
Arg("target_sample_rate") = (unsigned int)0)
|
|
733
804
|
// attributes
|
|
@@ -773,5 +844,11 @@ extern "C"
|
|
|
773
844
|
Arg("tail_sec") = 0.0f)
|
|
774
845
|
.define_method("pad_to_duration!", &AudioTrack::pad_to_duration_bang,
|
|
775
846
|
Arg("target_duration_sec"))
|
|
847
|
+
.define_method("add_wave!", &AudioTrack::add_wave_bang,
|
|
848
|
+
Arg("wave_type"),
|
|
849
|
+
Arg("frequency"),
|
|
850
|
+
Arg("duration_sec"),
|
|
851
|
+
Arg("start_sec") = -1.0f,
|
|
852
|
+
Arg("amplitude") = 1.0f)
|
|
776
853
|
.define_method("to_s", &AudioTrack::to_s);
|
|
777
854
|
}
|
data/lib/ruby_dsp/version.rb
CHANGED
data/ruby_dsp.gemspec
CHANGED
|
@@ -5,8 +5,8 @@ require_relative 'lib/ruby_dsp/version'
|
|
|
5
5
|
Gem::Specification.new do |s|
|
|
6
6
|
s.name = 'ruby_dsp'
|
|
7
7
|
s.version = RubyDSP::VERSION
|
|
8
|
-
s.summary = 'A fast, zero-dependency audio
|
|
9
|
-
s.description = 'RubyDSP is a
|
|
8
|
+
s.summary = 'A fast, zero-dependency audio processing and synthesis hobby gem built on Rice and miniaudio (C++).'
|
|
9
|
+
s.description = 'RubyDSP is a small gem for rudimentary audio processing, DSP, and synthesis. It aims to have basically zero dependencies (AND WARRANTIES)! See Documentation for more.' # rubocop:disable Layout/LineLength
|
|
10
10
|
s.authors = ['Radek C.']
|
|
11
11
|
s.email = 'cichrrad@cvut.cz'
|
|
12
12
|
s.homepage = 'https://github.com/cichrrad/rubyDSP'
|
|
@@ -21,13 +21,16 @@ module RubyDSP
|
|
|
21
21
|
# @return [Integer] number of samples in `samples`
|
|
22
22
|
attr_reader :sample_count
|
|
23
23
|
|
|
24
|
-
# Initializes a new AudioTrack
|
|
24
|
+
# Initializes a new AudioTrack.
|
|
25
25
|
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# @
|
|
30
|
-
|
|
26
|
+
# Decodes the given file using miniaudio. If `file_name` is an empty string `""`,
|
|
27
|
+
# it initializes an empty, blank audio canvas for synthesis and sequencing.
|
|
28
|
+
#
|
|
29
|
+
# @param file_name [String] Path to the audio file, or `""` for a blank track.
|
|
30
|
+
# @param target_channels [Integer] Optional. Force a specific number of channels (Defaults to 1 for blank tracks).
|
|
31
|
+
# @param target_sample_rate [Integer] Optional. Force a specific sample rate (Defaults to 44100 for blank tracks).
|
|
32
|
+
# @raise [RuntimeError] if a file is provided but cannot be processed or read.
|
|
33
|
+
def initialize(file_name = '', target_channels = 0, target_sample_rate = 0)
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
# Saves the audio track to disk.
|
|
@@ -68,7 +71,7 @@ module RubyDSP
|
|
|
68
71
|
|
|
69
72
|
# Destructively converts the track to mono by averaging the channels.
|
|
70
73
|
#
|
|
71
|
-
# @return [
|
|
74
|
+
# @return [AudioTrack] self for method chaining.
|
|
72
75
|
# @raise [RuntimeError] if channel count is invalid.
|
|
73
76
|
def to_mono!
|
|
74
77
|
end
|
|
@@ -76,7 +79,7 @@ module RubyDSP
|
|
|
76
79
|
# Destructively resamples the track to the target rate using linear resampling.
|
|
77
80
|
#
|
|
78
81
|
# @param target_rate [Integer] The new sample rate in Hz.
|
|
79
|
-
# @return [
|
|
82
|
+
# @return [AudioTrack] self for method chaining.
|
|
80
83
|
# @raise [RuntimeError] if the resampler fails to initialize or process.
|
|
81
84
|
def resample!(target_rate = 0)
|
|
82
85
|
end
|
|
@@ -126,42 +129,56 @@ module RubyDSP
|
|
|
126
129
|
# @param threshold_db [Float] The threshold in decibels below the peak RMS to consider as silence. Default is -60.0.
|
|
127
130
|
# @param frame_length [Integer] The number of samples per frame. Default is 2048.
|
|
128
131
|
# @param hop_length [Integer] The number of samples to advance each frame. Default is 512.
|
|
129
|
-
# @return [
|
|
132
|
+
# @return [AudioTrack] self for method chaining.
|
|
130
133
|
def trim_silence!(threshold_db = -60.0, frame_length = 2048, hop_length = 512)
|
|
131
134
|
end
|
|
132
135
|
|
|
133
136
|
# Normalizes the audio track to a specific peak decibel level.
|
|
134
137
|
# @param target_db [Float] The target peak amplitude in dBFS. Defaults to -10.0.
|
|
135
|
-
# @return [
|
|
138
|
+
# @return [AudioTrack] self for method chaining.
|
|
136
139
|
def normalize!(target_db = -10.0)
|
|
137
140
|
end
|
|
138
141
|
|
|
139
142
|
# Applies a linear fade-in to the beginning of the audio track.
|
|
140
143
|
# @param duration_sec [Float] The length of the fade-in in seconds.
|
|
141
|
-
# @return [
|
|
144
|
+
# @return [AudioTrack] self for method chaining.
|
|
142
145
|
def fade_in!(duration_sec)
|
|
143
146
|
end
|
|
144
147
|
|
|
145
148
|
# Applies a linear fade-out to the end of the audio track.
|
|
146
149
|
# @param duration_sec [Float] The length of the fade-out in seconds.
|
|
147
|
-
# @return [
|
|
150
|
+
# @return [AudioTrack] self for method chaining.
|
|
148
151
|
def fade_out!(duration_sec)
|
|
149
152
|
end
|
|
150
153
|
|
|
151
154
|
# Pads the audio track with digital silence (0.0) at the beginning and/or end.
|
|
152
155
|
# @param head_sec [Float] Seconds of silence to add to the beginning. Defaults to 0.0.
|
|
153
156
|
# @param tail_sec [Float] Seconds of silence to add to the end. Defaults to 0.0.
|
|
154
|
-
# @return [
|
|
157
|
+
# @return [AudioTrack] self for method chaining.
|
|
155
158
|
def pad!(head_sec = 0.0, tail_sec = 0.0)
|
|
156
159
|
end
|
|
157
160
|
|
|
158
161
|
# Pads the audio track with digital silence so that it reaches an exact target duration.
|
|
159
162
|
# The padding is distributed evenly to both the head and the tail, effectively centering the audio.
|
|
160
163
|
# @param target_duration_sec [Float] The desired total length of the track in seconds.
|
|
161
|
-
# @return [
|
|
164
|
+
# @return [AudioTrack] self for method chaining.
|
|
162
165
|
def pad_to_duration!(target_duration_sec)
|
|
163
166
|
end
|
|
164
167
|
|
|
168
|
+
# Generates and mixes a mathematical waveform into the track.
|
|
169
|
+
#
|
|
170
|
+
# Dynamically resizes the track if the wave extends past the current duration.
|
|
171
|
+
# If a wave overlaps with existing audio data, it is mixed (added) together, allowing for polyphony.
|
|
172
|
+
#
|
|
173
|
+
# @param wave_type [String] The shape of the waveform (`"sine"`, `"square"`, `"sawtooth"`, `"noise"`).
|
|
174
|
+
# @param frequency [Float] The frequency of the wave in Hz (e.g., 440.0).
|
|
175
|
+
# @param duration_sec [Float] The length of the generated wave in seconds.
|
|
176
|
+
# @param start_sec [Float] The timestamp in seconds to start the wave. Defaults to -1.0 (appends to the end).
|
|
177
|
+
# @param amplitude [Float] The peak amplitude of the wave. Defaults to 1.0.
|
|
178
|
+
# @return [AudioTrack] self for method chaining.
|
|
179
|
+
def add_wave!(wave_type, frequency, duration_sec, start_sec = -1.0, amplitude = 1.0)
|
|
180
|
+
end
|
|
181
|
+
|
|
165
182
|
# @return [String] a formatted summary of the track.
|
|
166
183
|
def to_s
|
|
167
184
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_dsp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Radek C.
|
|
@@ -135,11 +135,9 @@ dependencies:
|
|
|
135
135
|
- - "~>"
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
137
|
version: '0.9'
|
|
138
|
-
description: RubyDSP is a
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
time-domain manipulation (resampling, padding, fades, RMS). It is very much in early
|
|
142
|
-
development, so expect API changes and absolutely no warranties!
|
|
138
|
+
description: RubyDSP is a small gem for rudimentary audio processing, DSP, and synthesis.
|
|
139
|
+
It aims to have basically zero dependencies (AND WARRANTIES)! See Documentation
|
|
140
|
+
for more.
|
|
143
141
|
email: cichrrad@cvut.cz
|
|
144
142
|
executables: []
|
|
145
143
|
extensions:
|
|
@@ -179,5 +177,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
179
177
|
requirements: []
|
|
180
178
|
rubygems_version: 4.0.3
|
|
181
179
|
specification_version: 4
|
|
182
|
-
summary: A fast, zero-dependency audio
|
|
180
|
+
summary: A fast, zero-dependency audio processing and synthesis hobby gem built on
|
|
181
|
+
Rice and miniaudio (C++).
|
|
183
182
|
test_files: []
|