[comp.sys.mac.programmer] MacRecorder code

palmer@tybalt.caltech.edu (David Palmer) (11/27/88)

Due to the large number of requests that have been posted recently, I
am posting this code which reads the MacRecorder (sampling at 22 kHz).

MacRecorder comes with no programming documentation, so this is as good
as it gets.  If anyone has anything better, I'd be very interested.

I don't know where the original code comes from, but I made modifications
which allows it to work as an oscilloscope (on an SE or earlier machine.)
The style of the original code was lousy, and I opted to maintain stylistic
unity with my additions.

The core of the program is the routines which set up the port for reading
an externally clocked data stream.  To understand these, you must read the
Zilog SCC (Serial communications controller) data sheets.  At the MacRecorder's
input rate, interrupt driven routines are infeasible, so it uses continuous
polling.  This may cause data loss if other interrupts occur.

MacRecorder also transmits its data MSB first (if you understand how an SAR
ADC works, you will understand why.)  This is the opposite of most serial
ports, and so a translation table (included) which reverse the bit order
is needed.

Here is the code:
--------------------------------------------------------------

#include	<stdio.h>
#include	<QuickDraw.h>
#define 	x1Clock		12	/* Clock mode for Scc chip */
#define 	x16Clock	76


#define LENGTH 750		/* how much to read */
#define WIDTH 512		/* how large the screen is */
int Length = LENGTH,RealLength,x;
unsigned char buffer[LENGTH];
unsigned char table[256];
int screentable[256];
#define TOPMAR 20

void Die();

unsigned char *FindMin();

extern long *zero:0;
main() 
{
	register unsigned char *pch;
	Rect r;
	long SccIn();
	
	int i;
		
	InitGraf(&thePort);
	InitFonts ();
	InitWindows ();
	InitMenus ();
	TEInit ();
	InitDialogs(&Die);
	InitCursor ();

	SccInit();
	TableInit();
	r = thePort->portBits.bounds;
	PaintRect(&r);

	r.top = TOPMAR;
	r.bottom = TOPMAR+256;

	for (i = 0 ; i < 256 ; i++)
		screentable[i] = (table[i] + TOPMAR) * thePort->portBits.rowBytes;
	do {
		PenNormal();
		RealLength = MySccIn(buffer, LENGTH);
		pch = FindMin(buffer, LENGTH - WIDTH);
		DrawCurve(pch);
		if (Button()) {
			SysBeep(3);
			Die();
		}
		PaintRect(&r);
	} while (RealLength>=LENGTH);
	SysBeep(12);
}

DrawCurve(pch)		/* works only on MacI under non-multi finder */
register unsigned char *pch;
{
	register int byte;
	register int bit;
	register int *pscreentable = screentable;
	register unsigned char *pscreen  = 
				(unsigned char *)(thePort->portBits.baseAddr);
	
	for (byte = 0 ; byte < 64 ; byte++)
		for (bit = 0x100 ; bit >>= 1 ; )
			pscreen[pscreentable[*pch++] + byte] ^= (char)bit;
}

unsigned char *FindMin(pch, cch)
unsigned char *pch;
int cch;
{
	unsigned char *pchmin = pch;
	unsigned char min = *pch;
	
if (cch < 0) SysBeep(3);
	for ( ; --cch > 0 ; )
		if (*pch++ < min) {
			pchmin = pch - 1;
			min = *pchmin;
		}
	return pchmin;
} 

char **SccRd,**SccWr,*SccRBase,*SccWBase;	/* pointer to Scc chip */
int aCtl = 2;								/* offsets */
int aData = 6;
int bCtl = 0;
int dData = 4;
SccInit() /* initializes the Scc chip for the MacRecorder Plus */
	/* for the regular MacRecorder II, replace x1Clock with x16Clock below */
{	
	SccRd = (char **)0x1D8;		
	SccWr = (char **)0x1DC;		
	SccRBase = (char *)*SccRd;	
	SccWBase = (char *)*SccWr;
	SccPoke(9,2);			/* NV only */
						/* following line is for MacRecorder Plus */
	SccPoke(4,x1Clock);	/* 2 stop bits, Async, x1 clock mode  */
	SccPoke(1,1);			/* no Rx/Tx Int, Ext Int ON (mouse) */
	SccPoke(3,193);			/* initialize receiver, 8bits */
	SccPoke(5,122);			/* 8bits/char, send break(for other hardware!), Tx enable */
	SccPoke(11,48);			/* use TRxC as receiver clock */
	SccPoke(14,1);			/* BR enable, nothing else */
	SccPoke(15,8);			/* Interrupt on CD changes (mouse), turn off CTS interrupt */
	SccPoke(64,64);			/* Reset Rx CRC */
	SccPoke(9,10);			/* initialize master interrupt and NV */
}
SccPoke(n,v)		/* accesses the modem port */
char n,v;
{
	*(SccWBase + aCtl) = n;	/* set index to register n */
	*(SccWBase + aCtl) = v;	/* write v into register n */
}
SccPeek(n)
char n;
{	
	*(SccWBase + aCtl) = n;		/* set index to register n */
	return(*(SccRBase + aCtl));	/* retrun value in register n */
}
TableInit()
{
	MakeTable(&table[0]);
	ModifyTable(&table[0]);
}
MakeTable(table)
register char *table;
{
	asm	{
			adda.w	#256,table
			move.w	#255,D0
	lp0:	move.b	D0,D1
			move.w	#07,D3
	lp1:	lsr.b	#01,D1
			roxl.b	#01,D2
			dbf		D3,@lp1
			move.b	D2,-(table)
			dbf		D0,@lp0
		}
}
#ifdef FOO   /*( this is not needed if you use unsigned chars */
ModifyTable(table)
char *table;
{
	int i;
	for (i=0;i<256;i++)
	{
		if ( table[i]>=0 ) table[i] -= 128;
		else table[i] += 128 ;
	}
}
#endif
int MySccIn(dest, count)
register char *dest;
register int count;
{
	int	iCount = count;	/* actual number of bytes received */
	register	char	*pSccRBase = SccRBase;
	while (count-- > 0 && !Button()) {
		while (0 == (pSccRBase[2] & 0x1))
			if (Button())
				return (iCount - count + 1);
		*dest++ = pSccRBase[6];
	}
	return iCount;
}

long SccIn(dest,count)
register char *dest;
register long count;
{
	register	unsigned char	*Table;		/* a lookup table */
	register	long	aCount;	/* actual number of bytes received */
	Table = &table[0];
	aCount = 0L;
	asm	{
		move.l  #0x9FFFF8,A0			; /* SccRBase   */
#ifdef FOO
		move.l	#0xEFE1FE,A1			; /* mouse button */
		clr.l	D0
	lp:	btst	#03,(A1)				; /* mouse clicked? */
		beq		@lq
#else
	}
	lp:	if (Button())
			return(aCount);
	asm {
#endif
		btst	#00,2(A0)				; /* SccRBase + aCtl */
		beq		@lp
		move.b	6(A0), D0				; /* SccRBase + aData */
		move.b	00(Table,D0), (dest)+	; /* translate in lookup table */
		addq.l	#1,aCount				; /* one more byte received */
		subq.l	#1,count
		bne		@lp
	lq:	nop
		}
	return(aCount);
}

void Die()
{
	ExitToShell();
}
----------------------------------------------------------------

		David Palmer
		palmer@tybalt.caltech.edu
		...rutgers!cit-vax!tybalt.caltech.edu!palmer
	"I was sad that I had no shirt, until I met a man with no torso"

etsai@athena.mit.edu (Eugene C Tsai) (01/29/91)

I am interested in writing a program to take data from my MacRecorder device
via the serial port. I also remember that some time ago someone posted some
C code doing just that. I would appreciate it if someone could email me that
code or email me an ftp site from where I might be able to get this code.

Thanks in advance.
Eugene Tsai

mmt@client2.DRETOR.UUCP (Martin Taylor) (01/30/91)

etsai@athena.mit.edu (Eugene C Tsai)
asks:
--
--I am interested in writing a program to take data from my MacRecorder device
--via the serial port. I also remember that some time ago someone posted some
--C code doing just that. I would appreciate it if someone could email me that
--code or email me an ftp site from where I might be able to get this code.

=============

