Programmatically (in C) send MIDI out signal from modem port, SE/30

Mu0n

Well-known member
Let's get what I'm using as gear out of the way:

Dev Machine:
modern PC with Basilisk II set with System 7.5.5, Symantec C++ 6.0

Target Machine:
SE/30 with System 7.5.3, no Apple MIDI Manager (prefer not to use it since I want to target 6.0.8 and a Mac Plus as well)
PocketMac MIDI interface plugged in the modem port, has 2 MIDI OUT (only using 1) 1 MIDI IN (not using it...for now)
Roland SC-88ST to accept midi signals from the mac and play stuff

Goals:
1) at least play a scale on channel #0, in sync with NoteOn and NoteOff effects
2) load a .mid file and play it using a fine-grain enough delay function as part of a game title screen at first - I'd adapt a python project I have that already works on a modern PC.
3) same as 2, but use interrupt+callback functions to allow game logic, graphics, inputs, not sure how far I can push the old 68k macs (it was painful in Space Quest III on the Mac Plus but there was a lot going with their interpreter as well)

Progress made:
I've already made tons of tests with some python code running on my PC, I can parse a .mid file and deal with the 2 main cases of a type 0 .mid file (every data stuck in 1 track) and type 1 .mid file (info separated into a bunch of tracks, typically 4-8, but can go up to 22 sometimes) and using a delay function to sit idle and respect the events' time delta-to-go information. If you're curious, my code sits at: https://github.com/Mu0n/PythonMidiPlayer

I've settled on using documentation from here:
Inside Macintosh: Serial Driver

Needs:
I need the extra step to make it MIDI compliant and well synced.
1719601460709.png

But, SerRest, PBWrite, OpenDriver and such from the inside mac reference above allow me these settings:
1719601505192.png


The first configuration I tried was the default one from the documentation:
9600 bps + 1 stop bit + no parity + 8 data bits and I was met with complete silence.

2nd attempt was to use 19200 bps, and I try to send the note event 5 times.
Only the first and last event do something, there's a huge sync problem.
(put your sound way up):

3rd attempt was to use 57600 and that's total silence again.

How does stuff like cubase and other sequencers do it? They let you pick the modem or serial port and let you choose the base transmission speed like 0.5 MHz, 1 MHz, etc.
I notice that 1 000 000 MHz / 32 is exactly what MIDI wants, 31250.

Help!
 

Mu0n

Well-known member
C:
// Insert your include list here

#include <stdio.h>
#include "Serial.h"

// Insert your #define list here

// --- MIDI spec hard coded values ---
// good reference is here: https://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfileformat.html#BM1_1
// from the midi.org itself: https://midi.org/summary-of-midi-1-0-messages

#define kInputBufferSize 1024

// Insert your global variables here
char *buffer;

    // for serial stuff
short gOutputRefNum;
short gInputRefNum;
Handle gInputBufHandle;
OSErr gOSErr;


// Insert your function declarations here
void myInitStuff(void);
void MyOpenSerialDriver(void);
void MyChangeInputBuffer(void);
void MyConfigureThePort(void);
void MySendMessage(void);
void MyRestoreInputBuffer(void);
void MyCloseSerialDriver(void);


// Insert your function definitions here

void MyOpenSerialDriver(void)
    {
    gOSErr = OpenDriver("\p.AOut",&gOutputRefNum);
    if(gOSErr == noErr)
        {
        printf("output port successfully opened, refnum=%d \n",gOutputRefNum);
        gOSErr = OpenDriver("\p.AIn",&gInputRefNum);
        if(gOSErr == noErr) printf("output port successfully opened, refnum=%d \n",gInputRefNum);
        else printf("can't open in port, code=%d \n",gOSErr);
        }
    else printf("can't open out port, code=%d \n",gOSErr);
  
    printf("input refNum %d - output refNum %d\n",gInputRefNum,gOutputRefNum);
    }

void MyChangeInputBuffer(void)
    {
    gInputBufHandle = NewHandle(kInputBufferSize);
    HLock(gInputBufHandle);
    SerSetBuf(gInputRefNum, *gInputBufHandle,kInputBufferSize);
    }
  
void MySetHandshakeOptions(void)
    {
    SerShk mySerShkRec;
    mySerShkRec.fXOn = 0;
    mySerShkRec.fCTS = 0;
    mySerShkRec.errs = 0;
    mySerShkRec.evts = 0;
    mySerShkRec.fInX = 0;
    mySerShkRec.fDTR = 0;
  
    gOSErr = Control(gOutputRefNum, 14, &mySerShkRec);
  
    if(gOSErr == noErr)
        {
        printf("handshake successful\n");
        }
    else printf("handshake unsuccesful, code=%d \n",gOSErr);
  
    }

void MyConfigureThePort(void)
    {
    const int kConfigParam = baud57600+data8+noParity+stop10;
    gOSErr = SerReset(gOutputRefNum, kConfigParam);
  
    if(gOSErr == noErr)
        {
        printf("port configuration successful\n");
        }
    else printf("port configuration unsuccesful, code=%d \n",gOSErr);
    }

void MySendMessage(void)
    {
    char myMessage[3]={0x90,0x3C,0x64};
    long int myMsgLen;
    ParamBlockRec myParamBlock;
    ParmBlkPtr myPBPtr;
  
    myMsgLen=Length(myMessage);
  
    myParamBlock.ioParam.ioRefNum = gOutputRefNum;
    myParamBlock.ioParam.ioBuffer = (Ptr)&(myMessage[1]);
    myParamBlock.ioParam.ioReqCount = myMsgLen;
    myParamBlock.ioParam.ioCompletion = nil;
    myParamBlock.ioParam.ioVRefNum = 0;
    myParamBlock.ioParam.ioPosMode = 0;
    myPBPtr = (ParamBlockRec *)&myParamBlock;
  
    gOSErr = PBWrite(myPBPtr, FALSE);
    if(gOSErr == noErr)
        {
        printf("sending message successful\n");
        }
    else printf("sending message unsuccesful, code=%d \n",gOSErr);
    } 

void MyRestoreInputBuffer(void)
    {
    SerSetBuf(gInputRefNum, *gInputBufHandle,0);
    HUnlock(gInputBufHandle);
    }
  
void MyCloseSerialDriver(void)
    {
    gOSErr = KillIO(gOutputRefNum);
    if(gOSErr == noErr) gOSErr = CloseDriver(gInputRefNum);
    if(gOSErr == noErr) gOSErr = CloseDriver(gOutputRefNum);
    }
  
void myInitStuff()
    {
    InitGraf(&thePort); //qd is the quickdraw global, lets you access thePort (viewable area)
    InitFonts(); //Do this to mess with text and fonts
    InitWindows(); //A must if you plan on drawing on controlled window surfaces as opposed to the raw screen
    InitMenus(); //Do this to mess with menus
    InitDialogs(nil); //Dialog manager for alerts, dialogs, etc. nil = no return resumeProc
    InitCursor(); //Cursor manager, if you don't do this, you'll be stuck with the waiting cursor (watch icon or more modern ones)
    GetDateTime((unsigned long*)(&qd.randSeed)); //if you ever need random numbers, this will prep it with a unique time related seed that changes every launch
    //MoreMasters(); //useful at the very start, allows to have an additional block of master pointers. avoids fragmentation of memory if called at the start. uncomment if needed.
    FlushEvents(everyEvent,0); //Do this to prevent queued events from messing with your app right at launch.
    }
  
  
void main()
    {
    Rect r;
    EventRecord wutup;
    short savedMasks;
  
    myInitStuff(); //do this at startup
  
    SetRect(&r,60,30,350,450);
  
    printf("\nHello World");
    printf("\n");
     SetPort(FrontWindow());
  
    savedMasks = SysEvtMask;
  
    MyOpenSerialDriver();
    MyChangeInputBuffer();
    MySetHandshakeOptions();
    MyConfigureThePort();
    MySendMessage();
  
    SetEventMask(keyDownMask | keyUpMask | mDownMask);
  
    Delay(60,nil);
    MySendMessage();
    Delay(60,nil);
    MySendMessage();
    Delay(60,nil);
    MySendMessage();
    Delay(60,nil);
    MySendMessage();
    Delay(60,nil);
    MySendMessage();
         /*
    while(!Button())
        {
        Delay(60,nil);
        MySendMessage();
        GetNextEvent(everyEvent, &wutup);
        switch(wutup.what)
            {
            case keyDown:
                MySendMessage();
                break;
            case mouseDown:
                MySendMessage();
                break;
            }
        //Insert main loop code here; use functions and avoid clogging the main function
        }
    */
    MyRestoreInputBuffer();
    MyCloseSerialDriver();
  
    SetEventMask(savedMasks);
    ExitToShell(); //Go back to Finder
    }
 
MIDI interfaces for Macs use an external 1 MHz clock divided by 32 to obtain the 31250 bps used, so you have to configure the SCC to accept an external clock. I completely forgot that I wrote something about this two years ago, maybe this old thread is useful...
 

Mu0n

Well-known member
MIDI interfaces for Macs use an external 1 MHz clock divided by 32 to obtain the 31250 bps used, so you have to configure the SCC to accept an external clock. I completely forgot that I wrote something about this two years ago, maybe this old thread is useful...
I saw your thread but did not understand how to leverage it.
What doesn't connect in my mind is the fact that without bringing in my own breadboard circuit with a 1 MHz oscillator and frequency dividers, I'm able to use Cubase just fine and play arbitrary .MID files all day long, + play a keyboard arbitrarily for good measure.

Are the usual MIDI interface equipped with such a crystal? Is that where you take in that signal?
I'm using a PocketMac:
1719606351642.png
 
Yes, all MIDI interfaces have the required clock generator. Check out this example schematic. The interface also is important since MIDI signals are not voltage driven but implemented using a current loop (to reduce problems with hum due to line noise coupling etc.), so the other function of the interface is to isolate the computers, synths, etc. connected to each other using optocouplers.

Regarding the code, it should be sufficient to add the source in the linked thread to your project and call the required assembly routines from C. However, I haven't worked with Symantec/Think C for ages, so I would need to check how to get this to work...
 

Mu0n

Well-known member
I naively brought this code from this post https://68kmla.org/bb/index.php?threads/external-localtalk-serial-port-clocks.35645/post-453444
formatted for THINK C:
C:
void SCCASMInit(void)
    {
    asm
        {
        MOVE.B #4,(A0)  ; pointer for SCC reg 4
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #0x84,(A0) ; 32x clock, 1 stop bit
        
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #1,(A0) ; pointer for SCC reg 1
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #0x00,(A0) ; No W/Req
        
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #3,(A0); pointer for SCC reg 3
        MOVE.L (SP),(SP) ; Delay
        
        MOVE.B #0x00,(A0) ; Turn off Rx
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #5,(A0); pointer for SCC reg 5
        
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #0x00,(A0) ; turn off Tx
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #11,(A0); pointer for SCC reg 11
        
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #0x28,(A0); Make TRxC clock source
        MOVE.L (SP),(SP) ; Delay
        
        MOVE.B #14,(A0) ; pointer for SCC reg 14
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #0x00,(A0) ; Disable BRGen
        
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #3,(A0) ; pointer for SCC reg 3
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #0xE1,(A0) ; Enable Rx
        
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #5,(A0); pointer for SCC reg 5
        MOVE.L (SP),(SP) ; Delay
        
        MOVE.B #0x6A,(A0) ; Enable Tx and drivers
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #15,(A0) ; pointer for SCC reg 15
        MOVE.L (SP),(SP) ; Delay
        
        MOVE.B #0x08,(A0) ; Enable DCD int for mouse
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #0,(A0) ; pointer for SCC reg 0
        
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #0x10,(A0) ; Reset EXT/STATUS
        MOVE.L (SP),(SP) ; Delay
        
        MOVE.B #0,(A0) ; pointer for SCC reg 0
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #0x10,(A0); Reset EXT/STATUS again
        MOVE.L (SP),(SP) ; Delay
        
        MOVE.B #1,(A0) ; pointer for SCC reg 1
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #0x13,(A0) ; Enable interrupts
        
        MOVE.L (SP),(SP) ; Delay
        MOVE.B #9,(A0) ; pointer for SCC reg 9
        MOVE.L (SP),(SP) ; Delay
        
        MOVE.B #0x0A,(A0) ; Set master int enable
        MOVE.L (SP),(SP) ; Delay
        }
    }


I was hoping that the rest of my serial port functions I've already typed in would deal with port opening + transmission, that this asm block would be the magical bullet for external clock I was missing, but no. While the tranmission still happens, I'm back to total silence. The only feedback I get that something is still happening like before is the activity LED on my MIDI interface, toggles off and on while my test bytes are sent.

do I have to drop my old functions and go all in with every function in the post linked above? I'm now into above-my-pay-grade territory. That asm code involves hard coded addresses that I can't find referenced in any of the inside mac references I have laying about. I'm not sure what era of mac is specifically targeted with it but the mention of a past November 1985 article in here https://preserve.mactech.com/articles/mactech/Vol.03/03.07/MidiLib/index.html makes me think it has a good chance of working for a Mac Plus, maybe less so for a SE/30?
 

Mu0n

Well-known member
yeah, I started poring over the entire code and I guess that's only a small section of a bigger initializing routine. I'll see if I can replace everything I had for serial comm with this, hopefully my asm block adaptation chops are not too rusty
 

joevt

Well-known member
I don't see in void SCCASMInit where A0 gets initialized. That happens in PROCEDURE InitSCCA in the MacTech article. The article title is "A Midi Library for Pascal" but all the code is assembly? I don't think you need to keep it as assembly. You could translate it to C. I don't know if it would be performant or not. The assembly code sets up an interrupt vector. Is PBRead/PBWrite async callback not good enough? Maybe not.

The code doesn't seem to open the serial driver. That is required so that other applications can't open the driver at the same time. Did you read all the serial stuff at the Apple Documentation archive that I linked? Sort the results by date and read them in ascending order to better understand how serial stuff changed over the years.
 

Mu0n

Well-known member
You could translate it to C. I don't know if it would be performant or not. The assembly code sets up an interrupt vector. Is PBRead/PBWrite async callback not good enough? Maybe not.

I can *try* to convert it to C but you assume I'm solid in 68k assembly to begin with - I am not. There are so many questions that pop into my head as I'm typing this up and trying to compile it as I go, so that I don't let typos creep in.
branching seems to work if I use labels in THINK C using the @label notation, so that's good.
However, I'm at a point where handles are referred to in the code and are never assigned.
For example, fairly early in InitSCCA, there's this line:
C-like:
 LEA    PRxIntHandA,A1  ; point to previous vector stor

and the one place where it might be declared and initialized is past a good bunch of functions, right before the ResetSCCChan one, in this block:
Code:
TxQueueADCB.B  $100,0; this is the queue
TxQEmptyA DC0    ; the queue empty flag
TxByteInA DC0    ; index to next cell in
TxByteOutADC0    ; index to next cell out
RxQueueADCB.B  $400,0; this is the queue
RxQEmptyA DC0    ; the queue empty flag
RxByteInA DC0    ; index to next cell in
RxByteOutADC0    ; index to next cell out
PRxIntHandA DC.L 0 ; Previous interrupt vector
PTxIntHandA DC.L 0 ; Previous interrupt vector

are these the equivalent of globals? Is it normal that they are declared below instead of above? I'm second guessing myself at every step.

Did you read all the serial stuff at the Apple Documentation archive that I linked? Sort the results by date and read them in ascending order to better understand how serial stuff changed over the years.

I read the last 4 oldest ones, which have a chance of being directly helpful for this purpose. Master Tracks Pro 4 was released in 1989 and deals with MIDI on a Mac Plus without breaking a sweat. Surely, its core functionality of correct serial port set up can be replicated.

Here my impressions of them:
Serial PollProc 1992-06-01 - pertains to powerbook 170 issues about losing data due to buffer being overrun = while interesting, while I have a PB170, I'm not looking into using them primarily here and it won't help me with the first objectives I have

Serial GPi (General-Purpose Input) 1991-02-01 - talks about a software driven serial input called GPi = but I read this part early " Because DCD is monopolized by the mouse on the Macintosh Plus, GPi is not implemented on that machine. Other machines which do not support GPi include the Macintosh Classic and Macintosh LC. On these machines, pins 7 of the DIN-8 serial connectors are not connected." and since I want to target a Plus, this is out of the front burner for me for now.

Serial I/O Port Q&As 1990-10-01 - list of GPi supported macs (again the Plus is off of it), offers no technical solution, only presents generalistic problems about appletalk and the serial port in general

Opening the Serial Driver 1989-08-01 - this is very relevant, but I already got the memo from THINK Reference to not use the deprecated pre-128K ROM function RAMSDOpen to open the port, I was already on the following paradigm of using OpenPort, as I just fetched the code from IM: Serial Driver


