1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 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 166 167 168 169 170 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 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
|
module WaveFile
# Public: Error that is raised when a file is not in a format supported by this Gem.
# For example, because it's a valid Wave file whose format is not supported by
# this Gem. Or, because it's a not a valid Wave file period.
class FormatError < StandardError; end
# Public: Error that is raised when constructing a Format instance that is not valid,
# trying to read from a file that is not a wave file, or trying to read from a file
# that is not valid according to the wave file spec.
class InvalidFormatError < FormatError; end
# Public: Error that is raised when trying to read from a valid wave file that has its sample data
# stored in a format that Reader doesn't understand.
class UnsupportedFormatError < FormatError; end
# Public: Represents information about the data format for a Wave file, such as number of
# channels, bits per sample, sample rate, and so forth. A Format instance is used
# by Reader to indicate what format to read samples out as, and by Writer to
# indicate what format to write samples as.
class Format
# Public: Constructs a new immutable Format.
#
# channels - The number of channels in the format. Can either be an Integer
# (e.g. 1, 2, 3) or the symbols +:mono+ (equivalent to 1) or
# +:stereo+ (equivalent to 2).
# format_code - A symbol indicating the format of each sample. Consists of
# two parts: a format code, and the bits per sample. The valid
# values are +:pcm_8+, +:pcm_16+, +:pcm_24+, +:pcm_32+, +:float_32+,
# +:float_64+, and +:float+ (equivalent to +:float_32+)
# sample_rate - The number of samples per second, such as 44100
# speaker_mapping - An optional array which indicates which speaker each channel should be
# mapped to. Each value in the array should be one of these values:
#
# +:front_left+, +:front_right+, +:front_center+, +:low_frequency+, +:back_left+,
# +:back_right+, +:front_left_of_center+, +:front_right_of_center+,
# +:back_center+, +:side_left+, +:side_right+, +:top_center+, +:top_front_left+,
# +:top_front_center+, +:top_front_right+, +:top_back_left+, +:top_back_center+,
# +:top_back_right+.
#
# Each value should only appear once, and the channels must follow the ordering above.
#
# For example, <code>[:front_center, :back_left]</code>
# is a valid speaker mapping, but <code>[:back_left, :front_center]</code> is not.
# If a given channel should not be mapped to a specific speaker, the
# value +:undefined+ can be used. If this field is omitted, a default
# value for the given number of channels. For example, if there are 2
# channels, this will be set to <code>[:front_left, :front_right]</code>.
#
# Examples
#
# format = Format.new(1, :pcm_16, 44100)
# format = Format.new(:mono, :pcm_16, 44100) # Equivalent to above
#
# format = Format.new(:stereo, :float_32, 44100)
# format = Format.new(:stereo, :float, 44100) # Equivalent to above
#
# format = Format.new(2, :pcm_16, 44100, speaker_mapping: [:front_right, :front_center])
#
# # Channels should explicitly not be mapped to particular speakers
# # (otherwise, if no speaker_mapping set, it will be set to a default
# # value for the number of channels).
# format = Format.new(2, :pcm_16, 44100, speaker_mapping: [:undefined, :undefined])
#
# # Will result in InvalidFormatError, because speakers are defined in
# # invalid order
# format = Format.new(2, :pcm_16, 44100, speaker_mapping: [:front_right, :front_left])
#
# # speaker_mapping will be set to [:front_left, :undefined, :undefined],
# # because channels without a speaker mapping will be mapped to :undefined
# format = Format.new(3, :pcm_16, 44100, speaker_mapping: [:front_left])
#
# Raises InvalidFormatError if the given arguments are invalid.
def initialize(channels, format_code, sample_rate, speaker_mapping: nil)
channels = normalize_channels(channels)
validate_channels(channels)
validate_format_code(format_code)
validate_sample_rate(sample_rate)
sample_format, bits_per_sample = normalize_format_code(format_code)
speaker_mapping = normalize_speaker_mapping(channels, speaker_mapping)
validate_speaker_mapping(channels, speaker_mapping)
@channels = channels
@sample_format = sample_format
@bits_per_sample = bits_per_sample
@sample_rate = sample_rate
@block_align = (@bits_per_sample / 8) * @channels
@byte_rate = @block_align * @sample_rate
@speaker_mapping = speaker_mapping
end
# Public: Returns true if the format has 1 channel, false otherwise.
def mono?
@channels == 1
end
# Public: Returns true if the format has 2 channels, false otherwise.
def stereo?
@channels == 2
end
# Public: Returns the number of channels, such as 1 or 2. This will always return a
# Integer, even if the number of channels is specified with a symbol (e.g. +:mono+)
# in the constructor.
attr_reader :channels
# Public: Returns a symbol indicating the sample format, such as +:pcm+ or +:float+
attr_reader :sample_format
# Public: Returns the number of bits per sample, such as 8, 16, 24, 32, or 64.
attr_reader :bits_per_sample
# Public: Returns the number of samples per second, such as 44100.
attr_reader :sample_rate
# Public: Returns the number of bytes in each sample frame. For example, in a 16-bit stereo file,
# this will be 4 (2 bytes for each 16-bit sample, times 2 channels).
attr_reader :block_align
# Public: Returns the number of bytes contained in 1 second of sample data.
# Is equivalent to #block_align * #sample_rate.
attr_reader :byte_rate
# Public: Returns the mapping of each channel to a speaker.
attr_reader :speaker_mapping
private
# Internal
VALID_CHANNEL_RANGE = 1..65535 # :nodoc:
# Internal
VALID_SAMPLE_RATE_RANGE = 1..4_294_967_295 # :nodoc:
# Internal
SUPPORTED_FORMAT_CODES = [:pcm_8, :pcm_16, :pcm_24, :pcm_32, :float, :float_32, :float_64].freeze # :nodoc:
# Internal
def normalize_channels(channels)
if channels == :mono
return 1
elsif channels == :stereo
return 2
else
return channels
end
end
# Internal
def normalize_format_code(format_code)
if format_code == :float
[:float, 32]
else
sample_format, bits_per_sample = format_code.to_s.split("_")
[sample_format.to_sym, bits_per_sample.to_i]
end
end
# Internal
def normalize_speaker_mapping(channels, speaker_mapping)
if speaker_mapping.nil?
speaker_mapping = default_speaker_mapping(channels)
elsif !speaker_mapping.is_a?(Array)
return speaker_mapping
else
speaker_mapping = speaker_mapping.dup
end
if speaker_mapping.length < channels
speaker_mapping += [:undefined] * (channels - speaker_mapping.length)
end
speaker_mapping.freeze
end
# Internal
def default_speaker_mapping(channels)
# These default mappings determined from these sources:
#
# See https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/extensible-wave-format-descriptors
# This article says to use the `front_center` speaker for mono files when using WAVE_FORMAT_EXTENSIBLE
#
# https://msdn.microsoft.com/en-us/library/windows/desktop/dd390971(v=vs.85).aspx
#
# https://xiph.org/flac/format.html#frame_header
if channels == 1 # Mono
[:front_center]
elsif channels == 2 # Stereo
[:front_left, :front_right]
elsif channels == 3
[:front_left, :front_right, :front_center]
elsif channels == 4 # Quad
[:front_left, :front_right, :back_left, :back_right]
elsif channels == 5
[:front_left, :front_right, :front_center, :back_left, :back_right]
elsif channels == 6 # 5.1
[:front_left, :front_right, :front_center, :low_frequency, :back_left, :back_right]
elsif channels == 7
[:front_left, :front_right, :front_center, :low_frequency, :back_center, :side_left, :side_right]
elsif channels == 8 # 7.1
[:front_left, :front_right, :front_center, :low_frequency, :back_left, :back_right, :front_left_of_center, :front_right_of_center]
elsif channels <= UnvalidatedFormat::SPEAKER_POSITIONS.length
UnvalidatedFormat::SPEAKER_POSITIONS[0...channels]
else
UnvalidatedFormat::SPEAKER_POSITIONS
end
end
# Internal
def validate_channels(candidate_channels)
unless candidate_channels.is_a?(Integer) && VALID_CHANNEL_RANGE === candidate_channels
raise InvalidFormatError,
"Invalid number of channels: `#{candidate_channels}`. Must be an Integer between #{VALID_CHANNEL_RANGE.min} and #{VALID_CHANNEL_RANGE.max}."
end
end
# Internal
def validate_format_code(candidate_format_code)
unless SUPPORTED_FORMAT_CODES.include? candidate_format_code
raise InvalidFormatError,
"Invalid sample format: `#{candidate_format_code}`. Must be one of: #{SUPPORTED_FORMAT_CODES.inspect}"
end
end
# Internal
def validate_sample_rate(candidate_sample_rate)
unless candidate_sample_rate.is_a?(Integer) && VALID_SAMPLE_RATE_RANGE === candidate_sample_rate
raise InvalidFormatError,
"Invalid sample rate: `#{candidate_sample_rate}`. Must be an Integer between #{VALID_SAMPLE_RATE_RANGE.min} and #{VALID_SAMPLE_RATE_RANGE.max}"
end
end
# Internal
def validate_speaker_mapping(channels, candidate_speaker_mapping)
if candidate_speaker_mapping.is_a?(Array) && candidate_speaker_mapping.length == channels
speaker_mapping_without_invalid_speakers = UnvalidatedFormat::SPEAKER_POSITIONS & candidate_speaker_mapping
if speaker_mapping_without_invalid_speakers.length < channels
speaker_mapping_without_invalid_speakers += [:undefined] * (channels - speaker_mapping_without_invalid_speakers.length)
end
if speaker_mapping_without_invalid_speakers == candidate_speaker_mapping
return
end
end
raise InvalidFormatError,
"Invalid speaker_mapping: `#{candidate_speaker_mapping.inspect}`. Should be an array the same size as the number of channels, containing either :undefined or these known speakers: #{UnvalidatedFormat::SPEAKER_POSITIONS.inspect}. Each defined speaker must come before any of the ones after it in the master list, and all :undefined speakers must come after the last defined speaker."
end
end
end
|