Class: RGame::Core::Audio

Inherits:
Object
  • Object
show all
Defined in:
lib/rgame/core/audio.rb,
ext/rgame_core/ruby/audio_ext.c

Overview

The sound device: one per game, and the thing samples and songs are made from.

audio = RGame::Core::Audio.new
audio.volume = 0.8

It is not tied to a window. Sound outlives a resized or recreated window and has nothing to do with a GL context, so nothing here takes an app.

A machine with no sound hardware still gets a working Audio — it opens a null device and plays silently. That is deliberate: a game should run on a server, in a container, or on a laptop with the sound card disabled, and the alternative is a crash at startup for something nobody asked for. #backend says which one was chosen.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeObject



63
64
65
66
67
68
69
70
71
72
# File 'ext/rgame_core/ruby/audio_ext.c', line 63

static VALUE audio_initialize(VALUE self) {
    char error[256] = {0};
    rgame_audio *audio = rgame_audio_create(error, sizeof(error));
    if (!audio) {
        rb_raise(rb_eRuntimeError, "%s", error);
    }

    RTYPEDDATA_DATA(self) = audio;
    return self;
}

Class Method Details

.debug_live_soundsObject



107
108
109
110
# File 'ext/rgame_core/ruby/audio_ext.c', line 107

static VALUE audio_s_debug_live_sounds(VALUE klass) {
    (void)klass;
    return LONG2NUM(rgame_audio_live_sounds());
}

Instance Method Details

#backendObject



86
87
88
# File 'ext/rgame_core/ruby/audio_ext.c', line 86

static VALUE audio_backend(VALUE self) {
    return rb_str_new_cstr(rgame_audio_backend(audio_unwrap(self)));
}

#inspectObject



90
91
92
93
94
95
96
97
98
# File 'ext/rgame_core/ruby/audio_ext.c', line 90

static VALUE audio_inspect(VALUE self) {
    rgame_audio *audio;
    TypedData_Get_Struct(self, rgame_audio, &audio_data_type, audio);
    if (!audio) {
        return rb_sprintf("#<%" PRIsVALUE " (uninitialized)>", rb_obj_class(self));
    }
    return rb_sprintf("#<%" PRIsVALUE " %s>", rb_obj_class(self),
                      rgame_audio_backend(audio));
}

#play_music(id) ⇒ Object

Starts a registered song looping, and does nothing if it is already playing — so a scene that re-emits the same request every time it is entered never restarts the music mid-loop.



61
62
63
64
65
66
67
# File 'lib/rgame/core/audio.rb', line 61

def play_music(id)
  song = songs.fetch(id)
  return song if song.playing?

  @playing_song = song
  song.play(looping: true)
end

#play_sound(id) ⇒ Object

Plays a registered sample. Each call is another voice, layered over the ones already sounding.



54
55
56
# File 'lib/rgame/core/audio.rb', line 54

def play_sound(id)
  samples.fetch(id).play
end

#register_music(id, song) ⇒ Object



47
48
49
50
# File 'lib/rgame/core/audio.rb', line 47

def register_music(id, song)
  songs[id] = song
  self
end

#register_sound(id, sample) ⇒ Object

--- play-by-id -----------------------------------------------------

The same boundary the renderer's draw-by-id serves: game logic emits facts — "the ship was hit" — and names the sound, because a scene may not hold a Sample. Registration only, unlike the renderer: a sound id is whatever a game wants to call it, and there is no per-frame path to make resolving one worth caching.

audio.register_sound(:hit, app.assets.sound('example 09/hurt.ogg'))
audio.play_sound(:hit)


42
43
44
45
# File 'lib/rgame/core/audio.rb', line 42

def register_sound(id, sample)
  samples[id] = sample
  self
end

#sample(path) ⇒ Object

Loads a short sound to play over itself. The same thing as Sample.new(audio, path), and the form to prefer: it reads in the direction the objects depend, and it is the form a stand-in device can implement, which Sample.new is not.



26
# File 'lib/rgame/core/audio.rb', line 26

def sample(path) = Sample.new(self, path)

#song(path) ⇒ Object

Loads a long one to stream. See Song.



29
# File 'lib/rgame/core/audio.rb', line 29

def song(path) = Song.new(self, path)

#stop_musicObject

Stops the song this registry started.

Deliberately not "stop whatever is playing": the layer being replaced reached for a process-wide current_song, and there is no such global here by the decision that one-song-at-a-time is a game's policy rather than the engine's. A Song a game started by hand is its own to stop.



75
76
77
78
# File 'lib/rgame/core/audio.rb', line 75

def stop_music
  @playing_song&.stop
  @playing_song = nil
end

#volumeObject



74
75
76
# File 'ext/rgame_core/ruby/audio_ext.c', line 74

static VALUE audio_volume(VALUE self) {
    return DBL2NUM((double)rgame_audio_volume(audio_unwrap(self)));
}

#volume=(volume) ⇒ Object



78
79
80
81
# File 'ext/rgame_core/ruby/audio_ext.c', line 78

static VALUE audio_set_volume(VALUE self, VALUE volume) {
    rgame_audio_set_volume(audio_unwrap(self), (float)NUM2DBL(volume));
    return volume;
}