TS3Sound.c

/*
 *  File:       TS3Sound.c
 *
 *  Contents:   Handles some of the sound stuff.
 *
 *  Copyright © 1996 Apple Computer, Inc.
 */
 
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
 
#include <Components.h>
#include <Controls.h>
#include <Dialogs.h>
#include <Sound.h>
#include <SoundComponents.h>
#include <Resources.h>
#include <Timer.h>
#include <ToolUtils.h>
#include <Types.h>
 
#define __USING_OLD_SOUND_H__ 0
#include "SoundSprocket.h"
 
#include "TS3Message.h"
#include "TS3Resource.h"
#include "TS3Sound.h"
#include "TS3Utils.h"
#include "TS3Window.h"
 
static SndChannelPtr        gSoundChannel           = NULL;
static SndListHandle        gSoundResource          = NULL;
static DialogPtr            gSoundDialog            = NULL;
static SSpLocalizationData  gSoundPrev3DInfo;
 
 
static Boolean CheckVersionNumber(
    const NumVersion*       inVersion,
    UInt8                   inMajor,
    UInt8                   inMinor,
    UInt8                   inBug);
 
static WindowMethodPtr Sound_MetaHandler(
    WindowMethod        inMethod);
 
static void Sound_ConsumeEvent(
    WindowPtr           inWindow,
    const EventRecord*  inEvent,
    Boolean*            outConsumed);
 
static void Sound_Update(
    WindowPtr           inWindow);
 
static Boolean EventProc(
    EventRecord     *inEvent);
 
static pascal void Sound_BoxUserItem(
    DialogPtr           inDialog,
    short               inItem);
 
 
/*******************************************************************************
 *  CheckVersionNumber
 *
 *  Returns true if the given version number is compatible with (i. e not older
 *  than) version inMajor.inMinor.inBug.
 ******************************************************************************/
Boolean CheckVersionNumber(
    const NumVersion*       inVersion,
    UInt8                   inMajor,
    UInt8                   inMinor,
    UInt8                   inBug)
{
    if (inVersion->majorRev != inMajor)
    {
        return inVersion->majorRev > inMajor;
    }
    else
    {
        return inVersion->minorAndBugRev >= inMinor << 4 | inBug;
    }
}
 
 
// **************************** GetSSpFilterVersion ****************************
// Finds the manufacturer and version number of the SoundSprocket filter that
// may be installed.  inManufacturer should be the manufacturer code specified
// at the installation time, which may be zero to allow any manufacturer.
// If no error is encountered, outManufacturer is set to the actual manufacturer
// code and outMajorVersion and outMinorVersion are set to the component
// specification level and manufacturer's implementation revision, respectively.
static OSStatus GetSSpFilterVersion(
    OSType                  inManufacturer,
    OSType*                 outManufacturer,
    UInt32*                 outMajorVersion,
    UInt32*                 outMinorVersion)
{
    OSStatus                err;
    ComponentDescription    description;
    Component               componentRef;
    UInt32                  vers;
    
    // Set up the component description
    description.componentType           = kSoundEffectsType;
    description.componentSubType        = kSSpLocalizationSubType;
    description.componentManufacturer   = inManufacturer;
    description.componentFlags          = 0;        
    description.componentFlagsMask      = 0;    
    
    // Find a component matching the description
    componentRef = FindNextComponent(nil, &description);
    if (componentRef == nil)  return couldntGetRequiredComponent;
    
    // Get the component description (for the manufacturer code)
    err = GetComponentInfo(componentRef, &description, nil, nil, nil);
    if (err != noErr)  return err;
    
    // Get the version composite
    vers = (UInt32) GetComponentVersion((ComponentInstance) componentRef);
    
    // Return the results
    *outManufacturer = description.componentManufacturer;
    *outMajorVersion = HiWord(vers);
    *outMinorVersion = LoWord(vers);
    
    return noErr;
}
 
 
/* =============================================================================
 *      Sound_Init (external)
 *
 *  Initializes the sound stuff.
 * ========================================================================== */
