Module: DSP::Wav

Defined in:
lib/dsprb/wav.rb,
sig/dsprb.rbs

Constant Summary collapse

PCM =

Returns:

  • (::Integer)
1
IEEE_FLOAT =

Returns:

  • (::Integer)
3
EXTENSIBLE =

WAVE_FORMAT_EXTENSIBLE stores the effective format in the first two bytes of its SubFormat GUID rather than in the format field itself.

Returns:

  • (::Integer)
0xFFFE
HEADER_BYTES =

Returns:

  • (::Integer)
12
CHUNK_HEADER_BYTES =

Returns:

  • (::Integer)
8
EIGHT_BIT_MIDPOINT =

Unsigned 8-bit PCM is the one depth WAVE stores with a midpoint offset.

Returns:

  • (::Float)
128.0

Class Method Summary collapse

Class Method Details

.assert_riff!(bytes) ⇒ void

This method returns an undefined value.

Parameters:

  • (::String)

Raises:



54
55
56
57
58
# File 'lib/dsprb/wav.rb', line 54

def assert_riff!(bytes)
  return if bytes.bytesize >= HEADER_BYTES && bytes[0, 4] == "RIFF" && bytes[8, 4] == "WAVE"

  raise FormatError, "not a RIFF/WAVE stream"
end

.decode(bytes) ⇒ Waveform

Parameters:

  • (::String)

Returns:

Raises:



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
# File 'lib/dsprb/wav.rb', line 22

def decode(bytes)
  assert_riff!(bytes)

  format = nil
  channels = nil
  sample_rate = nil
  bits = nil
  data = nil
  position = HEADER_BYTES

  while position + CHUNK_HEADER_BYTES <= bytes.bytesize
    identifier = bytes[position, 4]
    size = bytes[position + 4, 4].unpack1("V")
    body = bytes[position + CHUNK_HEADER_BYTES, size]

    case identifier
    when "fmt "
      format, channels, sample_rate, _byte_rate, _align, bits = body.unpack("vvVVvv")
      format = body[24, 2].unpack1("v") if format == EXTENSIBLE && body.bytesize >= 26
    when "data"
      data = body
    end

    position += CHUNK_HEADER_BYTES + size + (size.odd? ? 1 : 0)
  end

  raise FormatError, "no fmt chunk" if sample_rate.nil?
  raise FormatError, "no data chunk" if data.nil?

  Waveform.new(samples: downmix(decode_samples(data, format, bits), channels), sample_rate: sample_rate)
end

.decode_packed_24(data) ⇒ ::Array[::Float]

Parameters:

  • (::String)

Returns:

  • (::Array[::Float])


84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/dsprb/wav.rb', line 84

def decode_packed_24(data)
  sign_bit = 1 << 23
  full_scale = sign_bit.to_f

  data.unpack("C*").each_slice(3).filter_map do |low, middle, high|
    next if high.nil?

    value = (high << 16) | (middle << 8) | low
    value -= sign_bit << 1 if value >= sign_bit
    value / full_scale
  end
end

.decode_samples(data, format, bits) ⇒ ::Array[::Float]

Parameters:

  • (::String)
  • (::Integer, nil)
  • (::Integer, nil)

Returns:

  • (::Array[::Float])


60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/dsprb/wav.rb', line 60

def decode_samples(data, format, bits)
  case [format, bits]
  in [PCM, 8]
    data.unpack("C*").map { |value| (value - EIGHT_BIT_MIDPOINT) / EIGHT_BIT_MIDPOINT }
  in [PCM, 16]
    scale(data.unpack("s<*"), 15)
  in [PCM, 24]
    decode_packed_24(data)
  in [PCM, 32]
    scale(data.unpack("l<*"), 31)
  in [IEEE_FLOAT, 32]
    data.unpack("e*")
  in [IEEE_FLOAT, 64]
    data.unpack("E*")
  else
    raise FormatError, "unsupported encoding: format #{format.inspect}, #{bits.inspect} bit"
  end
end

.downmix(samples, channels) ⇒ ::Array[::Float]

Parameters:

  • (::Array[::Float])
  • (::Integer, nil)

Returns:

  • (::Array[::Float])


97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/dsprb/wav.rb', line 97

def downmix(samples, channels)
  return samples if channels.nil? || channels <= 1

  Array.new(samples.length / channels) do |index|
    offset = index * channels
    total = 0.0
    channel = 0
    while channel < channels
      total += samples[offset + channel]
      channel += 1
    end

    total / channels
  end
end

.read(path) ⇒ Waveform

Parameters:

  • (::String)

Returns:



20
# File 'lib/dsprb/wav.rb', line 20

def read(path) = decode(File.binread(path))

.scale(integers, magnitude_bits) ⇒ ::Array[::Float]

Parameters:

  • (::Array[::Integer])
  • (::Integer)

Returns:

  • (::Array[::Float])


79
80
81
82
# File 'lib/dsprb/wav.rb', line 79

def scale(integers, magnitude_bits)
  full_scale = (1 << magnitude_bits).to_f
  integers.map { |value| value / full_scale }
end