📄 drv_mac.c
字号:
/* MikMod sound library (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for complete list. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*//*============================================================================== $Id: drv_mac.c,v 1.7 2004/02/16 17:58:05 raph Exp $ Driver for output to the Macintosh Sound Manager==============================================================================*//* Written by Anders F Bjoerklund <afb@algonet.se> Based on free code by: - Antoine Rosset <RossetAntoine@bluewin.ch> (author of PlayerPRO) - John Stiles <stiles@emulation.net> - Pierre-Olivier Latour <pol@french-touch.net> This code uses two different ways of filling the buffers: - Classic code uses SndPlayDoubleBuffer callbacks - Carbon code uses SndCallBacks with Deferred Tasks Updated by Axel Wefers <awe@fruitz-of-dojo.de>: - changed code for compatibility with ProjectBuilder/OSX: - "NewSndCallBackProc()" to "NewSndCallBackUPP()". - "NewDeferredTaskProc()" to "NewDeferredTaskUPP()". - added some conditionals to avoid compiler warnings. Updated again in 2004 by afb, to fix some bugs: - deadlock in Player_Paused, when using HAVE_PTHREAD (since it is now using the global "vars" MUTEX too) - playback was wrong speed when running under CarbonLib (due to Deferred Tasks having lame latencies there) - proper playing of partially filled buffers too*/#ifdef HAVE_CONFIG_H#include "config.h"#endif#include "mikmod_internals.h"#ifdef DRV_MAC#if defined(__APPLE__) && defined(__MACH__)#include <Carbon/Carbon.h>#elif TARGET_API_MAC_CARBON && (UNIVERSAL_INTERFACES_VERSION >= 0x0335)#include <Carbon.h>#else#include <Sound.h>#include <OSUtils.h>#include <Gestalt.h>#endif#ifndef TARGET_API_MAC_CARBON#define TARGET_API_MAC_CARBON TARGET_CARBON#endif#ifndef TARGET_CPU_68K#define TARGET_CPU_68K GENERATING68K#endif#if TARGET_API_MAC_CARBON#define USE_SNDDOUBLEBUFFER 0#else#define USE_SNDDOUBLEBUFFER 1#endif#if TARGET_API_MAC_CARBON#define USE_DEFERREDTASKS 0#else#define USE_DEFERREDTASKS 1#endif#define SOUND_BUFFER_SIZE 4096Lstatic SndChannelPtr soundChannel = NULL; /* pointer to a sound channel */#if USE_SNDDOUBLEBUFFERstatic SndDoubleBufferHeader doubleHeader; /* pointer to double buffers */#elsestatic SndCallBackUPP sndCallBack = NULL;static ExtSoundHeader sndHeader; /* a sound manager bufferCmd header */static Ptr sndBuffer1 = NULL;static Ptr sndBuffer2 = NULL;static Ptr currentBuffer;static long currentFrames;#if USE_DEFERREDTASKSstatic DeferredTask dtask; /* deferred task record */static volatile Boolean deferredTaskFired = true;static volatile Boolean deferredTaskDone = true;#endif#endif /*USE_SNDDOUBLEBUFFER*/#define FILL_BUFFER(_buffer_data,_buffer_size,_bytes) \ if (Player_Paused()) { /* <afb> note that Player_Paused locks "vars" too ! */ \ MUTEX_LOCK(vars); \ _bytes=VC_SilenceBytes((SBYTE*)_buffer_data,(ULONG)_buffer_size); \ MUTEX_UNLOCK(vars); \ } else { \ MUTEX_LOCK(vars); \ _bytes=VC_WriteBytes((SBYTE*)_buffer_data,(ULONG)_buffer_size); \ MUTEX_UNLOCK(vars); \ }#if USE_SNDDOUBLEBUFFER/* DoubleBackProc, called at interrupt time */static pascal void MyDoubleBackProc(SndChannelPtr channel,SndDoubleBufferPtr doubleBuffer){#ifndef GCC#pragma unused(channel)#endif long written;#if TARGET_CPU_68K long oldA5=SetA5(doubleBuffer->dbUserInfo[0]);#endif FILL_BUFFER( doubleBuffer->dbSoundData, SOUND_BUFFER_SIZE, written ) if (doubleHeader.dbhNumChannels==2) written>>=1; if (doubleHeader.dbhSampleSize==16) written>>=1; doubleBuffer->dbNumFrames=written; doubleBuffer->dbFlags|=dbBufferReady;#if TARGET_CPU_68K SetA5(oldA5);#endif}#else#if USE_DEFERREDTASKS/* DeferredTask, called at almost-interrupt time (not for 68K - doesn't set A5) */static pascal void DeferredTaskCallback(long param){ long written; deferredTaskFired = true; FILL_BUFFER( param, SOUND_BUFFER_SIZE, written ) deferredTaskDone = true;}#endif /* USE_DEFERREDTASKS *//* SoundCallback, called at interrupt time (not for 68K - doesn't set A5) */static pascal void SoundCallback(SndChannelPtr channel, SndCommand *command ){#ifndef GCC#pragma unused(channel,command)#endif SndCommand buffer = { bufferCmd, 0, (long) &sndHeader }; SndCommand callback = { callBackCmd, 0, 0 }; /* Install current buffer */ sndHeader.samplePtr = currentBuffer; sndHeader.numFrames = currentFrames; SndDoImmediate( soundChannel, &buffer );#if USE_DEFERREDTASKS /* Setup deferred task to fill next buffer */ if(deferredTaskFired) { currentBuffer = (currentBuffer == sndBuffer1) ? sndBuffer2 : sndBuffer1; if (currentBuffer != NULL) { deferredTaskFired = false; deferredTaskDone = false; dtask.dtParam = (long) currentBuffer; DTInstall((DeferredTaskPtr) &dtask); } }#else { long bytes; currentBuffer = (currentBuffer == sndBuffer1) ? sndBuffer2 : sndBuffer1; FILL_BUFFER( currentBuffer, SOUND_BUFFER_SIZE, bytes ) if (sndHeader.numChannels == 2) bytes >>= 1; if (sndHeader.sampleSize == 16) bytes >>= 1; currentFrames = bytes; }#endif /* USE_DEFERREDTASKS */ /* Queue next callback */ SndDoCommand( soundChannel, &callback, true );}#endifstatic BOOL MAC_IsThere(void){ NumVersion nVers; nVers=SndSoundManagerVersion(); if (nVers.majorRev>=2) return 1; /* need SoundManager 2.0+ */ else return 0;}static BOOL MAC_Init(void){ OSErr err,iErr; long rate,maxrate,maxbits; long gestaltAnswer; NumVersion nVers; Boolean Stereo,StereoMixing,Audio16; Boolean NewSoundManager,NewSoundManager31;#if USE_SNDDOUBLEBUFFER SndDoubleBufferPtr doubleBuffer; int i; // <AWE> avoids compiler warning.#endif NewSoundManager31=NewSoundManager=false; nVers=SndSoundManagerVersion(); if (nVers.majorRev>=3) { NewSoundManager=true; if (nVers.minorAndBugRev>=0x10) NewSoundManager31=true; } else if (nVers.majorRev<2) return 1; /* failure, need SoundManager 2.0+ */ iErr=Gestalt(gestaltSoundAttr,&gestaltAnswer); if (iErr==noErr) { Stereo=(gestaltAnswer & (1<<gestaltStereoCapability))!=0; StereoMixing=(gestaltAnswer & (1<<gestaltStereoMixing))!=0; Audio16=(gestaltAnswer & (1<<gestalt16BitSoundIO))!=0; } else { /* failure, couldn't get any sound info at all ? */ Stereo=StereoMixing=Audio16=false; }#if !TARGET_CPU_68K || !TARGET_RT_MAC_CFM if (NewSoundManager31) { iErr=GetSoundOutputInfo(0L,siSampleRate,(void*)&maxrate); if (iErr==noErr) iErr=GetSoundOutputInfo(0L,siSampleSize,(void*)&maxbits); } if (iErr!=noErr) {#endif maxrate=rate22khz; if (NewSoundManager && Audio16) maxbits=16; else maxbits=8;#if !TARGET_CPU_68K || !TARGET_RT_MAC_CFM }#endif switch (md_mixfreq) { case 48000:rate=rate48khz;break; case 44100:rate=rate44khz;break; case 22254:rate=rate22khz;break; case 22050:rate=rate22050hz;break; case 11127:rate=rate11khz;break; case 11025:rate=rate11025hz;break; default: rate=0;break; } if (!rate) { _mm_errno=MMERR_MAC_SPEED; return 1; } md_mode|=DMODE_SOFT_MUSIC|DMODE_SOFT_SNDFX; if ((md_mode&DMODE_16BITS)&&(maxbits<16)) md_mode&=~DMODE_16BITS; if (!Stereo || !StereoMixing) md_mode&=~DMODE_STEREO; if (rate>maxrate) rate=maxrate; if (md_mixfreq>(maxrate>>16)) md_mixfreq=maxrate>>16;#if USE_SNDDOUBLEBUFFER err=SndNewChannel(&soundChannel,sampledSynth, (md_mode&DMODE_STEREO)?initStereo:initMono, NULL ); if(err!=noErr) { _mm_errno=MMERR_OPENING_AUDIO; return 1; } doubleHeader.dbhCompressionID=0; doubleHeader.dbhPacketSize =0; doubleHeader.dbhSampleRate =rate; doubleHeader.dbhSampleSize =(md_mode&DMODE_16BITS)?16:8; doubleHeader.dbhNumChannels =(md_mode&DMODE_STEREO)?2:1; doubleHeader.dbhDoubleBack =NewSndDoubleBackProc(&MyDoubleBackProc); for(i=0;i<2;i++) { doubleBuffer=(SndDoubleBufferPtr)NewPtrClear(sizeof(SndDoubleBuffer)+ SOUND_BUFFER_SIZE); if(!doubleBuffer) { _mm_errno=MMERR_OUT_OF_MEMORY; return 1; } doubleBuffer->dbNumFrames=0; doubleBuffer->dbFlags=0; doubleBuffer->dbUserInfo[0]=SetCurrentA5(); doubleBuffer->dbUserInfo[1]=0; doubleHeader.dbhBufferPtr[i]=doubleBuffer; }#else if( sndCallBack == NULL ) sndCallBack = NewSndCallBackUPP( SoundCallback ); // <AWE> was "NewSndCallBackProc()". err=SndNewChannel(&soundChannel,sampledSynth, (md_mode&DMODE_STEREO)?initStereo:initMono, sndCallBack ); if(err!=noErr) { _mm_errno=MMERR_OPENING_AUDIO; return 1; } sndBuffer1 = NewPtrClear(SOUND_BUFFER_SIZE); sndBuffer2 = NewPtrClear(SOUND_BUFFER_SIZE); if (sndBuffer1 == NULL || sndBuffer2 == NULL) { _mm_errno=MMERR_OUT_OF_MEMORY; return 1; } currentBuffer = sndBuffer1; /* Setup sound header */ memset( &sndHeader, 0, sizeof(sndHeader) ); sndHeader.numChannels = (md_mode&DMODE_STEREO)? 2: 1; sndHeader.sampleRate = rate; sndHeader.encode = extSH; sndHeader.baseFrequency = kMiddleC; sndHeader.numFrames = SOUND_BUFFER_SIZE >> (((md_mode&DMODE_STEREO)? 1: 0) + ((md_mode&DMODE_16BITS)?1: 0)); sndHeader.sampleSize = (md_mode&DMODE_16BITS)? 16: 8; sndHeader.samplePtr = currentBuffer; #if USE_DEFERREDTASKS /* Setup deferred task record */ memset( &dtask, 0, sizeof(dtask) ); dtask.qType = dtQType; dtask.dtFlags = 0; dtask.dtAddr = NewDeferredTaskUPP(DeferredTaskCallback); // <AWE> was "NewDeferredTaskProc()". dtask.dtReserved = 0; deferredTaskFired = true;#endif /* USE_DEFERREDTASKS */#endif return VC_Init();}static void MAC_Exit(void){#if USE_SNDDOUBLEBUFFER // <AWE> avoids compiler warning. int i;#else Ptr temp1,temp2;#endif if ( soundChannel != NULL) { SndDisposeChannel(soundChannel,true); // "true" means to flush and quiet soundChannel=NULL; } #if USE_SNDDOUBLEBUFFER DisposeRoutineDescriptor((UniversalProcPtr)doubleHeader.dbhDoubleBack); doubleHeader.dbhDoubleBack=NULL; for(i=0;i<doubleHeader.dbhNumChannels;i++) { DisposePtr((Ptr)doubleHeader.dbhBufferPtr[i]); doubleHeader.dbhBufferPtr[i]=NULL; }#else if ( sndCallBack != NULL) { DisposeSndCallBackUPP(sndCallBack); sndCallBack = NULL; } temp1 = sndBuffer1; sndBuffer1 = NULL; temp2 = sndBuffer2; sndBuffer2 = NULL;#if USE_DEFERREDTASKS // <afb> we can't dispose of the buffers until the DT is done with them while (!deferredTaskDone) ;#endif DisposePtr( temp1 ); DisposePtr( temp2 );#endif VC_Exit();}static BOOL MAC_PlayStart(void){ OSErr err;#if USE_SNDDOUBLEBUFFER MyDoubleBackProc(soundChannel,doubleHeader.dbhBufferPtr[0]); MyDoubleBackProc(soundChannel,doubleHeader.dbhBufferPtr[1]); err=SndPlayDoubleBuffer(soundChannel,&doubleHeader); if(err!=noErr) { _mm_errno=MMERR_MAC_START; return 1; }#else SndCommand callback = { callBackCmd, 0, 0 }; err=SndDoCommand( soundChannel, &callback, true ); if(err!=noErr) { _mm_errno=MMERR_MAC_START; return 1; } #endif return VC_PlayStart();}static void MAC_PlayStop(void){ SndCommand flush = { flushCmd, 0, 0 }; SndCommand quiet = { quietCmd, 0, 0 }; // <afb> IM:Sound says we should issue the flushCmd before the quietCmd. SndDoImmediate(soundChannel,&flush); SndDoImmediate(soundChannel,&quiet); VC_PlayStop();}static void MAC_Update(void){ return;}MIKMODAPI MDRIVER drv_mac={ NULL, "Mac Driver (Carbonized)", "Macintosh Sound Manager Driver v2.1", 0,255, "mac", NULL, NULL, MAC_IsThere, VC_SampleLoad, VC_SampleUnload, VC_SampleSpace, VC_SampleLength, MAC_Init, MAC_Exit, NULL, VC_SetNumVoices, MAC_PlayStart, MAC_PlayStop, MAC_Update, NULL, VC_VoiceSetVolume, VC_VoiceGetVolume, VC_VoiceSetFrequency, VC_VoiceGetFrequency, VC_VoiceSetPanning, VC_VoiceGetPanning, VC_VoicePlay, VC_VoiceStop, VC_VoiceStopped, VC_VoiceGetPosition, VC_VoiceRealVolume};#else /* ifdef DRV_MAC */MISSING(drv_mac);#endif/* ex:set ts=4: */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -