Class: WaveFile
- Inherits:
-
Object
- Object
- WaveFile
- Defined in:
- lib/wavefile.rb
Overview
WAV File Specification FROM ccrma.stanford.edu/courses/422/projects/WaveFormat/ The canonical WAVE format starts with the RIFF header: 0 4 ChunkID Contains the letters “RIFF” in ASCII form
(0x52494646 big-endian form).
4 4 ChunkSize 36 + SubChunk2Size, or more precisely:
4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)
This is the size of the rest of the chunk
following this number. This is the size of the
entire file in bytes minus 8 bytes for the
two fields not included in this count:
ChunkID and ChunkSize.
8 4 Format Contains the letters “WAVE”
(0x57415645 big-endian form).
The “WAVE” format consists of two subchunks: “fmt ” and “data”: The “fmt ” subchunk describes the sound data’s format: 12 4 Subchunk1ID Contains the letters “fmt ”
(0x666d7420 big-endian form).
16 4 Subchunk1Size 16 for PCM. This is the size of the
rest of the Subchunk which follows this number.
20 2 AudioFormat PCM = 1 (i.e. Linear quantization)
Values other than 1 indicate some
form of compression.
22 2 NumChannels Mono = 1, Stereo = 2, etc. 24 4 SampleRate 8000, 44100, etc. 28 4 ByteRate == SampleRate * NumChannels * BitsPerSample/8 32 2 BlockAlign == NumChannels * BitsPerSample/8
The number of bytes for one sample including
all channels. I wonder what happens when
this number isn't an integer?
34 2 BitsPerSample 8 bits = 8, 16 bits = 16, etc.
The “data” subchunk contains the size of the data and the actual sound: 36 4 Subchunk2ID Contains the letters “data”
(0x64617461 big-endian form).
40 4 Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8
This is the number of bytes in the data.
You can also think of this as the size
of the read of the subchunk following this
number.
44 * Data The actual sound data.
Constant Summary collapse
- CHUNK_ID =
"RIFF"- FORMAT =
"WAVE"- FORMAT_CHUNK_ID =
"fmt "- SUB_CHUNK1_SIZE =
16- PCM =
1- DATA_CHUNK_ID =
"data"- HEADER_SIZE =
36
Instance Attribute Summary collapse
-
#bits_per_sample ⇒ Object
Returns the value of attribute bits_per_sample.
-
#block_align ⇒ Object
readonly
Returns the value of attribute block_align.
-
#byte_rate ⇒ Object
readonly
Returns the value of attribute byte_rate.
-
#num_channels ⇒ Object
Returns the value of attribute num_channels.
-
#sample_rate ⇒ Object
Returns the value of attribute sample_rate.
Class Method Summary collapse
Instance Method Summary collapse
- #duration ⇒ Object
-
#initialize(num_channels, sample_rate, bits_per_sample, sample_data = []) ⇒ WaveFile
constructor
A new instance of WaveFile.
- #inspect ⇒ Object
- #mono? ⇒ Boolean
- #normalized_sample_data ⇒ Object
- #reverse ⇒ Object
- #sample_data ⇒ Object
- #sample_data=(sample_data) ⇒ Object
- #save(path) ⇒ Object
- #stereo? ⇒ Boolean
Constructor Details
#initialize(num_channels, sample_rate, bits_per_sample, sample_data = []) ⇒ WaveFile
Returns a new instance of WaveFile.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/wavefile.rb', line 57 def initialize(num_channels, sample_rate, bits_per_sample, sample_data = []) if num_channels == :mono @num_channels = 1 elsif num_channels == :stereo @num_channels = 2 else @num_channels = num_channels end @sample_rate = sample_rate @bits_per_sample = bits_per_sample @sample_data = sample_data @byte_rate = sample_rate * @num_channels * (bits_per_sample / 8) @block_align = @num_channels * (bits_per_sample / 8) end |
Instance Attribute Details
#bits_per_sample ⇒ Object
Returns the value of attribute bits_per_sample.
352 353 354 |
# File 'lib/wavefile.rb', line 352 def bits_per_sample @bits_per_sample end |
#block_align ⇒ Object (readonly)
Returns the value of attribute block_align.
352 353 354 |
# File 'lib/wavefile.rb', line 352 def block_align @block_align end |
#byte_rate ⇒ Object (readonly)
Returns the value of attribute byte_rate.
352 353 354 |
# File 'lib/wavefile.rb', line 352 def byte_rate @byte_rate end |
#num_channels ⇒ Object
Returns the value of attribute num_channels.
352 353 354 |
# File 'lib/wavefile.rb', line 352 def num_channels @num_channels end |
#sample_rate ⇒ Object
Returns the value of attribute sample_rate.
353 354 355 |
# File 'lib/wavefile.rb', line 353 def sample_rate @sample_rate end |
Class Method Details
.open(path) ⇒ Object
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 |
# File 'lib/wavefile.rb', line 73 def self.open(path) file = File.open(path, "rb") begin header = read_header(file) errors = validate_header(header) if errors == [] sample_data = read_sample_data(file, header[:num_channels], header[:bits_per_sample], header[:sub_chunk2_size]) wave_file = self.new(header[:num_channels], header[:sample_rate], header[:bits_per_sample], sample_data) else error_msg = "#{path} can't be opened, due to the following errors:\n" errors.each {|error| error_msg += " * #{error}\n" } raise StandardError, error_msg end rescue EOFError raise StandardError, "An error occured while reading #{path}." ensure file.close() end return wave_file end |
Instance Method Details
#duration ⇒ Object
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/wavefile.rb', line 246 def duration() total_samples = sample_data.length samples_per_millisecond = @sample_rate / 1000.0 samples_per_second = @sample_rate samples_per_minute = samples_per_second * 60 samples_per_hour = samples_per_minute * 60 hours, minutes, seconds, milliseconds = 0, 0, 0, 0 if(total_samples >= samples_per_hour) hours = total_samples / samples_per_hour total_samples -= samples_per_hour * hours end if(total_samples >= samples_per_minute) minutes = total_samples / samples_per_minute total_samples -= samples_per_minute * minutes end if(total_samples >= samples_per_second) seconds = total_samples / samples_per_second total_samples -= samples_per_second * seconds end milliseconds = (total_samples / samples_per_millisecond).floor return { :hours => hours, :minutes => minutes, :seconds => seconds, :milliseconds => milliseconds } end |
#inspect ⇒ Object
340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/wavefile.rb', line 340 def inspect() duration = self.duration() result = "Channels: #{@num_channels}\n" + "Sample rate: #{@sample_rate}\n" + "Bits per sample: #{@bits_per_sample}\n" + "Block align: #{@block_align}\n" + "Byte rate: #{@byte_rate}\n" + "Sample count: #{@sample_data.length}\n" + "Duration: #{duration[:hours]}h:#{duration[:minutes]}m:#{duration[:seconds]}s:#{duration[:milliseconds]}ms\n" end |
#mono? ⇒ Boolean
234 235 236 |
# File 'lib/wavefile.rb', line 234 def mono?() return num_channels == 1 end |
#normalized_sample_data ⇒ Object
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 |
# File 'lib/wavefile.rb', line 153 def normalized_sample_data() if @bits_per_sample == 8 min_value = 128.0 max_value = 127.0 midpoint = 128 elsif @bits_per_sample == 16 min_value = 32768.0 max_value = 32767.0 midpoint = 0 else raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported" end if mono? normalized_sample_data = @sample_data.map {|sample| sample -= midpoint if sample < 0 sample.to_f / min_value else sample.to_f / max_value end } else normalized_sample_data = @sample_data.map {|sample| sample.map {|sub_sample| sub_sample -= midpoint if sub_sample < 0 sub_sample.to_f / min_value else sub_sample.to_f / max_value end } } end return normalized_sample_data end |
#reverse ⇒ Object
242 243 244 |
# File 'lib/wavefile.rb', line 242 def reverse() sample_data.reverse!() end |
#sample_data ⇒ Object
149 150 151 |
# File 'lib/wavefile.rb', line 149 def sample_data() return @sample_data end |
#sample_data=(sample_data) ⇒ Object
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 |
# File 'lib/wavefile.rb', line 191 def sample_data=(sample_data) if sample_data.length > 0 && ((mono? && sample_data[0].class == Float) || (!mono? && sample_data[0][0].class == Float)) if @bits_per_sample == 8 # Samples in 8-bit wave files are stored as a unsigned byte # Effective values are 0 to 255, midpoint at 128 min_value = 128.0 max_value = 127.0 midpoint = 128 elsif @bits_per_sample == 16 # Samples in 16-bit wave files are stored as a signed little-endian short # Effective values are -32768 to 32767, midpoint at 0 min_value = 32768.0 max_value = 32767.0 midpoint = 0 else raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported" end if mono? @sample_data = sample_data.map {|sample| if(sample < 0.0) (sample * min_value).round + midpoint else (sample * max_value).round + midpoint end } else @sample_data = sample_data.map {|sample| sample.map {|sub_sample| if(sub_sample < 0.0) (sub_sample * min_value).round + midpoint else (sub_sample * max_value).round + midpoint end } } end else @sample_data = sample_data end end |
#save(path) ⇒ Object
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 |
# File 'lib/wavefile.rb', line 104 def save(path) # All numeric values should be saved in little-endian format sample_data_size = @sample_data.length * @num_channels * (@bits_per_sample / 8) # Write the header file_contents = CHUNK_ID file_contents += [HEADER_SIZE + sample_data_size].pack("V") file_contents += FORMAT file_contents += FORMAT_CHUNK_ID file_contents += [SUB_CHUNK1_SIZE].pack("V") file_contents += [PCM].pack("v") file_contents += [@num_channels].pack("v") file_contents += [@sample_rate].pack("V") file_contents += [@byte_rate].pack("V") file_contents += [@block_align].pack("v") file_contents += [@bits_per_sample].pack("v") file_contents += DATA_CHUNK_ID file_contents += [sample_data_size].pack("V") # Write the sample data if !mono? output_sample_data = [] @sample_data.each{|sample| sample.each{|sub_sample| output_sample_data << sub_sample } } else output_sample_data = @sample_data end if @bits_per_sample == 8 file_contents += output_sample_data.pack("C*") elsif @bits_per_sample == 16 file_contents += output_sample_data.pack("s*") else raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported" end file = File.open(path, "w") file.syswrite(file_contents) file.close end |
#stereo? ⇒ Boolean
238 239 240 |
# File 'lib/wavefile.rb', line 238 def stereo?() return num_channels == 2 end |