ruby_dsp 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,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c8994eadc28b1923b14a4979f435f18973a83afb09e73d77f709b2f4cef81b8
4
- data.tar.gz: 1516af206f7b1010f871729f00fd5427b522baacbef2fc516090dc0759f4e1b3
3
+ metadata.gz: 413c7e687de2f9253fc92e7d57b1a9a9facc40a1597ffd0c6ce9135f1d4eaeb4
4
+ data.tar.gz: 816c5a826c06f77e9a306762185f26323ff0a06894b0d06e6669d39289295cdd
5
5
  SHA512:
6
- metadata.gz: f34843f736e5454ed4130d98f2cce6ff62dac04d250291222ce31201f4a4f9f986bfdeb413d1c2c89fab55dc1dceb094dd6b062bb7e1b978c020698f2f0d0f10
7
- data.tar.gz: f801c956425c34f6092d40cb3c820c22025aac7aa7a5d9916efd6f8048cccee42a04cebd4b808db7b27940a7360430369ad194dba1f765f313c0ac8aa30a9e6f
6
+ metadata.gz: a750759a4c53edf49001f021898c39d0c1ca473f64aa1a68983267b04117fb2e339867778c72d778891bac23d2ef44a42ace5cd8ae56c82feda4616dcb155fd9
7
+ data.tar.gz: c1744710c8b0f5a99f0a46dcf40fb91159d13a976c8f4fe0cef1751e85e457a07743924c90dba30916cced84760c74fd56ce6b2c82692aba01906b033fb9bc14
data/.gitignore CHANGED
@@ -7,4 +7,5 @@ doc
7
7
  *.o
8
8
  *.so
9
9
  *.bundle
10
- Makefile
10
+ Makefile
11
+ *.log
data/README.md CHANGED
@@ -1,54 +1,76 @@
1
- # RubyDSP | [Documentation](https://www.rubydoc.info/gems/ruby_dsp/0.0.6)
1
+ # [RubyDSP](https://github.com/cichrrad/rubyDSP) | [Documentation](https://www.rubydoc.info/gems/ruby_dsp/0.0.7)
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
 
5
- ---
6
-
7
5
  > 🚧 **Status:** This (***HOBBY***) project is currently in early development. It is hopefully functional, but API changes are expected. There is no warranty regarding anything 🗿.
8
6
 
9
7
  ---
10
8
 
11
9
  **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).
10
+ I made this gem to try hands-on binding these two languages together, as I would like to be able to bring some C++ speed to Ruby, oh my beloved....
12
11
 
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....
12
+ > **Note** that AI (Gemini) was used in the developement of this software, albeit only partially and with supervision and testing. If this goes against your ideals, I am sorry.
14
13
 
15
14
  ## Features
16
15
 
