Class: Deftones::OfflineContext
- Inherits:
-
Context
- Object
- Context
- Deftones::OfflineContext
show all
- Defined in:
- lib/deftones/offline_context.rb
Defined Under Namespace
Classes: RenderCancelled, RenderResult
Constant Summary
Constants inherited
from Context
Context::DEFAULT_BUFFER_SIZE, Context::DEFAULT_CHANNELS, Context::DEFAULT_SAMPLE_RATE
Instance Attribute Summary collapse
Attributes inherited from Context
#buffer_size, #draw, #latency_hint, #look_ahead, #on_stream_error, #output_device_id, #output_device_label, #sample_rate, #stream_error, #stream_status_flags, #transport
Instance Method Summary
collapse
-
#advance_schedulers(start_frame, chunk_frames) ⇒ Object
private
-
#current_time ⇒ Object
-
#dither_sample(sample, bit_depth, rng) ⇒ Object
private
-
#encode_streamed_wav(input_path, output_path, format) ⇒ Object
private
-
#initialize(duration:, channels: 2, sample_rate: DEFAULT_SAMPLE_RATE, buffer_size: DEFAULT_BUFFER_SIZE, transport: nil, draw: nil) ⇒ OfflineContext
constructor
A new instance of OfflineContext.
-
#metadata_for(buffer) ⇒ Object
private
-
#pcm_payload(samples, bit_depth:, dither:, dither_rng:) ⇒ Object
private
-
#quantize_pcm(sample, bit_depth, dither:, dither_rng:) ⇒ Object
private
-
#render(seed: nil, metadata: false, progress: nil, cancel: nil) ⇒ Object
-
#render_each_block(seed: nil, progress: nil, cancel: nil) ⇒ Object
(also: #renderEachBlock)
-
#render_to_file(path, format: nil, streaming: false, bit_depth: 16, dither: false, dither_rng: nil, **render_options) ⇒ Object
-
#render_with_metadata(**options) ⇒ Object
(also: #renderWithMetadata)
-
#state ⇒ Object
-
#stream_compressed_to_file(path, format, bit_depth:, dither:, dither_rng:, **render_options) ⇒ Object
private
-
#stream_to_file(path, format: nil, bit_depth:, dither:, dither_rng:, **render_options) ⇒ Object
private
-
#validate_wav_bit_depth(bit_depth) ⇒ Object
private
-
#wav_header(bit_depth) ⇒ Object
private
-
#with_render_state(seed: nil) ⇒ Object
private
Methods inherited from Context
#block_time, #build_realtime_backend, #close, #handle_stream_error, #monotonic_time, #normalize_stream_error_mode, #output, #pull_realtime_samples, #raw_context, #realtime?, #record_stream_status_flags, #render_block_frames, #render_frames, #reset!, #resume, #running?, #sample_time, #start, #start_realtime_stream, #stop, #stream_error_mode, #stream_error_mode=
Constructor Details
#initialize(duration:, channels: 2, sample_rate: DEFAULT_SAMPLE_RATE, buffer_size: DEFAULT_BUFFER_SIZE, transport: nil, draw: nil) ⇒ OfflineContext
Returns a new instance of OfflineContext.
12
13
14
15
16
17
18
19
20
21
|
# File 'lib/deftones/offline_context.rb', line 12
def initialize(duration:, channels: 2, sample_rate: DEFAULT_SAMPLE_RATE,
buffer_size: DEFAULT_BUFFER_SIZE, transport: nil, draw: nil)
super(sample_rate: sample_rate, buffer_size: buffer_size, channels: channels, autostart: false,
transport: transport, draw: draw)
@duration = duration.to_f
@total_frames = (@duration * sample_rate).ceil
@current_frame = 0
@rendering = false
@last_render_metadata = nil
end
|
Instance Attribute Details
#channels ⇒ Object
Returns the value of attribute channels.
10
11
12
|
# File 'lib/deftones/offline_context.rb', line 10
def channels
@channels
end
|
#current_frame ⇒ Object
Returns the value of attribute current_frame.
10
11
12
|
# File 'lib/deftones/offline_context.rb', line 10
def current_frame
@current_frame
end
|
#duration ⇒ Object
Returns the value of attribute duration.
10
11
12
|
# File 'lib/deftones/offline_context.rb', line 10
def duration
@duration
end
|
Returns the value of attribute last_render_metadata.
10
11
12
|
# File 'lib/deftones/offline_context.rb', line 10
def last_render_metadata
@last_render_metadata
end
|
#total_frames ⇒ Object
Returns the value of attribute total_frames.
10
11
12
|
# File 'lib/deftones/offline_context.rb', line 10
def total_frames
@total_frames
end
|
Instance Method Details
#advance_schedulers(start_frame, chunk_frames) ⇒ Object
106
107
108
109
110
111
112
113
|
# File 'lib/deftones/offline_context.rb', line 106
def advance_schedulers(start_frame, chunk_frames)
window_start = start_frame.to_f / sample_rate
window_end = (start_frame + chunk_frames).to_f / sample_rate
transport.prepare_render_window(window_start, window_end)
draw.advance_to(window_end)
Deftones.transport.prepare_render_window(window_start, window_end) unless Deftones.transport.equal?(transport)
Deftones.draw.advance_to(window_end) unless Deftones.draw.equal?(draw)
end
|
#current_time ⇒ Object
23
24
25
|
# File 'lib/deftones/offline_context.rb', line 23
def current_time
@current_frame.to_f / sample_rate
end
|
#dither_sample(sample, bit_depth, rng) ⇒ Object
210
211
212
213
214
|
# File 'lib/deftones/offline_context.rb', line 210
def dither_sample(sample, bit_depth, rng)
random = rng || Random
step = 1.0 / ((2**(bit_depth - 1)) - 1)
sample.to_f + ((random.rand - random.rand) * step)
end
|
#encode_streamed_wav(input_path, output_path, format) ⇒ Object
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
# File 'lib/deftones/offline_context.rb', line 150
def encode_streamed_wav(input_path, output_path, format)
backend = IO::Buffer.send(:encoder_backend_for, format)
raise MissingCodecBackendError, IO::Buffer.send(:missing_encoder_message, format) unless backend
if IO::Buffer.send(:custom_codec_backend?, backend)
backend.encode(input_path, output_path, format: format, sample_rate: sample_rate, channels: channels)
return
end
command = IO::Buffer.send(:encoder_command, backend, input_path, output_path, format, sample_rate, channels)
stdout, stderr, status = IO::Buffer.send(:capture_codec_command, *command)
return if status.success?
IO::Buffer.send(:raise_codec_command_error, "Failed to encode #{format}", command, stdout, stderr, status)
end
|
166
167
168
169
170
171
172
173
174
175
176
|
# File 'lib/deftones/offline_context.rb', line 166
def metadata_for(buffer)
{
duration: duration,
frames: total_frames,
channels: channels,
sample_rate: sample_rate,
peak: buffer.peak,
rms: buffer.rms,
clip_count: buffer.clip_count
}
end
|
#pcm_payload(samples, bit_depth:, dither:, dither_rng:) ⇒ Object
192
193
194
195
196
197
198
199
200
|
# File 'lib/deftones/offline_context.rb', line 192
def pcm_payload(samples, bit_depth:, dither:, dither_rng:)
quantized = samples.map { |sample| quantize_pcm(sample, bit_depth, dither: dither, dither_rng: dither_rng) }
case bit_depth
when 16 then quantized.pack("s<*")
when 24 then quantized.map { |value| [value & 0xFFFFFF].pack("V")[0, 3] }.join
when 32 then quantized.pack("l<*")
end
end
|
#quantize_pcm(sample, bit_depth, dither:, dither_rng:) ⇒ Object
202
203
204
205
206
207
208
|
# File 'lib/deftones/offline_context.rb', line 202
def quantize_pcm(sample, bit_depth, dither:, dither_rng:)
max = (2**(bit_depth - 1)) - 1
min = -(2**(bit_depth - 1))
value = dither ? dither_sample(sample, bit_depth, dither_rng) : sample.to_f
scaled = Deftones::DSP::Helpers.clamp(value, -1.0, 1.0) * max
Deftones::DSP::Helpers.clamp(scaled.round, min, max)
end
|
#render(seed: nil, metadata: false, progress: nil, cancel: nil) ⇒ Object
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
# File 'lib/deftones/offline_context.rb', line 31
def render(seed: nil, metadata: false, progress: nil, cancel: nil)
samples = Array.new(@total_frames * @channels, 0.0)
render_each_block(seed: seed, progress: progress, cancel: cancel) do |block, start_frame|
interleaved = block.fit_channels(@channels).interleaved
start_index = start_frame * @channels
samples[start_index, interleaved.length] = interleaved
end
buffer = IO::Buffer.new(samples, channels: @channels, sample_rate: sample_rate)
@last_render_metadata = metadata_for(buffer)
return RenderResult.new(buffer: buffer, metadata: @last_render_metadata) if metadata
buffer
end
|
#render_each_block(seed: nil, progress: nil, cancel: nil) ⇒ Object
Also known as:
renderEachBlock
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
# File 'lib/deftones/offline_context.rb', line 52
def render_each_block(seed: nil, progress: nil, cancel: nil)
return enum_for(:render_each_block) unless block_given?
with_render_state(seed: seed) do
frames_processed = 0
while frames_processed < @total_frames
raise RenderCancelled, "render cancelled" if cancel&.call(frames_processed.to_f / @total_frames)
chunk_frames = [buffer_size, @total_frames - frames_processed].min
@current_frame = frames_processed
advance_schedulers(frames_processed, chunk_frames)
yield render_block_frames(chunk_frames, frames_processed).fit_channels(@channels), frames_processed
frames_processed += chunk_frames
progress&.call(frames_processed.to_f / @total_frames)
end
@current_frame = @total_frames
end
self
end
|
#render_to_file(path, format: nil, streaming: false, bit_depth: 16, dither: false, dither_rng: nil, **render_options) ⇒ Object
78
79
80
81
82
83
84
85
86
87
88
89
|
# File 'lib/deftones/offline_context.rb', line 78
def render_to_file(path, format: nil, streaming: false, bit_depth: 16, dither: false, dither_rng: nil,
**render_options)
if streaming
return stream_to_file(path, format: format, bit_depth: bit_depth, dither: dither, dither_rng: dither_rng,
**render_options)
end
rendered_buffer = render(**render_options)
buffer = rendered_buffer.respond_to?(:buffer) ? rendered_buffer.buffer : rendered_buffer
buffer.save(path, format: format, bit_depth: bit_depth, dither: dither, dither_rng: dither_rng)
rendered_buffer
end
|
48
49
50
|
# File 'lib/deftones/offline_context.rb', line 48
def render_with_metadata(**options)
render(**options, metadata: true)
end
|
#state ⇒ Object
27
28
29
|
# File 'lib/deftones/offline_context.rb', line 27
def state
"suspended"
end
|
#stream_compressed_to_file(path, format, bit_depth:, dither:, dither_rng:, **render_options) ⇒ Object
140
141
142
143
144
145
146
147
148
|
# File 'lib/deftones/offline_context.rb', line 140
def stream_compressed_to_file(path, format, bit_depth:, dither:, dither_rng:, **render_options)
Tempfile.create(["deftones-stream-render", ".wav"]) do |tempfile|
tempfile.close
stream_to_file(tempfile.path, format: :wav, bit_depth: bit_depth, dither: dither, dither_rng: dither_rng,
**render_options)
encode_streamed_wav(tempfile.path, path, format)
end
path
end
|
#stream_to_file(path, format: nil, bit_depth:, dither:, dither_rng:, **render_options) ⇒ Object
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
# File 'lib/deftones/offline_context.rb', line 115
def stream_to_file(path, format: nil, bit_depth:, dither:, dither_rng:, **render_options)
resolved_format = format || File.extname(path).delete_prefix(".").downcase.to_sym
resolved_format = :wav if resolved_format.nil? || resolved_format == :""
resolved_format = :ogg if resolved_format.to_sym == :oga
unless IO::Buffer::SAVEABLE_FORMATS.include?(resolved_format.to_sym)
raise UnsupportedAudioFormatError, "Unsupported streaming render format: #{resolved_format}"
end
return stream_compressed_to_file(path, resolved_format.to_sym, bit_depth: bit_depth, dither: dither,
dither_rng: dither_rng, **render_options) unless resolved_format.to_sym == :wav
normalized_bit_depth = validate_wav_bit_depth(bit_depth)
File.open(path, "wb") do |file|
file.write((normalized_bit_depth))
render_each_block(**render_options) do |block, _start_frame|
file.write(pcm_payload(block.fit_channels(@channels).interleaved,
bit_depth: normalized_bit_depth,
dither: dither,
dither_rng: dither_rng))
end
end
path
end
|
#validate_wav_bit_depth(bit_depth) ⇒ Object
216
217
218
219
220
221
|
# File 'lib/deftones/offline_context.rb', line 216
def validate_wav_bit_depth(bit_depth)
normalized = bit_depth.to_i
return normalized if IO::Buffer::WAV_BIT_DEPTHS.include?(normalized)
raise ArgumentError, "Unsupported WAV bit depth: #{bit_depth}"
end
|
178
179
180
181
182
183
184
185
186
187
188
189
190
|
# File 'lib/deftones/offline_context.rb', line 178
def (bit_depth)
bytes_per_sample = bit_depth / 8
data_size = @total_frames * @channels * bytes_per_sample
byte_rate = sample_rate * @channels * bytes_per_sample
block_align = @channels * bytes_per_sample
"RIFF" \
+ [36 + data_size].pack("V") \
+ "WAVEfmt " \
+ [16, 1, @channels, sample_rate, byte_rate, block_align, bit_depth].pack("VvvVVvv") \
+ "data" \
+ [data_size].pack("V")
end
|
#with_render_state(seed: nil) ⇒ Object
93
94
95
96
97
98
99
100
101
102
103
104
|
# File 'lib/deftones/offline_context.rb', line 93
def with_render_state(seed: nil)
previous_frame = @current_frame
previous_rendering = @rendering
previous_seed = seed ? srand(seed) : nil
@rendering = true
@current_frame = 0
yield
ensure
srand(previous_seed) if seed
@current_frame = previous_frame
@rendering = previous_rendering
end
|