PDP-8-E-Simulator/ASR33/ASR33.m

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