Class: Doom::Wad::SoundManager

Inherits:
Object
  • Object
show all
Defined in:
lib/doom/wad/sound.rb

Overview

Loads DOOM sound effects from WAD and converts to WAV for Gosu playback. DOOM sound format: 8-byte header (2 format, 2 sample_rate, 4 num_samples) followed by unsigned 8-bit PCM samples.

Instance Method Summary collapse

Constructor Details

#initialize(wad) ⇒ SoundManager

Returns a new instance of SoundManager.



12
13
14
15
16
17
18
19
20
21
# File 'lib/doom/wad/sound.rb', line 12

def initialize(wad)
  @wad = wad
  @cache = {} # name => Gosu::Sample
  @temp_dir = File.join(Dir.tmpdir, "doom_rb_sounds_#{Process.pid}")
  Dir.mkdir(@temp_dir) unless Dir.exist?(@temp_dir)

  # Sounds are decoded into a per-process temp dir; clean it up when the
  # process exits so repeated runs do not leak WAV files.
  at_exit { cleanup }
end

Instance Method Details

#[](name) ⇒ Object

Get or load a sound effect. Returns a Gosu::Sample or nil.



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
# File 'lib/doom/wad/sound.rb', line 24

def [](name)
  return @cache[name] if @cache.key?(name)

  lump_name = name.start_with?('DS') ? name : "DS#{name}"
  entry = @wad.find_lump(lump_name)
  return @cache[name] = nil unless entry

  data = @wad.read_lump_at(entry)
  return @cache[name] = nil if data.size < 8

  # Parse DOOM sound header
  _format = data[0, 2].unpack1('v')
  sample_rate = data[2, 2].unpack1('v')
  num_samples = data[4, 4].unpack1('V')

  # PCM data starts at offset 8, skip 16 padding bytes at start and end
  pcm_start = 8 + 16
  pcm_end = 8 + num_samples - 16
  pcm_data = data[pcm_start...pcm_end]
  return @cache[name] = nil unless pcm_data && pcm_data.size > 0

  # Convert to WAV file for Gosu
  wav_path = File.join(@temp_dir, "#{lump_name}.wav")
  write_wav(wav_path, pcm_data, sample_rate) unless File.exist?(wav_path)

  @cache[name] = Gosu::Sample.new(wav_path)
rescue StandardError => e
  # A bad or unplayable lump should silence that one sound, not crash the
  # game -- but say so rather than swallowing the error entirely.
  warn "SoundManager: could not load '#{name}': #{e.class}: #{e.message}"
  @cache[name] = nil
end

#cleanupObject



57
58
59
# File 'lib/doom/wad/sound.rb', line 57

def cleanup
  FileUtils.rm_rf(@temp_dir) if @temp_dir && Dir.exist?(@temp_dir)
end