📄 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 4096L
static SndChannelPtr soundChannel = NULL; /* pointer to a sound channel */
#if USE_SNDDOUBLEBUFFER
static SndDoubleBufferHeader doubleHeader; /* pointer to double buffers */
#else
static 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_DEFERREDTASKS
static 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 );
}
#endif
static 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 + -