From 4f81bab8efb0251f6363303eb3b8e3c71aa3b42f Mon Sep 17 00:00:00 2001 From: Gordon Pedersen Date: Thu, 23 Mar 2023 16:16:35 +1100 Subject: [PATCH] refactored sound and music --- .rubocop.yml | 3 + app/main.rb | 4 ++ app/menu.rb | 6 +- app/music.rb | 66 +++++++++++++++++ app/scenes/cube_tube.rb | 52 ++++++++------ app/scenes/gameplay.rb | 2 +- app/scenes/paused.rb | 6 +- app/scenes/settings.rb | 4 +- app/sound.rb | 83 ++++++++++++++-------- app/sound_instance.rb | 134 +++++++++++++++++++++++++++++++++++ app/tick.rb | 10 +-- music/_list.rb | 8 +++ {sounds => music}/music1.ogg | Bin {sounds => music}/music2.ogg | Bin sounds/_list.rb | 13 ++++ 15 files changed, 326 insertions(+), 65 deletions(-) create mode 100644 app/music.rb create mode 100644 app/sound_instance.rb create mode 100644 music/_list.rb rename {sounds => music}/music1.ogg (100%) rename {sounds => music}/music2.ogg (100%) create mode 100644 sounds/_list.rb diff --git a/.rubocop.yml b/.rubocop.yml index 9024484..3e7b266 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -9,6 +9,9 @@ Metrics/AbcSize: # 10-line methods are /way/ too short Metrics/MethodLength: Max: 100 +# As are 100 line classes +Metrics/ClassLength: + Max: 1000 # Yeah, I've got constants with mutables in them. Doesn't bother me. Style/MutableConstant: Enabled: false diff --git a/app/main.rb b/app/main.rb index 8c4cd64..1d2110d 100644 --- a/app/main.rb +++ b/app/main.rb @@ -5,15 +5,19 @@ require 'lib/coalesce.rb' # then, some basic classes required for lists of assets require 'app/sprite_instance.rb' +require 'app/sound_instance.rb' # then, asset lists require 'sprites/_list.rb' +require 'sounds/_list.rb' +require 'music/_list.rb' # then, utility classes require 'app/input.rb' require 'app/sprite.rb' require 'app/util.rb' require 'app/sound.rb' +require 'app/music.rb' require 'app/constants.rb' require 'app/menu.rb' diff --git a/app/menu.rb b/app/menu.rb index 46942d8..144cfdf 100644 --- a/app/menu.rb +++ b/app/menu.rb @@ -55,7 +55,7 @@ module Menu if args.inputs.mouse.up && args.inputs.mouse.inside_rect?(button_border) o = options.find { |o| o[:key] == l[:key] } - play_sfx(args, :menu) + Sound.play(args, :menu) o[:on_select].call(args) if o end end @@ -75,7 +75,7 @@ module Menu menu_state.hold_delay -= 1 if menu_state.hold_delay <= 0 - play_sfx(args, :menu) + Sound.play(args, :menu) index = menu_state.current_option_i if move == :up index -= 1 @@ -94,7 +94,7 @@ module Menu end if primary_down?(args.inputs) - play_sfx(args, :select) + Sound.play(args, :select) options[menu_state.current_option_i][:on_select].call(args) end end diff --git a/app/music.rb b/app/music.rb new file mode 100644 index 0000000..8a2c297 --- /dev/null +++ b/app/music.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# Module for managing and interacting with music. +module Music + class << self + def queue + @queue ||= {} + @queue + end + + def queue_up(key, channel = 0) + @queue[channel] ||= [] + @queue[channel].push(MUSIC.fetch(key)) + end + + def for(key) + MUSIC.fetch(key) + end + + def play(args, key, opts = { channel: 0 }) + MUSIC.fetch(key).play_music(args, opts) + end + + def stopped(args, channel = 0) + !args.audio.key?("MUSIC_CHANNEL_#{channel}") + end + + def paused(args, channel = 0) + args.audio["MUSIC_CHANNEL_#{channel}"].paused + end + + def stop(args, channel = 0) + args.audio.delete("MUSIC_CHANNEL_#{channel}") + end + + def pause(args, channel = 0) + args.audio["MUSIC_CHANNEL_#{channel}"].paused = true + end + + def resume(args, channel = 0) + args.audio["MUSIC_CHANNEL_#{channel}"].paused = false + end + + def set_volume(args, volume, channel = 0) + args.audio["MUSIC_CHANNEL_#{channel}"].gain = volume + end + + def tick(args) + queue.each do |channel, value| + unless value.empty? + if args.audio["MUSIC_CHANNEL_#{channel}"] + puts "THERE'S MUSIC CURRENTLY PLAYING #{args.audio["MUSIC_CHANNEL_#{channel}"]}" + if args.audio["MUSIC_CHANNEL_#{channel}"].looping + puts "CANCEL THE LOOP ON CURRENT MUSIC" + args.audio["MUSIC_CHANNEL_#{channel}"].looping = false + end + else + puts "PLAY THAT FUNKY MUSIC! #{value}" + value.shift.play_music(args, { channel: channel, gain: args.state.setting.music ? 0.8 : 0.0 }) + puts "Now, the queue is #{value}" + end + end + end + end + end +end diff --git a/app/scenes/cube_tube.rb b/app/scenes/cube_tube.rb index af6e359..a385249 100644 --- a/app/scenes/cube_tube.rb +++ b/app/scenes/cube_tube.rb @@ -57,7 +57,7 @@ class CubeTubeGame @lines_to_clear = [] @line_clear_timer = 0 - @music_queue = [] + @current_music = :music1 reset_game end @@ -87,7 +87,8 @@ class CubeTubeGame end end - @music_queue = [:music1, :music2] + @current_music = :music1 + Music.play(@args, @current_music) end def render_grid_border x, y, w, h, color @@ -257,6 +258,16 @@ class CubeTubeGame end end + def change_music + if @current_music == :music1 + @current_music = :music2 + else + @current_music = :music1 + end + queue = Music.queue + Music.queue_up(@current_music) + end + def plant_current_piece rows_to_check = [] @@ -288,21 +299,21 @@ class CubeTubeGame @lines += 1 @line_clear_timer = 70 if (@lines%10).floor == 0 - @level += 1 - @args.audio[:music].looping = false + @level += 1 + change_music end end end select_next_piece if @lines_to_clear.empty? - play_sfx(@args, :drop) + Sound.play(@args, :drop) if current_piece_colliding @gameover = true - stop_music(@args) + Music.stop(@args) end else - play_sfx(@args, :clear) + Sound.play(@args, :clear) @current_speed = get_speed end @@ -330,7 +341,7 @@ class CubeTubeGame end def rotate_current_piece_left - play_sfx(@args, :rotate) + Sound.play(@args, :rotate) @current_piece = @current_piece.transpose.map(&:reverse) if(@current_piece_x + @current_piece.length) >= @grid_w @current_piece_x = @grid_w - @current_piece.length @@ -338,7 +349,7 @@ class CubeTubeGame end def rotate_current_piece_right - play_sfx(@args, :rotate) + Sound.play(@args, :rotate) @current_piece = @current_piece.transpose.map(&:reverse) @current_piece = @current_piece.transpose.map(&:reverse) @current_piece = @current_piece.transpose.map(&:reverse) @@ -379,13 +390,8 @@ class CubeTubeGame return end - if @args.audio[:music] - resume_music(@args) if @args.audio[:music].paused - @args.audio[:music].pitch = 1 + (@level * 0.125) - else - music = @music_queue.shift - @music_queue.push music - play_music(@args, music) + unless Music.stopped(@args) + Music.resume(@args) if Music.paused(@args) end if @line_clear_timer.positive? @@ -401,7 +407,7 @@ class CubeTubeGame @grid[i][0] = 0 end end - play_sfx(@args, :drop) + Sound.play(@args, :drop) @lines_to_clear = [] end return @@ -411,26 +417,26 @@ class CubeTubeGame if @current_piece_x > 0 @current_piece_x -= 1 if current_piece_colliding - play_sfx(@args, :move_deny) + Sound.play(@args, :move_deny) @current_piece_x += 1 else - play_sfx(@args, :move) + Sound.play(@args, :move) end else - play_sfx(@args, :move_deny) + Sound.play(@args, :move_deny) end end if k.key_down.up || k.key_down.w || c.key_down.up if (@current_piece_x + @current_piece.length) < @grid_w @current_piece_x += 1 if current_piece_colliding - play_sfx(@args, :move_deny) + Sound.play(@args, :move_deny) @current_piece_x -= 1 else - play_sfx(@args, :move) + Sound.play(@args, :move) end else - play_sfx(@args, :move_deny) + Sound.play(@args, :move_deny) end end if k.key_down.left || k.key_held.left || k.key_down.a || k.key_held.a || c.key_down.left || c.key_held.left diff --git a/app/scenes/gameplay.rb b/app/scenes/gameplay.rb index 5eea3a7..626050a 100644 --- a/app/scenes/gameplay.rb +++ b/app/scenes/gameplay.rb @@ -27,7 +27,7 @@ module Scene end def pause(args) - play_sfx(args, :select) + Sound.play(args, :select) return Scene.push(args, :paused, reset: true) end diff --git a/app/scenes/paused.rb b/app/scenes/paused.rb index 814f861..a61d646 100644 --- a/app/scenes/paused.rb +++ b/app/scenes/paused.rb @@ -28,12 +28,10 @@ module Scene Menu.tick(args, :paused, options) - if args.audio[:music] && !args.audio[:music].paused - pause_music(args) - end + Music.pause(args) unless Music.stopped(args) if secondary_down?(args.inputs) - play_sfx(args, :select) + Sound.play(args, :select) options.find { |o| o[:key] == :resume }[:on_select].call(args) end diff --git a/app/scenes/settings.rb b/app/scenes/settings.rb index 8a13c44..6351ad3 100644 --- a/app/scenes/settings.rb +++ b/app/scenes/settings.rb @@ -23,7 +23,7 @@ module Scene on_select: -> (args) do GameSetting.save_after(args) do |args| args.state.setting.music = !args.state.setting.music - set_music_vol(args) + Music.set_volume(args, args.state.setting.music ? 0.8 : 0.0) end end }, @@ -50,7 +50,7 @@ module Scene Menu.tick(args, :settings, options) if secondary_down?(args.inputs) - play_sfx(args, :select) + Sound.play(args, :select) options.find { |o| o[:key] == :back }[:on_select].call(args) end diff --git a/app/sound.rb b/app/sound.rb index f565f1b..6794a35 100644 --- a/app/sound.rb +++ b/app/sound.rb @@ -1,35 +1,62 @@ -# play a sound effect. the file in sounds/ must match the key name. ex: -# play_sfx(args, :select) -def play_sfx(args, key) - if args.state.setting.sfx - args.outputs.sounds << "sounds/#{key}.wav" +# frozen_string_literal: true + +# Module for managing and interacting with sounds. +module Sound + class << self + def for(key) + SOUNDS.fetch(key) + end + + def play(args, key, opts = {}) + SOUNDS.fetch(key).play(args, opts) + end + + def stop(args, key) + SOUNDS.fetch(key).stop(args) + end + + def pause(args, key) + SOUNDS.fetch(key).pause(args) + end + + def resume(args, key) + SOUNDS.fetch(key).resume(args) + end end end -# play the specified music track, the key must correspond to the -# `sounds/#{key}.ogg` file naming scheme. -def play_music(args, key) - args.audio[:music] = { input: "sounds/#{key}.ogg", looping: true, } - set_music_vol(args) -end +# # play a sound effect. the file in sounds/ must match the key name. ex: +# # play_sfx(args, :select) +# def play_sfx(args, key) +# if args.state.setting.sfx +# args.outputs.sounds << "sounds/#{key}.wav" +# end +# end -# sets the music vol based on whether or not music is enabled or disabled -def set_music_vol(args) - vol = args.state.setting.music ? 0.8 : 0.0 - args.audio[:music]&.gain = vol -end +# # play the specified music track, the key must correspond to the +# # `sounds/#{key}.ogg` file naming scheme. +# def play_music(args, key) +# args.audio[:music] = { input: "sounds/#{key}.ogg", looping: true, } +# set_music_vol(args) +# end -# pause the currently playing music track -def pause_music(args) - args.audio[:music].paused = true -end +# # sets the music vol based on whether or not music is enabled or disabled +# def set_music_vol(args) +# vol = args.state.setting.music ? 0.8 : 0.0 +# args.audio[:music]&.gain = vol +# end -# pause the current music track -def resume_music(args) - args.audio[:music].paused = false -end +# # pause the currently playing music track +# def pause_music(args) +# args.audio[:music].paused = true +# end -# stop the currently playing music track -def stop_music(args) - args.audio.delete(:music) -end \ No newline at end of file +# # pause the current music track +# def resume_music(args) +# args.audio[:music].paused = false +# end + +# # stop the currently playing music track +# def stop_music(args) +# args.audio.delete(:music) +# end diff --git a/app/sound_instance.rb b/app/sound_instance.rb new file mode 100644 index 0000000..480fa69 --- /dev/null +++ b/app/sound_instance.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +# class to represent sound files +class SoundInstance + def initialize(opts) + @input = opts[:input]._? opts[:path] + @key = opts[:key]._? @input + @x = opts[:x]._? 0.0 + @y = opts[:y]._? 0.0 + @z = opts[:z]._? 0.0 + @gain = opts[:gain]._? opts[:volume]._? 1.0 + @pitch = opts[:pitch]._? 1.0 + @paused = opts[:paused]._? false + @looping = opts[:looping]._? false + end + + attr_reader :key, :input, :x, :y, :z, :gain, :pitch, :paused, :looping + + def input=(input) + args.audio[@key].input = input if args.audio[@key] + @input = input + end + + def x=(xval) + args.audio[@key].x = xval if args.audio[@key] + @x = xval + end + + def y=(yval) + args.audio[@key].y = yval if args.audio[@key] + @y = yval + end + + def z=(zval) + args.audio[@key].z = zval if args.audio[@key] + @z = zval + end + + def xyz(xval, yval, zval) + if args.audio[@key] + args.audio[@key].x = xval + args.audio[@key].y = yval + args.audio[@key].z = zval + end + @x = xval + @y = yval + @z = zval + end + + def xyz=(xyz=[]) + xyz(xyz[0], xyz[1], xyz[2]) + end + + def gain=(gain) + args.audio[@key].gain = gain if args.audio[@key] + @gain = gain + end + + def pitch=(pitch) + args.audio[@key].pitch = pitch if args.audio[@key] + @pitch = pitch + end + + def paused=(paused) + args.audio[@key].paused = paused if args.audio[@key] + @paused = paused + end + + def looping=(looping) + args.audio[@key].looping = looping if args.audio[@key] + @looping = looping + end + + def playtime + args.audio[@key].playtime._?(0.0) + end + + def playtime=(playtime) + args.audio[@key].playtime = playtime + end + + # convenience getter for 'path' (@input) + def path + @input + end + + # convenience setter for 'path' (@input) + def path=(path) + input(path) + end + + # convenience getter for 'volume' (@gain) + def volume + @gain + end + + # convenience setter for 'volume' (@gain) + def volume=(volume) + gain(volume) + end + + def play(args, opts = {}) + obj = { + input: opts[:input]._?(opts[:path]._?(@input)), + x: opts[:x]._?(@x), + y: opts[:y]._?(@y), + z: opts[:z]._?(@z), + gain: opts[:gain]._?(opts[:volume]._?(@gain)), + pitch: opts[:pitch]._?(@pitch), + paused: opts[:paused]._?(@paused), + looping: opts[:looping]._?(@looping), + playtime: opts[:playtime]._?(0.0) + } + args.audio[@key] = obj + end + + def play_music(args, opts = { channel: 0 }) + @looping = true + @key = "MUSIC_CHANNEL_#{opts[:channel]}" + play(args, opts) + end + + def stop(args) + args.audio.delete(@key) + end + + def pause(args) + args.audio[@key].paused = true + end + + def resume(args) + args.audio[@key].paused = false + end +end diff --git a/app/tick.rb b/app/tick.rb index 80ef8b8..ac7f880 100644 --- a/app/tick.rb +++ b/app/tick.rb @@ -16,6 +16,8 @@ def tick(args) track_swipe(args) if mobile? Scene.send("tick_#{args.state.scene}", args) + + Music.tick(args) debug_tick(args) rescue FinishTick @@ -36,23 +38,23 @@ def debug_tick(args) if args.inputs.keyboard.key_down.zero - play_sfx(args, :select) + Sound.play(args, :select) args.state.render_debug_details = !args.state.render_debug_details end if args.inputs.keyboard.key_down.i - play_sfx(args, :select) + Sound.play(args, :select) Sprite.reset_all(args) args.gtk.notify!("Sprites reloaded") end if args.inputs.keyboard.key_down.r - play_sfx(args, :select) + Sound.play(args, :select) $gtk.reset end if args.inputs.keyboard.key_down.m - play_sfx(args, :select) + Sound.play(args, :select) args.state.simulate_mobile = !args.state.simulate_mobile msg = if args.state.simulate_mobile "Mobile simulation on" diff --git a/music/_list.rb b/music/_list.rb new file mode 100644 index 0000000..ec70bde --- /dev/null +++ b/music/_list.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Music + MUSIC = { + music1: SoundInstance.new({ path: 'music/music1.ogg', looping: true }), + music2: SoundInstance.new({ path: 'music/music2.ogg', looping: true }) + } +end diff --git a/sounds/music1.ogg b/music/music1.ogg similarity index 100% rename from sounds/music1.ogg rename to music/music1.ogg diff --git a/sounds/music2.ogg b/music/music2.ogg similarity index 100% rename from sounds/music2.ogg rename to music/music2.ogg diff --git a/sounds/_list.rb b/sounds/_list.rb new file mode 100644 index 0000000..eb83cdd --- /dev/null +++ b/sounds/_list.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Sound + SOUNDS = { + menu: SoundInstance.new({ path: 'sounds/menu.wav' }), + select: SoundInstance.new({ path: 'sounds/select.wav' }), + clear: SoundInstance.new({ path: 'sounds/clear.wav' }), + drop: SoundInstance.new({ path: 'sounds/drop.wav' }), + move: SoundInstance.new({ path: 'sounds/move.wav' }), + move_deny: SoundInstance.new({ path: 'sounds/move_deny.wav' }), + rotate: SoundInstance.new({ path: 'sounds/rotate.wav' }) + } +end \ No newline at end of file