void Sound_Init(
    void)
{
    OSStatus            err;
    SoundComponentLink  link;
    NumVersion          version;
    OSType              manufacturer;
    UInt32              majorVersion;
    UInt32              minorVersion;
    
    assert(kFeedbackItem_COUNT == kFeedbackItem_ExpectedCount);
    
    // Check the sound manager version
    version = SndSoundManagerVersion();  //¥¥¥ WARNING ¥¥¥
    //¥ IF YOU CAN'T COMPILE THE PREVIOUS LINE, YOU MUST UPGRADE TO ETO #20 OR
    //¥ EDIT YOUR Sound.h TO MAKE SndSoundManagerVersion RETURN THE TYPE NumVersion.
    
    if (!CheckVersionNumber(&version, 3, 2, 1))
    {
        StopAlert(kAlrtID_SoundMgrVersion, NULL);
        // Note: A normal application would bail on 3D sound here
    }
    
    // Allocate the sound channel
    err = SndNewChannel(&gSoundChannel, sampledSynth, initMono, NULL);
    Message_CheckError(err, "Sound_Init", "SndNewChannel");
    
    assert(gSoundChannel != NULL);
    
    // Install the 3D sound filters
    link.description.componentType          = kSoundEffectsType;
    link.description.componentSubType       = kSSpLocalizationSubType;
    link.description.componentManufacturer  = 0;
    link.description.componentFlags         = 0;        
    link.description.componentFlagsMask     = 0;    
    link.mixerID                            = nil;
    link.linkID                             = nil;
    
    SndSetInfo(gSoundChannel, siPreMixerSoundComponent, &link);
    Message_CheckError(err, "Sound_Init", "SndNewChannel");
    
    // Verify that the right version filter was installed
    err = GetSSpFilterVersion(
            link.description.componentManufacturer,
            &manufacturer,
            &majorVersion,
            &minorVersion);
    
    if (err != noErr)
    {
        // Filter must not be installed
        // Note: A normal application would bail on 3D sound here
        StopAlert(kAlrtID_FilterNotInstalled, NULL);
    }
    else
    {
        // The major version represents the component specification level
        if (majorVersion < 1)
        {
            //¥ if we couldn't handle some old version, we could bail here.
            
            StopAlert(kAlrtID_FilterVersion, NULL);
            // Note: A normal application would bail on 3D sound here
        }
        else
        {
            // The minor version specifies the manufacturer's implementation revision
            
            //¥ could do something here is we needed to handle tweaks for specific
            //¥ manufacturers or versions.
        }
    }
 
    // Grab the dialog
    gSoundDialog = GetNewDialog(kDlogID_Feedback, NULL, (WindowPtr) -1);
    assert(gSoundDialog != NULL);
    
    // Set up our method table
    Window_New(gSoundDialog, Sound_MetaHandler);
    
    // Show the dialog
    ShowWindow(gSoundDialog);
    
    // Initialize the SSpLocalizationData state to garbage
    memset(&gSoundPrev3DInfo, 0x55, sizeof(SSpLocalizationData));
}
 
 
/* =============================================================================
 *      Sound_Exit (external)
 *
 *  Prepares for exit.
 * ========================================================================== */
void Sound_Exit(
    void)
{
    Sound_PlaySilence();
    assert(gSoundResource == NULL);
    
    if (gSoundChannel != NULL)
    {
        SndDisposeChannel(gSoundChannel, true);
        gSoundChannel = NULL;
    }
    
    if (gSoundDialog != NULL)
    {
        DisposeDialog(gSoundDialog);
        gSoundDialog = NULL;
    }
}
 
 
/* =============================================================================
 *      Sound_MetaHandler (internal)
 *
 *  Returns the method function pointer that corresponds to the given ID.
 * ========================================================================== */
WindowMethodPtr Sound_MetaHandler(
    WindowMethod        inMethod)
{
    WindowMethodPtr     result;
    
    result = NULL;
    
    switch (inMethod)
    {
        case kWindowMethod_ConsumeEvent:
            result = (WindowMethodPtr) Sound_ConsumeEvent;
        break;
        
        case kWindowMethod_Update:
            result = (WindowMethodPtr) Sound_Update;
        break;
    }
    
    return result;
}
 
 
/* =============================================================================
 *      Sound_ConsumeEvent (internal)
 *
 *  Called for each event when this is the front window.
 * ========================================================================== */