I tried to respond by e-mail, but the mailer informs me there is no such
user at athena.mit.edu.  If Eugene C Tsai exists and wants further
information, he should e-mail me (and I don't mean the whole world
to do that).

Partial answer:  get the Usenet Macintosh Programmer's Guide from
sumex-aim.  There is some sample code in C that can be used as a basis.
-- 
Martin Taylor (mmt@ben.dciem.dnd.ca ...!uunet!dciem!mmt) (416) 635-2048
There is no legal canon prohibiting the application of common sense
(Judge James Fontana, July 1990, on staying the prosecution of a case)

ph@cci632.UUCP (Pete Hoch) (01/30/91)

mmt@client2.DRETOR.UUCP (Martin Taylor) writes:
 
> Partial answer:  get the Usenet Macintosh Programmer's Guide from
> sumex-aim.  There is some sample code in C that can be used as a basis.

Has this ever been posted to the mac...source group?  I do not remember
seeing it and some of us still do not have ftp access to such wonderful
things like sumex-aim.

If it is not worth a post will someone mail this to me?

Thenks,
Pete

hawley@adobe.COM (Steve Hawley) (01/31/91)

In article <1991Jan29.020423.15645@athena.mit.edu> etsai@athena.mit.edu (Eugene C Tsai) writes:
>I am interested in writing a program to take data from my MacRecorder device
>via the serial port. I also remember that some time ago someone posted some
>C code doing just that. I would appreciate it if someone could email me that
>code or email me an ftp site from where I might be able to get this code.

At one point I needed similar code.  I called Farallon and found out that they
have a package available called the "hackers toolkit".  It consists of a disk
with C source for recording as well as hypercard XCMDs etc etc.

Call them up.  They were very pleasant to deal with.

Steve Hawley
hawley@adobe.com

-- 
"Did you know that a cow was *MURDERED* to make that jacket?"
"Yes.    I didn't think there were any witnesses, so I guess I'll have to kill
 you too." -Jake Johansen

mmt@client1.DRETOR.UUCP (Martin Taylor) (02/04/91)

There were a couple of requests to post the code for programming the MacRecorder
that was in the Usenet Macintosh Programmers Guide.  Here it is.
==========

From: svc@well.UUCP (.i.Leonard Rosenthol;)
Subject: Re: .c2.Programming the SCC (Code to poll the Macrecorder)
Summary: Here is some code...

In article <347@eldritch.hss.bu.oz>, grue@melmac.hss.bu.oz (Frobozz) writes:

> In article <YYXJcay00UoL89Vkp3@andrew.cmu.edu> nf0i+@andrew.cmu.edu (Norman William Franke, III) writes:
> >I would also like information on using the serial ports at speeds greater
> >than 57K, or more to the point, how to read data from a MacRecorder.
> >
> >Norman Franke
> >nf0i+@andrew.cmu.edu
> 
> 
> Count me in too. (esp the bit about a MacRecorder)
> 
> 				Paul Dale

What follows is some source for reading the data direct from a Mac- Recorder that was posted to this group previously.  I did not write it, so I take no credit and especially NO BLAME!

/* Written  6:18 pm  Nov 26, 1988 by palmer@tybalt.caltech.edu in uxe.cso.uiuc.edu:comp.sys.mac.programmer */ Due to the large number of requests that have been posted recently, I am posting this code which reads the MacRecorder (sampling at 22 kHz).

MacRecorder comes with no programming documentation, so this is as good as it gets.  If anyone has anything better, I'd be very interested.

I don't know where the original code comes from, but I made modifications which allows it to work as an oscilloscope (on an SE or earlier machine.) The style of the original code was lousy, and I opted to maintain stylistic unity with my additions.

The core of the program is the routines which set up the port for reading an externally clocked data stream.  To understand these, you must read the Zilog SCC (Serial communications controller) data sheets.  At the MacRecorder's input rate, interrupt driven routines are infeasible, so it uses continuous polling.  This may cause data loss if other interrupts occur.

MacRecorder also transmits its data MSB first (if you understand how an SAR ADC works, you will understand why.)  This is the opposite of most serial ports, and so a translation table (included) which reverse the bit order is needed.

Here is the code:
--------------------------------------------------------------

#include	<stdio.h>
#include	<QuickDraw.h>
#define 	x1Clock		12	/* Clock mode for Scc chip */
#define 	x16Clock	76


#define LENGTH 750		/* how much to read */
#define WIDTH 512		/* how large the screen is */
int Length = LENGTH,RealLength,x;
unsigned char buffer[LENGTH];
unsigned char table[256];
int screentable[256];
#define TOPMAR 20

void Die();

unsigned char *FindMin();

extern long *zero:0;
main() 
{
	register unsigned char *pch;
	Rect r;
	long SccIn();
	
	int i;
		
	InitGraf(&thePort);
	InitFonts ();
	InitWindows ();
	InitMenus ();
	TEInit ();
	InitDialogs(&Die);
	InitCursor ();

	SccInit();
	TableInit();
	r = thePort->portBits.bounds;
	PaintRect(&r);

	r.top = TOPMAR;
	r.bottom = TOPMAR+256;

	for (i = 0 ; i < 256 ; i++)
		screentable[i] = (table[i] + TOPMAR) * thePort->portBits.rowBytes;
	do {
		PenNormal();
		RealLength = MySccIn(buffer, LENGTH);
		pch = FindMin(buffer, LENGTH - WIDTH);
		DrawCurve(pch);
		if (Button()) {
			SysBeep(3);
			Die();
		}
		PaintRect(&r);
	} while (RealLength>=LENGTH);
	SysBeep(12);
}

DrawCurve(pch)		/* works only on MacI under non-multi finder */
register unsigned char *pch;
{
	register int byte;
	register int bit;
	register int *pscreentable = screentable;
	register unsigned char *pscreen  = 
				(unsigned char *)(thePort->portBits.baseAddr);
	
	for (byte = 0 ; byte < 64 ; byte++)
		for (bit = 0x100 ; bit >>= 1 ; )
			pscreen[pscreentable[*pch++] + byte] ^= (char)bit;
}

unsigned char *FindMin(pch, cch)
unsigned char *pch;
int cch;
{
	unsigned char *pchmin = pch;
	unsigned char min = *pch;
	
if (cch < 0) SysBeep(3);
	for ( ; --cch > 0 ; )
		if (*pch++ < min) {
			pchmin = pch - 1;
			min = *pchmin;
		}
	return pchmin;
} 

char **SccRd,**SccWr,*SccRBase,*SccWBase;	/* pointer to Scc chip */
int aCtl = 2;								/* offsets */
int aData = 6;
int bCtl = 0;
int dData = 4;
SccInit() /* initializes the Scc chip for the MacRecorder Plus */
	/* for the regular MacRecorder II, replace x1Clock with x16Clock below */
{	
	SccRd = (char **)0x1D8;		
	SccWr = (char **)0x1DC;		
	SccRBase = (char *)*SccRd;	
	SccWBase = (char *)*SccWr;
	SccPoke(9,2);			/* NV only */
						/* following line is for MacRecorder Plus */
	SccPoke(4,x1Clock);	/* 2 stop bits, Async, x1 clock mode  */
	SccPoke(1,1);			/* no Rx/Tx Int, Ext Int ON (mouse) */
	SccPoke(3,193);			/* initialize receiver, 8bits */
	SccPoke(5,122);			/* 8bits/char, send break(for other hardware!), Tx enable */
	SccPoke(11,48);			/* use TRxC as receiver clock */
	SccPoke(14,1);			/* BR enable, nothing else */
	SccPoke(15,8);			/* Interrupt on CD changes (mouse), turn off CTS interrupt */
	SccPoke(64,64);			/* Reset Rx CRC */
	SccPoke(9,10);			/* initialize master interrupt and NV */
}
SccPoke(n,v)		/* accesses the modem port */
char n,v;
{
	*(SccWBase + aCtl) = n;	/* set index to register n */
	*(SccWBase + aCtl) = v;	/* write v into register n */
}
SccPeek(n)
char n;
{	
	*(SccWBase + aCtl) = n;		/* set index to register n */
	return(*(SccRBase + aCtl));	/* retrun value in register n */
}
TableInit()
{
	MakeTable(&table[0]);
	ModifyTable(&table[0]);
}
MakeTable(table)
register char *table;
{
	asm	{
			adda.w	#256,table
			move.w	#255,D0
	lp0:	move.b	D0,D1
			move.w	#07,D3
	lp1:	lsr.b	#01,D1
			roxl.b	#01,D2
			dbf		D3,@lp1
			move.b	D2,-(table)
			dbf		D0,@lp0
		}
}
#ifdef FOO   /*( this is not needed if you use unsigned chars */
ModifyTable(table)
char *table;
{
	int i;
	for (i=0;i<256;i++)
	{
		if ( table[i]>=0 ) table[i] -= 128;
		else table[i] += 128 ;
	}
}
#endif
int MySccIn(dest, count)
register char *dest;
register int count;
{
	int	iCount = count;	/* actual number of bytes received */
	register	char	*pSccRBase = SccRBase;
	while (count-- > 0 && !Button()) {
		while (0 == (pSccRBase[2] & 0x1))
			if (Button())
				return (iCount - count + 1);
		*dest++ = pSccRBase[6];
	}
	return iCount;
}

