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

Relating to ThinkC Development

Mu0n

Active Tinkerer
Oct 29, 2021
578
532
93
Quebec
www.youtube.com
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.
1719603138321.png

(source of this capture: https://ccrma.stanford.edu/~gary/controllers/midi.html#:~:text=MIDI is transmitted as asynchronous,is a "data" byte.)

But, SerRest, PBWrite, OpenDriver and such from the inside mac reference above allow me these settings:
1719603147709.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!
 
Last edited:

Mu0n

Active Tinkerer
Oct 29, 2021
578
532
93
Quebec
www.youtube.com
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
    }
 

joevt

Tinkerer
Mar 5, 2023
45
27
18
Other thread: https://68kmla.org/bb/index.php?threads/transformers-for-homemade-localtalk-phonenet-dongles.47052

Go to https://developer.apple.com/library/archive/navigation/#section=Platforms&topic=macOS
Search for Serial.
Read all the info.
TN1018 mentions MIDI but I don't think SE/30 has the SerialDMA Driver?

Does MIDI use GPi? DV16 has info. TN1018 says MIDI uses CTS which is actually HSKi.

HW545 says SE/30 doesn't have GPi so it must be HSKi.

Here's some notes (which leads to more notes and source code):
https://68kmla.org/bb/index.php?thr...-localtalk-phonenet-dongles.47052/post-527878