ftp.nice.ch/pub/next/graphics/video/VideoTeXT.1.1a.N.bs.tar.gz#/VideoTeXT1.1a/Quelltexte/PageSupplier.m

This is PageSupplier.m in view mode; [Download] [Up]

/* (c) 1992 Dirk Schwarzhans, Matthias Braun
   Use under the terms of the GNU General Public License */
   
#import "PageSupplier.h"

#import <appkit/Application.h>
#import <soundkit/soundkit.h>
#import <mach_error.h>
#import <sys/message.h>
#import <sound/sound.h>
#import <sound/sounddriver.h>
#import <string.h>
#import <appkit/Panel.h>

/* Host-Messages and den DSP (ACHTUNG: muû mit dsp.asm öbereinstimmen) */
#define HM_REQ_PAGE		0x200000	// setzt Anforderungsdaten
#define HM_DISP_CNTL	0x210000	// setzt die Display-Control-Register
#define HM_DISP_MODE	0x220000	// setzt das Display-Modus-Register
#define HM_DSP_STOP		0x230000	// Arbeit einstellen
#define HM_SYS_CONFIG	0x240000	// Messages und Uhrzeit schalten
#define	HM_MODE			0x250000	// Modus-Register Bits 2:0 schreiben

#define DMA_BUFFER_SIZE 1024		// in Bytes
#define LOW_WATER_MARK 4 * 1024		// Konfiguration des šbertragungskanals
#define HIGH_WATER_MARK 8 * 1024	//   vom DSP
#define READ_WIDTH 1				// Bytes pro Sample

#define BIN2ASC(a) (((a) < 10) ? ((a) + '0') : ((a) + 'A' - 10))

// die folgende Struktur nimmt die Daten för jedes Fenster auf
typedef struct
{
	VTPageNumber	page;			// angeforderte Seite
	VTSubpageNumber	subpage;		// und Unterseite
	BOOL			hold;			// Flag, ob Fenster angehalten
	BOOL			careSubpage;	// Flag, ob bestimmte Unterseite angef.
} windowInfo;

extern const char *NXArgv[];

static void dspThread(PageSupplier *self);
static void comThread(PageSupplier *self);
static void dataHandler(PageSupplier *self, int tag, unsigned char *data, int n);
static void msgHandler(PageSupplier *self, int *data, int n);

@implementation PageSupplier

// lokale Methoden

// holt das Storage-Objekt einer Seite; legt es notfalls an, wenn gefordert
- (Storage *)dataOfPage:(VTPageNumber)page subpage:(VTSubpageNumber)subpage
  create:(BOOL)flag
{
	void *key;
	HashTable *subpages;
	Storage *pageContainer;

	key = *((void **)&page);	// Umwandlung för HashTable erforderlich
	subpages = [allPages valueForKey:key];
	if (subpages == nil && flag)
	{
		// Tabelle mit allen Unterseiten dieser Seite neu anlegen
		subpages = [[HashTable alloc] initKeyDesc:"i" valueDesc:"@"];
		[allPages insertKey:key value:subpages];
	}
	
	// möglicherweise ist subpages nil, was aber nichts ausmacht
	key = *((void **)&subpage);
	pageContainer = [subpages valueForKey:key];
	if (pageContainer == nil && flag)
	{
		// Unterseitenspeicher neu anlegen
		pageContainer = [[Storage alloc] initCount:1 elementSize:1024
						 description:"[1024c]"];
		[subpages insertKey:key value:pageContainer];
	}
	
	return pageContainer;
}

// holt von einer gegebenen Seite die höchste Unterseite unterhalb einer
// öbergebenen Unterseitennummer; gibt bei Nichtvorhandensein nil zuröck
- (Storage *)greatestSubpageOf:(VTPageNumber)page
				beforeSubpage:(VTSubpageNumber)margin
				newSubpage:(VTSubpageNumber *)new
{
	NXHashState state;
	VTSubpageNumber maxSubpage, subpage;
	HashTable *subpages;
	Storage *maxPageContainer,*pageContainer;
	
	subpages = [allPages valueForKey:*((void **)&page)];
	maxSubpage = (VTSubpageNumber)0;
	maxPageContainer = nil;
	state = [subpages initState];
	while ([subpages nextState:&state key:(void *)&subpage
										value:(void *)&pageContainer])
	{
		if (subpage < margin && subpage >= maxSubpage)
		{
			// bessere Seite gefunden
			maxSubpage = subpage;
			maxPageContainer = pageContainer;
		}
	}
	if (new != NULL && maxPageContainer != nil)
		*new = maxSubpage;
	return maxPageContainer;
}

// holt von einer gegebenen Seite die kleinste Unterseite oberhalb einer
// öbergebenen Unterseitennummer; gibt bei Nichtvorhandensein nil zuröck
- (Storage *)smallestSubpageOf:(VTPageNumber)page
				afterSubpage:(VTSubpageNumber)margin
				newSubpage:(VTSubpageNumber *)new
{
	NXHashState state;
	VTSubpageNumber minSubpage, subpage;
	HashTable *subpages;
	Storage *minPageContainer,*pageContainer;
	
	subpages = [allPages valueForKey:*((void **)&page)];
	minSubpage = (VTSubpageNumber)0x00ffffff;
	minPageContainer = nil;
	state = [subpages initState];
	while ([subpages nextState:&state key:(void *)&subpage
										value:(void *)&pageContainer])
	{
		if (subpage > margin && subpage <= minSubpage)
		{
			// bessere Seite gefunden
			minSubpage = subpage;
			minPageContainer = pageContainer;
		}
	}
	if (new != NULL && minPageContainer != nil)
		*new = minSubpage;
	return minPageContainer;
}

// öberpröft, ob eine Seite gesendet werden kann und tut dies gegebenfalls
// wenn das nicht möglich sein sollte, wird die laufende Seitennummer eing.
- sendPageIfPossible
{
	Storage *pageContainer;
	windowInfo *info;

	if ((info = [(Storage *)[windowTable valueForKey:actualWindow] 
				 elementAt:0]) != NULL)
	{
		if (info->hold)
			[self requestPageMessages:NO];
		else
		{
			pageContainer = [self dataOfPage:info->page subpage:info->subpage
								create:NO];
			if (pageContainer == nil && !info->careSubpage)
			{	// möglicherweise andere Unterseite finden
				pageContainer = [self smallestSubpageOf:info->page
								 afterSubpage:(VTSubpageNumber)0
								 newSubpage:&info->subpage];
			}
			if (pageContainer != nil)
			{
				mutex_lock(self->comMem);
				self->pagePacket.data = [pageContainer elementAt:0];
				self->pagePacket.window = actualWindow;
				self->pagePacket.tag = tag;
				self->pageReady = YES;
				mutex_unlock(self->comMem);
				condition_signal(self->comMemChanged);
			}
			else
				[self requestPageMessages:YES];
		}
	}
	
	return self;
}

- init
{
	return [self initPort:PORT_NULL];
}

- initPort:(port_t)port
{
	int				soundError, protocol;
	Sound			*dspCode;
	BOOL			retry;

	dev_port=0;
	owner_port=0;

	do 
	{
		retry = NO;
		soundError = SNDAcquire(SND_ACCESS_DSP, 0, 0, 0, NULL_NEGOTIATION_FUN,
								0, &dev_port, &owner_port);
								
		if (soundError != SND_ERR_NONE)
		{
			retry = (NXRunAlertPanel(NULL, "DSP schon belegt.",
					"Nochmal", "Abbruch", NULL) == NX_ALERTDEFAULT);
			if (!retry)
				return nil;
		}
	}while(retry);

	snddriver_get_dsp_cmd_port(dev_port, owner_port, &cmd_port);
	port_allocate(task_self(), &reply_port);

	protocol = SNDDRIVER_DSP_PROTO_RAW;
	snddriver_stream_setup(dev_port, owner_port, 
						   SNDDRIVER_DMA_STREAM_FROM_DSP,
				 		   DMA_BUFFER_SIZE, READ_WIDTH,
						   LOW_WATER_MARK, HIGH_WATER_MARK,
				 		   &protocol, &read_port);
   
	snddriver_dsp_protocol(dev_port, owner_port, protocol);

	dspCode = [Sound newFromMachO:"DSP.snd"];

	soundError = SNDBootDSP(dev_port, owner_port, [dspCode soundStruct]);
	[dspCode free];

	if (soundError != SND_ERR_NONE)
	{
		NXRunAlertPanel(NULL, "DSP-Fehler: konnte nicht booten!",
						NULL, NULL, NULL);
		return nil;
	}
	
	DSPThreadAborted = threadsAborted = NO;
	strcpy(clockString,"12345678");
	strcpy(pageString,"123");
	interruptedSequenz = NO;
	allPages = [[HashTable alloc] initKeyDesc:"i" valueDesc:"@"];
	windowTable = [[HashTable alloc] initKeyDesc:"@" valueDesc:"@"];
	actualWindow = nil;
	tag = 0;
	clockMessages = pageMessages = NO;
	
	depositFull = NO;
	depositValid = NO;
	deposit = [[Storage alloc] initCount:1 elementSize:1024
			   description:"[1024c]"];
	VTNumbersFromInt(&(VTPageNumber)depositPage, 
					 &(VTSubpageNumber)depositSubpage, 0, 0);
	memoryInUse = mutex_alloc();
	comMem = mutex_alloc();
	comMemChanged = condition_alloc();
	sending = mutex_alloc();
	pageReady = clockReady = pageNumReady = error = NO;
	errorString = NULL;

	port_allocate(task_self(), &speakerReplyPort);
	speaker = [[VTSpeaker alloc] init];
	[speaker setSendPort:port];
	[speaker setReplyPort:speakerReplyPort];

	cthread_detach(cthread_fork((cthread_fn_t)dspThread, (any_t)self));
	cthread_detach(cthread_fork((cthread_fn_t)comThread, (any_t)self));
	
	return self;
}

- stopThreads
{
	int	hostMsg;

	hostMsg = HM_DSP_STOP;
	mutex_lock(sending);
	snddriver_dsp_write(cmd_port, &hostMsg, 1, sizeof(int),
						SNDDRIVER_HIGH_PRIORITY);
	mutex_unlock(sending);
		
	return self;
}

- free
{
	if (!threadsAborted)
		return self;

	SNDRelease(SND_ACCESS_DSP, dev_port, owner_port);	// Ports freigeben ???
	port_deallocate(task_self(), read_port);
	port_deallocate(task_self(), reply_port);
	port_deallocate(task_self(), cmd_port);

	[windowTable freeObjects];
	[windowTable free];
	[self forgetAll];
	[allPages free];
	[deposit free];
	[speaker free];
	port_deallocate(task_self(), speakerReplyPort);
	
	mutex_free(memoryInUse);
	mutex_free(comMem);
	condition_free(comMemChanged);
	mutex_free(sending);

	return [super free];
}

// löscht den gesamten Seitenspeicher und fordert die aktuelle Seite neu an
- (unsigned)forgetAll
{
	NXHashState state;
	VTPageNumber pageNum;
	HashTable *allSubpages;
	windowInfo *info;
	int	hostMsg;
	
	mutex_lock(memoryInUse);
	tag++;

	depositFull = NO;	// vorsichtshalber
	
	// Seitenspeicher löschen
	state = [allPages initState];
	while ([allPages nextState:&state key:(void *)&pageNum
			value:(void *)&allSubpages])
	{
		[allSubpages freeObjects];
	}
	[allPages freeObjects];

	// falls ein Fenster aktiv ist, dessen Seite neu anfordern
	if ((info = [(Storage *)[windowTable valueForKey:actualWindow]
				 elementAt:0]) != NULL)
	{
		[self requestPageMessages:YES];
		info->hold = NO;
		hostMsg = HM_REQ_PAGE | info->page;
		mutex_lock(sending);
		snddriver_dsp_write(cmd_port, &hostMsg, 1, sizeof(int),
							SNDDRIVER_HIGH_PRIORITY);
		mutex_unlock(sending);
	}
	
	mutex_unlock(memoryInUse);
	return tag;
}

// aktuelles Fenster setzen
- (unsigned)setMainWindow:(Window *)window
{
	Storage *infoContainer;
	windowInfo *info;
	int	hostMsg;
	
	mutex_lock(memoryInUse);
	tag++;

	// Wenn das Fenster bisher unbekannt ist, dann Tabelleneintrag erzeugen und 
	// nach Seite 100.XXXX suchen
	if ((infoContainer = [windowTable valueForKey:window]) == nil)
	{
		infoContainer = [[Storage alloc] initCount:1 elementSize:sizeof(*info)
						 description:"iicc"];
		[windowTable insertKey:window value:infoContainer];
		info = [infoContainer elementAt:0];
		VTNumbersFromInt(&info->page, &info->subpage, 100, 0);
		info->hold = NO;
		info->careSubpage = NO;
	}
	actualWindow = window;
	info = [infoContainer elementAt:0];

	// Seitenanforderung an den DSP senden
	hostMsg = HM_REQ_PAGE | info->page;
	mutex_lock(sending);
	snddriver_dsp_write(cmd_port, &hostMsg, 1, sizeof(int),
						SNDDRIVER_HIGH_PRIORITY);
	mutex_unlock(sending);

	// evtl. schon im Cache vorhandene Seite zuröckgeben
	[self sendPageIfPossible];

	mutex_unlock(memoryInUse);
	return tag;
}

// ein Fenster wurde geschlossen
- (unsigned)windowClosed:(Window *)window
{
	mutex_lock(memoryInUse);
	tag++;
	
	if (window == actualWindow)
	{
		[self requestPageMessages:NO];
		actualWindow = nil;
	}
	[(Storage *)[windowTable valueForKey:window] free];
	[windowTable removeKey:window];
	
	mutex_unlock(memoryInUse);
	return tag;
}

// Seitenanforderung ohne Beachtung von Unterseitennummern
- (unsigned)pageRequest:(VTPageNumber)number
{
	windowInfo *info;
	int	hostMsg;
	
	mutex_lock(memoryInUse);
	tag++;
	
	if ((info = [(Storage *)[windowTable valueForKey:actualWindow] 
				 elementAt:0]) != NULL)
	{
		VTNumbersFromInt(&info->page, &info->subpage, 0, 0);
		info->page = number;
		info->hold = info->careSubpage = NO;
			
		hostMsg = HM_REQ_PAGE | number;
		mutex_lock(sending);
		snddriver_dsp_write(cmd_port, &hostMsg, 1, sizeof(int),
							SNDDRIVER_HIGH_PRIORITY);
		mutex_unlock(sending);
		[self sendPageIfPossible];
	}
	mutex_unlock(memoryInUse);		 
	return tag;
}	

// Seitenanforderung mit Beachtung von Unterseitennummern
- (unsigned)pageRequest:(VTPageNumber)page subpage:(VTSubpageNumber)subpage
{
	windowInfo *info;
	int	hostMsg;
	
	mutex_lock(memoryInUse);
	tag++;
	
	if ((info = [(Storage *)[windowTable valueForKey:actualWindow] 
				 elementAt:0]) != NULL)
	{
		info = [(Storage *)[windowTable valueForKey:actualWindow] elementAt:0];
		info->subpage = subpage;
		info->page = page;
		info->hold = NO;
		info->careSubpage = YES;
			
		hostMsg = HM_REQ_PAGE | page;
		mutex_lock(sending);
		snddriver_dsp_write(cmd_port, &hostMsg, 1, sizeof(int),
							SNDDRIVER_HIGH_PRIORITY);
		mutex_unlock(sending);
		[self sendPageIfPossible];
	}
	mutex_unlock(memoryInUse);		 
	return tag;
}	

- (unsigned)doCareSubpage:(VTSubpageNumber)number
{
	windowInfo *info;
	
	mutex_lock(memoryInUse);
	tag++;

	if ((info = [(Storage *)[windowTable valueForKey:actualWindow] 
				 elementAt:0]) != NULL)
	{
		info->hold = NO;
		info->subpage = number;
		info->careSubpage = YES;
			
		[self sendPageIfPossible];
	}
	mutex_unlock(memoryInUse);		 
	return tag;
}

- (unsigned)dontCareSubpage
{
	windowInfo *info;
	
	mutex_lock(memoryInUse);
	tag++;
	
	if ((info = [(Storage *)[windowTable valueForKey:actualWindow] 
				 elementAt:0]) != NULL)
	{
		info->careSubpage = NO;
	}
		
	mutex_unlock(memoryInUse);		 
	return tag;
}

- (unsigned)holdPage:(BOOL)hold;
{
	windowInfo *info;
	
	mutex_lock(memoryInUse);
	tag++;
	
	if ((info = [(Storage *)[windowTable valueForKey:actualWindow] 
				 elementAt:0]) != NULL)
	{
		info->hold = hold;
		[self sendPageIfPossible];
	}
	mutex_unlock(memoryInUse);		 
	return tag;
}

- (unsigned)nextSubpage:(VTSubpageNumber *)requested
{
	windowInfo *info;
	VTSubpageNumber new;
	
	mutex_lock(memoryInUse);
	tag++;
	
	if ((info = [(Storage *)[windowTable valueForKey:actualWindow] 
				 elementAt:0]) != NULL)
	{
		info->hold = NO;
		info->careSubpage = YES;
		
		if ([self smallestSubpageOf:info->page afterSubpage:info->subpage 
					newSubpage:&new] != nil)
		{
			info->subpage = new;
		}
	
		if (requested != NULL)
			*requested = info->subpage;
	
		[self sendPageIfPossible];
	}
	mutex_unlock(memoryInUse);		 
	return tag;
}

- (unsigned)previousSubpage:(VTSubpageNumber *)requested
{
	windowInfo *info;
	VTSubpageNumber new;
	
	mutex_lock(memoryInUse);
	tag++;
	
	if ((info = [(Storage *)[windowTable valueForKey:actualWindow] 
				 elementAt:0]) != NULL)
	{
		info->hold = NO;
		info->careSubpage = YES;
	
		if ([self greatestSubpageOf:info->page beforeSubpage:info->subpage 
					newSubpage:&new] != nil)
		{
			info->subpage = new;
		}
	
		if (requested != NULL)
			*requested = info->subpage;
	
		[self sendPageIfPossible];
	}
	mutex_unlock(memoryInUse);		 
	return tag;
}

- requestClockMessages:(BOOL)flag
{
	int hostMsg;
	
	clockMessages = flag;
	
	hostMsg = HM_SYS_CONFIG;
	
	if (pageMessages)
		hostMsg |= 1;
	if (clockMessages)
		hostMsg |= 2;

	mutex_lock(sending);
	snddriver_dsp_write(cmd_port, &hostMsg, 1, sizeof(int),
						SNDDRIVER_HIGH_PRIORITY);
	mutex_unlock(sending);
						 
	return self;
}

- requestPageMessages:(BOOL)flag
{
	int hostMsg;
	
	pageMessages = flag;
	
	hostMsg = HM_SYS_CONFIG;
	
	if (pageMessages)
		hostMsg |= 1;
	if (clockMessages)
		hostMsg |= 2;

	mutex_lock(sending);
	snddriver_dsp_write(cmd_port, &hostMsg, 1, sizeof(int),
						SNDDRIVER_HIGH_PRIORITY);
	mutex_unlock(sending);
						 
	return self;
}

- writeModeRegister:(unsigned char)data
{
	int hostMsg;
	
	hostMsg = HM_MODE | data;
	
	mutex_lock(sending);
	snddriver_dsp_write(cmd_port, &hostMsg, 1, sizeof(int),
						SNDDRIVER_HIGH_PRIORITY);
	mutex_unlock(sending);
						 
	return self;
}
- writeDisplayControlRegisters:(unsigned char)normal:(unsigned char)news
{
	int hostMsg;
	
	hostMsg = HM_DISP_CNTL | (normal << 8) | news;
	
	mutex_lock(sending);
	snddriver_dsp_write(cmd_port, &hostMsg, 1, sizeof(int),
						SNDDRIVER_HIGH_PRIORITY);
	mutex_unlock(sending);
						 
	return self;
}

- writeDisplayModeRegister:(unsigned char)data
{
	int hostMsg;
	
	hostMsg = HM_DISP_MODE | data;
	
	mutex_lock(sending);
	snddriver_dsp_write(cmd_port, &hostMsg, 1, sizeof(int),
						SNDDRIVER_HIGH_PRIORITY);
	mutex_unlock(sending);
						 
	return self;
}

@end

static void dspThread(PageSupplier *self)
{
	snddriver_handlers_t handlers = {self, 0, 0, 0, 0, 0, 0, 0,
									 (sndreply_recorded_data_t)dataHandler, 
									 0, (sndreply_dsp_msg_t)msgHandler};
	msg_header_t *reply_msg;
	msg_return_t retValue;

	reply_msg = (msg_header_t *)malloc(MSG_SIZE_MAX);
	snddriver_stream_start_reading(self->read_port, NULL, DMA_BUFFER_SIZE,
								   0, 0, 0, 0, 0, 0, 0, self->reply_port);
	snddriver_stream_start_reading(self->read_port, NULL, DMA_BUFFER_SIZE,
								   0, 0, 0, 0, 0, 0, 0, self->reply_port);
	snddriver_dspcmd_req_msg(self->cmd_port, self->reply_port);

	while (!self->DSPThreadAborted) 
	{
		reply_msg->msg_size = MSG_SIZE_MAX;
		reply_msg->msg_local_port = self->reply_port;
		retValue = msg_receive(reply_msg, MSG_OPTION_NONE, 0);

		if (retValue != RCV_SUCCESS)
		{
			mutex_lock(self->comMem);
			self->error = YES;
			self->errorString = "Fehler beim Nachrichtenempfang";
			mutex_unlock(self->comMem);
			condition_signal(self->comMemChanged);
			self->DSPThreadAborted = YES;
		}
		else
		{
			snddriver_reply_handler(reply_msg, &handlers);
		}
	}
}

static void dataHandler(PageSupplier *self, int tag, unsigned char *data, int n)
{
	Storage *pageContainer;
	VTPageNumber pageNum;
	VTSubpageNumber subpageNum;
	windowInfo *info;
	
	if (n == 1024)
	{
		VTNumbersFromPageData(&pageNum, &subpageNum, data);

		mutex_lock(self->memoryInUse);
		
		// Daten in Cache eintragen, wenn erlaubt
		 
		if (!self->depositValid || self->depositPage != pageNum ||
			self->depositSubpage != subpageNum)
		{
			pageContainer = [self dataOfPage:pageNum subpage:subpageNum
								create:YES];
			[pageContainer replace:data at:0];
		}
		else // sonst zwischenspeichern
		{
			[self->deposit replace:data at:0];
			self->depositFull = YES;
			pageContainer = nil;
		}
		
		if (((info = [(Storage *)[self->windowTable
					  valueForKey:self->actualWindow] elementAt:0]) != NULL) &&
			(!info->hold) && (info->page == pageNum) &&
			((!info->careSubpage) || (info->subpage == subpageNum)))
		{
			info->subpage = subpageNum;
			if (pageContainer != nil)
			{
				mutex_lock(self->comMem);
				self->pagePacket.data = [pageContainer elementAt:0];
				self->pagePacket.window = self->actualWindow;
				self->pagePacket.tag = self->tag;
				self->pageReady = YES;
				mutex_unlock(self->comMem);
				condition_signal(self->comMemChanged);
			}
		}
		mutex_unlock(self->memoryInUse);
	}
	if (snddriver_stream_start_reading(self->read_port, NULL, DMA_BUFFER_SIZE,
								   0, 0, 0, 0, 0, 0, 0, self->reply_port) != 0)
	{
		mutex_lock(self->comMem);
		self->error = YES;
		self->errorString = "Fehler beim Anfordern weiterer Seitendaten.";
		mutex_unlock(self->comMem);
		condition_signal(self->comMemChanged);
		self->DSPThreadAborted = YES;
	}
	vm_deallocate(task_self(), (pointer_t)data, n);
}