long SccIn(dest,count)
register char *dest;
register long count;
{
	register	unsigned char	*Table;		/* a lookup table */
	register	long	aCount;	/* actual number of bytes received */
	Table = &table[0];
	aCount = 0L;
	asm	{
		move.l  #0x9FFFF8,A0			; /* SccRBase   */
#ifdef FOO
		move.l	#0xEFE1FE,A1			; /* mouse button */
		clr.l	D0
	lp:	btst	#03,(A1)				; /* mouse clicked? */
		beq		@lq
#else
	}
	lp:	if (Button())
			return(aCount);
	asm {
#endif
		btst	#00,2(A0)				; /* SccRBase + aCtl */
		beq		@lp
		move.b	6(A0), D0				; /* SccRBase + aData */
		move.b	00(Table,D0), (dest)+	; /* translate in lookup table */
		addq.l	#1,aCount				; /* one more byte received */
		subq.l	#1,count
		bne		@lp
	lq:	nop
		}
	return(aCount);
}

void Die()
{
	ExitToShell();
}
----------------------------------------------------------------

		David Palmer
		palmer@tybalt.caltech.edu
		...rutgers!cit-vax!tybalt.caltech.edu!palmer
	"I was sad that I had no shirt, until I met a man with no torso"
/* End of text from uxe.cso.uiuc.edu:comp.sys.mac.programmer */

-- 

Leonard Rosenthol 

=============
My comments:  This writes directly to the screen -- the whole screen -- and it
does not clean up after itself, but it does give you the codes to get the
MacRecorder sampling at 22128 Hz (I think that's right, but it is hard to be
sure).  In the actual sampling code, there is a busy wait for Button().
This makes you miss samples, at least on a 68000 series Mac and I think also
on the II si.  You can remove this check at the cost of not being able to
exit the sampling loop if the Mac Recorder is disconnected. (You can, by
rebooting the computer, but not by an interrupt, because they are masked out).

I would like to know the control codes for changing the sampling rate, a 
what the precise sampling rates are (not the approximations in the Farallon
manual).


As an exercise, I have been using this code in TransSkel to make the scope
work in a window and to clean up after itself.  It kind of works, but as a
rusty C programmer and non-Mac programmer, I wouldn't want to propagate that
code except to individuals who would expect all sorts of stupidities in it.
-- 
Martin Taylor (mmt@ben.dciem.dnd.ca ...!uunet!dciem!mmt) (416) 635-2048
There is no legal canon prohibiting the application of common sense
(Judge James Fontana, July 1990, on staying the prosecution of a case)

kinsey@athena.cs.uga.edu (Kevin Kinsey) (02/23/91)

 how about something really radical, like code that can detect sound levels,
 or perhaps some clues in how to write such a creature...

 kevin

===============================================================================
Kevin Kinsey              |"honey, i want words and i want some touch and maybe
UCNS                      | that's a bit too much for someone so far removed
University of Georgia     | from what you used to claim. you can be cruel, i'll
Athens, GA                |  still be kind, you know i've got it in my mind i'm
kinsey@athena.cs.uga.edu  | in love with you and i'm not gonna play your games."
st19@uga.cc.uga.edu       |		- 'Rock & Roll' Amy Ray