17
- * **Fast:** Basically all of the code is written in C++. While not extremely optimized currently, it still absolutely shreds native Ruby.
16
+ * **Fast:** Basically all of the code is written in C++. While not extremely optimized currently, it still absolutely shreds native Ruby (see [Benchmark section](#benchmarks--performance)).
18
17
 
19
18
  * **Fluent API:** Mutating methods return `self`, allowing for beautiful, highly readable method chaining (e.g., `track.to_mono!.normalize!.fade_in!(0.5)`).
20
19
 
21
20
  * **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
21
 
23
22
  * **Format Agnostic Loading:** Automatically decodes standard audio formats (WAV, MP3, FLAC) via `miniaudio`.
24
-
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.
23
+ > **Note:** While the loading of these formats is supported, `miniaudio` saves only in `.wav`. While other encodings might be considered in the future, they would require more dependencies and thus are not available right now.
26
24
 
27
25
  * **Zero-Dependency Native Build:** No need to install `ffmpeg` or `libsndfile` on your system.
28
26
 
29
27
  * **YARD Support:** Includes pure-Ruby stubs (in `stubs`, duh) for IDE autocomplete and inline documentation.
30
28
 
29
+ ## Benchmarks & Performance
30
+
31
+ My primary motivation (love for Ruby aside) was to try and bring C++ speed to Ruby, avoiding the massive memory and garbage collection overhead of native Ruby math for audio/DSP tasks.
32
+
33
+ To back up my words, `RubyDSP` was benchmarked against optimized pure Ruby mock implementation (in `benchmark/bench.rb`), where MRI's internal C-backed methods like `Array#sum` and `Array#fill` were preffered over *prettier* ways of coding. The tests were run on a 10-second mono audio track at 44.1kHz (i.e., `441,000` samples) generated by the gem itself.
34
+
35
+ Benchmark script files are in `benchmark` directory. If you clone the repo for development, you can do `rake bench:summary` from the root directory to re-run the benchmark on your machine.
36
+
37
+ ### Benchmark Output
38
+
39
+ > CPU: AMD RYZEN 5 5600X
40
+ ```bash
41
+ -----------------------------------------------------------------------------------------------
42
+ | Benchmark | C++ Speedup | Ruby Allocation | C++ Allocation |
43
+ -----------------------------------------------------------------------------------------------
44
+ | Read-Only (RMS) | 49.07x | 40 B (1 obj) | 64 B (1 obj) |
45
+ | Mutation (Normalize) | 462.51x | 3,528,040 B (1 obj) | 0 B (0 obj) |
46
+ | Complex (Framed RMS) | 49.25x | 41,264 B (860 obj) | 64 B (1 obj) |
47
+ | Dynamic (Add Wave) | 11.73x | N/A | N/A |
48
+ -----------------------------------------------------------------------------------------------
49
+ ```
50
+ ### Why is it faster?
51
+
52
+ * **Zero GC Overhead on Mutations:** When you modify an array in pure Ruby (e.g., `samples.map!`), Ruby must dynamically allocate hundreds of thousands of new `Float` objects, heavily taxing the Garbage Collector. Thanks to [Rice stl mapping](https://ruby-rice.github.io/4.x/stl/stl/), `RubyDSP` loops over a contiguous block of memory and mutates raw 32-bit floats natively in place, resulting in exactly zero Ruby allocations.
53
+
54
+ * **No Sub-Array Slicing:** For sliding window calculations like `framed_rms`, pure Ruby creates hundreds of intermediate sub-arrays. C++ calculates the windows directly and returns a single, lightweight proxy object to Ruby via Rice.
55
+
31
56
  ## Installation
32
57
 
33
58
  Add this line to your application's `Gemfile`:
34
59
 
35
60
  ```ruby
36
61
  gem 'ruby_dsp'
37
-
38
62
  ```
39
63
 
40
64
  And then execute:
41
65
 
42
66
  ```bash
43
67
  $ bundle install
44
-
45
68
  ```
46
69
 
47
70
  Or install it yourself directly via:
48
71
 
49
72
  ```bash
50
73
  $ gem install ruby_dsp
51
-
52
74
  ```
53
75
 
54
76
  *(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+).*
@@ -61,7 +83,7 @@ Here is a quick look at what you can do with a loaded `AudioTrack`. Thanks to th
61
83
  require 'ruby_dsp'
62
84
 
63
85
  # Load an audio file
64
- track = RubyDSP::AudioTrack.new("raw_vocals.wav")
86
+ track = RubyDSP::AudioTrack.new("raw_voC++ backend cals.wav")
65
87
 
66
88
  puts track
67
89
  # => ['raw_vocals.wav', 12.450s duration, 2 channel(s), 48000Hz sample rate]
@@ -83,7 +105,6 @@ puts "Overall ZCR: #{track.zcr}"
83
105
 
84
106
  # You can also get framed analysis for time-series data:
85
107
  framed_rms_data = track.framed_rms(frame_length: 2048, hop_length: 512)
86
-
87
108
  ```
88
109
 
89
110
  ## Quick Start: Synthesis & Sequencing
@@ -103,8 +124,7 @@ track.add_wave!("sine", 261.63, 3.0, 0.0)
103
124
  # Polish and export
104
125
  track.normalize!(-30.0) # This goes a long way for your hearing
105
126
  .fade_out!(0.5) # Smooth tail
106
- .save_track("c_major_chord.wav")
107
-
127
+ .save_track("c_major_chord.wav")J
108
128
  ```
109
129
 
110
130
  ### Demos
@@ -113,15 +133,27 @@ track.normalize!(-30.0) # This goes a long way for your hearing
113
133
 
114
134
  #### **TWINKLE**
115
135
 
116
- * <video src="./demo/twinkle_twinkle.mp4" controls title="Twinkle"></video>
136
+
137
+
138
+ https://github.com/user-attachments/assets/af6dbaee-630f-49e3-9704-8ff3440334cb
139
+
140
+
117
141
 
118
142
  #### **TETRIS**
119
143
 
120
- * <video src="./demo/tetris_theme.mp4" controls title="Tetris"></video>
144
+
145
+
146
+ https://github.com/user-attachments/assets/b3ad6886-3552-4200-b725-f14083f96792
147
+
148
+
121
149
 
122
150
  #### **SUPER MARIO BROS**
123
151
 
124
- * <video src="./demo/mario_intro.mp4" controls title="SMB"></video>
152
+
153
+
154
+ https://github.com/user-attachments/assets/5193d72d-4c32-4c83-8253-206402ac2889
155
+
156
+
125
157
 
126
158
  ## Development
127
159
 
@@ -137,4 +169,4 @@ The gem is available as open source under the terms of the **MIT License**.
137
169
 
138
170
  ---
139
171
 
140
- > Cheers! - RC
172
+ > Cheers! - RC
data/Rakefile CHANGED
@@ -2,6 +2,21 @@
2
2
  require 'bundler/gem_tasks'
3
3
  require 'rake/extensiontask'
4
4
  require 'rake/testtask'
5
+ require 'securerandom'
6
+
7
+ namespace :bench do
8
+ desc 'Run benchmark, save to UUID log, and display a parsed summary'
9
+ task :summary do
10
+ log_filename = "benchmark/logs/bench_#{SecureRandom.uuid}.log"
11
+
12
+ puts 'Running benchmarks... This will take a moment (~1 min).'
13
+ system("bundle exec ruby benchmark/bench.rb > #{log_filename}")
14
+
15
+ puts "Done! Parsing results from #{log_filename}...\n\n"
16
+
17
+ system("bundle exec ruby benchmark/parse_log.rb #{log_filename}")
18
+ end
19
+ end
5
20
 
6
21
  Rake::ExtensionTask.new('ruby_dsp') do |ext|
7
22
  ext.lib_dir = 'lib/ruby_dsp'
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: false
2
+
3
+ require 'benchmark/ips'
4
+ require 'ruby_dsp'
5
+ require 'memory_profiler'
6
+
7
+ # MOCK NATIVE RUBY IMPLEMENTATION for 1 channel audio
8
+ class PureRubyAudioTrack
9
+ attr_accessor :samples, :channels, :sample_rate
10
+
11
+ def initialize(channels = 1, sample_rate = 44_100)
12
+ @channels = channels
13
+ @sample_rate = sample_rate
14
+ @samples = []
15
+ end
16
+
17
+ # Pure Ruby RMS calculation
18
+ def rms
19
+ return [0.0] if @samples.empty?
20
+
21
+ sum_sq = @samples.sum { |s| s * s }
22
+ [Math.sqrt(sum_sq / @samples.size)]
23
+ end
24
+
25
+ # Pure Ruby array mutation
26
+ def normalize!(target_db = -1.0)
27
+ return self if @samples.empty?
28
+
29
+ peak = @samples.map(&:abs).max
30
+ return self if peak <= 0.0
31
+
32
+ target_linear = 10.0**(target_db / 20.0)
33
+ scale_factor = target_linear / peak
34
+
35
+ @samples.map! { |s| s * scale_factor }
36
+ self
37
+ end
38
+
39
+ def duration
40
+ @samples.size.to_f / @sample_rate
41
+ end
42
+
43
+ # optimized Pure Ruby framed RMS
44
+ def framed_rms(frame_length = 2048, hop_length = 512)
45
+ return [] if @samples.empty?
46
+
47
+ return [rms] if @samples.size < frame_length
48
+
49
+ expected_frames = ((@samples.size - frame_length) / hop_length) + 1
50
+ result = Array.new(expected_frames)
51
+
52
+ expected_frames.times do |i|
53
+ start_idx = i * hop_length
54
+ # Array slicing optimized in MRI
55
+ frame = @samples[start_idx, frame_length]
56
+ # .sum is implemented in C is faster than manual iteration
57
+ sum_sq = frame.sum { |s| s * s }
58
+ result[i] = Math.sqrt(sum_sq / frame_length)
59
+ end
60
+
61
+ [result] # Wrap in 2D array to match our API
62
+ end
63
+
64
+ # optimized Pure Ruby add_wave (just sine for the benchmark -- discards wave_type)
65
+ def add_wave!(_wave_type, frequency, duration_sec, start_sec = -1.0, amplitude = 1.0)
66
+ start_sec = duration if start_sec < 0.0
67
+
68
+ start_sample = (start_sec * @sample_rate).to_i
69
+ wave_samples = (duration_sec * @sample_rate).to_i
70
+ end_sample = start_sample + wave_samples
71
+
72
+ # memory pre-allocation using MRI's internal C fill
73
+ @samples.fill(0.0, @samples.size...end_sample) if end_sample > @samples.size
74
+
75
+ two_pi_freq = 2.0 * Math::PI * frequency
76
+
77
+ wave_samples.times do |i|
78
+ t = i.to_f / @sample_rate
79
+ val = Math.sin(two_pi_freq * t) * amplitude
80
+
81
+ @samples[start_sample + i] += val
82
+ end
83
+
84
+ self
85
+ end
86
+ end
87
+
88
+ # TEST DATA
89
+ DURATION = 10.0
90
+ FREQ = 440.0
91
+
92
+ puts 'Generating test data...'
93
+
94
+ # Setup C++ Track
95
+ cpp_track = RubyDSP::AudioTrack.new('', 1, 44_100)
96
+ cpp_track.add_wave!('sine', FREQ, DURATION)
97
+
98
+ # Setup Native Ruby Track
99
+ ruby_track = PureRubyAudioTrack.new(1, 44_100)
100
+ ruby_track.add_wave!('sine', FREQ, DURATION)
101
+
102
+ puts "Data generated! Starting benchmarks...\n\n"
103
+
104
+ # RUNTIME
105
+
106
+ Benchmark.ips do |x|
107
+ x.config(time: 5, warmup: 2)
108
+
109
+ x.report('Read-Only (RMS) - Ruby') { ruby_track.rms }
110
+ x.report('Read-Only (RMS) - C++ ') { cpp_track.rms }
111
+
112
+ x.compare!
113
+ end
114
+
115
+ puts "\n#{'-' * 50}\n\n"
116
+
117
+ Benchmark.ips do |x|
118
+ x.config(time: 5, warmup: 2)
119
+
120
+ x.report('Mutation (Normalize) - Ruby') { ruby_track.normalize!(-3.0) }
121
+ x.report('Mutation (Normalize) - C++ ') { cpp_track.normalize!(-3.0) }
122
+
123
+ x.compare!
124
+ end
125
+
126
+ Benchmark.ips do |x|
127
+ x.config(time: 5, warmup: 2)
128
+
129
+ x.report('Complex (Framed RMS) - Ruby') { ruby_track.framed_rms(2048, 512) }
130
+ x.report('Complex (Framed RMS) - C++ ') { cpp_track.framed_rms(2048, 512) }
131
+
132
+ x.compare!
133
+ end
134
+
135
+ puts "\n#{'-' * 50}\n\n"
136
+
137
+ Benchmark.ips do |x|
138
+ x.config(time: 5, warmup: 2)
139
+
140
+ # Adding a 2-second wave into the middle of the track
141
+ x.report('Dynamic (Add Wave) - Ruby') { ruby_track.add_wave!('sine', 880.0, 2.0, 4.0, 0.5) }
142
+ x.report('Dynamic (Add Wave) - C++ ') { cpp_track.add_wave!('sine', 880.0, 2.0, 4.0, 0.5) }
143
+
144
+ x.compare!
145
+ end
146
+
147
+ # MEMORY
148
+
149
+ puts "\n#{'=' * 50}"
150
+ puts 'MEMORY PROFILING: Read-Only (RMS)'
151
+ puts '=' * 50
152
+
153
+ # Profile Ruby RMS
154
+ puts "\n[ Profiling Ruby RMS... ]"
155
+ report_rms_ruby = MemoryProfiler.report { ruby_track.rms }
156
+ report_rms_ruby.pretty_print(scale_bytes: false, color_output: false)
157
+
158
+ # Profile C++ RMS
159
+ puts "\n[ Profiling C++ RMS... ]"
160
+ report_rms_cpp = MemoryProfiler.report { cpp_track.rms }
161
+ report_rms_cpp.pretty_print(scale_bytes: false, color_output: false)
162
+
163
+ puts "\n#{'=' * 50}"
164
+ puts 'MEMORY PROFILING: Mutation (Normalize)'
165
+ puts '=' * 50
166
+
167
+ # Profile Ruby Normalize
168
+ puts "\n[ Profiling Ruby Normalize... ]"
169
+ report_norm_ruby = MemoryProfiler.report { ruby_track.normalize!(-3.0) }
170
+ report_norm_ruby.pretty_print(scale_bytes: false, color_output: false)
171
+
172
+ # Profile C++ Normalize
173
+ puts "\n[ Profiling C++ Normalize... ]"
174
+ report_norm_cpp = MemoryProfiler.report { cpp_track.normalize!(-3.0) }
175
+ report_norm_cpp.pretty_print(scale_bytes: false, color_output: false)
176
+
177
+ puts "\n#{'=' * 50}"
178
+ puts 'MEMORY PROFILING: Complex (Framed RMS)'
179
+ puts '=' * 50
180
+
181
+ # Profile Ruby Framed RMS
182
+ puts "\n[ Profiling Ruby Framed RMS... ]"
183
+ report_framed_ruby = MemoryProfiler.report { ruby_track.framed_rms }
184
+ report_framed_ruby.pretty_print(scale_bytes: false, color_output: false)
185
+
186
+ # Profile C++ Framed RMS
187
+ puts "\n[ Profiling C++ Framed RMS... ]"
188
+ report_framed_cpp = MemoryProfiler.report { cpp_track.framed_rms }
189
+ report_framed_cpp.pretty_print(scale_bytes: false, color_output: false)
File without changes
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ def parse_benchmark_log(filename)
4
+ results = {}
5
+ current_mem_test = nil
6
+ parsing_target = nil
7
+
8
+ File.foreach(filename) do |line|
9
+ # Parse Speedup (e.g., Read-Only (RMS) - Ruby: 71.3 i/s - 48.69x slower)
10
+ if match = line.match(%r{^\s*(.*?)\s*-\s*Ruby:\s+[\d.]+\s+i/s\s+-\s+([\d.]+)x\s+slower})
11
+ test_name = match[1].strip
12
+ results[test_name] ||= {}
13
+ results[test_name][:speedup] = match[2]
14
+ end
15
+
16
+ # Parse Memory Profiling sections headers
17
+ if match = line.match(/^MEMORY PROFILING:\s*(.*)/)
18
+ current_mem_test = match[1].strip
19
+ results[current_mem_test] ||= {}
20
+ end
21
+
22
+ # Determine if we are looking at Ruby or C++ memory
23
+ if line.include?('[ Profiling Ruby')
24
+ parsing_target = :ruby
25
+ elsif line.include?('[ Profiling C++')
26
+ parsing_target = :cpp
27
+ end
28
+
29
+ # Extract allocation numbers
30
+ if (match = line.match(/Total allocated:\s+(\d+)\s+bytes\s+\((\d+)\s+objects\)/)) && current_mem_test && parsing_target
31
+ results[current_mem_test][:"#{parsing_target}_bytes"] = format_number(match[1])
32
+ results[current_mem_test][:"#{parsing_target}_objs"] = format_number(match[2])
33
+ parsing_target = nil # reset after capturing
34
+ end
35
+ end
36
+
37
+ results
38
+ end
39
+
40
+ def format_number(num_str)
41
+ # Adds commas to large numbers for readability
42
+ num_str.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
43
+ end
44
+
45
+ def print_summary_table(results, log_filename)
46
+ puts '-' * 95
47
+ puts format('| %-25s | %-12s | %-22s | %-22s |', 'Benchmark', 'C++ Speedup', 'Ruby Allocation', 'C++ Allocation') # rubocop:disable Style/FormatStringToken
48
+ puts '-' * 95
49
+
50
+ results.each do |name, data|
51
+ speedup = data[:speedup] ? "#{data[:speedup]}x" : 'N/A'
52
+ ruby_alloc = data[:ruby_bytes] ? "#{data[:ruby_bytes]} B (#{data[:ruby_objs]} obj)" : 'N/A'
53
+ cpp_alloc = data[:cpp_bytes] ? "#{data[:cpp_bytes]} B (#{data[:cpp_objs]} obj)" : 'N/A'
54
+
55
+ puts format('| %-25s | %-12s | %-22s | %-22s |', name, speedup, ruby_alloc, cpp_alloc) # rubocop:disable Style/FormatStringToken
56
+ end
57
+
58
+ puts '-' * 95
59
+ puts "\nFull detailed log saved to: #{log_filename}"
60
+ end
61
+
62
+ # Execute only if run directly
63
+ if __FILE__ == $0
64
+ log_file = ARGV[0]
65
+
66
+ unless log_file && File.exist?(log_file)
67
+ puts 'Usage: ruby parse_log.rb <path_to_log_file>'
68
+ exit 1
69
+ end
70
+
71
+ results = parse_benchmark_log(log_file)
72
+ print_summary_table(results, log_file)
73
+ end
@@ -0,0 +1,23 @@
1
+ require 'ruby_dsp'
2
+
3
+ track = RubyDSP::AudioTrack.new('', 1, 44_100)
4
+
5
+ # Frequencies for Mario (Hz)
6
+ e6 = 1318.51
7
+ c6 = 1046.50
8
+ g6 = 1567.98
9
+ g5 = 783.99
10
+
11
+ puts 'Sequencing Super Mario Bros Intro...'
12
+
13
+ track.add_wave!('square', e6, 0.1, 0.00)
14
+ .add_wave!('square', e6, 0.1, 0.15)
15
+ .add_wave!('square', e6, 0.1, 0.45)
16
+ .add_wave!('square', c6, 0.1, 0.75)
17
+ .add_wave!('square', e6, 0.1, 0.90)
18
+ .add_wave!('square', g6, 0.1, 1.20)
19
+ .add_wave!('square', g5, 0.1, 1.80)
20
+ .normalize!(-35.0)
21
+ .save_track('mario_intro.wav')
22
+
23
+ puts 'Done! Saved to mario_intro.wav'
@@ -0,0 +1,27 @@
1
+ require 'ruby_dsp'
2
+
3
+ track = RubyDSP::AudioTrack.new('', 1, 44_100)
4
+
5
+ # Frequencies for Tetris (Hz)
6
+ e5 = 659.25
7
+ b4 = 493.88
8
+ c5 = 523.25
9
+ d5 = 587.33
10
+ a4 = 440.00
11
+
12
+ puts 'Sequencing Tetris (Korobeiniki)...'
13
+
14
+ track.add_wave!('sawtooth', e5, 0.30, 0.0)
15
+ .add_wave!('sawtooth', b4, 0.15, 0.4)
16
+ .add_wave!('sawtooth', c5, 0.15, 0.6)
17
+ .add_wave!('sawtooth', d5, 0.30, 0.8)
18
+ .add_wave!('sawtooth', c5, 0.15, 1.2)
19
+ .add_wave!('sawtooth', b4, 0.15, 1.4)
20
+ .add_wave!('sawtooth', a4, 0.30, 1.6)
21
+ .add_wave!('sawtooth', a4, 0.15, 2.0)
22
+ .add_wave!('sawtooth', c5, 0.15, 2.2)
23
+ .add_wave!('sawtooth', e5, 0.30, 2.4)
24
+ .normalize!(-35.0)
25
+ .save_track('tetris_theme.wav')
26
+
27
+ puts 'Done! Saved to tetris_theme.wav'
@@ -0,0 +1,33 @@
1
+ require 'ruby_dsp'
2
+
3
+ track = RubyDSP::AudioTrack.new('', 1, 44_100)
4
+
5
+ # frequencies for Twinkle (Hz)
6
+ c4 = 261.63
7
+ d4 = 293.66
8
+ e4 = 329.63
9
+ f4 = 349.23
10
+ g4 = 392.00
11
+ a4 = 440.00
12
+
13
+ puts 'Sequencing 8-bit Twinkle Twinkle...'
14
+
15
+ track.add_wave!('square', c4, 0.4, 0.0)
16
+ .add_wave!('square', c4, 0.4, 0.5)
17
+ .add_wave!('square', g4, 0.4, 1.0)
18
+ .add_wave!('square', g4, 0.4, 1.5)
19
+ .add_wave!('square', a4, 0.4, 2.0)
20
+ .add_wave!('square', a4, 0.4, 2.5)
21
+ .add_wave!('square', g4, 0.9, 3.0)
22
+ .add_wave!('square', f4, 0.4, 4.0)
23
+ .add_wave!('square', f4, 0.4, 4.5)
24
+ .add_wave!('square', e4, 0.4, 5.0)
25
+ .add_wave!('square', e4, 0.4, 5.5)
26
+ .add_wave!('square', d4, 0.4, 6.0)
27
+ .add_wave!('square', d4, 0.4, 6.5)
28
+ .add_wave!('square', c4, 0.9, 7.0)
29
+ .normalize!(-35.0)
30
+ .fade_out!(0.5)
31
+ .save_track('twinkle_twinkle.wav')
32
+
33
+ puts 'Done! Saved to twinkle_8bit.wav'
Binary file
Binary file
Binary file
@@ -762,7 +762,6 @@ struct AudioTrack
762
762
  {
763
763
  sample_val = ((float)std::rand() / RAND_MAX) * 2.0f - 1.0f;
764
764
  }
765
-
766
765
  sample_val *= amplitude;
767
766
 
768
767
  // Mix into all channels at the correct offset
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyDSP
5
- VERSION = '0.0.6'
5
+ VERSION = '0.0.7'
6
6
  end
data/ruby_dsp.gemspec CHANGED
@@ -21,6 +21,9 @@ Gem::Specification.new do |s|
21
21
 
22
22
  s.add_dependency 'rice', '~> 4.11.2'
23
23
 
24
+ s.add_development_dependency 'benchmark', '~> 0.5.0'
25
+ s.add_development_dependency 'benchmark-ips', '~> 2.14'
26
+ s.add_development_dependency 'memory_profiler', '~> 1.1'
24
27
  s.add_development_dependency 'minitest', '~> 6.0'
25
28
  s.add_development_dependency 'rack', '~> 3.2'
26
29
  s.add_development_dependency 'rackup', '~> 2.3'
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.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radek C.
@@ -23,6 +23,48 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: 4.11.2
26
+ - !ruby/object:Gem::Dependency
27
+ name: benchmark
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.5.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.5.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: benchmark-ips
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.14'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.14'
54
+ - !ruby/object:Gem::Dependency
55
+ name: memory_profiler
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.1'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.1'
26
68
  - !ruby/object:Gem::Dependency
27
69
  name: minitest
28
70
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +192,15 @@ files:
150
192
  - Gemfile
151
193
  - README.md
152
194
  - Rakefile
195
+ - benchmark/bench.rb
196
+ - benchmark/logs/.gitkeep
197
+ - benchmark/parse_log.rb
198
+ - demo/demo_mbros.rb
199
+ - demo/demo_tetris.rb
200
+ - demo/demo_twinkle.rb
201
+ - demo/mario_intro.mp4
202
+ - demo/tetris_theme.mp4
203
+ - demo/twinkle_twinkle.mp4
153
204
  - ext/ruby_dsp/extconf.rb
154
205
  - ext/ruby_dsp/ruby_dsp.cpp
155
206
  - ext/ruby_dsp/vendor/miniaudio.h