#include "precompiled.h" #include "JSI_Sound.h" #include "maths/Vector3D.h" #include "lib/res/sound/snd_mgr.h" #include "lib/res/h_mgr.h" // h_filename JSI_Sound::JSI_Sound( const CStr& Filename ) { const char* fn = Filename.c_str(); m_Handle = snd_open( fn ); // special-case to avoid throwing exceptions if quickstart has // disabled sound: set a flag queried by Construct; the object will // then immediately be freed. if(m_Handle == ERR::AGAIN) { m_SoundDisabled = true; return; } m_SoundDisabled = false; // if open failed, raise an exception - it's the only way to // report errors, since we're in the ctor and don't want to move // the open call elsewhere (by requiring an explicit open() call). THROW_ERR(m_Handle); // caught by JSI_Sound::Construct. snd_set_pos( m_Handle, 0,0,0, true); } JSI_Sound::~JSI_Sound() { this->Free(0, 0, 0); } bool JSI_Sound::SetGain( JSContext* cx, uintN argc, jsval* argv ) { debug_assert( argc >= 1 ); float gain; if( !ToPrimitive( cx, argv[0], gain) ) return false; snd_set_gain(m_Handle, gain); return true; } bool JSI_Sound::SetPitch( JSContext* cx, uintN argc, jsval* argv ) { debug_assert( argc >= 1 ); float pitch; if( !ToPrimitive( cx, argv[0], pitch) ) return false; snd_set_pitch(m_Handle, pitch); return true; } bool JSI_Sound::SetPosition( JSContext* cx, uintN argc, jsval* argv ) { debug_assert( argc >= 1 ); CVector3D pos; // absolute world coords if( ToPrimitive( cx, argv[0], pos ) ) snd_set_pos(m_Handle, pos[0], pos[1], pos[2]); // relative, 0 offset - right on top of the listener // (we don't need displacement from the listener, e.g. always behind) else snd_set_pos(m_Handle, 0,0,0, true); return true; } bool JSI_Sound::Fade( JSContext* cx, uintN argc, jsval* argv ) { debug_assert( argc >= 3 ); float initial_gain, final_gain; float length; if( !ToPrimitive( cx, argv[0], initial_gain) ) return false; if( !ToPrimitive( cx, argv[1], final_gain) ) return false; if( !ToPrimitive( cx, argv[2], length) ) return false; snd_fade(m_Handle, initial_gain, final_gain, length, FT_S_CURVE); // HACK: snd_fade causes to be automatically freed when a // fade to 0 has completed. however, we're still holding on to a // reference, which will cause a double-free warning when Free() is // called from the dtor. solution is to neuter our Handle by // setting it to 0 (ok since it'll be freed). this does mean that // no further operations can be carried out during that final fade. m_Handle = 0; return true; } // start playing the sound (one-shot). // it will automatically be freed when done. bool JSI_Sound::Play( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) { snd_play(m_Handle); return true; } // request the sound be played until free() is called. returns immediately. bool JSI_Sound::Loop( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) { snd_set_loop(m_Handle, true); snd_play(m_Handle); return true; } // stop sound if currently playing and free resources. // doesn't need to be called unless played via loop() - // sounds are freed automatically when done playing. bool JSI_Sound::Free( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) { if(m_Handle) snd_free(m_Handle); // resets it to 0 return true; } // Script-bound functions void JSI_Sound::ScriptingInit() { AddMethod( "toString", 0 ); AddMethod( "play", 0 ); AddMethod( "loop", 0 ); AddMethod( "free", 0 ); AddMethod( "setGain", 0 ); AddMethod( "setPitch", 0 ); AddMethod( "setPosition", 0 ); AddMethod( "fade", 0 ); CJSObject::ScriptingInit( "Sound", &JSI_Sound::Construct, 1 ); } jsval JSI_Sound::ToString( JSContext* cx, uintN UNUSED(argc), jsval* UNUSED(argv) ) { const char* Filename = h_filename(m_Handle); wchar_t buffer[256]; swprintf( buffer, 256, L"[object Sound: %hs]", Filename ); buffer[255] = 0; utf16string str16(buffer, buffer+wcslen(buffer)); return( STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, str16.c_str() ) ) ); } JSBool JSI_Sound::Construct( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { debug_assert( argc >= 1 ); CStrW filename; if( !ToPrimitive( cx, argv[0], filename ) ) return( JS_FALSE ); try { JSI_Sound* newObject = new JSI_Sound( filename ); // somewhat of a hack to avoid throwing exceptions if // sound was disabled due to quickstart. see JSI_Sound(). if( newObject->m_SoundDisabled ) { delete newObject; *rval = JSVAL_NULL; return( JS_TRUE ); } newObject->m_EngineOwned = false; *rval = OBJECT_TO_JSVAL( newObject->GetScript() ); } catch(int) { // failed, but this can happen easily enough that we don't want to // return JS_FALSE (since that stops the script). *rval = JSVAL_NULL; } return( JS_TRUE ); }