hammerspoon/extensions/sound/libsound.m

702 lines
25 KiB
Objective-C

#import <Cocoa/Cocoa.h>
#import <LuaSkin/LuaSkin.h>
#import <AVFoundation/AVFoundation.h>
#define USERDATA_TAG "hs.sound"
static LSRefTable refTable = LUA_NOREF;
#define get_objectFromUserdata(objType, L, idx) (objType*)*((void**)luaL_checkudata(L, idx, USERDATA_TAG))
// #define get_structFromUserdata(objType, L, idx) ((objType *)luaL_checkudata(L, idx, USERDATA_TAG))
// #define get_cfobjectFromUserdata(objType, L, idx) *((objType*)luaL_checkudata(L, idx, USERDATA_TAG))
#pragma mark - Support Functions and Classes
@interface HSSoundObject : NSObject <NSSoundDelegate>
@property NSSound *soundObject ;
@property int callbackRef ;
@property int selfRef ;
@property BOOL stopOnRelease ;
@end
@implementation HSSoundObject
- (instancetype)initWithSound:(NSSound *)theSound {
self = [super init] ;
if (self) {
_soundObject = theSound ;
_callbackRef = LUA_NOREF ;
_selfRef = LUA_NOREF ;
_stopOnRelease = YES ;
[_soundObject setDelegate:self] ;
}
return self ;
}
#pragma mark - NSSoundDelegate methods
- (void) sound:(NSSound __unused *)sound didFinishPlaying:(BOOL)playbackSuccessful {
dispatch_async(dispatch_get_main_queue(), ^{
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
_lua_stackguard_entry(skin.L);
// [skin logVerbose:[NSString stringWithFormat:@"%s:in delegate", USERDATA_TAG]] ;
if (self->_callbackRef != LUA_NOREF) {
lua_State *L = skin.L;
[skin pushLuaRef:refTable ref:self->_callbackRef];
lua_pushboolean(L, playbackSuccessful);
[skin pushNSObject:self];
[skin protectedCallAndError:@"hs.sound:didFinishPlaying callback" nargs:2 nresults:0];
}
// a completed song should rely solely on user saved userdata values to prevent __gc
// since there will be no other way to access it once this point is reached if it hasn't
// been saved in a variable somewhere.
self->_selfRef = [skin luaUnref:refTable ref:self->_selfRef] ;
_lua_stackguard_exit(skin.L);
}) ;
}
@end
#pragma mark - Module Functions
/// hs.sound.getAudioEffectNames() -> table
/// Function
/// Gets a table of installed Audio Units Effect names.
///
/// Parameters:
/// * None
///
/// Returns:
/// * A table containing the names of all installed Audio Units Effects.
///
/// Notes:
/// * Example usage: `hs.inspect(hs.audiounit.getAudioEffectNames())`
static int sound_getAudioEffectNames(lua_State *L) {
AudioComponentDescription description;
description.componentType = kAudioUnitType_Effect;
description.componentSubType = 0;
description.componentManufacturer = 0;
description.componentFlags = 0;
description.componentFlagsMask = 0;
AudioComponent component = nil;
int count = 1;
lua_newtable(L);
while((component = AudioComponentFindNext(component, &description))) {
CFStringRef name;
AudioComponentCopyName(component, &name);
NSString *theName = (__bridge NSString *)name;
if (theName) {
lua_pushstring(L,[[NSString stringWithFormat:@"%@", theName] UTF8String]);
lua_rawseti(L, -2, count++);
}
if (name) {
CFRelease(name);
}
}
return 1 ;
}
/// hs.sound.getByName(name) -> sound or nil
/// Constructor
/// Creates an `hs.sound` object from a named sound
///
/// Parameters:
/// * name - A string containing the name of a sound
///
/// Returns:
/// * An `hs.sound` object or nil if no matching sound could be found
///
/// Notes:
/// * Sounds can only be loaded by name if they are System Sounds (i.e. those found in ~/Library/Sounds, /Library/Sounds, /Network/Library/Sounds and /System/Library/Sounds) or are sound files that have previously been loaded and named
static int sound_byname(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TSTRING | LS_TNUMBER, LS_TBREAK] ;
luaL_checkstring(L, 1) ; // force number to be a string
NSSound* theSound = [NSSound soundNamed:[skin toNSObjectAtIndex:1]] ;
if (theSound) {
[skin pushNSObject:theSound] ;
} else {
lua_pushnil(L) ;
}
return 1;
}
/// hs.sound.getByFile(path) -> sound or nil
/// Constructor
/// Creates an `hs.sound` object from a file
///
/// Parameters:
/// * path - A string containing the path to a sound file
///
/// Returns:
/// * An `hs.sound` object or nil if the file could not be loaded
static int sound_byfile(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TSTRING | LS_TNUMBER, LS_TBREAK] ;
luaL_checkstring(L, 1) ; // force number to be a string
NSSound* theSound = [[NSSound alloc] initWithContentsOfFile:[skin toNSObjectAtIndex:1] byReference: NO] ;
if (theSound) {
[skin pushNSObject:theSound] ;
} else {
lua_pushnil(L) ;
}
return 1;
}
/// hs.sound.systemSounds() -> table
/// Function
/// Gets a table of available system sounds
///
/// Parameters:
/// * None
///
/// Returns:
/// * A table containing all of the available sound files (i.e. those found in ~/Library/Sounds, /Library/Sounds, /Network/Library/Sounds and /System/Library/Sounds)
///
/// Notes:
/// * The sounds listed by this function can be loaded using `hs.sound.getByName()`
static int sound_systemSounds(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TBREAK] ;
int i = 0;
lua_newtable(L) ;
NSEnumerator *librarySources = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, YES) objectEnumerator];
NSString *sourcePath;
while ( sourcePath = [librarySources nextObject] )
{
NSEnumerator *soundSource = [[NSFileManager defaultManager] enumeratorAtPath: [sourcePath stringByAppendingPathComponent: @"Sounds"]];
NSString *soundFile;
while ( soundFile = [soundSource nextObject] )
if ( [NSSound soundNamed: [soundFile stringByDeletingPathExtension]] ) {
[skin pushNSObject:[soundFile stringByDeletingPathExtension]] ;
lua_rawseti(L, -2, ++i);
}
}
return 1;
}
/// hs.sound.soundTypes() -> table
/// Function
/// Gets the supported UTI sound file formats
///
/// Parameters:
/// * None
///
/// Returns:
/// * A table containing the UTI sound formats that are supported by the system
static int sound_soundUnfilteredTypes(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TBREAK] ;
[skin pushNSObject:[NSSound soundUnfilteredTypes]];
return 1;
}
/// hs.sound.soundFileTypes() -> table
/// Function
/// Gets the supported sound file types
///
/// Parameters:
/// * None
///
/// Returns:
/// * A table containing the sound file filename extensions that are supported by the system
///
/// Notes:
/// * This function is unlikely to be tremendously useful, as filename extensions are essentially meaningless. The data returned by `hs.sound.soundTypes()` is far more valuable
static int sound_soundUnfilteredFileTypes(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TBREAK] ;
if ([NSSound respondsToSelector:@selector(soundUnfilteredFileTypes)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[skin pushNSObject:[NSSound soundUnfilteredFileTypes]];
#pragma clang diagnostic pop
} else {
lua_pushstring(L, "Deprecated selector soundUnfilteredFileTypes not supported in this OS X version. Please use `hs.sound.soundTypes` instead.");
}
return 1;
}
#pragma mark - Module Methods
/// hs.sound:play() -> soundObject | bool
/// Method
/// Plays an `hs.sound` object
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.sound` object if the command was successful, otherwise false.
static int sound_play(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
HSSoundObject *obj = [skin luaObjectAtIndex:1 toClass:"HSSoundObject"] ;
if ([obj.soundObject play]) {
lua_pushvalue(L, 1) ;
if (obj.selfRef == LUA_NOREF) {
lua_pushvalue(L, 1) ;
obj.selfRef = [skin luaRef:refTable];
}
} else {
lua_pushboolean(L, NO);
}
return 1;
}
/// hs.sound:pause() -> soundObject | bool
/// Method
/// Pauses an `hs.sound` object
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.sound` object if the command was successful, otherwise false.
static int sound_pause(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
NSSound *obj = [skin luaObjectAtIndex:1 toClass:"NSSound"] ;
if ([obj pause]) {
lua_pushvalue(L, 1) ;
} else {
lua_pushboolean(L, NO);
}
return 1 ;
}
/// hs.sound:resume() -> soundObject | bool
/// Method
/// Resumes playing a paused `hs.sound` object
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.sound` object if the command was successful, otherwise false.
static int sound_resume(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
NSSound *obj = [skin luaObjectAtIndex:1 toClass:"NSSound"] ;
if ([obj resume]) {
lua_pushvalue(L, 1) ;
} else {
lua_pushboolean(L, NO);
}
return 1 ;
}
/// hs.sound:stop() -> soundObject | bool
/// Method
/// Stops playing an `hs.sound` object
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.sound` object if the command was successful, otherwise false.
static int sound_stop(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
NSSound *obj = [skin luaObjectAtIndex:1 toClass:"NSSound"] ;
if ([obj stop]) {
lua_pushvalue(L, 1) ;
} else {
lua_pushboolean(L, NO);
}
return 1 ;
}
/// hs.sound:loopSound([loop]) -> soundObject | bool
/// Method
/// Get or set the looping behaviour of an `hs.sound` object
///
/// Parameters:
/// * loop - An optional boolean, true to loop playback, false to not loop
///
/// Returns:
/// * If a parameter is provided, returns the sound object; otherwise returns the current setting.
///
/// Notes:
/// * If you have registered a callback function for completion of a sound's playback, it will not be called when the sound loops
static int sound_loopSound(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK] ;
NSSound *obj = [skin luaObjectAtIndex:1 toClass:"NSSound"] ;
if (lua_gettop(L) == 2) {
[obj setLoops:(BOOL)lua_toboolean(L, 2)];
lua_pushvalue(L, 1) ;
} else {
lua_pushboolean(L, [obj loops]);
}
return 1;
}
/// hs.sound:stopOnReload([stopOnReload]) -> soundObject | bool
/// Method
/// Get or set whether a sound should be stopped when Hammerspoon reloads its configuration
///
/// Parameters:
/// * stopOnReload - An optional boolean, true to stop playback when Hammerspoon reloads its config, false to continue playback regardless. Defaults to true.
///
/// Returns:
/// * If a parameter is provided, returns the sound object; otherwise returns the current setting.
///
/// Notes:
/// * This method can only be used on a named `hs.sound` object, see `hs.sound:name()`
static int sound_stopOnRelease(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK] ;
HSSoundObject *obj = [skin luaObjectAtIndex:1 toClass:"HSSoundObject"] ;
if (lua_gettop(L) == 2) {
if ([obj.soundObject name]) {
obj.stopOnRelease = (BOOL)lua_toboolean(L, 2);
lua_pushvalue(L, 1) ;
} else {
return luaL_error(L, "you must first assign a name to this sound in order to change this attribute");
}
} else {
lua_pushboolean(L, obj.stopOnRelease);
}
return 1;
}
/// hs.sound:name([soundName]) -> soundObject | name string
/// Method
/// Get or set the name of an `hs.sound` object
///
/// Parameters:
/// * soundName - An optional string to use as the name of the object; use an explicit nil to remove the name
///
/// Returns:
/// * If a parameter is provided, returns the sound object; otherwise returns the current setting.
///
/// Notes:
/// * If remove the sound name by specifying `nil`, the sound will automatically be set to stop when Hammerspoon is reloaded.
static int sound_name(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING | LS_TNUMBER | LS_TNIL | LS_TOPTIONAL, LS_TBREAK] ;
HSSoundObject *obj = [skin luaObjectAtIndex:1 toClass:"HSSoundObject"] ;
if (lua_gettop(L) == 2) {
if (lua_isnil(L,2)) {
[obj.soundObject setName:nil];
obj.stopOnRelease = YES;
} else {
[obj.soundObject setName:[skin toNSObjectAtIndex:2]];
}
lua_pushvalue(L, 1) ;
} else {
[skin pushNSObject:[obj.soundObject name]] ;
}
return 1;
}
/// hs.sound:device([deviceUID]) -> soundObject | UID string
/// Method
/// Get or set the playback device to use for an `hs.sound` object
///
/// Parameters:
/// * deviceUID - An optional string containing the UID of an `hs.audiodevice` object to use for playback of this sound. Use an explicit nil to use the system's default device
///
/// Returns:
/// * If a parameter is provided, returns the sound object; otherwise returns the current setting.
///
/// Notes:
/// * To obtain the UID of a sound device, see `hs.audiodevice:uid()`
static int sound_device(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING | LS_TNUMBER | LS_TNIL | LS_TOPTIONAL, LS_TBREAK] ;
NSSound *obj = [skin luaObjectAtIndex:1 toClass:"NSSound"] ;
if (lua_gettop(L) == 2) {
if (lua_type(L, 2) == LUA_TNIL) {
[obj setPlaybackDeviceIdentifier:nil] ;
} else {
luaL_checkstring(L, 2) ;
@try {
[obj setPlaybackDeviceIdentifier:[skin toNSObjectAtIndex:2]] ;
} @catch(NSException *theException) {
return luaL_error(L, [[NSString stringWithFormat:@"%@: %@", theException.name,
theException.reason] UTF8String]);
}
}
lua_pushvalue(L, 1) ;
} else {
[skin pushNSObject:[obj playbackDeviceIdentifier]];
}
return 1;
}
/// hs.sound:currentTime([seekTime]) -> soundObject | seconds
/// Method
/// Get or set the current seek offset within an `hs.sound` object.
///
/// Parameters:
/// * seekTime - An optional number of seconds to seek to within the sound object
///
/// Returns:
/// * If a parameter is provided, returns the sound object; otherwise returns the current position.
static int sound_currentTime(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TOPTIONAL, LS_TBREAK] ;
NSSound *obj = [skin luaObjectAtIndex:1 toClass:"NSSound"] ;
if (lua_gettop(L) == 2) {
[obj setCurrentTime:luaL_checknumber(L, 2)];
lua_pushvalue(L, 1) ;
} else {
lua_pushnumber(L, [obj currentTime]);
}
return 1;
}
/// hs.sound:duration() -> seconds
/// Method
/// Gets the length of an `hs.sound` object
///
/// Parameters:
/// * None
///
/// Returns:
/// * A number containing the length of the sound, in seconds
static int sound_duration(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
NSSound *obj = [skin luaObjectAtIndex:1 toClass:"NSSound"] ;
lua_pushnumber(L, [obj duration]);
return 1;
}
/// hs.sound:volume([level]) -> soundObject | number
/// Method
/// Get or set the playback volume of an `hs.sound` object
///
/// Parameters:
/// * level - A number between 0.0 and 1.0, representing the volume of the sound object relative to the current system volume
///
/// Returns:
/// * If a parameter is provided, returns the sound object; otherwise returns the current value.
static int sound_volume(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TOPTIONAL, LS_TBREAK] ;
NSSound *obj = [skin luaObjectAtIndex:1 toClass:"NSSound"] ;
if (lua_gettop(L) == 2) {
[obj setVolume:(float)luaL_checknumber(L, 2)];
lua_pushvalue(L, 1) ;
} else {
lua_pushnumber(L, [obj volume]);
}
return 1;
}
/// hs.sound:isPlaying() -> bool
/// Method
/// Gets the current playback state of an `hs.sound` object
///
/// Parameters:
/// * None
///
/// Returns:
/// * A boolean, true if the sound is currently playing, otherwise false
static int sound_isPlaying(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
NSSound *obj = [skin luaObjectAtIndex:1 toClass:"NSSound"] ;
lua_pushboolean(L, [obj isPlaying]);
return 1;
}
/// hs.sound:setCallback(function) -> soundObject
/// Method
/// Set or remove the callback for receiving completion notification for the sound object.
///
/// Parameters:
/// * function - A function which should be called when the sound completes playing. Specify an explicit nil to remove the callback function.
///
/// Returns:
/// * the sound object
///
/// Notes:
/// * the callback function should accept two parameters and return none. The parameters passed to the callback function are:
/// * state - a boolean flag indicating if the sound completed playing. Returns true if playback completes properly, or false if a decoding error occurs or if the sound is stopped early with `hs.sound:stop`.
/// * sound - the soundObject userdata
static int sound_callback(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TFUNCTION | LS_TNIL, LS_TBREAK] ;
HSSoundObject *obj = [skin luaObjectAtIndex:1 toClass:"HSSoundObject"] ;
// in either case, we need to remove an existing callback, so...
obj.callbackRef = [skin luaUnref:refTable ref:obj.callbackRef];
if (lua_type(L, 2) == LUA_TFUNCTION) {
lua_pushvalue(L, 2);
obj.callbackRef = [skin luaRef:refTable];
if (obj.selfRef == LUA_NOREF) {
lua_pushvalue(L, 1) ;
obj.selfRef = [skin luaRef:refTable];
}
} else {
if (![obj.soundObject isPlaying]) {
obj.selfRef = [skin luaUnref:refTable ref:obj.selfRef];
}
}
lua_pushvalue(L, 1) ;
return 1;
}
#pragma mark - Lua<->NSObject Conversion Functions
// These must not throw a lua error to ensure LuaSkin can safely be used from Objective-C
// delegates and blocks.
// pushes HSSoundObject userdata onto stack, or reuses selfRef, if defined
static int pushHSSoundObject(lua_State *L, id obj) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
HSSoundObject *value = obj ;
if (value.selfRef != LUA_NOREF) {
[skin pushLuaRef:refTable ref:value.selfRef] ;
} else {
void** valuePtr = lua_newuserdata(L, sizeof(HSSoundObject *));
*valuePtr = (__bridge_retained void *)value;
luaL_getmetatable(L, USERDATA_TAG);
lua_setmetatable(L, -2);
}
return 1;
}
// retrieves userdata on stack as HSSoundObject
static id toHSSoundObjectFromLua(lua_State *L, int idx) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
HSSoundObject *value ;
if (luaL_testudata(L, idx, USERDATA_TAG)) {
value = get_objectFromUserdata(__bridge HSSoundObject, L, idx) ;
} else {
[skin logError:[NSString stringWithFormat:@"expected %s object, found %s", USERDATA_TAG,
lua_typename(L, lua_type(L, idx))]] ;
}
return value ;
}
// creates new HSSoundObject from NSSound and pushes userdata onto stack
static int pushNSSound(lua_State *L, id obj) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
HSSoundObject *value = [[HSSoundObject alloc] initWithSound:obj] ;
return [skin pushNSObject:value] ;
}
// retrieves userdata on stack as HSSoundObject, but returns NSSound portion only
static id toNSSoundFromLua(lua_State *L, int idx) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
HSSoundObject *value = [skin luaObjectAtIndex:idx toClass:"HSSoundObject"];
return [value soundObject];
}
#pragma mark - Hammerspoon/Lua Infrastructure
static int userdata_tostring(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
HSSoundObject *obj = [skin luaObjectAtIndex:1 toClass:"HSSoundObject"] ;
NSString *title = [obj.soundObject name] ;
if (!title) title = @"(unnamed sound)" ;
[skin pushNSObject:[NSString stringWithFormat:@"%s: %@ (%p)", USERDATA_TAG, title, lua_topointer(L, 1)]] ;
return 1 ;
}
static int userdata_eq(lua_State* L) {
// can't get here if at least one of us isn't a userdata type, and we only care if both types are ours,
// so use luaL_testudata before the macro causes a lua error
if (luaL_testudata(L, 1, USERDATA_TAG) && luaL_testudata(L, 2, USERDATA_TAG)) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
HSSoundObject *obj1 = [skin luaObjectAtIndex:1 toClass:"HSSoundObject"] ;
HSSoundObject *obj2 = [skin luaObjectAtIndex:2 toClass:"HSSoundObject"] ;
lua_pushboolean(L, [obj1 isEqualTo:obj2]) ;
} else {
lua_pushboolean(L, NO) ;
}
return 1 ;
}
static int userdata_gc(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
// [skin logVerbose:[NSString stringWithFormat:@"%s:__gc", USERDATA_TAG]] ;
HSSoundObject *obj = get_objectFromUserdata(__bridge_transfer HSSoundObject, L, 1) ;
if (obj) {
obj.selfRef = [skin luaUnref:refTable ref:obj.selfRef] ;
obj.callbackRef = [skin luaUnref:refTable ref:obj.callbackRef] ;
[obj.soundObject setDelegate:nil] ;
if (obj.stopOnRelease) [obj.soundObject stop] ;
obj.soundObject = nil ;
obj = nil ;
}
// Remove the Metatable so future use of the variable in Lua won't think its valid
lua_pushnil(L) ;
lua_setmetatable(L, 1) ;
return 0 ;
}
// static int meta_gc(lua_State* __unused L) {
// return 0 ;
// }
// Metatable for userdata objects
static const luaL_Reg userdata_metaLib[] = {
{"play", sound_play},
{"pause", sound_pause},
{"resume", sound_resume},
{"stop", sound_stop},
{"loopSound", sound_loopSound},
{"name", sound_name},
{"volume", sound_volume},
{"currentTime", sound_currentTime},
{"duration", sound_duration},
{"device", sound_device},
{"stopOnReload", sound_stopOnRelease},
{"setCallback", sound_callback},
{"isPlaying", sound_isPlaying},
{"__tostring", userdata_tostring},
{"__eq", userdata_eq},
{"__gc", userdata_gc},
{NULL, NULL}
};
// Functions for returned object when module loads
static luaL_Reg moduleLib[] = {
{"soundTypes", sound_soundUnfilteredTypes},
{"soundFileTypes", sound_soundUnfilteredFileTypes},
{"getByName", sound_byname},
{"getByFile", sound_byfile},
{"systemSounds", sound_systemSounds},
{"getAudioEffectNames", sound_getAudioEffectNames},
{NULL, NULL}
};
// // Metatable for module, if needed
// static const luaL_Reg module_metaLib[] = {
// {"__gc", meta_gc},
// {NULL, NULL}
// };
int luaopen_hs_libsound(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
refTable = [skin registerLibraryWithObject:USERDATA_TAG
functions:moduleLib
metaFunctions:nil // or module_metaLib
objectFunctions:userdata_metaLib];
// pushes HSSoundObject userdata onto stack, or reuses selfRef, if defined
[skin registerPushNSHelper:pushHSSoundObject forClass:"HSSoundObject"];
// retrieves userdata on stack as HSSoundObject
[skin registerLuaObjectHelper:toHSSoundObjectFromLua forClass:"HSSoundObject"];
// creates new HSSoundObject from NSSound and pushes userdata onto stack
[skin registerPushNSHelper:pushNSSound forClass:"NSSound"];
// retrieves userdata on stack as HSSoundObject, but returns NSSound portion only; also
// makes this the default for the USERDATA type, since I doubt there will be much call
// for HSSoundObject outside of this specific module, but may for NSSound in array's, etc.
[skin registerLuaObjectHelper:toNSSoundFromLua forClass:"NSSound" withUserdataMapping:USERDATA_TAG] ;
return 1;
}