Class: Wavify::Codecs::Aiff

Inherits:
Base
  • Object
show all
Defined in:
lib/wavify/codecs/aiff.rb,
sig/codecs.rbs

Overview

AIFF codec for PCM audio and uncompressed AIFF-C variants.

Constant Summary collapse

EXTENSIONS =

Recognized filename extensions.

%w[.aiff .aif .aifc].freeze
AIFF_MAX_SIZE =

:nodoc:

0xFFFF_FFFF

Class Method Summary collapse

Methods inherited from Base

available?

Class Method Details

.can_read?(io_or_path) ⇒ Boolean

Parameters:

  • io_or_path (String, IO)

Returns:

  • (Boolean)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/wavify/codecs/aiff.rb', line 14

def can_read?(io_or_path)
  if io_or_path.is_a?(String)
    return true if EXTENSIONS.include?(File.extname(io_or_path).downcase)
    return false unless File.file?(io_or_path)
  end

  io, close_io = open_input(io_or_path)
  return false unless io

  header = probe_bytes(io, 12)
  header&.start_with?("FORM") && %w[AIFF AIFC].include?(header[8, 4])
rescue StreamError
  false
ensure
  io.close if close_io && io
end

.metadata(io_or_path) ⇒ Hash

Reads AIFF metadata without decoding the full audio payload.

Parameters:

  • io_or_path (String, IO)

Returns:

  • (Hash)


171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/wavify/codecs/aiff.rb', line 171

def (io_or_path)
  io, close_io = open_input(io_or_path)
  ensure_seekable!(io)

  info = parse_chunks(io)
  format = info.fetch(:format)
  sample_frame_count = info.fetch(:sample_frame_count)

  {
    format: format,
    sample_frame_count: sample_frame_count,
    duration: Core::Duration.from_samples(sample_frame_count, format.sample_rate),
    form_type: info[:form_type],
    compression_type: info[:compression_type],
    compression_name: info[:compression_name],
    encoded_sample_rate: info[:encoded_sample_rate],
    container_bit_depth: info[:container_bit_depth],
    valid_bits_per_sample: info[:valid_bits_per_sample],
    warnings: info[:warnings].freeze,
    markers: info[:markers],
    instrument: info[:instrument]
  }
ensure
  io.close if close_io && io
end

.read(io_or_path, format: nil, warning_io: nil) ⇒ Wavify::Core::SampleBuffer

Reads an AIFF file/IO into a sample buffer.

Parameters:

  • io_or_path (String, IO)
  • format (Wavify::Core::Format, nil) (defaults to: nil)
  • format: (Core::Format, nil) (defaults to: nil)
  • warning_io: (IO, nil) (defaults to: nil)

Returns:



36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/wavify/codecs/aiff.rb', line 36

def read(io_or_path, format: nil, warning_io: nil)
  io, close_io = open_input(io_or_path)
  ensure_seekable!(io)

  info = parse_chunks(io)
  emit_warnings(info.fetch(:warnings), warning_io)
  source_format = info.fetch(:format)
  samples = read_sound_data(io, info, source_format)
  buffer = Core::SampleBuffer.new(samples, source_format)
  format ? buffer.convert(format) : buffer
ensure
  io.close if close_io && io
end

.stream_read(io_or_path, chunk_size: 4096, warning_io: nil) {|arg0| ... } ⇒ Enumerator

Streams AIFF decoding in frame chunks.

Parameters:

  • io_or_path (String, IO)
  • chunk_size (Integer) (defaults to: 4096)
  • chunk_size: (Integer) (defaults to: 4096)
  • warning_io: (IO, nil) (defaults to: nil)

Yields:

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (Enumerator)


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/wavify/codecs/aiff.rb', line 80

def stream_read(io_or_path, chunk_size: 4096, warning_io: nil)
  return enum_for(__method__, io_or_path, chunk_size: chunk_size, warning_io: warning_io) unless block_given?
  raise InvalidParameterError, "chunk_size must be a positive Integer" unless chunk_size.is_a?(Integer) && chunk_size.positive?

  io, close_io = open_input(io_or_path)
  ensure_seekable!(io)

  info = parse_chunks(io)
  emit_warnings(info.fetch(:warnings), warning_io)
  format = info.fetch(:format)
  bytes_per_frame = format.block_align
  remaining = info.fetch(:sound_data_size)
  io.seek(info.fetch(:sound_data_offset), IO::SEEK_SET)

  while remaining.positive?
    bytes = [remaining, bytes_per_frame * chunk_size].min
    chunk_data = read_exact(io, bytes, "truncated SSND data")
    yield Core::SampleBuffer.new(decode_samples(chunk_data, format, byte_order: info.fetch(:byte_order)), format)
    remaining -= bytes
  end
