Catakig/Source/Cocoa/MyDocument/Audio.m

293 lines
7.3 KiB
Objective-C

#import "Catakig-Cocoa.h"
#import "MyDocument.h"
#import "ScreenView.h"
#import "IndicatorLight.h"
@implementation MyDocument (Audio)
//---------------------------------------------------------------------------
enum
{
kSampleRate = 22050,
kNumSpeeds = 4,
kSpeedMask = kNumSpeeds - 1,
kBytesPerChannel = 1,
kUnsigned = YES,
kFormatFlags = 0,
#if 0
kFormatFlags2 = kAudioFormatFlagIsSignedInteger
#if __BIG_ENDIAN__
| kAudioFormatFlagIsBigEndian
#endif
| kAudioFormatFlagIsNonInterleaved
| kAudioFormatFlagIsPacked,
#endif
};
//---------------------------------------------------------------------------
#if 0
static OSStatus InputProc0(
ScreenView* screen,
AudioUnitRenderActionFlags* ioActionFlags,
const AudioTimeStamp* timeStamp,
UInt32 busNumber,
UInt32 nFrames,
AudioBufferList* ioData)
{
*ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;
memset(ioData->mBuffers[0].mData, 0x80, nFrames);
return noErr;
}
#endif
//---------------------------------------------------------------------------
static OSStatus InputProc12(
ScreenView* screen,
AudioUnitRenderActionFlags* ioActionFlags,
const AudioTimeStamp* timeStamp,
UInt32 busNumber,
UInt32 nFrames,
AudioBufferList* ioData)
{/*
Called during normal-speed and double-speed emulation.
*/
uint8_t* data = ioData->mBuffers[0].mData;
#if 0
static BOOL printed;
if (not printed)
{
printed = YES;
NSLog(@"%d %d %ld %ld",
ioData->mNumberBuffers,
ioData->mBuffers[0].mNumberChannels,
ioData->mBuffers[0].mDataByteSize,
numberFrames);
}
#endif
screen->mRunForOneStep(screen->mA2, nil, data);
#if 0
if (nFrames == 368)
{
for (int i = 91; --i >= 0;)
{
(data+276)[i] = (data+273)[i];
(data+184)[i] = (data+182)[i];
(data+ 92)[i] = (data+ 91)[i];
data[183] = data[182];
data[275] = data[274];
data[367] = data[366];
}
}
#endif
return noErr;
}
//---------------------------------------------------------------------------
static OSStatus InputProc3(
ScreenView* screen,
AudioUnitRenderActionFlags* ioActionFlags,
const AudioTimeStamp* timeStamp,
UInt32 busNumber,
UInt32 nFrames,
AudioBufferList* ioData)
{/*
Called during 6x speed emulation. The playback rate is double the
norm, and we call the Apple II emulator 3 times in this callback.
*/
A2Computer* a2 = screen->mA2;
uint8_t* data = ioData->mBuffers[0].mData;
screen->mRunForOneStep(a2, nil, data);
screen->mRunForOneStep(a2, nil, data);
screen->mRunForOneStep(a2, nil, data);
return noErr;
}
//---------------------------------------------------------------------------
static struct
{
void* inputProc;
UInt32 sampleRate,
bufFrameSize;
}
gSpeedInfo[kNumSpeeds] =
{
{0},
{InputProc12, kSampleRate, kA2SamplesPerStep*2},
{InputProc12, kSampleRate*2, kA2SamplesPerStep},
{InputProc3, kSampleRate*2, kA2SamplesPerStep},
};
//---------------------------------------------------------------------------
- (OSStatus)_PrepareAudioUnit:(int)speed
{
AURenderCallbackStruct input =
{
.inputProcRefCon = mScreen,
.inputProc = (AURenderCallback)
(gSpeedInfo[speed].inputProc),
};
AudioStreamBasicDescription format =
{
.mSampleRate = gSpeedInfo[speed].sampleRate,
.mFormatID = kAudioFormatLinearPCM,
.mFormatFlags = kFormatFlags,
.mFramesPerPacket = 1, // must be 1 for uncompressed data
.mChannelsPerFrame = 1, // or 2 for stereo??
.mBitsPerChannel = kBytesPerChannel * 8,
.mBytesPerFrame = kBytesPerChannel,
.mBytesPerPacket = kBytesPerChannel,
};
OSStatus sts;
sts = AU_SetBufferFrameSize(G.audioUnit,
gSpeedInfo[speed].bufFrameSize);
if (sts != noErr)
return sts;
sts = AudioUnitSetProperty(G.audioUnit,
kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
0, &format, sizeof(format));
if (sts != noErr)
return sts;
sts = AudioUnitSetProperty(G.audioUnit,
kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
0, &input, sizeof(input));
if (sts != noErr)
return sts;
// kAudioUnitProperty_MaximumFramesPerSlice (UInt32)
// kAudioUnitProperty_SetExternalBuffer (AudioUnitExternalBuffer)
return noErr;
}
//---------------------------------------------------------------------------
- (void)_SetRunState:(int)newState
{/*
Sets the run-state of this document to a new value. The run state is
an integer: a combination of the user-selected emulation speed (lower
2 bits) and the pause level (remaining upper bits). Emulation occurs
only when the run-state value is greater than zero.
*/
int speed = newState & kSpeedMask;
OSStatus sts;
[mSpeedLight setIntValue:speed];
sts = AudioOutputUnitStop(G.audioUnit);
if ((mRunState = newState) > 0)
{
sts = [self _PrepareAudioUnit:speed];
sts = AudioOutputUnitStart(G.audioUnit);
}
}
//---------------------------------------------------------------------------
- (void)awakeFromNib
{
NSWindow* mainWindow = [mScreen window];
// NSLog(@"doc window key? %c", "ny"[[mainWindow isKeyWindow]]); //!!
[mModelEmblem setStringValue:[mA2 ModelName]];
[self setHasUndoManager:NO];
[self _SetRunState:(1 - kNumSpeeds)];
[mScreen setNextResponder:mA2];
[mA2 setNextResponder:mainWindow];
[mainWindow useOptimizedDrawing:YES];
// [mainWindow setBackgroundColor:[NSColor blackColor]];
// [mainWindow setAcceptsMouseMovedEvents:YES];
// [mainWindow setResizeIncrements:NSMakeSize(0, 100)];
}
//---------------------------------------------------------------------------
- (IBAction)HitSpeedControl:(id)sender
{/*
Responds to the user invoking one of the speed control commands.
*/
[self _SetRunState:( mRunState & ~kSpeedMask | [sender tag] )];
}
//---------------------------------------------------------------------------
- (void)windowDidResignKey:(NSNotification*)note
{
G.activeScreen = nil;
[self Pause];
[ScreenView FullScreenOff];
}
//---------------------------------------------------------------------------
- (void)windowDidBecomeKey:(NSNotification*)note
{
[self Unpause];
G.activeScreen = mScreen;
}
//---------------------------------------------------------------------------
- (void)windowWillMiniaturize:(NSNotification*)note
{/*
Called when this window is about to be miniturized and put in the Dock.
Here we make sure that the window's dock image looks like its content.
We must do this ourselves because NSOpenGLViews don't co-operate with
Quartz.
Calling '-setOpaque' is required to make the Quartz underlay and the
window shadow appear correctly. We restore the opaque-ness property
to YES in '-windowDidMiniaturize'.
*/
NSWindow* window = [note object];
[self Pause];
[mScreen PrepareToMiniaturize];
[window setOpaque:NO];
}
//---------------------------------------------------------------------------
- (void)Pause
{ [self _SetRunState:(mRunState - kNumSpeeds)]; }
- (void)Unpause
{ if (mRunState < 0) [self _SetRunState:(mRunState + kNumSpeeds)]; }
- (BOOL)IsRunning
{ return mRunState > 0; }
- (void)windowWillBeginSheet:(NSNotification*)note
{ [self Pause]; }
- (void)windowDidEndSheet:(NSNotification*)note
{ [self Unpause]; }
- (void)windowWillClose:(NSNotification*)note
{ [self windowDidResignKey:note]; }
- (void)windowDidMiniaturize:(NSNotification*)note
{ [[note object] setOpaque:YES]; [self Unpause]; }
// see '-windowWillMiniaturize'
- (BOOL)keepBackupFile
{ return G.prefs.keepBackupFiles; }
//---------------------------------------------------------------------------
@end