Class: Deftones::IO::Buffer
- Inherits:
-
Object
- Object
- Deftones::IO::Buffer
- Includes:
- Enumerable
- Defined in:
- lib/deftones/io/buffer.rb
Defined Under Namespace
Classes: Statistics
Constant Summary collapse
- COMPRESSED_EXTENSIONS =
%w[.mp3 .ogg .oga].freeze
- SAVEABLE_FORMATS =
%i[wav mp3 ogg].freeze
- DEFAULT_CODEC_TIMEOUT =
30.0- INTERPOLATION_MODES =
%i[linear nearest cubic sinc_lite].freeze
- SINC_LITE_RADIUS =
8- WAV_BIT_DEPTHS =
[16, 24, 32].freeze
Class Attribute Summary collapse
-
.codec_backend ⇒ Object
Returns the value of attribute codec_backend.
-
.codec_timeout ⇒ Object
Returns the value of attribute codec_timeout.
Instance Attribute Summary collapse
-
#channels ⇒ Object
readonly
Returns the value of attribute channels.
-
#interpolation ⇒ Object
Returns the value of attribute interpolation.
-
#sample_rate ⇒ Object
readonly
Returns the value of attribute sample_rate.
-
#samples ⇒ Object
readonly
Returns the value of attribute samples.
Class Method Summary collapse
- .capture_codec_command(*command) ⇒ Object private
- .compressed_audio_available? ⇒ Boolean (also: compressedAudioAvailable)
- .custom_codec_backend?(backend) ⇒ Boolean private
- .decoder_backend_for(extension) ⇒ Object private
- .decoder_command(backend, input_path, output_path, sample_rate: nil, channels: nil) ⇒ Object private
- .encoder_backend_for(format) ⇒ Object private
- .encoder_command(backend, input_path, output_path, format, sample_rate, channels) ⇒ Object private
- .ensure_wav_backend! ⇒ Object private
- .executable_available?(name) ⇒ Boolean private
- .from_array(samples, sample_rate: Context::DEFAULT_SAMPLE_RATE, channels: nil, interpolation: :linear) ⇒ Object (also: fromArray)
- .from_mono(samples, channels: 1, sample_rate: Context::DEFAULT_SAMPLE_RATE, interpolation: :linear) ⇒ Object
- .from_url(path) ⇒ Object (also: fromUrl)
- .interleave(mono_samples, channels) ⇒ Object
- .load(source, sample_rate: nil, channels: nil) ⇒ Object
- .load_compressed(path, extension, sample_rate: nil, channels: nil) ⇒ Object private
- .load_io(io) ⇒ Object private
- .load_wav(path) ⇒ Object private
- .loaded ⇒ Object
- .missing_decoder_message(extension) ⇒ Object private
- .missing_encoder_message(format) ⇒ Object private
- .normalize_format(format) ⇒ Object private
- .raise_codec_command_error(prefix, command, stdout, stderr, status) ⇒ Object private
- .resolve_save_format(path, format, on_format_mismatch:) ⇒ Object private
- .validate_path_string!(path, role:) ⇒ Object private
- .validate_wav_bit_depth(bit_depth) ⇒ Object private
- .wavify_wav_format(channels, sample_rate, bit_depth) ⇒ Object private
- .wavify_work_format(channels, sample_rate) ⇒ Object private
Instance Method Summary collapse
- #[](frame_index, channel = nil) ⇒ Object
- #clip_count(threshold = 1.0) ⇒ Object
- #cubic_sample_at(clamped_position, channel) ⇒ Object
- #dispose ⇒ Object
- #dithered_samples(bit_depth, rng) ⇒ Object private
- #duration ⇒ Object
- #each(&block) ⇒ Object
- #each_frame ⇒ Object
- #frame(frame_index) ⇒ Object
- #frames ⇒ Object
- #get_channel_data(channel) ⇒ Object (also: #getChannelData)
- #hann_window(normalized_distance) ⇒ Object
-
#initialize(samples, channels:, sample_rate:, interpolation: :linear) ⇒ Buffer
constructor
A new instance of Buffer.
- #integrated_lufs ⇒ Object (also: #integratedLufs)
- #length ⇒ Object
- #linear_sample_at(clamped_position, channel) ⇒ Object
- #loaded? ⇒ Boolean
- #mixdown ⇒ Object
- #mono ⇒ Object
- #new_like(samples, channels: @channels, sample_rate: @sample_rate) ⇒ Object private
- #normalize(target_peak = 0.99) ⇒ Object
- #normalize_interpolation(value) ⇒ Object private
- #normalize_level_target(value, name) ⇒ Object private
- #normalize_lufs(target_lufs = -14.0)) ⇒ Object (also: #normalizeLufs)
- #normalize_rms(target_rms = 0.2) ⇒ Object (also: #normalizeRms)
- #number_of_channels ⇒ Object (also: #numberOfChannels)
- #peak ⇒ Object
- #resample(target_sample_rate, interpolation: @interpolation) ⇒ Object (also: #resampleTo)
- #reverse ⇒ Object
- #rms ⇒ Object
- #sample_at(frame_position, channel = 0, interpolation: @interpolation) ⇒ Object (also: #sampleAt)
- #sample_at_cubic(frame_position, channel = 0) ⇒ Object (also: #sampleAtCubic)
- #sample_at_nearest(frame_position, channel = 0) ⇒ Object (also: #sampleAtNearest)
- #sample_at_sinc_lite(frame_position, channel = 0) ⇒ Object (also: #sampleAtSincLite)
- #save(target, format: nil, on_format_mismatch: :error, bit_depth: 16, dither: false, dither_rng: nil) ⇒ Object
- #save_compressed(path, format, bit_depth:, dither:, dither_rng:) ⇒ Object private
- #save_io(io, format:, bit_depth:, dither:, dither_rng:) ⇒ Object private
- #save_wav(path, bit_depth:, dither:, dither_rng:) ⇒ Object private
- #sinc(value) ⇒ Object
- #sinc_lite_sample_at(clamped_position, channel) ⇒ Object
- #slice(start_frame, length) ⇒ Object
- #slice_seconds(start_time, duration) ⇒ Object (also: #sliceSeconds)
- #statistics(clip_threshold: 1.0) ⇒ Object (also: #stats)
- #to_array ⇒ Object (also: #toArray)
Constructor Details
#initialize(samples, channels:, sample_rate:, interpolation: :linear) ⇒ Buffer
Returns a new instance of Buffer.
88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/deftones/io/buffer.rb', line 88 def initialize(samples, channels:, sample_rate:, interpolation: :linear) @samples = samples.map(&:to_f) @channels = channels @sample_rate = sample_rate @interpolation = normalize_interpolation(interpolation) @disposed = false @mono_cache = nil @peak_cache = nil @rms_cache = nil @statistics_cache = {} end |
Class Attribute Details
.codec_backend ⇒ Object
Returns the value of attribute codec_backend.
23 24 25 |
# File 'lib/deftones/io/buffer.rb', line 23 def codec_backend @codec_backend end |
.codec_timeout ⇒ Object
Returns the value of attribute codec_timeout.
23 24 25 |
# File 'lib/deftones/io/buffer.rb', line 23 def codec_timeout @codec_timeout end |
Instance Attribute Details
#channels ⇒ Object (readonly)
Returns the value of attribute channels.
12 13 14 |
# File 'lib/deftones/io/buffer.rb', line 12 def channels @channels end |
#interpolation ⇒ Object
Returns the value of attribute interpolation.
12 13 14 |
# File 'lib/deftones/io/buffer.rb', line 12 def interpolation @interpolation end |
#sample_rate ⇒ Object (readonly)
Returns the value of attribute sample_rate.
12 13 14 |
# File 'lib/deftones/io/buffer.rb', line 12 def sample_rate @sample_rate end |
#samples ⇒ Object (readonly)
Returns the value of attribute samples.
12 13 14 |
# File 'lib/deftones/io/buffer.rb', line 12 def samples @samples end |
Class Method Details
.capture_codec_command(*command) ⇒ Object (private)
606 607 608 609 610 611 612 |
# File 'lib/deftones/io/buffer.rb', line 606 def capture_codec_command(*command) Timeout.timeout(codec_timeout || DEFAULT_CODEC_TIMEOUT) do Open3.capture3(*command) end rescue Timeout::Error raise ArgumentError, "Codec command timed out after #{codec_timeout || DEFAULT_CODEC_TIMEOUT} seconds" end |
.compressed_audio_available? ⇒ Boolean Also known as: compressedAudioAvailable
64 65 66 67 68 69 |
# File 'lib/deftones/io/buffer.rb', line 64 def self.compressed_audio_available? return true if codec_backend&.respond_to?(:decode) return true if codec_backend&.respond_to?(:encode) send(:executable_available?, "ffmpeg") || send(:executable_available?, "afconvert") end |
.custom_codec_backend?(backend) ⇒ Boolean (private)
614 615 616 |
# File 'lib/deftones/io/buffer.rb', line 614 def custom_codec_backend?(backend) !backend.is_a?(Symbol) end |
.decoder_backend_for(extension) ⇒ Object (private)
549 550 551 552 553 554 555 |
# File 'lib/deftones/io/buffer.rb', line 549 def decoder_backend_for(extension) return codec_backend if codec_backend&.respond_to?(:decode) return :ffmpeg if executable_available?("ffmpeg") return :afconvert if extension == ".mp3" && executable_available?("afconvert") nil end |
.decoder_command(backend, input_path, output_path, sample_rate: nil, channels: nil) ⇒ Object (private)
565 566 567 568 569 570 571 572 573 574 575 576 577 |
# File 'lib/deftones/io/buffer.rb', line 565 def decoder_command(backend, input_path, output_path, sample_rate: nil, channels: nil) case backend when :ffmpeg command = ["ffmpeg", "-v", "error", "-y", "-i", input_path, "-vn", "-map", "0:a:0", "-acodec", "pcm_f32le"] command += ["-ar", sample_rate.to_i.to_s] if sample_rate command += ["-ac", channels.to_i.to_s] if channels command + ["-f", "wav", output_path] when :afconvert ["afconvert", "-f", "WAVE", "-d", "LEI16", input_path, output_path] else raise ArgumentError, "Unknown decoder backend: #{backend}" end end |
.encoder_backend_for(format) ⇒ Object (private)
557 558 559 560 561 562 563 |
# File 'lib/deftones/io/buffer.rb', line 557 def encoder_backend_for(format) return codec_backend if codec_backend&.respond_to?(:encode) return :ffmpeg if executable_available?("ffmpeg") return :afconvert if format == :mp3 && executable_available?("afconvert") nil end |
.encoder_command(backend, input_path, output_path, format, sample_rate, channels) ⇒ Object (private)
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 |
# File 'lib/deftones/io/buffer.rb', line 579 def encoder_command(backend, input_path, output_path, format, sample_rate, channels) case backend when :ffmpeg container = format == :ogg ? "ogg" : format.to_s sample_format = format == :mp3 ? "s16p" : "s16" codec = format == :mp3 ? "libmp3lame" : "flac" [ "ffmpeg", "-v", "error", "-y", "-i", input_path, "-vn", "-map", "0:a:0", "-codec:a", codec, "-sample_fmt", sample_format, "-ar", sample_rate.to_s, "-ac", channels.to_s, "-f", container, output_path ] when :afconvert raise ArgumentError, "afconvert only supports mp3 export" unless format == :mp3 ["afconvert", "-f", "MPG3", "-d", ".mp3", input_path, output_path] else raise ArgumentError, "Unknown encoder backend: #{backend}" end end |
.ensure_wav_backend! ⇒ Object (private)
517 518 519 520 521 522 |
# File 'lib/deftones/io/buffer.rb', line 517 def ensure_wav_backend! return if Deftones.wavify_available? raise Deftones::MissingCodecBackendError, "WAV codec backend is unavailable. Install the wavify gem to load or save WAV audio." end |
.executable_available?(name) ⇒ Boolean (private)
599 600 601 602 603 604 |
# File 'lib/deftones/io/buffer.rb', line 599 def executable_available?(name) ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |directory| executable = File.join(directory, name) File.file?(executable) && File.executable?(executable) end end |
.from_array(samples, sample_rate: Context::DEFAULT_SAMPLE_RATE, channels: nil, interpolation: :linear) ⇒ Object Also known as: fromArray
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/deftones/io/buffer.rb', line 37 def self.from_array(samples, sample_rate: Context::DEFAULT_SAMPLE_RATE, channels: nil, interpolation: :linear) if samples.first.is_a?(Array) channel_count = channels || samples.length frame_count = samples.map(&:length).max || 0 interleaved = Array.new(frame_count * channel_count, 0.0) frame_count.times do |frame_index| channel_count.times do |channel_index| source_channel = samples[channel_index] || [] interleaved[(frame_index * channel_count) + channel_index] = source_channel[frame_index].to_f end end new(interleaved, channels: channel_count, sample_rate: sample_rate, interpolation: interpolation) else from_mono(samples, channels: channels || 1, sample_rate: sample_rate, interpolation: interpolation) end end |
.from_mono(samples, channels: 1, sample_rate: Context::DEFAULT_SAMPLE_RATE, interpolation: :linear) ⇒ Object
32 33 34 35 |
# File 'lib/deftones/io/buffer.rb', line 32 def self.from_mono(samples, channels: 1, sample_rate: Context::DEFAULT_SAMPLE_RATE, interpolation: :linear) interleaved = channels == 1 ? samples : interleave(samples, channels) new(interleaved, channels: channels, sample_rate: sample_rate, interpolation: interpolation) end |
.from_url(path) ⇒ Object Also known as: fromUrl
56 57 58 |
# File 'lib/deftones/io/buffer.rb', line 56 def self.from_url(path) load(path) end |
.interleave(mono_samples, channels) ⇒ Object
26 27 28 29 30 |
# File 'lib/deftones/io/buffer.rb', line 26 def self.interleave(mono_samples, channels) return mono_samples.dup if channels == 1 mono_samples.flat_map { |sample| Array.new(channels, sample) } end |
.load(source, sample_rate: nil, channels: nil) ⇒ Object
77 78 79 80 81 82 83 84 85 86 |
# File 'lib/deftones/io/buffer.rb', line 77 def self.load(source, sample_rate: nil, channels: nil) return load_io(source) if source.respond_to?(:read) && !source.is_a?(String) validate_path_string!(source, role: "audio source") extension = File.extname(source).downcase return load_wav(source) if extension == ".wav" return load_compressed(source, extension, sample_rate: sample_rate, channels: channels) if COMPRESSED_EXTENSIONS.include?(extension) raise Deftones::UnsupportedAudioFormatError, "Unsupported audio format: #{extension}" end |
.load_compressed(path, extension, sample_rate: nil, channels: nil) ⇒ Object (private)
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 |
# File 'lib/deftones/io/buffer.rb', line 494 def load_compressed(path, extension, sample_rate: nil, channels: nil) validate_path_string!(path, role: "compressed audio source") backend = decoder_backend_for(extension) raise Deftones::MissingCodecBackendError, (extension) unless backend Tempfile.create(["deftones-buffer", ".wav"]) do |tempfile| tempfile.close if custom_codec_backend?(backend) = { extension: extension } [:sample_rate] = sample_rate if sample_rate [:channels] = channels if channels backend.decode(path, tempfile.path, **) next load_wav(tempfile.path) end command = decoder_command(backend, path, tempfile.path, sample_rate: sample_rate, channels: channels) stdout, stderr, status = capture_codec_command(*command) next load_wav(tempfile.path) if status.success? raise_codec_command_error("Failed to decode #{extension}", command, stdout, stderr, status) end end |
.load_io(io) ⇒ Object (private)
483 484 485 486 487 488 489 490 491 492 |
# File 'lib/deftones/io/buffer.rb', line 483 def load_io(io) extension = io.respond_to?(:path) ? File.extname(io.path).downcase : ".wav" extension = ".wav" if extension.empty? Tempfile.create(["deftones-buffer-load", extension]) do |tempfile| tempfile.binmode tempfile.write(io.read) tempfile.close load(tempfile.path) end end |
.load_wav(path) ⇒ Object (private)
469 470 471 472 473 474 475 476 477 478 479 480 481 |
# File 'lib/deftones/io/buffer.rb', line 469 def load_wav(path) validate_path_string!(path, role: "WAV source") ensure_wav_backend! sample_buffer = Wavify::Codecs::Wav.read(path) float_buffer = sample_buffer.convert(wavify_work_format(sample_buffer.format.channels, sample_buffer.format.sample_rate)) new(float_buffer.samples, channels: float_buffer.format.channels, sample_rate: float_buffer.format.sample_rate) rescue StandardError => error raise if error.is_a?(Deftones::MissingCodecBackendError) raise unless defined?(Wavify::Error) && error.is_a?(Wavify::Error) raise ArgumentError, "Failed to load WAV: #{error.}" end |
.loaded ⇒ Object
60 61 62 |
# File 'lib/deftones/io/buffer.rb', line 60 def self.loaded true end |
.missing_decoder_message(extension) ⇒ Object (private)
630 631 632 |
# File 'lib/deftones/io/buffer.rb', line 630 def (extension) "No decoder available for #{extension}. Install ffmpeg to enable compressed audio loading." end |
.missing_encoder_message(format) ⇒ Object (private)
634 635 636 |
# File 'lib/deftones/io/buffer.rb', line 634 def (format) "No encoder available for #{format}. Install ffmpeg to enable compressed audio export." end |
.normalize_format(format) ⇒ Object (private)
656 657 658 659 660 661 |
# File 'lib/deftones/io/buffer.rb', line 656 def normalize_format(format) normalized = format.to_sym return :ogg if normalized == :oga normalized end |
.raise_codec_command_error(prefix, command, stdout, stderr, status) ⇒ Object (private)
618 619 620 621 622 623 624 625 626 627 628 |
# File 'lib/deftones/io/buffer.rb', line 618 def raise_codec_command_error(prefix, command, stdout, stderr, status) detail = [stderr, stdout].map(&:to_s).map(&:strip).reject(&:empty?).first || "unknown codec error" exit_status = status.respond_to?(:exitstatus) && status.exitstatus ? " (exit #{status.exitstatus})" : "" raise Deftones::CodecCommandError.new( "#{prefix}: #{detail}#{exit_status}", command: command, stdout: stdout, stderr: stderr, status: status ) end |
.resolve_save_format(path, format, on_format_mismatch:) ⇒ Object (private)
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 |
# File 'lib/deftones/io/buffer.rb', line 638 def resolve_save_format(path, format, on_format_mismatch:) extension = File.extname(path).downcase if format normalized = normalize_format(format) expected_extension = ".#{normalized}" if on_format_mismatch == :error && !extension.empty? && extension != expected_extension raise Deftones::UnsupportedAudioFormatError, "Format #{normalized} does not match file extension #{extension}" end return normalized end return :mp3 if extension == ".mp3" return :ogg if COMPRESSED_EXTENSIONS.include?(extension) :wav end |
.validate_path_string!(path, role:) ⇒ Object (private)
663 664 665 666 667 668 669 |
# File 'lib/deftones/io/buffer.rb', line 663 def validate_path_string!(path, role:) string = path.to_s raise ArgumentError, "#{role} path must not be empty" if string.empty? raise ArgumentError, "#{role} path contains a null byte" if string.include?("\0") true end |
.validate_wav_bit_depth(bit_depth) ⇒ Object (private)
542 543 544 545 546 547 |
# File 'lib/deftones/io/buffer.rb', line 542 def validate_wav_bit_depth(bit_depth) normalized = bit_depth.to_i return normalized if WAV_BIT_DEPTHS.include?(normalized) raise ArgumentError, "Unsupported WAV bit depth: #{bit_depth}" end |
.wavify_wav_format(channels, sample_rate, bit_depth) ⇒ Object (private)
533 534 535 536 537 538 539 540 |
# File 'lib/deftones/io/buffer.rb', line 533 def wavify_wav_format(channels, sample_rate, bit_depth) Wavify::Core::Format.new( channels: channels, sample_rate: sample_rate, bit_depth: bit_depth, sample_format: :pcm ) end |
.wavify_work_format(channels, sample_rate) ⇒ Object (private)
524 525 526 527 528 529 530 531 |
# File 'lib/deftones/io/buffer.rb', line 524 def wavify_work_format(channels, sample_rate) Wavify::Core::Format.new( channels: channels, sample_rate: sample_rate, bit_depth: 32, sample_format: :float ) end |
Instance Method Details
#[](frame_index, channel = nil) ⇒ Object
170 171 172 173 174 |
# File 'lib/deftones/io/buffer.rb', line 170 def [](frame_index, channel = nil) return mono[frame_index] if channel.nil? @samples[(frame_index * @channels) + channel] end |
#clip_count(threshold = 1.0) ⇒ Object
156 157 158 159 |
# File 'lib/deftones/io/buffer.rb', line 156 def clip_count(threshold = 1.0) limit = threshold.to_f.abs @samples.count { |sample| sample.abs >= limit } end |
#cubic_sample_at(clamped_position, channel) ⇒ Object
265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/deftones/io/buffer.rb', line 265 def cubic_sample_at(clamped_position, channel) base = clamped_position.floor fraction = clamped_position - base p0 = self[[base - 1, 0].max, channel] p1 = self[base, channel] p2 = self[[base + 1, frames - 1].min, channel] p3 = self[[base + 2, frames - 1].min, channel] a0 = (-0.5 * p0) + (1.5 * p1) - (1.5 * p2) + (0.5 * p3) a1 = p0 - (2.5 * p1) + (2.0 * p2) - (0.5 * p3) a2 = (-0.5 * p0) + (0.5 * p2) a3 = p1 (((a0 * fraction) + a1) * fraction * fraction) + (a2 * fraction) + a3 end |
#dispose ⇒ Object
356 357 358 359 360 361 362 363 364 |
# File 'lib/deftones/io/buffer.rb', line 356 def dispose @samples = [] @mono_cache = nil @peak_cache = nil @rms_cache = nil @statistics_cache.clear @disposed = true self end |
#dithered_samples(bit_depth, rng) ⇒ Object (private)
458 459 460 461 462 463 464 |
# File 'lib/deftones/io/buffer.rb', line 458 def dithered_samples(bit_depth, rng) random = rng || Random step = 1.0 / ((2**(bit_depth - 1)) - 1) @samples.map do |sample| Deftones::DSP::Helpers.clamp(sample + ((random.rand - random.rand) * step), -1.0, 1.0) end end |
#duration ⇒ Object
118 119 120 |
# File 'lib/deftones/io/buffer.rb', line 118 def duration frames.to_f / @sample_rate end |
#each(&block) ⇒ Object
100 101 102 103 104 |
# File 'lib/deftones/io/buffer.rb', line 100 def each(&block) return enum_for(:each) unless block mono.each(&block) end |
#each_frame ⇒ Object
106 107 108 109 110 111 112 |
# File 'lib/deftones/io/buffer.rb', line 106 def each_frame return enum_for(:each_frame) unless block_given? frames.times do |frame_index| yield frame(frame_index) end end |
#frame(frame_index) ⇒ Object
176 177 178 179 |
# File 'lib/deftones/io/buffer.rb', line 176 def frame(frame_index) offset = frame_index * @channels @samples[offset, @channels] end |
#frames ⇒ Object
114 115 116 |
# File 'lib/deftones/io/buffer.rb', line 114 def frames @samples.length / @channels end |
#get_channel_data(channel) ⇒ Object Also known as: getChannelData
185 186 187 188 189 190 |
# File 'lib/deftones/io/buffer.rb', line 185 def get_channel_data(channel) channel_index = channel.to_i raise ArgumentError, "channel is out of range" if channel_index.negative? || channel_index >= @channels Array.new(frames) { |frame_index| self[frame_index, channel_index] } end |
#hann_window(normalized_distance) ⇒ Object
305 306 307 308 309 310 |
# File 'lib/deftones/io/buffer.rb', line 305 def hann_window(normalized_distance) distance = normalized_distance.abs return 0.0 if distance >= 1.0 0.5 * (1.0 + Math.cos(Math::PI * distance)) end |
#integrated_lufs ⇒ Object Also known as: integratedLufs
150 151 152 153 154 |
# File 'lib/deftones/io/buffer.rb', line 150 def integrated_lufs return -Float::INFINITY if rms <= 0.0 (20.0 * Math.log10(rms)) - 0.691 end |
#length ⇒ Object
122 123 124 |
# File 'lib/deftones/io/buffer.rb', line 122 def length frames end |
#linear_sample_at(clamped_position, channel) ⇒ Object
256 257 258 259 260 261 262 263 |
# File 'lib/deftones/io/buffer.rb', line 256 def linear_sample_at(clamped_position, channel) lower = clamped_position.floor upper = [lower + 1, frames - 1].min fraction = clamped_position - lower lower_sample = self[lower, channel] upper_sample = self[upper, channel] Deftones::DSP::Helpers.lerp(lower_sample, upper_sample, fraction) end |
#loaded? ⇒ Boolean
126 127 128 |
# File 'lib/deftones/io/buffer.rb', line 126 def loaded? !@disposed end |
#mixdown ⇒ Object
352 353 354 |
# File 'lib/deftones/io/buffer.rb', line 352 def mixdown self.class.new(mono, channels: 1, sample_rate: @sample_rate, interpolation: @interpolation) end |
#mono ⇒ Object
130 131 132 133 134 135 136 137 138 |
# File 'lib/deftones/io/buffer.rb', line 130 def mono return @samples if @channels == 1 return @mono_cache if @mono_cache @mono_cache = Array.new(frames) do |frame| offset = frame * @channels @samples[offset, @channels].sum / @channels.to_f end end |
#new_like(samples, channels: @channels, sample_rate: @sample_rate) ⇒ Object (private)
395 396 397 |
# File 'lib/deftones/io/buffer.rb', line 395 def new_like(samples, channels: @channels, sample_rate: @sample_rate) self.class.new(samples, channels: channels, sample_rate: sample_rate, interpolation: @interpolation) end |
#normalize(target_peak = 0.99) ⇒ Object
331 332 333 334 335 336 337 |
# File 'lib/deftones/io/buffer.rb', line 331 def normalize(target_peak = 0.99) normalized_target = normalize_level_target(target_peak, "target peak") return new_like(@samples) if peak.zero? scale = normalized_target / peak new_like(@samples.map { |sample| sample * scale }) end |
#normalize_interpolation(value) ⇒ Object (private)
399 400 401 402 403 404 |
# File 'lib/deftones/io/buffer.rb', line 399 def normalize_interpolation(value) normalized = value.to_sym return normalized if INTERPOLATION_MODES.include?(normalized) raise ArgumentError, "Unsupported interpolation mode: #{value}" end |
#normalize_level_target(value, name) ⇒ Object (private)
406 407 408 409 410 411 |
# File 'lib/deftones/io/buffer.rb', line 406 def normalize_level_target(value, name) normalized = value.to_f raise ArgumentError, "#{name} must be finite and non-negative" unless normalized.finite? && !normalized.negative? normalized end |
#normalize_lufs(target_lufs = -14.0)) ⇒ Object Also known as: normalizeLufs
347 348 349 350 |
# File 'lib/deftones/io/buffer.rb', line 347 def normalize_lufs(target_lufs = -14.0) target_gain = 10.0**((target_lufs.to_f + 0.691) / 20.0) normalize_rms(target_gain) end |
#normalize_rms(target_rms = 0.2) ⇒ Object Also known as: normalizeRms
339 340 341 342 343 344 345 |
# File 'lib/deftones/io/buffer.rb', line 339 def normalize_rms(target_rms = 0.2) normalized_target = normalize_level_target(target_rms, "target RMS") return new_like(@samples) if rms.zero? scale = normalized_target / rms new_like(@samples.map { |sample| sample * scale }) end |
#number_of_channels ⇒ Object Also known as: numberOfChannels
181 182 183 |
# File 'lib/deftones/io/buffer.rb', line 181 def number_of_channels @channels end |
#peak ⇒ Object
140 141 142 |
# File 'lib/deftones/io/buffer.rb', line 140 def peak @peak_cache ||= @samples.map(&:abs).max || 0.0 end |
#resample(target_sample_rate, interpolation: @interpolation) ⇒ Object Also known as: resampleTo
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/deftones/io/buffer.rb', line 234 def resample(target_sample_rate, interpolation: @interpolation) normalized_sample_rate = target_sample_rate.to_f raise ArgumentError, "sample rate must be positive" unless normalized_sample_rate.positive? && normalized_sample_rate.finite? return new_like(@samples) if normalized_sample_rate == @sample_rate.to_f target_frames = (frames * (normalized_sample_rate / @sample_rate.to_f)).round channel_data = Array.new(@channels) do |channel_index| Array.new(target_frames) do |frame_index| source_position = frame_index * (@sample_rate.to_f / normalized_sample_rate) sample_at(source_position, channel_index, interpolation: interpolation) end end self.class.from_array( channel_data, sample_rate: normalized_sample_rate.round, channels: @channels, interpolation: normalize_interpolation(interpolation) ) end |
#reverse ⇒ Object
326 327 328 329 |
# File 'lib/deftones/io/buffer.rb', line 326 def reverse reversed_frames = each_frame.to_a.reverse.flatten new_like(reversed_frames) end |
#rms ⇒ Object
144 145 146 147 148 |
# File 'lib/deftones/io/buffer.rb', line 144 def rms return 0.0 if @samples.empty? @rms_cache ||= Math.sqrt(@samples.sum { |sample| sample * sample } / @samples.length) end |
#sample_at(frame_position, channel = 0, interpolation: @interpolation) ⇒ Object Also known as: sampleAt
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/deftones/io/buffer.rb', line 200 def sample_at(frame_position, channel = 0, interpolation: @interpolation) return 0.0 if @samples.empty? clamped_position = Deftones::DSP::Helpers.clamp(frame_position.to_f, 0.0, [frames - 1, 0].max) channel_index = [channel, @channels - 1].min case normalize_interpolation(interpolation) when :nearest self[clamped_position.round, channel_index] when :cubic cubic_sample_at(clamped_position, channel_index) when :sinc_lite sinc_lite_sample_at(clamped_position, channel_index) else linear_sample_at(clamped_position, channel_index) end end |
#sample_at_cubic(frame_position, channel = 0) ⇒ Object Also known as: sampleAtCubic
221 222 223 |
# File 'lib/deftones/io/buffer.rb', line 221 def sample_at_cubic(frame_position, channel = 0) sample_at(frame_position, channel, interpolation: :cubic) end |
#sample_at_nearest(frame_position, channel = 0) ⇒ Object Also known as: sampleAtNearest
217 218 219 |
# File 'lib/deftones/io/buffer.rb', line 217 def sample_at_nearest(frame_position, channel = 0) sample_at(frame_position, channel, interpolation: :nearest) end |
#sample_at_sinc_lite(frame_position, channel = 0) ⇒ Object Also known as: sampleAtSincLite
225 226 227 |
# File 'lib/deftones/io/buffer.rb', line 225 def sample_at_sinc_lite(frame_position, channel = 0) sample_at(frame_position, channel, interpolation: :sinc_lite) end |
#save(target, format: nil, on_format_mismatch: :error, bit_depth: 16, dither: false, dither_rng: nil) ⇒ Object
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/deftones/io/buffer.rb', line 375 def save(target, format: nil, on_format_mismatch: :error, bit_depth: 16, dither: false, dither_rng: nil) if target.respond_to?(:write) && !target.is_a?(String) return save_io(target, format: format || :wav, bit_depth: bit_depth, dither: dither, dither_rng: dither_rng) end self.class.send(:validate_path_string!, target, role: "audio target") resolved_format = self.class.send(:resolve_save_format, target, format, on_format_mismatch: on_format_mismatch) raise Deftones::UnsupportedAudioFormatError, "Unsupported format: #{resolved_format}" unless SAVEABLE_FORMATS.include?(resolved_format) case resolved_format when :wav save_wav(target, bit_depth: bit_depth, dither: dither, dither_rng: dither_rng) when :mp3, :ogg save_compressed(target, resolved_format, bit_depth: bit_depth, dither: dither, dither_rng: dither_rng) end target end |
#save_compressed(path, format, bit_depth:, dither:, dither_rng:) ⇒ Object (private)
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 |
# File 'lib/deftones/io/buffer.rb', line 438 def save_compressed(path, format, bit_depth:, dither:, dither_rng:) backend = self.class.send(:encoder_backend_for, format) raise Deftones::MissingCodecBackendError, self.class.send(:missing_encoder_message, format) unless backend Tempfile.create(["deftones-buffer-export", ".wav"]) do |tempfile| tempfile.close save_wav(tempfile.path, bit_depth: bit_depth, dither: dither, dither_rng: dither_rng) if self.class.send(:custom_codec_backend?, backend) backend.encode(tempfile.path, path, format: format, sample_rate: @sample_rate, channels: @channels) return end command = self.class.send(:encoder_command, backend, tempfile.path, path, format, @sample_rate, @channels) stdout, stderr, status = self.class.send(:capture_codec_command, *command) return if status.success? self.class.send(:raise_codec_command_error, "Failed to encode #{format}", command, stdout, stderr, status) end end |
#save_io(io, format:, bit_depth:, dither:, dither_rng:) ⇒ Object (private)
413 414 415 416 417 418 419 420 |
# File 'lib/deftones/io/buffer.rb', line 413 def save_io(io, format:, bit_depth:, dither:, dither_rng:) Tempfile.create(["deftones-buffer-save", ".#{format}"]) do |tempfile| tempfile.close save(tempfile.path, format: format, bit_depth: bit_depth, dither: dither, dither_rng: dither_rng) io.write(File.binread(tempfile.path)) end io end |
#save_wav(path, bit_depth:, dither:, dither_rng:) ⇒ Object (private)
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 |
# File 'lib/deftones/io/buffer.rb', line 422 def save_wav(path, bit_depth:, dither:, dither_rng:) self.class.send(:ensure_wav_backend!) normalized_bit_depth = self.class.send(:validate_wav_bit_depth, bit_depth) output_samples = dither ? dithered_samples(normalized_bit_depth, dither_rng) : @samples sample_buffer = Wavify::Core::SampleBuffer.new( output_samples, self.class.send(:wavify_work_format, @channels, @sample_rate) ) Wavify::Codecs::Wav.write( path, sample_buffer, format: self.class.send(:wavify_wav_format, @channels, @sample_rate, normalized_bit_depth) ) end |
#sinc(value) ⇒ Object
298 299 300 301 302 303 |
# File 'lib/deftones/io/buffer.rb', line 298 def sinc(value) return 1.0 if value.abs < 1.0e-12 x = Math::PI * value Math.sin(x) / x end |
#sinc_lite_sample_at(clamped_position, channel) ⇒ Object
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/deftones/io/buffer.rb', line 279 def sinc_lite_sample_at(clamped_position, channel) center = clamped_position.floor weighted_sum = 0.0 weight_total = 0.0 ((center - SINC_LITE_RADIUS + 1)..(center + SINC_LITE_RADIUS)).each do |index| next if index.negative? || index >= frames distance = clamped_position - index weight = sinc(distance) * hann_window(distance / SINC_LITE_RADIUS) weighted_sum += self[index, channel] * weight weight_total += weight end return linear_sample_at(clamped_position, channel) if weight_total.abs < 1.0e-12 weighted_sum / weight_total end |
#slice(start_frame, length) ⇒ Object
312 313 314 315 316 317 318 |
# File 'lib/deftones/io/buffer.rb', line 312 def slice(start_frame, length) frame_count = [length.to_i, 0].max first_frame = [start_frame.to_i, 0].max offset = first_frame * @channels subset = @samples.slice(offset, frame_count * @channels) || [] new_like(subset) end |
#slice_seconds(start_time, duration) ⇒ Object Also known as: sliceSeconds
320 321 322 323 324 |
# File 'lib/deftones/io/buffer.rb', line 320 def slice_seconds(start_time, duration) start_frame = (Deftones::Music::Time.parse(start_time) * @sample_rate).floor frame_count = (Deftones::Music::Time.parse(duration) * @sample_rate).ceil slice(start_frame, frame_count) end |
#statistics(clip_threshold: 1.0) ⇒ Object Also known as: stats
161 162 163 164 165 166 167 168 |
# File 'lib/deftones/io/buffer.rb', line 161 def statistics(clip_threshold: 1.0) threshold = clip_threshold.to_f.abs @statistics_cache[threshold] ||= Statistics.new( peak: peak, rms: rms, clip_count: clip_count(threshold) ) end |
#to_array ⇒ Object Also known as: toArray
192 193 194 |
# File 'lib/deftones/io/buffer.rb', line 192 def to_array Array.new(@channels) { |channel_index| get_channel_data(channel_index) } end |