wavify 0.1.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 +7 -0
- data/.serena/.gitignore +1 -0
- data/.serena/memories/project_overview.md +5 -0
- data/.serena/memories/style_and_completion.md +5 -0
- data/.serena/memories/suggested_commands.md +11 -0
- data/.serena/project.yml +126 -0
- data/.simplecov +18 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +11 -0
- data/LICENSE +21 -0
- data/README.md +196 -0
- data/Rakefile +190 -0
- data/benchmarks/README.md +46 -0
- data/benchmarks/benchmark_helper.rb +112 -0
- data/benchmarks/dsp_effects_benchmark.rb +46 -0
- data/benchmarks/flac_benchmark.rb +74 -0
- data/benchmarks/streaming_memory_benchmark.rb +94 -0
- data/benchmarks/wav_io_benchmark.rb +110 -0
- data/examples/audio_processing.rb +73 -0
- data/examples/cinematic_transition.rb +118 -0
- data/examples/drum_machine.rb +74 -0
- data/examples/format_convert.rb +81 -0
- data/examples/hybrid_arrangement.rb +165 -0
- data/examples/streaming_master_chain.rb +129 -0
- data/examples/synth_pad.rb +42 -0
- data/lib/wavify/audio.rb +483 -0
- data/lib/wavify/codecs/aiff.rb +338 -0
- data/lib/wavify/codecs/base.rb +108 -0
- data/lib/wavify/codecs/flac.rb +1322 -0
- data/lib/wavify/codecs/ogg_vorbis.rb +1447 -0
- data/lib/wavify/codecs/raw.rb +193 -0
- data/lib/wavify/codecs/registry.rb +87 -0
- data/lib/wavify/codecs/wav.rb +459 -0
- data/lib/wavify/core/duration.rb +99 -0
- data/lib/wavify/core/format.rb +133 -0
- data/lib/wavify/core/sample_buffer.rb +216 -0
- data/lib/wavify/core/stream.rb +129 -0
- data/lib/wavify/dsl.rb +537 -0
- data/lib/wavify/dsp/effects/chorus.rb +98 -0
- data/lib/wavify/dsp/effects/compressor.rb +85 -0
- data/lib/wavify/dsp/effects/delay.rb +69 -0
- data/lib/wavify/dsp/effects/distortion.rb +64 -0
- data/lib/wavify/dsp/effects/effect_base.rb +68 -0
- data/lib/wavify/dsp/effects/reverb.rb +112 -0
- data/lib/wavify/dsp/effects.rb +21 -0
- data/lib/wavify/dsp/envelope.rb +97 -0
- data/lib/wavify/dsp/filter.rb +271 -0
- data/lib/wavify/dsp/oscillator.rb +123 -0
- data/lib/wavify/errors.rb +34 -0
- data/lib/wavify/sequencer/engine.rb +278 -0
- data/lib/wavify/sequencer/note_sequence.rb +132 -0
- data/lib/wavify/sequencer/pattern.rb +102 -0
- data/lib/wavify/sequencer/track.rb +298 -0
- data/lib/wavify/sequencer.rb +12 -0
- data/lib/wavify/version.rb +6 -0
- data/lib/wavify.rb +28 -0
- data/tools/fixture_writer.rb +85 -0
- metadata +129 -0
|
@@ -0,0 +1,1322 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module Wavify
|
|
6
|
+
module Codecs
|
|
7
|
+
# Pure Ruby FLAC codec (metadata, decode, encode, and streaming support).
|
|
8
|
+
class Flac < Base
|
|
9
|
+
# Recognized filename extensions.
|
|
10
|
+
EXTENSIONS = %w[.flac].freeze
|
|
11
|
+
STREAMINFO_BLOCK_TYPE = 0 # :nodoc:
|
|
12
|
+
STREAMINFO_LENGTH = 34 # :nodoc:
|
|
13
|
+
FLAC_SYNC_CODE = 0x3FFE # :nodoc:
|
|
14
|
+
# Default block size used by the FLAC stream encoder.
|
|
15
|
+
DEFAULT_ENCODE_BLOCK_SIZE = 4096
|
|
16
|
+
|
|
17
|
+
BLOCK_SIZE_CODES = { # :nodoc:
|
|
18
|
+
1 => 192,
|
|
19
|
+
2 => 576,
|
|
20
|
+
3 => 1152,
|
|
21
|
+
4 => 2304,
|
|
22
|
+
5 => 4608,
|
|
23
|
+
8 => 256,
|
|
24
|
+
9 => 512,
|
|
25
|
+
10 => 1024,
|
|
26
|
+
11 => 2048,
|
|
27
|
+
12 => 4096,
|
|
28
|
+
13 => 8192,
|
|
29
|
+
14 => 16_384,
|
|
30
|
+
15 => 32_768
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
SAMPLE_RATE_CODES = { # :nodoc:
|
|
34
|
+
1 => 88_200,
|
|
35
|
+
2 => 176_400,
|
|
36
|
+
3 => 192_000,
|
|
37
|
+
4 => 8_000,
|
|
38
|
+
5 => 16_000,
|
|
39
|
+
6 => 22_050,
|
|
40
|
+
7 => 24_000,
|
|
41
|
+
8 => 32_000,
|
|
42
|
+
9 => 44_100,
|
|
43
|
+
10 => 48_000,
|
|
44
|
+
11 => 96_000
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
SAMPLE_SIZE_CODES = { # :nodoc:
|
|
48
|
+
1 => 8,
|
|
49
|
+
2 => 12,
|
|
50
|
+
4 => 16,
|
|
51
|
+
5 => 20,
|
|
52
|
+
6 => 24
|
|
53
|
+
}.freeze
|
|
54
|
+
|
|
55
|
+
# Internal bit reader used by the FLAC decoder.
|
|
56
|
+
class BitReader # :nodoc:
|
|
57
|
+
def initialize(io)
|
|
58
|
+
@io = io
|
|
59
|
+
@buffer = 0
|
|
60
|
+
@bits_available = 0
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def read_bits(count)
|
|
64
|
+
raise InvalidFormatError, "bit count must be non-negative" unless count.is_a?(Integer) && count >= 0
|
|
65
|
+
return 0 if count.zero?
|
|
66
|
+
|
|
67
|
+
value = 0
|
|
68
|
+
remaining = count
|
|
69
|
+
while remaining.positive?
|
|
70
|
+
fill_buffer_if_needed!
|
|
71
|
+
|
|
72
|
+
take = [remaining, @bits_available].min
|
|
73
|
+
shift = @bits_available - take
|
|
74
|
+
chunk = (@buffer >> shift) & ((1 << take) - 1)
|
|
75
|
+
value = (value << take) | chunk
|
|
76
|
+
@bits_available -= take
|
|
77
|
+
@buffer &= ((1 << @bits_available) - 1)
|
|
78
|
+
remaining -= take
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
value
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def read_signed_bits(count) # :nodoc:
|
|
85
|
+
value = read_bits(count)
|
|
86
|
+
sign_bit = 1 << (count - 1)
|
|
87
|
+
value.nobits?(sign_bit) ? value : (value - (1 << count))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def align_to_byte # :nodoc:
|
|
91
|
+
@buffer = 0
|
|
92
|
+
@bits_available = 0
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def fill_buffer_if_needed!
|
|
98
|
+
return unless @bits_available.zero?
|
|
99
|
+
|
|
100
|
+
byte = @io.read(1)
|
|
101
|
+
raise InvalidFormatError, "truncated FLAC frame" if byte.nil?
|
|
102
|
+
|
|
103
|
+
@buffer = byte.getbyte(0)
|
|
104
|
+
@bits_available = 8
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Internal bit writer used by the FLAC encoder.
|
|
109
|
+
class BitWriter # :nodoc:
|
|
110
|
+
def initialize
|
|
111
|
+
@bytes = []
|
|
112
|
+
@buffer = 0
|
|
113
|
+
@bits_used = 0
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def write_bits(value, count)
|
|
117
|
+
raise InvalidParameterError, "bit count must be a non-negative Integer" unless count.is_a?(Integer) && count >= 0
|
|
118
|
+
return if count.zero?
|
|
119
|
+
|
|
120
|
+
count.times do |shift_index|
|
|
121
|
+
shift = (count - 1) - shift_index
|
|
122
|
+
bit = (value >> shift) & 0x1
|
|
123
|
+
@buffer = (@buffer << 1) | bit
|
|
124
|
+
@bits_used += 1
|
|
125
|
+
flush_byte_if_needed
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def write_signed_bits(value, count) # :nodoc:
|
|
130
|
+
mask = (1 << count) - 1
|
|
131
|
+
write_bits(value & mask, count)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def write_unary_zeros_then_one(zero_count) # :nodoc:
|
|
135
|
+
zero_count.times { write_bits(0, 1) }
|
|
136
|
+
write_bits(1, 1)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def write_rice_signed(value, parameter) # :nodoc:
|
|
140
|
+
unsigned = value >= 0 ? (value << 1) : ((-value << 1) - 1)
|
|
141
|
+
quotient = unsigned >> parameter
|
|
142
|
+
remainder = parameter.zero? ? 0 : (unsigned & ((1 << parameter) - 1))
|
|
143
|
+
|
|
144
|
+
write_unary_zeros_then_one(quotient)
|
|
145
|
+
write_bits(remainder, parameter) if parameter.positive?
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def align_to_byte # :nodoc:
|
|
149
|
+
return if @bits_used.zero?
|
|
150
|
+
|
|
151
|
+
@buffer <<= (8 - @bits_used)
|
|
152
|
+
@bytes << @buffer
|
|
153
|
+
@buffer = 0
|
|
154
|
+
@bits_used = 0
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def to_s # :nodoc:
|
|
158
|
+
align_to_byte
|
|
159
|
+
@bytes.pack("C*")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
def flush_byte_if_needed
|
|
165
|
+
return unless @bits_used == 8
|
|
166
|
+
|
|
167
|
+
@bytes << @buffer
|
|
168
|
+
@buffer = 0
|
|
169
|
+
@bits_used = 0
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
class << self
|
|
174
|
+
# @param io_or_path [String, IO]
|
|
175
|
+
# @return [Boolean]
|
|
176
|
+
def can_read?(io_or_path)
|
|
177
|
+
return true if io_or_path.is_a?(String) && EXTENSIONS.include?(File.extname(io_or_path).downcase)
|
|
178
|
+
return false unless io_or_path.respond_to?(:read)
|
|
179
|
+
|
|
180
|
+
magic = io_or_path.read(4)
|
|
181
|
+
io_or_path.rewind if io_or_path.respond_to?(:rewind)
|
|
182
|
+
magic == "fLaC"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Reads a FLAC stream and returns decoded samples.
|
|
186
|
+
#
|
|
187
|
+
# @param io_or_path [String, IO]
|
|
188
|
+
# @param format [Wavify::Core::Format, nil]
|
|
189
|
+
# @return [Wavify::Core::SampleBuffer]
|
|
190
|
+
def read(io_or_path, format: nil)
|
|
191
|
+
io, close_io = open_input(io_or_path)
|
|
192
|
+
ensure_seekable!(io)
|
|
193
|
+
|
|
194
|
+
metadata = parse_metadata(io)
|
|
195
|
+
source_format = metadata.fetch(:format)
|
|
196
|
+
samples = decode_frames(io, metadata)
|
|
197
|
+
buffer = Core::SampleBuffer.new(samples, source_format)
|
|
198
|
+
format ? buffer.convert(format) : buffer
|
|
199
|
+
ensure
|
|
200
|
+
io.close if close_io && io
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Writes a sample buffer as FLAC.
|
|
204
|
+
#
|
|
205
|
+
# @param io_or_path [String, IO]
|
|
206
|
+
# @param sample_buffer [Wavify::Core::SampleBuffer]
|
|
207
|
+
# @param format [Wavify::Core::Format]
|
|
208
|
+
# @return [String, IO]
|
|
209
|
+
def write(io_or_path, sample_buffer, format:)
|
|
210
|
+
raise InvalidParameterError, "sample_buffer must be Core::SampleBuffer" unless sample_buffer.is_a?(Core::SampleBuffer)
|
|
211
|
+
|
|
212
|
+
target_format = validate_encode_format!(format)
|
|
213
|
+
buffer = sample_buffer.format == target_format ? sample_buffer : sample_buffer.convert(target_format)
|
|
214
|
+
|
|
215
|
+
io, close_io = open_output(io_or_path)
|
|
216
|
+
io.rewind if io.respond_to?(:rewind)
|
|
217
|
+
io.truncate(0) if io.respond_to?(:truncate)
|
|
218
|
+
io.write(encode_verbatim_stream(buffer, target_format))
|
|
219
|
+
io.flush if io.respond_to?(:flush)
|
|
220
|
+
io.rewind if io.respond_to?(:rewind)
|
|
221
|
+
io_or_path
|
|
222
|
+
ensure
|
|
223
|
+
io.close if close_io && io
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Streams FLAC decoding as chunked sample buffers.
|
|
227
|
+
#
|
|
228
|
+
# @param io_or_path [String, IO]
|
|
229
|
+
# @param chunk_size [Integer]
|
|
230
|
+
# @return [Enumerator]
|
|
231
|
+
def stream_read(io_or_path, chunk_size: 4096)
|
|
232
|
+
return enum_for(__method__, io_or_path, chunk_size: chunk_size) unless block_given?
|
|
233
|
+
raise InvalidParameterError, "chunk_size must be a positive Integer" unless chunk_size.is_a?(Integer) && chunk_size.positive?
|
|
234
|
+
|
|
235
|
+
io, close_io = open_input(io_or_path)
|
|
236
|
+
ensure_seekable!(io)
|
|
237
|
+
|
|
238
|
+
metadata = parse_metadata(io)
|
|
239
|
+
format = metadata.fetch(:format)
|
|
240
|
+
chunk_sample_count = chunk_size * format.channels
|
|
241
|
+
pending_samples = []
|
|
242
|
+
|
|
243
|
+
each_decoded_frame_samples(io, metadata) do |frame_samples|
|
|
244
|
+
pending_samples.concat(frame_samples)
|
|
245
|
+
|
|
246
|
+
while pending_samples.length >= chunk_sample_count
|
|
247
|
+
yield Core::SampleBuffer.new(pending_samples.shift(chunk_sample_count), format)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
yield Core::SampleBuffer.new(pending_samples, format) unless pending_samples.empty?
|
|
252
|
+
ensure
|
|
253
|
+
io.close if close_io && io
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Streams FLAC encoding and finalizes STREAMINFO on completion.
|
|
257
|
+
#
|
|
258
|
+
# @param io_or_path [String, IO]
|
|
259
|
+
# @param format [Wavify::Core::Format]
|
|
260
|
+
# @param block_size [Integer]
|
|
261
|
+
# @param block_size_strategy [Symbol] `:per_chunk`, `:fixed`, or `:source_chunk`
|
|
262
|
+
# @return [Enumerator, String, IO]
|
|
263
|
+
def stream_write(io_or_path, format:, block_size: DEFAULT_ENCODE_BLOCK_SIZE, block_size_strategy: :per_chunk)
|
|
264
|
+
unless block_given?
|
|
265
|
+
return enum_for(
|
|
266
|
+
__method__,
|
|
267
|
+
io_or_path,
|
|
268
|
+
format: format,
|
|
269
|
+
block_size: block_size,
|
|
270
|
+
block_size_strategy: block_size_strategy
|
|
271
|
+
)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
target_format = validate_encode_format!(format)
|
|
275
|
+
stream_write_options = normalize_stream_write_options(block_size, block_size_strategy)
|
|
276
|
+
io, close_io = open_output(io_or_path)
|
|
277
|
+
ensure_seekable!(io)
|
|
278
|
+
io.rewind if io.respond_to?(:rewind)
|
|
279
|
+
io.truncate(0) if io.respond_to?(:truncate)
|
|
280
|
+
|
|
281
|
+
header = write_stream_header(io)
|
|
282
|
+
total_sample_frames = 0
|
|
283
|
+
next_frame_number = 0
|
|
284
|
+
encode_stats = empty_encode_stats
|
|
285
|
+
header[:md5] = Digest::MD5.new
|
|
286
|
+
pending_samples = []
|
|
287
|
+
|
|
288
|
+
writer = lambda do |chunk|
|
|
289
|
+
raise InvalidParameterError, "stream chunk must be Core::SampleBuffer" unless chunk.is_a?(Core::SampleBuffer)
|
|
290
|
+
|
|
291
|
+
buffer = chunk.format == target_format ? chunk : chunk.convert(target_format)
|
|
292
|
+
header.fetch(:md5).update(pcm_bytes_for_md5(buffer.samples, target_format))
|
|
293
|
+
total_sample_frames += buffer.sample_frame_count
|
|
294
|
+
|
|
295
|
+
if stream_write_options[:strategy] == :fixed
|
|
296
|
+
pending_samples.concat(buffer.samples)
|
|
297
|
+
fixed_chunk_sample_count = stream_write_options.fetch(:block_size) * target_format.channels
|
|
298
|
+
|
|
299
|
+
while pending_samples.length >= fixed_chunk_sample_count
|
|
300
|
+
encoded = encode_verbatim_frames(
|
|
301
|
+
pending_samples.shift(fixed_chunk_sample_count),
|
|
302
|
+
target_format,
|
|
303
|
+
start_frame_number: next_frame_number,
|
|
304
|
+
block_size: stream_write_options.fetch(:block_size)
|
|
305
|
+
)
|
|
306
|
+
io.write(encoded.fetch(:bytes))
|
|
307
|
+
next_frame_number = encoded.fetch(:next_frame_number)
|
|
308
|
+
merge_encode_stats!(encode_stats, encoded)
|
|
309
|
+
end
|
|
310
|
+
elsif stream_write_options[:strategy] == :source_chunk
|
|
311
|
+
encoded = encode_verbatim_frames(
|
|
312
|
+
buffer.samples,
|
|
313
|
+
target_format,
|
|
314
|
+
start_frame_number: next_frame_number,
|
|
315
|
+
block_size: buffer.sample_frame_count
|
|
316
|
+
)
|
|
317
|
+
io.write(encoded.fetch(:bytes))
|
|
318
|
+
next_frame_number = encoded.fetch(:next_frame_number)
|
|
319
|
+
merge_encode_stats!(encode_stats, encoded)
|
|
320
|
+
else
|
|
321
|
+
encoded = encode_verbatim_frames(
|
|
322
|
+
buffer.samples,
|
|
323
|
+
target_format,
|
|
324
|
+
start_frame_number: next_frame_number,
|
|
325
|
+
block_size: stream_write_options.fetch(:block_size)
|
|
326
|
+
)
|
|
327
|
+
io.write(encoded.fetch(:bytes))
|
|
328
|
+
next_frame_number = encoded.fetch(:next_frame_number)
|
|
329
|
+
merge_encode_stats!(encode_stats, encoded)
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
yield writer
|
|
334
|
+
|
|
335
|
+
unless pending_samples.empty?
|
|
336
|
+
encoded = encode_verbatim_frames(
|
|
337
|
+
pending_samples,
|
|
338
|
+
target_format,
|
|
339
|
+
start_frame_number: next_frame_number,
|
|
340
|
+
block_size: stream_write_options.fetch(:block_size)
|
|
341
|
+
)
|
|
342
|
+
io.write(encoded.fetch(:bytes))
|
|
343
|
+
next_frame_number = encoded.fetch(:next_frame_number)
|
|
344
|
+
merge_encode_stats!(encode_stats, encoded)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
finalize_stream_header(io, header, target_format, total_sample_frames, encode_stats)
|
|
348
|
+
io.flush if io.respond_to?(:flush)
|
|
349
|
+
io.rewind if io.respond_to?(:rewind)
|
|
350
|
+
io_or_path
|
|
351
|
+
ensure
|
|
352
|
+
io.close if close_io && io
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Reads FLAC metadata (including STREAMINFO-derived format/duration).
|
|
356
|
+
#
|
|
357
|
+
# @param io_or_path [String, IO]
|
|
358
|
+
# @return [Hash]
|
|
359
|
+
def metadata(io_or_path)
|
|
360
|
+
io, close_io = open_input(io_or_path)
|
|
361
|
+
ensure_seekable!(io)
|
|
362
|
+
|
|
363
|
+
parse_metadata(io)
|
|
364
|
+
ensure
|
|
365
|
+
io.close if close_io && io
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
private
|
|
369
|
+
|
|
370
|
+
def parse_metadata(io)
|
|
371
|
+
io.rewind
|
|
372
|
+
marker = read_exact(io, 4, "missing FLAC stream marker")
|
|
373
|
+
raise InvalidFormatError, "invalid FLAC stream marker" unless marker == "fLaC"
|
|
374
|
+
|
|
375
|
+
streaminfo = nil
|
|
376
|
+
loop do
|
|
377
|
+
header = read_exact(io, 4, "truncated FLAC metadata block header")
|
|
378
|
+
byte0 = header.getbyte(0)
|
|
379
|
+
last_block = byte0.anybits?(0x80)
|
|
380
|
+
block_type = byte0 & 0x7F
|
|
381
|
+
length = ((header.getbyte(1) << 16) | (header.getbyte(2) << 8) | header.getbyte(3))
|
|
382
|
+
data = read_exact(io, length, "truncated FLAC metadata block")
|
|
383
|
+
|
|
384
|
+
streaminfo = parse_streaminfo(data) if block_type == STREAMINFO_BLOCK_TYPE
|
|
385
|
+
break if last_block
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
raise InvalidFormatError, "STREAMINFO metadata block missing" unless streaminfo
|
|
389
|
+
|
|
390
|
+
streaminfo
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def parse_streaminfo(data)
|
|
394
|
+
raise InvalidFormatError, "STREAMINFO block must be 34 bytes" unless data.bytesize == STREAMINFO_LENGTH
|
|
395
|
+
|
|
396
|
+
min_block_size, max_block_size = data[0, 4].unpack("n2")
|
|
397
|
+
min_frame_size = unpack_uint24(data[4, 3])
|
|
398
|
+
max_frame_size = unpack_uint24(data[7, 3])
|
|
399
|
+
|
|
400
|
+
packed = data[10, 8].unpack1("Q>")
|
|
401
|
+
sample_rate = (packed >> 44) & 0xFFFFF
|
|
402
|
+
channels = ((packed >> 41) & 0x7) + 1
|
|
403
|
+
bit_depth = ((packed >> 36) & 0x1F) + 1
|
|
404
|
+
total_samples = packed & 0xFFFFFFFFF
|
|
405
|
+
md5 = data[18, 16].unpack1("H*")
|
|
406
|
+
|
|
407
|
+
format = Core::Format.new(
|
|
408
|
+
channels: channels,
|
|
409
|
+
sample_rate: sample_rate,
|
|
410
|
+
bit_depth: bit_depth,
|
|
411
|
+
sample_format: :pcm
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
{
|
|
415
|
+
format: format,
|
|
416
|
+
sample_frame_count: total_samples,
|
|
417
|
+
duration: Core::Duration.from_samples(total_samples, sample_rate),
|
|
418
|
+
min_block_size: min_block_size,
|
|
419
|
+
max_block_size: max_block_size,
|
|
420
|
+
min_frame_size: min_frame_size,
|
|
421
|
+
max_frame_size: max_frame_size,
|
|
422
|
+
md5: md5
|
|
423
|
+
}
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def encode_verbatim_stream(buffer, format)
|
|
427
|
+
encoded_frames = encode_verbatim_frames(
|
|
428
|
+
buffer.samples,
|
|
429
|
+
format,
|
|
430
|
+
start_frame_number: 0,
|
|
431
|
+
block_size: DEFAULT_ENCODE_BLOCK_SIZE
|
|
432
|
+
)
|
|
433
|
+
md5_hex = pcm_md5_hex(buffer.samples, format)
|
|
434
|
+
|
|
435
|
+
streaminfo = build_streaminfo_bytes(
|
|
436
|
+
format: format,
|
|
437
|
+
sample_frame_count: buffer.sample_frame_count,
|
|
438
|
+
stats: encoded_frames,
|
|
439
|
+
md5_hex: md5_hex
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
bytes = +"fLaC"
|
|
443
|
+
bytes << [0x80, 0x00, 0x00, STREAMINFO_LENGTH].pack("C4")
|
|
444
|
+
bytes << streaminfo
|
|
445
|
+
bytes << encoded_frames.fetch(:bytes)
|
|
446
|
+
bytes
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def write_stream_header(io)
|
|
450
|
+
io.write("fLaC")
|
|
451
|
+
io.write([0x80, 0x00, 0x00, STREAMINFO_LENGTH].pack("C4"))
|
|
452
|
+
streaminfo_offset = io.pos
|
|
453
|
+
io.write("\x00" * STREAMINFO_LENGTH)
|
|
454
|
+
{ streaminfo_offset: streaminfo_offset }
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def finalize_stream_header(io, header, format, total_sample_frames, encode_stats)
|
|
458
|
+
file_end = io.pos
|
|
459
|
+
io.seek(header.fetch(:streaminfo_offset), IO::SEEK_SET)
|
|
460
|
+
io.write(
|
|
461
|
+
build_streaminfo_bytes(
|
|
462
|
+
format: format,
|
|
463
|
+
sample_frame_count: total_sample_frames,
|
|
464
|
+
stats: encode_stats,
|
|
465
|
+
md5_hex: header.fetch(:md5).hexdigest
|
|
466
|
+
)
|
|
467
|
+
)
|
|
468
|
+
io.seek(file_end, IO::SEEK_SET)
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def empty_encode_stats
|
|
472
|
+
{
|
|
473
|
+
min_block_size: 0,
|
|
474
|
+
max_block_size: 0,
|
|
475
|
+
min_frame_size: 0,
|
|
476
|
+
max_frame_size: 0
|
|
477
|
+
}
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def normalize_stream_write_options(block_size, block_size_strategy)
|
|
481
|
+
strategy = block_size_strategy.to_sym
|
|
482
|
+
supported = %i[per_chunk source_chunk fixed]
|
|
483
|
+
unless supported.include?(strategy)
|
|
484
|
+
raise InvalidParameterError, "unsupported FLAC stream_write block_size_strategy: #{block_size_strategy.inspect}"
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
{
|
|
488
|
+
strategy: strategy,
|
|
489
|
+
block_size: normalize_encode_block_size(block_size)
|
|
490
|
+
}
|
|
491
|
+
rescue NoMethodError
|
|
492
|
+
raise InvalidParameterError, "block_size_strategy must be Symbol/String: #{block_size_strategy.inspect}"
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def merge_encode_stats!(aggregate, encoded)
|
|
496
|
+
current_min_block = encoded.fetch(:min_block_size)
|
|
497
|
+
current_max_block = encoded.fetch(:max_block_size)
|
|
498
|
+
current_min_frame = encoded.fetch(:min_frame_size)
|
|
499
|
+
current_max_frame = encoded.fetch(:max_frame_size)
|
|
500
|
+
return if current_max_block.zero?
|
|
501
|
+
|
|
502
|
+
aggregate[:min_block_size] =
|
|
503
|
+
aggregate[:min_block_size].zero? ? current_min_block : [aggregate[:min_block_size], current_min_block].min
|
|
504
|
+
aggregate[:max_block_size] = [aggregate[:max_block_size], current_max_block].max
|
|
505
|
+
aggregate[:min_frame_size] =
|
|
506
|
+
aggregate[:min_frame_size].zero? ? current_min_frame : [aggregate[:min_frame_size], current_min_frame].min
|
|
507
|
+
aggregate[:max_frame_size] = [aggregate[:max_frame_size], current_max_frame].max
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def encode_verbatim_frames(interleaved_samples, format, start_frame_number:, block_size:)
|
|
511
|
+
channels = format.channels
|
|
512
|
+
samples_per_frame = channels * normalize_encode_block_size(block_size)
|
|
513
|
+
bytes = +""
|
|
514
|
+
frame_number = start_frame_number
|
|
515
|
+
block_sizes = []
|
|
516
|
+
frame_sizes = []
|
|
517
|
+
|
|
518
|
+
interleaved_samples.each_slice(samples_per_frame) do |frame_samples|
|
|
519
|
+
encoded_frame = encode_pcm_frame(frame_samples, format, frame_number: frame_number)
|
|
520
|
+
bytes << encoded_frame
|
|
521
|
+
block_sizes << (frame_samples.length / channels)
|
|
522
|
+
frame_sizes << encoded_frame.bytesize
|
|
523
|
+
frame_number += 1
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
{
|
|
527
|
+
bytes: bytes,
|
|
528
|
+
next_frame_number: frame_number,
|
|
529
|
+
min_block_size: block_sizes.min || 0,
|
|
530
|
+
max_block_size: block_sizes.max || 0,
|
|
531
|
+
min_frame_size: frame_sizes.min || 0,
|
|
532
|
+
max_frame_size: frame_sizes.max || 0
|
|
533
|
+
}
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def encode_pcm_frame(interleaved_samples, format, frame_number:)
|
|
537
|
+
channels = format.channels
|
|
538
|
+
block_size = interleaved_samples.length / channels
|
|
539
|
+
raise InvalidParameterError, "FLAC frame block size must be positive" if block_size <= 0
|
|
540
|
+
|
|
541
|
+
block_size_code, block_size_extra_bits = encode_block_size_descriptor(block_size)
|
|
542
|
+
channel_samples = deinterleave_samples(interleaved_samples, channels)
|
|
543
|
+
header_without_crc8 = build_frame_header_bytes(
|
|
544
|
+
block_size: block_size,
|
|
545
|
+
block_size_code: block_size_code,
|
|
546
|
+
block_size_extra_bits: block_size_extra_bits,
|
|
547
|
+
channels: channels,
|
|
548
|
+
frame_number: frame_number
|
|
549
|
+
)
|
|
550
|
+
header_crc8 = flac_crc8(header_without_crc8)
|
|
551
|
+
|
|
552
|
+
payload_writer = BitWriter.new
|
|
553
|
+
channel_samples.each do |channel|
|
|
554
|
+
write_best_subframe(payload_writer, channel, format.bit_depth)
|
|
555
|
+
end
|
|
556
|
+
payload_writer.align_to_byte
|
|
557
|
+
payload_bytes = payload_writer.to_s
|
|
558
|
+
|
|
559
|
+
crc16_input = header_without_crc8 + [header_crc8].pack("C") + payload_bytes
|
|
560
|
+
crc16 = flac_crc16(crc16_input)
|
|
561
|
+
|
|
562
|
+
crc16_input + [crc16].pack("n")
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def write_best_subframe(writer, channel_samples, sample_size)
|
|
566
|
+
selection = select_subframe_encoding(channel_samples, sample_size)
|
|
567
|
+
|
|
568
|
+
if selection[:kind] == :fixed
|
|
569
|
+
write_fixed_subframe(
|
|
570
|
+
writer,
|
|
571
|
+
channel_samples,
|
|
572
|
+
sample_size,
|
|
573
|
+
selection: selection
|
|
574
|
+
)
|
|
575
|
+
return
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
write_verbatim_subframe(writer, channel_samples, sample_size)
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def select_subframe_encoding(channel_samples, sample_size)
|
|
582
|
+
best = {
|
|
583
|
+
kind: :verbatim,
|
|
584
|
+
bit_length: verbatim_subframe_bit_length(channel_samples.length, sample_size)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
max_predictor_order = [4, channel_samples.length - 1].min
|
|
588
|
+
(0..max_predictor_order).each do |predictor_order|
|
|
589
|
+
candidate = build_fixed_subframe_encoding(channel_samples, sample_size, predictor_order)
|
|
590
|
+
next unless candidate
|
|
591
|
+
next unless candidate.fetch(:bit_length) < best.fetch(:bit_length)
|
|
592
|
+
|
|
593
|
+
best = candidate
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
best
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def verbatim_subframe_bit_length(sample_count, sample_size)
|
|
600
|
+
8 + (sample_count * sample_size)
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def build_fixed_subframe_encoding(channel_samples, sample_size, predictor_order)
|
|
604
|
+
residuals = fixed_subframe_residuals(channel_samples, predictor_order)
|
|
605
|
+
residual_encoding = choose_residual_encoding(residuals)
|
|
606
|
+
return nil unless residual_encoding
|
|
607
|
+
|
|
608
|
+
{
|
|
609
|
+
kind: :fixed,
|
|
610
|
+
predictor_order: predictor_order,
|
|
611
|
+
residuals: residuals,
|
|
612
|
+
residual_encoding: residual_encoding,
|
|
613
|
+
bit_length: 8 + (predictor_order * sample_size) + residual_encoding.fetch(:bit_length)
|
|
614
|
+
}
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def fixed_subframe_residuals(samples, predictor_order)
|
|
618
|
+
history = samples.first(predictor_order).dup
|
|
619
|
+
|
|
620
|
+
samples.drop(predictor_order).each_with_object([]) do |sample, residuals|
|
|
621
|
+
predicted = fixed_predictor_value(history, predictor_order)
|
|
622
|
+
residuals << (sample - predicted)
|
|
623
|
+
history << sample
|
|
624
|
+
history.shift if predictor_order.positive? && history.length > predictor_order
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def choose_residual_encoding(residuals)
|
|
629
|
+
rice_candidates = (0..14).map do |parameter|
|
|
630
|
+
{
|
|
631
|
+
kind: :rice,
|
|
632
|
+
parameter: parameter,
|
|
633
|
+
bit_length: rice_partition0_bit_length(residuals, parameter)
|
|
634
|
+
}
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
escape_candidate = escape_residual_encoding(residuals)
|
|
638
|
+
candidates = rice_candidates
|
|
639
|
+
candidates << escape_candidate if escape_candidate
|
|
640
|
+
candidates.min_by { |candidate| candidate.fetch(:bit_length) }
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
def rice_partition0_bit_length(residuals, parameter)
|
|
644
|
+
residuals.reduce(6 + 4) do |bits, residual|
|
|
645
|
+
unsigned = residual >= 0 ? (residual << 1) : ((-residual << 1) - 1)
|
|
646
|
+
quotient = unsigned >> parameter
|
|
647
|
+
bits + quotient + 1 + parameter
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
def escape_residual_encoding(residuals)
|
|
652
|
+
raw_bits = residuals.map { |residual| signed_bit_width(residual) }.max.to_i
|
|
653
|
+
return nil if raw_bits > 31
|
|
654
|
+
|
|
655
|
+
{
|
|
656
|
+
kind: :escape,
|
|
657
|
+
raw_bits: raw_bits,
|
|
658
|
+
bit_length: 6 + 4 + 5 + (residuals.length * raw_bits)
|
|
659
|
+
}
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
def signed_bit_width(value)
|
|
663
|
+
return 0 if value.zero?
|
|
664
|
+
|
|
665
|
+
bits = 1
|
|
666
|
+
bits += 1 until value.between?(-(1 << (bits - 1)), (1 << (bits - 1)) - 1)
|
|
667
|
+
bits
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
def write_fixed_subframe(writer, channel_samples, sample_size, selection:)
|
|
671
|
+
predictor_order = selection.fetch(:predictor_order)
|
|
672
|
+
residual_encoding = selection.fetch(:residual_encoding)
|
|
673
|
+
residuals = selection.fetch(:residuals)
|
|
674
|
+
|
|
675
|
+
writer.write_bits(0, 1) # padding bit
|
|
676
|
+
writer.write_bits(8 + predictor_order, 6)
|
|
677
|
+
writer.write_bits(0, 1) # no wasted bits
|
|
678
|
+
|
|
679
|
+
channel_samples.first(predictor_order).each { |sample| writer.write_signed_bits(sample, sample_size) }
|
|
680
|
+
write_partition0_residuals(writer, residual_encoding, residuals)
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
def write_partition0_residuals(writer, residual_encoding, residuals)
|
|
684
|
+
if residual_encoding[:kind] == :rice
|
|
685
|
+
writer.write_bits(0, 2) # Rice
|
|
686
|
+
writer.write_bits(0, 4) # partition order = 0
|
|
687
|
+
writer.write_bits(residual_encoding.fetch(:parameter), 4)
|
|
688
|
+
residuals.each { |residual| writer.write_rice_signed(residual, residual_encoding.fetch(:parameter)) }
|
|
689
|
+
return
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
writer.write_bits(0, 2) # Rice coding method family
|
|
693
|
+
writer.write_bits(0, 4) # partition order = 0
|
|
694
|
+
writer.write_bits(0xF, 4) # escape code
|
|
695
|
+
raw_bits = residual_encoding.fetch(:raw_bits)
|
|
696
|
+
writer.write_bits(raw_bits, 5)
|
|
697
|
+
residuals.each { |residual| writer.write_signed_bits(residual, raw_bits) } if raw_bits.positive?
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
def build_frame_header_bytes(block_size:, block_size_code:, block_size_extra_bits:, channels:, frame_number:)
|
|
701
|
+
writer = BitWriter.new
|
|
702
|
+
writer.write_bits(FLAC_SYNC_CODE, 14)
|
|
703
|
+
writer.write_bits(0, 1) # reserved
|
|
704
|
+
writer.write_bits(0, 1) # fixed-blocksize stream
|
|
705
|
+
writer.write_bits(block_size_code, 4)
|
|
706
|
+
writer.write_bits(0, 4) # sample rate from STREAMINFO
|
|
707
|
+
writer.write_bits(channels - 1, 4) # independent channels
|
|
708
|
+
writer.write_bits(0, 3) # sample size from STREAMINFO
|
|
709
|
+
writer.write_bits(0, 1) # reserved
|
|
710
|
+
write_utf8_uint(writer, frame_number)
|
|
711
|
+
writer.write_bits(block_size - 1, block_size_extra_bits) if block_size_extra_bits.positive?
|
|
712
|
+
writer.align_to_byte
|
|
713
|
+
writer.to_s
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def write_verbatim_subframe(writer, channel_samples, sample_size)
|
|
717
|
+
writer.write_bits(0, 1) # padding bit
|
|
718
|
+
writer.write_bits(1, 6) # verbatim subframe type
|
|
719
|
+
writer.write_bits(0, 1) # no wasted bits
|
|
720
|
+
channel_samples.each { |sample| writer.write_signed_bits(sample, sample_size) }
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
def deinterleave_samples(interleaved_samples, channels)
|
|
724
|
+
channel_samples = Array.new(channels) { [] }
|
|
725
|
+
|
|
726
|
+
interleaved_samples.each_slice(channels) do |frame|
|
|
727
|
+
channels.times do |channel_index|
|
|
728
|
+
channel_samples[channel_index] << frame.fetch(channel_index)
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
channel_samples
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def encode_block_size_descriptor(block_size)
|
|
736
|
+
raise UnsupportedFormatError, "FLAC block size exceeds 65536 samples" if block_size > 65_536
|
|
737
|
+
|
|
738
|
+
if block_size <= 256
|
|
739
|
+
[6, 8]
|
|
740
|
+
else
|
|
741
|
+
[7, 16]
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
def write_utf8_uint(writer, value)
|
|
746
|
+
raise InvalidParameterError, "FLAC frame number must be non-negative Integer" unless value.is_a?(Integer) && value >= 0
|
|
747
|
+
|
|
748
|
+
if value <= 0x7F
|
|
749
|
+
writer.write_bits(value, 8)
|
|
750
|
+
return
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
payload_bits = Math.log2(value + 1).floor + 1
|
|
754
|
+
length = 2
|
|
755
|
+
length += 1 while payload_capacity_for_utf8_uint(length) < payload_bits
|
|
756
|
+
raise UnsupportedFormatError, "FLAC frame number is too large to encode" if length > 7
|
|
757
|
+
|
|
758
|
+
bytes = Array.new(length, 0)
|
|
759
|
+
remaining = value
|
|
760
|
+
(length - 1).downto(1) do |index|
|
|
761
|
+
bytes[index] = 0x80 | (remaining & 0x3F)
|
|
762
|
+
remaining >>= 6
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
prefix = ((1 << length) - 1) << (8 - length)
|
|
766
|
+
bytes[0] = prefix | remaining
|
|
767
|
+
|
|
768
|
+
bytes.each { |byte| writer.write_bits(byte, 8) }
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
def payload_capacity_for_utf8_uint(length)
|
|
772
|
+
(7 - length) + (6 * (length - 1))
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
def normalize_encode_block_size(block_size)
|
|
776
|
+
size = block_size.to_i
|
|
777
|
+
raise InvalidParameterError, "FLAC block_size must be a positive Integer" unless size.positive?
|
|
778
|
+
|
|
779
|
+
[size, 65_536].min
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
def build_streaminfo_bytes(format:, sample_frame_count:, stats:, md5_hex:)
|
|
783
|
+
raise UnsupportedFormatError, "FLAC total sample count exceeds 36-bit STREAMINFO limit" if sample_frame_count > 0xFFFFFFFFF
|
|
784
|
+
|
|
785
|
+
min_block_size = stats.fetch(:min_block_size)
|
|
786
|
+
max_block_size = stats.fetch(:max_block_size)
|
|
787
|
+
min_frame_size = stats.fetch(:min_frame_size)
|
|
788
|
+
max_frame_size = stats.fetch(:max_frame_size)
|
|
789
|
+
md5_bytes = [md5_hex].pack("H*")
|
|
790
|
+
raise InvalidParameterError, "md5_hex must be 32 hex characters" unless md5_bytes.bytesize == 16
|
|
791
|
+
|
|
792
|
+
packed = ((format.sample_rate & 0xFFFFF) << 44) |
|
|
793
|
+
(((format.channels - 1) & 0x7) << 41) |
|
|
794
|
+
(((format.bit_depth - 1) & 0x1F) << 36) |
|
|
795
|
+
(sample_frame_count & 0xFFFFFFFFF)
|
|
796
|
+
|
|
797
|
+
[min_block_size, max_block_size].pack("n2") +
|
|
798
|
+
pack_uint24(min_frame_size) +
|
|
799
|
+
pack_uint24(max_frame_size) +
|
|
800
|
+
[packed].pack("Q>") +
|
|
801
|
+
md5_bytes
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def pcm_md5_hex(samples, format)
|
|
805
|
+
Digest::MD5.hexdigest(pcm_bytes_for_md5(samples, format))
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
def pcm_bytes_for_md5(samples, format)
|
|
809
|
+
case format.bit_depth
|
|
810
|
+
when 8
|
|
811
|
+
samples.pack("c*")
|
|
812
|
+
when 16
|
|
813
|
+
samples.pack("s<*")
|
|
814
|
+
when 24
|
|
815
|
+
encode_pcm24_le(samples)
|
|
816
|
+
when 32
|
|
817
|
+
samples.pack("l<*")
|
|
818
|
+
else
|
|
819
|
+
raise UnsupportedFormatError, "unsupported FLAC MD5 PCM bit depth: #{format.bit_depth}"
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
def encode_pcm24_le(samples)
|
|
824
|
+
bytes = samples.flat_map do |sample|
|
|
825
|
+
value = sample
|
|
826
|
+
value += 0x1000000 if value.negative?
|
|
827
|
+
[value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF]
|
|
828
|
+
end
|
|
829
|
+
bytes.pack("C*")
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
def flac_crc8(data)
|
|
833
|
+
crc = 0
|
|
834
|
+
data.each_byte do |byte|
|
|
835
|
+
crc ^= byte
|
|
836
|
+
8.times do
|
|
837
|
+
crc = crc.anybits?(0x80) ? ((crc << 1) ^ 0x07) : (crc << 1)
|
|
838
|
+
crc &= 0xFF
|
|
839
|
+
end
|
|
840
|
+
end
|
|
841
|
+
crc
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def flac_crc16(data)
|
|
845
|
+
crc = 0
|
|
846
|
+
data.each_byte do |byte|
|
|
847
|
+
crc ^= (byte << 8)
|
|
848
|
+
8.times do
|
|
849
|
+
crc = crc.anybits?(0x8000) ? ((crc << 1) ^ 0x8005) : (crc << 1)
|
|
850
|
+
crc &= 0xFFFF
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
crc
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
def pack_uint24(value)
|
|
857
|
+
raise InvalidParameterError, "value must fit in uint24" unless value.is_a?(Integer) && value.between?(0, 0xFFFFFF)
|
|
858
|
+
|
|
859
|
+
[(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF].pack("C3")
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
def validate_encode_format!(format)
|
|
863
|
+
raise InvalidParameterError, "format must be Core::Format" unless format.is_a?(Core::Format)
|
|
864
|
+
raise UnsupportedFormatError, "FLAC encoding only supports PCM sample format" unless format.sample_format == :pcm
|
|
865
|
+
raise UnsupportedFormatError, "FLAC encoding supports 1..8 channels" unless format.channels.between?(1, 8)
|
|
866
|
+
raise UnsupportedFormatError, "FLAC encoding supports up to 32-bit PCM" if format.bit_depth > 32
|
|
867
|
+
|
|
868
|
+
format
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
def decode_frames(io, metadata)
|
|
872
|
+
decoded_samples = []
|
|
873
|
+
each_decoded_frame_samples(io, metadata) { |frame_samples| decoded_samples.concat(frame_samples) }
|
|
874
|
+
decoded_samples
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
def each_decoded_frame_samples(io, metadata)
|
|
878
|
+
return enum_for(__method__, io, metadata) unless block_given?
|
|
879
|
+
|
|
880
|
+
format = metadata.fetch(:format)
|
|
881
|
+
remaining_frames = metadata[:sample_frame_count]
|
|
882
|
+
bounded_total = remaining_frames.is_a?(Integer) && remaining_frames.positive?
|
|
883
|
+
|
|
884
|
+
until io.eof?
|
|
885
|
+
break if bounded_total && remaining_frames <= 0
|
|
886
|
+
|
|
887
|
+
next_byte = io.read(1)
|
|
888
|
+
break if next_byte.nil?
|
|
889
|
+
|
|
890
|
+
io.seek(-1, IO::SEEK_CUR)
|
|
891
|
+
frame_samples = decode_frame(io, metadata)
|
|
892
|
+
|
|
893
|
+
if bounded_total
|
|
894
|
+
max_samples = remaining_frames * format.channels
|
|
895
|
+
frame_samples = frame_samples.first(max_samples) if frame_samples.length > max_samples
|
|
896
|
+
|
|
897
|
+
decoded_frame_count = frame_samples.length / format.channels
|
|
898
|
+
remaining_frames -= decoded_frame_count
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
yield frame_samples unless frame_samples.empty?
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
return unless bounded_total && remaining_frames.positive?
|
|
905
|
+
|
|
906
|
+
raise InvalidFormatError, "decoded FLAC samples are shorter than STREAMINFO total sample count"
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
def decode_frame(io, metadata)
|
|
910
|
+
bit_reader = BitReader.new(io)
|
|
911
|
+
frame_header = parse_frame_header(bit_reader, metadata)
|
|
912
|
+
channel_samples = decode_subframes(bit_reader, frame_header)
|
|
913
|
+
channel_samples = restore_channel_assignment(channel_samples, frame_header)
|
|
914
|
+
bit_reader.align_to_byte
|
|
915
|
+
_crc16 = bit_reader.read_bits(16)
|
|
916
|
+
|
|
917
|
+
interleave_channels(channel_samples, frame_header.fetch(:block_size), frame_header.fetch(:channels))
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
def parse_frame_header(bit_reader, metadata)
|
|
921
|
+
sync = bit_reader.read_bits(14)
|
|
922
|
+
raise InvalidFormatError, "invalid FLAC frame sync code" unless sync == FLAC_SYNC_CODE
|
|
923
|
+
|
|
924
|
+
reserved = bit_reader.read_bits(1)
|
|
925
|
+
raise InvalidFormatError, "reserved FLAC frame header bit must be 0" unless reserved.zero?
|
|
926
|
+
|
|
927
|
+
blocking_strategy = bit_reader.read_bits(1)
|
|
928
|
+
raise UnsupportedFormatError, "FLAC variable-blocksize frames are not supported yet" unless blocking_strategy.zero?
|
|
929
|
+
|
|
930
|
+
block_size_code = bit_reader.read_bits(4)
|
|
931
|
+
sample_rate_code = bit_reader.read_bits(4)
|
|
932
|
+
channel_assignment = bit_reader.read_bits(4)
|
|
933
|
+
sample_size_code = bit_reader.read_bits(3)
|
|
934
|
+
reserved2 = bit_reader.read_bits(1)
|
|
935
|
+
raise InvalidFormatError, "reserved FLAC frame header bit must be 0" unless reserved2.zero?
|
|
936
|
+
|
|
937
|
+
_frame_number = read_utf8_uint(bit_reader)
|
|
938
|
+
block_size = decode_block_size(block_size_code, bit_reader)
|
|
939
|
+
sample_rate = decode_sample_rate(sample_rate_code, bit_reader, metadata.fetch(:format).sample_rate)
|
|
940
|
+
sample_size = decode_sample_size(sample_size_code, metadata.fetch(:format).bit_depth)
|
|
941
|
+
_crc8 = bit_reader.read_bits(8)
|
|
942
|
+
|
|
943
|
+
channels = decode_channel_count(channel_assignment, metadata.fetch(:format).channels)
|
|
944
|
+
|
|
945
|
+
{
|
|
946
|
+
block_size: block_size,
|
|
947
|
+
sample_rate: sample_rate,
|
|
948
|
+
sample_size: sample_size,
|
|
949
|
+
channels: channels,
|
|
950
|
+
channel_assignment: channel_assignment
|
|
951
|
+
}
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
def read_utf8_uint(bit_reader)
|
|
955
|
+
first = bit_reader.read_bits(8)
|
|
956
|
+
return first if first.nobits?(0x80)
|
|
957
|
+
|
|
958
|
+
mask = 0x80
|
|
959
|
+
length = 0
|
|
960
|
+
while first.anybits?(mask)
|
|
961
|
+
length += 1
|
|
962
|
+
mask >>= 1
|
|
963
|
+
end
|
|
964
|
+
raise InvalidFormatError, "invalid UTF-8 integer in FLAC frame header" if length < 2 || length > 7
|
|
965
|
+
|
|
966
|
+
value_mask = (1 << (7 - length)) - 1
|
|
967
|
+
value = first & value_mask
|
|
968
|
+
(length - 1).times do
|
|
969
|
+
byte = bit_reader.read_bits(8)
|
|
970
|
+
raise InvalidFormatError, "invalid UTF-8 continuation byte in FLAC frame header" unless (byte & 0xC0) == 0x80
|
|
971
|
+
|
|
972
|
+
value = (value << 6) | (byte & 0x3F)
|
|
973
|
+
end
|
|
974
|
+
value
|
|
975
|
+
end
|
|
976
|
+
|
|
977
|
+
def decode_block_size(code, bit_reader)
|
|
978
|
+
case code
|
|
979
|
+
when 0
|
|
980
|
+
raise InvalidFormatError, "reserved FLAC block size code"
|
|
981
|
+
when 6
|
|
982
|
+
bit_reader.read_bits(8) + 1
|
|
983
|
+
when 7
|
|
984
|
+
bit_reader.read_bits(16) + 1
|
|
985
|
+
else
|
|
986
|
+
BLOCK_SIZE_CODES.fetch(code)
|
|
987
|
+
end
|
|
988
|
+
rescue KeyError
|
|
989
|
+
raise UnsupportedFormatError, "unsupported FLAC block size code: #{code}"
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
def decode_sample_rate(code, bit_reader, stream_sample_rate)
|
|
993
|
+
case code
|
|
994
|
+
when 0
|
|
995
|
+
stream_sample_rate
|
|
996
|
+
when 12
|
|
997
|
+
bit_reader.read_bits(8) * 1000
|
|
998
|
+
when 13
|
|
999
|
+
bit_reader.read_bits(16)
|
|
1000
|
+
when 14
|
|
1001
|
+
bit_reader.read_bits(16) * 10
|
|
1002
|
+
else
|
|
1003
|
+
SAMPLE_RATE_CODES.fetch(code)
|
|
1004
|
+
end
|
|
1005
|
+
rescue KeyError
|
|
1006
|
+
raise UnsupportedFormatError, "unsupported FLAC sample rate code: #{code}"
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
def decode_sample_size(code, stream_bit_depth)
|
|
1010
|
+
return stream_bit_depth if code.zero?
|
|
1011
|
+
|
|
1012
|
+
SAMPLE_SIZE_CODES.fetch(code)
|
|
1013
|
+
rescue KeyError
|
|
1014
|
+
raise UnsupportedFormatError, "unsupported FLAC sample size code: #{code}"
|
|
1015
|
+
end
|
|
1016
|
+
|
|
1017
|
+
def decode_channel_count(channel_assignment, stream_channels)
|
|
1018
|
+
case channel_assignment
|
|
1019
|
+
when 0..7
|
|
1020
|
+
channels = channel_assignment + 1
|
|
1021
|
+
raise InvalidFormatError, "FLAC frame channel count does not match STREAMINFO" if channels != stream_channels
|
|
1022
|
+
|
|
1023
|
+
channels
|
|
1024
|
+
when 8..10
|
|
1025
|
+
raise InvalidFormatError, "FLAC side/mid channel assignments require stereo STREAMINFO" unless stream_channels == 2
|
|
1026
|
+
|
|
1027
|
+
2
|
|
1028
|
+
else
|
|
1029
|
+
raise InvalidFormatError, "reserved FLAC channel assignment: #{channel_assignment}"
|
|
1030
|
+
end
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
def decode_subframes(bit_reader, frame_header)
|
|
1034
|
+
channels = frame_header.fetch(:channels)
|
|
1035
|
+
block_size = frame_header.fetch(:block_size)
|
|
1036
|
+
sample_sizes = subframe_sample_sizes(frame_header)
|
|
1037
|
+
|
|
1038
|
+
Array.new(channels) do |channel_index|
|
|
1039
|
+
decode_subframe(bit_reader, block_size: block_size, sample_size: sample_sizes.fetch(channel_index))
|
|
1040
|
+
end
|
|
1041
|
+
end
|
|
1042
|
+
|
|
1043
|
+
def subframe_sample_sizes(frame_header)
|
|
1044
|
+
sample_size = frame_header.fetch(:sample_size)
|
|
1045
|
+
|
|
1046
|
+
case frame_header.fetch(:channel_assignment)
|
|
1047
|
+
when 8, 10
|
|
1048
|
+
[sample_size, sample_size + 1]
|
|
1049
|
+
when 9
|
|
1050
|
+
[sample_size + 1, sample_size]
|
|
1051
|
+
else
|
|
1052
|
+
Array.new(frame_header.fetch(:channels), sample_size)
|
|
1053
|
+
end
|
|
1054
|
+
end
|
|
1055
|
+
|
|
1056
|
+
def decode_subframe(bit_reader, block_size:, sample_size:)
|
|
1057
|
+
padding = bit_reader.read_bits(1)
|
|
1058
|
+
raise InvalidFormatError, "FLAC subframe padding bit must be 0" unless padding.zero?
|
|
1059
|
+
|
|
1060
|
+
subframe_type = bit_reader.read_bits(6)
|
|
1061
|
+
wasted_bits_flag = bit_reader.read_bits(1)
|
|
1062
|
+
wasted_bits = wasted_bits_flag.zero? ? 0 : (read_unary_zero_run(bit_reader) + 1)
|
|
1063
|
+
effective_sample_size = sample_size - wasted_bits
|
|
1064
|
+
raise InvalidFormatError, "invalid FLAC wasted bits count" unless effective_sample_size.positive?
|
|
1065
|
+
|
|
1066
|
+
decoded = case subframe_type
|
|
1067
|
+
when 0
|
|
1068
|
+
value = bit_reader.read_signed_bits(effective_sample_size)
|
|
1069
|
+
Array.new(block_size, value)
|
|
1070
|
+
when 1
|
|
1071
|
+
Array.new(block_size) { bit_reader.read_signed_bits(effective_sample_size) }
|
|
1072
|
+
when 8..12
|
|
1073
|
+
predictor_order = subframe_type - 8
|
|
1074
|
+
decode_fixed_subframe(
|
|
1075
|
+
bit_reader,
|
|
1076
|
+
block_size: block_size,
|
|
1077
|
+
sample_size: effective_sample_size,
|
|
1078
|
+
predictor_order: predictor_order
|
|
1079
|
+
)
|
|
1080
|
+
when 32..63
|
|
1081
|
+
predictor_order = (subframe_type & 0x1F) + 1
|
|
1082
|
+
decode_lpc_subframe(
|
|
1083
|
+
bit_reader,
|
|
1084
|
+
block_size: block_size,
|
|
1085
|
+
sample_size: effective_sample_size,
|
|
1086
|
+
predictor_order: predictor_order
|
|
1087
|
+
)
|
|
1088
|
+
else
|
|
1089
|
+
raise UnsupportedFormatError, "unsupported FLAC subframe type: #{subframe_type}"
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
return decoded if wasted_bits.zero?
|
|
1093
|
+
|
|
1094
|
+
decoded.map { |sample| sample << wasted_bits }
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
def decode_fixed_subframe(bit_reader, block_size:, sample_size:, predictor_order:)
|
|
1098
|
+
raise InvalidFormatError, "FLAC fixed predictor order exceeds block size" if predictor_order > block_size
|
|
1099
|
+
|
|
1100
|
+
warmup = Array.new(predictor_order) { bit_reader.read_signed_bits(sample_size) }
|
|
1101
|
+
residuals = decode_residuals(
|
|
1102
|
+
bit_reader,
|
|
1103
|
+
block_size: block_size,
|
|
1104
|
+
predictor_order: predictor_order
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
reconstruct_fixed_subframe(warmup, residuals, predictor_order)
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
def decode_lpc_subframe(bit_reader, block_size:, sample_size:, predictor_order:)
|
|
1111
|
+
raise InvalidFormatError, "FLAC LPC predictor order exceeds block size" if predictor_order > block_size
|
|
1112
|
+
|
|
1113
|
+
warmup = Array.new(predictor_order) { bit_reader.read_signed_bits(sample_size) }
|
|
1114
|
+
|
|
1115
|
+
precision_minus_one = bit_reader.read_bits(4)
|
|
1116
|
+
raise InvalidFormatError, "invalid FLAC LPC coefficient precision" if precision_minus_one == 0xF
|
|
1117
|
+
|
|
1118
|
+
coefficient_precision = precision_minus_one + 1
|
|
1119
|
+
qlp_shift = bit_reader.read_signed_bits(5)
|
|
1120
|
+
coefficients = Array.new(predictor_order) { bit_reader.read_signed_bits(coefficient_precision) }
|
|
1121
|
+
residuals = decode_residuals(
|
|
1122
|
+
bit_reader,
|
|
1123
|
+
block_size: block_size,
|
|
1124
|
+
predictor_order: predictor_order
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1127
|
+
reconstruct_lpc_subframe(warmup, residuals, coefficients, qlp_shift)
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1130
|
+
def decode_residuals(bit_reader, block_size:, predictor_order:)
|
|
1131
|
+
coding_method = bit_reader.read_bits(2)
|
|
1132
|
+
partition_order = bit_reader.read_bits(4)
|
|
1133
|
+
partition_count = 1 << partition_order
|
|
1134
|
+
raise InvalidFormatError, "invalid FLAC residual partitioning" if partition_count.zero?
|
|
1135
|
+
raise InvalidFormatError, "FLAC block size must be divisible by residual partitions" unless (block_size % partition_count).zero?
|
|
1136
|
+
|
|
1137
|
+
partition_block_size = block_size / partition_count
|
|
1138
|
+
case coding_method
|
|
1139
|
+
when 0
|
|
1140
|
+
decode_rice_partitions(
|
|
1141
|
+
bit_reader,
|
|
1142
|
+
partition_count: partition_count,
|
|
1143
|
+
partition_block_size: partition_block_size,
|
|
1144
|
+
predictor_order: predictor_order,
|
|
1145
|
+
coding: { parameter_bits: 4, escape_parameter: 0xF }
|
|
1146
|
+
)
|
|
1147
|
+
when 1
|
|
1148
|
+
decode_rice_partitions(
|
|
1149
|
+
bit_reader,
|
|
1150
|
+
partition_count: partition_count,
|
|
1151
|
+
partition_block_size: partition_block_size,
|
|
1152
|
+
predictor_order: predictor_order,
|
|
1153
|
+
coding: { parameter_bits: 5, escape_parameter: 0x1F }
|
|
1154
|
+
)
|
|
1155
|
+
else
|
|
1156
|
+
raise UnsupportedFormatError, "unsupported FLAC residual coding method: #{coding_method}"
|
|
1157
|
+
end
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
def decode_rice_partitions(bit_reader, partition_count:, partition_block_size:, predictor_order:, coding:)
|
|
1161
|
+
residuals = []
|
|
1162
|
+
parameter_bits = coding.fetch(:parameter_bits)
|
|
1163
|
+
escape_parameter = coding.fetch(:escape_parameter)
|
|
1164
|
+
|
|
1165
|
+
partition_count.times do |partition_index|
|
|
1166
|
+
sample_count = partition_block_size
|
|
1167
|
+
sample_count -= predictor_order if partition_index.zero?
|
|
1168
|
+
raise InvalidFormatError, "invalid FLAC residual partition sample count" if sample_count.negative?
|
|
1169
|
+
|
|
1170
|
+
parameter = bit_reader.read_bits(parameter_bits)
|
|
1171
|
+
if parameter == escape_parameter
|
|
1172
|
+
raw_bits = bit_reader.read_bits(5)
|
|
1173
|
+
sample_count.times { residuals << (raw_bits.zero? ? 0 : bit_reader.read_signed_bits(raw_bits)) }
|
|
1174
|
+
else
|
|
1175
|
+
sample_count.times { residuals << read_rice_signed(bit_reader, parameter) }
|
|
1176
|
+
end
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1179
|
+
residuals
|
|
1180
|
+
end
|
|
1181
|
+
|
|
1182
|
+
def read_rice_signed(bit_reader, parameter)
|
|
1183
|
+
quotient = read_unary_zero_run(bit_reader)
|
|
1184
|
+
|
|
1185
|
+
remainder = parameter.zero? ? 0 : bit_reader.read_bits(parameter)
|
|
1186
|
+
unsigned = (quotient << parameter) | remainder
|
|
1187
|
+
unsigned.even? ? (unsigned >> 1) : -((unsigned + 1) >> 1)
|
|
1188
|
+
end
|
|
1189
|
+
|
|
1190
|
+
def read_unary_zero_run(bit_reader)
|
|
1191
|
+
count = 0
|
|
1192
|
+
count += 1 while bit_reader.read_bits(1).zero?
|
|
1193
|
+
count
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
def reconstruct_fixed_subframe(warmup, residuals, predictor_order)
|
|
1197
|
+
samples = warmup.dup
|
|
1198
|
+
residuals.each do |residual|
|
|
1199
|
+
predicted = fixed_predictor_value(samples, predictor_order)
|
|
1200
|
+
samples << (predicted + residual)
|
|
1201
|
+
end
|
|
1202
|
+
samples
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1205
|
+
def reconstruct_lpc_subframe(warmup, residuals, coefficients, qlp_shift)
|
|
1206
|
+
samples = warmup.dup
|
|
1207
|
+
|
|
1208
|
+
residuals.each do |residual|
|
|
1209
|
+
sum = 0
|
|
1210
|
+
coefficients.each_with_index do |coefficient, index|
|
|
1211
|
+
sum += coefficient * samples[-1 - index]
|
|
1212
|
+
end
|
|
1213
|
+
|
|
1214
|
+
predicted = qlp_shift.negative? ? (sum << -qlp_shift) : (sum >> qlp_shift)
|
|
1215
|
+
samples << (predicted + residual)
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
samples
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
def fixed_predictor_value(samples, predictor_order)
|
|
1222
|
+
case predictor_order
|
|
1223
|
+
when 0
|
|
1224
|
+
0
|
|
1225
|
+
when 1
|
|
1226
|
+
samples[-1]
|
|
1227
|
+
when 2
|
|
1228
|
+
(2 * samples[-1]) - samples[-2]
|
|
1229
|
+
when 3
|
|
1230
|
+
(3 * samples[-1]) - (3 * samples[-2]) + samples[-3]
|
|
1231
|
+
when 4
|
|
1232
|
+
(4 * samples[-1]) - (6 * samples[-2]) + (4 * samples[-3]) - samples[-4]
|
|
1233
|
+
else
|
|
1234
|
+
raise UnsupportedFormatError, "unsupported FLAC fixed predictor order: #{predictor_order}"
|
|
1235
|
+
end
|
|
1236
|
+
end
|
|
1237
|
+
|
|
1238
|
+
def interleave_channels(channel_samples, block_size, channels)
|
|
1239
|
+
samples = Array.new(block_size * channels)
|
|
1240
|
+
|
|
1241
|
+
block_size.times do |frame_index|
|
|
1242
|
+
channels.times do |channel_index|
|
|
1243
|
+
samples[(frame_index * channels) + channel_index] = channel_samples[channel_index][frame_index]
|
|
1244
|
+
end
|
|
1245
|
+
end
|
|
1246
|
+
|
|
1247
|
+
samples
|
|
1248
|
+
end
|
|
1249
|
+
|
|
1250
|
+
def restore_channel_assignment(channel_samples, frame_header)
|
|
1251
|
+
assignment = frame_header.fetch(:channel_assignment)
|
|
1252
|
+
return channel_samples if assignment <= 7
|
|
1253
|
+
|
|
1254
|
+
left_or_side = channel_samples.fetch(0)
|
|
1255
|
+
right_or_side = channel_samples.fetch(1)
|
|
1256
|
+
|
|
1257
|
+
case assignment
|
|
1258
|
+
when 8 # left + side
|
|
1259
|
+
left = left_or_side
|
|
1260
|
+
side = right_or_side
|
|
1261
|
+
right = left.zip(side).map { |l, s| l - s }
|
|
1262
|
+
[left, right]
|
|
1263
|
+
when 9 # side + right
|
|
1264
|
+
side = left_or_side
|
|
1265
|
+
right = right_or_side
|
|
1266
|
+
left = side.zip(right).map { |s, r| s + r }
|
|
1267
|
+
[left, right]
|
|
1268
|
+
when 10 # mid + side
|
|
1269
|
+
mid = left_or_side
|
|
1270
|
+
side = right_or_side
|
|
1271
|
+
left = []
|
|
1272
|
+
right = []
|
|
1273
|
+
|
|
1274
|
+
mid.each_with_index do |mid_sample, index|
|
|
1275
|
+
side_sample = side.fetch(index)
|
|
1276
|
+
adjusted_mid = (mid_sample << 1) | (side_sample & 0x1)
|
|
1277
|
+
left << ((adjusted_mid + side_sample) >> 1)
|
|
1278
|
+
right << ((adjusted_mid - side_sample) >> 1)
|
|
1279
|
+
end
|
|
1280
|
+
|
|
1281
|
+
[left, right]
|
|
1282
|
+
else
|
|
1283
|
+
raise InvalidFormatError, "unsupported FLAC channel assignment: #{assignment}"
|
|
1284
|
+
end
|
|
1285
|
+
end
|
|
1286
|
+
|
|
1287
|
+
def unpack_uint24(bytes)
|
|
1288
|
+
bytes.unpack("C3").then { |b0, b1, b2| (b0 << 16) | (b1 << 8) | b2 }
|
|
1289
|
+
end
|
|
1290
|
+
|
|
1291
|
+
def read_exact(io, size, message)
|
|
1292
|
+
data = io.read(size)
|
|
1293
|
+
raise InvalidFormatError, message if data.nil? || data.bytesize != size
|
|
1294
|
+
|
|
1295
|
+
data
|
|
1296
|
+
end
|
|
1297
|
+
|
|
1298
|
+
def ensure_seekable!(io)
|
|
1299
|
+
return if io.respond_to?(:seek) && io.respond_to?(:rewind)
|
|
1300
|
+
|
|
1301
|
+
raise StreamError, "FLAC codec requires seekable IO"
|
|
1302
|
+
end
|
|
1303
|
+
|
|
1304
|
+
def open_input(io_or_path)
|
|
1305
|
+
return [io_or_path, false] if io_or_path.respond_to?(:read)
|
|
1306
|
+
raise InvalidParameterError, "input path must be String or IO: #{io_or_path.inspect}" unless io_or_path.is_a?(String)
|
|
1307
|
+
|
|
1308
|
+
[File.open(io_or_path, "rb"), true]
|
|
1309
|
+
rescue Errno::ENOENT
|
|
1310
|
+
raise InvalidFormatError, "input file not found: #{io_or_path}"
|
|
1311
|
+
end
|
|
1312
|
+
|
|
1313
|
+
def open_output(io_or_path)
|
|
1314
|
+
return [io_or_path, false] if io_or_path.respond_to?(:write)
|
|
1315
|
+
raise InvalidParameterError, "output path must be String or IO: #{io_or_path.inspect}" unless io_or_path.is_a?(String)
|
|
1316
|
+
|
|
1317
|
+
[File.open(io_or_path, "wb"), true]
|
|
1318
|
+
end
|
|
1319
|
+
end
|
|
1320
|
+
end
|
|
1321
|
+
end
|
|
1322
|
+
end
|