so the current status of my needle threading:
-make it so it works on a Plus (it's factually proven that a Plus can deal with this since the commercial software of Master Tracks Pro 4 works on it)
-opening the port is already achieved, sending bytes is already achieved thanks to Inside Mac
-setting the port at the right rate is not achieved - how much asm code must I salvage and what can be left out
 

Mu0n

Well-known member
Interesting parts in IM: Serial Device:

Synchronous Clocking 7 Although the Serial Driver does not support synchronous communication protocols, it does allow you to select an external timing signal for synchronous clocking between the sender and receiver. You connect the external timing signal to the handshake input (HSKi) signal on pin 2 of the serial port, and select external clocking by sending a control request to the output driver with a csCode value of 16 and bit 6 set in the csParam field. See the section “Low-Level Routines,” beginning on page 7-27, for more information.

Low-Level Routines 7 This section describes the low-level Serial Driver routines that you can call using the Device Manager control and status functions. These calls should be made to the output device driver—they affect the input driver as well.

Set Miscellaneous Options [control code 16]
csCode = 16 csParam = byte
This control routine sets miscellaneous control options. Bits 0-5 are reserved and should be set to 0 for compatibility with future options. Bit 6 enables external clocking through the CTS handshake line (the HSKi signal on pin 2 of the serial port). Set bit 6 to 1 to allow an external device to drive the serial data clock. Set bit 6 to 0 to restore internal clocking. Bit 7 controls the state of the DTR signal when the driver is closed. When bit 7 is 0 (the default) the DTR signal is automatically negated when the driver closes. Set bit 7 to 1 if you want the DTR signal to be left unchanged when the driver is closed. This can be used to prevent a modem from hanging up or a printer from going offline when the driver closes.

so I could just drop the asm stuff that seems to lead me astray, go back to the IM: serial device code, and just massage the right data for a PBControl call inserted at the right place to get external clock going?

1719753926565.png
 

Mk.558

Well-known member
Finding out information about what GPi was really used for is not easy. From what I can tell, Apple recommends you use it for DCD on modems, but aside from that, I don't have any information what people used it for.

If you're running an external clock, all the information I have indicates that HSKi is used.
 

joevt

Well-known member
Yes, GPi / DCD is not what we want to use since it doesn't exist in all Macintoshs.

However, I'm at a point where handles are referred to in the code and are never assigned.
For example, fairly early in InitSCCA, there's this line:
C-like:
 LEA    PRxIntHandA,A1  ; point to previous vector stor

and the one place where it might be declared and initialized is past a good bunch of functions, right before the ResetSCCChan one, in this block:
Code:
TxQueueADCB.B  $100,0; this is the queue
TxQEmptyA DC0    ; the queue empty flag
TxByteInA DC0    ; index to next cell in
TxByteOutADC0    ; index to next cell out
RxQueueADCB.B  $400,0; this is the queue
RxQEmptyA DC0    ; the queue empty flag
RxByteInA DC0    ; index to next cell in
RxByteOutADC0    ; index to next cell out
PRxIntHandA DC.L 0 ; Previous interrupt vector
PTxIntHandA DC.L 0 ; Previous interrupt vector

are these the equivalent of globals? Is it normal that they are declared below instead of above? I'm second guessing myself at every step.
Yes, those are globals stored in the code.
P = pointer
Rx = receive
Tx = transmit
Int = interrupt
Hand = handler
A = modem port
B = printer port

PRxIntHandA is used to store the previous modem port receive interrupt handler.
PTxIntHandA is used to store the previous modem port transmit interrupt handler.

They are essentially function pointers. See RxIntHandA to see what registers are used for arguments and what registers you're allowed to modify.

so the current status of my needle threading:
-make it so it works on a Plus (it's factually proven that a Plus can deal with this since the commercial software of Master Tracks Pro 4 works on it)
-opening the port is already achieved, sending bytes is already achieved thanks to Inside Mac
-setting the port at the right rate is not achieved - how much asm code must I salvage and what can be left out
One of the links I posted includes my ADB Analyzer 3 source code. It doesn't use the Serial Port drivers. The OpenSerialPort function checks PortAUse or PortBUse low memory globals to see if the ports are used and if not sets them to indicate to the system that they are being used.

OpenSerialPort is called by OpenADBAnalyzer (reverse engineered code) which passes gADBAnalyzerInitTable to InitializeSCCPort to setup the registers. ADBAnalyzer uses synchronous mode (no start bits or stop bits or parity) to read ADB data using the serial port.

OpenSerialPort is also called by OpenSerialAnalyzer (my code) which passes gSerialAnalyzerInitTable to InitializeSCCPort after changing all the registers to values selected by the user.

Interesting parts in IM: Serial Device:

so I could just drop the asm stuff that seems to lead me astray, go back to the IM: serial device code, and just massage the right data for a PBControl call inserted at the right place to get external clock going?
I think you can use Control instead of PBControl so you don't need to setup a parameter block.
csCode 16 is kSERDMiscOptions. Maybe you need to update your headers if you can't find kSERDMiscOptions

TechNote 1119 - "Serial Port Apocrypha" explains listing and opening serial ports using Open Transport or Communications Resource Manager (part of Communications Toolbox). I didn't include those parts in the code below. I have code that combines both methods since one doesn't give all the info of the other. If you don't want to support arbitrary serial ports then they are not necessary.

C:
#include <Serial.h>
#include "LSerialEndpoint.h"
#include "USerialSupport.h"


LSerialEndpoint::LSerialEndpoint(
	ConstStr255Param	inSerialPortName,
	SInt16				inReceiveBufferSize)
{
	ThrowIfOSErr_( USerialSupport::OpenSerialDrivers( inSerialPortName, mInRefNum, mOutRefNum ) );
	mReceiveBufferSize = inReceiveBufferSize;
	mReceiveBuffer = NewPtrClear( mReceiveBufferSize );
	ThrowIfMemError_();
	ThrowIfOSErr_( SerSetBuf( mInRefNum, mReceiveBuffer, mReceiveBufferSize ) );
}


LSerialEndpoint::~LSerialEndpoint()
{
	SInt16	inRef = mInRefNum;
	SInt16	outRef = mOutRefNum;

	mInRefNum = 0;
	mOutRefNum = 0;

	USerialSupport::CloseSerialDrivers( inRef, outRef );
	if ( mReceiveBuffer )
		DisposePtr( mReceiveBuffer );
}



OSErr
LSerialEndpoint::SetOptions(
									UInt32					inBaudRate,
									EDataBitsOption			inDataBits,
									EParityOption			inParity,
									EStopBitsOption			inStopBits,
									EHandshakeOption		inHandshakeOpt,
									Boolean					inExtClock)
{
	short theSerConfig = baud9600;

	switch ( inDataBits ) {
		case Serial_5Bits	:	theSerConfig += data5; break;
		case Serial_6Bits	:	theSerConfig += data6; break;
		case Serial_7Bits	:	theSerConfig += data7; break;
		case Serial_8Bits	:	theSerConfig += data8; break;
	}
	
	switch ( inParity ) {
		case Serial_NoParity	:	theSerConfig += noParity; break;
		case Serial_OddParity	:	theSerConfig += oddParity; break;
		case Serial_EvenParity	:	theSerConfig += evenParity; break;
	}

	switch ( inStopBits ) {
		case Serial_OneStopBit			:	theSerConfig += stop10; break;
		case Serial_OnePoint5StopBits	:	theSerConfig += stop15; break;
		case Serial_TwoStopBits			:	theSerConfig += stop20; break;
	}


	OSErr err;

	if ( ( err = ::SerReset( mOutRefNum, theSerConfig ) ) != noErr )
		return err;

	if ( inExtClock )
	{
		char theClock = 1 << 6;
		if ( ( err = ::Control( mOutRefNum, kSERDMiscOptions, &theClock ) ) != noErr )
			return err;
	}
	else
	{
		short theBaud = inBaudRate;
		if ( ( err = ::Control( mOutRefNum, kSERDBaudRate, &theBaud ) ) != noErr )
			return err;
	}
	
	{
		SerShk theHandShake;

		theHandShake.fXOn	= 0; // turn off XON/XOFF output flow control
		theHandShake.fCTS	= 0; // turn off CTS/DTR flow control
		theHandShake.xOn	= 'Q' - '@'; // CTRL-Q
		theHandShake.xOff	= 'S' - '@'; // CTRL-S
		theHandShake.errs	= 0; // clear error mask
		theHandShake.evts	= 0; // clear event mask
		theHandShake.fInX	= 0; // turn off XON/XOFF input flow control
		theHandShake.fDTR	= 0; // turn off DTR input flow control
		
		switch ( inHandshakeOpt ) {
			case Serial_NoHandshake	:	break;
			case Serial_CTS_DTR		:	theHandShake.fCTS = 1; theHandShake.fDTR = 1; break;
			case Serial_XON_XOFF	:	theHandShake.fXOn = 1; theHandShake.fInX = 1; break;
		}

		if ( ( err = ::Control( mOutRefNum, kSERDHandshake, &theHandShake ) ) != noErr )
			return err;
	}
	return noErr;
}


void
LSerialEndpoint::SetDTR(
							Boolean			inNegateDTR)
{
	::Control( mOutRefNum, kSERDAssertDTR + inNegateDTR, nil );
}
 

Forrest

Well-known member
The Roland SC88ST has a Mac Serial port on it, as does the Yamaha TG100, MU50 and other MIDI devices of the era. Why are you using the Pocket MAC interface?
 

Mu0n

Well-known member
The Roland SC88ST has a Mac Serial port on it, as does the Yamaha TG100, MU50 and other MIDI devices of the era. Why are you using the Pocket MAC interface?
I don't want the solution to depend on this. I have other modules that should be used for this purpose, like a Roland MT-32, just as it was used without a special system folder extension for MIDI at all in Space Quest III, just a pure code wise solution dealing with this.
 

joevt

Well-known member
Maybe there is an undocumented SerReset value in the 1-3 range for 31250bps.
The baud rate generator uses a two byte value (timeConstant) which divides a clock (between 3.672 and 3.686 MHz).

Calculations:
baud = clockFrequency / ( 2 * clockMode * ( timeConstant + 2 ) )
clockFrequency = baud * ( 2 * clockMode * ( timeConstant + 2 ) )

clockMode is x16. It could also be x32 or x64 but those would just slow the baud rate.

These are all the defined time constants in Serial.h from MPW universal headers. Symantic C++ headers is missing some of these.
Code:
baud=150 timeconstant=763 clock=3672000
baud=300 timeconstant=380 clock=3667200
baud=600 timeconstant=189 clock=3667200
baud=1200 timeconstant=94 clock=3686400
baud=1800 timeconstant=62 clock=3686400
baud=2400 timeconstant=46 clock=3686400
baud=3600 timeconstant=30 clock=3686400
baud=4800 timeconstant=22 clock=3686400
baud=7200 timeconstant=14 clock=3686400
baud=9600 timeconstant=10 clock=3686400
baud=14400 timeconstant=6 clock=3686400
baud=19200 timeconstant=4 clock=3686400
baud=28800 timeconstant=2 clock=3686400
baud=38400 timeconstant=1 clock=3686400
baud=57600 timeconstant=0 clock=3686400
So you see, the closest you can get to 31250 bps is 28800 bps.

You can bypass the baud rate generator to get 57600, 115200, or 230400 bps using clockMode = x64, x32, and x16 respectively.
Code:
baud = clockFrequency / clockMode

There is a clockMode x1 but it can only be used for synchronous modes. Asynchronous mode is required for data input for the DPLL but I suppose you could use synchronous mode for output.

So if you consider clockMode x1, then you could get:
- 31077 bps using a timeConstant of 57 assuming clock = 3667200
- 31118 bps using a time constant of 57 assuming clock = 3672000
- 31240 bps using a timeConstant of 57 assuming clock = 3686400

Of course, using any clock other than the external clock can only work if the receiving MIDI device is asynchronous - meaning it doesn't depend on the clock.

There is another clock "PCLK" that can be selected by the serial controller chip for the baud rate generator. The PCLK is different on different Macs. My B&W G3 has a 22 MHz PCLK and my Power Mac 8600 has a 13 MHz PCLK. It can be measured using my ADB Analyzer 3. I don't think the PCLK would be any more useful than the 3.6 MHz internal clock except that a faster clock could get you closer to 31250 bps using x1 clock mode.
 

ymk

Well-known member
There is a clockMode x1 but it can only be used for synchronous modes. Asynchronous mode is required for data input for the DPLL but I suppose you could use synchronous mode for output.

So if you consider clockMode x1, then you could get:
- 31077 bps using a timeConstant of 57 assuming clock = 3667200
- 31118 bps using a time constant of 57 assuming clock = 3672000
- 31240 bps using a timeConstant of 57 assuming clock = 3686400

Of course, using any clock other than the external clock can only work if the receiving MIDI device is asynchronous - meaning it doesn't depend on the clock.

For output only to a synthesizer, all of those rates should be close enough.

As far as I know, all standard MIDI devices are asynchronous.
 
Top