deathshadow
Veteran Member
- Joined
- Jan 4, 2011
- Messages
- 1,378
Just thought I'd share the next part of my rewrite of my DOS game engine, the sound engine.
As I've been switching to putting everything in the data segment instead of using the heap and heading towards a monolithic executable, the music playback system needs an overhaul since I was dynamically allocating space for not just the music, but things like MIDI SYSEX, Patch Data, and even the music itself.
I also needed a rewrite (as my other thread exists to try to get help testing) of the C/MS code as it wasn't working on all C/MS setups. Was good on my 1320B, was good on my game blaster, was good in DOSBox, but didn't work on a real "C/MS" card or any other models of Blasters with the SA1099 chips in them. I THINK I got it right with this version of the code:
Though if anyone wants to give that a once over I'd love to hear any suggestions. (I'm always screwing up when to use ja/jg vs. jb/jl - cannot keep those straight!)
One of the big changes is that in the old game I was using an if statement EVERY time a sound was being set to point it at the code for the correct card -- while this is the method I see most people using, it's not exactly the best way of going about it. As such I've switched to using procedural types and procedural variables so that at the start I can do the IF to point at the correct procedure for the correct card, removing the IF inside the call.
So on and so forth.
I've also set up a two voice PC speaker arpeggio in the 120hz game timer -- if only one voice is playing that note is held, but if both voices are playing it arpeggio's. I only load the speaker timer version if the speaker is selected, otherwise I load a simpler version so as to not suck down extra time for nothing.
I keep feeling like that could be more efficiently written, but it works.
I found a MAJOR bug in my tandy/jr sound code that came from doing a copypasta of someone else's code... bits 4, 5 and 6 of the command byte is listed in the docs as channel select -- and whatever code I was working from took that literally... but that's NOT the whole picture. Only 5 and 6 select the channel -- bit 4 selects if you are setting frequency (0) or amplitude (1)
The code I had gotten/was using was doing:
mov al, 0x10
mov cl, voice
shl al, cl
Which is just WRONG. Like... crazy wrong. It meant that the lower frequency bit was setting amplitude, which is why the tone was off slightly and volume setting was jacked.
The PROPER code...
mov al, voice
mov cl, 3
and al, cl ; just to be on the safe side
ror al, cl
using cl as the mask and the rotate amount is a nice double-whammy, as is that the rotate by 3 is faster than doing a shift by five to the left.
*** side note *** I'm shocked how many audio codebases I look at do NOT do any input sanitization
I'm expanding the MIDI support a good deal as well in that I'm adding code for more than just the MPU-401 style interface. I've already got the Sound Blaster DSP MIDI up and working since I FINALLY found a document that had some REALLY good code examples:
http://www.phatcode.net/articles.php?id=243
I'm used to the code presented in such documents being garbage (like say, the stuff for the IMF) -- I've not seen that one before and it was a breath of fresh air. Being a Borland fan I'm a stickler for good documentation! Probably why I like PHP even though I hate PHP. The SB "Normal" MIDI code (I'm not using "UART" mode since that's DSP 2.0+ only) I'm pretty much using from that PDF with no real modifications... their reset function seems a bit wonky, but after playing with a few different variations I understand the choices they made.
IMF (IBM Music Feature) is still on my 'to be supported' list, though my FB-01 went up with a bang and cloud of smoke (cap blew with enough force to crack the board and lift traces, I'm not all that hopeful on ressurecting it but I'm gonna try) and I don't have a real IMF to test with... I'm also still playing with making a FB-01 softsynth emulation on the pi and/or cubieboard. I also have a ... crazy idea related to such project boards I'm going to be playing with; directly interfacing the GPIO
Are there any other semi-common MIDI interfaces? I was thinking on adding the non MPU-401 style MIDIMAN support (like the 2x2 and 2x4), but I'm not certain it's worth the effort.
One new feature I'm starting coding on today is for the Innovation SSI-2001, a card you rarely if ever hear about... for those of you unfamiliar with it the card is a Commodore/MOS SID for the PC, with an 8 bit DIGITAL joystick interface (that maps/pretends to be analog like many gamepads do). Honestly I never even heard of the card until... well...
http://www.vintage-computer.com/vcf...-quot-The-Entertainer-quot-sound-card-exposed
Even though I don't have one, DOSBox has a very good implementation since SID emulation as a whole is quite mature at this point (though the incorrect clock isn't 100% right compared to real hardware) -- and coding for it should go pretty quick since I already wrote SID code for the C64 version of Paku Paku... All I have to do is port my C / 6502 ASM to Pascal / 8086 ASM since apparently it just maps the 29 SID registers to a range of ports (typically 0x0280..0x29D) -- easy peasy.
I'm also looking for information on programming the Covox Sound Master -- I figure it can't be too different from programming the mockingboard in my IIe Platinum... but I can't find any actual programming examples for it on the PC much less information on where/how the card is even mapped... that I don't actually HAVE said card and there's no emulation (that I'm aware of) is also a bit limiting; but I figure AY-3-8910 is AY-3-8910.
One further thing I'm playing with is setting a much higher timer rate on faster (386+, MAYBE faster 286) systems to allow for 1.5 bit PC speaker support and Covox/Disney software synth. (I'm thinking 4.8khz should be sufficient for parallel, not sure about speaker) -- For my needs I only NEED two voices, though for the parallel port DAC version I'll probably aim for 4 voice with 6 bits per voice (so it's simple addition mixing with no shifting or divides)... I may also allow the soft-synth to output to the 'blaster's DSP just for laughs, possibly even defaulting to that instead of Adlib on the 'blaster since I really HATE how Yamaha FM sounds. (always have!). The speaker version is probably just going to be square wave based, I'm not sure if it's worth my time (or the RAM footprint) to have a SIN lookup or the logic for triangle / sawtooth for the 8 bit output, or if I should just "add al, 0x40" to go square wave there too. Sometimes simpler is better.
Basically I'm devoting the month of November to sound support. I'm planning on when complete uploading not just a test of it, but the full source of the test so if anyone else wants to run with it, they can.
Any advice or opinions welcome.
As I've been switching to putting everything in the data segment instead of using the heap and heading towards a monolithic executable, the music playback system needs an overhaul since I was dynamically allocating space for not just the music, but things like MIDI SYSEX, Patch Data, and even the music itself.
I also needed a rewrite (as my other thread exists to try to get help testing) of the C/MS code as it wasn't working on all C/MS setups. Was good on my 1320B, was good on my game blaster, was good in DOSBox, but didn't work on a real "C/MS" card or any other models of Blasters with the SA1099 chips in them. I THINK I got it right with this version of the code:
Code:
; Creative Music Source dual SA1099 Sound support
; Jason M. Knight, October 2014
; Intended for compilation with NASM for use with TP7
; labels starting with "i_" are for local/internal use only
BITS 16
CPU 8086
%include "TURBOPAS.MAC"
segment CONST
; CMS registers are read only, so we have to buffer these ourselves!
cmsOctaveStore dw 0x0000, 0x0000, 0x0000
cmsFreqBits dw 0x0000
cmsNoiseBits dw 0x0000
cmsFreqMap: ; convert 0..243 to 0..255 with SAA1099 freq curve
db 0, 2, 3, 5, 7, 9, 11, 13
db 15, 17, 19, 21, 24, 26, 28, 30
db 32, 33, 35, 37, 39, 40, 42, 45
db 47, 48, 50, 51, 53, 55, 57, 58
db 60, 61, 63, 65, 66, 67, 68, 70
db 71, 73, 74, 76, 77, 79, 80, 82
db 83, 85, 86, 87, 88, 90, 91, 93
db 95, 96, 97, 98, 100, 101, 103, 104
db 105, 106, 108, 109, 111, 112, 113, 114
db 115, 116, 118, 119, 120, 121, 123, 124
db 125, 126, 127, 129, 130, 131, 132, 133
db 134, 135, 136, 137, 138, 140, 141, 142
db 143, 144, 145, 146, 148, 149, 150, 151
db 152, 153, 154, 155, 156, 157, 158, 159
db 160, 161, 162, 162, 163, 164, 165, 166
db 167, 168, 170, 171, 172, 173, 174, 174
db 175, 176, 177, 178, 179, 180, 181, 182
db 182, 183, 184, 185, 186, 187, 188, 189
db 190, 191, 192, 193, 193, 194, 195, 196
db 196, 197, 198, 199, 200, 200, 201, 202
db 203, 203, 204, 205, 206, 206, 208, 208
db 209, 209, 210, 211, 212, 212, 213, 213
db 214, 215, 216, 217, 217, 218, 218, 219
db 219, 220, 221, 222, 222, 223, 224, 225
db 225, 226, 226, 227, 227, 228, 228, 229
db 230, 231, 232, 232, 233, 233, 234, 234
db 235, 235, 236, 236, 237, 238, 239, 239
db 240, 240, 241, 241, 242, 243, 243, 244
db 244, 245, 245, 246, 246, 247, 247, 248
db 249, 250, 250, 251, 251, 252, 252, 253
db 253, 254, 254, 255
segment CODE
; let's do the math here instead of during execution
%define cmsFreqMin 32
%define cmsFreqMax 7823
%define cmsFreqLo 245
%define cmsFreqHi 489
%define cmsFreqMapOffsetMinusLo cmsFreqMap - cmsFreqLo
extern cmsSoundPort
i_WriteByteBoth:
; INPUT
; dx = soundport
; al = register
; ah = data
; PRESERVES
; all
add dx, 3
out dx, al
dec dx
xchg ah, al
out dx, al
dec dx
xchg ah, al
out dx, al
dec dx
xchg ah, al
out dx, al
xchg ah, al
ret
i_WriteWordBoth:
; INPUT
; dx = soundport
; al = register
; bx = data
; PRESERVES
; all
mov ah, al ; save for second output
add dx, 3
out dx, al ; oAL
dec dx
xchg bl, al ; BL = oAL, AL = oBL, BH = oBH
out dx, al ; oBL
xchg bh, al ; BL = oAL, AL = oBH, BH = oBL
out dx, al ; oBH
dec dx
xchg bl, al ; BL = oBH, AL = oAL, BH = oBL
out dx, al ; oAL
dec dx
xchg bh, al ; BL = oBH, AL = oBL, BH = oAL
out dx, al ; oBL
xchg bl, al ; BL = oBL, AL = oBH, BH = oAL
out dx, al ; oBH
xchg bh, al ; original order.
ret
i_CmsSelectPort:
; INPUT
; [bp + 6] == voice 0..11
; OUTPUT
; bx = cms register (0..5)
; dx = port (0x02?1 or 0x02?3)
mov bx, [bp + 6]
mov dx, [cmsSoundPort]
inc dx
cmp bx, 6
js .done
sub bx, 6
inc dx
inc dx
.done:
ret
i_CmsFindBits:
; INPUT
; al = register to set
; bx = voice register 0..5
; dx = chip register port (typically 0x02?1 or 0x02?3)
; OUTPUT
; al == bitmask
; bx == address of appropriate cmsFreqBits byte
; dx == chip data port (typically 0x02?0 or 0x2?2)
; CORRUPTS
; cl
out dx, al
dec dx
mov cl, bl
mov al, 1
shl al, cl
mov bx, cmsFreqBits
test dx, 0x0002
jz .done
inc bx
.done:
ret
i_CmsSetAL:
; INPUT
; al = bit mask
; bx = address of cmsBits byte
; dx = chip data port
or [bx], al
mov al, [bx]
out dx, al
ret
i_CmsMaskAL:
; INPUT
; al = bit mask
; bx = address of cmsBits byte
; dx = chip data port
; CORRUPTS
; al (masks off top two bits)
not al
and al, 0x3F
and [bx], al
mov al, [bx]
out dx, al
ret
i_SetFreq:
; INPUT
; ah = freq
; bx = voice 0..5
; dx = port 0x02?1 or 0x02?3
; OUTPUT
; none
; CORRUPTS
; al
mov al, bl
or al, 0x08
out dx, al
dec dx
mov al, ah
out dx, al
inc dx
ret
i_SetOctave:
; INPUT
; ch = octave
; bx = voice 0..5
; dx = port 0x02?1 or 0x2?3
; OUTPUT
; none
; CORRUPTS
; al, bx
mov al, bl
shr al, 1
or al, 0x10
out dx, al
dec dx
shr bx, 1
mov al, [bx + cmsOctaveStore]
jc .setHigh
; setLow
and al, 0xF0
jmp .done
.setHigh:
and al, 0x0F
mov cl, 4
shl ch, cl
.done:
or al, ch
out dx, al
inc dx
mov [bx + cmsOctaveStore], al
ret
; procedure cmsReset
pProcNoArgs cmsReset
mov ax, ds
mov es, ax
xor ax, ax
mov di, cmsOctaveStore
stosw
stosw
stosw
stosw ; cmsFreqBits
stosw ; cmsNoiseBits
mov dx, [cmsSoundPort]
mov cx, 0x20
.loopZero:
call i_WriteByteBoth
inc al
loop .loopZero
mov al, 0x1C
mov bx, 0x0102
call i_WriteWordBoth
mov ax, 0x0015
call i_WriteByteBoth
mov ax, 0x0014
call i_WriteByteBoth
mov ax, 0x0016
call i_WriteByteBoth
mov cx, 6
mov ax, 0x8800
.loopVolume:
call i_WriteByteBoth
inc al
loop .loopVolume
retf
; proceudre cmsSetAmplitude(left, right, voice:word);
pProcArgs cmsSetAmplitude
call i_CmsSelectPort
mov al, bl
out dx, al
mov al, [bp + 8]
mov cl, 4
shr al, cl
add al, [bp + 10]
dec dx
out dx, al
pRet 6
; procedure cmsEnableFreq(voice:word);
pProcArgs cmsEnableFreq
call i_CmsSelectPort
mov al, 0x14
call i_CmsFindBits
call i_CmsSetAL
pRet 2
; procedure cmsDisableFreq(voice:word);
pProcArgs cmsDisableFreq
call i_CmsSelectPort
mov al, 0x14
call i_CmsFindBits
call i_CmsMaskAL
pRet 2
; procedure cmsEnableNoise(voice:word);
pProcArgs cmsEnableNoise
call i_CmsSelectPort
mov al, 0x15
call i_CmsFindBits
call i_CmsSetAL
pRet 2
; procedure cmsDisableNoise(voice:word);
pProcArgs cmsDisableNoise
call i_CmsSelectPort
mov al, 0x15
call i_CmsFindBits
call i_CmsMaskAL
pRet 2
; procedure cmsSetFreq(freq, voice:word);
pProcArgs cmsSetFreq
call i_CmsSelectPort
mov ah, [bp + 8]
call i_SetFreq
pRet 4
; procedure cmsSetOctave(octave, voice:word);
pProcArgs cmsSetOctave
call i_CmsSelectPort
mov ch, [bp + 8]
call i_SetOctave
pRet 4
; function cmsSetHz(hz, voice:word);
pProcArgs cmsSetHz
; first let's turn off this voice
call i_CmsSelectPort ; bx == register 0..5, dx == 0x2?1 or 0x2?3
mov di, bx
mov si, dx
mov al, 0x14
call i_CmsFindBits
call i_CmsMaskAL
; determine freq and octave from hz
mov bx, [bp + 8]
cmp bx, cmsFreqMin
jl .exit
cmp bx, cmsFreqMax
jg .exit
mov ch, 3
; I HATE "WHILE" LOGIC!!!
.loopDownOctave:
cmp bx, cmsFreqLo
jg .loopUpOctave
shl bx, 1
dec ch
jmp .loopDownOctave
.loopUpOctave:
cmp bx, cmsFreqHi
jl .calcFreq
shr bx, 1
inc ch
jmp .loopUpOctave
.calcFreq:
mov ah, [bx + cmsFreqMapOffsetMinusLo]
; AH = freq, CH = OCTAVE
mov bx, di
mov dx, si
call i_SetFreq
call i_SetOctave
; turn this voice back on.
mov bx, di
mov al, 0x14
call i_CmsFindBits
call i_CmsSetAL
.exit:
pRet 4
Though if anyone wants to give that a once over I'd love to hear any suggestions. (I'm always screwing up when to use ja/jg vs. jb/jl - cannot keep those straight!)
One of the big changes is that in the old game I was using an if statement EVERY time a sound was being set to point it at the code for the correct card -- while this is the method I see most people using, it's not exactly the best way of going about it. As such I've switched to using procedural types and procedural variables so that at the start I can do the IF to point at the correct procedure for the correct card, removing the IF inside the call.
Code:
case soundCard of
sound_none:begin
setFreq := quiet2;
setNote := quiet2;
setVolume := quiet3;
end;
sound_pcSpeaker:begin
speakerEnable;
setFreq := speakerSetHz;
setNote := speakerSetNote;
setVolume := quiet3;
end;
sound_adlib:begin
adlibReset;
adlibDefaultAllVoices;
setFreq := adlibSetHz;
setNote := adlibSetNote;
setVolume := adlibSetVolume;
end;
So on and so forth.
I've also set up a two voice PC speaker arpeggio in the 120hz game timer -- if only one voice is playing that note is held, but if both voices are playing it arpeggio's. I only load the speaker timer version if the speaker is selected, otherwise I load a simpler version so as to not suck down extra time for nothing.
Code:
checkVoice:
; ACCEPTS
; BX offset into speaker voices
; CORRUPTS
; AX, CX
; RETURNS
; ZF set == no playback, unset == playback
mov cx, WORD [bx + voices]
or cx, cx
jz .done
mov al, 0xB6
out 0x43, al
mov al, cl
out 0x42, al
mov al, ch
out 0x42, al
in al, 0x61
or al, 3
out 0x61, al
.done:
ret
speakerTimerISR:
push ax
push bx
push cx
push ds
mov ax, DATA
mov ds, ax
; uncomment next two lines to slow the arpeggio to 60hz
; xor [speakerFlags], BYTE 0x80
; jns .tick
mov bx, [currentVoice]
xor bx, 2
mov [currentVoice], bx
call checkVoice
jnz .tick
xor bx, 2
call checkVoice
jnz .tick
in al, 0x61
and al, 0xFC
out 0x61, al
.tick:
inc WORD [tickCounter]
sub [countISR], countDec
jns .done
add [countISR], countInc
pushf
call far [oldISR]
.done:
pop ds
pop cx
pop bx
pop ax
iret
I keep feeling like that could be more efficiently written, but it works.
I found a MAJOR bug in my tandy/jr sound code that came from doing a copypasta of someone else's code... bits 4, 5 and 6 of the command byte is listed in the docs as channel select -- and whatever code I was working from took that literally... but that's NOT the whole picture. Only 5 and 6 select the channel -- bit 4 selects if you are setting frequency (0) or amplitude (1)
The code I had gotten/was using was doing:
mov al, 0x10
mov cl, voice
shl al, cl
Which is just WRONG. Like... crazy wrong. It meant that the lower frequency bit was setting amplitude, which is why the tone was off slightly and volume setting was jacked.
The PROPER code...
mov al, voice
mov cl, 3
and al, cl ; just to be on the safe side
ror al, cl
using cl as the mask and the rotate amount is a nice double-whammy, as is that the rotate by 3 is faster than doing a shift by five to the left.
*** side note *** I'm shocked how many audio codebases I look at do NOT do any input sanitization
I'm expanding the MIDI support a good deal as well in that I'm adding code for more than just the MPU-401 style interface. I've already got the Sound Blaster DSP MIDI up and working since I FINALLY found a document that had some REALLY good code examples:
http://www.phatcode.net/articles.php?id=243
I'm used to the code presented in such documents being garbage (like say, the stuff for the IMF) -- I've not seen that one before and it was a breath of fresh air. Being a Borland fan I'm a stickler for good documentation! Probably why I like PHP even though I hate PHP. The SB "Normal" MIDI code (I'm not using "UART" mode since that's DSP 2.0+ only) I'm pretty much using from that PDF with no real modifications... their reset function seems a bit wonky, but after playing with a few different variations I understand the choices they made.
IMF (IBM Music Feature) is still on my 'to be supported' list, though my FB-01 went up with a bang and cloud of smoke (cap blew with enough force to crack the board and lift traces, I'm not all that hopeful on ressurecting it but I'm gonna try) and I don't have a real IMF to test with... I'm also still playing with making a FB-01 softsynth emulation on the pi and/or cubieboard. I also have a ... crazy idea related to such project boards I'm going to be playing with; directly interfacing the GPIO
Are there any other semi-common MIDI interfaces? I was thinking on adding the non MPU-401 style MIDIMAN support (like the 2x2 and 2x4), but I'm not certain it's worth the effort.
One new feature I'm starting coding on today is for the Innovation SSI-2001, a card you rarely if ever hear about... for those of you unfamiliar with it the card is a Commodore/MOS SID for the PC, with an 8 bit DIGITAL joystick interface (that maps/pretends to be analog like many gamepads do). Honestly I never even heard of the card until... well...
http://www.vintage-computer.com/vcf...-quot-The-Entertainer-quot-sound-card-exposed
Even though I don't have one, DOSBox has a very good implementation since SID emulation as a whole is quite mature at this point (though the incorrect clock isn't 100% right compared to real hardware) -- and coding for it should go pretty quick since I already wrote SID code for the C64 version of Paku Paku... All I have to do is port my C / 6502 ASM to Pascal / 8086 ASM since apparently it just maps the 29 SID registers to a range of ports (typically 0x0280..0x29D) -- easy peasy.
I'm also looking for information on programming the Covox Sound Master -- I figure it can't be too different from programming the mockingboard in my IIe Platinum... but I can't find any actual programming examples for it on the PC much less information on where/how the card is even mapped... that I don't actually HAVE said card and there's no emulation (that I'm aware of) is also a bit limiting; but I figure AY-3-8910 is AY-3-8910.
One further thing I'm playing with is setting a much higher timer rate on faster (386+, MAYBE faster 286) systems to allow for 1.5 bit PC speaker support and Covox/Disney software synth. (I'm thinking 4.8khz should be sufficient for parallel, not sure about speaker) -- For my needs I only NEED two voices, though for the parallel port DAC version I'll probably aim for 4 voice with 6 bits per voice (so it's simple addition mixing with no shifting or divides)... I may also allow the soft-synth to output to the 'blaster's DSP just for laughs, possibly even defaulting to that instead of Adlib on the 'blaster since I really HATE how Yamaha FM sounds. (always have!). The speaker version is probably just going to be square wave based, I'm not sure if it's worth my time (or the RAM footprint) to have a SIN lookup or the logic for triangle / sawtooth for the 8 bit output, or if I should just "add al, 0x40" to go square wave there too. Sometimes simpler is better.
Basically I'm devoting the month of November to sound support. I'm planning on when complete uploading not just a test of it, but the full source of the test so if anyone else wants to run with it, they can.
Any advice or opinions welcome.
Last edited: