deathshadow
Veteran Member
- Joined
- Jan 4, 2011
- Messages
- 1,378
Rather than go full threadjack on Trixter's thread about his way cool recovery of the long-lost dreadful-sounding IMF demo, I figured I'd start a new thread in programming answering some questions and relating where I'm at on trying to implement support.
For example:
MPU401 UART
Writing a data byte: wait for the the write flag (0x40) to be set on the command port (0x331), send the data byte on the data port (0x330).
Writing a command byte: Same as data, but write the command byte ot 0x331 instead of 0x330
"command ok" message: you have an outer loop (20 max) that first runs an inner loop (255 max) waiting for the read flag to be set at 0x331, then reads 0x330 to compare against commandOk (0xFE)
Resetting the device: Write MPUReset (0xFF) as a command, if commandOk set UART mode, otherwise MPU401 is not present and/or has a problem.
Sound Blaster "Normal" MIDI
First you need a "DSPWrite" command that accesses the DSP status (typically 0x022C, but could be one of any number of ports), loops until bit 7 is set. You then out the same port the DSP command.
Writing a data byte: for "dumb" mode you send DSPWrite(0x38 ) then DSPWrite(dataByte).
Reading a data byte: loop until 0x022E bit 7 is set, read 0x0226
Resetting the device: Same as you would for setting up for digital audio -- and is why SB digital Audio the same time as SB Midi is unreliable and quite often disastrous; at least on early models. Basically you out 0x226, 0x01 - then wait a LONG unspecified period of time (usually 256 loops of DEC is sufficient), out 0x226, 0 - then loop until 0x226 bit 7 goes high. If it fails to in say, 63356 loops, the DSP failed to initilize so bomb out with an error. If it succeeds you read 0x222 and throw away the result (!?!) then compare 0x226 with 0xAA, if true it worked, if it fails you keep looping.
PHEW.
IBM Music Feature
Assuming it's at 0x2A20 (could also be at 0x2A30)
Writing Command OR Data: You loop until 0x2A22 (PIU2) bit 1 (txReady) is set, if it's not set in a reasonable amount of time (256 loops) it's not there so bomb out. If it is there you out 0x2A28 either 0x10 for a command or 0x00 for Data. You then out that command or data to 0x2A21 (PUI1)
Reading Command Or Data: Again loop until 0x2A22 (PIU2) has a bit set, though in this case it's bit 3 (rxReady). Takes too long, bomb out... when/if it is set you pull bit 7 from that same port as if that's set it's a command response, if it isn't it's MIDI data. You then read 0x2A20 (PIU0) for the actual command or midi data.
Resetting the device: Send the reset command ($E5), wait an arbitrary amount of time (0x0400 loops of "in al, 0x61" should be good as always -- untested) for it to actually happen, then loop reads until the same command is sent back to us. Then to set it for Internal Synth or External port we send command 0xE0 followed by 0x00 for internal or 0x01 for external MIDI. This resets both the Internal Synth and the MIDI device.
They are all similar, but they are NOT the same.
Again though, sending MIDI isn't as simple as say... Serial. Mostly due to needlessly and pointlessly convoluted implementations. Resetting and configuring the devices being the actual "hard" part.
Made harder by REALLY crappy documentation on IBM's part.
If anything reading that thing I'm half tempted to knee-jerk Samuel L. Jackson style into "Ácweðan hwá ágéncuman! Ácweðan hwá ágéncuman! Ic néðe þú, Ic twifealde néðe þú! Englisc, modor-wyrter! Gedon eow cweþan hit!?!". That's called getting medieval on someones tuchas. -- It almost reads like Twitter generation bullshit; laughable since it was written decades before there even was such a thing.
It took me FOUR re-reads of it just to decipher that if bit 4 on the TCR is on you send IMF commands on the data port (PIU1), otherwise the data port is dumb midi writes... and laughably that's 90%+ of what I needed to know; Why the hell they couldn't just say that cleanly/clearly and instead insist on dicking around trying to pass that as a full word is beyond me. MUCH LESS IF you're going to pass it as a word, wouldn't it be better to pass 0x10 instead of 0x01 in the high word since *SHOCK* that's actually what's being used? You know, making all their 0x01BE be 0x10BE since that's what's ACTUALLY sent to the ports? :/
Of course, sending 0x10nn and recieving 0x80nn would be WAY too simple an answer. Documenting commands as commands and midi as MIDI would be way too simple... Putting stuff in a logical order instead of splitting it between chapters willy-nilly so you're constantly scanning over 2 pages worth of text spread out across 20 pages hunting for "what category do THEY consider this command to be part of" would have been WAY too simple for IBM to go and do.
Their code, their examples, and the documentation almost reads like whoever wrote it didn't understand the device or x86 machine language enough to do EITHER. I've not been this annoyed with documentation since the last time I was in the PCJr tech ref which to be frank is equally rubbish. I got more out of the badly coded and poorly documented CMS.ZIP unit for TP that's buried over on cdtextfiles.
The other 10% of what I needed just being how to send a reset, select thru vs. music, and set the pitch bender range; the last of which was far better explained in the FB01 manual...
I also disliked how their code-base left retry up to the caller; a delay on the check for say... 256 loops wouldn't be the end of the world to have included.
Which I THINK I've got down solid with this...
Assembly write commands:
My setup for internal synth or external port being:
Though I'm still a bit sketchy on decoding the nodes and instrument assignments, so the latter will likely go unused.
Kinda wish I could find more of these boards... I've not seen another one in a decade or more.
I was actually thinking that it might be really cool if we could have a modern board that had pinouts for connecting something like a PI or a cubieBoard directly to the PC bus -- the Cubie might be better as it actually has a proper number of inputs (as opposed to the Pi being so anemic on inputs I usually have to put an AtMega on USB in front of it) you could have it hooked up to monitor the entire PC bus. It could then be used to emulate any number of possible devices from ROM, to mapped RAM, to disk controllers, to sound cards.
But that's probably a noodle-doodle pipe-dream idea.
In any case, I'll probably be uploading a test executable later today that should just play the pac man theme; It will probably be the full current monolithic package of sound card support which currently means:
Local Synths:
Adlib
CMS
Tandy/Jr
Speaker (arpeggio)
Midi Devices:
MPU401 UART
SoundBlaster proprietary MIDI (aka through the DSP, not via UART mode)
IMF external MIDI
IMF internal MIDI
With patch/maps and specific behaviors for the following MIDI synths:
MT-32
GM
FB-01
and will hopefully soon add:
Innovation SSI 2001 (Commodore SID on a PC card)
Covox Sound Master (AY8930/P)
I'm having fun with it in a nostalgic sort of way -- back in the day I wrote a LOT of software for systems that hadn't even been built yet; blind implementation is tons of fun.
Laugh is even in it's current state it's only around 18k of code total; a full 6k less than my previous implementation -- and it's far, FAR faster since I'm using procedural variables. I've been implementing pointered lists of constants (TP constants are funny, they ARE writable if you know how -- so I have next: pSoundCardInfo as nil inside tSoundCardInfo, with firstCard,lastCard, currentCard: pSoundCardInfo to build the list.) for adding devices by simply including their unit allowing me to scan for both command line parameters and for autodetection without hardcoding any of it.
The other approach would be to load them as TSR's (I'm pretty sure that's what Sierra did) but one of my objectives here is to have the new distribution be self-contained in a single executable. The ONLY external file is going to be the high score list.
BTW, does anyone have any hard docs on PS/1 audio? 3 Voice similar to Junior, but apart from one thread on Vogons my Google-fu is failing me.
The thing is not all controller cards function even remotely the same. MPU-401 doesn't work how the Sound Blaster's does (at least not until DSP 2.0 added UART mode), neither of those work how the IMF's implementation works, etc, etc... It's not as simple as saying "use midi on this port" if the port implementations are wildly different.Isn't the MFC just a Yamaha FB-01 on the other end of a MIDI controller card?
For example:
MPU401 UART
Writing a data byte: wait for the the write flag (0x40) to be set on the command port (0x331), send the data byte on the data port (0x330).
Writing a command byte: Same as data, but write the command byte ot 0x331 instead of 0x330
"command ok" message: you have an outer loop (20 max) that first runs an inner loop (255 max) waiting for the read flag to be set at 0x331, then reads 0x330 to compare against commandOk (0xFE)
Resetting the device: Write MPUReset (0xFF) as a command, if commandOk set UART mode, otherwise MPU401 is not present and/or has a problem.
Sound Blaster "Normal" MIDI
First you need a "DSPWrite" command that accesses the DSP status (typically 0x022C, but could be one of any number of ports), loops until bit 7 is set. You then out the same port the DSP command.
Writing a data byte: for "dumb" mode you send DSPWrite(0x38 ) then DSPWrite(dataByte).
Reading a data byte: loop until 0x022E bit 7 is set, read 0x0226
Resetting the device: Same as you would for setting up for digital audio -- and is why SB digital Audio the same time as SB Midi is unreliable and quite often disastrous; at least on early models. Basically you out 0x226, 0x01 - then wait a LONG unspecified period of time (usually 256 loops of DEC is sufficient), out 0x226, 0 - then loop until 0x226 bit 7 goes high. If it fails to in say, 63356 loops, the DSP failed to initilize so bomb out with an error. If it succeeds you read 0x222 and throw away the result (!?!) then compare 0x226 with 0xAA, if true it worked, if it fails you keep looping.
PHEW.
IBM Music Feature
Assuming it's at 0x2A20 (could also be at 0x2A30)
Writing Command OR Data: You loop until 0x2A22 (PIU2) bit 1 (txReady) is set, if it's not set in a reasonable amount of time (256 loops) it's not there so bomb out. If it is there you out 0x2A28 either 0x10 for a command or 0x00 for Data. You then out that command or data to 0x2A21 (PUI1)
Reading Command Or Data: Again loop until 0x2A22 (PIU2) has a bit set, though in this case it's bit 3 (rxReady). Takes too long, bomb out... when/if it is set you pull bit 7 from that same port as if that's set it's a command response, if it isn't it's MIDI data. You then read 0x2A20 (PIU0) for the actual command or midi data.
Resetting the device: Send the reset command ($E5), wait an arbitrary amount of time (0x0400 loops of "in al, 0x61" should be good as always -- untested) for it to actually happen, then loop reads until the same command is sent back to us. Then to set it for Internal Synth or External port we send command 0xE0 followed by 0x00 for internal or 0x01 for external MIDI. This resets both the Internal Synth and the MIDI device.
They are all similar, but they are NOT the same.
... which that part I'm fine on. Once I get it sending MIDI, that's the easy-peasy part; I already had FB-01 support working on other MIDI port types (before mine went kaboom).The FB-01's documentation is reasonably clear.
Again though, sending MIDI isn't as simple as say... Serial. Mostly due to needlessly and pointlessly convoluted implementations. Resetting and configuring the devices being the actual "hard" part.
Made harder by REALLY crappy documentation on IBM's part.
Extensive? Methinks we have a different definition of that word. The PAMPHLET you linked to with it's invalid assembly (since the port addresses are 16 bit you can't do "in al, PIU2"), broken and confusing "Engrish moist goodry", and "how few words can we put on a page" making it near impossible to follow the text... extensive is NOT a word that comes to mind.The official documentation is extremely clear. The card is capable of much more than you think it would be (for starters, it has an independent 8253 PIT, interrupts, and more), so naturally the documentation is extensive.
If anything reading that thing I'm half tempted to knee-jerk Samuel L. Jackson style into "Ácweðan hwá ágéncuman! Ácweðan hwá ágéncuman! Ic néðe þú, Ic twifealde néðe þú! Englisc, modor-wyrter! Gedon eow cweþan hit!?!". That's called getting medieval on someones tuchas. -- It almost reads like Twitter generation bullshit; laughable since it was written decades before there even was such a thing.
It took me FOUR re-reads of it just to decipher that if bit 4 on the TCR is on you send IMF commands on the data port (PIU1), otherwise the data port is dumb midi writes... and laughably that's 90%+ of what I needed to know; Why the hell they couldn't just say that cleanly/clearly and instead insist on dicking around trying to pass that as a full word is beyond me. MUCH LESS IF you're going to pass it as a word, wouldn't it be better to pass 0x10 instead of 0x01 in the high word since *SHOCK* that's actually what's being used? You know, making all their 0x01BE be 0x10BE since that's what's ACTUALLY sent to the ports? :/
Of course, sending 0x10nn and recieving 0x80nn would be WAY too simple an answer. Documenting commands as commands and midi as MIDI would be way too simple... Putting stuff in a logical order instead of splitting it between chapters willy-nilly so you're constantly scanning over 2 pages worth of text spread out across 20 pages hunting for "what category do THEY consider this command to be part of" would have been WAY too simple for IBM to go and do.
Their code, their examples, and the documentation almost reads like whoever wrote it didn't understand the device or x86 machine language enough to do EITHER. I've not been this annoyed with documentation since the last time I was in the PCJr tech ref which to be frank is equally rubbish. I got more out of the badly coded and poorly documented CMS.ZIP unit for TP that's buried over on cdtextfiles.
The other 10% of what I needed just being how to send a reset, select thru vs. music, and set the pitch bender range; the last of which was far better explained in the FB01 manual...
I also disliked how their code-base left retry up to the caller; a delay on the check for say... 256 loops wouldn't be the end of the world to have included.
Which I THINK I've got down solid with this...
Assembly write commands:
Code:
IMFWrite:
; INPUT
; BH = DATA
; BL = Command (0x10) or MIDI (0x00)
; OUTPUT
; DX = IMFPort + 1 (PIU1)
; CX = Zero on failure, non-zero on success
; CORRUPTS
; AX
mov ax, 0x0100
mov cx, ax
mov dx, IMFPort
inc dx
inc dx
cli
.loop:
in al, dx
test al, ah
loopz .loop
jz .done
mov al, bl
add dx, 6
out dx, al
mov al, bh
sub dx, 7
out dx, al
.done:
sti
ret
; procedure IMFWriteCommand(data:byte);
pProcArgs IMFWriteCommand
mov bl, 0x10
mov bh, [bp + 6]
call IMFWrite
pRet 2
; procedure IMFWriteMIDI(data:byte);
pProcArgs IMFWriteMIDI
xor bl, bl
mov bh, [bp + 6]
call IMFWrite
pRet 2
; function IMFRead:integer;
pProcNoArgs IMFRead
mov dx, IMFPort
inc dx
inc dx
mov cx, 0x0100
cli
.loop:
in al, dx
test al, 0x08
loopz .loop
jz .failed
and al, 0x80
mov ah, al
dec dx
dec dx
in al, dx
sti
retf
.failed:
mov ax, -1
sti
retf
My setup for internal synth or external port being:
Code:
function IMFLocalSetup(mode:byte):boolean;
var
i, c:integer;
begin
IMFWriteCommand($E5); { reboot }
InALx61Delay($0400);
c := $0100;
repeat
i := IMFRead;
dec(c);
until (i = $80E5) or (i < 0) or (c = 0);
if (i < 0) or (c = 0) then begin
IMFLocalSetup := false;
end else begin
IMFWriteCommand($E0);
IMFWriteCommand(mode);
IMFLocalSetup := true;
end;
end;
function IMFSynthSetup:boolean;
begin
IMFPort := IMFMidiCard.cardPort;
if (IMFLocalSetup($00)) then begin
IMFSynthSetup := FB01Setup;
end else IMFSynthSetup := false;
end;
function IMFExternalSetup:boolean;
begin
IMFPort := IMFMidiCardExt.cardPort;
IMFExternalSetup := IMFLocalSetup($01);
end;
Though I'm still a bit sketchy on decoding the nodes and instrument assignments, so the latter will likely go unused.
Only one I can think of is channel aftertouch. Most of the rest of it is just there so you can try to brute-force implement things other synths already do. (like auto-determining voice to channel assignments)The MIDI module itself has some features that the MT-32 doesn't
VC_PITCH_FINE in the instrument parameters... or you could just restrict the pitch bender range. Setting it in the program/patch can actually be very useful as you can set multiple patches of the same timbre with different de-tune rates.such as extremely fine pitch adjustment.
One of the things I've held onto is a 8 bit card I bought from Jameco a few decades ago called a "DiagnoSys 8x8" -- you can configure it through dip switches to sit at any base port, with it occupying 8 ports in a row. It provides latches for the last written values, a tiny breadboard mapped to the latches, and a series of LED's to show you what's currently there for values. I'm running PIU1's values to a cheap arduino nano knockoff so I can at least send the midi output stream as MIDI (right now rigged to my SC-7 since my FB01 is now a writeoff) and fake the proper return values for the setup routine.How are you testing then?
Kinda wish I could find more of these boards... I've not seen another one in a decade or more.
I was actually thinking that it might be really cool if we could have a modern board that had pinouts for connecting something like a PI or a cubieBoard directly to the PC bus -- the Cubie might be better as it actually has a proper number of inputs (as opposed to the Pi being so anemic on inputs I usually have to put an AtMega on USB in front of it) you could have it hooked up to monitor the entire PC bus. It could then be used to emulate any number of possible devices from ROM, to mapped RAM, to disk controllers, to sound cards.
But that's probably a noodle-doodle pipe-dream idea.
In any case, I'll probably be uploading a test executable later today that should just play the pac man theme; It will probably be the full current monolithic package of sound card support which currently means:
Local Synths:
Adlib
CMS
Tandy/Jr
Speaker (arpeggio)
Midi Devices:
MPU401 UART
SoundBlaster proprietary MIDI (aka through the DSP, not via UART mode)
IMF external MIDI
IMF internal MIDI
With patch/maps and specific behaviors for the following MIDI synths:
MT-32
GM
FB-01
and will hopefully soon add:
Innovation SSI 2001 (Commodore SID on a PC card)
Covox Sound Master (AY8930/P)
I'm having fun with it in a nostalgic sort of way -- back in the day I wrote a LOT of software for systems that hadn't even been built yet; blind implementation is tons of fun.
Laugh is even in it's current state it's only around 18k of code total; a full 6k less than my previous implementation -- and it's far, FAR faster since I'm using procedural variables. I've been implementing pointered lists of constants (TP constants are funny, they ARE writable if you know how -- so I have next: pSoundCardInfo as nil inside tSoundCardInfo, with firstCard,lastCard, currentCard: pSoundCardInfo to build the list.) for adding devices by simply including their unit allowing me to scan for both command line parameters and for autodetection without hardcoding any of it.
The other approach would be to load them as TSR's (I'm pretty sure that's what Sierra did) but one of my objectives here is to have the new distribution be self-contained in a single executable. The ONLY external file is going to be the high score list.
BTW, does anyone have any hard docs on PS/1 audio? 3 Voice similar to Junior, but apart from one thread on Vogons my Google-fu is failing me.