├── ADLIB.BNK ├── ADLIB.C ├── ADLIB.DEF ├── ADLIB.H ├── ADLIB21.RC ├── ADLIBA.ASM ├── BUILD.BAT ├── DRUMKIT.BIN ├── DRVPROC.C ├── INIT.C ├── LIBINIT.ASM ├── MAKEFILE ├── MIDIC.C ├── MIDIMAIN.C ├── MIDIMAP.CFG ├── PUB ├── ADLIB │ ├── ADLIB21.SYM │ └── MSADLIB.DRV ├── LPT278 │ ├── ADLIB21.SYM │ └── MSADLIB.DRV ├── LPT378 │ ├── ADLIB21.SYM │ └── MSADLIB.DRV └── LPT3BC │ ├── ADLIB21.SYM │ └── MSADLIB.DRV └── README.MD /ADLIB.BNK: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/ADLIB.BNK -------------------------------------------------------------------------------- /ADLIB.C: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * 3 | * adlib.c 4 | * 5 | * Copyright Ad Lib Inc, 1988, 1989 6 | * Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 7 | * Copyright (c) 2019-2020 Andrei Warkentin 8 | * 9 | ***************************************************************************/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include "adlib.h" 15 | 16 | static void NEAR PASCAL ChangePitch(BYTE voice, WORD pitchBend); 17 | static void NEAR PASCAL SndSetAllPrm(BYTE slot); 18 | static void NEAR PASCAL SndSAVEK(BYTE slot); 19 | static void NEAR PASCAL SndSFeedFm(BYTE slot); 20 | static void NEAR PASCAL SndSAttDecay(BYTE slot); 21 | static void NEAR PASCAL SndSSusRelease(BYTE slot); 22 | static void NEAR PASCAL SndWaveSelect(BYTE slot); 23 | 24 | BYTE slotRelVolume[18]; /* relative volume of slots */ 25 | BYTE percBits; /* control bits of percussive voices */ 26 | BYTE amDepth; /* chip global parameters ... */ 27 | BYTE vibDepth; /* ... */ 28 | BYTE noteSel; /* ... */ 29 | BYTE modeWaveSel; /* != 0 if used with 'wave-select' parms */ 30 | BOOL fPercussion; /* percussion mode parameter */ 31 | int pitchRangeStep; /* == pitchRange * NR_STEP_PITCH */ 32 | WORD fNumNotes[NR_STEP_PITCH][12]; 33 | NPWORD fNumFreqPtr[11]; /* lines of fNumNotes table (one per voice) */ 34 | int halfToneOffset[11]; /* one per voice */ 35 | BYTE noteDIV12[96]; /* table of (0..95) DIV 12 */ 36 | BYTE noteMOD12[96]; /* table of (0..95) MOD 12 */ 37 | 38 | /* this table gives the offset of each slot within the chip. */ 39 | BYTE offsetSlot[] = { 40 | 0, 1, 2, 3, 4, 5, 41 | 8, 9, 10, 11, 12, 13, 42 | 16, 17, 18, 19, 20, 21 43 | }; 44 | 45 | /* this table indicates if the slot is a modulator (0) or a carrier (1). */ 46 | BYTE operSlot[] = { 47 | 0, 0, 0, /* 1 2 3 */ 48 | 1, 1, 1, /* 4 5 6 */ 49 | 0, 0, 0, /* 7 8 9 */ 50 | 1, 1, 1, /* 10 11 12 */ 51 | 0, 0, 0, /* 13 14 15 */ 52 | 1, 1, 1, /* 16 17 18 */ 53 | }; 54 | 55 | static BYTE notePitch[11]; /* pitch value for each voice (implicit 0 init) */ 56 | static BYTE voiceKeyOn[11]; /* keyOn bit for each voice (implicit 0 init) */ 57 | static BYTE percMasks[] = { 0x10, 0x08, 0x04, 0x02, 0x01 }; 58 | 59 | /* voice number associated with each slot (melodic mode only) */ 60 | static BYTE voiceSlot[] = { 61 | 0, 1, 2, 62 | 0, 1, 2, 63 | 3, 4, 5, 64 | 3, 4, 5, 65 | 6, 7, 8, 66 | 6, 7, 8, 67 | }; 68 | 69 | /* slot numbers for percussive voices 70 | * (0 indicates that there is only one slot) 71 | */ 72 | static BYTE slotPerc[][2] = { 73 | {12, 15}, /* Bass Drum */ 74 | {16, 0}, /* SD */ 75 | {14, 0}, /* TOM */ 76 | {17, 0}, /* TOP-CYM */ 77 | {13, 0} /* HH */ 78 | }; 79 | 80 | /* slot numbers as a function of the voice and the operator (melodic only) */ 81 | static BYTE slotVoice[][2] = { 82 | {0, 3}, /* voice 0 */ 83 | {1, 4}, /* 1 */ 84 | {2, 5}, /* 2 */ 85 | {6, 9}, /* 3 */ 86 | {7, 10}, /* 4 */ 87 | {8, 11}, /* 5 */ 88 | {12, 15}, /* 6 */ 89 | {13, 16}, /* 7 */ 90 | {14, 17} /* 8 */ 91 | }; 92 | 93 | static BYTE paramSlot[18][NUMLOCPARAM]; /* all the parameters of slots... */ 94 | 95 | #define GetLocPrm(slot, prm) ((WORD)paramSlot[slot][prm]) 96 | 97 | /**************************************************************************** 98 | * @doc INTERNAL 99 | * 100 | * @api void | SetSlotParam | Sets the 14 parameters (13 in

, 101 | * 1 in

) of slot

