赞 | 0 |
VIP | 2 |
好人卡 | 27 |
积分 | 1 |
经验 | 26327 |
最后登录 | 2019-10-13 |
在线时间 | 953 小时 |
Lv1.梦旅人
- 梦石
- 0
- 星屑
- 110
- 在线时间
- 953 小时
- 注册时间
- 2007-4-25
- 帖子
- 805
|
加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
本帖最后由 苏小脉 于 2012-12-31 05:40 编辑
Scorched End Audio Library
原本是給 RGE2 做的音頻渲染庫,但由於 RGE2 目前基本沒有活躍開發,故而先獨立出來給各位 RM 用戶試用。這個庫可以給 3D 遊戲用,但也兼容 2D。
Seal 是 Scorched End Audio Library 的簡稱,是從「焦尾音頻庫」翻譯過來的。相傳焦尾是東漢蔡邕所製名琴,與號鐘、繞梁、綠綺並稱為四大名琴。Seal 的底層是 OpenAL(相當於音頻界的 OpenGL),在其基礎上添加了對 Ogg Vorbis、MPEG1、2、3 和簡單 WAVE 格式的支持,同時也抽象了流式音頻的播放,在未來還會根據用戶的需求提供各種便捷的輔助方法、抽象層等。Seal 在原本的 OpenAL 的 C API 基礎上建立了 Ruby 綁定。
Seal 與默認的音頻模塊
默認的音頻模塊提供的是純粹的音頻重放,外加調節音量和音調的能力,除此之外並無其他特效。Seal 目前通過 OpenAL 支持對以下自然界的聲音現象的模擬:
聲音的增長和衰減
通俗的說就是聲源和觀察者之間的距離與觀察者聽到的聲音強度成正比。一個發聲物體離得越遠,觀察者附近的壓強震動就越小。比如牆上一把火炬,靠近它時燃燒的聲音變大,遠離它時聲音變小。RPG的場景中隨處都可以有這方面的應用——玩家走近市井,漸聞喧嘩;NPC 漸行漸遠,腳步聲漸悄。
多普勒效應
聲源和觀察者保持相對運動,當觀察者接收到聲波時,聲波的頻率與最初聲源發出的頻率和波長產生了差別的現象。比如當一輛救護車朝朝觀察者行駛而來,觀察者聽到的鳴笛聲會變得很急促,這是由於頻率變高、波長變短;當救護車駛過並遠離觀察者時,觀察者聽到的鳴笛聲則會變得很低沉,這是由於頻率變低、波長變長。遊戲中,法師的一個火球飛來,擦身而過的馬車都應該產生多普勒效應。
聲音的空間定位
人類通常可以識別出聲源在空間中的位置。前面提到的聲音的增長和衰減實際上是空間定位的一部分,因為通過聲音強度通常就能反過來推出發聲物體的距離,但僅限於距離,不含方向。玩家理應識別出兩個正在左手邊決鬥的NPC所發出的刀劍交擊聲是從左方傳來,而不是從右方。這個效果的具體表現手法是將音頻輸出到在不同位置的音頻輸出設備終端上。一對音箱或是當代筆記本電腦內置的揚聲器都有一左一右兩個輸出口,如此就把聲音的渲染從一個點提升到了一條線——聲音可以來自左邊,中間或者右邊。更多的輸出聲道和終端的搭配可以產生更真實的環繞音,把線提升到平面,平面提升到空間。
迴響
在特殊的環境中,一部分聲波被物體反射並逆向運動,與原有聲波交融所形成的效果(在特定場合下,反射回來的波甚至會再次被反射)。同樣的音效在不同的環境中會產生不同屬性的迴響——小屋內,管道中,森林中,空間站中,冰霜宮殿中……總之,有了對迴響效果的渲染,聲音的真實度便能大幅提升,尤其是在從一個環境切換到另一個環境中的時候那種聲音的驟變。
其它
除特效外,Seal 目前還具備 RM 所沒有的諸如播放時調節音量、音調,暫停播放、獲取音頻屬性等能力,當然仍然還有很多功能待實現,見「待完成」一欄。
編程模型
一個音頻數據文件的通過 Buffer 類或者 Stream 類對象加載——小巧的音頻數據用 Buffer 加載,大體積的音頻數據用 Stream 流式傳播。Buffer 或 Stream 的實例會被關聯上一個抽象地代表聲源的 Source 類對象,並通過 Source 的各種接口對音頻就行各種實時的操控、加工和過濾。全局可以有 n 個 Source 實例(遊戲中各種發聲的物體通常都是不同的 Source 實例),但只有一個 Listener 類的單一實例,表示觀察者(通常是場景中移動的玩家)。Source 的實例和觀察者都有位置和速度屬性,而實時更新這些屬性就能自動改變聲音在空間定位和減淡上的渲染。此間涉及到的大量數學計算都是底層引擎負責的,它最終會渲染出在某一時刻觀察者所在的地點聽到的把所有 Source 的聲音混合後的聲音。
Seal 的模型和 RGSS 的圖形編程模型有很大的可比性,彼此的對象之間有大致的對應關係。
Source 類似於 RGSS 的 Sprite,一個表示音頻,一個表示圖形,前者的不同實例最終集成於混音器的輸出,後者的不同實例最終集成到整個遊戲的圖形場景,兩者的存在都是為了方便對原有數據的實時加工渲染。
Buffer 類似 Bitmap,前者是音頻數據的容器,後者是圖像數據的容器。通常用於加載小橋的音頻文件,比如 RM 的各種 BGS、ME 和 SE。
Stream 從抽象層來看也基本等同於 Buffer,但從程序效率角度來看的話則大有不同——Buffer是將音頻一次性讀入內存,而 Stream 是流式數據傳輸。音頻數據通常比圖像數據大,一首5分鐘的MP3解壓之後會佔據50MB的空間,如果一次性讀入主內存那將會是很大的開銷。由於音頻播放的特性是只需要當前正在播放的這一部分數據,所以流式每次只讀取一小塊數據並傳輸給音頻輸出設備,而之前的數據佔據的內存則可以直接被釋放或重用。一個流隨時保持在內存中的數據只是原有音頻數據的很小一部分,就不會導致各種內存效率的問題(Cache miss,碎片,大量的頁缺失、內存顛簸)。通常需要用到流式的是背景音樂,比如 RM 的各種 BGM。
迴響是 OpenAL 軟件實現的的特效擴展系列之一。在 Seal 中,迴響是通過控制另外兩種對象實現的:EffectSlot 和 Reverb(Reverb 是迴響的英文 Reverberation 的簡寫)。Reverb 的實例有各種關於迴響的參數設置,表示一種特定環境的迴響。Source 的實例可以選擇應用某種迴響,這時就需要使用 EffectSlot。EffectSlot 可以看作是流水線上作業的一個過濾器,負責過濾數字信號,將輸入的音頻應用上迴響的效果。EffectSlot 可以插入不同類型的效果,但由於目前 Seal 只實現了迴響的效果,所以目前僅限 Reverb。從設計模式角度來看,EffectSlot 就是適配器,統一了 Source 和特效對象之間的關聯接口。
簡單流程
初始化 Seal:
Seal.startup include Seal # Seal 模塊是 Seal 的頂層命名空間,必要的時候可以 include 一下避免重複修飾 Seal::*
Seal.startup
include Seal # Seal 模塊是 Seal 的頂層命名空間,必要的時候可以 include 一下避免重複修飾 Seal::*
利用 Source 對象表示聲源,並關聯上一個 Buffer 對象:
source = Source.new source.buffer = Buffer.new("audio.ogg")
source = Source.new
source.buffer = Buffer.new("audio.ogg")
改變 source 的位置:
source.position = 3, 2, -4 # 歐幾里德 3D 空間座標體系,2D 遊戲只要永遠保持第三個座標為 0 即可
source.position = 3, 2, -4 # 歐幾里德 3D 空間座標體系,2D 遊戲只要永遠保持第三個座標為 0 即可
改變 listener (觀察者,Seal::Listener 的單例)的位置:
Seal.listener.position = -1, -1, 0
Seal.listener.position = -1, -1, 0
播放 source:
需要播放大體積的音頻資源時則採用 Stream:
source.stream = Stream.open("background_music.ogg")
source.stream = Stream.open("background_music.ogg")
從一個 buffer 切換到 stream 及其相反:
source.buffer = ... # ... source.buffer = nil source.stream = ... # ... source.stream = nil source.buffer = ...
source.buffer = ...
# ...
source.buffer = nil
source.stream = ...
# ...
source.stream = nil
source.buffer = ...
應用迴響效果:
# 分配一個 EffectSlot 對象,並關聯上一個 Reverb 對象 slot = EffectSlot.new(Reverb.new(Reverb::Preset::FOREST)) # 開始進行迴響效果的過濾 source.feed(slot, 0)
# 分配一個 EffectSlot 對象,並關聯上一個 Reverb 對象
slot = EffectSlot.new(Reverb.new(Reverb::Preset::FOREST))
# 開始進行迴響效果的過濾
source.feed(slot, 0)
其中 Reverb::Preset 包含一系列預定義的迴響效果,如城堡、工廠、戶外、城市等。詳見:
http://zhang.su/seal/Seal/Reverb/Preset.html
最終化 Seal:
詳細文檔
在以下兩個地方都可以查閱:
http://zhang.su/seal/
http://rubydoc.info/gems/seal
rubydoc.info 上那個可以看以前版本的。暫時還沒有精力寫中文文檔 TvT,先湊合看著英文吧……
這裡有一些命令行的 demo 程序:
https://github.com/zhangsu/seal/tree/master/demo
注意事項
- 只有單聲道的音頻數據文件在被 Seal 加載後才有空間定位、聲音減淡的效果。OpenAL 默認多聲道的音頻在設計的時候就考慮了立體音,所以不會強制改變它切換聲道的效果。多聲道音頻需要被事先轉換為單聲道音頻文件才能利用 Seal 的空間定位和減淡。
- Seal 並不支持解碼所有 WAVE 的子格式。Seal 只是出於測試的目的提供了對無壓縮的小於等於 16bps 的 WAVE 的支持,但實際上微軟官方的規格中有很多 WAVE 的子格式(包括壓縮失真格式)。建議大家統一使用 Ogg Vorbis 格式,開放自由。
- 當 Source 被 GC 回收時,底層的對象被釋放,聲音的播放自然也會終止。管理好 Source 的作用域和生命週期,慎用局部變量。使用局部變量極容易出現聲音還沒有播放完就突然中斷的情況,原因是被 GC 回收了對象。
- 用 VA 測試時發現有很小的幾率在調用 Seal.startup 時出現打開設備失敗的異常,但重試又好使了,原因不明,而且只是在 RM 環境中出現。目前的救急方案是:
begin Seal.startup rescue Seal::SealError retry end
begin
Seal.startup
rescue Seal::SealError
retry
end
或者限制重試次數:
3.times do begin Seal.startup break rescue Seal::SealError next end end
3.times do
begin
Seal.startup
break
rescue Seal::SealError
next
end
end
下載
首先必需的是 OpenAL 庫,這裡提供 MSVC 10.0 預編譯的 32 位二進制,你也可以自己編譯,只要是這裡的 OpenAL 軟件實現。
OpenAL32.zip
(220.15 KB, 下载次数: 163)
RM 程序
可以使用提供的 Win32API 綁定以及 seal 的 C 庫。庫文件(OpenAL32.dll,seal.dll)默認需要放在工程根目錄,如果需要更改,可以在 class SealAPI 下面添加一行:Win32API 綁定:
module Seal class SealAPI < Win32API STRCPY_S = Win32API.new('msvcrt', 'strcpy_s', 'pll', 'i') def initialize(func, arg_types, return_type = 'i', *args) @return_string = return_type == 'p' library = File.join(defined?(LIB_DIR) ? LIB_DIR : '.', 'seal') super(library, "seal_#{func}", arg_types, return_type, *args) end def [](*args) result = call(*args) if @return_string and result.is_a? Integer # String pointer is returned to Ruby as an integer even though we # specified 'p' as the return value - possibly a bug in Ruby 1.9.3's # Win32API implementation. Work around it. message_buffer = ' ' * 128 STRCPY_S.call(message_buffer, message_buffer.size, result) return message_buffer.strip end result end unless method_defined? :[] end module Helper GET_ERR_MSG = SealAPI.new('get_err_msg', 'i', 'p') class << self def define_enum(mod, constants, start_value = 0) constants.each_with_index do |constant, index| mod.const_set(constant, start_value + index) end end # Returns a destructor for a native Seal object. This is most likely # called in the initializer method, but we cannot define the proc handler # there because that will capture the binding of the implicit `self` (due # to the nature of closure), which makes the object that `self` refers to # unrecyclable. def free(obj, destroyer) lambda { |object_id| destroyer[obj] } end end private def check_error(error_code) raise SealError, GET_ERR_MSG[error_code], caller.shift if error_code != 0 nil end def input_audio(media, filename, format, inputter) check_error(inputter[media, filename, format]) end def set_obj_int(obj, int, setter) check_error(setter[obj, int]) int end def get_obj_int(obj, getter) buffer = ' ' check_error(getter[obj, buffer]) buffer.unpack('i')[0] end def set_obj_char(obj, bool, setter) set_obj_int(obj, bool ? 1 : 0, setter) end def get_obj_char(obj, getter) buffer = ' ' check_error(getter[obj, buffer]) buffer.unpack('c')[0] != 0 end # Win32API does not support float argument type, need to pass as integer. def set_obj_float(obj, float, setter) check_error(setter[obj, [float].pack('f').unpack('i')[0]]) float end def get_obj_float(obj, getter) float_buffer = ' ' check_error(getter[obj, float_buffer]) float_buffer.unpack('f')[0] end end VERSION = SealAPI.new('get_version', 'v', 'p')[] class << self include Helper STARTUP = SealAPI.new('startup', 'p') CLEANUP = SealAPI.new('cleanup', 'v', 'v') GET_PER_SRC_EFFECT_LIMIT = SealAPI.new('get_per_src_effect_limit', 'v') def startup(device = nil) check_error(STARTUP[device ? device : 0]) end def cleanup CLEANUP[] end def per_source_effect_limit GET_PER_SRC_EFFECT_LIMIT[] end end module Format Helper.define_enum(self, [ :UNKNOWN, :WAV, :OV, :MPG ]) end class SealError < Exception end class Listener include Helper SET_GAIN = SealAPI.new('set_listener_gain', 'i') GET_GAIN = SealAPI.new('get_listener_gain', 'p') SET_POS = SealAPI.new('set_listener_pos', 'iii') GET_POS = SealAPI.new('get_listener_pos', 'ppp') SET_VEL = SealAPI.new('set_listener_vel', 'iii') GET_VEL = SealAPI.new('get_listener_vel', 'ppp') SET_ORIEN = SealAPI.new('set_listener_orien', 'p') GET_ORIEN = SealAPI.new('get_listener_orien', 'p') def gain=(gain) check_error(SET_GAIN[[gain].pack('f').unpack('i')[0]]) gain end def gain gain_buffer = ' ' check_error(GET_GAIN[gain_buffer]) return gain_buffer.unpack('f')[0] end def position=(position) set_3float(position, SET_POS) end def position get_3float(GET_POS) end def velocity=(velocity) set_3float(velocity, SET_VEL) end def velocity get_3float(GET_VEL) end def orientation=(orientation) check_error(SET_ORIEN[orientation.flatten.pack('f*')]) end def orientation orientation_buffer = ' ' * 6 check_error(GET_ORIEN[orientation_buffer]) orientation = orientation_buffer.unpack('f*') [orientation[0..2], orientation[3..5]] end private def set_3float(float_tuple, setter) integer_tuple = float_tuple.pack('f*').unpack('i*') check_error(setter[*integer_tuple]) return float_tuple end def get_3float(getter) float_tuple_buffers = Array.new(3) { ' ' } check_error(getter[*float_tuple_buffers]) return float_tuple_buffers.join.unpack('f*') end end LISTENER = Listener.new def self.listener LISTENER end class << Listener undef allocate undef new end class Buffer include Helper INIT = SealAPI.new('init_buf', 'p') DESTROY = SealAPI.new('destroy_buf', 'p') LOAD = SealAPI.new('load2buf', 'ppi') GET_SIZE = SealAPI.new('get_buf_size', 'pp') GET_FREQ = SealAPI.new('get_buf_freq', 'pp') GET_BPS = SealAPI.new('get_buf_bps', 'pp') GET_NCHANNELS = SealAPI.new('get_buf_nchannels', 'pp') def initialize(filename, format = Format::UNKNOWN) @buffer = ' ' check_error(INIT[@buffer]) input_audio(@buffer, filename, format, LOAD) ObjectSpace.define_finalizer(self, Helper.free(@buffer, DESTROY)) self end def load(filename, format = Format::UNKNOWN) input_audio(@buffer, filename, format, LOAD) self end def size get_obj_int(@buffer, GET_SIZE) end def frequency get_obj_int(@buffer, GET_FREQ) end def bit_depth get_obj_int(@buffer, GET_BPS) end def channel_count get_obj_int(@buffer, GET_NCHANNELS) end end class Stream include Helper OPEN = SealAPI.new('open_stream', 'ppi') CLOSE = SealAPI.new('close_stream', 'p') REWIND = SealAPI.new('rewind_stream', 'p') class << self alias open new end def initialize(filename, format = Format::UNKNOWN) [url=home.php?mod=space&uid=33438]@stream[/url] = ' ' * 5 input_audio(@stream, filename, format, OPEN) ObjectSpace.define_finalizer(self, Helper.free(@stream, CLOSE)) self end def frequency field(4) end def bit_depth field(2) end def channel_count field(3) end def rewind check_error(REWIND[@stream]) end def close check_error(CLOSE[@stream]) end private def field(index) @stream[index * 4, 4].unpack('i')[0] end end class Reverb include Helper INIT = SealAPI.new('init_rvb', 'p') DESTROY = SealAPI.new('destroy_rvb', 'p') LOAD = SealAPI.new('load_rvb', 'pi') SET_DENSITY = SealAPI.new('set_rvb_density', 'pi') SET_DIFFUSION = SealAPI.new('set_rvb_diffusion', 'pi') SET_GAIN = SealAPI.new('set_rvb_gain', 'pi') SET_HFGAIN = SealAPI.new('set_rvb_hfgain', 'pi') SET_DECAY_TIME = SealAPI.new('set_rvb_decay_time', 'pi') SET_HFDECAY_RATIO = SealAPI.new('set_rvb_hfdecay_ratio', 'pi') SET_REFLECTIONS_GAIN = SealAPI.new('set_rvb_reflections_gain', 'pi') SET_REFLECTIONS_DELAY = SealAPI.new('set_rvb_reflections_delay', 'pi') SET_LATE_GAIN = SealAPI.new('set_rvb_late_gain', 'pi') SET_LATE_DELAY = SealAPI.new('set_rvb_late_delay', 'pi') SET_AIR_ABSORBTION_HFGAIN = SealAPI.new('set_rvb_air_absorbtion_hfgain', 'pi') SET_ROOM_ROLLOFF_FACTOR = SealAPI.new('set_rvb_room_rolloff_factor', 'pi') SET_HFDECAY_LIMITED = SealAPI.new('set_rvb_hfdecay_limited', 'pi') GET_DENSITY = SealAPI.new('get_rvb_density', 'pp') GET_DIFFUSION = SealAPI.new('get_rvb_diffusion', 'pp') GET_GAIN = SealAPI.new('get_rvb_gain', 'pp') GET_HFGAIN = SealAPI.new('get_rvb_hfgain', 'pp') GET_DECAY_TIME = SealAPI.new('get_rvb_decay_time', 'pp') GET_HFDECAY_RATIO = SealAPI.new('get_rvb_hfdecay_ratio', 'pp') GET_REFLECTIONS_GAIN = SealAPI.new('get_rvb_reflections_gain', 'pp') GET_REFLECTIONS_DELAY = SealAPI.new('get_rvb_reflections_delay', 'pp') GET_LATE_GAIN = SealAPI.new('get_rvb_late_gain', 'pp') GET_LATE_DELAY = SealAPI.new('get_rvb_late_delay', 'pp') GET_AIR_ABSORBTION_HFGAIN = SealAPI.new('get_rvb_air_absorbtion_hfgain', 'pp') GET_ROOM_ROLLOFF_FACTOR = SealAPI.new('get_rvb_room_rolloff_factor', 'pp') IS_HFDECAY_LIMITED = SealAPI.new('is_rvb_hfdecay_limited', 'pp') def initialize(preset = nil) @effect = ' ' check_error(INIT[@effect]) load(preset) if preset ObjectSpace.define_finalizer(self, Helper.free(@effect, DESTROY)) self end def load(preset) check_error(LOAD[@effect, preset]) end def density=(density) set_obj_float(@effect, density, SET_DENSITY) end def diffusion=(diffusion) set_obj_float(@effect, diffusion, SET_DIFFUSION) end def gain=(gain) set_obj_float(@effect, gain, SET_GAIN) end def hfgain=(hfgain) set_obj_float(@effect, hfgain, SET_HFGAIN) end def decay_time=(decay_time) set_obj_float(@effect, decay_time, SET_DECAY_TIME) end def hfdecay_ratio=(hfdecay_ratio) set_obj_float(@effect, hfdecay_ratio, SET_HFDECAY_RATIO) end def reflections_gain=(reflections_gain) set_obj_float(@effect, reflections_gain, SET_REFLECTIONS_GAIN) end def reflections_delay=(reflections_delay) set_obj_float(@effect, reflections_delay, SET_REFLECTIONS_DELAY) end def late_gain=(late_gain) set_obj_float(@effect, late_gain, SET_LATE_GAIN) end def late_delay=(late_delay) set_obj_float(@effect, late_delay, SET_LATE_DELAY) end def air_absorbtion_hfgain=(air_absorbtion_hfgain) set_obj_float(@effect, air_absorbtion_hfgain, SET_AIR_ABSORBTION_HFGAIN) end def room_rolloff_factor=(room_rolloff_factor) set_obj_float(@effect, room_rolloff_factor, SET_ROOM_ROLLOFF_FACTOR) end def hfdecay_limited=(hfdecay_limited) set_obj_char(@effect, hfdecay_limited, SET_HFDECAY_LIMITED) end def density get_obj_float(@effect, GET_DENSITY) end def diffusion get_obj_float(@effect, GET_DIFFUSION) end def gain get_obj_float(@effect, GET_GAIN) end def hfgain get_obj_float(@effect, GET_HFGAIN) end def decay_time get_obj_float(@effect, GET_DECAY_TIME) end def hfdecay_ratio get_obj_float(@effect, GET_HFDECAY_RATIO) end def reflections_gain get_obj_float(@effect, GET_REFLECTIONS_GAIN) end def reflections_delay get_obj_float(@effect, GET_REFLECTIONS_DELAY) end def late_gain get_obj_float(@effect, GET_LATE_GAIN) end def late_delay get_obj_float(@effect, GET_LATE_DELAY) end def air_absorbtion_hfgain get_obj_float(@effect, GET_AIR_ABSORBTION_HFGAIN) end def room_rolloff_factor get_obj_float(@effect, GET_ROOM_ROLLOFF_FACTOR) end def hfdecay_limited get_obj_char(@effect, IS_HFDECAY_LIMITED) end alias hfdecay_limited? hfdecay_limited module Preset Helper.define_enum(self, [ :GENERIC, :PADDEDCELL, :ROOM, :BATHROOM, :LIVINGROOM, :STONEROOM, :AUDITORIUM, :CONCERTHALL, :CAVE, :ARENA, :HANGAR, :CARPETEDHALLWAY, :HALLWAY, :STONECORRIDOR, :ALLEY, :FOREST, :CITY, :MOUNTAINS, :QUARRY, :PLAIN, :PARKINGLOT, :SEWERPIPE, :UNDERWATER, :DRUGGED, :DIZZY, :PSYCHOTIC ]) module Castle Helper.define_enum(self, [ :SMALLROOM, :SHORTPASSAGE, :MEDIUMROOM, :LARGEROOM, :LONGPASSAGE, :HALL, :CUPBOARD, :COURTYARD, :ALCOVE ], Preset::PSYCHOTIC + 1) end module Factory Helper.define_enum(self, [ :SMALLROOM, :SHORTPASSAGE, :MEDIUMROOM, :LARGEROOM, :LONGPASSAGE, :HALL, :CUPBOARD, :COURTYARD, :ALCOVE ], Castle::ALCOVE + 1) end module IcePalace Helper.define_enum(self, [ :SMALLROOM, :SHORTPASSAGE, :MEDIUMROOM, :LARGEROOM, :LONGPASSAGE, :HALL, :CUPBOARD, :COURTYARD, :ALCOVE ], Factory::ALCOVE + 1) end module SpaceStation Helper.define_enum(self, [ :SMALLROOM, :SHORTPASSAGE, :MEDIUMROOM, :LARGEROOM, :LONGPASSAGE, :HALL, :CUPBOARD, :ALCOVE ], IcePalace::ALCOVE + 1) end module WoodenGalleon Helper.define_enum(self, [ :SMALLROOM, :SHORTPASSAGE, :MEDIUMROOM, :LARGEROOM, :LONGPASSAGE, :HALL, :CUPBOARD, :COURTYARD, :ALCOVE ], SpaceStation::ALCOVE + 1) end module Sports Helper.define_enum(self, [ :EMPTYSTADIUM, :SQUASHCOURT, :SMALLSWIMMINGPOOL, :LARGESWIMMINGPOOL, :GYMNASIUM, :FULLSTADIUM, :STADIUMTANNOY ], WoodenGalleon::ALCOVE + 1) end module Prefab Helper.define_enum(self, [ :WORKSHOP, :SCHOOLROOM, :PRACTISEROOM, :OUTHOUSE, :CARAVAN ], Sports::STADIUMTANNOY + 1) end module Dome Helper.define_enum(self, [ :TOMB, :SAINTPAULS ], Prefab::CARAVAN + 1) end module Pipe Helper.define_enum(self, [ :SMALL, :LONGTHIN, :LARGE, :RESONANT ], Dome::SAINTPAULS + 1) end module Outdoors Helper.define_enum(self, [ :BACKYARD, :ROLLINGPLAINS, :DEEPCANYON, :CREEK, :VALLEY ], Pipe::RESONANT + 1) end module Mood Helper.define_enum(self, [ :HEAVEN, :HELL, :MEMORY ], Outdoors::VALLEY + 1) end module Driving Helper.define_enum(self, [ :COMMENTATOR, :PITGARAGE, :INCAR_RACER, :INCAR_SPORTS, :INCAR_LUXURY, :FULLGRANDSTAND, :EMPTYGRANDSTAND, :TUNNEL ], Mood::MEMORY + 1) end module City Helper.define_enum(self, [ :STREETS, :SUBWAY, :MUSEUM, :LIBRARY, :UNDERPASS, :ABANDONED ], Driving::TUNNEL + 1) end module Misc Helper.define_enum(self, [ :DUSTYROOM, :CHAPEL, :SMALLWATERROOM ], City::ABANDONED + 1) end end end class Source include Helper INIT = SealAPI.new('init_src', 'p') DESTROY = SealAPI.new('destroy_src', 'p') PLAY = SealAPI.new('play_src', 'p') STOP = SealAPI.new('stop_src', 'p') REWIND = SealAPI.new('rewind_src', 'p') PAUSE = SealAPI.new('pause_src', 'p') DETACH = SealAPI.new('detach_src_audio', 'p') SET_BUF = SealAPI.new('set_src_buf', 'pp') SET_STREAM = SealAPI.new('set_src_stream', 'pp') FEED_EFS = SealAPI.new('feed_efs', 'ppi') UPDATE = SealAPI.new('update_src', 'p') SET_POS = SealAPI.new('set_src_pos', 'piii') SET_VEL = SealAPI.new('set_src_vel', 'piii') SET_GAIN = SealAPI.new('set_src_gain', 'pi') SET_PITCH = SealAPI.new('set_src_pitch', 'pi') SET_AUTO = SealAPI.new('set_src_auto', 'pi') SET_RELATIVE = SealAPI.new('set_src_relative', 'pi') SET_LOOPING = SealAPI.new('set_src_looping', 'pi') SET_QUEUE_SIZE = SealAPI.new('set_src_queue_size', 'pi') SET_CHUNK_SIZE = SealAPI.new('set_src_chunk_size', 'pi') GET_POS = SealAPI.new('get_src_pos', 'pppp') GET_VEL = SealAPI.new('get_src_vel', 'pppp') GET_GAIN = SealAPI.new('get_src_gain', 'pp') GET_PITCH = SealAPI.new('get_src_pitch', 'pp') GET_AUTO = SealAPI.new('is_src_auto', 'pp') GET_RELATIVE = SealAPI.new('is_src_relative', 'pp') GET_LOOPING = SealAPI.new('is_src_looping', 'pp') GET_QUEUE_SIZE = SealAPI.new('get_src_queue_size', 'pp') GET_CHUNK_SIZE = SealAPI.new('get_src_chunk_size', 'pp') GET_TYPE = SealAPI.new('get_src_type', 'pp') GET_STATE = SealAPI.new('get_src_state', 'pp') def initialize [url=home.php?mod=space&uid=171370]@source[/url] = ' ' * 5 check_error(INIT[@source]) ObjectSpace.define_finalizer(self, Helper.free(@source, DESTROY)) self end def play operate(PLAY) end def stop operate(STOP) end def rewind operate(REWIND) end def pause operate(PAUSE) end def buffer=(buffer) set_audio(:@buffer, buffer, SET_BUF) end def stream=(stream) set_audio(:@stream, stream, SET_STREAM) end attr_reader :buffer, :stream def feed(effect_slot, index) native_efs_obj = effect_slot.instance_variable_get(:@effect_slot) check_error(FEED_EFS[@source, native_efs_obj, index]) self end def update operate(UPDATE) end def position=(position) set_3float(position, SET_POS) end def velocity=(velocity) set_3float(velocity, SET_VEL) end def gain=(gain) set_obj_float(@source, gain, SET_GAIN) end def pitch=(pitch) set_obj_float(@source, pitch, SET_PITCH) end def auto=(auto) set_obj_char(@source, auto, SET_AUTO) end def queue_size=(queue_size) set_obj_int(@source, queue_size, SET_QUEUE_SIZE) end def chunk_size=(chunk_size) set_obj_int(@source, chunk_size, SET_CHUNK_SIZE) end def relative=(relative) set_obj_char(@source, relative, SET_RELATIVE) end def looping=(looping) set_obj_char(@source, looping, SET_LOOPING) end def position get_3float(GET_POS) end def velocity get_3float(GET_VEL) end def gain get_obj_float(@source, GET_GAIN) end def pitch get_obj_float(@source, GET_PITCH) end def auto get_obj_char(@source, GET_AUTO) end def relative get_obj_char(@source, GET_RELATIVE) end def looping get_obj_char(@source, GET_LOOPING) end alias auto? auto alias relative? relative alias looping? looping def queue_size get_obj_int(@source, GET_QUEUE_SIZE) end def chunk_size get_obj_int(@source, GET_CHUNK_SIZE) end def type case get_obj_int(@source, GET_TYPE) when Type::STATIC Type::STATIC when Type::STREAMING Type::STREAMING else Type::UNDETERMINED end end def state case get_obj_int(@source, GET_STATE) when State::PLAYING State::PLAYING when State::PAUSED State::PAUSED when State::STOPPED State::STOPPED else State::INITIAL end end private def operate(operation) check_error(operation[@source]) self end def set_audio(var, audio, setter) if audio.nil? operate(DETACH) else check_error(setter[@source, audio.instance_variable_get(var)]) end instance_variable_set(var, audio) audio end def set_3float(float_tuple, setter) integer_tuple = float_tuple.pack('f*').unpack('i*') check_error(setter[@source, *integer_tuple]) float_tuple end def get_3float(getter) float_tuple_buffers = Array.new(3) { ' ' } check_error(getter[@source, *float_tuple_buffers]) float_tuple_buffers.join.unpack('f*') end module State Helper.define_enum(self, [ :INITIAL, :PLAYING, :PAUSED, :STOPPED ]) end module Type Helper.define_enum(self, [ :UNDETERMINED, :STATIC, :STREAMING ]) end end class EffectSlot include Helper INIT = SealAPI.new('init_efs', 'p') DESTROY = SealAPI.new('destroy_efs', 'p') SET_EFFECT = SealAPI.new('set_efs_effect', 'pp') SET_GAIN = SealAPI.new('set_efs_gain', 'pi') SET_AUTO = SealAPI.new('set_efs_auto', 'pi') GET_GAIN = SealAPI.new('get_efs_gain', 'pp') GET_AUTO = SealAPI.new('is_efs_auto', 'pp') def initialize(effect = nil) @effect_slot = ' ' check_error(INIT[@effect_slot]) self.effect = effect if effect ObjectSpace.define_finalizer(self, Helper.free(@effect_slot, DESTROY)) self end def effect=(effect) native_effect_obj = effect ? effect.instance_variable_get(:@effect) : 0 check_error(SET_EFFECT[@effect_slot, native_effect_obj]) @effect = effect effect end attr_reader :effect def gain=(gain) set_obj_float(@effect_slot, gain, SET_GAIN) end def gain get_obj_float(@effect_slot, GET_GAIN) end def auto=(auto) set_obj_char(@effect_slot, auto, SET_AUTO) end def auto get_obj_char(@effect_slot, GET_AUTO) end alias auto? auto end end
module Seal
class SealAPI < Win32API
STRCPY_S = Win32API.new('msvcrt', 'strcpy_s', 'pll', 'i')
def initialize(func, arg_types, return_type = 'i', *args)
@return_string = return_type == 'p'
library = File.join(defined?(LIB_DIR) ? LIB_DIR : '.', 'seal')
super(library, "seal_#{func}", arg_types, return_type, *args)
end
def [](*args)
result = call(*args)
if @return_string and result.is_a? Integer
# String pointer is returned to Ruby as an integer even though we
# specified 'p' as the return value - possibly a bug in Ruby 1.9.3's
# Win32API implementation. Work around it.
message_buffer = ' ' * 128
STRCPY_S.call(message_buffer, message_buffer.size, result)
return message_buffer.strip
end
result
end unless method_defined? :[]
end
module Helper
GET_ERR_MSG = SealAPI.new('get_err_msg', 'i', 'p')
class << self
def define_enum(mod, constants, start_value = 0)
constants.each_with_index do |constant, index|
mod.const_set(constant, start_value + index)
end
end
# Returns a destructor for a native Seal object. This is most likely
# called in the initializer method, but we cannot define the proc handler
# there because that will capture the binding of the implicit `self` (due
# to the nature of closure), which makes the object that `self` refers to
# unrecyclable.
def free(obj, destroyer)
lambda { |object_id| destroyer[obj] }
end
end
private
def check_error(error_code)
raise SealError, GET_ERR_MSG[error_code], caller.shift if error_code != 0
nil
end
def input_audio(media, filename, format, inputter)
check_error(inputter[media, filename, format])
end
def set_obj_int(obj, int, setter)
check_error(setter[obj, int])
int
end
def get_obj_int(obj, getter)
buffer = ' '
check_error(getter[obj, buffer])
buffer.unpack('i')[0]
end
def set_obj_char(obj, bool, setter)
set_obj_int(obj, bool ? 1 : 0, setter)
end
def get_obj_char(obj, getter)
buffer = ' '
check_error(getter[obj, buffer])
buffer.unpack('c')[0] != 0
end
# Win32API does not support float argument type, need to pass as integer.
def set_obj_float(obj, float, setter)
check_error(setter[obj, [float].pack('f').unpack('i')[0]])
float
end
def get_obj_float(obj, getter)
float_buffer = ' '
check_error(getter[obj, float_buffer])
float_buffer.unpack('f')[0]
end
end
VERSION = SealAPI.new('get_version', 'v', 'p')[]
class << self
include Helper
STARTUP = SealAPI.new('startup', 'p')
CLEANUP = SealAPI.new('cleanup', 'v', 'v')
GET_PER_SRC_EFFECT_LIMIT = SealAPI.new('get_per_src_effect_limit', 'v')
def startup(device = nil)
check_error(STARTUP[device ? device : 0])
end
def cleanup
CLEANUP[]
end
def per_source_effect_limit
GET_PER_SRC_EFFECT_LIMIT[]
end
end
module Format
Helper.define_enum(self, [
:UNKNOWN,
:WAV,
:OV,
:MPG
])
end
class SealError < Exception
end
class Listener
include Helper
SET_GAIN = SealAPI.new('set_listener_gain', 'i')
GET_GAIN = SealAPI.new('get_listener_gain', 'p')
SET_POS = SealAPI.new('set_listener_pos', 'iii')
GET_POS = SealAPI.new('get_listener_pos', 'ppp')
SET_VEL = SealAPI.new('set_listener_vel', 'iii')
GET_VEL = SealAPI.new('get_listener_vel', 'ppp')
SET_ORIEN = SealAPI.new('set_listener_orien', 'p')
GET_ORIEN = SealAPI.new('get_listener_orien', 'p')
def gain=(gain)
check_error(SET_GAIN[[gain].pack('f').unpack('i')[0]])
gain
end
def gain
gain_buffer = ' '
check_error(GET_GAIN[gain_buffer])
return gain_buffer.unpack('f')[0]
end
def position=(position)
set_3float(position, SET_POS)
end
def position
get_3float(GET_POS)
end
def velocity=(velocity)
set_3float(velocity, SET_VEL)
end
def velocity
get_3float(GET_VEL)
end
def orientation=(orientation)
check_error(SET_ORIEN[orientation.flatten.pack('f*')])
end
def orientation
orientation_buffer = ' ' * 6
check_error(GET_ORIEN[orientation_buffer])
orientation = orientation_buffer.unpack('f*')
[orientation[0..2], orientation[3..5]]
end
private
def set_3float(float_tuple, setter)
integer_tuple = float_tuple.pack('f*').unpack('i*')
check_error(setter[*integer_tuple])
return float_tuple
end
def get_3float(getter)
float_tuple_buffers = Array.new(3) { ' ' }
check_error(getter[*float_tuple_buffers])
return float_tuple_buffers.join.unpack('f*')
end
end
LISTENER = Listener.new
def self.listener
LISTENER
end
class << Listener
undef allocate
undef new
end
class Buffer
include Helper
INIT = SealAPI.new('init_buf', 'p')
DESTROY = SealAPI.new('destroy_buf', 'p')
LOAD = SealAPI.new('load2buf', 'ppi')
GET_SIZE = SealAPI.new('get_buf_size', 'pp')
GET_FREQ = SealAPI.new('get_buf_freq', 'pp')
GET_BPS = SealAPI.new('get_buf_bps', 'pp')
GET_NCHANNELS = SealAPI.new('get_buf_nchannels', 'pp')
def initialize(filename, format = Format::UNKNOWN)
@buffer = ' '
check_error(INIT[@buffer])
input_audio(@buffer, filename, format, LOAD)
ObjectSpace.define_finalizer(self, Helper.free(@buffer, DESTROY))
self
end
def load(filename, format = Format::UNKNOWN)
input_audio(@buffer, filename, format, LOAD)
self
end
def size
get_obj_int(@buffer, GET_SIZE)
end
def frequency
get_obj_int(@buffer, GET_FREQ)
end
def bit_depth
get_obj_int(@buffer, GET_BPS)
end
def channel_count
get_obj_int(@buffer, GET_NCHANNELS)
end
end
class Stream
include Helper
OPEN = SealAPI.new('open_stream', 'ppi')
CLOSE = SealAPI.new('close_stream', 'p')
REWIND = SealAPI.new('rewind_stream', 'p')
class << self
alias open new
end
def initialize(filename, format = Format::UNKNOWN)
[url=home.php?mod=space&uid=33438]@stream[/url] = ' ' * 5
input_audio(@stream, filename, format, OPEN)
ObjectSpace.define_finalizer(self, Helper.free(@stream, CLOSE))
self
end
def frequency
field(4)
end
def bit_depth
field(2)
end
def channel_count
field(3)
end
def rewind
check_error(REWIND[@stream])
end
def close
check_error(CLOSE[@stream])
end
private
def field(index)
@stream[index * 4, 4].unpack('i')[0]
end
end
class Reverb
include Helper
INIT = SealAPI.new('init_rvb', 'p')
DESTROY = SealAPI.new('destroy_rvb', 'p')
LOAD = SealAPI.new('load_rvb', 'pi')
SET_DENSITY = SealAPI.new('set_rvb_density', 'pi')
SET_DIFFUSION = SealAPI.new('set_rvb_diffusion', 'pi')
SET_GAIN = SealAPI.new('set_rvb_gain', 'pi')
SET_HFGAIN = SealAPI.new('set_rvb_hfgain', 'pi')
SET_DECAY_TIME = SealAPI.new('set_rvb_decay_time', 'pi')
SET_HFDECAY_RATIO = SealAPI.new('set_rvb_hfdecay_ratio', 'pi')
SET_REFLECTIONS_GAIN = SealAPI.new('set_rvb_reflections_gain', 'pi')
SET_REFLECTIONS_DELAY = SealAPI.new('set_rvb_reflections_delay', 'pi')
SET_LATE_GAIN = SealAPI.new('set_rvb_late_gain', 'pi')
SET_LATE_DELAY = SealAPI.new('set_rvb_late_delay', 'pi')
SET_AIR_ABSORBTION_HFGAIN =
SealAPI.new('set_rvb_air_absorbtion_hfgain', 'pi')
SET_ROOM_ROLLOFF_FACTOR = SealAPI.new('set_rvb_room_rolloff_factor', 'pi')
SET_HFDECAY_LIMITED = SealAPI.new('set_rvb_hfdecay_limited', 'pi')
GET_DENSITY = SealAPI.new('get_rvb_density', 'pp')
GET_DIFFUSION = SealAPI.new('get_rvb_diffusion', 'pp')
GET_GAIN = SealAPI.new('get_rvb_gain', 'pp')
GET_HFGAIN = SealAPI.new('get_rvb_hfgain', 'pp')
GET_DECAY_TIME = SealAPI.new('get_rvb_decay_time', 'pp')
GET_HFDECAY_RATIO = SealAPI.new('get_rvb_hfdecay_ratio', 'pp')
GET_REFLECTIONS_GAIN = SealAPI.new('get_rvb_reflections_gain', 'pp')
GET_REFLECTIONS_DELAY = SealAPI.new('get_rvb_reflections_delay', 'pp')
GET_LATE_GAIN = SealAPI.new('get_rvb_late_gain', 'pp')
GET_LATE_DELAY = SealAPI.new('get_rvb_late_delay', 'pp')
GET_AIR_ABSORBTION_HFGAIN =
SealAPI.new('get_rvb_air_absorbtion_hfgain', 'pp')
GET_ROOM_ROLLOFF_FACTOR = SealAPI.new('get_rvb_room_rolloff_factor', 'pp')
IS_HFDECAY_LIMITED = SealAPI.new('is_rvb_hfdecay_limited', 'pp')
def initialize(preset = nil)
@effect = ' '
check_error(INIT[@effect])
load(preset) if preset
ObjectSpace.define_finalizer(self, Helper.free(@effect, DESTROY))
self
end
def load(preset)
check_error(LOAD[@effect, preset])
end
def density=(density)
set_obj_float(@effect, density, SET_DENSITY)
end
def diffusion=(diffusion)
set_obj_float(@effect, diffusion, SET_DIFFUSION)
end
def gain=(gain)
set_obj_float(@effect, gain, SET_GAIN)
end
def hfgain=(hfgain)
set_obj_float(@effect, hfgain, SET_HFGAIN)
end
def decay_time=(decay_time)
set_obj_float(@effect, decay_time, SET_DECAY_TIME)
end
def hfdecay_ratio=(hfdecay_ratio)
set_obj_float(@effect, hfdecay_ratio, SET_HFDECAY_RATIO)
end
def reflections_gain=(reflections_gain)
set_obj_float(@effect, reflections_gain, SET_REFLECTIONS_GAIN)
end
def reflections_delay=(reflections_delay)
set_obj_float(@effect, reflections_delay, SET_REFLECTIONS_DELAY)
end
def late_gain=(late_gain)
set_obj_float(@effect, late_gain, SET_LATE_GAIN)
end
def late_delay=(late_delay)
set_obj_float(@effect, late_delay, SET_LATE_DELAY)
end
def air_absorbtion_hfgain=(air_absorbtion_hfgain)
set_obj_float(@effect, air_absorbtion_hfgain, SET_AIR_ABSORBTION_HFGAIN)
end
def room_rolloff_factor=(room_rolloff_factor)
set_obj_float(@effect, room_rolloff_factor, SET_ROOM_ROLLOFF_FACTOR)
end
def hfdecay_limited=(hfdecay_limited)
set_obj_char(@effect, hfdecay_limited, SET_HFDECAY_LIMITED)
end
def density
get_obj_float(@effect, GET_DENSITY)
end
def diffusion
get_obj_float(@effect, GET_DIFFUSION)
end
def gain
get_obj_float(@effect, GET_GAIN)
end
def hfgain
get_obj_float(@effect, GET_HFGAIN)
end
def decay_time
get_obj_float(@effect, GET_DECAY_TIME)
end
def hfdecay_ratio
get_obj_float(@effect, GET_HFDECAY_RATIO)
end
def reflections_gain
get_obj_float(@effect, GET_REFLECTIONS_GAIN)
end
def reflections_delay
get_obj_float(@effect, GET_REFLECTIONS_DELAY)
end
def late_gain
get_obj_float(@effect, GET_LATE_GAIN)
end
def late_delay
get_obj_float(@effect, GET_LATE_DELAY)
end
def air_absorbtion_hfgain
get_obj_float(@effect, GET_AIR_ABSORBTION_HFGAIN)
end
def room_rolloff_factor
get_obj_float(@effect, GET_ROOM_ROLLOFF_FACTOR)
end
def hfdecay_limited
get_obj_char(@effect, IS_HFDECAY_LIMITED)
end
alias hfdecay_limited? hfdecay_limited
module Preset
Helper.define_enum(self, [
:GENERIC,
:PADDEDCELL,
:ROOM,
:BATHROOM,
:LIVINGROOM,
:STONEROOM,
:AUDITORIUM,
:CONCERTHALL,
:CAVE,
:ARENA,
:HANGAR,
:CARPETEDHALLWAY,
:HALLWAY,
:STONECORRIDOR,
:ALLEY,
:FOREST,
:CITY,
:MOUNTAINS,
:QUARRY,
:PLAIN,
:PARKINGLOT,
:SEWERPIPE,
:UNDERWATER,
:DRUGGED,
:DIZZY,
:PSYCHOTIC
])
module Castle
Helper.define_enum(self, [
:SMALLROOM,
:SHORTPASSAGE,
:MEDIUMROOM,
:LARGEROOM,
:LONGPASSAGE,
:HALL,
:CUPBOARD,
:COURTYARD,
:ALCOVE
], Preset::PSYCHOTIC + 1)
end
module Factory
Helper.define_enum(self, [
:SMALLROOM,
:SHORTPASSAGE,
:MEDIUMROOM,
:LARGEROOM,
:LONGPASSAGE,
:HALL,
:CUPBOARD,
:COURTYARD,
:ALCOVE
], Castle::ALCOVE + 1)
end
module IcePalace
Helper.define_enum(self, [
:SMALLROOM,
:SHORTPASSAGE,
:MEDIUMROOM,
:LARGEROOM,
:LONGPASSAGE,
:HALL,
:CUPBOARD,
:COURTYARD,
:ALCOVE
], Factory::ALCOVE + 1)
end
module SpaceStation
Helper.define_enum(self, [
:SMALLROOM,
:SHORTPASSAGE,
:MEDIUMROOM,
:LARGEROOM,
:LONGPASSAGE,
:HALL,
:CUPBOARD,
:ALCOVE
], IcePalace::ALCOVE + 1)
end
module WoodenGalleon
Helper.define_enum(self, [
:SMALLROOM,
:SHORTPASSAGE,
:MEDIUMROOM,
:LARGEROOM,
:LONGPASSAGE,
:HALL,
:CUPBOARD,
:COURTYARD,
:ALCOVE
], SpaceStation::ALCOVE + 1)
end
module Sports
Helper.define_enum(self, [
:EMPTYSTADIUM,
:SQUASHCOURT,
:SMALLSWIMMINGPOOL,
:LARGESWIMMINGPOOL,
:GYMNASIUM,
:FULLSTADIUM,
:STADIUMTANNOY
], WoodenGalleon::ALCOVE + 1)
end
module Prefab
Helper.define_enum(self, [
:WORKSHOP,
:SCHOOLROOM,
:PRACTISEROOM,
:OUTHOUSE,
:CARAVAN
], Sports::STADIUMTANNOY + 1)
end
module Dome
Helper.define_enum(self, [
:TOMB,
:SAINTPAULS
], Prefab::CARAVAN + 1)
end
module Pipe
Helper.define_enum(self, [
:SMALL,
:LONGTHIN,
:LARGE,
:RESONANT
], Dome::SAINTPAULS + 1)
end
module Outdoors
Helper.define_enum(self, [
:BACKYARD,
:ROLLINGPLAINS,
:DEEPCANYON,
:CREEK,
:VALLEY
], Pipe::RESONANT + 1)
end
module Mood
Helper.define_enum(self, [
:HEAVEN,
:HELL,
:MEMORY
], Outdoors::VALLEY + 1)
end
module Driving
Helper.define_enum(self, [
:COMMENTATOR,
:PITGARAGE,
:INCAR_RACER,
:INCAR_SPORTS,
:INCAR_LUXURY,
:FULLGRANDSTAND,
:EMPTYGRANDSTAND,
:TUNNEL
], Mood::MEMORY + 1)
end
module City
Helper.define_enum(self, [
:STREETS,
:SUBWAY,
:MUSEUM,
:LIBRARY,
:UNDERPASS,
:ABANDONED
], Driving::TUNNEL + 1)
end
module Misc
Helper.define_enum(self, [
:DUSTYROOM,
:CHAPEL,
:SMALLWATERROOM
], City::ABANDONED + 1)
end
end
end
class Source
include Helper
INIT = SealAPI.new('init_src', 'p')
DESTROY = SealAPI.new('destroy_src', 'p')
PLAY = SealAPI.new('play_src', 'p')
STOP = SealAPI.new('stop_src', 'p')
REWIND = SealAPI.new('rewind_src', 'p')
PAUSE = SealAPI.new('pause_src', 'p')
DETACH = SealAPI.new('detach_src_audio', 'p')
SET_BUF = SealAPI.new('set_src_buf', 'pp')
SET_STREAM = SealAPI.new('set_src_stream', 'pp')
FEED_EFS = SealAPI.new('feed_efs', 'ppi')
UPDATE = SealAPI.new('update_src', 'p')
SET_POS = SealAPI.new('set_src_pos', 'piii')
SET_VEL = SealAPI.new('set_src_vel', 'piii')
SET_GAIN = SealAPI.new('set_src_gain', 'pi')
SET_PITCH = SealAPI.new('set_src_pitch', 'pi')
SET_AUTO = SealAPI.new('set_src_auto', 'pi')
SET_RELATIVE = SealAPI.new('set_src_relative', 'pi')
SET_LOOPING = SealAPI.new('set_src_looping', 'pi')
SET_QUEUE_SIZE = SealAPI.new('set_src_queue_size', 'pi')
SET_CHUNK_SIZE = SealAPI.new('set_src_chunk_size', 'pi')
GET_POS = SealAPI.new('get_src_pos', 'pppp')
GET_VEL = SealAPI.new('get_src_vel', 'pppp')
GET_GAIN = SealAPI.new('get_src_gain', 'pp')
GET_PITCH = SealAPI.new('get_src_pitch', 'pp')
GET_AUTO = SealAPI.new('is_src_auto', 'pp')
GET_RELATIVE = SealAPI.new('is_src_relative', 'pp')
GET_LOOPING = SealAPI.new('is_src_looping', 'pp')
GET_QUEUE_SIZE = SealAPI.new('get_src_queue_size', 'pp')
GET_CHUNK_SIZE = SealAPI.new('get_src_chunk_size', 'pp')
GET_TYPE = SealAPI.new('get_src_type', 'pp')
GET_STATE = SealAPI.new('get_src_state', 'pp')
def initialize
[url=home.php?mod=space&uid=171370]@source[/url] = ' ' * 5
check_error(INIT[@source])
ObjectSpace.define_finalizer(self, Helper.free(@source, DESTROY))
self
end
def play
operate(PLAY)
end
def stop
operate(STOP)
end
def rewind
operate(REWIND)
end
def pause
operate(PAUSE)
end
def buffer=(buffer)
set_audio(:@buffer, buffer, SET_BUF)
end
def stream=(stream)
set_audio(:@stream, stream, SET_STREAM)
end
attr_reader :buffer, :stream
def feed(effect_slot, index)
native_efs_obj = effect_slot.instance_variable_get(:@effect_slot)
check_error(FEED_EFS[@source, native_efs_obj, index])
self
end
def update
operate(UPDATE)
end
def position=(position)
set_3float(position, SET_POS)
end
def velocity=(velocity)
set_3float(velocity, SET_VEL)
end
def gain=(gain)
set_obj_float(@source, gain, SET_GAIN)
end
def pitch=(pitch)
set_obj_float(@source, pitch, SET_PITCH)
end
def auto=(auto)
set_obj_char(@source, auto, SET_AUTO)
end
def queue_size=(queue_size)
set_obj_int(@source, queue_size, SET_QUEUE_SIZE)
end
def chunk_size=(chunk_size)
set_obj_int(@source, chunk_size, SET_CHUNK_SIZE)
end
def relative=(relative)
set_obj_char(@source, relative, SET_RELATIVE)
end
def looping=(looping)
set_obj_char(@source, looping, SET_LOOPING)
end
def position
get_3float(GET_POS)
end
def velocity
get_3float(GET_VEL)
end
def gain
get_obj_float(@source, GET_GAIN)
end
def pitch
get_obj_float(@source, GET_PITCH)
end
def auto
get_obj_char(@source, GET_AUTO)
end
def relative
get_obj_char(@source, GET_RELATIVE)
end
def looping
get_obj_char(@source, GET_LOOPING)
end
alias auto? auto
alias relative? relative
alias looping? looping
def queue_size
get_obj_int(@source, GET_QUEUE_SIZE)
end
def chunk_size
get_obj_int(@source, GET_CHUNK_SIZE)
end
def type
case get_obj_int(@source, GET_TYPE)
when Type::STATIC
Type::STATIC
when Type::STREAMING
Type::STREAMING
else
Type::UNDETERMINED
end
end
def state
case get_obj_int(@source, GET_STATE)
when State::PLAYING
State::PLAYING
when State::PAUSED
State::PAUSED
when State::STOPPED
State::STOPPED
else
State::INITIAL
end
end
private
def operate(operation)
check_error(operation[@source])
self
end
def set_audio(var, audio, setter)
if audio.nil?
operate(DETACH)
else
check_error(setter[@source, audio.instance_variable_get(var)])
end
instance_variable_set(var, audio)
audio
end
def set_3float(float_tuple, setter)
integer_tuple = float_tuple.pack('f*').unpack('i*')
check_error(setter[@source, *integer_tuple])
float_tuple
end
def get_3float(getter)
float_tuple_buffers = Array.new(3) { ' ' }
check_error(getter[@source, *float_tuple_buffers])
float_tuple_buffers.join.unpack('f*')
end
module State
Helper.define_enum(self, [
:INITIAL,
:PLAYING,
:PAUSED,
:STOPPED
])
end
module Type
Helper.define_enum(self, [
:UNDETERMINED,
:STATIC,
:STREAMING
])
end
end
class EffectSlot
include Helper
INIT = SealAPI.new('init_efs', 'p')
DESTROY = SealAPI.new('destroy_efs', 'p')
SET_EFFECT = SealAPI.new('set_efs_effect', 'pp')
SET_GAIN = SealAPI.new('set_efs_gain', 'pi')
SET_AUTO = SealAPI.new('set_efs_auto', 'pi')
GET_GAIN = SealAPI.new('get_efs_gain', 'pp')
GET_AUTO = SealAPI.new('is_efs_auto', 'pp')
def initialize(effect = nil)
@effect_slot = ' '
check_error(INIT[@effect_slot])
self.effect = effect if effect
ObjectSpace.define_finalizer(self, Helper.free(@effect_slot, DESTROY))
self
end
def effect=(effect)
native_effect_obj = effect ? effect.instance_variable_get(:@effect) : 0
check_error(SET_EFFECT[@effect_slot, native_effect_obj])
@effect = effect
effect
end
attr_reader :effect
def gain=(gain)
set_obj_float(@effect_slot, gain, SET_GAIN)
end
def gain
get_obj_float(@effect_slot, GET_GAIN)
end
def auto=(auto)
set_obj_char(@effect_slot, auto, SET_AUTO)
end
def auto
get_obj_char(@effect_slot, GET_AUTO)
end
alias auto? auto
end
end
Seal 的 C 庫:
seal.zip
(243.88 KB, 下载次数: 151)
(以防萬一以上壓縮包也包含了 Win32API 綁定的 Ruby 源文件,可以直接粘貼入腳本編輯器)
非 Ruby 程序
可以直接將 Seal 編譯為 C 庫並調用(其實就是上面提供的 seal.dll):
https://github.com/zhangsu/seal#msvc--microsoft-visual-studio-2010
Ruby 程序
可以直接使用 gem:
https://rubygems.org/gems/seal
目前正和@叶子 趕製一款小遊戲演示 Seal 的音效功能,預計這幾天發佈。
待完成功能
- Source 屬性
- 最遠減淡距離
- 滾降因子
- 參考距離
- 最大、最小音量
- 方向矢量與錐形音量範圍
- 當前播放位置
- 改變 OpenAL 距離模型
- 音速
- 多普勒因子
- 輸出設備枚舉
- 音頻輸入(錄製)
- 特效
- 前奏
- 對象克隆(Object#clone/Object#dup)
貢獻代碼
目前的開發是在 Github 上:https://github.com/zhangsu/seal
強烈歡迎提交 pull request,大小不論,哪怕只是修復一個錯字。 |
评分
-
查看全部评分
|