mirror of
https://github.com/DerekK19/PDP-8-E-Simulator.git
synced 2024-10-08 13:55:28 +00:00
485 lines
13 KiB
Objective-C
485 lines
13 KiB
Objective-C
/*
|
|
* PDP-8/E Simulator
|
|
*
|
|
* Copyright © 1994-2015 Bernhard Baehr
|
|
*
|
|
* ASR33.m - ASR 33 Teletype for the PDP-8/E Simulator
|
|
*
|
|
* This file is part of PDP-8/E Simulator.
|
|
*
|
|
* PDP-8/E Simulator is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
#import "PluginFramework/PluginAPI.h"
|
|
#import "PluginFramework/PDP8.h"
|
|
#import "PluginFramework/Utilities.h"
|
|
#import "PluginFramework/FileDropControlTargetProtocol.h"
|
|
#import "PluginFramework/PaperTapeController.h"
|
|
#import "PluginFramework/InputConsumerProtocol.h"
|
|
#import "PluginFramework/NSThread+MainThread.h"
|
|
|
|
#define USE_ASR33_REGISTERS_DIRECTLY 1
|
|
|
|
#import "ASR33.h"
|
|
#import "ASR33TextView.h"
|
|
#import "ASR33WindowController.h"
|
|
#import "ASR33iot.h"
|
|
#import "ASR33Preferences.h"
|
|
#import "TypeaheadBuffer.h"
|
|
|
|
|
|
#define ASR33_CONTTY_PLUGIN_NAME NSLocalizedStringFromTableInBundle( \
|
|
@"ASR 33 Console Teletype.pdp8Plugin", nil, [self bundle], @"")
|
|
#define ASR33_AUXTTY_IO_INFO_FILENAME @"auxtty-io-info"
|
|
|
|
#define CODER_KEY_KBB @"kbb"
|
|
#define CODER_KEY_TTO @"tto"
|
|
#define CODER_KEY_INFLAG @"inflag"
|
|
#define CODER_KEY_INMASK @"inmask"
|
|
#define CODER_KEY_OUTFLAG @"outflag"
|
|
#define CODER_KEY_OUTMASK @"outmask"
|
|
#define CODER_KEY_ONLINE @"online"
|
|
|
|
#define NO_OUTPUT 0
|
|
#define OUTPUT 1
|
|
#define NO_INPUT 0
|
|
#define INPUT 1
|
|
|
|
#define TELETYPE_DELAY 100000 // 100.000 microseconds = 0.1 second
|
|
|
|
#define SOUND_BACKSPACE @"tty-backspace"
|
|
#define SOUND_BELL @"tty-bell"
|
|
#define SOUND_CARRIAGE_RETURN @"tty-carriage-return"
|
|
#define SOUND_KEYSTROKE1 @"tty-keystroke1"
|
|
#define SOUND_KEYSTROKE2 @"tty-keystroke2"
|
|
#define SOUND_KEYSTROKE3 @"tty-keystroke3"
|
|
#define SOUND_KEYSTROKE4 @"tty-keystroke4"
|
|
#define SOUND_SPACE @"tty-space"
|
|
#define SOUND_TYPE @"mp3"
|
|
|
|
|
|
@interface NSSound (SetVolume)
|
|
|
|
- (void) setVolume:(float)volume; // this is a Leopard method, not available with Tiger
|
|
|
|
@end
|
|
|
|
|
|
@implementation ASR33
|
|
|
|
|
|
API_VERSION
|
|
|
|
|
|
#pragma mark Plugin Methods
|
|
|
|
|
|
- (NSString *) ioInformationPlistName
|
|
{
|
|
isConsoleTTY = [[self pluginName] isEqualToString:ASR33_CONTTY_PLUGIN_NAME];
|
|
return isConsoleTTY ? [super ioInformationPlistName] : ASR33_AUXTTY_IO_INFO_FILENAME;
|
|
}
|
|
|
|
|
|
- (NSArray *) iotsForAddress:(int)ioAddress
|
|
{
|
|
if (inAddress == 0) {
|
|
inAddress = ioAddress;
|
|
return [NSArray arrayWithObjects:
|
|
[NSValue valueWithPointer:i6030],
|
|
[NSValue valueWithPointer:i6031],
|
|
[NSValue valueWithPointer:i6032],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:i6034],
|
|
[NSValue valueWithPointer:i6035],
|
|
[NSValue valueWithPointer:i6036],
|
|
[NSValue valueWithPointer:nil],
|
|
nil];
|
|
} else {
|
|
outAddress = ioAddress;
|
|
return [NSArray arrayWithObjects:
|
|
[NSValue valueWithPointer:i6040],
|
|
[NSValue valueWithPointer:i6041],
|
|
[NSValue valueWithPointer:i6042],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:i6044],
|
|
[NSValue valueWithPointer:i6045],
|
|
[NSValue valueWithPointer:i6046],
|
|
[NSValue valueWithPointer:nil],
|
|
nil];
|
|
}
|
|
}
|
|
|
|
|
|
- (NSArray *) skiptestsForAddress:(int)ioAddress
|
|
{
|
|
return ioAddress == inAddress ?
|
|
[NSArray arrayWithObjects:
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:s6031],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:nil],
|
|
nil] :
|
|
[NSArray arrayWithObjects:
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:s6041],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:s6045],
|
|
[NSValue valueWithPointer:nil],
|
|
[NSValue valueWithPointer:nil],
|
|
nil];
|
|
}
|
|
|
|
|
|
- (void) setIOFlag:(unsigned long)flag forIOFlagName:(NSString *)name;
|
|
{
|
|
if (inflag)
|
|
outflag = flag;
|
|
else
|
|
inflag = flag;
|
|
}
|
|
|
|
|
|
- (void) CAF:(int)ioAddress
|
|
{
|
|
if (ioAddress == inAddress) { // CAF is called twice, ignore second call
|
|
KBB = 0;
|
|
TTO = 0;
|
|
[pdp8 setInterruptMaskBits:inflag | outflag];
|
|
[pdp8 clearIOFlagBits:inflag | outflag];
|
|
}
|
|
}
|
|
|
|
|
|
- (void) clearAllFlags:(int)ioAddress
|
|
{
|
|
/* Don't modify the online state - software can't turn the TTY power knob.
|
|
Don't modify the on/off state of the reader/punch (else the user cannot
|
|
load BIN tapes as described in the Small Computer Systems Handbook,
|
|
where he must operate CLEAR/CONT *after* turning on the reader). */
|
|
if (ioAddress == inAddress) { // CAF is called twice, ignore second call
|
|
[self setKBB:0];
|
|
[self setTTO:0];
|
|
[pdp8 setInterruptMaskBits:inflag | outflag];
|
|
[pdp8 clearIOFlagBits:inflag | outflag];
|
|
}
|
|
}
|
|
|
|
|
|
- (void) resetDevice
|
|
{
|
|
[typeaheadBuffer flush:self];
|
|
[self setKBB:0];
|
|
[self setTTO:0];
|
|
[pdp8 setInterruptMaskBits:inflag | outflag];
|
|
[pdp8 clearIOFlagBits:inflag | outflag];
|
|
[self setOnline:YES];
|
|
}
|
|
|
|
|
|
#pragma mark Thread Handling
|
|
|
|
|
|
- (void) playSound:(unsigned short)key
|
|
{
|
|
NSString *soundname;
|
|
|
|
NSAssertRunningOnMainThread ();
|
|
key &= 0177;
|
|
if (playSound || key == '\a') {
|
|
if (isblank(key))
|
|
soundname = SOUND_SPACE;
|
|
else if (key == '\b')
|
|
soundname = SOUND_BACKSPACE;
|
|
else if (key == '\a')
|
|
soundname = SOUND_BELL;
|
|
else if (key == '\r')
|
|
soundname = SOUND_CARRIAGE_RETURN;
|
|
else if (isupper(key))
|
|
soundname = SOUND_KEYSTROKE1;
|
|
else if (islower(key))
|
|
soundname = SOUND_KEYSTROKE2;
|
|
else if (isdigit(key))
|
|
soundname = SOUND_KEYSTROKE3;
|
|
else
|
|
soundname = SOUND_KEYSTROKE4;
|
|
NSSound *sound = [[NSSound alloc] initWithContentsOfFile:
|
|
[[self bundle] pathForResource:soundname ofType:SOUND_TYPE] byReference:YES];
|
|
[sound autorelease];
|
|
if ([sound respondsToSelector:@selector(setVolume:)])
|
|
[sound setVolume:soundVolume];
|
|
[sound play];
|
|
}
|
|
}
|
|
|
|
|
|
- (void) canContinueInput
|
|
{
|
|
if ([inputLock tryLockWhenCondition:NO_INPUT])
|
|
[inputLock unlockWithCondition:INPUT];
|
|
/* else
|
|
User typed so fast that input would be lost, but the typeahead buffer keeps this input. */
|
|
}
|
|
|
|
|
|
- (void) canContinueOutput
|
|
{
|
|
if ([outputLock tryLockWhenCondition:NO_OUTPUT])
|
|
[outputLock unlockWithCondition:OUTPUT];
|
|
#if ! defined(NS_BLOCK_ASSERTIONS)
|
|
else
|
|
NSLog (@"PDP-8 software bug: TPC or TLS executed before preceding TTY output finished");
|
|
#endif
|
|
}
|
|
|
|
|
|
- (void) setTeletypeOutputFlag
|
|
{
|
|
NSAssertRunningOnMainThread ();
|
|
[pdp8 setIOFlagBits:outflag];
|
|
}
|
|
|
|
|
|
- (void) processInput
|
|
{
|
|
NSAssertRunningOnMainThread ();
|
|
input &= 0377; // strip off Unicode characters etc.
|
|
if (online) {
|
|
[self setKBB:input];
|
|
[pdp8 setIOFlagBits:inflag];
|
|
} else {
|
|
[self playSound:input];
|
|
[punch putChar:input & punchMask handleBackspace:YES];
|
|
[textview putChar:input & 0177];
|
|
}
|
|
}
|
|
|
|
|
|
- (void) processOutput
|
|
{
|
|
NSAssertRunningOnMainThread ();
|
|
[self playSound:output];
|
|
[self setTTO:output];
|
|
[punch putChar:output & punchMask handleBackspace:NO];
|
|
[textview putChar:output & 0177];
|
|
}
|
|
|
|
|
|
- (void) getReaderChar
|
|
{
|
|
NSAssertRunningOnMainThread ();
|
|
input = [reader getChar];
|
|
}
|
|
|
|
|
|
- (uint64_t) realtimeDelay:(uint64_t)maTime
|
|
{
|
|
if (runWithRealtimeSpeed) {
|
|
uint64_t us = absolute2nanoseconds(mach_absolute_time() - maTime) / 1000;
|
|
if (us < TELETYPE_DELAY)
|
|
usleep (TELETYPE_DELAY - us);
|
|
maTime = mach_absolute_time();
|
|
}
|
|
return maTime;
|
|
}
|
|
|
|
|
|
- (void) asr33InputThread:(id)object
|
|
{
|
|
[[NSAutoreleasePool alloc] init];
|
|
for (;;) {
|
|
[inputLock lockWhenCondition:INPUT];
|
|
uint64_t maTime = mach_absolute_time();
|
|
while ((! online || [pdp8 getIOFlagBits:inflag] == 0) &&
|
|
([self performSelectorOnMainThread:@selector(getReaderChar)
|
|
withObject:nil waitUntilDone:YES], input != EOF)) {
|
|
maTime = [self realtimeDelay:maTime];
|
|
[self performSelectorOnMainThread:@selector(processInput)
|
|
withObject:nil waitUntilDone:YES];
|
|
}
|
|
while ([typeaheadBuffer hasCharacters] &&
|
|
(! online || [pdp8 isStopped] || [pdp8 getIOFlagBits:inflag] == 0)) {
|
|
input = [typeaheadBuffer getNextChar] | 0200;
|
|
maTime = [self realtimeDelay:maTime];
|
|
[self performSelectorOnMainThread:@selector(processInput)
|
|
withObject:nil waitUntilDone:YES];
|
|
}
|
|
[inputLock unlockWithCondition:NO_INPUT];
|
|
}
|
|
}
|
|
|
|
|
|
- (void) asr33OutputThread:(id)object
|
|
{
|
|
[[NSAutoreleasePool alloc] init];
|
|
[outputOnline lock];
|
|
for (;;) {
|
|
if (! online)
|
|
[outputOnline wait];
|
|
[outputLock lockWhenCondition:OUTPUT];
|
|
uint64_t maTime = mach_absolute_time();
|
|
[self performSelectorOnMainThread:@selector(processOutput)
|
|
withObject:nil waitUntilDone:YES];
|
|
[self realtimeDelay:maTime];
|
|
[outputLock unlockWithCondition:NO_OUTPUT];
|
|
[self performSelectorOnMainThread:@selector(setTeletypeOutputFlag)
|
|
withObject:nil waitUntilDone:YES];
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark Register Access
|
|
|
|
|
|
- (unsigned short) getKBB
|
|
{
|
|
return KBB;
|
|
}
|
|
|
|
|
|
- (void) setKBB:(unsigned short)kbb
|
|
{
|
|
NSAssert1 ((kbb & ~0377) == 0, @"Bad KBB: 0%o", kbb);
|
|
KBB = kbb;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:KBB_CHANGED_NOTIFICATION object:self];
|
|
}
|
|
|
|
|
|
- (unsigned short) getTTO
|
|
{
|
|
return TTO;
|
|
}
|
|
|
|
|
|
- (void) setTTO:(unsigned short)tto
|
|
{
|
|
NSAssert1 ((tto & ~0377) == 0, @"Bad TTO: 0%o", tto);
|
|
TTO = tto;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:TTO_CHANGED_NOTIFICATION object:self];
|
|
}
|
|
|
|
|
|
- (BOOL) getOnline
|
|
{
|
|
return online;
|
|
}
|
|
|
|
|
|
- (void) setOnline:(BOOL)onlineOffline
|
|
{
|
|
online = onlineOffline;
|
|
[outputOnline signal];
|
|
if (! online)
|
|
[self canContinueInput];
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName:TTY_ONLINE_CHANGED_NOTIFICATION object:self];
|
|
}
|
|
|
|
|
|
#pragma mark Initialization
|
|
|
|
|
|
- (id) initWithCoder:(NSCoder *)coder
|
|
{
|
|
self = [super init];
|
|
[self setKBB:[coder decodeIntForKey:CODER_KEY_KBB]];
|
|
[self setTTO:[coder decodeIntForKey:CODER_KEY_TTO]];
|
|
[coder decodeBoolForKey:CODER_KEY_INFLAG] ?
|
|
[pdp8 setIOFlagBits:inflag] : [pdp8 clearIOFlagBits:inflag];
|
|
[coder decodeBoolForKey:CODER_KEY_INMASK] ?
|
|
[pdp8 setInterruptMaskBits:inflag] : [pdp8 clearInterruptMaskBits:inflag];
|
|
[coder decodeBoolForKey:CODER_KEY_OUTFLAG] ?
|
|
[pdp8 setIOFlagBits:outflag] : [pdp8 clearIOFlagBits:outflag];
|
|
[coder decodeBoolForKey:CODER_KEY_OUTMASK] ?
|
|
[pdp8 setInterruptMaskBits:outflag] : [pdp8 clearInterruptMaskBits:outflag];
|
|
[self setOnline:[coder decodeBoolForKey:CODER_KEY_ONLINE]];
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void) encodeWithCoder:(NSCoder *)coder
|
|
{
|
|
[coder encodeInt:[self getKBB] forKey:CODER_KEY_KBB];
|
|
[coder encodeInt:[self getTTO] forKey:CODER_KEY_TTO];
|
|
[coder encodeBool:[pdp8 getIOFlagBits:inflag] ? YES : NO forKey:CODER_KEY_INFLAG];
|
|
[coder encodeBool:[pdp8 getInterruptMaskBits:inflag] ? YES : NO forKey:CODER_KEY_INMASK];
|
|
[coder encodeBool:[pdp8 getIOFlagBits:outflag] ? YES : NO forKey:CODER_KEY_OUTFLAG];
|
|
[coder encodeBool:[pdp8 getInterruptMaskBits:outflag] ? YES : NO forKey:CODER_KEY_OUTMASK];
|
|
[coder encodeBool:[self getOnline] forKey:CODER_KEY_ONLINE];
|
|
}
|
|
|
|
|
|
- (void) notifyApplicationWillTerminate:(NSNotification *)notification
|
|
{
|
|
// NSLog (@"ASR33 notifyApplicationWillTerminate");
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification
|
|
object:nil];
|
|
NSMutableData *data = [NSMutableData data];
|
|
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
|
|
[self encodeWithCoder:archiver];
|
|
[archiver finishEncoding];
|
|
[archiver release];
|
|
[[NSUserDefaults standardUserDefaults] setObject:data forKey:[self pluginName]];
|
|
}
|
|
|
|
|
|
- (void) notifyPreferencesChanged:(NSNotification *)notification
|
|
{
|
|
// NSLog (@"ASR33 notifyPreferencesChanged");
|
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
runWithRealtimeSpeed = [defaults integerForKey:ASR33_PREFS_SPEED_KEY];
|
|
playSound = runWithRealtimeSpeed && [defaults boolForKey:ASR33_PREFS_PLAY_SOUND];
|
|
soundVolume = [defaults objectForKey:ASR33_PREFS_SOUND_VOLUME] ?
|
|
[defaults floatForKey:ASR33_PREFS_SOUND_VOLUME] : 0.5f;
|
|
punchMask = [defaults boolForKey:ASR33_PREFS_MASK_HIGHBIT_KEY] ? 0177 : 0377;
|
|
}
|
|
|
|
|
|
- (void) pluginDidLoad
|
|
{
|
|
NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:[self pluginName]];
|
|
if (data) {
|
|
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
|
|
self = [self initWithCoder:unarchiver];
|
|
[unarchiver finishDecoding];
|
|
[unarchiver release];
|
|
} else
|
|
[self setOnline:YES];
|
|
inputLock = [[NSConditionLock alloc] initWithCondition:NO_INPUT];
|
|
outputLock = [[NSConditionLock alloc] initWithCondition:NO_OUTPUT];
|
|
outputOnline = [[NSCondition alloc] init];
|
|
[NSThread detachNewThreadSelector:@selector(asr33InputThread:) toTarget:self withObject:nil];
|
|
[NSThread detachNewThreadSelector:@selector(asr33OutputThread:) toTarget:self withObject:nil];
|
|
[windowController setWindowTitle:[[self pluginName] stringByDeletingPathExtension]];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(notifyApplicationWillTerminate:)
|
|
name:NSApplicationWillTerminateNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(notifyPreferencesChanged:)
|
|
name:NSUserDefaultsDidChangeNotification object:nil];
|
|
[self notifyPreferencesChanged:nil];
|
|
}
|
|
|
|
|
|
@end
|