static void msgHandler(PageSupplier *self, int *data, int n)
{
	int i,j;
	
	for (i=0; i<n; i++)
	{
		switch (data[i] >> 16)
		{
		case 0x10:
			self->pageString[2] = BIN2ASC((data[i] >> 10) & 0xf);
			self->pageString[1] = BIN2ASC((data[i] >> 5)  & 0xf);
			break;
		case 0x11:
			break;
		case 0x12:
		    self->interruptedSequenz = (data[i] & 0x1000) ? YES : NO;
			self->pageString[0] = BIN2ASC(data[i] & 0x7);
			if (self->pageString[0] == '0')
				self->pageString[0] = '8';
			mutex_lock(self->comMem);
			if (!self->interruptedSequenz && self->pageMessages)
			{
				for (j=0; j<4; j++)
					self->pageString2[j] = self->pageString[j];
				self->pageNumReady = YES;
			}
			mutex_unlock(self->comMem);
			condition_signal(self->comMemChanged);
			break;
		case 0x13:
			self->clockString[0] = (data[i] >> 8) & 0xff;
			self->clockString[1] = data[i] & 0xff;
			break;
		case 0x14:
			self->clockString[2] = (data[i] >> 8) & 0xff;
			self->clockString[3] = data[i] & 0xff;
			break;
		case 0x15:
			self->clockString[4] = (data[i] >> 8) & 0xff;
			self->clockString[5] = data[i] & 0xff;
			break;
		case 0x16:
			self->clockString[6] = (data[i] >> 8) & 0xff;
			self->clockString[7] = data[i] & 0xff;
			mutex_lock(self->comMem);
			for (j=0; j<9; j++)
				self->clockString2[j] = self->clockString[j];
			self->clockReady = YES;
			mutex_unlock(self->comMem);
			condition_signal(self->comMemChanged);
			break;
		case 0x17:
			mutex_lock(self->comMem);
			self->error = YES;
			mutex_unlock(self->comMem);
			condition_signal(self->comMemChanged);
			self->DSPThreadAborted = YES;
			break;
		default:
			break;
		}
	}
	
	if (!self->DSPThreadAborted)
		if (snddriver_dspcmd_req_msg(self->cmd_port, self->reply_port) != 0)
		{
			mutex_lock(self->comMem);
			self->error = YES;
			self->errorString = "Fehler beim Anfordern weiterer DSP-Messages.";
			mutex_unlock(self->comMem);
			condition_signal(self->comMemChanged);
			self->DSPThreadAborted = YES;
		}
	
}

static void comThread(PageSupplier *self)
{
	enum {pag, clk, pgn, stop} whatToDo;
	PagePacket pagePacket;
	char clockString[9];
	char pageString[4];
	char *errorString;
	int i, dummy;

	errorString = NULL;
	while (!self->threadsAborted)
	{
		mutex_lock(self->comMem);
		while(!self->pageReady && !self->clockReady && 
			  !self->pageNumReady && !self->error)
			condition_wait(self->comMemChanged, self->comMem);
	
		if (self->pageReady)
		{
			whatToDo = pag;
			pagePacket = self->pagePacket;
			self->pageReady = NO;
		}
		else if (self->clockReady)
		{
			whatToDo = clk;
			for (i=0; i<9; i++)
				clockString[i] = self->clockString2[i];
			self->clockReady = NO;
		}
		else if (self->pageNumReady)
		{
			whatToDo = pgn;
			for (i=0; i<4; i++)
				pageString[i] = self->pageString2[i];
			self->pageNumReady = NO;
		}
		else
		{
			whatToDo = stop;
			errorString = (char *)self->errorString;
		}
		mutex_unlock(self->comMem);
		
		switch (whatToDo)
		{
		case pag:
			mutex_lock(self->memoryInUse);
			if (self->tag == pagePacket.tag)
			{
				[self requestPageMessages:NO];
				// ab jetzt dörfen die Seitendaten nicht mehr verÙndert werden
				VTNumbersFromPageData(&(VTPageNumber)self->depositPage, 
									  &(VTSubpageNumber)self->depositSubpage,
									  pagePacket.data);
				self->depositValid = YES;
				mutex_unlock(self->memoryInUse);
				
				// richtige Seitennummer senden
				stringFromVTPageNumber(pageString, self->depositPage);
				[self->speaker updateRollingHeader:pageString
								size:4 return:&dummy];
				
				// Seitendaten senden
				[self->speaker updatePageData:(char *)&pagePacket
								size:sizeof(pagePacket) return:&dummy];
				
				// evtl. zwischengespeicherte Seite einfögen			
				mutex_lock(self->memoryInUse);
				if (self->depositFull)
				{
					[[self dataOfPage:self->depositPage
						subpage:self->depositSubpage
						create:YES] replace:[self->deposit elementAt:0] at:0];
				}
				self->depositFull = NO;
				self->depositValid = NO;
				mutex_unlock(self->memoryInUse);

				// šberschreiben der Seitennummer verhindern
				self->pageNumReady = NO;
			}
			else
				mutex_unlock(self->memoryInUse);
			
			break;
		case clk:
			[self->speaker updateClock:clockString
							size:9 return:&dummy];
			break;
		case pgn:
			[self->speaker updateRollingHeader:pageString
							size:4 return:&dummy];
			break;
		case stop:
			if (errorString != NULL)
				[self->speaker printError:errorString];
			[self->speaker threadsStopped];
			self->threadsAborted = YES;
			break;
		}
	}
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.