2018-5-18 : Now available on SourceForge: https://sourceforge.net/projects/volume-controls-cmdline/files/
bll 2017-2-16: The Swift version does not work with tclkits due to incorrect linkage with the tclStub library.
Example Usage
load [file join [pwd] macvolume[info sharedlibextension]] set vol [macvolume] ; # get the volume incr vol -10 set newvol [macvolume $vol] ; # set the volume, always returns the current volume.
mkvol.sh
#!/bin/bash set -x lpath=/Users/bll/tcl/build/tcl/Deployment ipath=/Users/bll/tcl/build/tcl/Tcl.framework/Versions/8.6/Headers tcllib=libtclstub8.6.a tcllibnm=tclstub8.6 clang \ -mmacosx-version-min=10.9 \ -framework Cocoa \ -framework AudioToolbox \ -o macvolume \ main.m volume.m clang \ -mmacosx-version-min=10.9 \ -framework Cocoa \ -framework AudioToolbox \ -shared -fPIC \ -o macvolume.dylib \ -DUSE_TCL_STUBS \ -I$ipath \ volume.m tclmacvol.m \ -L$lpath -l$tcllibnm
volume.m
#import "AudioToolbox/AudioServices.h" #import "Foundation/NSObject.h" #include <stdio.h> #include <stdlib.h> #include <MacTypes.h> /* * has objective-c code to get all output devices... * http://stackoverflow.com/questions/11347989/get-built-in-output-from-core-audio */ int macvolume_cmd (int set, int vol) { AudioDeviceID outputDeviceID; UInt32 outputDeviceIDSize = sizeof (outputDeviceID); OSStatus status; AudioObjectPropertyAddress propertyAOPA; Float32 volume; UInt32 volumeSize = sizeof (volume); int ivol; propertyAOPA.mSelector = kAudioHardwarePropertyDefaultOutputDevice; propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal; propertyAOPA.mElement = kAudioObjectPropertyElementMaster; status = AudioHardwareServiceGetPropertyData( kAudioObjectSystemObject, &propertyAOPA, 0, NULL, &outputDeviceIDSize, &outputDeviceID); if (outputDeviceID == kAudioObjectUnknown) { return 0; } propertyAOPA.mSelector = kAudioHardwareServiceDeviceProperty_VirtualMasterVolume; propertyAOPA.mScope = kAudioDevicePropertyScopeOutput; propertyAOPA.mElement = kAudioObjectPropertyElementMaster; if (set == 1) { volume = (Float32) vol / 100.0; AudioHardwareServiceSetPropertyData( outputDeviceID, &propertyAOPA, 0, NULL, volumeSize, &volume); } AudioHardwareServiceGetPropertyData( outputDeviceID, &propertyAOPA, 0, NULL, &volumeSize, &volume); ivol = (int) round(volume*100.0); return ivol; }
main.m
#include <stdio.h> #include <stdlib.h> extern int macvolume_cmd (int, int); int main (int argc, const char * argv[]) { int set = 0; int vol = 0; if (argc > 1) { set = 1; vol = atoi (argv[1]); } printf ("%d\n", macvolume_cmd(set, vol)); }
tclmacvol.m
#include <stdlib.h> #include <tcl.h> extern int macvolume_cmd (int, int); int Macvolume_Cmd ( ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[] ) { int vol = 0; int rvol; int rc = 0; int set = 0; if (objc != 1 && objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, NULL); return TCL_ERROR; } if (objc == 2) { rc = Tcl_GetIntFromObj (interp, objv[1], &vol); if (rc != TCL_OK) { return TCL_ERROR; } set = 1; } rvol = macvolume_cmd (set, vol); Tcl_SetObjResult(interp, Tcl_NewIntObj(rvol)); return TCL_OK; } int Macvolume_Init (Tcl_Interp *interp) { if (! Tcl_InitStubs(interp, TCL_VERSION, 0)) { return TCL_ERROR; } Tcl_CreateObjCommand (interp, "macvolume", Macvolume_Cmd, NULL, NULL); Tcl_PkgProvide (interp, "macvolume", "0.1"); return TCL_OK; }
This version does not work with a tclkit due to incorrect linkage with the stub library.
mkvol.sh
#!/bin/bash set -x lpath=/Users/bll/tcl/build/tcl/Deployment ipath=/Users/bll/tcl/build/tcl/Tcl.framework/Versions/8.6/Headers tcllib=libtclstub8.6.a tcllibnm=tclstub8.6 swiftc -O -o macvolume volume.swift main.swift swiftc -O -emit-library \ -Xcc -DUSE_TCL_STUBS \ -I$ipath -import-objc-header tclbridge.h \ volume.swift tclmacvol.swift \ -Xlinker -undefined -Xlinker dynamic_lookup \ -Xlinker -L$lpath \ -l$tcllibnm \ -o macvolume.dylib
volume.swift
import AudioToolbox func macvolume_cmd (set: Int32, vol: Int32) -> Int32 { var defaultOutputDeviceID = AudioDeviceID(0) var defaultOutputDeviceIDSize = UInt32(MemoryLayout.size(ofValue:defaultOutputDeviceID)) var getDefaultOutputDevicePropertyAddress = AudioObjectPropertyAddress( mSelector: AudioObjectPropertySelector(kAudioHardwarePropertyDefaultOutputDevice), mScope: AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal), mElement: AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)) AudioObjectGetPropertyData( AudioObjectID(kAudioObjectSystemObject), &getDefaultOutputDevicePropertyAddress, 0, nil, &defaultOutputDeviceIDSize, &defaultOutputDeviceID) var volume = Float32() var volumeSize = UInt32(MemoryLayout.size(ofValue:volume)) var volumePropertyAddress = AudioObjectPropertyAddress( mSelector: AudioObjectPropertySelector(kAudioHardwareServiceDeviceProperty_VirtualMasterVolume), mScope: AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput), mElement: AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)) if (set == 1) { volume = Float(vol) / 100.0 AudioHardwareServiceSetPropertyData( defaultOutputDeviceID, &volumePropertyAddress, 0, nil, volumeSize, &volume) } AudioHardwareServiceGetPropertyData( defaultOutputDeviceID, &volumePropertyAddress, 0, nil, &volumeSize, &volume) let ivol = Int32(round(volume*100.0)) return ivol }
main.swift
import Foundation var vol = Int32(0) var set = Int32(0) if (CommandLine.argc > 1) { vol = Int32((CommandLine.arguments[1] as NSString).integerValue) set = 1 } var rvol = Int32() rvol = macvolume_cmd (set:set, vol:vol) print ("\(rvol)");
tclbridge.h
#include "tcl.h"
tclmacvol.swift
func Macvolume_Cmd(cdata: ClientData?, interp: UnsafeMutablePointer<Tcl_Interp>?, objc: Int32, objv: UnsafePointer<UnsafeMutablePointer<Tcl_Obj>?>?) -> Int32 { var vol = Int32(0) var rc = Int32(0) var set = Int32(0) if (objc != 1 && objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, nil) return TCL_ERROR } if (objc == 2) { rc = Tcl_GetIntFromObj (interp, objv![1], &vol); if (rc == 0) { } // clear compiler warning set = 1 } var rvol = Int32()link problems. rvol = macvolume_cmd (set:set, vol:vol) Tcl_SetObjResult(interp, Tcl_NewIntObj(rvol)) return TCL_OK } @_cdecl("Macvolume_Init") public func Macvolume_Init( interp: UnsafeMutablePointer<Tcl_Interp>) -> Int32 { if Tcl_InitStubs(interp, TCL_VERSION, 0) == nil { return TCL_ERROR; } Tcl_CreateObjCommand(interp, "::macvolume", Macvolume_Cmd, nil, nil); Tcl_PkgProvide(interp, "macvolume", "0.1"); return TCL_OK }