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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c72440d5d1ce22c2665cb7b467e3f57433994b12e7ce2b35b046b2a2056382b
4
- data.tar.gz: bd624dbf28ce1f45e1569b767cd24ea4fe56dafc47e9544c0369ddb4ae41ee20
3
+ metadata.gz: 0c8994eadc28b1923b14a4979f435f18973a83afb09e73d77f709b2f4cef81b8
4
+ data.tar.gz: 1516af206f7b1010f871729f00fd5427b522baacbef2fc516090dc0759f4e1b3
5
5
  SHA512:
6
- metadata.gz: 79b8573dfa3bb57cfd4af2b2a8af7b4e1d72094fcf869a3c103c1aa3fc7dfa9d06150a297ac351e6a8475f695a92d7273731855ac6a3d02a0d0874e1d83bc190
7
- data.tar.gz: 2f9cc438c960788df93214f7edd870917043c215ef9a7c7006dcdedc8254ffdc25a641f9316493cfb90b3e8b7d37e76e6bfe6cc404a2ab2278b342735bf3e6c0
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.5)
1
+ # RubyDSP | [Documentation](https://www.rubydoc.info/gems/ruby_dsp/0.0.6)
2
2
 
3
3
  [![Ruby CI](https://github.com/cichrrad/rubyDSP/actions/workflows/test.yml/badge.svg)](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. Ultimately, it aims to be `librosa`-wannabe for Ruby in some far utopian future which might never come. It uses C++ under the hood, utilizing [miniaudio](https://miniaud.io/) and [Rice](https://github.com/jasonroelofs/rice).
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
- # Do stuff!
61
- track.to_mono! # Averages channels into mono
62
- track.resample!(44100) # Linearly resamples to target rate
63
- track.trim_silence!(-60.0) # Strips leading/trailing silence below -60dB
64
-
65
- # Edit & Manipulate
66
- track.normalize!(-1.0) # Scales audio to target peak dBFS
67
- track.fade_in!(0.5) # Adds a 0.5s linear fade-in
68
- track.fade_out!(0.5) # Adds a 0.5s linear fade-out
69
- track.pad!(1.0, 1.0) # Pads 1s of silence to both head and tail
70
- track.pad_to_duration!(15.0) # Centers audio evenly into a 15s window
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
@@ -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
- bool to_mono_bang()
178
+ AudioTrack &to_mono_bang()
167
179
  {
168
180
  if (is_mono)
169
181
  {
170
- return false; // no-op
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 true;
211
+ return *this;
200
212
  }
201
213
 
202
- bool resample_bang(unsigned int target_rate = 0)
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 false; // no-op
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 true;
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
- bool trim_silence_bang(float threshold_db = -60.0f, unsigned int frame_length = 2048, unsigned int hop_length = 512)
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 false;
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 false;
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 true;
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 true;
578
+ return *this;
567
579
  }
568
580
 
569
- bool normalize_bang(float target_db = -1.0f)
581
+ AudioTrack &normalize_bang(float target_db = -1.0f)
570
582
  {
571
583
  if (samples.empty())
572
- return false;
584
+ return *this;
573
585
 
574
586
  float current_peak = peak_amplitude();
575
587
  if (current_peak <= 0.0f)
576
- return false; // silent track, nothing to scale
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 false;
596
+ return *this;
585
597
 
586
598
  for (auto &sample : samples)
587
599
  {
588
600
  sample *= scale_factor;
589
601
  }
590
602
 
591
- return true;
603
+ return *this;
592
604
  }
593
605
 
594
- bool fade_in_bang(float duration_sec)
606
+ AudioTrack &fade_in_bang(float duration_sec)
595
607
  {
596
608
  if (samples.empty() || duration_sec <= 0.0f)
597
- return false;
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 true;
626
+ return *this;
615
627
  }
616
628
 
617
- bool fade_out_bang(float duration_sec)
629
+ AudioTrack &fade_out_bang(float duration_sec)
618
630
  {
619
631
  if (samples.empty() || duration_sec <= 0.0f)
620
- return false;
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 true;
651
+ return *this;
640
652
  }
641
653
 
642
- bool pad_bang(float head_sec = 0.0f, float tail_sec = 0.0f)
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 false;
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 true;
679
+ return *this;
668
680
  }
669
681
 
670
- bool pad_to_duration_bang(float target_duration_sec)
682
+ AudioTrack &pad_to_duration_bang(float target_duration_sec)
671
683
  {
672
684
  if (target_duration_sec <= 0.0f)
673
- return false;
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 false;
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 true;
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) "default.wav",
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
  }
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyDSP
5
- VERSION = '0.0.5'
5
+ VERSION = '0.0.6'
6
6
  end
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/DSP gem (WIP hobby project) using C++.'
9
- s.description = 'RubyDSP is a hobby project exploring C++ extensions via Rice to bring faster audio processing to Ruby. Powered by miniaudio, it features a zero-dependency build and supports basic format-agnostic loading (WAV, MP3, FLAC), WAV export, and time-domain manipulation (resampling, padding, fades, RMS). It is very much in early development, so expect API changes and absolutely no warranties!' # rubocop:disable Layout/LineLength
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 and decodes the given file.
24
+ # Initializes a new AudioTrack.
25
25
  #
26
- # @param file_name [String] Path to the audio file.
27
- # @param target_channels [Integer] Optional. Force a specific number of channels (0 = original).
28
- # @param target_sample_rate [Integer] Optional. Force a specific sample rate (0 = original).
29
- # @raise [RuntimeError] if the file cannot be processed or read.
30
- def initialize(file_name = 'default.wav', target_channels = 0, target_sample_rate = 0)
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 [Boolean] true if conversion happened, false if already mono.
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 [Boolean] true if resampling happened, false if the rate was unchanged.
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 [Boolean] true if the track was trimmed, false if no trimming occurred.
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 [Boolean] true if the track was altered, false if it was already at the target or silent.
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 [Boolean] true if the fade was applied, false if the track is empty or duration is <= 0.
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 [Boolean] true if the fade was applied, false if the track is empty or duration is <= 0.
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 [Boolean] true if padding was added, false otherwise.
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 [Boolean] true if padding was added, false if the track is already longer than the target.
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.5
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 hobby project exploring C++ extensions via Rice to bring
139
- faster audio processing to Ruby. Powered by miniaudio, it features a zero-dependency
140
- build and supports basic format-agnostic loading (WAV, MP3, FLAC), WAV export, and
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/DSP gem (WIP hobby project) using C++.
180
+ summary: A fast, zero-dependency audio processing and synthesis hobby gem built on
181
+ Rice and miniaudio (C++).
183
182
  test_files: []