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
|
module WaveFile
# Public: Error that is raised when an attempt is made to perform an unsupported or
# undefined conversion between two sample data formats. For example, converting a Buffer
# with 4 channels into a Buffer with 3 channels is undefined.
class BufferConversionError < StandardError; end
# Public: Represents a collection of samples in a certain format (e.g. 16-bit mono Integer PCM).
# Reader returns sample data contained in Buffers, and Writer expects incoming sample
# data to be contained in a Buffer as well.
#
# Contains methods to convert the sample data in the buffer to a different format.
class Buffer
# Public: Creates a new Buffer.
#
# samples - An array of samples. If the Format has 1 channel (i.e. is mono), this
# should be a flat array of samples such as <code>[0.5, 0.4, -0.3, ...]</code>.
# If the Format has 2 or more channels the array should include a sub-array for
# each sample frame. For example, <code>[[0.5, 0.2], [0.1, 0.6], [-0.2, 0.4], ...]</code>
# for a stereo file.
#
# The individual samples should match the given format:
#
# :pcm_8 - Integer between 0 and 255
# :pcm_16 - Integer between -32_768 and 32_767
# :pcm_24 - Integer between -8_388_608 and 8_388_607
# :pcm_32 - Integer between 2_147_483_648 and 2_147_483_647
# :float - Float between -1.0 and 1.0
# :float_32 - Float between -1.0 and 1.0
# :float_64 - Float between -1.0 and 1.0
#
# format - A Format instance which describes the sample format of the sample array.
#
# Note that the sample array is not compared with the format to make sure
# they match - you are on the honor system to make sure they do. If they
# don't match, unexpected things will happen.
#
# Examples
# # One cycle of a floating point 441Hz mono square wave
# samples = ([0.5] * 50) + ([-0.5] * 50)
# buffer = Buffer.new(samples, Format.new(:mono, :float, 44100))
#
# # One cycle of a floating point 441Hz stereo square wave
# samples = ([0.5, 0.5] * 50) + ([-0.5, -0.5] * 50)
# buffer = Buffer.new(samples, Format.new(2, :float, 44100))
#
# # One cycle of a 16-bit PCM 441Hz mono square wave
# samples = ([16000] * 50) + ([-16000] * 50)
# buffer = Buffer.new(samples, Format.new(1, :pcm_16, 44100))
#
# Returns a constructed Buffer.
def initialize(samples, format)
@samples = samples
@format = format
end
# Public: Creates a new Buffer containing the sample data of this Buffer, but converted
# to a different format.
#
# new_format - The format that the sample data should be converted to. If the new format
# has a different number of channels than the original buffer format, the sample data
# will be converted in the following way:
#
# 1 -> n: Each mono sample will be duplicated into the new number of channels.
#
# n -> 1: Each sample in each sample frame will be averaged into a single sample.
#
# (n > 2) -> 2: The first two channels will be kept, all other channels discarded.
#
# other: Unsupported, will cause BufferConversionError to be raised.
#
# Examples
#
# new_format = Format.new(:mono, :pcm_16, 44100)
# new_buffer = old_buffer.convert(new_format)
#
# Returns a new Buffer; the existing Buffer is unmodified.
#
# Raises BufferConversionError if the Buffer can't be converted to the given format
def convert(new_format)
new_samples = convert_buffer(@samples.dup, @format, new_format)
Buffer.new(new_samples, new_format)
end
# Public: Converts the sample data contained in the Buffer to a new format. The sample
# data is converted in place, so the existing Buffer is modified.
#
# new_format - The format that the sample data should be converted to. See Buffer#convert
# for how samples will be mapped if the new number of channels differs from
# the original number of channels.
#
# Examples
#
# new_format = Format.new(:mono, :pcm_16, 44100)
# old_buffer.convert!(new_format)
#
# Returns self.
#
# Raises BufferConversionError if the Buffer can't be converted to the given format
def convert!(new_format)
@samples = convert_buffer(@samples, @format, new_format)
@format = new_format
self
end
# Public: Returns the number of channels the buffer's sample data has
def channels
@format.channels
end
# Public: Returns the bits per sample of the buffer's sample data
def bits_per_sample
@format.bits_per_sample
end
# Public: Returns the sample rate of the buffer's sample data
def sample_rate
@format.sample_rate
end
# Public: Returns the sample data contained in the Buffer as an Array. If the Format
# has 1 channel, the Array will be a flat list of samples. If the Format has 2 or
# more channels, the Array will include sub arrays for each sample frame, with a sample
# for each channel.
#
# Examples
#
# samples = mono_buffer.samples
# # => [-0.5, 0.3, 0.2, -0.9, ...]
#
# samples = stereo_buffer.samples
# # => [[-0.2, 0.5], [0.1, 0.2], [-0.4, 0.7], [0.1, 0.2], ...]
#
# samples = three_channel_buffer.samples
# # => [[0.3, 0.5, 0.2], [-0.1, 0.2, -0.9], [0.2, 0.3, -0.4], [0.1, 0.2, -0.8], ...]
attr_reader :samples
private
def convert_buffer(samples, old_format, new_format)
if old_format.channels > new_format.channels
samples = convert_channels(samples, old_format.channels, new_format.channels)
samples = convert_sample_format(samples, old_format, new_format)
else
samples = convert_sample_format(samples, old_format, new_format)
samples = convert_channels(samples, old_format.channels, new_format.channels)
end
samples
end
def convert_channels(samples, old_channels, new_channels)
return samples if old_channels == new_channels
# The cases of mono -> stereo and vice-versa are handled specially,
# because those conversion methods are faster than the general methods,
# and the large majority of wave files are expected to be either mono or stereo.
if old_channels == 1 && new_channels == 2
samples.map! {|sample| [sample, sample]}
elsif old_channels == 2 && new_channels == 1
samples.map! {|sample| (sample[0] + sample[1]) / 2}
elsif old_channels == 1 && new_channels >= 2
samples.map! {|sample| [].fill(sample, 0, new_channels)}
elsif old_channels >= 2 && new_channels == 1
samples.map! {|sample| sample.inject(0) {|sub_sample, sum| sum + sub_sample } / old_channels }
elsif old_channels > 2 && new_channels == 2
samples.map! {|sample| [sample[0], sample[1]]}
else
raise BufferConversionError,
"Conversion of sample data from #{old_channels} channels to #{new_channels} channels is unsupported"
end
samples
end
def convert_sample_format(samples, old_format, new_format)
return samples if old_format.sample_format == :float && new_format.sample_format == :float
if old_format.sample_format == :pcm && new_format.sample_format == :pcm
convert_sample_format_pcm_to_pcm(samples, old_format.bits_per_sample, new_format.bits_per_sample)
elsif old_format.sample_format == :pcm && new_format.sample_format == :float
convert_sample_format_pcm_to_float(samples, old_format.bits_per_sample, new_format.bits_per_sample)
elsif old_format.sample_format == :float && new_format.sample_format == :pcm
convert_sample_format_float_to_pcm(samples, old_format.bits_per_sample, new_format.bits_per_sample)
end
end
def convert_sample_format_pcm_to_pcm(samples, old_bits_per_sample, new_bits_per_sample)
return samples if old_bits_per_sample == new_bits_per_sample
shift_amount = (new_bits_per_sample - old_bits_per_sample).abs
if old_bits_per_sample == 8
convert_sample_format_helper(samples) {|sample| (sample - 128) << shift_amount }
elsif new_bits_per_sample == 8
convert_sample_format_helper(samples) {|sample| (sample >> shift_amount) + 128 }
else
if new_bits_per_sample > old_bits_per_sample
convert_sample_format_helper(samples) {|sample| sample << shift_amount }
else
convert_sample_format_helper(samples) {|sample| sample >> shift_amount }
end
end
end
def convert_sample_format_pcm_to_float(samples, old_bits_per_sample, new_bits_per_sample)
if old_bits_per_sample == 8
convert_sample_format_helper(samples) {|sample| (sample - 128).to_f / 128.0 }
elsif old_bits_per_sample == 16
convert_sample_format_helper(samples) {|sample| sample.to_f / 32768.0 }
elsif old_bits_per_sample == 24
convert_sample_format_helper(samples) {|sample| sample.to_f / 8388608.0 }
elsif old_bits_per_sample == 32
convert_sample_format_helper(samples) {|sample| sample.to_f / 2147483648.0 }
end
end
def convert_sample_format_float_to_pcm(samples, old_bits_per_sample, new_bits_per_sample)
if new_bits_per_sample == 8
convert_sample_format_helper(samples) {|sample| (sample * 127.0).round + 128 }
elsif new_bits_per_sample == 16
convert_sample_format_helper(samples) {|sample| (sample * 32767.0).round }
elsif new_bits_per_sample == 24
convert_sample_format_helper(samples) {|sample| (sample * 8388607.0).round }
elsif new_bits_per_sample == 32
convert_sample_format_helper(samples) {|sample| (sample * 2147483647.0).round }
end
end
def convert_sample_format_helper(samples, &converter)
more_than_one_channel = (Array === samples.first)
if more_than_one_channel
samples.map! do |sample|
sample.map!(&converter)
end
else
samples.map!(&converter)
end
end
end
end
|