void Sound_ConsumeEvent(
    WindowPtr           inWindow,
    const EventRecord*  inEvent,
    Boolean*            outConsumed)
{
    #pragma unused (inWindow)
    Boolean             consumed;
    WindowPtr           window;
    short               item;
    
    assert(inEvent != NULL);
    assert(outConsumed != NULL);
    
    consumed = false;
    
    if (inEvent->what == activateEvt)
    {
        // We need to look at the activate event here because it is
        // consumed by IsDialogEvent/DialogSelect below and so never
        // gets to the window stuff
        window = (WindowPtr) inEvent->message;
        
        if (inEvent->modifiers & activeFlag)
        {
            Window_Activate(window);
        }
        else
        {
            Window_Deactivate(window);
        }
    }
        
    // Do dialog stuff
    if (inEvent->what != keyDown || (inEvent->modifiers & cmdKey) == 0)
    {
        consumed = IsDialogEvent(inEvent);
        if (consumed)
        {
            DialogSelect(inEvent, &window, &item);
        }
    }
    
    // Return the result
    *outConsumed = consumed;
}
 
 
/* =============================================================================
 *      Sound_Update (internal)
 *
 *  Updates the contents of the window.
 * ========================================================================== */
void Sound_Update(
    WindowPtr           inWindow)
{
    DrawDialog(inWindow);
}
 
 
/* =============================================================================
 *      Sound_Configure (external)
 *
 *  Stops the current sound, puts up the Configure dialog box, and resumes the
 *  stopped sound.
 * ========================================================================== */
void Sound_Configure(
    void)
{
    OSStatus            err;
    Boolean             playing;
    short               resID;
    ResType             resType;
    Str255              resName;
    
    // Find out which sound to restore
    playing = gSoundResource != NULL;
    
    if (playing)
    {
        GetResInfo((Handle) gSoundResource, &resID, &resType, resName);
    }
    
    // Quiet
    Sound_PlaySilence();
    
    // Configure
    err = SSpConfigureSpeakerSetup(EventProc);
    Message_CheckError(err, "Sound_Configure", "SSpConfigureSpeakerSetup");
    
    // Play it again, Sam
    if (playing)
    {
        Sound_PlayResource(resName);
    }
}
 
 
/* =============================================================================
 *      EventProc (internal)
 *
 *  Called to process events during the 3D sound Configure dialog.  Returns
 *  true if the event was handled (except for update events).
 * ========================================================================== */
Boolean EventProc(
    EventRecord     *inEvent)
{
    WindowPtr               wind;
    
    assert(inEvent != NULL);
    
    if (inEvent->what == updateEvt)
    {
        wind = (WindowPtr) inEvent->message;
        
        if (Window_IsMine(wind))
        {
            SetPort(wind);
            BeginUpdate(wind);
            Window_Update(wind);
            EndUpdate(wind);
        }
    }
    
    return false;
}
 
 
/* =============================================================================
 *      Sound_PlaySilence (external)
 *
 *  Stops any sound that is playing.
 * ========================================================================== */
void Sound_PlaySilence(
    void)
{
    OSStatus            err;
    SndCommand          sndCommand;
    
    sndCommand.cmd = quietCmd;
    sndCommand.param1 = 0;
    sndCommand.param2 = 0;
    err = SndDoImmediate(gSoundChannel, &sndCommand);
    Message_CheckError(err, "Sound_PlaySilence", "SndDoImmediate");
    
    if (gSoundResource != NULL)
    {
        ReleaseResource((Handle) gSoundResource);
        gSoundResource = NULL;
    }
}
 
 
/* =============================================================================
 *      Sound_PlayResource (external)
 *
 *  Plays the 'snd ' resource that has the given name.  Returns true if
 *  successful; returns false if unsuccessful and therefore silence is happening.
 * ========================================================================== */
Boolean Sound_PlayResource(
    Str255              inSndName)
{
    OSStatus            err;
    SndCommand          sndCommand;
    long                offset;
    
    // Silence the sound channel and get rid of the old resource
    Sound_PlaySilence();
    
    // Grab the resource
    gSoundResource = (SndListHandle) GetNamedResource('snd ', inSndName);
    if (gSoundResource == NULL || ResError() != noErr)
    {
        StopAlert(kAlrtID_BadSndLoad, NULL);
        goto bail;
    }
    
    // Lock it down
    HLockHi((Handle) gSoundResource);
    
    // Play it indefinitely
    GetSoundHeaderOffset(gSoundResource, &offset);
    
    sndCommand.cmd = soundCmd;
    sndCommand.param1 = 0;
    sndCommand.param2 = (long) *gSoundResource + offset;
    err = SndDoImmediate(gSoundChannel, &sndCommand);
    Message_CheckError(err, "Sound_PlayResource", "SndDoImmediate");
 
    sndCommand.cmd = freqCmd;
    sndCommand.param1 = 0;
    sndCommand.param2 = 60;
    err = SndDoImmediate(gSoundChannel, &sndCommand);
    Message_CheckError(err, "Sound_PlayResource", "SndDoImmediate");
    
    return true;
 
    // Error exit
bail:
    if (gSoundResource != NULL)
    {
        ReleaseResource((Handle) gSoundResource);
        gSoundResource = NULL;
    }
    
    return false;
}
 
 
/* =============================================================================
 *      Sound_Set3DInfo (external)
 *
 *  Sends the given 3D info to the sound channel.
 * ========================================================================== */
