Version 2 of Mac OS X: volume control

Updated 2017-02-16 13:30:54 by bll

Mac OS X: volume control

bll 2017-2-16: This was a pain to figure out...and it is not quite working.

When using a tclkit I built (rkeene's). Using the MacPorts tclsh works fine.

% load macvolume.dylib
dlopen(macvolume.dylib, 6): Symbol not found: _Tcl_CreateObjCommand
  Referenced from: macvolume.dylib
  Expected in: flat namespace
 in macvolume.dylib
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
swiftc -O -o macvolume volume.swift main.swift
swiftc -O -emit-library \
    -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()
  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
}