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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -1
  3. data/README.md +204 -22
  4. data/exe/wavify +6 -0
  5. data/lib/wavify/adapters.rb +81 -0
  6. data/lib/wavify/audio.rb +1066 -120
  7. data/lib/wavify/cli.rb +237 -0
  8. data/lib/wavify/codecs/aiff.rb +388 -87
  9. data/lib/wavify/codecs/base.rb +98 -5
  10. data/lib/wavify/codecs/flac.rb +660 -170
  11. data/lib/wavify/codecs/ogg_vorbis.rb +515 -155
  12. data/lib/wavify/codecs/raw.rb +235 -45
  13. data/lib/wavify/codecs/registry.rb +272 -10
  14. data/lib/wavify/codecs/wav.rb +453 -74
  15. data/lib/wavify/core/duration.rb +54 -2
  16. data/lib/wavify/core/format.rb +96 -11
  17. data/lib/wavify/core/sample_buffer.rb +648 -60
  18. data/lib/wavify/core/stream.rb +590 -34
  19. data/lib/wavify/dsl.rb +712 -100
  20. data/lib/wavify/dsp/automation.rb +109 -0
  21. data/lib/wavify/dsp/effects/auto_pan.rb +71 -0
  22. data/lib/wavify/dsp/effects/bitcrusher.rb +74 -0
  23. data/lib/wavify/dsp/effects/chorus.rb +15 -4
  24. data/lib/wavify/dsp/effects/compressor.rb +133 -23
  25. data/lib/wavify/dsp/effects/delay.rb +46 -1
  26. data/lib/wavify/dsp/effects/distortion.rb +3 -2
  27. data/lib/wavify/dsp/effects/effect_base.rb +69 -2
  28. data/lib/wavify/dsp/effects/effect_chain.rb +94 -0
  29. data/lib/wavify/dsp/effects/envelope_controlled_effect.rb +93 -0
  30. data/lib/wavify/dsp/effects/eq.rb +102 -0
  31. data/lib/wavify/dsp/effects/expander.rb +90 -0
  32. data/lib/wavify/dsp/effects/flanger.rb +112 -0
  33. data/lib/wavify/dsp/effects/limiter.rb +259 -0
  34. data/lib/wavify/dsp/effects/mastering_chain.rb +31 -0
  35. data/lib/wavify/dsp/effects/noise_gate.rb +72 -0
  36. data/lib/wavify/dsp/effects/phaser.rb +112 -0
  37. data/lib/wavify/dsp/effects/podcast_chain.rb +32 -0
  38. data/lib/wavify/dsp/effects/reverb.rb +100 -14
  39. data/lib/wavify/dsp/effects/soft_limiter.rb +55 -0
  40. data/lib/wavify/dsp/effects/stereo_widener.rb +73 -0
  41. data/lib/wavify/dsp/effects/tremolo.rb +66 -0
  42. data/lib/wavify/dsp/effects/vibrato.rb +102 -0
  43. data/lib/wavify/dsp/effects.rb +106 -0
  44. data/lib/wavify/dsp/envelope.rb +90 -19
  45. data/lib/wavify/dsp/filter.rb +149 -8
  46. data/lib/wavify/dsp/headroom.rb +57 -0
  47. data/lib/wavify/dsp/lfo.rb +65 -0
  48. data/lib/wavify/dsp/loudness_meter.rb +281 -0
  49. data/lib/wavify/dsp/oscillator.rb +261 -22
  50. data/lib/wavify/dsp/processor.rb +90 -0
  51. data/lib/wavify/errors.rb +2 -2
  52. data/lib/wavify/sequencer/engine.rb +405 -89
  53. data/lib/wavify/sequencer/note_sequence.rb +45 -7
  54. data/lib/wavify/sequencer/pattern.rb +121 -12
  55. data/lib/wavify/sequencer/track.rb +149 -10
  56. data/lib/wavify/version.rb +1 -1
  57. data/lib/wavify.rb +18 -0
  58. data/sig/audio.rbs +86 -0
  59. data/sig/codecs.rbs +77 -0
  60. data/sig/core.rbs +108 -0
  61. data/sig/dsl.rbs +32 -0
  62. data/sig/dsp.rbs +87 -0
  63. data/sig/effects.rbs +129 -0
  64. data/sig/public_api.yml +111 -0
  65. data/sig/sequencer.rbs +126 -0
  66. data/sig/stream.rbs +30 -0
  67. data/sig/wavify.rbs +24 -0
  68. metadata +44 -58
  69. data/.serena/.gitignore +0 -1
  70. data/.serena/memories/project_overview.md +0 -5
  71. data/.serena/memories/style_and_completion.md +0 -5
  72. data/.serena/memories/suggested_commands.md +0 -11
  73. data/.serena/project.yml +0 -126
  74. data/.simplecov +0 -18
  75. data/.yardopts +0 -4
  76. data/Rakefile +0 -190
  77. data/benchmarks/README.md +0 -46
  78. data/benchmarks/benchmark_helper.rb +0 -112
  79. data/benchmarks/dsp_effects_benchmark.rb +0 -46
  80. data/benchmarks/flac_benchmark.rb +0 -74
  81. data/benchmarks/streaming_memory_benchmark.rb +0 -94
  82. data/benchmarks/wav_io_benchmark.rb +0 -110
  83. data/examples/audio_processing.rb +0 -73
  84. data/examples/cinematic_transition.rb +0 -118
  85. data/examples/drum_machine.rb +0 -74
  86. data/examples/format_convert.rb +0 -81
  87. data/examples/hybrid_arrangement.rb +0 -165
  88. data/examples/streaming_master_chain.rb +0 -129
  89. data/examples/synth_pad.rb +0 -42
  90. data/tools/fixture_writer.rb +0 -85
@@ -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 = io.read(size)
84
- raise InvalidFormatError, message if data.nil? || data.bytesize != size
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
- return if io.respond_to?(:seek) && io.respond_to?(:pos)
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