ensure
  io.close if close_io && io
end

.stream_write(io_or_path, format:, form_type: nil, compression_type: nil, compression_name: nil, **codec_options) {|arg0| ... } ⇒ Enumerator, ...

Streams AIFF encoding through a yielded chunk writer.

Parameters:

Yields:

Yield Parameters:

  • arg0 (Object)

Yield Returns:

  • (void)

Returns:

  • (Enumerator, String, IO)


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/wavify/codecs/aiff.rb', line 109

def stream_write(io_or_path, format:, form_type: nil, compression_type: nil, compression_name: nil,
                 **codec_options)
  validate_no_codec_options!(codec_options, operation: "AIFF stream_write")
  unless block_given?
    return enum_for(
      __method__,
      io_or_path,
      format: format,
      form_type: form_type,
      compression_type: compression_type,
      compression_name: compression_name,
      **codec_options
    )
  end
  raise InvalidParameterError, "format must be Core::Format" unless format.is_a?(Core::Format)
  raise UnsupportedFormatError, "AIFF stream writer supports PCM only" unless format.sample_format == :pcm

  write_options = normalize_write_options(
    io_or_path,
    form_type: form_type,
    compression_type: compression_type,
    compression_name: compression_name
  )
  io, close_io = open_output(io_or_path)
  ensure_seekable!(io)
  prepare_output!(io, owned: close_io)

  header = write_stream_header(io, format, write_options)
  total_data_bytes = 0
  total_sample_frames = 0
  writer = lambda do |buffer|
    raise InvalidParameterError, "stream chunk must be Core::SampleBuffer" unless buffer.is_a?(Core::SampleBuffer)

    converted = buffer.format == format ? buffer : buffer.convert(format)
    encoded_bytes = converted.sample_frame_count * format.block_align
    validate_aiff_sizes!(
      data_bytes: total_data_bytes + encoded_bytes,
      sample_frames: total_sample_frames + converted.sample_frame_count
    )
    encoded = encode_samples(converted.samples, format, byte_order: write_options.fetch(:byte_order))
    unless encoded.bytesize == encoded_bytes
      raise StreamError, "AIFF encoder produced an unexpected byte count"
    end

    write_all(io, encoded)
    total_data_bytes += encoded_bytes
    total_sample_frames += converted.sample_frame_count
  end
  yield writer

  write_all(io, "\x00") if total_data_bytes.odd?
  finalize_stream_header(io, header, total_data_bytes, total_sample_frames)
  finalize_output!(io, owned: close_io)
  io_or_path
ensure
  io.close if close_io && io
end

.write(io_or_path, sample_buffer, format: nil, form_type: nil, compression_type: nil, compression_name: nil, **codec_options) ⇒ String, IO

Writes a sample buffer as AIFF (PCM only).

Parameters:

Returns:

  • (String, IO)

Raises:



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/wavify/codecs/aiff.rb', line 56

def write(io_or_path, sample_buffer, format: nil, form_type: nil, compression_type: nil, compression_name: nil,
          **codec_options)
  validate_no_codec_options!(codec_options, operation: "AIFF write")
  raise InvalidParameterError, "sample_buffer must be Core::SampleBuffer" unless sample_buffer.is_a?(Core::SampleBuffer)

  target_format = format || sample_buffer.format
  raise InvalidParameterError, "format must be Core::Format" unless target_format.is_a?(Core::Format)
  raise UnsupportedFormatError, "AIFF writer supports PCM only" unless target_format.sample_format == :pcm

  buffer = sample_buffer.format == target_format ? sample_buffer : sample_buffer.convert(target_format)
  stream_write(
    io_or_path,
    format: target_format,
    form_type: form_type,
    compression_type: compression_type,
    compression_name: compression_name
  ) { |writer| writer.call(buffer) }
end