. Updates both the parameter array 102 | * and the chip. 103 | * 104 | * @parm BYTE | slot | Specifies which slot to set. 105 | * 106 | * @parm NPBYTE | param | Pointer to the new parameter array. 107 | * 108 | * @parm BYTE | waveSel | The new waveSel value. 109 | * 110 | * @rdesc There is no return value. 111 | ***************************************************************************/ 112 | void FAR PASCAL 113 | SetSlotParam(BYTE slot, 114 | NPBYTE param, 115 | BYTE waveSel) 116 | { 117 | int i; 118 | LPBYTE ptr; 119 | 120 | for (i = 0, ptr = ¶mSlot[slot][0]; i < NUMLOCPARAM - 1; i++) { 121 | *ptr++ = *param++; 122 | } 123 | *ptr = waveSel &= 0x3; 124 | SndSetAllPrm(slot); 125 | } 126 | 127 | /**************************************************************************** 128 | * @doc INTERNAL 129 | * 130 | * @api void | SetFreq | Changes the pitch of voices 0 to 8, for melodic or 131 | * percussive mode. 132 | * 133 | * @parm BYTE | voice | Specifies which voice to set. 134 | * 135 | * @parm BYTE | pitch | Specifies the pitch (0 to 95). 136 | * 137 | * @parm BYTE | keyOn | Flag specifying whether the key is on or off. 138 | * 139 | * @rdesc There is no return value. 140 | ***************************************************************************/ 141 | void FAR PASCAL 142 | SetFreq(BYTE voice, 143 | BYTE pitch, 144 | BYTE keyOn) 145 | { 146 | WORD FNum; 147 | BYTE t1; 148 | 149 | /* remember the keyon and pitch of the voice */ 150 | voiceKeyOn[voice] = keyOn; 151 | notePitch[voice] = pitch; 152 | 153 | pitch += halfToneOffset[voice]; 154 | if (pitch > 95) { 155 | pitch = 95; 156 | } 157 | 158 | /* get the FNum for the voice */ 159 | FNum = * (fNumFreqPtr[voice] + noteMOD12[pitch]); 160 | 161 | /* output the FNum, KeyOn and Block values */ 162 | SndOutput((BYTE)(0xA0 | voice), (BYTE)FNum); /* FNum bits 0 - 7 (D0 - D7) */ 163 | t1 = (BYTE)(keyOn ? 32 : 0); /* Key On (D5) */ 164 | t1 += (noteDIV12[pitch] << 2); /* Block (D2 - D4) */ 165 | t1 += (0x3 & (BYTE)(FNum >> 8)); /* FNum bits 8 - 9 (D0 - D1) */ 166 | SndOutput((BYTE)(0xB0 | voice), t1); 167 | } 168 | 169 | /**************************************************************************** 170 | * @doc INTERNAL 171 | * 172 | * @api void | SndSAmVibRhythm | Sets the AM Depth, VIB depth and Rhythm values. 173 | * 174 | * @rdesc There is no return value. 175 | ***************************************************************************/ 176 | void FAR PASCAL 177 | SndSAmVibRhythm(void) 178 | { 179 | BYTE t1; 180 | 181 | t1 = (BYTE)(amDepth ? 0x80 : 0); 182 | t1 |= vibDepth ? 0x40 : 0; 183 | t1 |= fPercussion ? 0x20 : 0; 184 | t1 |= percBits; 185 | SndOutput(0xBD, t1); 186 | } 187 | 188 | /**************************************************************************** 189 | * @doc INTERNAL 190 | * 191 | * @api void | SndSNoteSel | Sets the NoteSel value. 192 | * 193 | * @rdesc There is no return value. 194 | ***************************************************************************/ 195 | void FAR PASCAL 196 | SndSNoteSel(void) 197 | { 198 | SndOutput(0x08, (BYTE)(noteSel ? 64 : 0)); 199 | } 200 | 201 | /**************************************************************************** 202 | * @doc INTERNAL 203 | * 204 | * @api void | SndSKslLevel | Sets the KSL and LEVEL values. 205 | * 206 | * @parm BYTE | slot | Specifies which slot to set. 207 | * 208 | * @rdesc There is no return value. 209 | ***************************************************************************/ 210 | void NEAR PASCAL 211 | SndSKslLevel(BYTE slot) 212 | { 213 | WORD t1; 214 | 215 | t1 = 63 - (GetLocPrm(slot, prmLevel) & 0x3f); /* amplitude */ 216 | t1 = slotRelVolume[slot] * t1; 217 | t1 += t1 + MAXVOLUME; /* round off to 0.5 */ 218 | t1 = 63 - t1 / (2 * MAXVOLUME); 219 | 220 | t1 |= GetLocPrm(slot, prmKsl) << 6; 221 | SndOutput((BYTE)(0x40 | offsetSlot[slot]), (BYTE)t1); 222 | } 223 | 224 | /**************************************************************************** 225 | * @doc INTERNAL 226 | * 227 | * @api void | SetVoiceTimbre | This routine sets the parameters of the 228 | * voice

. 229 | * 230 | * @parm BYTE | voice | Specifies which voice to set. 231 | * 232 | * @parm NPOPERATOR | pOper0 | Pointer to operator 0. 233 | * 234 | * @comm In melodic mode,

varies from 0 to 8. In percussive mode, 235 | * voices 0 to 5 are melodic and 6 to 10 are percussive. 236 | * 237 | * A timbre (melodic or percussive) is defined as follows: the 13 first 238 | * parameters of operator 0 (ksl, multi, feedBack, attack, sustain, 239 | * eg-type, decay, release, level, am, vib, ksr, fm), followed by the 240 | * 13 parameters of operator 1 (if a percussive voice, all the parameters 241 | * are zero), followed by the wave-select parameter for the operators 0 242 | * and 1. 243 | * 244 | *

is actually pointing to the element of the 245 | * structure, which is defined as follows: 246 | * 247 | * typedef struct { 248 | * BYTE mode; 0 = melodic, 1 = percussive 249 | * BYTE percVoice; if mode == 1, voice number to be used 250 | * OPERATOR op0; a 13 byte array of op0 parameters 251 | * OPERATOR op1; a 13 byte array of op1 parameters 252 | * BYTE wave0; waveform for operator 0 253 | * BYTE wave1; waveform for operator 1 254 | * } TIMBRE, *NPTIMBRE, FAR *LPTIMBRE; 255 | * 256 | * The old timbre files (*.INS) do not contain the parameters 257 | * 'wave0' and 'wave1'. Set these two parameters to zero if 258 | * you are using the old file format. 259 | * 260 | * @rdesc There is no return value. 261 | ***************************************************************************/ 262 | void NEAR PASCAL 263 | SetVoiceTimbre(BYTE voice, 264 | NPOPERATOR pOper0) 265 | { 266 | NPBYTE prm0; 267 | NPBYTE prm1; 268 | BYTE wave0; 269 | BYTE wave1; 270 | NPBYTE wavePtr; 271 | 272 | prm0 = (NPBYTE)pOper0; 273 | prm1 = prm0 + NUMLOCPARAM - 1; 274 | wavePtr = prm0 + 2 * (NUMLOCPARAM - 1); 275 | wave0 = *wavePtr++; 276 | wave1 = *wavePtr; 277 | 278 | if (!fPercussion || voice < BD) { /* melodic only */ 279 | D3("Set melodic voice"); 280 | SetSlotParam(slotVoice[voice][0], prm0, wave0); 281 | SetSlotParam(slotVoice[voice][1], prm1, wave1); 282 | } 283 | else if (voice == BD) { /* bass drum */ 284 | D3("Set bass drum"); 285 | SetSlotParam(slotPerc[0][0], prm0, wave0); 286 | SetSlotParam(slotPerc[0][1], prm1, wave1); 287 | } 288 | else { /* percussion, 1 slot */ 289 | D3("Set percussion"); 290 | SetSlotParam(slotPerc[voice - BD][0], prm0, wave0); 291 | } 292 | } 293 | 294 | /**************************************************************************** 295 | * @doc INTERNAL 296 | * 297 | * @api void | SetVoicePitch | Changes the pitch value of a voice. Does 298 | * not affect the percussive voices, except for the bass drum. The change 299 | * takes place immediately. 300 | * 301 | * @parm BYTE | voice | Specifies which voice to set. 302 | * 303 | * @parm WORD | pitchBend | Specifies the new pitch bend value (0 to 0x3fff, 304 | * where 0x2000 == exact tuning). 305 | * 306 | * @comm The variation in pitch is a function of the previous call to 307 | * and the value of

. A value of 0 means 308 | * -half-tone * pitchRangeStep, 0x2000 means no variation (exact pitch) and 309 | * 0x3fff means +half-tone * pitchRangeStep. 310 | * 311 | * @rdesc There is no return value. 312 | ***************************************************************************/ 313 | void NEAR PASCAL 314 | SetVoicePitch(BYTE voice, 315 | WORD pitchBend) 316 | { 317 | if (!fPercussion || voice <= BD) { 318 | 319 | /* melodic and bass drum voices */ 320 | if (pitchBend > MAX_PITCH) { 321 | pitchBend = MAX_PITCH; 322 | } 323 | ChangePitch(voice, pitchBend); 324 | SetFreq(voice, notePitch[voice], voiceKeyOn[voice]); 325 | } 326 | } 327 | 328 | /**************************************************************************** 329 | * @doc INTERNAL 330 | * 331 | * @api void | SetVoiceVolume | Sets the volume of the voice

to 332 | *

. The resulting output level is (timbreVolume * volume / 127). 333 | * The change takes place immediately. 334 | * 335 | * @parm BYTE | voice | Specifies which voice to set. 336 | * 337 | * @parm BYTE | volume | Specifies the new volume level (0 to 127). 338 | * 339 | * @rdesc There is no return value. 340 | ***************************************************************************/ 341 | void NEAR PASCAL 342 | SetVoiceVolume(BYTE voice, 343 | BYTE volume) 344 | { 345 | BYTE slot; 346 | NPBYTE slots; 347 | 348 | if (volume > MAXVOLUME) { 349 | volume = MAXVOLUME; 350 | } 351 | 352 | if (!fPercussion || voice <= BD) { 353 | 354 | /* melodic voice */ 355 | slots = slotVoice[voice]; 356 | slotRelVolume[slots[1]] = volume; 357 | SndSKslLevel(slots[1]); 358 | if (!GetLocPrm(slots[0], prmFm)) { 359 | 360 | /* additive synthesis: set volume of first slot too */ 361 | slotRelVolume[slots[0]] = volume; 362 | SndSKslLevel(slots[0]); 363 | } 364 | } else { 365 | 366 | /* percussive voice */ 367 | slot = slotPerc[voice - BD][0]; 368 | slotRelVolume[slot] = volume; 369 | SndSKslLevel(slot); 370 | } 371 | } 372 | 373 | /**************************************************************************** 374 | * @doc INTERNAL 375 | * 376 | * @api void | NoteOn | This routine starts a note playing. 377 | * 378 | * @parm BYTE | voice | Specifies which voice to use (0 to 8 in melodic mode, 379 | * 0 to 10 in percussive mode). 380 | * 381 | * @parm BYTE | pitch | Specifies the pitch (0 to 127, where 60 == MID_C). The 382 | * card can play between 12 and 107. 383 | * 384 | * @rdesc There is no return value. 385 | ***************************************************************************/ 386 | void NEAR PASCAL 387 | NoteOn(BYTE voice, 388 | BYTE pitch) 389 | { 390 | D3("NoteON"); 391 | 392 | /* adjust pitch for chip */ 393 | if (pitch < (MID_C - CHIP_MID_C)) { 394 | pitch = 0; 395 | } else { 396 | pitch -= (MID_C - CHIP_MID_C); 397 | } 398 | 399 | if (voice < BD || !fPercussion) { /* this is a melodic voice */ 400 | SetFreq(voice, pitch, 1); 401 | } else { /* this is a percussive voice */ 402 | if (voice == BD) { 403 | SetFreq(BD, pitch, 0); 404 | } else if (voice == TOM) { 405 | /* for the last 4 percussions, only the TOM may change */ 406 | /* in frequency, which also modifies the SD */ 407 | SetFreq(TOM, pitch, 0); 408 | SetFreq(SD, (BYTE)(pitch + TOM_TO_SD), 0); 409 | } 410 | 411 | percBits |= percMasks[voice - BD]; 412 | SndSAmVibRhythm(); 413 | } 414 | } 415 | 416 | /**************************************************************************** 417 | * @doc INTERNAL 418 | * 419 | * @api void | NoteOff | This routine stops playing the note which was 420 | * started in . 421 | * 422 | * @parm BYTE | voice | Specifies which voice to use (0 to 8 in melodic mode, 423 | * 0 to 10 in percussive mode). 424 | * 425 | * @rdesc There is no return value. 426 | ***************************************************************************/ 427 | void NEAR PASCAL 428 | NoteOff(BYTE voice) 429 | { 430 | D3("NoteOff"); 431 | 432 | if (!fPercussion || voice < BD) { 433 | SetFreq(voice, notePitch[voice], 0); /* shut off */ 434 | } else { 435 | percBits &= ~percMasks[voice - BD]; 436 | SndSAmVibRhythm(); 437 | } 438 | } 439 | 440 | /**************************************************************************** 441 | * @doc INTERNAL 442 | * 443 | * @api void | ChangePitch | This routine sets the and 444 | * arrays. These two global variables are used to 445 | * determine the frequency variation to use when a note is played. 446 | * 447 | * @parm BYTE | voice | Specifies which voice to use. 448 | * 449 | * @parm WORD | pitchBend | Specifies the pitch bend value (0 to 0x3fff, 450 | * where 0x2000 is exact tuning). 451 | * 452 | * @rdesc There is no return value. 453 | ***************************************************************************/ 454 | static void NEAR PASCAL 455 | ChangePitch(BYTE voice, 456 | WORD pitchBend) 457 | { 458 | static int oldt1 = -1; 459 | static int oldHt; 460 | static NPWORD oldPtr; 461 | int t1; 462 | int t2; 463 | int delta; 464 | 465 | #if (MID_PITCH != 8192) 466 | #pragma message("ChangePitch: DANGER! C-RUNTIME NOT IN FIXED SEGMENT!!!") 467 | t1 = (int)(((long)((int)pitchBend - MID_PITCH) * pitchRangeStep)/MID_PITCH); 468 | #else 469 | DWORD dw; 470 | 471 | dw = (DWORD)((long)((int)pitchBend - MID_PITCH) * pitchRangeStep); 472 | t1 = (int)((LOBYTE(HIWORD(dw)) << 8) | HIBYTE(LOWORD(dw))) >> 5; 473 | #endif 474 | 475 | if (oldt1 == t1) { 476 | fNumFreqPtr[voice] = oldPtr; 477 | halfToneOffset[voice] = oldHt; 478 | } else { 479 | if (t1 < 0) { 480 | t2 = NR_STEP_PITCH - 1 - t1; 481 | oldHt = halfToneOffset[voice] = -(t2 / NR_STEP_PITCH); 482 | delta = (t2 - NR_STEP_PITCH + 1) % NR_STEP_PITCH; 483 | if (delta) 484 | delta = NR_STEP_PITCH - delta; 485 | } else { 486 | oldHt = halfToneOffset[voice] = t1 / NR_STEP_PITCH; 487 | delta = t1 % NR_STEP_PITCH; 488 | } 489 | 490 | oldPtr = fNumFreqPtr[voice] = fNumNotes[delta]; 491 | oldt1 = t1; 492 | } 493 | } 494 | 495 | /**************************************************************************** 496 | * @doc INTERNAL 497 | * 498 | * @api void | SndSetAllPrm | Transfers all the parameters from slot

499 | * to the chip. 500 | * 501 | * @parm BYTE | slot | Specifies which slot to set. 502 | * 503 | * @rdesc There is no return value. 504 | ***************************************************************************/ 505 | static void NEAR PASCAL 506 | SndSetAllPrm(BYTE slot) 507 | { 508 | D3("SndSetAllPrm"); 509 | 510 | SndSAmVibRhythm(); 511 | SndSNoteSel(); 512 | SndSKslLevel(slot); 513 | SndSFeedFm(slot); 514 | SndSAttDecay(slot); 515 | SndSSusRelease(slot); 516 | SndSAVEK(slot); 517 | SndWaveSelect(slot); 518 | } 519 | 520 | /**************************************************************************** 521 | * @doc INTERNAL 522 | * 523 | * @api void | SndSAVEK | Sets the AM, VIB, EG-TYP (sustaining), KSR, and 524 | * MULTI values. 525 | * 526 | * @parm BYTE | slot | Specifies which slot to set. 527 | * 528 | * @rdesc There is no return value. 529 | ***************************************************************************/ 530 | static void NEAR PASCAL 531 | SndSAVEK(BYTE slot) 532 | { 533 | BYTE t1; 534 | 535 | t1 = (BYTE)(GetLocPrm(slot, prmAm) ? 0x80 : 0); 536 | t1 += GetLocPrm(slot, prmVib) ? 0x40 : 0; 537 | t1 += GetLocPrm(slot, prmStaining) ? 0x20 : 0; 538 | t1 += GetLocPrm(slot, prmKsr) ? 0x10 : 0; 539 | t1 += (BYTE) GetLocPrm(slot, prmMulti) & 0xf; 540 | SndOutput((BYTE) (0x20 | offsetSlot[slot]), t1); 541 | } 542 | 543 | /**************************************************************************** 544 | * @doc INTERNAL 545 | * 546 | * @api void | SndSFeedFm | Sets the FEEDBACK and FM (connection) values. 547 | * Applicable only to operator 0 for melodic voices. 548 | * 549 | * @parm BYTE | slot | Specifies which slot to set. 550 | * 551 | * @rdesc There is no return value. 552 | ***************************************************************************/ 553 | static void NEAR PASCAL 554 | SndSFeedFm(BYTE slot) 555 | { 556 | BYTE t1; 557 | 558 | if (operSlot[slot]) { 559 | return; 560 | } 561 | 562 | t1 = (BYTE)(GetLocPrm(slot, prmFeedBack) << 1); 563 | t1 |= GetLocPrm(slot, prmFm) ? 0 : 1; 564 | SndOutput((BYTE)(0xC0 | voiceSlot[slot]), t1); 565 | } 566 | 567 | /**************************************************************************** 568 | * @doc INTERNAL 569 | * 570 | * @api void | SndSAttDecay | Sets the ATTACK and DECAY values. 571 | * 572 | * @parm BYTE | slot | Specifies which slot to set. 573 | * 574 | * @rdesc There is no return value. 575 | ***************************************************************************/ 576 | static void NEAR PASCAL 577 | SndSAttDecay(BYTE slot) 578 | { 579 | BYTE t1; 580 | 581 | t1 = (BYTE)(GetLocPrm(slot, prmAttack) << 4); 582 | t1 |= GetLocPrm(slot, prmDecay) & 0xf; 583 | SndOutput((BYTE)(0x60 | offsetSlot[slot]), t1); 584 | } 585 | 586 | /**************************************************************************** 587 | * @doc INTERNAL 588 | * 589 | * @api void | SndSSusRelease | Sets the SUSTAIN and RELEASE values. 590 | * 591 | * @parm BYTE | slot | Specifies which slot to set. 592 | * 593 | * @rdesc There is no return value. 594 | ***************************************************************************/ 595 | static void NEAR PASCAL 596 | SndSSusRelease(BYTE slot) 597 | { 598 | BYTE t1; 599 | 600 | t1 = (BYTE)(GetLocPrm(slot, prmSustain) << 4); 601 | t1 |= GetLocPrm(slot, prmRelease) & 0xf; 602 | SndOutput((BYTE)(0x80 | offsetSlot[slot]), t1); 603 | } 604 | 605 | /**************************************************************************** 606 | * @doc INTERNAL 607 | * 608 | * @api void | SndWaveSelect | Sets the wave-select parameter. 609 | * 610 | * @parm BYTE | slot | Specifies which slot to set. 611 | * 612 | * @rdesc There is no return value. 613 | ***************************************************************************/ 614 | static void NEAR PASCAL 615 | SndWaveSelect(BYTE slot) 616 | { 617 | BYTE wave; 618 | 619 | if (modeWaveSel) { 620 | wave = (BYTE)(GetLocPrm(slot, prmWaveSel) & 0x03); 621 | } else { 622 | wave = 0; 623 | } 624 | SndOutput((BYTE)(0xE0 | offsetSlot[slot]), wave); 625 | } 626 | -------------------------------------------------------------------------------- /ADLIB.DEF: -------------------------------------------------------------------------------- 1 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 2 | ; 3 | ; adlib.def 4 | ; 5 | ; Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 6 | ; Copyright (c) 2019-2020 Andrei Warkentin 7 | ; 8 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 9 | 10 | LIBRARY MSADLIB 11 | 12 | DESCRIPTION 'midi:Ad Lib' 13 | 14 | EXETYPE WINDOWS 15 | PROTMODE 16 | 17 | CODE MOVEABLE DISCARDABLE LOADONCALL 18 | DATA MOVEABLE PRELOAD SINGLE 19 | 20 | SEGMENTS _FIX MOVEABLE DISCARDABLE PRELOAD 21 | _TEXT MOVEABLE DISCARDABLE PRELOAD 22 | 23 | HEAPSIZE 0 24 | 25 | EXPORTS WEP 26 | DriverProc @1 27 | modMessage @2 28 | -------------------------------------------------------------------------------- /ADLIB.H: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * 3 | * adlib.h 4 | * 5 | * Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 6 | * Copyright (c) 2019-2020 Andrei Warkentin 7 | * 8 | ***************************************************************************/ 9 | 10 | /* this way we get more symbols for debug */ 11 | #ifdef DEBUG 12 | #define static 13 | #endif 14 | 15 | #define _TEXTIFY(A) #A 16 | #define TEXTIFY(A) _TEXTIFY(A) 17 | 18 | #define DRIVER_VERSION 1; 19 | 20 | #ifndef DEF_PORT 21 | #ifdef OPL_ON_LPT 22 | #define DEF_PORT 0x378 /* LPT1 */ 23 | #else 24 | #define DEF_PORT 0x388 /* default port address */ 25 | #endif 26 | #endif 27 | 28 | #define NUMVOICES (fPercussion?11:9) /* number of voices we have */ 29 | #define NUMMELODIC (fPercussion?6:9) /* number of melodic voices */ 30 | #define NUMPERCUSSIVE (fPercussion?5:0) /* number of percussive voices */ 31 | 32 | #define NUMCHANNELS 16 /* number of MIDI channels */ 33 | #define DRUMKITCHANNEL 15 /* our drum kit channel */ 34 | 35 | #define MAXPATCH 180 /* max patch number (>127 for drums) */ 36 | #define MAXVOLUME 0x7f 37 | #define NUMLOCPARAM 14 /* number of loc params per voice */ 38 | 39 | #define FIRSTDRUMNOTE 35 40 | #define LASTDRUMNOTE 81 41 | #define NUMDRUMNOTES (LASTDRUMNOTE - FIRSTDRUMNOTE + 1) 42 | 43 | #define MAX_PITCH 0x3fff /* maximum pitch bend value */ 44 | #define MID_PITCH 0x2000 /* mid pitch bend value (no shift) */ 45 | #define NR_STEP_PITCH 25 /* steps per half-tone for pitch bend */ 46 | #define MID_C 60 /* MIDI standard mid C */ 47 | #define CHIP_MID_C 48 /* sound chip mid C */ 48 | 49 | #define RT_BANK 256 50 | #define RT_DRUMKIT 257 51 | #define DEFAULTBANK 1 52 | #define DEFAULTDRUMKIT 1 53 | 54 | #define ISSTATUS(bData) ((bData) & 0x80) 55 | #define FILTERCHANNEL(bStatus) ((BYTE)((bStatus) & 0xf0)) 56 | #define FILTERSTATUS(bStatus) ((BYTE)((bStatus) & 0x0f)) 57 | 58 | #define STATUS_NOTEOFF 0x80 59 | #define STATUS_NOTEON 0x90 60 | #define STATUS_POLYPHONICKEY 0xa0 61 | #define STATUS_CONTROLCHANGE 0xb0 62 | #define STATUS_PROGRAMCHANGE 0xc0 63 | #define STATUS_CHANNELPRESSURE 0xd0 64 | #define STATUS_PITCHBEND 0xe0 65 | 66 | #define STATUS_SYS 0xf0 67 | #define STATUS_SYSEX 0xf0 68 | #define STATUS_QFRAME 0xf1 69 | #define STATUS_SONGPOINTER 0xf2 70 | #define STATUS_SONGSELECT 0xf3 71 | #define STATUS_F4 0xf4 72 | #define STATUS_F5 0xf5 73 | #define STATUS_TUNEREQUEST 0xf6 74 | #define STATUS_EOX 0xf7 75 | #define STATUS_TIMINGCLOCK 0xf8 76 | #define STATUS_F9 0xf9 77 | #define STATUS_START 0xfa 78 | #define STATUS_CONTINUE 0xfb 79 | #define STATUS_STOP 0xfc 80 | #define STATUS_FD 0xfd 81 | #define STATUS_ACTIVESENSING 0xfe 82 | #define STATUS_SYSTEMRESET 0xff 83 | 84 | /* parameters of each voice */ 85 | #define prmKsl 0 /* key scale level (KSL) - level controller */ 86 | #define prmMulti 1 /* frequency multiplier (MULTI) - oscillator */ 87 | #define prmFeedBack 2 /* modulation feedback (FB) - oscillator */ 88 | #define prmAttack 3 /* attack rate (AR) - envelope generator */ 89 | #define prmSustain 4 /* sustain level (SL) - envelope generator */ 90 | #define prmStaining 5 /* sustaining sound (SS) - envelope generator */ 91 | #define prmDecay 6 /* decay rate (DR) - envelope generator */ 92 | #define prmRelease 7 /* release rate (RR) - envelope generator */ 93 | #define prmLevel 8 /* output level (OL) - level controller */ 94 | #define prmAm 9 /* amplitude vibrato (AM) - level controller */ 95 | #define prmVib 10 /* frequency vibrator (VIB) - oscillator */ 96 | #define prmKsr 11 /* envelope scaling (KSR) - envelope generator */ 97 | #define prmFm 12 /* fm=0, additive=1 (FM) (operator 0 only) */ 98 | #define prmWaveSel 13 /* wave select */ 99 | 100 | /* global parameters */ 101 | #define prmAmDepth 14 102 | #define prmVibDepth 15 103 | #define prmNoteSel 16 104 | #define prmPercussion 17 105 | 106 | /* percussive voice numbers (0-5 are melodic voices, 12 op): */ 107 | #define BD 6 /* bass drum (2 op) */ 108 | #define SD 7 /* snare drum (1 op) */ 109 | #define TOM 8 /* tom-tom (1 op) */ 110 | #define CYMB 9 /* cymbal (1 op) */ 111 | #define HIHAT 10 /* hi-hat (1 op) */ 112 | 113 | /* 114 | * In percussive mode, the last 4 voices (SD TOM HH CYMB) are created 115 | * using melodic voices 7 & 8. A noise generator uses channels 7 & 8 116 | * frequency information for creating rhythm instruments. Best results 117 | * are obtained by setting TOM two octaves below mid C and SD 7 half-tones 118 | * above TOM. In this implementation, only the TOM pitch may vary, with the 119 | * SD always set 7 half-tones above. 120 | */ 121 | #define TOM_PITCH 24 /* best frequency, in range of 0 to 95 */ 122 | #define TOM_TO_SD 7 /* 7 half-tones between voice 7 & 8 */ 123 | #define SD_PITCH (TOM_PITCH + TOM_TO_SD) 124 | 125 | #define BANK_SIG_LEN 6 126 | #define BANK_FILLER_SIZE 8 127 | 128 | typedef BYTE huge *HPBYTE; 129 | 130 | /* instrument bank file header */ 131 | typedef struct { 132 | char majorVersion; 133 | char minorVersion; 134 | char sig[BANK_SIG_LEN]; /* signature: "ADLIB-" */ 135 | WORD nrDefined; /* number of list entries used */ 136 | WORD nrEntry; /* number of total entries in list */ 137 | long offsetIndex; /* offset of start of list of names */ 138 | long offsetTimbre; /* offset of start of data */ 139 | char filler[BANK_FILLER_SIZE]; /* must be zero */ 140 | } BANKHDR, NEAR *NPBANKHDR, FAR *LPBANKHDR; 141 | 142 | typedef BYTE NEAR * NPBYTE; 143 | typedef WORD NEAR * NPWORD; 144 | 145 | typedef struct { 146 | BYTE ksl; /* KSL */ 147 | BYTE freqMult; /* MULTI */ 148 | BYTE feedBack; /* FB */ 149 | BYTE attack; /* AR */ 150 | BYTE sustLevel; /* SL */ 151 | BYTE sustain; /* SS */ 152 | BYTE decay; /* DR */ 153 | BYTE release; /* RR */ 154 | BYTE output; /* OL */ 155 | BYTE am; /* AM */ 156 | BYTE vib; /* VIB */ 157 | BYTE ksr; /* KSR */ 158 | BYTE fm; /* FM */ 159 | } OPERATOR, NEAR *NPOPERATOR, FAR *LPOPERATOR; 160 | 161 | typedef struct { 162 | BYTE mode; /* 0 = melodic, 1 = percussive */ 163 | BYTE percVoice; /* if mode == 1, voice number to be used */ 164 | OPERATOR op0; 165 | OPERATOR op1; 166 | BYTE wave0; /* waveform for operator 0 */ 167 | BYTE wave1; /* waveform for operator 1 */ 168 | } TIMBRE, NEAR *NPTIMBRE, FAR *LPTIMBRE; 169 | 170 | typedef struct drumpatch_tag { 171 | BYTE patch; /* the patch to use */ 172 | BYTE note; /* the note to play */ 173 | } DRUMPATCH; 174 | 175 | /* client information structure */ 176 | typedef struct synthalloc_tag { 177 | HANDLE hMidiOut; /* handle for our parent in MMSYSTEM */ 178 | DWORD dwCallback; /* callback for client */ 179 | DWORD dwInstance; /* DWORD of reference data from client */ 180 | DWORD dwFlags; /* allocation flags */ 181 | } SYNTHALLOC, NEAR *NPSYNTHALLOC; 182 | 183 | typedef struct _MIDIMSG { 184 | BYTE ch; /* channel number */ 185 | BYTE b1; /* first data byte */ 186 | BYTE b2; /* second data byte */ 187 | BYTE pad; /* not used */ 188 | } MIDIMSG, FAR *LPMIDIMSG; 189 | 190 | /* midic.c */ 191 | extern DWORD FAR PASCAL _loadds modMessage(WORD id, WORD msg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2); 192 | extern BYTE bCurrentLen; 193 | extern BYTE status; 194 | 195 | /* midimain.c */ 196 | extern void NEAR PASCAL synthMidiData(HPBYTE lpBuf, DWORD dwLength); 197 | extern void NEAR PASCAL synthAllNotesOff(void); 198 | 199 | /* adlib.c */ 200 | extern BOOL FAR PASCAL vadlibdGetEntryPoint( void ); 201 | extern WORD FAR PASCAL vadlibdAcquireAdLibSynth( void ); 202 | extern WORD FAR PASCAL vadlibdReleaseAdLibSynth( void ); 203 | 204 | extern void FAR PASCAL SetSlotParam(BYTE slot, NPBYTE param, BYTE waveSel); 205 | extern void FAR PASCAL SetFreq(BYTE voice, BYTE pitch, BYTE keyOn); 206 | extern void FAR PASCAL SndSAmVibRhythm(void); 207 | extern void FAR PASCAL SndSNoteSel(void); 208 | extern void NEAR PASCAL SndSKslLevel(BYTE slot); 209 | extern void NEAR PASCAL SetVoiceTimbre(BYTE voice, NPOPERATOR pOper0); 210 | extern void NEAR PASCAL SetVoicePitch(BYTE voice, WORD pitchBend); 211 | extern void NEAR PASCAL SetVoiceVolume(BYTE voice, BYTE volume); 212 | extern void NEAR PASCAL NoteOn(BYTE voice, BYTE pitch); 213 | extern void NEAR PASCAL NoteOff(BYTE voice); 214 | 215 | extern BYTE slotRelVolume[18]; /* relative volume of slots */ 216 | extern BYTE percBits; /* control bits of percussive voices */ 217 | extern BYTE amDepth; /* chip global parameters ... */ 218 | extern BYTE vibDepth; /* ... */ 219 | extern BYTE noteSel; /* ... */ 220 | extern BYTE modeWaveSel; /* != 0 if used with 'wave-select' parms */ 221 | extern BOOL fPercussion; /* percussion mode parameter */ 222 | extern int pitchRangeStep; /* == pitchRange * NR_STEP_PITCH */ 223 | extern WORD fNumNotes[NR_STEP_PITCH] [12]; 224 | extern NPWORD fNumFreqPtr[11]; /* lines of fNumNotes table (one per voice) */ 225 | extern int halfToneOffset[11]; /* one per voice */ 226 | extern BYTE noteDIV12[96]; /* table of (0..95) DIV 12 */ 227 | extern BYTE noteMOD12[96]; /* table of (0..95) MOD 12 */ 228 | extern BYTE offsetSlot[]; /* offset of each slot */ 229 | extern BYTE operSlot[]; /* operator (0 or 1) in each slot */ 230 | 231 | extern BYTE gbMidiLengths[]; 232 | extern BYTE gbSysLengths[]; 233 | 234 | #define MIDILENGTH(bStatus) (gbMidiLengths[((bStatus) & 0x70) >> 4]) 235 | #define SYSLENGTH(bStatus) (gbSysLengths[(bStatus) & 0x07]) 236 | 237 | /* init.c */ 238 | extern BOOL NEAR PASCAL Enable(void); 239 | extern void NEAR PASCAL Disable(void); 240 | extern int NEAR PASCAL LibMain(HANDLE hInstance, WORD wHeapSize, LPSTR lpCmdLine); 241 | 242 | extern WORD wPort; /* card's port address */ 243 | extern WORD wDebugLevel; /* debug level */ 244 | extern BOOL fEnabled; /* have we successfully enabled? */ 245 | extern TIMBRE patches[MAXPATCH]; /* melodic patch information */ 246 | extern DRUMPATCH drumpatch[NUMDRUMNOTES]; /* drumkit patch information */ 247 | 248 | /* adliba.asm */ 249 | extern BYTE FAR PASCAL inport(void); 250 | extern void FAR PASCAL SndOutput(BYTE addr, BYTE dataVal); 251 | 252 | #ifndef NOSTR 253 | extern char far aszDriverName[]; 254 | extern char far aszProductName[]; 255 | extern char far aszSystemIni[]; 256 | #ifdef DEBUG 257 | extern char far aszAdlib[]; 258 | extern char far aszMMDebug[]; 259 | #endif 260 | #endif /* NOSTR */ 261 | 262 | #ifdef DEBUG 263 | #define D1(sz) if (wDebugLevel >= 1) (OutputDebugStr("\r\nADLIB: "),OutputDebugStr(sz)) 264 | #define D2(sz) if (wDebugLevel >= 2) (OutputDebugStr(" "),OutputDebugStr(sz)) 265 | #define D3(sz) if (wDebugLevel >= 3) (OutputDebugStr(" "),OutputDebugStr(sz)) 266 | #define D4(sz) if (wDebugLevel >= 4) (OutputDebugStr(" "),OutputDebugStr(sz)) 267 | #else 268 | #define D1(sz) 0 269 | #define D2(sz) 0 270 | #define D3(sz) 0 271 | #define D4(sz) 0 272 | #endif 273 | -------------------------------------------------------------------------------- /ADLIB21.RC: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * 3 | * adlib21.rc 4 | * 5 | * Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 6 | * Copyright (c) 2019-2020 Andrei Warkentin 7 | * 8 | ***************************************************************************/ 9 | 10 | #include 11 | #include 12 | #include "adlib.h" 13 | 14 | DEFAULTBANK RT_BANK adlib.bnk 15 | DEFAULTDRUMKIT RT_DRUMKIT drumkit.bin 16 | 17 | /* 18 | * All strings MUST have an explicit \0. The MMRELEASE and the version 19 | * string should be changed every build, and the MMRELEASE build extension 20 | * should be removed on final release. See the Windows 3.1 SDK documentation 21 | * for details on version information and the VERSIONINFO structure. 22 | */ 23 | 24 | #ifdef RC_INVOKED 25 | #define MMVERSION DRIVER_VERSION 26 | #define MMREVISION 0 27 | #define MMRELEASE 1 28 | #define MMVERSIONSTR "1.0\0" 29 | #define MMVERSIONNAME "adlib21.drv\0" 30 | #ifdef OPL_ON_LPT 31 | #define MMVERSIONDESCRIPTION "MIDI for OPL" TEXTIFY(OPL) "LPT on " TEXTIFY(DEF_PORT) "\0" 32 | #else 33 | #define MMVERSIONDESCRIPTION "MIDI for OPL" TEXTIFY(OPL) " Adlib compatibles\0" 34 | #endif 35 | #define MMVERSIONCOMPANYNAME "Andrei Warkentin\0" 36 | #define MMVERSIONPRODUCTNAME "Adlib21\0" 37 | #define MMVERSIONCOPYRIGHT "Copyright \251 Andrei Warkentin 2019-2020\0" 38 | 39 | VS_VERSION_INFO VERSIONINFO 40 | FILEVERSION MMVERSION, MMREVISION, 0, MMRELEASE 41 | PRODUCTVERSION MMVERSION, MMREVISION, 0, MMRELEASE 42 | FILEFLAGSMASK 0x0000003FL 43 | FILEFLAGS 0 44 | FILEOS VOS_DOS_WINDOWS16 45 | FILETYPE VFT_DRV 46 | FILESUBTYPE VFT2_DRV_INSTALLABLE 47 | BEGIN 48 | BLOCK "StringFileInfo" 49 | BEGIN 50 | BLOCK "040904E4" 51 | BEGIN 52 | VALUE "CompanyName", MMVERSIONCOMPANYNAME 53 | VALUE "FileDescription", MMVERSIONDESCRIPTION 54 | VALUE "FileVersion", MMVERSIONSTR 55 | VALUE "InternalName", MMVERSIONNAME 56 | VALUE "LegalCopyright", MMVERSIONCOPYRIGHT 57 | VALUE "OriginalFilename", MMVERSIONNAME 58 | VALUE "ProductName", MMVERSIONPRODUCTNAME 59 | VALUE "ProductVersion", MMVERSIONSTR 60 | END 61 | END 62 | 63 | BLOCK "VarFileInfo" 64 | BEGIN 65 | /* the following line should be extended for localized versions */ 66 | VALUE "Translation", 0x409, 1252 67 | END 68 | END 69 | #endif 70 | -------------------------------------------------------------------------------- /ADLIBA.ASM: -------------------------------------------------------------------------------- 1 | page 60, 132 2 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 3 | ; 4 | ; adliba.asm 5 | ; 6 | ; Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 7 | ; Copyright (c) 2019-2020 Andrei Warkentin 8 | ; 9 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 10 | 11 | PMODE = 1 12 | 13 | .xlist 14 | include cmacros.inc 15 | .list 16 | 17 | ?PLM=1 ; Pascal calling convention 18 | ?WIN=0 ; NO! Windows prolog/epilog code 19 | 20 | 21 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 22 | ; 23 | ; debug support 24 | ; 25 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 26 | 27 | ifdef DEBUG 28 | extrn OutputDebugStr:far ; mmsystem 29 | extrn _wDebugLevel:word ; initc.c 30 | endif 31 | 32 | D1 macro text 33 | DOUT 1, < ",13,10,"ADLIB: &text&> 34 | endm 35 | D2 macro text 36 | DOUT 2, < &text&> 37 | endm 38 | D3 macro text 39 | DOUT 3, < &text&> 40 | endm 41 | D4 macro text 42 | DOUT 4, < &text&> 43 | endm 44 | 45 | DOUT macro level, text 46 | local string_buffer 47 | local wrong_level 48 | 49 | ifdef DEBUG 50 | 51 | _DATA segment 52 | string_buffer label byte 53 | db "&text&", 0 54 | _DATA ends 55 | 56 | cmp [_wDebugLevel], level 57 | jl wrong_level 58 | push ds 59 | push DataOFFSET string_buffer 60 | call OutputDebugStr 61 | wrong_level: 62 | endif 63 | endm 64 | 65 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 66 | ; 67 | ; assert macros 68 | ; 69 | ; AssertF byte -- fail iff byte==0 70 | ; AssertT byte -- fail iff byte!=0 71 | ; 72 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 73 | 74 | AssertF macro exp 75 | local assert_ok 76 | ifdef DEBUG 77 | push ax 78 | 79 | mov al, exp 80 | or al, al 81 | jnz assert_ok 82 | 83 | D1 84 | int 3 85 | 86 | assert_ok: 87 | pop ax 88 | endif 89 | endm 90 | 91 | AssertT macro exp 92 | local assert_ok 93 | ifdef DEBUG 94 | push ax 95 | 96 | mov al, exp 97 | or al, al 98 | jz assert_ok 99 | 100 | D1 101 | int 3 102 | 103 | assert_ok: 104 | pop ax 105 | endif 106 | endm 107 | 108 | 109 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 110 | ; 111 | ; data segment 112 | ; 113 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 114 | 115 | sBegin Data 116 | 117 | externW <_wPort> ; address of sound chip 118 | 119 | sEnd Data 120 | 121 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 122 | ; 123 | ; code segment 124 | ; 125 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 126 | 127 | ifndef SEGNAME 128 | SEGNAME equ <_TEXT> 129 | endif 130 | 131 | createSeg %SEGNAME, CodeSeg, word, public, CODE 132 | 133 | sBegin CodeSeg 134 | 135 | assumes cs, CodeSeg 136 | assumes ds, Data 137 | 138 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 139 | ; @doc INTERNAL 140 | ; 141 | ; @api BYTE | inport | Read a port. 142 | ; 143 | ; @parm WORD | port | The port to read. 144 | ; 145 | ; @rdesc Returns the port value read. 146 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 147 | 148 | assumes ds, Data 149 | assumes es, nothing 150 | 151 | cProc inport <> 152 | cBegin nogen 153 | 154 | mov dx, [_wPort] 155 | in al, dx 156 | 157 | D4 158 | 159 | retf 160 | 161 | cEnd nogen 162 | 163 | 164 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 165 | ; @doc INTERNAL 166 | ; 167 | ; @api void | SndOutput | This function writes data to the chip registers. 168 | ; 169 | ; @parm BYTE | bRegister | The address of the register to write. 170 | ; 171 | ; @parm BYTE | bData | The data value to write. 172 | ; 173 | ; @rdesc There is no return value. 174 | ; 175 | ; @comm Delay is included after each write. 176 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 177 | 178 | assumes ds, Data 179 | assumes es, nothing 180 | 181 | cProc SndOutput <> 182 | ParmB bRegister ; register address 183 | ParmB bData ; value to write 184 | cBegin 185 | 186 | ifdef DEBUG 187 | mov al, bRegister 188 | mov ah, bData 189 | D4 190 | endif 191 | 192 | mov dx, [_wPort] ; get register select address of sound chip 193 | mov al, bRegister ; register number to write to 194 | out dx, al 195 | 196 | if OPL_ON_LPT 197 | inc dx 198 | inc dx 199 | mov al, 13 200 | out dx, al 201 | xor al, 4 202 | out dx, al 203 | xor al, 4 204 | out dx, al 205 | endif 206 | 207 | mov cx, 4 ; 3.3us delay for OPL2/OPL3 208 | @@: in al, dx 209 | dec cx 210 | jnz @B 211 | 212 | if OPL_ON_LPT 213 | dec dx 214 | dec dx 215 | mov al, bData 216 | out dx, al 217 | inc dx 218 | inc dx 219 | mov al, 12 220 | out dx, al 221 | xor al, 4 222 | out dx, al 223 | xor al, 4 224 | out dx, al 225 | else 226 | inc dx ; data write address of chip (2/2/1 cycles) 227 | mov al, bData ; value to write (5/4/1 cycles) 228 | out dx, al 229 | endif 230 | 231 | if OPL-2 232 | mov cx, 4 ; 3.3us delay for OPL3 233 | else 234 | mov cx, 23 ; 23us delay for OPL2 235 | endif 236 | @@: in al, dx 237 | dec cx 238 | jnz @B 239 | cEnd 240 | 241 | 242 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 243 | ; @doc INTERNAL 244 | ; 245 | ; @asm WEP | This function is called when the DLL is unloaded. 246 | ; 247 | ; @parm WORD | UselessParm | This parameter has no meaning. 248 | ; 249 | ; @comm WARNING: This function is basically useless since you can't call any 250 | ; kernel function that may cause the LoadModule() code to be reentered. 251 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 252 | 253 | assumes ds, nothing 254 | assumes es, nothing 255 | 256 | cProc WEP , <> 257 | ; ParmW wUselessParm 258 | cBegin nogen 259 | mov ax, 1 260 | retf 2 261 | cEnd nogen 262 | 263 | sEnd CodeSeg 264 | 265 | end 266 | -------------------------------------------------------------------------------- /BUILD.BAT: -------------------------------------------------------------------------------- 1 | if exist pub\nul deltree /y pub 2 | mkdir pub 3 | 4 | nmake clean 5 | mkdir pub\adlib 6 | nmake OPL_ON_LPT=NO OPL=2 DRV_BIN=pub\adlib 7 | 8 | nmake clean 9 | mkdir pub\lpt3bc 10 | nmake OPL_ON_LPT=YES OPL=2 DEF_PORT=0x3BC DRV_BIN=pub\lpt3bc 11 | 12 | nmake clean 13 | mkdir pub\lpt378 14 | nmake OPL_ON_LPT=YES OPL=2 DEF_PORT=0x378 DRV_BIN=pub\lpt378 15 | 16 | nmake clean 17 | mkdir pub\lpt278 18 | nmake OPL_ON_LPT=YES OPL=2 DEF_PORT=0x278 DRV_BIN=pub\lpt278 19 | 20 | nmake clean 21 | deltree /y bin 22 | -------------------------------------------------------------------------------- /DRUMKIT.BIN: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/DRUMKIT.BIN -------------------------------------------------------------------------------- /DRVPROC.C: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * 3 | * drvproc.c 4 | * 5 | * Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 6 | * Copyright (c) 2019-2020 Andrei Warkentin 7 | * 8 | ***************************************************************************/ 9 | 10 | #include 11 | #include 12 | #include "adlib.h" 13 | 14 | /*************************************************************************** 15 | * @doc INTERNAL 16 | * 17 | * @func void | DrvLoadInitialize | This is where system.ini can get parsed 18 | * using GetPrivateProfileInt. 19 | * 20 | * @rdesc Nothing. 21 | ***************************************************************************/ 22 | static void PASCAL NEAR 23 | DrvLoadInitialize(void) 24 | { 25 | } 26 | 27 | /*************************************************************************** 28 | * @doc INTERNAL 29 | * 30 | * @api LONG | DriverProc | The entry point for an installable driver. 31 | * 32 | * @parm DWORD | dwDriverId | For most messages,

is the DWORD 33 | * value that the driver returns in response to a message. 34 | * Each time that the driver is opened, through the API, 35 | * the driver receives a message and can return an 36 | * arbitrary, non-zero value. The installable driver interface 37 | * saves this value and returns a unique driver handle to the 38 | * application. Whenever the application sends a message to the 39 | * driver using the driver handle, the interface routes the message 40 | * to this entry point and passes the corresponding

. 41 | * This mechanism allows the driver to use the same or different 42 | * identifiers for multiple opens but ensures that driver handles 43 | * are unique at the application interface layer. 44 | * 45 | * The following messages are not related to a particular open 46 | * instance of the driver. For these messages, the dwDriverId 47 | * will always be zero. 48 | * 49 | * DRV_LOAD, DRV_FREE, DRV_ENABLE, DRV_DISABLE, DRV_OPEN 50 | * 51 | * @parm HANDLE | hDriver | This is the handle returned to the 52 | * application by the driver interface. 53 | 54 | * @parm WORD | wMessage | The requested action to be performed. Message 55 | * values below are used for globally defined messages. 56 | * Message values from to are used for 57 | * defined driver protocols. Messages above are used 58 | * for driver specific messages. 59 | * 60 | * @parm LONG | lParam1 | Data for this message. Defined separately for 61 | * each message 62 | * 63 | * @parm LONG | lParam2 | Data for this message. Defined separately for 64 | * each message 65 | * 66 | * @rdesc Defined separately for each message. 67 | ***************************************************************************/ 68 | LONG FAR PASCAL _loadds 69 | DriverProc(DWORD dwDriverID, 70 | HANDLE hDriver, 71 | WORD wMessage, 72 | LONG lParam1, 73 | LONG lParam2) 74 | { 75 | switch (wMessage) { 76 | case DRV_LOAD: 77 | D1("DRV_LOAD"); 78 | 79 | /* 80 | * Sent to the driver when it is loaded. Always the first 81 | * message received by a driver. 82 | * 83 | * dwDriverID is 0L. 84 | * lParam1 is 0L. 85 | * lParam2 is 0L. 86 | * 87 | * Return 0L to fail the load. 88 | * 89 | * DefDriverProc will return NON-ZERO so we don't have to 90 | * handle DRV_LOAD 91 | */ 92 | 93 | DrvLoadInitialize(); 94 | return 1L; 95 | 96 | case DRV_FREE: 97 | D1("DRV_FREE"); 98 | 99 | /* 100 | * Sent to the driver when it is about to be discarded. This 101 | * will always be the last message received by a driver before 102 | * it is freed. 103 | * 104 | * dwDriverID is 0L. 105 | * lParam1 is 0L. 106 | * lParam2 is 0L. 107 | * Return value is ignored. 108 | */ 109 | return 1L; 110 | 111 | case DRV_OPEN: 112 | D1("DRV_OPEN"); 113 | 114 | /* 115 | * Sent to the driver when it is opened. 116 | * 117 | * dwDriverID is 0L. 118 | * 119 | * lParam1 is a far pointer to a zero-terminated string 120 | * containing the name used to open the driver. 121 | * 122 | * lParam2 is passed through from the drvOpen call. 123 | * 124 | * Return 0L to fail the open. 125 | * 126 | * DefDriverProc will return ZERO so we do have to 127 | * handle the DRV_OPEN message. 128 | */ 129 | return 1L; 130 | 131 | case DRV_CLOSE: 132 | D1("DRV_CLOSE"); 133 | 134 | /* 135 | * Sent to the driver when it is closed. Drivers are unloaded 136 | * when the close count reaches zero. 137 | * 138 | * dwDriverID is the driver identifier returned from the 139 | * corresponding DRV_OPEN. 140 | * 141 | * lParam1 is passed through from the drvClose call. 142 | * 143 | * lParam2 is passed through from the drvClose call. 144 | * 145 | * Return 0L to fail the close. 146 | * 147 | * DefDriverProc will return ZERO so we do have to 148 | * handle the DRV_CLOSE message. 149 | */ 150 | return 1L; 151 | 152 | case DRV_ENABLE: 153 | D1("DRV_ENABLE"); 154 | 155 | /* 156 | * Sent to the driver when the driver is loaded or reloaded 157 | * and whenever Windows is enabled. Drivers should only 158 | * hook interrupts or expect ANY part of the driver to be in 159 | * memory between enable and disable messages 160 | * 161 | * dwDriverID is 0L. 162 | * lParam1 is 0L. 163 | * lParam2 is 0L. 164 | * 165 | * Return value is ignored. 166 | */ 167 | return Enable() ? 1L : 0L; 168 | 169 | case DRV_DISABLE: 170 | D1("DRV_DISABLE"); 171 | 172 | /* 173 | * Sent to the driver before the driver is freed. 174 | * and whenever Windows is disabled 175 | * 176 | * dwDriverID is 0L. 177 | * lParam1 is 0L. 178 | * lParam2 is 0L. 179 | * 180 | * Return value is ignored. 181 | */ 182 | Disable(); 183 | return 1L; 184 | 185 | case DRV_QUERYCONFIGURE: 186 | D1("DRV_QUERYCONFIGURE"); 187 | 188 | /* 189 | * Sent to the driver so that applications can 190 | * determine whether the driver supports custom 191 | * configuration. The driver should return a 192 | * non-zero value to indicate that configuration 193 | * is supported. 194 | * 195 | * dwDriverID is the value returned from the DRV_OPEN 196 | * call that must have succeeded before this message 197 | * was sent. 198 | * 199 | * lParam1 is passed from the app and is undefined. 200 | * lParam2 is passed from the app and is undefined. 201 | * 202 | * Return 0L to indicate configuration NOT supported. 203 | */ 204 | return 0L; /* we don't do configuration */ 205 | 206 | case DRV_CONFIGURE: 207 | D1("DRV_CONFIGURE"); 208 | 209 | /* 210 | * Sent to the driver so that it can display a custom 211 | * configuration dialog box. 212 | * 213 | * lParam1 is passed from the app. and should contain 214 | * the parent window handle in the loword. 215 | * lParam2 is passed from the app and is undefined. 216 | * 217 | * Return value is undefined. 218 | * 219 | * Drivers should create their own section in system.ini. 220 | * The section name should be the driver name. 221 | */ 222 | return DRVCNF_CANCEL; 223 | 224 | case DRV_INSTALL: 225 | D1("DRV_INSTALL"); 226 | return DRVCNF_RESTART; 227 | 228 | default: 229 | return DefDriverProc(dwDriverID, hDriver, wMessage, lParam1, lParam2); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /INIT.C: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * 3 | * init.c 4 | * 5 | * Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 6 | * Copyright (c) 2019-2020 Andrei Warkentin 7 | * 8 | ***************************************************************************/ 9 | 10 | #include 11 | #include 12 | #include 13 | #define NOSTR /* to avoid redefining the strings */ 14 | #include "adlib.h" 15 | 16 | void FAR SoundWarmInit(void); 17 | 18 | static int NEAR SoundColdInit(void); 19 | static int NEAR BoardInstalled(void); 20 | static void NEAR SetMode(BYTE mode); 21 | static void NEAR SetGParam(BYTE amD, BYTE vibD, BYTE nSel); 22 | static void NEAR Set3812(BYTE state); 23 | static void NEAR InitSlotVolume(void); 24 | static void NEAR InitFNums(void); 25 | static void NEAR SoundChut(BYTE voice); 26 | static void NEAR SetPitchRange(WORD pR); 27 | static void NEAR SetFNum(NPWORD fNumVec, int num, int den); 28 | static long NEAR CalcPremFNum(int numDeltaDemiTon, int denDeltaDemiTon); 29 | static int NEAR PASCAL LoadPatches(void); 30 | static int NEAR PASCAL LoadDrumPatches(void); 31 | 32 | WORD wPort = DEF_PORT; /* address of sound chip */ 33 | BOOL fEnabled; /* are we enabled? */ 34 | TIMBRE patches[MAXPATCH]; /* patch data */ 35 | DRUMPATCH drumpatch[NUMDRUMNOTES]; /* drum kit data */ 36 | 37 | #ifdef DEBUG 38 | WORD wDebugLevel; /* debug level */ 39 | #endif 40 | 41 | #define BCODE _based(_segname("_CODE")) 42 | 43 | /* non-localized strings */ 44 | char BCODE aszDriverName[] = "adlib21.drv"; 45 | char BCODE aszProductName[] = "Ad Lib"; 46 | char BCODE aszSystemIni[] = "system.ini"; 47 | #ifdef DEBUG 48 | char BCODE aszAdlib[] = "adlib"; 49 | char BCODE aszMMDebug[] = "mmdebug"; 50 | #endif 51 | 52 | static HANDLE ghInstance; /* our global instance */ 53 | static BOOL fInit; /* have we initialized yet? */ 54 | 55 | /* format of drumkit.bin file */ 56 | typedef struct drumfilepatch_tag { 57 | BYTE key; /* the key to map */ 58 | BYTE patch; /* the patch to use */ 59 | BYTE note; /* the note to play */ 60 | } DRUMFILEPATCH, *NPDRUMFILEPATCH, FAR *LPDRUMFILEPATCH; 61 | 62 | /**************************************************************************** 63 | * @doc INTERNAL 64 | * 65 | * @api int | SoundColdInit | Must be called for start-up initialization. 66 | * 67 | * @rdesc Returns a nonzero value if the board is installed and zero otherwise. 68 | ***************************************************************************/ 69 | static int NEAR 70 | SoundColdInit(void) 71 | { 72 | int hardware; 73 | 74 | D1("SoundColdInit"); 75 | 76 | if (hardware = BoardInstalled()) { 77 | SoundWarmInit(); 78 | } 79 | 80 | return hardware; 81 | } 82 | 83 | /**************************************************************************** 84 | * @doc INTERNAL 85 | * 86 | * @api void | SoundWarmInit | Initializes the chip in melodic mode (mode == 0). 87 | * 88 | * @rdesc There is no return value. 89 | ***************************************************************************/ 90 | void FAR 91 | SoundWarmInit(void) 92 | { 93 | BYTE i; 94 | 95 | D1("SoundWarmInit"); 96 | 97 | SetGParam(0, 0, 0); /* init global parameters */ 98 | InitSlotVolume(); /* sets volume of each slot to MAXVOLUME */ 99 | InitFNums(); /* initializes frequency shift table to no shift */ 100 | for (i = 0 ; i <= 8; i++) { 101 | SoundChut(i); /* set frequencies of voices 0 - 8 to 0 */ 102 | } 103 | SetMode(1); /* percussion mode (melodic mode == 0) */ 104 | SetPitchRange(2); /* GMM pitch range is 2 semitones */ 105 | 106 | #if 1 107 | Set3812(1); /* sets wave-select parameter */ 108 | #else 109 | Set3812(0); /* DOES NOT set wave-select parameter */ 110 | #endif 111 | } 112 | 113 | /**************************************************************************** 114 | * @doc INTERNAL 115 | * 116 | * @api int | BoardInstalled | Checks to see if the board is installed. 117 | * 118 | * @rdesc Returns a nonzero value if the board is installed and zero otherwise. 119 | ***************************************************************************/ 120 | static int NEAR 121 | BoardInstalled(void) 122 | { 123 | D1("BoardInstalled"); 124 | 125 | if (OPL_ON_LPT) { 126 | /* 127 | * OPL2LPT/OPL3LPT cannot be detected. 128 | */ 129 | return 1; 130 | } else { 131 | BYTE t1, t2, i; 132 | 133 | SndOutput(4, 0x60); /* mask T1 & T2 */ 134 | SndOutput(4, 0x80); /* reset IRQ */ 135 | t1 = inport(); /* read status register */ 136 | SndOutput(2, 0xff); /* set timer - 1 latch */ 137 | SndOutput(4, 0x21); /* unmask & start T1 */ 138 | for (i = 0; i < 80; i++) { /* At least 80 uSec delay */ 139 | inport(); 140 | } 141 | t2 = inport(); /* read status register */ 142 | SndOutput(4, 0x60); 143 | SndOutput(4, 0x80); 144 | 145 | return (t1 & 0xE0) == 0 && (t2 & 0xE0) == 0xC0; 146 | } 147 | } 148 | 149 | /**************************************************************************** 150 | * @doc INTERNAL 151 | * 152 | * @api void | SetMode | Puts the chip in melodic mode (mode == 0), or in 153 | * percussive mode (mode != 0). 154 | * 155 | * @parm BYTE | mode | Specifies which mode to put the chip into. 156 | * 157 | * @rdesc There is no return value. 158 | ***************************************************************************/ 159 | static void NEAR 160 | SetMode(BYTE mode) 161 | { 162 | if (mode) { 163 | SetFreq(TOM, TOM_PITCH, 0); /* set frequency of TOM voice */ 164 | SetFreq(SD, SD_PITCH, 0); /* set frequency of SD voice */ 165 | } 166 | 167 | fPercussion = mode; 168 | percBits = 0; /* initialize control bits of percussive voices */ 169 | } 170 | 171 | /**************************************************************************** 172 | * @doc INTERNAL 173 | * 174 | * @api void | SetGParam | Sets the 3 global parameters AmDepth, VibDepth 175 | * and NoteSel. The change takes place immediately. 176 | * 177 | * @parm BYTE | amD | The new AmDepth parameter. 178 | * 179 | * @parm BYTE | vibD | The new VibDepth parameter. 180 | * 181 | * @parm BYTE | nSel | The new NoteSel parameter. 182 | * 183 | * @rdesc There is no return value. 184 | ***************************************************************************/ 185 | static void NEAR 186 | SetGParam(BYTE amD, 187 | BYTE vibD, 188 | BYTE nSel) 189 | { 190 | amDepth = amD; 191 | vibDepth = vibD; 192 | noteSel = nSel; 193 | 194 | SndSAmVibRhythm(); 195 | SndSNoteSel(); 196 | } 197 | 198 | /**************************************************************************** 199 | * @doc INTERNAL 200 | * 201 | * @api void | Set3812 | Enables (state != 0) or disables (state == 0) the 202 | * wave-select parameters. 203 | * 204 | * @parm BYTE | state | Indicates whether to enable or disable the wave-select 205 | * parameters. 206 | * 207 | * @comm If you do not want to use the wave-select parameters, call this 208 | * function with a value of 0 AFTER calling SoundColdInit() or 209 | * SoundWarmInit(). 210 | * 211 | * @rdesc There is no return value. 212 | ***************************************************************************/ 213 | static void NEAR 214 | Set3812(BYTE state) 215 | { 216 | BYTE i; 217 | 218 | D1("Set3812"); 219 | 220 | /* set waveform for each of the 18 slots to sine wave */ 221 | for (i = 0; i < 18; i++) { 222 | SndOutput((BYTE)(0xE0 | offsetSlot[i]), 0); 223 | } 224 | 225 | /* enable/disable the wave-select parameters */ 226 | modeWaveSel = (BYTE)(state ? 0x20 : 0); 227 | SndOutput(1, modeWaveSel); 228 | } 229 | 230 | #if 0 /* never used */ 231 | static void NEAR 232 | InitSlotParams(void); 233 | /**************************************************************************** 234 | * @doc INTERNAL 235 | * 236 | * @api void | InitSlotParams | In melodic mode, this function initializes all 237 | * voices to electric-pianos. In percussive mode, it initializes the 6 238 | * melodic voices to electric-pianos and the 5 percussive voices to their 239 | * default timbres. 240 | * 241 | * @comm This function is pointless because the timbre of each voice gets 242 | * set as soon as the voice is allocated, so it's commented out. 243 | * 244 | * @rdesc There is no return value. 245 | ***************************************************************************/ 246 | static void NEAR 247 | InitSlotParams(void) 248 | { 249 | BYTE i; 250 | 251 | /* definition of default melodic(electric piano) and percussive voices: */ 252 | static BYTE pianoOpr0[] = { 1, 1, 3, 15, 5, 0, 1, 3, 15, 0, 0, 0, 1, 0 }; 253 | static BYTE pianoOpr1[] = { 0, 1, 1, 15, 7, 0, 2, 4, 0, 0, 0, 1, 0, 0 }; 254 | static BYTE bdOpr0[] = { 0, 0, 0, 10, 4, 0, 8, 12, 11, 0, 0, 0, 1, 0 }; 255 | static BYTE bdOpr1[] = { 0, 0, 0, 13, 4, 0, 6, 15, 0, 0, 0, 0, 1, 0 }; 256 | static BYTE sdOpr[] = { 0, 12, 0, 15, 11, 0, 8, 5, 0, 0, 0, 0, 0, 0 }; 257 | static BYTE tomOpr[] = { 0, 4, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 }; 258 | static BYTE cymbOpr[] = { 0, 1, 0, 15, 11, 0, 5, 5, 0, 0, 0, 0, 0, 0 }; 259 | static BYTE hhOpr[] = { 0, 1, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 }; 260 | 261 | for (i = 0; i < 18; i++) { 262 | if (operSlot[i]) { 263 | SetSlotParam(i, pianoOpr1, 0); 264 | } else { 265 | SetSlotParam(i, pianoOpr0, 0); 266 | } 267 | } 268 | 269 | if (fPercussion) { 270 | SetSlotParam(12, bdOpr0, 0); 271 | SetSlotParam(15, bdOpr1, 0); 272 | SetSlotParam(16, sdOpr, 0); 273 | SetSlotParam(14, tomOpr, 0); 274 | SetSlotParam(17, cymbOpr, 0); 275 | SetSlotParam(13, hhOpr, 0); 276 | } 277 | } 278 | #endif 279 | 280 | /**************************************************************************** 281 | * @doc INTERNAL 282 | * 283 | * @api void | InitSlotVolume | Sets the volume values in the 284 | * array to MAXVOLUME. 285 | * 286 | * @rdesc There is no return value. 287 | ***************************************************************************/ 288 | static void NEAR 289 | InitSlotVolume(void) 290 | { 291 | int i; 292 | 293 | for (i = 0; i < 18; i++) { 294 | slotRelVolume[i] = MAXVOLUME; 295 | } 296 | } 297 | 298 | /**************************************************************************** 299 | * @doc INTERNAL 300 | * 301 | * @api void | InitFNums | Initializes all lines of the frequency table 302 | * (the

array). Each line represents 12 half-tones shifted 303 | * by (n / NR_STEP_PITCH), where 'n' is the line number and ranges from 304 | * 1 to NR_STEP_PITCH. 305 | * 306 | * @rdesc There is no return value. 307 | ***************************************************************************/ 308 | static void NEAR 309 | InitFNums(void) 310 | { 311 | WORD i, j, k; 312 | WORD num; /* numerator */ 313 | WORD numStep; /* step value for numerator */ 314 | WORD row; /* row in the frequency table */ 315 | 316 | /* calculate each row in the fNumNotes table */ 317 | numStep = 100 / NR_STEP_PITCH; 318 | for (num = row = 0; row < NR_STEP_PITCH; row++, num += numStep) { 319 | SetFNum(fNumNotes[row], num, 100); 320 | } 321 | 322 | /* fNumFreqPtr has an element for each voice, pointing to the */ 323 | /* appropriate row in the fNumNotes table. They're all initialized */ 324 | /* to the first row, which represents no pitch shift. */ 325 | for (i = 0; i < 11; i++) { 326 | fNumFreqPtr[i] = fNumNotes[0]; 327 | halfToneOffset[i] = 0; 328 | } 329 | 330 | /* just for optimization */ 331 | for (i = 0, k = 0; i < 8; i++) { 332 | for (j = 0; j < 12; j++, k++) { 333 | noteDIV12[k] = (BYTE)i; 334 | noteMOD12[k] = (BYTE)j; 335 | } 336 | } 337 | } 338 | 339 | /**************************************************************************** 340 | * @doc INTERNAL 341 | * 342 | * @api void | SoundChut | Sets the frequency of voice

to 0 Hz. 343 | * 344 | * @parm BYTE | voice | Specifies which voice to set. 345 | * 346 | * @rdesc There is no return value. 347 | ***************************************************************************/ 348 | static void NEAR 349 | SoundChut(BYTE voice) 350 | { 351 | D1("SoundChut"); 352 | 353 | SndOutput((BYTE)(0xA0 | voice), 0); 354 | SndOutput((BYTE)(0xB0 | voice), 0); 355 | } 356 | 357 | /**************************************************************************** 358 | * @doc INTERNAL 359 | * 360 | * @api void | SetPitchRange | This routine changes the global pitch bend 361 | * range value. 362 | * 363 | * @parm WORD | pR | The new pitch bend range. 364 | * 365 | * @comm The value can be from 1 to 12 (in half-tones). For example, the 366 | * value 12 means that the pitch bend will range from -12 (pitchBend == 0, 367 | * see ) to +12 (pitchBend == 0x3fff) half-tones. The 368 | * change will be effective as of the next call to . 369 | * 370 | * @rdesc There is no return value. 371 | ***************************************************************************/ 372 | static void NEAR 373 | SetPitchRange(WORD pR) 374 | { 375 | if (pR > 12) { 376 | pR = 12; 377 | } else if (pR < 1) { 378 | pR = 1; 379 | } 380 | pitchRangeStep = pR * NR_STEP_PITCH; 381 | } 382 | 383 | /**************************************************************************** 384 | * @doc INTERNAL 385 | * 386 | * @api void | SetFNum | Initializes a line in the frequency table with 387 | * shifted frequency values. The values are shifted a fraction (num/den) 388 | * of a half-tone. 389 | * 390 | * @parm NPWORD | fNumVec | The line from the frequency table. 391 | * 392 | * @parm int | num | Numerator. 393 | * 394 | * @parm int | den | Denominator. 395 | * 396 | * @xref CalcPremFNum 397 | * 398 | * @rdesc There is no return value. 399 | ***************************************************************************/ 400 | static void NEAR 401 | SetFNum(NPWORD fNumVec, 402 | int num, 403 | int den) 404 | { 405 | int i; 406 | long val; 407 | 408 | *fNumVec++ = (WORD)((4 + (val = CalcPremFNum(num, den))) >> 3); 409 | for (i = 1; i < 12; i++) { 410 | val *= 106; 411 | *fNumVec++ = (WORD)((4 + (val /= 100)) >> 3); 412 | } 413 | } 414 | 415 | /**************************************************************************** 416 | * @doc INTERNAL 417 | * 418 | * @api long | CalcPremFNum | Calculates some magic number that is used in 419 | * setting the values in the

table. 420 | * 421 | * @parm int | numDeltaDemiTon | Numerator (-100 to +100). 422 | * 423 | * @parm int | denDeltaDemiTon | Denominator (1 to 100). 424 | * 425 | * @comm If the numerator (numDeltaDemiTon) is positive, the frequency is 426 | * increased; if negative, it is decreased. The function calculates: 427 | * f8 = Fb(1 + 0.06 num /den) (where Fb = 26044 * 2 / 25) 428 | * fNum8 = f8 * 65536 * 72 / 3.58e6 429 | * 430 | * @rdesc Returns fNum8, which is the binary value of the frequency 260.44 (C) 431 | * shifted by +/-

/

* 8. 432 | ***************************************************************************/ 433 | static long NEAR 434 | CalcPremFNum(int numDeltaDemiTon, 435 | int denDeltaDemiTon) 436 | { 437 | long f8; 438 | long fNum8; 439 | long d100; 440 | 441 | d100 = denDeltaDemiTon * 100; 442 | f8 = (d100 + 6 * numDeltaDemiTon) * (26044L * 2L); 443 | f8 /= d100 * 25; 444 | 445 | fNum8 = f8 * 16384; 446 | fNum8 *= 9L; 447 | fNum8 /= 179L * 625L; 448 | 449 | return fNum8; 450 | } 451 | 452 | /**************************************************************************** 453 | * @doc INTERNAL 454 | * 455 | * @api int | LoadPatches | Reads the patch set from the BANK resource and 456 | * builds the

array. 457 | * 458 | * @rdesc Returns the number of patches loaded, or 0 if an error occurs. 459 | ***************************************************************************/ 460 | static int NEAR PASCAL 461 | LoadPatches(void) 462 | { 463 | HANDLE hResInfo; 464 | HANDLE hResData; 465 | LPSTR lpRes; 466 | int iPatches; 467 | DWORD dwOffset; 468 | DWORD dwResSize; 469 | LPTIMBRE lpBankTimbre; 470 | LPTIMBRE lpPatchTimbre; 471 | LPBANKHDR lpBankHdr; 472 | 473 | /* find resource and get its size */ 474 | hResInfo = FindResource(ghInstance, MAKEINTRESOURCE(DEFAULTBANK), MAKEINTRESOURCE(RT_BANK)); 475 | if (!hResInfo) { 476 | D1("Default bank resource not found"); 477 | return 0; 478 | } 479 | dwResSize = (DWORD)SizeofResource(ghInstance, hResInfo); 480 | 481 | /* load and lock resource */ 482 | hResData = LoadResource(ghInstance, hResInfo); 483 | if (!hResData) { 484 | D1("Bank resource not loaded"); 485 | return 0; 486 | } 487 | lpRes = LockResource(hResData); 488 | if (!lpRes) { 489 | D1("Bank resource not locked"); 490 | return 0; 491 | } 492 | 493 | /* read the bank resource, loading patches as we find them */ 494 | 495 | D1("loading patches"); 496 | lpBankHdr = (LPBANKHDR)lpRes; 497 | dwOffset = lpBankHdr->offsetTimbre; /* point to first one */ 498 | 499 | for (iPatches = 0; iPatches < MAXPATCH; iPatches++) { 500 | lpBankTimbre = (LPTIMBRE)(lpRes + dwOffset); 501 | lpPatchTimbre = &patches[iPatches]; 502 | *lpPatchTimbre = *lpBankTimbre; 503 | 504 | dwOffset += sizeof(TIMBRE); 505 | if (dwOffset + sizeof(TIMBRE) > dwResSize) { 506 | D1("Attempt to read past end of bank resource"); 507 | break; 508 | } 509 | } 510 | 511 | UnlockResource(hResData); 512 | FreeResource(hResData); 513 | 514 | return iPatches; 515 | } 516 | 517 | /**************************************************************************** 518 | * @doc INTERNAL 519 | * 520 | * @api int | LoadDrumPatches | Reads the drum kit patch set from the 521 | * DRUMKIT resource and builds the

array. 522 | * 523 | * @comm Each entry of the array (representing a key number 524 | * from the "drum patch") consists of a patch number and note number 525 | * from some other patch. 526 | * 527 | * @rdesc Returns the number of patches loaded, or 0 if an error occurs. 528 | ***************************************************************************/ 529 | static int NEAR PASCAL 530 | LoadDrumPatches(void) 531 | { 532 | HANDLE hResInfo; 533 | HANDLE hResData; 534 | LPSTR lpRes; 535 | int iPatches; 536 | int key; 537 | DWORD dwOffset; 538 | DWORD dwResSize; 539 | LPDRUMFILEPATCH lpResPatch; 540 | 541 | /* find resource and get its size */ 542 | hResInfo = FindResource(ghInstance, MAKEINTRESOURCE(DEFAULTDRUMKIT), MAKEINTRESOURCE(RT_DRUMKIT)); 543 | if (!hResInfo) { 544 | D1("Default drum resource not found"); 545 | return 0; 546 | } 547 | dwResSize = (DWORD)SizeofResource(ghInstance, hResInfo); 548 | 549 | /* load and lock resource */ 550 | hResData = LoadResource(ghInstance, hResInfo); 551 | if (!hResData) { 552 | D1("Drum resource not loaded"); 553 | return 0; 554 | } 555 | lpRes = LockResource(hResData); 556 | if (!lpRes) { 557 | D1("Drum resource not locked"); 558 | return 0; 559 | } 560 | 561 | /* read the drum resource, loading patches as we find them */ 562 | 563 | D1("reading drum data"); 564 | dwOffset = 0; 565 | for (iPatches = 0; iPatches < NUMDRUMNOTES; iPatches++) { 566 | lpResPatch = (LPDRUMFILEPATCH)(lpRes + dwOffset); 567 | key = lpResPatch->key; 568 | if ((key >= FIRSTDRUMNOTE) && (key <= LASTDRUMNOTE)) { 569 | drumpatch[key - FIRSTDRUMNOTE].patch = lpResPatch->patch; 570 | drumpatch[key - FIRSTDRUMNOTE].note = lpResPatch->note; 571 | } else { 572 | D1("Drum patch key out of range"); 573 | } 574 | 575 | dwOffset += sizeof(DRUMFILEPATCH); 576 | if (dwOffset + sizeof(DRUMFILEPATCH) > dwResSize) { 577 | D1("Attempt to read past end of drum resource"); 578 | break; 579 | } 580 | } 581 | 582 | UnlockResource(hResData); 583 | FreeResource(hResData); 584 | 585 | return iPatches; 586 | } 587 | 588 | /**************************************************************************** 589 | * @doc INTERNAL 590 | * 591 | * @api BOOL | Enable | Enables the card. If we haven't yet enabled in 592 | * this session, it will do a cold restart of the card and load the 593 | * patches; otherwise it will do a warm restart. 594 | * 595 | * @rdesc Returns TRUE if successful and false otherwise. 596 | ***************************************************************************/ 597 | BOOL NEAR PASCAL 598 | Enable(void) 599 | { 600 | D1("Enable"); 601 | 602 | if (!OPL_ON_LPT && vadlibdAcquireAdLibSynth()) { 603 | D1("AdLib could NOT be aquired for ENABLE!!!"); 604 | return FALSE; 605 | } 606 | 607 | if (!fInit) { /* if we haven't initialized yet */ 608 | if (!SoundColdInit()) { /* if we can't find a card */ 609 | return FALSE; /* keep fInit set to FALSE */ 610 | } 611 | 612 | if (!LoadPatches()) { /* load the melodic patches */ 613 | return FALSE; 614 | } 615 | 616 | if (!LoadDrumPatches()) { /* load the drum kit information */ 617 | return FALSE; 618 | } 619 | } else { /* we've already initialized */ 620 | SoundWarmInit(); /* so do a warm restart */ 621 | } 622 | 623 | fInit = TRUE; 624 | fEnabled = TRUE; 625 | 626 | if (!OPL_ON_LPT && vadlibdReleaseAdLibSynth()) { 627 | D1("AdLib could NOT be RELEASED for ENABLE!!! VERY GOOFY!!"); 628 | } 629 | 630 | return TRUE; 631 | } 632 | 633 | /**************************************************************************** 634 | * @doc INTERNAL 635 | * 636 | * @api void | Disable | Since this function is called either when a 637 | * Windows session ends or when we switch to a DOS box (in 286 mode), 638 | * we'll reset the card in preparation for someone else to use it. 639 | * 640 | * @rdesc There is no return value. 641 | ***************************************************************************/ 642 | void NEAR PASCAL 643 | Disable(void) 644 | { 645 | D1("Disable"); 646 | 647 | if (fInit) { 648 | if (!OPL_ON_LPT && vadlibdAcquireAdLibSynth()) { 649 | D1("AdLib could NOT be aquired for DISABLE!!!"); 650 | } 651 | 652 | /* reset card to be good */ 653 | SoundWarmInit(); 654 | 655 | if (!OPL_ON_LPT && vadlibdReleaseAdLibSynth()) { 656 | D1("AdLib could NOT be RELEASED for DISABLE!!!"); 657 | } 658 | } 659 | 660 | fEnabled = FALSE; 661 | } 662 | 663 | /**************************************************************************** 664 | * @doc INTERNAL 665 | * 666 | * @api int | LibMain | Library initialization code. 667 | * 668 | * @parm HANDLE | hInstance | Our instance handle. 669 | * 670 | * @parm WORD | wHeapSize | The heap size from the .def file. 671 | * 672 | * @parm LPSTR | lpCmdLine | The command line. 673 | * 674 | * @rdesc Returns 1 if the initialization was successful and 0 otherwise. 675 | ***************************************************************************/ 676 | 677 | int NEAR PASCAL 678 | LibMain(HANDLE hInstance, 679 | WORD wHeapSize, 680 | LPSTR lpCmdLine) 681 | { 682 | #ifdef DEBUG 683 | /* get debug level - default is 0 */ 684 | wDebugLevel = GetProfileInt(aszMMDebug, aszAdlib, 0); 685 | #endif 686 | 687 | D1("LibMain"); 688 | 689 | ghInstance = hInstance; 690 | 691 | if (!OPL_ON_LPT) { 692 | vadlibdGetEntryPoint(); 693 | } 694 | 695 | return 1; 696 | } 697 | -------------------------------------------------------------------------------- /LIBINIT.ASM: -------------------------------------------------------------------------------- 1 | page 60,132 2 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 3 | ; 4 | ; libinit.asm 5 | ; 6 | ; Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 7 | ; Copyright (c) 2019-2020 Andrei Warkentin 8 | ; 9 | ; General Description: 10 | ; Library stub to do local init for a dynamic linked library. 11 | ; 12 | ; Restrictions: 13 | ; This must be the first object file in the LINK line. This assures 14 | ; that the reserved parameter block is at the *base* of DGROUP. 15 | ; 16 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 17 | if1 18 | %out link me first!! 19 | endif 20 | 21 | PMODE = 1 22 | 23 | .xlist 24 | include cmacros.inc 25 | include windows.inc 26 | include vadlibd.inc 27 | .list 28 | 29 | ?PLM=1 ; Pascal calling convention 30 | ?WIN=0 ; NO! Windows prolog/epilog code 31 | 32 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 33 | ; 34 | ; equates 35 | ; 36 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 37 | 38 | OFFSEL struc 39 | off dw ? 40 | sel dw ? 41 | OFFSEL ends 42 | 43 | 44 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 45 | ; 46 | ; segmentation 47 | ; 48 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 49 | 50 | ifndef SEGNAME 51 | SEGNAME equ <_TEXT> 52 | endif 53 | 54 | createSeg %SEGNAME, CodeSeg, word, public, CODE 55 | 56 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 57 | ; 58 | ; external functions 59 | ; 60 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 61 | 62 | externFP LocalInit ; in KERNEL 63 | externNP LibMain ; C code to do DLL init 64 | 65 | 66 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 67 | ; 68 | ; data segment 69 | ; 70 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 71 | 72 | sBegin Data 73 | 74 | assumes ds, Data 75 | 76 | ; stuff needed to avoid the C runtime coming in, and init the Windows 77 | ; reserved parameter block at the base of DGROUP 78 | 79 | org 0 ; base of DATA segment! 80 | 81 | dd 0 ; so null pointers get 0 82 | 83 | maxRsrvPtrs = 5 84 | dw maxRsrvPtrs 85 | 86 | usedRsrvPtrs = 0 87 | labelDP 88 | 89 | DefRsrvPtr macro name 90 | globalW name, 0 91 | usedRsrvPtrs = usedRsrvPtrs + 1 92 | endm 93 | 94 | DefRsrvPtr pLocalHeap ; local heap pointer 95 | DefRsrvPtr pAtomTable ; atom table pointer 96 | DefRsrvPtr pStackTop ; top of stack 97 | DefRsrvPtr pStackMin ; minimum value of SP 98 | DefRsrvPtr pStackBot ; bottom of stack 99 | 100 | if maxRsrvPtrs-usedRsrvPtrs 101 | dw maxRsrvPtrs-usedRsrvPtrs DUP (0) 102 | endif 103 | 104 | public __acrtused 105 | __acrtused = 1 106 | 107 | externW <_wPort> ; address of sound chip 108 | 109 | 110 | public VADLIBD_Entry 111 | VADLIBD_Entry dd 0 112 | 113 | sEnd Data 114 | 115 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 116 | ; 117 | ; code segment 118 | ; 119 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 120 | 121 | sBegin CodeSeg 122 | 123 | assumes cs, CodeSeg 124 | 125 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 126 | ; @doc INTERNAL 127 | ; 128 | ; @asm LibEntry | Called when DLL is loaded. 129 | ; 130 | ; @reg CX | Size of heap. 131 | ; 132 | ; @reg DI | Module handle. 133 | ; 134 | ; @reg DS | Automatic data segment. 135 | ; 136 | ; @reg ES:SI | Address of command line (not used). 137 | ; 138 | ; @rdesc AX is TRUE if the load is successful and FALSE otherwise. 139 | ; 140 | ; @comm Registers preserved are SI,DI,DS,BP. Registers destroyed are 141 | ; AX,BX,CX,DX,ES,FLAGS. 142 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 143 | cProc LibEntry , <> 144 | cBegin 145 | 146 | ; push frame for LibMain (hModule, cbHeap, lpszCmdLine) 147 | 148 | push di 149 | push cx 150 | push es 151 | push si 152 | 153 | ; init the local heap (if one is declared in the .def file) 154 | 155 | jcxz no_heap 156 | 157 | cCall LocalInit, <0, 0, cx> 158 | 159 | no_heap: 160 | cCall LibMain 161 | 162 | cEnd 163 | 164 | 165 | ;---------------------------------------------------------------------------; 166 | ; 167 | ; vadlibdGetEntryPoint 168 | ; 169 | ; DESCRIPTION: 170 | ; 171 | ; 172 | ;---------------------------------------------------------------------------; 173 | 174 | assumes ds, Data 175 | assumes es, nothing 176 | 177 | cProc vadlibdGetEntryPoint 178 | cBegin 179 | 180 | xor di, di ; zero ES:DI before call 181 | mov es, di 182 | mov ax, 1684h ; get device API entry point 183 | mov bx, VADLIBD_Device_ID ; virtual device ID 184 | int 2Fh ; call WIN/386 INT 2F API 185 | mov ax, es ; return farptr 186 | or ax, di 187 | jz gvadlibd_exit 188 | mov word ptr [VADLIBD_Entry.off], di 189 | mov word ptr [VADLIBD_Entry.sel], es 190 | 191 | gvadlibd_exit: 192 | cEnd 193 | 194 | 195 | ;---------------------------------------------------------------------------; 196 | ; 197 | ; WORD FAR PASCAL vadlibdAcquireAdLibSynth( void ) 198 | ; 199 | ; DESCRIPTION: 200 | ; 201 | ; ENTRY: 202 | ; 203 | ; EXIT: 204 | ; IF Carry Clear 205 | ; AX = 0, success--go ahead and open 206 | ; ELSE 207 | ; carry set, AX = error code: 208 | ; ADLIB_AS_Err_Bad_Synth equ 01h 209 | ; ADLIB_AS_Err_Already_Owned equ 02h 210 | ; 211 | ; USES: 212 | ; Flags, AX, BX, DX 213 | ; 214 | ;---------------------------------------------------------------------------; 215 | 216 | assumes ds, Data 217 | assumes es, nothing 218 | 219 | cProc vadlibdAcquireAdLibSynth <> 220 | cBegin 221 | 222 | mov ax, [VADLIBD_Entry.off] ; Q: is VADLIBD installed? 223 | or ax, [VADLIBD_Entry.sel] 224 | jz vadlibd_Acquire_Exit ; N: then leave (return 0) 225 | 226 | 227 | ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -; 228 | ; AX = Base of Syth to acquire (for example, 0388h) 229 | ; BX = Flags, should be zero. 230 | ; DX = ADLIB_API_Acquire_Synth (1) 231 | ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -; 232 | 233 | mov ax, [_wPort] ; base port to acquire 234 | xor bx, bx 235 | mov dx, ADLIB_API_Acquire_Synth 236 | call [VADLIBD_Entry] 237 | 238 | vadlibd_Acquire_Exit: 239 | 240 | cEnd 241 | 242 | 243 | ;---------------------------------------------------------------------------; 244 | ; 245 | ; WORD FAR PASCAL vadlibdReleaseAdLibSynth( void ) 246 | ; 247 | ; DESCRIPTION: 248 | ; 249 | ; ENTRY: 250 | ; 251 | ; EXIT: 252 | ; IF Carry Clear 253 | ; AX = 0, success--go ahead and open 254 | ; ELSE 255 | ; carry set, AX = error code: 256 | ; ADLIB_RS_Err_Bad_Synth equ 01h 257 | ; ADLIB_RS_Err_Not_Yours equ 02h 258 | ; 259 | ; USES: 260 | ; Flags, AX, BX, DX 261 | ; 262 | ;---------------------------------------------------------------------------; 263 | 264 | assumes ds, Data 265 | assumes es, nothing 266 | 267 | cProc vadlibdReleaseAdLibSynth <> 268 | cBegin 269 | 270 | mov ax, [VADLIBD_Entry.off] ; Q: is VADLIBD installed? 271 | or ax, [VADLIBD_Entry.sel] 272 | jz vadlibd_Release_Exit ; N: then leave (return 0) 273 | 274 | 275 | ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -; 276 | ; AX = Base of synth to release (for example, 0388h) 277 | ; BX = Flags should be zero. 278 | ; DX = ADLIB_API_Release_Synth (2) 279 | ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -; 280 | 281 | mov ax, [_wPort] ; base port to acquire 282 | xor bx, bx 283 | mov dx, ADLIB_API_Release_Synth 284 | call [VADLIBD_Entry] 285 | 286 | vadlibd_Release_Exit: 287 | 288 | cEnd 289 | 290 | sEnd CodeSeg 291 | 292 | end LibEntry 293 | -------------------------------------------------------------------------------- /MAKEFILE: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # 3 | # makefile 4 | # 5 | # Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 6 | # Copyright (c) 2019 Andrei Warkentin 7 | # 8 | # constructs adlib device driver 9 | # 10 | # to build a debug version: 11 | # NMAKE 12 | # to build a non debug version: 13 | # NMAKE DEBUG=NO 14 | # to build for OPL2LPT/OPL3LPT 15 | # NMAKE OPL_ON_LPT=YES 16 | # to build for OPL3 17 | # NMAKE OPL_ON_LPT=YES OPL=3 18 | # to pick a different base address (for LPT, mostly) 19 | # NMAKE DEF_PORT=0x3BC 20 | # 21 | # 0x378 is typical for LPT1, but some machines use 0x3BC. 22 | # 0x278 is typical for LPT2, and 0x3BC for LPT3. 23 | # 24 | # If the driver is built outside of the WIN31 DDK, you will need to 25 | # set the DDK path. 26 | # NMAKE DDK=C:\DEV\DDK 27 | # 28 | ############################################################################ 29 | 30 | # 31 | # You can hardcode the overrides here for your convenience. 32 | # 33 | DEBUG = NO 34 | DDK = D:\DEV\DDK 35 | # OPL = 3 36 | # OPL_ON_LPT=YES 37 | # DEF_PORT=0x3BC 38 | 39 | NAME = adlib21 40 | OBJ1 = adliba.obj adlib.obj midimain.obj midic.obj 41 | OBJ2 = init.obj drvproc.obj 42 | OBJS = libinit.obj $(OBJ1) $(OBJ2) 43 | LIBS = libw mmsystem mdllcew 44 | 45 | !if "$(DRV_BIN)" == "" 46 | DRV_BIN = bin 47 | !endif 48 | 49 | !if "$(DDK)" == "" 50 | # 51 | # Assume the driver is part of the DDK, 52 | # e.g. YOUR_DDK_DIR\MULTIMED\ADLIB21 53 | # 54 | DDK = ..\..\ 55 | !endif 56 | 57 | !if "$(DEBUG)" == "NO" 58 | DEF = 59 | CLOPT = -Oxws 60 | MASMOPT = 61 | LINKOPT = 62 | !else 63 | DEF = -DDEBUG 64 | CLOPT = -Oxws -Zid 65 | MASMOPT = -Zi 66 | LINKOPT = /CO/LI 67 | !endif 68 | INCOPT = -I$(DDK)\MULTIMED\INC -I$(DDK)\286\INC 69 | CLOPT = $(CLOPT) $(INCOPT) 70 | MASMOPT = $(MASMOPT) $(INCOPT) 71 | 72 | 73 | !if "$(OPL)" == "" 74 | OPL = 2 75 | !endif 76 | DEF = $(DEF) -DOPL=$(OPL) 77 | 78 | !if "$(DEF_PORT)" != "" 79 | DEF = $(DEF) -DDEF_PORT=$(DEF_PORT) 80 | !endif 81 | 82 | !if "$(OPL_ON_LPT)" == "" || "$(OPL_ON_LPT)" == "NO" 83 | OPL_ON_LPT = NO 84 | OPL_ON_LPT_VAL = 0 85 | !endif 86 | !if "$(OPL_ON_LPT)" == "YES" 87 | OPL_ON_LPT_VAL = 1 88 | !endif 89 | DEF = $(DEF) -DOPL_ON_LPT=$(OPL_ON_LPT_VAL) 90 | 91 | # 92 | # NOTE - this code is compiled *without* windows prolog/epilog 93 | # (no -Gw), so all exported routines must have _loadds 94 | 95 | CC = cl -c -nologo -W3 -Zp -G2s -Alnw -Fc $(DEF) $(CLOPT) 96 | ASM = $(DDK)\286\tools\masm -Mx -t $(DEF) $(MASMOPT) 97 | LINK = $(DDK)\286\tools\link /NOPACK/NOD/NOE/MAP/ALIGN:16 $(LINKOPT) 98 | 99 | .c.obj: 100 | @$(CC) -NT _TEXT $*.c 101 | 102 | .asm.obj: 103 | @echo $(@B).asm 104 | @$(ASM) $*; 105 | 106 | goal: $(NAME).drv 107 | @echo ***** finished making $(NAME).drv and $(NAME).sym ***** 108 | @if not exist $(DRV_BIN)\nul mkdir $(DRV_BIN) 109 | @copy $(NAME).drv $(DRV_BIN)\msadlib.drv 110 | @copy $(NAME).sym $(DRV_BIN) 111 | 112 | $(NAME).drv: $(OBJS) adlib.def $(NAME).res 113 | @echo OPL is $(OPL) 114 | @echo OPL_ON_LPT is $(OPL_ON_LPT) 115 | @$(LINK) @<< 116 | libinit.obj+ 117 | $(OBJ1)+ 118 | $(OBJ2), 119 | $(NAME).drv, 120 | $(NAME).map, 121 | $(LIBS), 122 | adlib.def 123 | << 124 | @rc -t $(DEF) $(NAME).res $(NAME).drv 125 | @mapsym /n $(NAME).map 126 | 127 | $(NAME).res: adlib21.rc adlib.h adlib.bnk drumkit.bin 128 | @rc $(DEF) -r -z adlib21.rc 129 | 130 | # 131 | # _TEXT is the init/exit and non-interrupt time segment 132 | # _FIX is the interrupt time fixed segment 133 | # 134 | 135 | SEG = $(CC) -NT TSEG $*.c 136 | SEGA = $(ASM) -DSEGNAME=TSEG $*; 137 | 138 | libinit.obj : ; $(SEGA:TSEG=_TEXT) 139 | init.obj : ; @$(SEG:TSEG=_TEXT) 140 | drvproc.obj : ; @$(SEG:TSEG=_TEXT) 141 | adliba.obj : ; $(SEGA:TSEG=_FIX) 142 | midic.obj : ; @$(SEG:TSEG=_FIX) 143 | midimain.obj : ; @$(SEG:TSEG=_FIX) 144 | adlib.obj : ; @$(SEG:TSEG=_FIX) 145 | 146 | clean: 147 | -del $(NAME).drv 148 | -del $(NAME).res 149 | -del *.sym 150 | -del *.map 151 | -del *.obj 152 | -del *.cod 153 | 154 | depend: 155 | mv makefile makefile.old 156 | sed "/^# START Dependencies/,/^# END Dependencies/D" makefile.old > makefile 157 | -del makefile.old 158 | echo # START Dependencies >> makefile 159 | includes -l *.c *.asm >> makefile 160 | echo # END Dependencies >> makefile 161 | 162 | # START Dependencies 163 | 164 | adlib.obj: adlib.c adlib.h 165 | 166 | drvproc.obj: drvproc.c adlib.h 167 | 168 | adliba.obj: adliba.asm 169 | 170 | init.obj: init.c adlib.h 171 | 172 | midic.obj: midic.c adlib.h 173 | 174 | libinit.obj: libinit.asm 175 | 176 | midimain.obj: midimain.c adlib.h 177 | 178 | # END Dependencies 179 | 180 | -------------------------------------------------------------------------------- /MIDIC.C: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * 3 | * midic.c 4 | * 5 | * Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 6 | * Copyright (c) 2019-2020 Andrei Warkentin 7 | * 8 | ***************************************************************************/ 9 | 10 | #include 11 | #include 12 | #include 13 | #include "adlib.h" 14 | 15 | static void NEAR PASCAL midiCallback(NPSYNTHALLOC pClient, WORD msg, 16 | DWORD dw1, DWORD dw2); 17 | 18 | static void FAR PASCAL GetSynthCaps(LPBYTE lpCaps, WORD wSize); 19 | #pragma alloc_text(_TEXT, GetSynthCaps) 20 | 21 | static SYNTHALLOC gClient; /* client information */ 22 | static WORD wAllocated; /* have we already been allocated? */ 23 | static int synthreenter; /* reentrancy check */ 24 | BYTE status; /* don't make assumptions about initial status */ 25 | BYTE bCurrentLen; 26 | 27 | #define FIXED_DS() HIWORD((DWORD)(LPVOID)(&wAllocated)) 28 | #define FIXED_CS() HIWORD((DWORD)(LPVOID)modMessage) 29 | 30 | BYTE gbMidiLengths[] = 31 | { 32 | 3, /* STATUS_NOTEOFF */ 33 | 3, /* STATUS_NOTEON */ 34 | 3, /* STATUS_POLYPHONICKEY */ 35 | 3, /* STATUS_CONTROLCHANGE */ 36 | 2, /* STATUS_PROGRAMCHANGE */ 37 | 2, /* STATUS_CHANNELPRESSURE */ 38 | 3, /* STATUS_PITCHBEND */ 39 | }; 40 | 41 | BYTE gbSysLengths[] = 42 | { 43 | 1, /* STATUS_SYSEX */ 44 | 2, /* STATUS_QFRAME */ 45 | 3, /* STATUS_SONGPOINTER */ 46 | 2, /* STATUS_SONGSELECT */ 47 | 1, /* STATUS_F4 */ 48 | 1, /* STATUS_F5 */ 49 | 1, /* STATUS_TUNEREQUEST */ 50 | 1, /* STATUS_EOX */ 51 | }; 52 | 53 | /**************************************************************************** 54 | * @doc INTERNAL 55 | * 56 | * @api void | midiCallback | This calls DriverCallback, which calls the 57 | * client's callback or window if the client has requested notification. 58 | * 59 | * @parm NPSYNTHALLOC | pClient | Pointer to the SYNTHALLOC structure. 60 | * 61 | * @parm WORD | msg | The message to send. 62 | * 63 | * @parm DWORD | dw1 | Message-dependent parameter. 64 | * 65 | * @parm DWORD | dw2 | Message-dependent parameter. 66 | * 67 | * @rdesc There is no return value. 68 | ***************************************************************************/ 69 | static void NEAR PASCAL 70 | midiCallback(NPSYNTHALLOC pClient, 71 | WORD msg, 72 | DWORD dw1, 73 | DWORD dw2) 74 | { 75 | /* dwFlags contains midi driver specific flags in the LOWORD */ 76 | /* and generic driver flags in the HIWORD */ 77 | 78 | if (pClient->dwCallback) { 79 | DriverCallback(pClient->dwCallback, /* client's callback DWORD */ 80 | HIWORD(pClient->dwFlags), /* callback flags */ 81 | pClient->hMidiOut, /* handle to the wave device */ 82 | msg, /* the message */ 83 | pClient->dwInstance, /* client's instance data */ 84 | dw1, /* first DWORD */ 85 | dw2); /* second DWORD */ 86 | } 87 | } 88 | 89 | /**************************************************************************** 90 | * @doc INTERNAL 91 | * 92 | * @api void | GetSynthCaps | Get the capabilities of the synth. 93 | * 94 | * @parm LPBYTE | lpCaps | Far pointer to a MIDICAPS structure. 95 | * 96 | * @parm WORD | wSize | Size of the MIDICAPS structure. 97 | * 98 | * @rdesc There is no return value. 99 | ***************************************************************************/ 100 | static void FAR PASCAL 101 | GetSynthCaps(LPBYTE lpCaps, 102 | WORD wSize) 103 | { 104 | MIDIOUTCAPS mc; /* caps structure we know about */ 105 | LPBYTE mp; /* place in client's buffer */ 106 | WORD w; /* number of bytes to copy */ 107 | 108 | mc.wMid = MM_MICROSOFT; 109 | mc.wPid = MM_ADLIB; 110 | mc.wTechnology = MOD_FMSYNTH; 111 | mc.wVoices = NUMVOICES; 112 | mc.wNotes = NUMVOICES; 113 | mc.wChannelMask = 0xff; /* all channels */ 114 | mc.vDriverVersion = DRIVER_VERSION; 115 | mc.dwSupport = 0L; 116 | lstrcpy(mc.szPname, aszProductName); 117 | 118 | /* copy as much as will fit into client's buffer */ 119 | w = min(wSize, sizeof(MIDIOUTCAPS)); 120 | mp = (LPBYTE)&mc; 121 | while (w--) { 122 | *lpCaps++ = *mp++; 123 | } 124 | } 125 | 126 | /* 127 | * This function conforms to the standard MIDI output driver message proc 128 | * modMessage, which is documented in the multimedia DDK. 129 | */ 130 | DWORD FAR PASCAL _loadds 131 | modMessage(WORD id, 132 | WORD msg, 133 | DWORD dwUser, 134 | DWORD dwParam1, 135 | DWORD dwParam2) 136 | { 137 | LPMIDIHDR lpHdr; /* header of long message buffer */ 138 | LPSTR lpBuf; /* current spot in long message buffer */ 139 | DWORD dwLength; /* length of data being processed */ 140 | 141 | /* has the whole card been enabled? */ 142 | if (!fEnabled) { 143 | D1("modMessage called while disabled"); 144 | if (msg == MODM_GETNUMDEVS) { 145 | return 0L; 146 | } else { 147 | return MMSYSERR_NOTENABLED; 148 | } 149 | } 150 | 151 | /* this driver only supports one device */ 152 | if (id != 0) { 153 | D1("invalid midi device id"); 154 | return MMSYSERR_BADDEVICEID; 155 | } 156 | 157 | switch (msg) { 158 | case MODM_GETNUMDEVS: 159 | D1("MODM_GETNUMDEVS"); 160 | return 1L; 161 | 162 | case MODM_GETDEVCAPS: 163 | D1("MODM_GETDEVCAPS"); 164 | GetSynthCaps((LPBYTE)dwParam1, (WORD)dwParam2); 165 | return 0L; 166 | 167 | case MODM_OPEN: 168 | D1("MODM_OPEN"); 169 | 170 | /* check if allocated */ 171 | if (wAllocated) { 172 | return MMSYSERR_ALLOCATED; 173 | } 174 | 175 | if (!OPL_ON_LPT && vadlibdAcquireAdLibSynth()) { 176 | D1("AdLib could NOT be aquired!!!"); 177 | return MMSYSERR_ALLOCATED; 178 | } 179 | 180 | { 181 | extern void FAR SoundWarmInit(void); 182 | SoundWarmInit(); 183 | } 184 | 185 | /* save client information */ 186 | gClient.dwCallback = ((LPMIDIOPENDESC)dwParam1)->dwCallback; 187 | gClient.dwInstance = ((LPMIDIOPENDESC)dwParam1)->dwInstance; 188 | gClient.hMidiOut = ((LPMIDIOPENDESC)dwParam1)->hMidi; 189 | gClient.dwFlags = dwParam2; 190 | 191 | /* !!! fix for 3.0 286p mode */ 192 | if (!GlobalPageLock(FIXED_DS()) || !GlobalPageLock(FIXED_CS())) { 193 | if (!OPL_ON_LPT) { 194 | vadlibdReleaseAdLibSynth(); 195 | } 196 | return MMSYSERR_NOMEM; 197 | } 198 | 199 | wAllocated++; 200 | bCurrentLen = 0; 201 | status = 0; 202 | 203 | /* notify client */ 204 | midiCallback(&gClient, MOM_OPEN, 0L, 0L); 205 | 206 | return 0L; 207 | case MODM_CLOSE: 208 | D1("MODM_CLOSE"); 209 | 210 | /* shut up */ 211 | synthAllNotesOff(); 212 | 213 | midiCallback(&gClient, MOM_CLOSE, 0L, 0L); 214 | 215 | /* get out */ 216 | wAllocated--; 217 | 218 | GlobalPageUnlock(FIXED_DS()); 219 | GlobalPageUnlock(FIXED_CS()); 220 | 221 | if (!OPL_ON_LPT && vadlibdReleaseAdLibSynth()) { 222 | D1("AdLib could NOT be RELEASED!!! VERY GOOFY!!"); 223 | } 224 | 225 | return 0L; 226 | case MODM_RESET: 227 | D1("MODM_RESET"); 228 | 229 | /* we don't need to return all long buffers since we've */ 230 | /* implemented MODM_LONGDATA synchronously */ 231 | synthAllNotesOff(); 232 | return 0L; 233 | case MODM_DATA: 234 | D4("MODM_DATA"); 235 | 236 | /* make sure we're not being reentered */ 237 | synthreenter++; 238 | if (synthreenter > 1) { 239 | D1("MODM_DATA reentered!"); 240 | synthreenter--; 241 | return MIDIERR_NOTREADY; 242 | } 243 | 244 | lpBuf = (LPBYTE)&dwParam1; 245 | if (*lpBuf >= STATUS_TIMINGCLOCK) { 246 | dwLength = 1; 247 | } else { 248 | bCurrentLen = 0; 249 | if (ISSTATUS(*lpBuf)) { 250 | if (*lpBuf >= STATUS_SYSEX) { 251 | dwLength = SYSLENGTH(*lpBuf); 252 | } else { 253 | dwLength = MIDILENGTH(*lpBuf); 254 | } 255 | } else { 256 | if (!status) { 257 | return 0L; 258 | } 259 | dwLength = MIDILENGTH(status) - 1; 260 | } 261 | } 262 | synthMidiData(lpBuf, dwLength); 263 | 264 | synthreenter--; 265 | return 0L; 266 | case MODM_LONGDATA: 267 | D1("MODM_LONGDATA"); 268 | 269 | /* make sure we're not being reentered */ 270 | synthreenter++; 271 | if (synthreenter > 1) { 272 | D1("MODM_LONGDATA reentered!"); 273 | synthreenter--; 274 | return MIDIERR_NOTREADY; 275 | } 276 | 277 | /* check if it's been prepared */ 278 | lpHdr = (LPMIDIHDR)dwParam1; 279 | if (!(lpHdr->dwFlags & MHDR_PREPARED)) { 280 | synthreenter--; 281 | return MIDIERR_UNPREPARED; 282 | } 283 | 284 | synthMidiData(lpHdr->lpData, lpHdr->dwBufferLength); 285 | 286 | /* return buffer to client */ 287 | lpHdr->dwFlags |= MHDR_DONE; 288 | midiCallback(&gClient, MOM_DONE, dwParam1, 0L); 289 | 290 | synthreenter--; 291 | return 0L; 292 | 293 | default: 294 | return MMSYSERR_NOTSUPPORTED; 295 | } 296 | 297 | /* should never get here... */ 298 | return MMSYSERR_NOTSUPPORTED; 299 | } 300 | -------------------------------------------------------------------------------- /MIDIMAIN.C: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * 3 | * midimain.c 4 | * 5 | * Copyright (c) 1991-1992 Microsoft Corporation. All Rights Reserved. 6 | * Copyright (c) 2019-2020 Andrei Warkentin 7 | * 8 | ***************************************************************************/ 9 | 10 | #include 11 | #include 12 | #include 13 | #include "adlib.h" 14 | 15 | static void NEAR PASCAL synthNoteOff(MIDIMSG msg); 16 | static void NEAR PASCAL synthNoteOn(MIDIMSG msg); 17 | static void NEAR PASCAL synthPitchBend(MIDIMSG msg); 18 | static void NEAR PASCAL synthControlChange(MIDIMSG msg); 19 | static void NEAR PASCAL synthProgramChange(MIDIMSG msg); 20 | 21 | static BYTE NEAR PASCAL FindVoice(BYTE note, BYTE channel); 22 | static BYTE NEAR PASCAL GetNewVoice(BYTE note, BYTE channel); 23 | static void NEAR PASCAL FreeVoice(BYTE voice); 24 | 25 | typedef struct _VOICE { 26 | BYTE alloc; /* is voice allocated? */ 27 | BYTE note; /* note that is currently being played */ 28 | BYTE channel; /* channel that it is being played on */ 29 | BYTE volume; /* current volume setting of voice */ 30 | DWORD dwTimeStamp; /* time voice was allocated */ 31 | } VOICE; 32 | 33 | static VOICE voices[11]; /* 9 voices if melodic mode or 11 if percussive */ 34 | 35 | typedef struct _CHANNEL { 36 | BYTE patch; /* the patch on this channel */ 37 | WORD wPitchBend; 38 | } CHANNEL; 39 | 40 | /* which patch and PB value (0x2000 = normal) is active on which channel */ 41 | static CHANNEL channels[NUMCHANNELS] = { 42 | 0, 0x2000, /* 0 */ 43 | 0, 0x2000, /* 1 */ 44 | 0, 0x2000, /* 2 */ 45 | 0, 0x2000, /* 3 */ 46 | 0, 0x2000, /* 4 */ 47 | 0, 0x2000, /* 5 */ 48 | 0, 0x2000, /* 6 */ 49 | 0, 0x2000, /* 7 */ 50 | 0, 0x2000, /* 9 */ 51 | 0, 0x2000, /* 8 */ 52 | 0, 0x2000, /* 10 */ 53 | 0, 0x2000, /* 11 */ 54 | 0, 0x2000, /* 12 */ 55 | 0, 0x2000, /* 13 */ 56 | 0, 0x2000, /* 14 */ 57 | 129, 0x2000 /* 15 - percussive channel */ 58 | }; 59 | 60 | static BYTE loudervol[128] = { 61 | 0, 0, 65, 65, 66, 66, 67, 67, /* 0 - 7 */ 62 | 68, 68, 69, 69, 70, 70, 71, 71, /* 8 - 15 */ 63 | 72, 72, 73, 73, 74, 74, 75, 75, /* 16 - 23 */ 64 | 76, 76, 77, 77, 78, 78, 79, 79, /* 24 - 31 */ 65 | 80, 80, 81, 81, 82, 82, 83, 83, /* 32 - 39 */ 66 | 84, 84, 85, 85, 86, 86, 87, 87, /* 40 - 47 */ 67 | 88, 88, 89, 89, 90, 90, 91, 91, /* 48 - 55 */ 68 | 92, 92, 93, 93, 94, 94, 95, 95, /* 56 - 63 */ 69 | 96, 96, 97, 97, 98, 98, 99, 99, /* 64 - 71 */ 70 | 100, 100, 101, 101, 102, 102, 103, 103, /* 72 - 79 */ 71 | 104, 104, 105, 105, 106, 106, 107, 107, /* 80 - 87 */ 72 | 108, 108, 109, 109, 110, 110, 111, 111, /* 88 - 95 */ 73 | 112, 112, 113, 113, 114, 114, 115, 115, /* 96 - 103 */ 74 | 116, 116, 117, 117, 118, 118, 119, 119, /* 104 - 111 */ 75 | 120, 120, 121, 121, 122, 122, 123, 123, /* 112 - 119 */ 76 | 124, 124, 125, 125, 126, 126, 127, 127 /* 120 - 127 */ 77 | }; 78 | 79 | static char patchKeyOffset[] = { 80 | 0, -12, 12, 0, 0, 12, -12, 0, 0, -24, /* 0 - 9 */ 81 | 0, 0, 0, 0, 0, 0, 0, 0, -12, 0, /* 10 - 19 */ 82 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 29 */ 83 | 0, 0, 12, 12, 12, 0, 0, 12, 12, 0, /* 30 - 39 */ 84 | -12, -12, 0, 12, -12, -12, 0, 12, 0, 0, /* 40 - 49 */ 85 | -12, 0, 0, 0, 12, 12, 0, 0, 12, 0, /* 50 - 59 */ 86 | 0, 0, 12, 0, 0, 0, 12, 12, 0, 12, /* 60 - 69 */ 87 | 0, 0, -12, 0, -12, -12, 0, 0, -12, -12, /* 70 - 79 */ 88 | 0, 0, 0, 0, 0, -12, -19, 0, 0, -12, /* 80 - 89 */ 89 | 0, 0, 0, 0, 0, 0, -31, -12, 0, 12, /* 90 - 99 */ 90 | 12, 12, 12, 0, 12, 0, 12, 0, 0, 0, /* 100 - 109 */ 91 | 0, 12, 0, 0, 0, 0, 12, 12, 12, 0, /* 110 - 119 */ 92 | 0, 0, 0, 0, -24, -36, 0, 0 /* 120 - 127 */ 93 | }; 94 | 95 | static DWORD dwAge; /* voice relative age */ 96 | 97 | #define msg_ch msg.ch /* all messages */ 98 | 99 | #define msg_note msg.b1 /* noteoff(0x80),noteon(0x90),keypressure(0xA0) */ 100 | #define msg_controller msg.b1 /* controlchange(0xB0) */ 101 | #define msg_patch msg.b1 /* programchange(0xC0) */ 102 | #define msg_cpress msg.b1 /* channelpressure(0xD0) */ 103 | #define msg_lsb msg.b1 /* pitchbend(0xE0) */ 104 | 105 | #define msg_velocity msg.b2 /* noteoff(0x80), noteon(0x90) */ 106 | #define msg_kpress msg.b2 /* keypressure(0xA0) */ 107 | #define msg_value msg.b2 /* controlchange(0xB0) */ 108 | #define msg_unused msg.b2 /* programchange(0xC0), channelpressure(0xD0) */ 109 | #define msg_msb msg.b2 /* pitchbend(0xE0) */ 110 | 111 | /*************************************************************************** 112 | * 113 | * MIDI function director array 114 | * 115 | * (x = channel) 116 | * 0x8x Note Off 117 | * 0x9x Note On (vel 0 == Note Off) 118 | * 0xAx Key Pressure (Aftertouch) 119 | * 0xBx Control Change 120 | * 0xCx Program Change 121 | * 0xDx Channel Pressure (Aftertouch) 122 | * 0xEx Pitch Bend Change 123 | * 0xF0 Sysex 124 | * 011111sssb System Common 125 | * 011110tttb System Real Time 126 | * 127 | *************************************************************************/ 128 | void (NEAR PASCAL * synthmidi [8]) (MIDIMSG); 129 | void (NEAR PASCAL * synthmidi []) () = { 130 | synthNoteOff, 131 | synthNoteOn, 132 | NULL, /* key pressure not currently implemented */ 133 | synthControlChange, 134 | synthProgramChange, 135 | NULL, /* channel pressure not currently implemented */ 136 | synthPitchBend, 137 | NULL /* sysex etc. not currently implemented */ 138 | }; 139 | 140 | /************************************************************************** 141 | * @doc INTERNAL 142 | * 143 | * @func void | synthMidiData | Process a stream of MIDI messages by calling 144 | * the appropriate function based on each status byte. The function deals 145 | * with messages spanning buffers, and with invalid data being passed. 146 | * 147 | * In general, the function steps through the buffer, using the current 148 | * state in determining what to look for in checking the next data byte. 149 | * This may mean looking for a new status byte, or looking for data 150 | * associated with a current status whose data has not been completely 151 | * read yet. 152 | * 153 | * The key item to determine the current state of message processing is 154 | * the "bCurrentLen" global static byte, which indicates the number of 155 | * bytes that are needed to complete the current message being processed. 156 | * The current message is stored in the local static "bCurrentStatus", not 157 | * to be confused with the global static "status", which is the running 158 | * status. The local static "msg" is used to build the current message, 159 | * and is static in order to enable messages to cross buffer boundaries. 160 | * The local static "bPosition" determines where in the message buffer to 161 | * place the next byte of the message, if any. 162 | * 163 | * The first item in the processing loop is a check for the presence of a 164 | * real time message, which can occur anywhere in the data stream, and does 165 | * not affect the current state (unless it is possibly a reset command). 166 | * Real time messages are ignored for now, including the reset command. 167 | * After ignoring them, the loop is continued in case that was the last 168 | * byte in the buffer. If the loop was not continued at this point, the 169 | * message sending portion would not function correctly, as "bCurrentLen" 170 | * and "bCurrentStatus" is not modified by a real time message. 171 | * 172 | * The next loop item checks to determine if a message is currently being 173 | * built. If "bCurrentLen" is zero, no message is being built, and the 174 | * next status byte can be retrieved. At this point, the current message 175 | * position is reset, as any new byte will be the first for this new 176 | * message. 177 | * 178 | * If the next byte is a system command, as opposed to a channel command, 179 | * it must reset the current running status, and extract the message length 180 | * from a different message length table (subtracting one for the status 181 | * already retrieved). Even though these messages are eventually ignored, 182 | * the actual message buffer is built as normal. This will enable a 183 | * function to be attached to the message function table which would deal 184 | * with system messages. Note that the system message id is placed into 185 | * the channel portion of the message, in place of the channel for a normal 186 | * message. 187 | * 188 | * If the next byte is not a system command, then it might be either a 189 | * new status byte, or a data byte which depends upon the running status. 190 | * If it is a new status byte, running status is updated. If it is not, 191 | * and there is not running status, the byte is ignored, and the loop is 192 | * continued. This might be the case when ignoring data after a SYSEX, 193 | * or when invalid data occurs. If a valid status byte is retrieved, or 194 | * is already present in the running status, the internal current status 195 | * is updated, in case this message spans buffers, and the length of the 196 | * message is retrieved from the channel table (subtracting one for the 197 | * status byte already retrieved). 198 | * 199 | * At this point, the message may be completely built (i.e., a one byte 200 | * message), in which case, it will fall into the message dispatch code. 201 | * 202 | * The next loop item is fallen into if a message is currently being 203 | * processed. It checks the next byte in the buffer to ensure that it 204 | * is actually a data byte. If it is a status byte instead, the current 205 | * message is aborted by resetting "bCurrentLen", and the loop is 206 | * continued. Note that running status is not reset in this case. 207 | * 208 | * If however the next byte is valid, it is placed in either the first or 209 | * the second data position in the message being built. The local static 210 | * "bPosition" is used to determine which in position to place the data. 211 | * 212 | * The next loop item checks to see if a complete message has been built. 213 | * If so, it dispatches the message based on the current command. It does 214 | * not use running status, as that might have been reset for a system 215 | * command. If a function for the particular command is present, the 216 | * message is dispatched, else it is ignored. If the message was not 217 | * complete, the next pass through the loop will pick up the next data 218 | * byte for the message. 219 | * 220 | * The loop then continues until it is out of data. 221 | * 222 | * @parm HPBYTE | lpBuf | Points to a buffer containing the stream of MIDI 223 | * data. 224 | * 225 | * @parm DWORD | dwLength | Contains the length of the data pointed to by 226 | *

lpBuf. 227 | * 228 | * @rdesc There is no return value. 229 | *************************************************************************/ 230 | void NEAR PASCAL 231 | synthMidiData(HPBYTE lpBuf, 232 | DWORD dwLength) 233 | { 234 | static MIDIMSG msg; 235 | static BYTE bCurrentStatus; 236 | static BYTE bPosition; 237 | BYTE bByte; 238 | 239 | for (; dwLength; dwLength--) { 240 | bByte = *lpBuf++; 241 | 242 | if (bByte >= STATUS_TIMINGCLOCK) { 243 | continue; 244 | } 245 | 246 | if (!bCurrentLen) { 247 | kludge_city: 248 | bPosition = 0; 249 | if (bByte >= STATUS_SYSEX) { 250 | bCurrentStatus = bByte; 251 | status = 0; 252 | bCurrentLen = (BYTE)(SYSLENGTH(bCurrentStatus) - 1); 253 | } else { 254 | if (bByte >= STATUS_NOTEOFF) { 255 | status = bByte; 256 | } else if (!status) { 257 | continue; 258 | } 259 | bCurrentStatus = status; 260 | bCurrentLen = (BYTE)(MIDILENGTH(status) - 1); 261 | if (bByte < STATUS_NOTEOFF) { 262 | goto first_byte; 263 | } 264 | } 265 | msg_ch = FILTERSTATUS(bCurrentStatus); 266 | } 267 | else { 268 | if (bByte >= STATUS_NOTEOFF) { 269 | goto kludge_city; 270 | } 271 | if (!bPosition) { 272 | first_byte: 273 | bPosition++; 274 | msg.b1 = bByte; 275 | } else { 276 | msg.b2 = bByte; 277 | } 278 | bCurrentLen--; 279 | } 280 | if (!bCurrentLen) { 281 | bByte = (BYTE)((bCurrentStatus >> 4) & 0x07); 282 | if (*synthmidi[bByte]) { 283 | (*synthmidi[bByte]) (msg); 284 | } else { 285 | D1("MIDI message type not supported"); 286 | } 287 | } 288 | } 289 | } 290 | 291 | /************************************************************************** 292 | * @doc INTERNAL 293 | * 294 | * @api void | synthAllNotesOff | Turn any notes off which are playing. 295 | * 296 | * @rdesc There is no return value. 297 | ************************************************************************/ 298 | void NEAR PASCAL 299 | synthAllNotesOff(void) 300 | { 301 | BYTE voice; 302 | MIDIMSG msg; 303 | 304 | for (voice = 0; voice < (BYTE)NUMVOICES; voice++) { 305 | if (voices[voice].alloc) { 306 | msg_ch = voices[voice].channel; 307 | msg_note = voices[voice].note; 308 | synthNoteOff(msg); 309 | } 310 | } 311 | } 312 | 313 | /************************************************************************** 314 | * @doc INTERNAL 315 | * 316 | * @api void | synthOctaveReg | Perform octave registration on a message. 317 | * 318 | * @parm BYTE | msg_ch | The channel the note is to be played on. 319 | * 320 | * @parm BYTE | msg_note | The MIDI note number. 321 | * 322 | * @rdesc There is no return value. 323 | **************************************************************************/ 324 | static void NEAR PASCAL 325 | synthOctaveReg(MIDIMSG FAR *pMsg) 326 | { 327 | int iModNote; 328 | MIDIMSG msg = *pMsg; 329 | 330 | if ((msg_ch == DRUMKITCHANNEL) || (channels[msg_ch].patch > 127)) { 331 | return; /* only affect normal melodic patches */ 332 | } 333 | 334 | iModNote = msg_note + patchKeyOffset[channels[msg_ch].patch]; 335 | if ((iModNote < 0) || (iModNote > 127)) { 336 | iModNote = msg_note; 337 | } 338 | 339 | msg_note = (BYTE) iModNote; 340 | *pMsg = msg; /* modify what was pointed to */ 341 | } 342 | 343 | /************************************************************************** 344 | * @doc INTERNAL 345 | * 346 | * @api void | synthNoteOn | Turn on a requested note. 347 | * 348 | * @parm BYTE | msg_ch | The channel the note is to be played on. 349 | * 350 | * @parm BYTE | msg_note | The MIDI note number. 351 | * 352 | * @parm BYTE | msg_velocity | The velocity level for the note. 353 | * 354 | * @rdesc There is no return value. 355 | **************************************************************************/ 356 | static void NEAR PASCAL 357 | synthNoteOn(MIDIMSG msg) 358 | { 359 | BYTE voice; 360 | 361 | if (msg_velocity == 0) { /* 0 velocity means note off */ 362 | synthNoteOff(msg); 363 | return; 364 | } 365 | 366 | synthOctaveReg(&msg); /* adjust key # to overcome patch octave diffs */ 367 | 368 | /* hack to overcome how quiet this thing is in relation to wave output */ 369 | msg_velocity = loudervol[msg_velocity]; 370 | 371 | if (msg_ch == DRUMKITCHANNEL) { /* drum kit hardwired on channel 15 */ 372 | if ((msg_note < FIRSTDRUMNOTE) || (msg_note > LASTDRUMNOTE)) { 373 | return; 374 | } 375 | 376 | channels[DRUMKITCHANNEL].patch = drumpatch[msg_note - FIRSTDRUMNOTE].patch; 377 | msg_note = drumpatch[msg_note - FIRSTDRUMNOTE].note; 378 | 379 | if ((voice = FindVoice(msg_note, msg_ch)) != 0xFF) { 380 | NoteOff(voice); 381 | } 382 | voice = GetNewVoice(msg_note, msg_ch); 383 | } else { 384 | voice = FindVoice(msg_note, msg_ch); 385 | 386 | /* voice already assigned? */ 387 | if (voice == 0xff) { 388 | 389 | /* if not, get one */ 390 | voice = GetNewVoice(msg_note, msg_ch); 391 | } else { 392 | NoteOff(voice); 393 | } 394 | } 395 | 396 | /* check if it's already set */ 397 | if (voices[voice].volume != msg_velocity) { 398 | SetVoiceVolume(voice, msg_velocity); 399 | voices[voice].volume = msg_velocity; 400 | } 401 | 402 | /* apply any pb for this channel */ 403 | SetVoicePitch(voice, channels[msg_ch].wPitchBend); 404 | 405 | /* adjust for octave reg. */ 406 | NoteOn(voice, msg_note); 407 | } 408 | 409 | /************************************************************************** 410 | * @doc INTERNAL 411 | * 412 | * @api void | synthNoteOff | Turn off a requested note. 413 | * 414 | * @parm BYTE | msg_ch | The channel the note is on. 415 | * 416 | * @parm BYTE | msg_note | The MIDI note number. 417 | * 418 | * @rdesc There is no return value. 419 | **************************************************************************/ 420 | static void NEAR PASCAL 421 | synthNoteOff(MIDIMSG msg) 422 | { 423 | BYTE voice; 424 | 425 | /* adjust key # to overcome patch octave differences */ 426 | synthOctaveReg(&msg); 427 | 428 | /* drum kit hardwired on channel 15 */ 429 | if (msg_ch == DRUMKITCHANNEL) { 430 | BYTE bPatch; 431 | 432 | if ((msg_note < FIRSTDRUMNOTE) || (msg_note > LASTDRUMNOTE)) { 433 | return; 434 | } 435 | 436 | bPatch = drumpatch[msg_note - FIRSTDRUMNOTE].patch; 437 | msg_note = drumpatch[msg_note - FIRSTDRUMNOTE].note; 438 | 439 | if ((voice = FindVoice(msg_note, msg_ch)) != 0xFF) { 440 | if (LOWORD(voices[voice].dwTimeStamp) == bPatch) { 441 | NoteOff(voice); 442 | FreeVoice(voice); 443 | } 444 | } 445 | return; 446 | } else { 447 | 448 | /* get the assigned voice */ 449 | voice = FindVoice(msg_note, msg_ch); 450 | } 451 | 452 | if (voice == 0xFF) { 453 | return; 454 | } 455 | 456 | /* check if note is playing */ 457 | if (voices[voice].note) { 458 | NoteOff(voice); 459 | 460 | /* return note to pool of notes. */ 461 | FreeVoice(voice); 462 | } 463 | } 464 | 465 | #if 0 466 | /* 467 | * These functions are commented out because we are not currently supporting 468 | * channel and key pressure messages in this driver. Ad Lib had originally 469 | * interpreted them as volume values, which produces incorrect results. 470 | * I haven't implemented them because it's not clear from the technical 471 | * documentation how produce the low-frequency oscillation that is often 472 | * produced by these messages. To support the messages, change the 473 | * entries in the synthmidi array to call these functions again, uncomment 474 | * these two functions, and define an xSetVoicePressure routine. 475 | */ 476 | static void NEAR PASCAL synthKeyPressure(MIDIMSG msg); 477 | static void NEAR PASCAL synthChannelPressure(MIDIMSG msg); 478 | 479 | /************************************************************************** 480 | * @doc INTERNAL 481 | * 482 | * @api void | synthChannelPressure | Set the pressure for a channel. 483 | * 484 | * @parm BYTE | msg_ch | The channel to be set. 485 | * 486 | * @parm BYTE | msg_cpress | The pressure level for the channel. 487 | * 488 | * @rdesc There is no return value. 489 | **************************************************************************/ 490 | static void NEAR PASCAL 491 | synthChannelPressure(MIDIMSG msg) 492 | { 493 | int i; 494 | 495 | for (i = 0; i < NUMVOICES; i++) { 496 | if ((voices[i].alloc) && (voices[i].channel == msg_ch)) { 497 | xSetVoicePressure(i, msg_cpress); 498 | } 499 | } 500 | } 501 | 502 | /************************************************************************** 503 | * @doc INTERNAL 504 | * 505 | * @api void | synthKeyPressure | Set the key pressure for a note. 506 | * 507 | * @parm BYTE | msg_ch | The channel the note is on. 508 | * 509 | * @parm BYTE | msg_note | The MIDI note number. 510 | * 511 | * @parm BYTE | msg_kpress | The pressure level for the note. 512 | * 513 | * @rdesc There is no return value. 514 | **************************************************************************/ 515 | static void NEAR PASCAL 516 | synthKeyPressure(MIDIMSG msg) 517 | { 518 | BYTE voice; 519 | 520 | voice = FindVoice(msg_note, msg_ch); 521 | if (voice == 0xFF) 522 | return; 523 | 524 | xSetVoicePressure(voice, msg_kpress); 525 | } 526 | #endif 527 | 528 | /************************************************************************** 529 | * @doc INTERNAL 530 | * 531 | * @api void | synthPitchBend | Bend the pitch. 532 | * 533 | * @parm BYTE | msg_ch | The channel to bend the pitch for. 534 | * 535 | * @parm BYTE | msg_lsb | LSB of pitch bend. 536 | * 537 | * @parm BYTE | msg_msb | MSB of pitch bend. 538 | * 539 | * @rdesc There is no return value. 540 | **************************************************************************/ 541 | static void NEAR PASCAL 542 | synthPitchBend(MIDIMSG msg) 543 | { 544 | BYTE i; 545 | WORD wPB = (((WORD)msg_msb) << 7) | msg_lsb; 546 | 547 | /* 548 | * msb is shifted by 7 because we've redefined the MIDI pitch bend 549 | * range of 0 - 0x7f7f to 0 - 3fff by concatenating the two 550 | * 7-bit values in msb and lsb together 551 | */ 552 | for (i = 0; i < (BYTE)NUMVOICES; i++) { 553 | if ((voices[i].alloc) && (voices[i].channel == msg_ch)) { 554 | SetVoicePitch(i, wPB); 555 | } 556 | } 557 | 558 | /* remember for subsequent notes on this channel */ 559 | channels[msg_ch].wPitchBend = wPB; 560 | } 561 | 562 | /************************************************************************** 563 | * @doc INTERNAL 564 | * 565 | * @api void | synthControlChange | Change a controller value. 566 | * 567 | * @parm BYTE | msg_ch | The MIDI channel. 568 | * 569 | * @parm BYTE | msg_controller | The controller number to change. 570 | * 571 | * @parm BYTE | msg_value | The value to change the controller to. 572 | * 573 | * @comm The only controllers supported are 123-127 (all notes off). 574 | * 575 | * @rdesc There is no return value. 576 | **************************************************************************/ 577 | static void NEAR PASCAL 578 | synthControlChange(MIDIMSG msg) 579 | { 580 | if (msg_controller >= 123) { 581 | synthAllNotesOff(); 582 | } 583 | } 584 | 585 | /************************************************************************** 586 | * @doc INTERNAL 587 | * 588 | * @api void | synthProgramChange | Change a channel patch assignment. 589 | * 590 | * @parm BYTE | msg_ch | The channel the patch is to apply to. 591 | * 592 | * @parm BYTE | msg_patch | The new patch number. 593 | * 594 | * @rdesc There is no return value. 595 | **************************************************************************/ 596 | static void NEAR PASCAL 597 | synthProgramChange(MIDIMSG msg) 598 | { 599 | BYTE voice; 600 | 601 | /* drum kit hardwired on channel 15, so ignore patch changes there */ 602 | if (msg_ch == DRUMKITCHANNEL) { 603 | return; 604 | } 605 | 606 | /* turn off any notes on this channel which are currently on */ 607 | for (voice = 0; voice < (BYTE)NUMVOICES; voice++) { 608 | 609 | /* check if note is playing */ 610 | if ((voices[voice].alloc) && (voices[voice].channel == msg_ch) 611 | && (voices[voice].note)) { 612 | NoteOff(voice); 613 | FreeVoice(voice); 614 | } 615 | } 616 | 617 | /* change the patch for this channel */ 618 | channels[msg_ch].patch = msg_patch; 619 | 620 | } 621 | 622 | /************************************************************************** 623 | * @doc INTERNAL 624 | * 625 | * @api BYTE | FindVoice | Find the voice a note/channel is using. 626 | * 627 | * @parm BYTE | note | The note in use. 628 | * 629 | * @parm BYTE | channel | The channel in use. 630 | * 631 | * @rdesc There return value is the voice number or 0xFF if no match is found. 632 | **************************************************************************/ 633 | static BYTE NEAR PASCAL 634 | FindVoice(BYTE note, 635 | BYTE channel) 636 | { 637 | BYTE i; 638 | 639 | if (channel == DRUMKITCHANNEL) { 640 | i = patches[channels[DRUMKITCHANNEL].patch].percVoice; 641 | 642 | if (voices[i].alloc) { 643 | return i; 644 | } 645 | } else { 646 | for (i = 0; i < (BYTE)NUMVOICES; i++) { 647 | if ((voices[i].alloc) && (voices[i].note == note) 648 | && (voices[i].channel == channel)) { 649 | voices[i].dwTimeStamp = dwAge++; 650 | return i; 651 | } 652 | } 653 | } 654 | 655 | /* not found */ 656 | return 0xFF; 657 | } 658 | 659 | /************************************************************************** 660 | * @doc INTERNAL 661 | * 662 | * @api BYTE | GetNewVoice | Find a new voice to play a note. Uses an LRU 663 | * algorithm to steal voices. The timestamp is set at allocation time 664 | * incremented in . 665 | * 666 | * @parm BYTE | note | The note we want to play. 667 | * 668 | * @parm BYTE | channel | The channel we want to play it on. 669 | * 670 | * @rdesc Returns the voice number. 671 | **************************************************************************/ 672 | static BYTE NEAR PASCAL 673 | GetNewVoice(BYTE note, 674 | BYTE channel) 675 | { 676 | BYTE i; 677 | BYTE voice; 678 | BYTE patch; 679 | BYTE bVoiceToUse; 680 | DWORD dwOldestTime = dwAge; /* init to current "time" */ 681 | 682 | /* get the patch in use for this channel */ 683 | patch = channels[channel].patch; 684 | 685 | if (patches[patch].mode) { 686 | /* it's a percussive patch - use fixed percussion voice */ 687 | voice = patches[patch].percVoice; 688 | voices[voice].alloc = TRUE; 689 | voices[voice].note = note; 690 | voices[voice].channel = channel; 691 | voices[voice].dwTimeStamp = MAKELONG(patch, 0); 692 | SetVoiceTimbre(voice, &patches[patch].op0); 693 | return voice; 694 | } 695 | 696 | /* find a free melodic voice to use */ 697 | for (i = 0; i < (BYTE)NUMMELODIC; i++) { 698 | if (!voices[i].alloc) { 699 | bVoiceToUse = i; 700 | break; 701 | } else if (voices[i].dwTimeStamp < dwOldestTime) { 702 | dwOldestTime = voices[i].dwTimeStamp; 703 | 704 | /* remember oldest one to steal */ 705 | bVoiceToUse = i; 706 | } 707 | } 708 | 709 | /* 710 | * At this point, we have either found an unused voice, 711 | * or have found the oldest one among a totally used voice bank 712 | */ 713 | if (voices[bVoiceToUse].alloc) { 714 | /* If we stole it, turn it off. */ 715 | NoteOff(bVoiceToUse); 716 | } 717 | 718 | voices[bVoiceToUse].alloc = 1; 719 | voices[bVoiceToUse].note = note; 720 | voices[bVoiceToUse].channel = channel; 721 | voices[bVoiceToUse].dwTimeStamp = dwAge++; 722 | 723 | SetVoiceTimbre(bVoiceToUse, &patches[patch].op0); 724 | 725 | return bVoiceToUse; 726 | } 727 | 728 | /************************************************************************** 729 | * @doc INTERNAL 730 | * 731 | * @api BYTE | FreeVoice | Free a voice we have been using. 732 | * 733 | * @parm BYTE | voice | The voice to free. 734 | * 735 | * @rdesc There is no return value. 736 | **************************************************************************/ 737 | static void NEAR PASCAL 738 | FreeVoice(BYTE voice) 739 | { 740 | voices[voice].alloc = 0; 741 | } 742 | -------------------------------------------------------------------------------- /MIDIMAP.CFG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/MIDIMAP.CFG -------------------------------------------------------------------------------- /PUB/ADLIB/ADLIB21.SYM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/PUB/ADLIB/ADLIB21.SYM -------------------------------------------------------------------------------- /PUB/ADLIB/MSADLIB.DRV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/PUB/ADLIB/MSADLIB.DRV -------------------------------------------------------------------------------- /PUB/LPT278/ADLIB21.SYM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/PUB/LPT278/ADLIB21.SYM -------------------------------------------------------------------------------- /PUB/LPT278/MSADLIB.DRV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/PUB/LPT278/MSADLIB.DRV -------------------------------------------------------------------------------- /PUB/LPT378/ADLIB21.SYM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/PUB/LPT378/ADLIB21.SYM -------------------------------------------------------------------------------- /PUB/LPT378/MSADLIB.DRV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/PUB/LPT378/MSADLIB.DRV -------------------------------------------------------------------------------- /PUB/LPT3BC/ADLIB21.SYM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/PUB/LPT3BC/ADLIB21.SYM -------------------------------------------------------------------------------- /PUB/LPT3BC/MSADLIB.DRV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/adlib21/6e1b5dffc93b77e08ca20763c2fd3029bee43179/PUB/LPT3BC/MSADLIB.DRV -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ADLIB21.DRV 2 | =========== 3 | 4 | This is an updated version of msadlib.drv 5 | with support for OPL2LPT and OPL3LPT devices. 6 | 7 | BUILDING 8 | ======== 9 | 10 | See Makefile for details on building. You will 11 | need the Windows 3.1 DDK and VC++ 5.2. 12 | 13 | BUILD.BAT is used to recreate the prebuilts under PUB\ 14 | 15 | PREBUILTS 16 | ========= 17 | 18 | * PUB\ADLIB is for OPL2/OPL3 Ad Lib cards. 19 | * OPL2LPT/OPL3LPT 20 | * PUB\LPT3BC is for port 0x3BC (LPT1 on some machines) 21 | * PUB\LPT378 is for port 0x378 (LPT1 on most machines) 22 | * PUB\LPT278 is for port 0x278 (LPT2 on most machines) 23 | 24 | There are no prebuilts with OPL=3 as, frankly, the driver doesn't do 25 | anything special today outside of some tweaked I/O timings. 26 | 27 | INSTALLING 28 | ========== 29 | 30 | Copy over the exising file under \WINDOWS\SYSTEM. 31 | Then, proceed to install Ad Lib support as usual 32 | using Control Panel / Drivers. 33 | 34 | If you are using OPL2/3 via LPT, you can comment 35 | out the vadlibd.386 driver in \WINDOWS\SYSTEM.INI, 36 | as it is not used. The regular vpd.386 should be 37 | sufficient to deal with any contention issues 38 | between Windows and DOS VM users. Find the line 39 | that reads: 40 | 41 | device=vadlibd.386 42 | 43 | ...and comment it out by prepending a semicolon, 44 | like this: 45 | 46 | ;device=vadlibd.386 47 | 48 | If you had previously installed a 3rd-party sound 49 | card driver, your original \WINDOWS\SYSTEM\MIDIMAP.CFG 50 | file might have been overwritten. You can either copy 51 | over MIDIMAP.CFG from this repository, or add the 52 | missing configurations using the "MIDI Sequencer" Control 53 | Panel applet. The 'Ad Lib' configuration (which sounds 54 | as expected) only sets up channels 13 through 16 55 | (no patch maps). The 'Ad Lib' general sets up all 16 56 | channels. 57 | --------------------------------------------------------------------------------