void Sound_Set3DInfo(
    const SSpLocalizationData*  in3DInfo)
{
    OSStatus            err;
    Str255              str;
    UnsignedWide        time;
    
    static UnsignedWide prevTime = {0, 0};
    
    assert(in3DInfo != NULL);
    
    // Change the filter
    err = SndSetInfo(gSoundChannel, siSSpLocalization, (SSpLocalizationData*) in3DInfo);
    Message_CheckError(err, "Sound_Set3DInfo", "SndSetInfo");
    
    // Update the dialog
    Microseconds(&time);
    
    Utils_SetUInt32Field(
        gSoundDialog,
        kFeedbackItem_Updates,
        1000000.0 / (time.lo-prevTime.lo),
        true);
    
    prevTime = time;
    
    if (gSoundPrev3DInfo.cpuLoad != in3DInfo->cpuLoad)
    {
        Utils_SetUInt32Field(
            gSoundDialog,
            kFeedbackItem_CPULoad,
            in3DInfo->cpuLoad,
            true);
        
        gSoundPrev3DInfo.cpuLoad = in3DInfo->cpuLoad;
    }
    
    if (gSoundPrev3DInfo.medium != in3DInfo->medium)
    {
        switch (in3DInfo->medium)
        {
            case kSSpMedium_Air:
                strcpy((char*) str, (char*) "\pkSSpÉAir");
            break;
            
            case kSSpMedium_Water:
                strcpy((char*) str, (char*) "\pkSSpÉWater");
            break;
            
            default:
                sprintf((char*) str, "x<<%lu>>", (unsigned long) in3DInfo->medium);
                str[0] = strlen((char*) str) - 1;
        }
        
        Utils_SetStr255Field(
            gSoundDialog,
            kFeedbackItem_Medium,
            str,
            true);
        
        gSoundPrev3DInfo.medium = in3DInfo->medium;
    }
    
    if (gSoundPrev3DInfo.humidity != in3DInfo->humidity)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_Humidity,
            in3DInfo->humidity,
            true);
        
        gSoundPrev3DInfo.humidity = in3DInfo->humidity;
    }
    
    if (gSoundPrev3DInfo.roomSize != in3DInfo->roomSize)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_RoomSize,
            in3DInfo->roomSize,
            true);
        
        gSoundPrev3DInfo.roomSize = in3DInfo->roomSize;
    }
    
    if (gSoundPrev3DInfo.roomReflectivity != in3DInfo->roomReflectivity)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_RoomReflectivity,
            in3DInfo->roomReflectivity,
            true);
        
        gSoundPrev3DInfo.roomReflectivity = in3DInfo->roomReflectivity;
    }
    
    if (gSoundPrev3DInfo.reverbAttenuation != in3DInfo->reverbAttenuation)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_ReverbAttenuation,
            in3DInfo->reverbAttenuation,
            true);
        
        gSoundPrev3DInfo.reverbAttenuation = in3DInfo->reverbAttenuation;
    }
    
    if (gSoundPrev3DInfo.sourceMode != in3DInfo->sourceMode)
    {
        switch (in3DInfo->sourceMode)
        {
            case kSSpSourceMode_Unfiltered:
                strcpy((char*) str, (char*) "\pkSSpÉUnfiltered");
            break;
            
            case kSSpSourceMode_Localized:
                strcpy((char*) str, (char*) "\pkSSpÉLocalized");
            break;
            
            case kSSpSourceMode_Ambient:
                strcpy((char*) str, (char*) "\pkSSpÉAmbient");
            break;
            
            case kSSpSourceMode_Binaural:
                strcpy((char*) str, (char*) "\pkSSpÉBinaural");
            break;
            
            default:
                sprintf((char*) str, "x<<%lu>>", (unsigned long) in3DInfo->sourceMode);
                str[0] = strlen((char*) str) - 1;
        }
        
        Utils_SetStr255Field(
            gSoundDialog,
            kFeedbackItem_SourceMode,
            str,
            true);
        
        gSoundPrev3DInfo.sourceMode = in3DInfo->sourceMode;
    }
    
    if (gSoundPrev3DInfo.referenceDistance != in3DInfo->referenceDistance)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_ReferenceDistance,
            in3DInfo->referenceDistance,
            true);
        
        gSoundPrev3DInfo.referenceDistance = in3DInfo->referenceDistance;
    }
    
    if (gSoundPrev3DInfo.coneAngleCos != in3DInfo->coneAngleCos)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_ConeAngleCos,
            in3DInfo->coneAngleCos,
            true);
        
        gSoundPrev3DInfo.coneAngleCos = in3DInfo->coneAngleCos;
    }
    
    if (gSoundPrev3DInfo.coneAttenuation != in3DInfo->coneAttenuation)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_ConeAttenuation,
            in3DInfo->coneAttenuation,
            true);
        
        gSoundPrev3DInfo.coneAttenuation = in3DInfo->coneAttenuation;
    }
    
    if (gSoundPrev3DInfo.currentLocation.elevation != in3DInfo->currentLocation.elevation)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_Elevation,
            in3DInfo->currentLocation.elevation,
            true);
        
        gSoundPrev3DInfo.currentLocation.elevation = in3DInfo->currentLocation.elevation;
    }
    
    if (gSoundPrev3DInfo.currentLocation.azimuth != in3DInfo->currentLocation.azimuth)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_Azimuth,
            in3DInfo->currentLocation.azimuth,
            true);
        
        gSoundPrev3DInfo.currentLocation.azimuth = in3DInfo->currentLocation.azimuth;
    }
    
    if (gSoundPrev3DInfo.currentLocation.distance != in3DInfo->currentLocation.distance)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_Distance,
            in3DInfo->currentLocation.distance,
            true);
        
        gSoundPrev3DInfo.currentLocation.distance = in3DInfo->currentLocation.distance;
    }
    
    if (gSoundPrev3DInfo.currentLocation.projectionAngle != in3DInfo->currentLocation.projectionAngle)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_ProjectionAngle,
            in3DInfo->currentLocation.projectionAngle,
            true);
        
        gSoundPrev3DInfo.currentLocation.projectionAngle = in3DInfo->currentLocation.projectionAngle;
    }
    
    if (gSoundPrev3DInfo.currentLocation.sourceVelocity != in3DInfo->currentLocation.sourceVelocity)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_SourceVelocity,
            in3DInfo->currentLocation.sourceVelocity,
            true);
        
        gSoundPrev3DInfo.currentLocation.sourceVelocity = in3DInfo->currentLocation.sourceVelocity;
    }
    
    if (gSoundPrev3DInfo.currentLocation.listenerVelocity != in3DInfo->currentLocation.listenerVelocity)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_ListenerVelocity,
            in3DInfo->currentLocation.listenerVelocity,
            true);
        
        gSoundPrev3DInfo.currentLocation.listenerVelocity = in3DInfo->currentLocation.listenerVelocity;
    }
    
    if (gSoundPrev3DInfo.reserved0 != in3DInfo->reserved0)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_Reserved0,
            in3DInfo->reserved0,
            true);
        
        gSoundPrev3DInfo.reserved0 = in3DInfo->reserved0;
    }
    
    if (gSoundPrev3DInfo.reserved1 != in3DInfo->reserved1)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_Reserved1,
            in3DInfo->reserved1,
            true);
        
        gSoundPrev3DInfo.reserved1 = in3DInfo->reserved1;
    }
    
    if (gSoundPrev3DInfo.reserved2 != in3DInfo->reserved2)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_Reserved2,
            in3DInfo->reserved2,
            true);
        
        gSoundPrev3DInfo.reserved2 = in3DInfo->reserved2;
    }
    
    if (gSoundPrev3DInfo.reserved3 != in3DInfo->reserved3)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_Reserved3,
            in3DInfo->reserved3,
            true);
        
        gSoundPrev3DInfo.reserved3 = in3DInfo->reserved3;
    }
    
    if (gSoundPrev3DInfo.virtualSourceCount != in3DInfo->virtualSourceCount)
    {
        Utils_SetFloatField(
            gSoundDialog,
            kFeedbackItem_VirtualSourceCount,
            in3DInfo->virtualSourceCount,
            true);
        
        gSoundPrev3DInfo.virtualSourceCount = in3DInfo->virtualSourceCount;
    }
}