wavify 0.1.0 → 0.2.0
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/CHANGELOG.md +40 -1
- data/README.md +204 -22
- data/exe/wavify +6 -0
- data/lib/wavify/adapters.rb +81 -0
- data/lib/wavify/audio.rb +1066 -120
- data/lib/wavify/cli.rb +237 -0
- data/lib/wavify/codecs/aiff.rb +388 -87
- data/lib/wavify/codecs/base.rb +98 -5
- data/lib/wavify/codecs/flac.rb +660 -170
- data/lib/wavify/codecs/ogg_vorbis.rb +515 -155
- data/lib/wavify/codecs/raw.rb +235 -45
- data/lib/wavify/codecs/registry.rb +272 -10
- data/lib/wavify/codecs/wav.rb +453 -74
- data/lib/wavify/core/duration.rb +54 -2
- data/lib/wavify/core/format.rb +96 -11
- data/lib/wavify/core/sample_buffer.rb +648 -60
- data/lib/wavify/core/stream.rb +590 -34
- data/lib/wavify/dsl.rb +712 -100
- data/lib/wavify/dsp/automation.rb +109 -0
- data/lib/wavify/dsp/effects/auto_pan.rb +71 -0
- data/lib/wavify/dsp/effects/bitcrusher.rb +74 -0
- data/lib/wavify/dsp/effects/chorus.rb +15 -4
- data/lib/wavify/dsp/effects/compressor.rb +133 -23
- data/lib/wavify/dsp/effects/delay.rb +46 -1
- data/lib/wavify/dsp/effects/distortion.rb +3 -2
- data/lib/wavify/dsp/effects/effect_base.rb +69 -2
- data/lib/wavify/dsp/effects/effect_chain.rb +94 -0
- data/lib/wavify/dsp/effects/envelope_controlled_effect.rb +93 -0
- data/lib/wavify/dsp/effects/eq.rb +102 -0
- data/lib/wavify/dsp/effects/expander.rb +90 -0
- data/lib/wavify/dsp/effects/flanger.rb +112 -0
- data/lib/wavify/dsp/effects/limiter.rb +259 -0
- data/lib/wavify/dsp/effects/mastering_chain.rb +31 -0
- data/lib/wavify/dsp/effects/noise_gate.rb +72 -0
- data/lib/wavify/dsp/effects/phaser.rb +112 -0
- data/lib/wavify/dsp/effects/podcast_chain.rb +32 -0
- data/lib/wavify/dsp/effects/reverb.rb +100 -14
- data/lib/wavify/dsp/effects/soft_limiter.rb +55 -0
- data/lib/wavify/dsp/effects/stereo_widener.rb +73 -0
- data/lib/wavify/dsp/effects/tremolo.rb +66 -0
- data/lib/wavify/dsp/effects/vibrato.rb +102 -0
- data/lib/wavify/dsp/effects.rb +106 -0
- data/lib/wavify/dsp/envelope.rb +90 -19
- data/lib/wavify/dsp/filter.rb +149 -8
- data/lib/wavify/dsp/headroom.rb +57 -0
- data/lib/wavify/dsp/lfo.rb +65 -0
- data/lib/wavify/dsp/loudness_meter.rb +281 -0
- data/lib/wavify/dsp/oscillator.rb +261 -22
- data/lib/wavify/dsp/processor.rb +90 -0
- data/lib/wavify/errors.rb +2 -2
- data/lib/wavify/sequencer/engine.rb +405 -89
- data/lib/wavify/sequencer/note_sequence.rb +45 -7
- data/lib/wavify/sequencer/pattern.rb +121 -12
- data/lib/wavify/sequencer/track.rb +149 -10
- data/lib/wavify/version.rb +1 -1
- data/lib/wavify.rb +18 -0
- data/sig/audio.rbs +86 -0
- data/sig/codecs.rbs +77 -0
- data/sig/core.rbs +108 -0
- data/sig/dsl.rbs +32 -0
- data/sig/dsp.rbs +87 -0
- data/sig/effects.rbs +129 -0
- data/sig/public_api.yml +111 -0
- data/sig/sequencer.rbs +126 -0
- data/sig/stream.rbs +30 -0
- data/sig/wavify.rbs +24 -0
- metadata +44 -58
- data/.serena/.gitignore +0 -1
- data/.serena/memories/project_overview.md +0 -5
- data/.serena/memories/style_and_completion.md +0 -5
- data/.serena/memories/suggested_commands.md +0 -11
- data/.serena/project.yml +0 -126
- data/.simplecov +0 -18
- data/.yardopts +0 -4
- data/Rakefile +0 -190
- data/benchmarks/README.md +0 -46
- data/benchmarks/benchmark_helper.rb +0 -112
- data/benchmarks/dsp_effects_benchmark.rb +0 -46
- data/benchmarks/flac_benchmark.rb +0 -74
- data/benchmarks/streaming_memory_benchmark.rb +0 -94
- data/benchmarks/wav_io_benchmark.rb +0 -110
- data/examples/audio_processing.rb +0 -73
- data/examples/cinematic_transition.rb +0 -118
- data/examples/drum_machine.rb +0 -74
- data/examples/format_convert.rb +0 -81
- data/examples/hybrid_arrangement.rb +0 -165
- data/examples/streaming_master_chain.rb +0 -129
- data/examples/synth_pad.rb +0 -42
- data/tools/fixture_writer.rb +0 -85
data/lib/wavify/codecs/base.rb
CHANGED
|
@@ -7,6 +7,9 @@ module Wavify
|
|
|
7
7
|
#
|
|
8
8
|
# Concrete codecs implement the class methods below.
|
|
9
9
|
class Base
|
|
10
|
+
READ_CHUNK_SIZE = 64 * 1024
|
|
11
|
+
private_constant :READ_CHUNK_SIZE
|
|
12
|
+
|
|
10
13
|
class << self
|
|
11
14
|
# Returns whether this codec can read the given path/IO.
|
|
12
15
|
#
|
|
@@ -31,7 +34,7 @@ module Wavify
|
|
|
31
34
|
# @param _sample_buffer [Wavify::Core::SampleBuffer]
|
|
32
35
|
# @param format [Wavify::Core::Format]
|
|
33
36
|
# @return [String, IO]
|
|
34
|
-
def write(_io_or_path, _sample_buffer, format
|
|
37
|
+
def write(_io_or_path, _sample_buffer, format:, **_codec_options)
|
|
35
38
|
raise NotImplementedError
|
|
36
39
|
end
|
|
37
40
|
|
|
@@ -49,7 +52,7 @@ module Wavify
|
|
|
49
52
|
# @param _io_or_path [String, IO]
|
|
50
53
|
# @param format [Wavify::Core::Format]
|
|
51
54
|
# @return [Enumerator, String, IO]
|
|
52
|
-
def stream_write(_io_or_path, format
|
|
55
|
+
def stream_write(_io_or_path, format:, **_codec_options)
|
|
53
56
|
raise NotImplementedError
|
|
54
57
|
end
|
|
55
58
|
|
|
@@ -61,6 +64,13 @@ module Wavify
|
|
|
61
64
|
raise NotImplementedError
|
|
62
65
|
end
|
|
63
66
|
|
|
67
|
+
# Returns whether optional runtime dependencies for this codec are present.
|
|
68
|
+
#
|
|
69
|
+
# @return [Boolean]
|
|
70
|
+
def available?
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
|
|
64
74
|
private
|
|
65
75
|
|
|
66
76
|
def open_input(io_or_path)
|
|
@@ -79,11 +89,81 @@ module Wavify
|
|
|
79
89
|
[File.open(io_or_path, "wb"), true]
|
|
80
90
|
end
|
|
81
91
|
|
|
92
|
+
def prepare_output!(io, owned:)
|
|
93
|
+
io.binmode if io.respond_to?(:binmode)
|
|
94
|
+
return io.pos if owned
|
|
95
|
+
return nil unless io.respond_to?(:pos)
|
|
96
|
+
|
|
97
|
+
start_position = io.pos
|
|
98
|
+
if io.respond_to?(:size) && io.size > start_position && !io.respond_to?(:truncate)
|
|
99
|
+
raise StreamError, "caller-owned IO with trailing data must support truncate"
|
|
100
|
+
end
|
|
101
|
+
start_position
|
|
102
|
+
rescue IOError, SystemCallError => e
|
|
103
|
+
raise StreamError, "output IO position is unavailable: #{e.message}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def finalize_output!(io, owned:)
|
|
107
|
+
io.flush if io.respond_to?(:flush)
|
|
108
|
+
return unless io.respond_to?(:pos)
|
|
109
|
+
|
|
110
|
+
end_position = io.pos
|
|
111
|
+
io.truncate(end_position) if !owned && io.respond_to?(:truncate)
|
|
112
|
+
rescue IOError, SystemCallError => e
|
|
113
|
+
raise StreamError, "failed to finalize output IO: #{e.message}"
|
|
114
|
+
end
|
|
115
|
+
|
|
82
116
|
def read_exact(io, size, message)
|
|
83
|
-
data =
|
|
84
|
-
|
|
117
|
+
data = +"".b
|
|
118
|
+
while data.bytesize < size
|
|
119
|
+
chunk = io.read([size - data.bytesize, READ_CHUNK_SIZE].min)
|
|
120
|
+
break if chunk.nil? || chunk.empty?
|
|
121
|
+
|
|
122
|
+
data << chunk
|
|
123
|
+
end
|
|
124
|
+
raise InvalidFormatError, message if data.bytesize != size
|
|
125
|
+
|
|
126
|
+
data
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def read_to_end(io, chunk_size: 16_384)
|
|
130
|
+
data = +"".b
|
|
131
|
+
loop do
|
|
132
|
+
chunk = io.read(chunk_size)
|
|
133
|
+
break if chunk.nil? || chunk.empty?
|
|
134
|
+
|
|
135
|
+
data << chunk
|
|
136
|
+
end
|
|
137
|
+
data
|
|
138
|
+
end
|
|
85
139
|
|
|
140
|
+
def probe_bytes(io, size)
|
|
141
|
+
ensure_seekable!(io)
|
|
142
|
+
original_position = io.pos
|
|
143
|
+
data = +"".b
|
|
144
|
+
while data.bytesize < size
|
|
145
|
+
chunk = io.read(size - data.bytesize)
|
|
146
|
+
break if chunk.nil? || chunk.empty?
|
|
147
|
+
|
|
148
|
+
data << chunk
|
|
149
|
+
end
|
|
86
150
|
data
|
|
151
|
+
ensure
|
|
152
|
+
io.seek(original_position, IO::SEEK_SET) if defined?(original_position) && original_position
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def write_all(io, data)
|
|
156
|
+
bytes = data.b
|
|
157
|
+
offset = 0
|
|
158
|
+
while offset < bytes.bytesize
|
|
159
|
+
written = io.write(bytes.byteslice(offset, bytes.bytesize - offset))
|
|
160
|
+
unless written.is_a?(Integer) && written.positive?
|
|
161
|
+
raise IOError, "write returned #{written.inspect} before all bytes were written"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
offset += written
|
|
165
|
+
end
|
|
166
|
+
bytes.bytesize
|
|
87
167
|
end
|
|
88
168
|
|
|
89
169
|
def skip_bytes(io, count)
|
|
@@ -98,9 +178,22 @@ module Wavify
|
|
|
98
178
|
end
|
|
99
179
|
|
|
100
180
|
def ensure_seekable!(io)
|
|
101
|
-
|
|
181
|
+
if io.respond_to?(:seek) && io.respond_to?(:pos)
|
|
182
|
+
position = io.pos
|
|
183
|
+
io.seek(position, IO::SEEK_SET)
|
|
184
|
+
return
|
|
185
|
+
end
|
|
102
186
|
|
|
103
187
|
raise StreamError, "codec requires seekable IO"
|
|
188
|
+
rescue IOError, SystemCallError => e
|
|
189
|
+
raise StreamError, "codec requires seekable IO: #{e.message}"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def validate_no_codec_options!(codec_options, operation:)
|
|
193
|
+
return if codec_options.empty?
|
|
194
|
+
|
|
195
|
+
names = codec_options.keys.map(&:inspect).join(", ")
|
|
196
|
+
raise InvalidParameterError, "unsupported #{operation} codec_options: #{names}"
|
|
104
197
|
end
|
|
105
198
|
end
|
|
106
199
|
end
|