mirror of
https://github.com/chocolate-doom/research.git
synced 2024-11-21 11:51:19 +00:00
Add OPL research data to version control.
Subversion-branch: /research Subversion-revision: 1480
This commit is contained in:
commit
58512f18d4
28 changed files with 1508 additions and 0 deletions
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
# These are the default patterns globally ignored by Subversion:
|
||||
*.o
|
||||
*.lo
|
||||
*.la
|
||||
*.al
|
||||
.libs
|
||||
*.so
|
||||
*.so.[0-9]*
|
||||
*.a
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.rej
|
||||
*~
|
||||
.#*
|
||||
.*.swp
|
||||
.DS_store
|
15
opl/README
Normal file
15
opl/README
Normal file
|
@ -0,0 +1,15 @@
|
|||
Research data from investigating Chocolate Doom OPL MIDI playback.
|
||||
|
||||
dosbox-trace DOSBox patch and script to monitor Vanilla Doom OPL
|
||||
register writes and interpret.
|
||||
|
||||
genmidi Script for parsing and converting contents of Doom
|
||||
GENMIDI lump
|
||||
|
||||
oplinit Linux program to initialise Adlib card and play a tone.
|
||||
|
||||
scale Constructed MIDI file to play each note in sequence
|
||||
in order to determine the register writes for each note.
|
||||
|
||||
droplay Test program for playing back DOSBox raw OPL files.
|
||||
|
479
opl/adlib.txt
Normal file
479
opl/adlib.txt
Normal file
|
@ -0,0 +1,479 @@
|
|||
|
||||
Programming the AdLib/Sound Blaster
|
||||
FM Music Chips
|
||||
Version 2.0 (24 Feb 1992)
|
||||
|
||||
Copyright (c) 1991, 1992 by Jeffrey S. Lee
|
||||
|
||||
jlee@smylex.uucp
|
||||
|
||||
|
||||
|
||||
Warranty and Copyright Policy
|
||||
|
||||
This document is provided on an "as-is" basis, and its author makes
|
||||
no warranty or representation, express or implied, with respect to
|
||||
its quality performance or fitness for a particular purpose. In no
|
||||
event will the author of this document be liable for direct, indirect,
|
||||
special, incidental, or consequential damages arising out of the use
|
||||
or inability to use the information contained within. Use of this
|
||||
document is at your own risk.
|
||||
|
||||
This file may be used and copied freely so long as the applicable
|
||||
copyright notices are retained, and no modifications are made to the
|
||||
text of the document. No money shall be charged for its distribution
|
||||
beyond reasonable shipping, handling and duplication costs, nor shall
|
||||
proprietary changes be made to this document so that it cannot be
|
||||
distributed freely. This document may not be included in published
|
||||
material or commercial packages without the written consent of its
|
||||
author.
|
||||
|
||||
|
||||
|
||||
Overview
|
||||
|
||||
Two of the most popular sound cards for the IBM-PC, the AdLib and the
|
||||
Sound Blaster, suffer from a real dearth of clear documentation for
|
||||
programmers. AdLib Inc. and Creative Labs, Inc. both sell developers'
|
||||
kits for their sound cards, but these are expensive, and (in the case
|
||||
of the Sound Blaster developers' kit) can be extremely cryptic.
|
||||
|
||||
This document is intended to provide programmers with a FREE source
|
||||
of information about the programming of these sound cards.
|
||||
|
||||
The information contained in this document is a combination of
|
||||
information found in the Sound Blaster Software Developer's Kit, and
|
||||
that learned by painful experience. Some of the information may not
|
||||
be valid for AdLib cards; if this is so, I apologize in advance.
|
||||
|
||||
Please note that numbers will be given in hexadecimal, unless otherwise
|
||||
indicated. If a number is written out longhand (sixteen instead of 16)
|
||||
it is in decimal.
|
||||
|
||||
| Changes from Version 1 of the file will be indicated by the use of change
|
||||
| bars in the left-hand margin.
|
||||
|
||||
|
||||
|
||||
Chapter One - Sound Card I/O
|
||||
|
||||
The sound card is programmed by sending data to its internal registers
|
||||
via its two I/O ports:
|
||||
|
||||
0388 (hex) - Address/Status port (R/W)
|
||||
0389 (hex) - Data port (W/O)
|
||||
|
||||
| The Sound Blaster Pro is capable of stereo FM music, which is accessed
|
||||
| in exactly the same manner. Ports 0220 and 0221 (hex) are the address/
|
||||
| data ports for the left speaker, and ports 0222 and 0223 (hex) are the
|
||||
| ports for the right speaker. Ports 0388 and 0389 (hex) will cause both
|
||||
| speakers to output sound.
|
||||
|
||||
The sound card possesses an array of two hundred forty-four registers;
|
||||
to write to a particular register, send the register number (01-F5) to
|
||||
the address port, and the desired value to the data port.
|
||||
|
||||
After writing to the register port, you must wait twelve cycles before
|
||||
sending the data; after writing the data, eighty-four cycles must elapse
|
||||
before any other sound card operation may be performed.
|
||||
|
||||
| The AdLib manual gives the wait times in microseconds: three point three
|
||||
| (3.3) microseconds for the address, and twenty-three (23) microseconds
|
||||
| for the data.
|
||||
|
|
||||
| The most accurate method of producing the delay is to read the register
|
||||
| port six times after writing to the register port, and read the register
|
||||
| port thirty-five times after writing to the data port.
|
||||
|
||||
The sound card registers are write-only.
|
||||
|
||||
The address port also functions as a sound card status byte. To
|
||||
retrieve the sound card's status, simply read port 388. The status
|
||||
byte has the following structure:
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+------+------+------+------+------+------+------+------+
|
||||
| both | tmr | tmr | unused |
|
||||
| tmrs | 1 | 2 | |
|
||||
+------+------+------+------+------+------+------+------+
|
||||
|
||||
Bit 7 - set if either timer has expired.
|
||||
6 - set if timer 1 has expired.
|
||||
5 - set if timer 2 has expired.
|
||||
|
||||
|
||||
|
||||
Chapter Two - The Registers
|
||||
|
||||
The following table shows the function of each register in the sound
|
||||
card. Registers will be explained in detail after the table. Registers
|
||||
not listed are unused.
|
||||
|
||||
Address Function
|
||||
------- ----------------------------------------------------
|
||||
01 Test LSI / Enable waveform control
|
||||
02 Timer 1 data
|
||||
03 Timer 2 data
|
||||
04 Timer control flags
|
||||
08 Speech synthesis mode / Keyboard split note select
|
||||
20..35 Amp Mod / Vibrato / EG type / Key Scaling / Multiple
|
||||
40..55 Key scaling level / Operator output level
|
||||
60..75 Attack Rate / Decay Rate
|
||||
80..95 Sustain Level / Release Rate
|
||||
A0..A8 Frequency (low 8 bits)
|
||||
B0..B8 Key On / Octave / Frequency (high 2 bits)
|
||||
BD AM depth / Vibrato depth / Rhythm control
|
||||
C0..C8 Feedback strength / Connection type
|
||||
E0..F5 Wave Select
|
||||
|
||||
The groupings of twenty-two registers (20-35, 40-55, etc.) have an odd
|
||||
order due to the use of two operators for each FM voice. The following
|
||||
table shows the offsets within each group of registers for each operator.
|
||||
|
||||
|
||||
Channel 1 2 3 4 5 6 7 8 9
|
||||
Operator 1 00 01 02 08 09 0A 10 11 12
|
||||
Operator 2 03 04 05 0B 0C 0D 13 14 15
|
||||
|
||||
Thus, the addresses of the attack/decay bytes for channel 3 are 62 for
|
||||
the first operator, and 65 for the second. (The address of the second
|
||||
operator is always the address of the first operator plus three).
|
||||
|
||||
To further illustrate the relationship, the addresses needed to control
|
||||
channel 5 are:
|
||||
|
||||
29 - Operator 1 AM/VIB/EG/KSR/Multiplier
|
||||
2C - Operator 2 AM/VIB/EG/KSR/Multiplier
|
||||
49 - Operator 1 KSL/Output Level
|
||||
4C - Operator 2 KSL/Output Level
|
||||
69 - Operator 1 Attack/Decay
|
||||
6C - Operator 2 Attack/Decay
|
||||
89 - Operator 1 Sustain/Release
|
||||
8C - Operator 2 Sustain/Release
|
||||
A4 - Frequency (low 8 bits)
|
||||
B4 - Key On/Octave/Frequency (high 2 bits)
|
||||
C4 - Feedback/Connection Type
|
||||
E9 - Operator 1 Waveform
|
||||
EC - Operator 2 Waveform
|
||||
|
||||
|
||||
|
||||
Explanations of Registers
|
||||
|
||||
Byte 01 - This byte is normally used to test the LSI device. All bits
|
||||
should normally be zero. Bit 5, if enabled, allows the FM
|
||||
chips to control the waveform of each operator.
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| unused | WS | unused |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
|
||||
Byte 02 - Timer 1 Data. If Timer 1 is enabled, the value in this
|
||||
register will be incremented until it overflows. Upon
|
||||
overflow, the sound card will signal a TIMER interrupt
|
||||
(INT 08) and set bits 7 and 6 in its status byte. The
|
||||
value for this timer is incremented every eighty (80)
|
||||
microseconds.
|
||||
|
||||
|
||||
Byte 03 - Timer 2 Data. If Timer 2 is enabled, the value in this
|
||||
register will be incremented until it overflows. Upon
|
||||
overflow, the sound card will signal a TIMER interrupt
|
||||
(INT 08) and set bits 7 and 5 in its status byte. The
|
||||
value for this timer is incremented every three hundred
|
||||
twenty (320) microseconds.
|
||||
|
||||
|
||||
Byte 04 - Timer Control Byte
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| IRQ | T1 | T2 | unused | T2 | T1 |
|
||||
| RST | MSK | MSK | | CTL | CTL |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
bit 7 - Resets the flags for timers 1 & 2. If set,
|
||||
all other bits are ignored.
|
||||
bit 6 - Masks Timer 1. If set, bit 0 is ignored.
|
||||
bit 5 - Masks Timer 2. If set, bit 1 is ignored.
|
||||
bit 1 - When clear, Timer 2 does not operate.
|
||||
When set, the value from byte 03 is loaded into
|
||||
Timer 2, and incrementation begins.
|
||||
bit 0 - When clear, Timer 1 does not operate.
|
||||
When set, the value from byte 02 is loaded into
|
||||
Timer 1, and incrementation begins.
|
||||
|
||||
|
||||
Byte 08 - CSM Mode / Keyboard Split.
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| CSM | Key | unused |
|
||||
| sel | Spl | |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
bit 7 - When set, selects composite sine-wave speech synthesis
|
||||
mode (all KEY-ON bits must be clear). When clear,
|
||||
selects FM music mode.
|
||||
|
||||
bit 6 - Selects the keyboard split point (in conjunction with
|
||||
the F-Number data). The documentation in the Sound
|
||||
Blaster manual is utterly incomprehensible on this;
|
||||
I can't reproduce it without violating their copyright.
|
||||
|
||||
|
||||
Bytes 20-35 - Amplitude Modulation / Vibrato / Envelope Generator Type /
|
||||
Keyboard Scaling Rate / Modulator Frequency Multiple
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| Amp | Vib | EG | KSR | Modulator Frequency |
|
||||
| Mod | | Typ | | Multiple |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
bit 7 - Apply amplitude modulation when set; AM depth is
|
||||
controlled by the AM-Depth flag in address BD.
|
||||
bit 6 - Apply vibrato when set; vibrato depth is controlled
|
||||
by the Vib-Depth flag in address BD.
|
||||
bit 5 - When set, the sustain level of the voice is maintained
|
||||
until released; when clear, the sound begins to decay
|
||||
immediately after hitting the SUSTAIN phase.
|
||||
bit 4 - Keyboard scaling rate. This is another incomprehensible
|
||||
bit in the Sound Blaster manual. From experience, if
|
||||
this bit is set, the sound's envelope is foreshortened as
|
||||
it rises in pitch.
|
||||
bits 3-0 - These bits indicate which harmonic the operator will
|
||||
produce sound (or modulation) in relation to the voice's
|
||||
specified frequency:
|
||||
|
||||
0 - one octave below
|
||||
1 - at the voice's specified frequency
|
||||
2 - one octave above
|
||||
3 - an octave and a fifth above
|
||||
4 - two octaves above
|
||||
5 - two octaves and a major third above
|
||||
6 - two octaves and a fifth above
|
||||
7 - two octaves and a minor seventh above
|
||||
8 - three octaves above
|
||||
9 - three octaves and a major second above
|
||||
A - three octaves and a major third above
|
||||
B - " " " " " " "
|
||||
C - three octaves and a fifth above
|
||||
D - " " " " " "
|
||||
E - three octaves and a major seventh above
|
||||
F - " " " " " " "
|
||||
|
||||
|
||||
Bytes 40-55 - Level Key Scaling / Total Level
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| Scaling | Total Level |
|
||||
| Level | 24 12 6 3 1.5 .75 | <-- dB
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
bits 7-6 - causes output levels to decrease as the frequency
|
||||
rises:
|
||||
|
||||
00 - no change
|
||||
10 - 1.5 dB/8ve
|
||||
01 - 3 dB/8ve
|
||||
11 - 6 dB/8ve
|
||||
|
||||
bits 5-0 - controls the total output level of the operator.
|
||||
all bits CLEAR is loudest; all bits SET is the
|
||||
softest. Don't ask me why.
|
||||
|
||||
|
||||
Bytes 60-75 - Attack Rate / Decay Rate
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| Attack | Decay |
|
||||
| Rate | Rate |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
bits 7-4 - Attack rate. 0 is the slowest, F is the fastest.
|
||||
bits 3-0 - Decay rate. 0 is the slowest, F is the fastest.
|
||||
|
||||
|
||||
Bytes 80-95 - Sustain Level / Release Rate
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| Sustain Level | Release |
|
||||
| 24 12 6 3 | Rate |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
bits 7-4 - Sustain Level. 0 is the loudest, F is the softest.
|
||||
bits 3-0 - Release Rate. 0 is the slowest, F is the fastest.
|
||||
|
||||
|
||||
Bytes A0-B8 - Octave / F-Number / Key-On
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| F-Number (least significant byte) | (A0-A8)
|
||||
| |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| Unused | Key | Octave | F-Number | (B0-B8)
|
||||
| | On | | most sig. |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
bit 5 - Channel is voiced when set, silent when clear.
|
||||
bits 4-2 - Octave (0-7). 0 is lowest, 7 is highest.
|
||||
bits 1-0 - Most significant bits of F-number.
|
||||
|
||||
In octave 4, the F-number values for the chromatic scale and their
|
||||
corresponding frequencies would be:
|
||||
|
||||
F Number Frequency Note
|
||||
16B 277.2 C#
|
||||
181 293.7 D
|
||||
198 311.1 D#
|
||||
1B0 329.6 E
|
||||
1CA 349.2 F
|
||||
1E5 370.0 F#
|
||||
202 392.0 G
|
||||
220 415.3 G#
|
||||
241 440.0 A
|
||||
263 466.2 A#
|
||||
287 493.9 B
|
||||
2AE 523.3 C
|
||||
|
||||
|
||||
Bytes C0-C8 - Feedback / Algorithm
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| unused | Feedback | Alg |
|
||||
| | | |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
bits 3-1 - Feedback strength. If all three bits are set to
|
||||
zero, no feedback is present. With values 1-7,
|
||||
operator 1 will send a portion of its output back
|
||||
into itself. 1 is the least amount of feedback,
|
||||
7 is the most.
|
||||
bit 0 - If set to 0, operator 1 modulates operator 2. In this
|
||||
case, operator 2 is the only one producing sound.
|
||||
If set to 1, both operators produce sound directly.
|
||||
Complex sounds are more easily created if the algorithm
|
||||
is set to 0.
|
||||
|
||||
|
||||
Byte BD - Amplitude Modulation Depth / Vibrato Depth / Rhythm
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| AM | Vib | Rhy | BD | SD | TOM | Top | HH |
|
||||
| Dep | Dep | Ena | | | | Cym | |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
bit 7 - Set: AM depth is 4.8dB
|
||||
Clear: AM depth is 1 dB
|
||||
bit 6 - Set: Vibrato depth is 14 cent
|
||||
Clear: Vibrato depth is 7 cent
|
||||
bit 5 - Set: Rhythm enabled (6 melodic voices)
|
||||
Clear: Rhythm disabled (9 melodic voices)
|
||||
bit 4 - Bass drum on/off
|
||||
bit 3 - Snare drum on/off
|
||||
bit 2 - Tom tom on/off
|
||||
bit 1 - Cymbal on/off
|
||||
bit 0 - Hi Hat on/off
|
||||
|
||||
Note: KEY-ON registers for channels 06, 07, and 08 must be OFF
|
||||
in order to use the rhythm section. Other parameters
|
||||
such as attack/decay/sustain/release must also be set
|
||||
appropriately.
|
||||
|
||||
|
||||
Bytes E0-F5 - Waveform Select
|
||||
|
||||
7 6 5 4 3 2 1 0
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
| unused | Waveform |
|
||||
| | Select |
|
||||
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
|
||||
bits 1-0 - When bit 5 of address 01 is set, the output waveform
|
||||
will be distorted according to the waveform indicated
|
||||
by these two bits. I'll try to diagram them here,
|
||||
but this medium is fairly restrictive.
|
||||
|
||||
___ ___ ___ ___ _ _
|
||||
/ \ / \ / \ / \ / | / |
|
||||
/_____\_______ /_____\_____ /_____\/_____\ /__|___/__|___
|
||||
\ /
|
||||
\___/
|
||||
|
||||
00 01 10 11
|
||||
|
||||
|
||||
|
||||
| Detecting a Sound Card
|
||||
|
|
||||
| According to the AdLib manual, the 'official' method of checking for a
|
||||
| sound card is as follows:
|
||||
|
|
||||
| 1) Reset both timers by writing 60h to register 4.
|
||||
| 2) Enable the interrupts by writing 80h to register 4. NOTE: this
|
||||
| must be a separate step from number 1.
|
||||
| 3) Read the status register (port 388h). Store the result.
|
||||
| 4) Write FFh to register 2 (Timer 1).
|
||||
| 5) Start timer 1 by writing 21h to register 4.
|
||||
| 6) Delay for at least 80 microseconds.
|
||||
| 7) Read the status register (port 388h). Store the result.
|
||||
| 8) Reset both timers and interrupts (see steps 1 and 2).
|
||||
| 9) Test the stored results of steps 3 and 7 by ANDing them
|
||||
| with E0h. The result of step 3 should be 00h, and the
|
||||
| result of step 7 should be C0h. If both are correct, an
|
||||
| AdLib-compatible board is installed in the computer.
|
||||
|
|
||||
|
|
||||
| Making a Sound
|
||||
|
|
||||
| Many people have asked me, upon reading this document, what the proper
|
||||
| register values should be to make a simple sound. Well, here they are.
|
||||
|
|
||||
| First, clear out all of the registers by setting all of them to zero.
|
||||
| This is the quick-and-dirty method of resetting the sound card, but it
|
||||
| works. Note that if you wish to use different waveforms, you must then
|
||||
| turn on bit 5 of register 1. (This reset need be done only once, at the
|
||||
| start of the program, and optionally when the program exits, just to
|
||||
| make sure that your program doesn't leave any notes on when it exits.)
|
||||
|
|
||||
| Now, set the following registers to the indicated value:
|
||||
|
|
||||
| REGISTER VALUE DESCRIPTION
|
||||
| 20 01 Set the modulator's multiple to 1
|
||||
| 40 10 Set the modulator's level to about 40 dB
|
||||
| 60 F0 Modulator attack: quick; decay: long
|
||||
| 80 77 Modulator sustain: medium; release: medium
|
||||
| A0 98 Set voice frequency's LSB (it'll be a D#)
|
||||
| 23 01 Set the carrier's multiple to 1
|
||||
| 43 00 Set the carrier to maximum volume (about 47 dB)
|
||||
| 63 F0 Carrier attack: quick; decay: long
|
||||
| 83 77 Carrier sustain: medium; release: medium
|
||||
| B0 31 Turn the voice on; set the octave and freq MSB
|
||||
|
|
||||
| To turn the voice off, set register B0h to 11h (or, in fact, any value
|
||||
| which leaves bit 5 clear). It's generally preferable, of course, to
|
||||
| induce a delay before doing so.
|
||||
|
|
||||
|
|
||||
| Acknowledgements
|
||||
|
|
||||
| Thanks are due to the following people:
|
||||
|
|
||||
| Ezra M. Dreisbach (ed10+@andrew.cmu.edu), for providing the information
|
||||
| about the recommended port write delay from the AdLib manual, and the
|
||||
| 'official' method of detecting an AdLib-compatible sound card.
|
||||
|
|
||||
| Nathan Isaac Laredo (gt7080a@prism.gatech.edu), for providing the
|
||||
| port numbers for stereo sound on the Sound Blaster Pro.
|
3
opl/dosbox-trace/README
Normal file
3
opl/dosbox-trace/README
Normal file
|
@ -0,0 +1,3 @@
|
|||
Patch to DOSBox to trace the register writes to the OPL chip while running
|
||||
Doom.
|
||||
|
32
opl/dosbox-trace/dosbox-trace.diff
Normal file
32
opl/dosbox-trace/dosbox-trace.diff
Normal file
|
@ -0,0 +1,32 @@
|
|||
--- dosbox-0.72-clean/src/hardware/adlib.cpp 2009-02-19 18:12:05.000000000 +0000
|
||||
+++ dosbox-0.72/src/hardware/adlib.cpp 2009-03-12 18:15:41.000000000 +0000
|
||||
@@ -135,15 +135,17 @@
|
||||
|
||||
static Bitu OPL_Read(Bitu port,Bitu iolen) {
|
||||
Bitu addr=port & 3;
|
||||
+ Bitu result = 0xff;
|
||||
switch (opl.mode) {
|
||||
case OPL_opl2:
|
||||
- return OPL2::YM3812Read(0,addr);
|
||||
+ result = OPL2::YM3812Read(0,addr);
|
||||
case OPL_dualopl2:
|
||||
- return OPL2::YM3812Read(addr>>1,addr);
|
||||
+ result = OPL2::YM3812Read(addr>>1,addr);
|
||||
case OPL_opl3:
|
||||
- return THEOPL3::YMF262Read(0,addr);
|
||||
+ result = THEOPL3::YMF262Read(0,addr);
|
||||
}
|
||||
- return 0xff;
|
||||
+ printf("OPL_read: %x -> %x\n", addr, result);
|
||||
+ return result;
|
||||
}
|
||||
|
||||
static void OPL_RawAdd(Bitu index,Bitu val);
|
||||
@@ -160,6 +162,7 @@
|
||||
if (opl.raw.capturing) OPL_RawAdd(index,val);
|
||||
} else opl.raw.cmd[port>>1]=val;
|
||||
if (!port) adlib_commandreg=val;
|
||||
+ printf("OPL_write: %x, %x\n", port, val);
|
||||
switch (opl.mode) {
|
||||
case OPL_opl2:
|
||||
OPL2::YM3812Write(0,port,val);
|
BIN
opl/dosbox-trace/e1m1-trace-opl3.txt.gz
Normal file
BIN
opl/dosbox-trace/e1m1-trace-opl3.txt.gz
Normal file
Binary file not shown.
BIN
opl/dosbox-trace/e1m1-trace.txt.gz
Normal file
BIN
opl/dosbox-trace/e1m1-trace.txt.gz
Normal file
Binary file not shown.
BIN
opl/dosbox-trace/e1m1_annotated.txt.gz
Normal file
BIN
opl/dosbox-trace/e1m1_annotated.txt.gz
Normal file
Binary file not shown.
BIN
opl/dosbox-trace/e1m1_processed.txt.gz
Normal file
BIN
opl/dosbox-trace/e1m1_processed.txt.gz
Normal file
Binary file not shown.
376
opl/dosbox-trace/opl-parser
Executable file
376
opl/dosbox-trace/opl-parser
Executable file
|
@ -0,0 +1,376 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require "scanf"
|
||||
|
||||
FREQ_MULT_OFFSET = 0x20
|
||||
SCALE_LEVEL_OFFSET = 0x40
|
||||
ATTACK_DECAY_OFFSET = 0x60
|
||||
SUSTAIN_RELEASE_OFFSET = 0x80
|
||||
OCTAVE_KEY_LSB_OFFSET = 0xa0
|
||||
OCTAVE_KEY_MSB_OFFSET = 0xb0
|
||||
FEEDBACK_OFFSET = 0xc0
|
||||
WAVEFORM_SELECT_OFFSET = 0xe0
|
||||
|
||||
CHANNEL_OPERATORS = [ [ 0x00, 0x03 ],
|
||||
[ 0x01, 0x04 ],
|
||||
[ 0x02, 0x05 ],
|
||||
[ 0x08, 0x0b ],
|
||||
[ 0x09, 0x0c ],
|
||||
[ 0x0a, 0x0d ],
|
||||
[ 0x10, 0x13 ],
|
||||
[ 0x11, 0x14 ],
|
||||
[ 0x12, 0x15 ] ]
|
||||
|
||||
def reverse_channels_list
|
||||
result = [ -1 ] * 21
|
||||
|
||||
for channel in 0...CHANNEL_OPERATORS.length
|
||||
result[CHANNEL_OPERATORS[channel][0]] = channel
|
||||
result[CHANNEL_OPERATORS[channel][1]] = channel
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
OPERATOR_TO_CHANNEL = reverse_channels_list
|
||||
|
||||
class RegisterWrite
|
||||
|
||||
attr_reader :register, :value
|
||||
|
||||
def initialize(register, value)
|
||||
@register = register
|
||||
@value = value
|
||||
end
|
||||
|
||||
def to_s
|
||||
sprintf("%02x: %02x", @register, @value)
|
||||
end
|
||||
end
|
||||
|
||||
class MatchPattern
|
||||
def initialize(pattern_class, pattern)
|
||||
@pattern_class = pattern_class
|
||||
@pattern = pattern
|
||||
end
|
||||
|
||||
def matches(array, offset)
|
||||
|
||||
# Sanity check
|
||||
|
||||
if offset + length > array.length
|
||||
return nil
|
||||
end
|
||||
|
||||
# Check that all values in this pattern match
|
||||
|
||||
for i in 0...@pattern.length
|
||||
reg_pattern = @pattern[i]
|
||||
reg_write = array[offset + i]
|
||||
|
||||
# Is this a write to the expected register?
|
||||
|
||||
expected = reg_pattern[0]
|
||||
mask = reg_pattern[1]
|
||||
|
||||
if (reg_write.register & mask) != (expected & mask)
|
||||
return nil
|
||||
end
|
||||
|
||||
# Optional checking of the value:
|
||||
|
||||
if reg_pattern.length > 2
|
||||
expected = reg_pattern[2]
|
||||
mask = reg_pattern[3]
|
||||
|
||||
if (reg_write.value & mask) != (expected & mask)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@pattern_class.new(array[offset,length])
|
||||
end
|
||||
|
||||
def length
|
||||
@pattern.length
|
||||
end
|
||||
end
|
||||
|
||||
class Event
|
||||
attr_reader :reg_writes
|
||||
|
||||
def initialize(reg_writes)
|
||||
@reg_writes = reg_writes
|
||||
end
|
||||
|
||||
# Get a particular register value set in this event:
|
||||
|
||||
def find_value(register)
|
||||
for r in @reg_writes
|
||||
if register == r.register
|
||||
return r.value
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Event to configure a channel:
|
||||
|
||||
class ChannelEvent < Event
|
||||
def channel
|
||||
first_reg = @reg_writes[0].register
|
||||
first_reg & 0xf
|
||||
end
|
||||
|
||||
def operator_1
|
||||
CHANNEL_OPERATORS[channel][0]
|
||||
end
|
||||
|
||||
def operator_2
|
||||
CHANNEL_OPERATORS[channel][1]
|
||||
end
|
||||
end
|
||||
|
||||
# Event to configure a channel, but we are actually only configuring
|
||||
# a single operator
|
||||
|
||||
class OperatorEvent < ChannelEvent
|
||||
def channel
|
||||
first_reg = @reg_writes[0].register
|
||||
operator = first_reg & 0x1f
|
||||
OPERATOR_TO_CHANNEL[operator]
|
||||
end
|
||||
|
||||
# Operator number - 0 or 1
|
||||
|
||||
def operator_num
|
||||
first_reg = @reg_writes[0].register
|
||||
operator = first_reg & 0x1f
|
||||
|
||||
if operator == operator_1
|
||||
0
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class InitChannel < OperatorEvent
|
||||
|
||||
GENMIDI_REGS = [ FREQ_MULT_OFFSET, ATTACK_DECAY_OFFSET,
|
||||
SUSTAIN_RELEASE_OFFSET, WAVEFORM_SELECT_OFFSET,
|
||||
SCALE_LEVEL_OFFSET, SCALE_LEVEL_OFFSET ]
|
||||
|
||||
def operator_values(op)
|
||||
result = []
|
||||
for base in GENMIDI_REGS
|
||||
result.push(find_value(base + op))
|
||||
end
|
||||
|
||||
result[4] &= 0xc0 # Key scale level
|
||||
result[5] &= 0x3f # Output level
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# Get values in the order that they are stored in the GENMIDI
|
||||
# lump.
|
||||
|
||||
def values
|
||||
operator_values(operator_1) +
|
||||
[ find_value(FEEDBACK_OFFSET + channel) ] +
|
||||
operator_values(operator_2)
|
||||
end
|
||||
|
||||
def to_s
|
||||
stringified = values.map { |val| sprintf("%02x", val) }
|
||||
stringified = stringified.join(",")
|
||||
"Initialising channel #{channel}: #{stringified}"
|
||||
end
|
||||
end
|
||||
|
||||
class DetectionSequence < Event
|
||||
def to_s
|
||||
"Adlib detection sequence"
|
||||
end
|
||||
end
|
||||
|
||||
class ScaleLevelChange < OperatorEvent
|
||||
SCALE_LEVELS = [
|
||||
"no change",
|
||||
"3dB/8ve",
|
||||
"1.5dB/8ve",
|
||||
"6dB/8ve"
|
||||
]
|
||||
|
||||
def to_s
|
||||
value = @reg_writes[0].value
|
||||
|
||||
scale_level = (value >> 6) & 0x3
|
||||
total_level = (value & 0x3f) * 0.75
|
||||
|
||||
"Scale level change on channel #{channel}, op #{operator_num}: " +
|
||||
"#{total_level}dB, #{SCALE_LEVELS[scale_level]}"
|
||||
end
|
||||
end
|
||||
|
||||
class KeyOn < ChannelEvent
|
||||
def to_s
|
||||
value = @reg_writes[0].value | (@reg_writes[1].value << 8)
|
||||
|
||||
octave = (value >> 10) & 0x7
|
||||
f_number = value & 0x3ff
|
||||
|
||||
sprintf("Key on, channel %i: octave %i, freq 0x%x",
|
||||
channel, octave, f_number)
|
||||
end
|
||||
end
|
||||
|
||||
class KeyOff < ChannelEvent
|
||||
OPERATOR_TYPE = true
|
||||
|
||||
def to_s
|
||||
"Key off, channel #{channel}"
|
||||
end
|
||||
end
|
||||
|
||||
class InitialInit < Event
|
||||
def to_s
|
||||
"Initial initialisation of registers..."
|
||||
end
|
||||
end
|
||||
|
||||
# Fallback for if we can't match a pattern:
|
||||
|
||||
class BasicRegisterWrite < Event
|
||||
def to_s
|
||||
"Basic register write: " + @reg_writes[0].to_s
|
||||
end
|
||||
end
|
||||
|
||||
def parse_file(stream)
|
||||
register = nil
|
||||
|
||||
result = []
|
||||
|
||||
stream.each_line do |s|
|
||||
if s =~ /OPL_write: (\d+), ([0-9a-fA-F]+)/
|
||||
reg = $1.to_i
|
||||
value = $2.scanf("%x")[0]
|
||||
|
||||
if reg == 0
|
||||
register = value
|
||||
else
|
||||
result.push(RegisterWrite.new(register, value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
MATCH_PATTERNS = [
|
||||
# Adlib detection sequence:
|
||||
|
||||
MatchPattern.new(DetectionSequence,
|
||||
[ [ 4, 0xff, 0x60, 0xff ],
|
||||
[ 4, 0xff, 0x80, 0xff ],
|
||||
[ 2, 0xff, 0xff, 0xff ],
|
||||
[ 4, 0xff, 0x21, 0xff ],
|
||||
[ 4, 0xff, 0x60, 0xff ],
|
||||
[ 4, 0xff, 0x80, 0xff ] ]),
|
||||
|
||||
# Startup initialisation values:
|
||||
|
||||
MatchPattern.new(InitialInit,
|
||||
[ [ 0x40, 0xe3, 0x3f, 0xff ],
|
||||
[ 0x41, 0xe3, 0x3f, 0xff ],
|
||||
[ 0x42, 0xe3, 0x3f, 0xff ],
|
||||
[ 0x43, 0xe3, 0x3f, 0xff ] ]),
|
||||
|
||||
MatchPattern.new(InitialInit,
|
||||
[ [ 0x00, 0x03, 0x00, 0xff ],
|
||||
[ 0x01, 0x03, 0x00, 0xff ],
|
||||
[ 0x02, 0x03, 0x00, 0xff ],
|
||||
[ 0x03, 0x03, 0x00, 0xff ] ]),
|
||||
|
||||
# Key on
|
||||
|
||||
MatchPattern.new(KeyOn,
|
||||
[ [ OCTAVE_KEY_LSB_OFFSET, 0xf0 ],
|
||||
[ OCTAVE_KEY_MSB_OFFSET, 0xf0, 0x20, 0x20 ]]),
|
||||
|
||||
# Key off
|
||||
MatchPattern.new(KeyOff,
|
||||
[ [ OCTAVE_KEY_MSB_OFFSET, 0xf0, 0x20, 0x00 ]]),
|
||||
|
||||
# This pattern occurs when a channel has the registers for its
|
||||
# operators initialised.
|
||||
|
||||
MatchPattern.new(InitChannel,
|
||||
[ [SCALE_LEVEL_OFFSET, 0xe0], # First op
|
||||
[FREQ_MULT_OFFSET, 0xe0],
|
||||
[ATTACK_DECAY_OFFSET, 0xe0],
|
||||
[SUSTAIN_RELEASE_OFFSET, 0xe0],
|
||||
[WAVEFORM_SELECT_OFFSET, 0xe0],
|
||||
|
||||
[SCALE_LEVEL_OFFSET, 0xe0], # Second op
|
||||
[FREQ_MULT_OFFSET, 0xe0],
|
||||
[ATTACK_DECAY_OFFSET, 0xe0],
|
||||
[SUSTAIN_RELEASE_OFFSET, 0xe0],
|
||||
[WAVEFORM_SELECT_OFFSET, 0xe0],
|
||||
|
||||
[FEEDBACK_OFFSET, 0xf0] ]),
|
||||
|
||||
# Scale level change
|
||||
|
||||
MatchPattern.new(ScaleLevelChange,
|
||||
[ [ SCALE_LEVEL_OFFSET, 0xe0 ] ]),
|
||||
|
||||
# Fallback basic register write:
|
||||
|
||||
MatchPattern.new(BasicRegisterWrite, [[0, 0]])
|
||||
]
|
||||
|
||||
writes = parse_file($stdin)
|
||||
|
||||
offset = 0
|
||||
parsed = []
|
||||
|
||||
channel_in_use = [ false ] * 9
|
||||
channel_allocate_queue = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]
|
||||
|
||||
while offset < writes.length
|
||||
for pattern in MATCH_PATTERNS
|
||||
match = pattern.matches(writes, offset)
|
||||
|
||||
if match != nil
|
||||
parsed.push(match)
|
||||
offset += pattern.length
|
||||
|
||||
if match.is_a?(KeyOn) and (match.channel >= 0 and match.channel < 9)
|
||||
if !channel_in_use[match.channel]
|
||||
expected = channel_allocate_queue[0]
|
||||
channel_allocate_queue = (channel_allocate_queue[1,9] or [])
|
||||
channel_in_use[match.channel] = true
|
||||
puts "expect: #{expected}"
|
||||
end
|
||||
end
|
||||
if match.is_a?(KeyOff) and (match.channel >= 0 and match.channel < 9)
|
||||
channel_allocate_queue.push(match.channel)
|
||||
channel_in_use[match.channel] = false
|
||||
end
|
||||
|
||||
puts match
|
||||
for r in match.reg_writes
|
||||
puts "\t#{r}"
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
2
opl/droplay/README
Normal file
2
opl/droplay/README
Normal file
|
@ -0,0 +1,2 @@
|
|||
Test program for playing back DOSBox Raw OPL files.
|
||||
|
BIN
opl/droplay/doom_000.dro
Normal file
BIN
opl/droplay/doom_000.dro
Normal file
Binary file not shown.
110
opl/droplay/droplay.c
Normal file
110
opl/droplay/droplay.c
Normal file
|
@ -0,0 +1,110 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/io.h>
|
||||
|
||||
#define HEADER_STRING "DBRAWOPL"
|
||||
#define ADLIB_PORT 0x388
|
||||
|
||||
void write_reg(unsigned int reg, unsigned int val)
|
||||
{
|
||||
int i;
|
||||
|
||||
outb(reg, ADLIB_PORT);
|
||||
|
||||
for (i=0; i<6; ++i) {
|
||||
inb(ADLIB_PORT);
|
||||
}
|
||||
|
||||
outb(val, ADLIB_PORT + 1);
|
||||
|
||||
for (i=0; i<35; ++i) {
|
||||
inb(ADLIB_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void init(void)
|
||||
{
|
||||
if (ioperm(ADLIB_PORT, 2, 1) != 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to get IO permissions\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
write_reg(4, 0x60);
|
||||
write_reg(4, 0x80);
|
||||
int val1 = inb(ADLIB_PORT) & 0xe0;
|
||||
write_reg(2, 0xff);
|
||||
write_reg(4, 0x21);
|
||||
sleep(1);
|
||||
int val2 = inb(ADLIB_PORT) & 0xe0;
|
||||
write_reg(4, 0x60);
|
||||
write_reg(4, 0x80);
|
||||
|
||||
if (val1 != 0 || val2 != 0xc0)
|
||||
{
|
||||
fprintf(stderr, "Adlib not detected\n");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void play_file(char *filename)
|
||||
{
|
||||
FILE *stream;
|
||||
char buf[8];
|
||||
|
||||
stream = fopen(filename, "rb");
|
||||
|
||||
fread(buf, 1, 8, stream);
|
||||
|
||||
if (strncmp(buf, HEADER_STRING, 8) != 0)
|
||||
{
|
||||
fprintf(stderr, "Raw OPL header not found\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
fseek(stream, 28, SEEK_SET);
|
||||
|
||||
while (!feof(stream))
|
||||
{
|
||||
int reg, val;
|
||||
|
||||
reg = fgetc(stream);
|
||||
val = fgetc(stream);
|
||||
|
||||
// Delay?
|
||||
|
||||
if (reg == 0x00)
|
||||
{
|
||||
usleep(val * 1000);
|
||||
}
|
||||
else if (reg == 0x01)
|
||||
{
|
||||
val |= (fgetc(stream) << 8);
|
||||
usleep(val * 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_reg(reg, val);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(stream);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
init();
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
printf("Usage: %s <filename>\n", argv[0]);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
play_file(argv[1]);
|
||||
|
||||
}
|
||||
|
3
opl/genmidi/README
Normal file
3
opl/genmidi/README
Normal file
|
@ -0,0 +1,3 @@
|
|||
Script to parse the Doom GENMIDI lump and dump to text file or SBI
|
||||
format file for use with sbiload.
|
||||
|
BIN
opl/genmidi/drums.o3
Normal file
BIN
opl/genmidi/drums.o3
Normal file
Binary file not shown.
202
opl/genmidi/genmidi-dumper
Executable file
202
opl/genmidi/genmidi-dumper
Executable file
|
@ -0,0 +1,202 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
NUM_INSTRUMENTS = 175
|
||||
|
||||
# Layout of GENMIDI lump data:
|
||||
|
||||
LMP_AM_VIB_OFFSET = 0
|
||||
LMP_ATTACK_DECAY_OFFSET = 1
|
||||
LMP_SUSTAIN_RELEASE_OFFSET = 2
|
||||
LMP_WAVE_SELECT_OFFSET = 3
|
||||
LMP_KSL_OFFSET = 4
|
||||
LMP_OUTPUT_LEVEL_OFFSET = 5
|
||||
|
||||
LMP_OPERATOR2_OFFSET = 7
|
||||
LMP_OPERATOR_LENGTH = 6
|
||||
LMP_FEEDBACK_OFFSET = 6
|
||||
|
||||
# Layout of SBI format files:
|
||||
|
||||
SBI_HEADER = "2OP\032"
|
||||
SBI_AM_VIB_OFFSET = 0
|
||||
SBI_KSL_LEVEL_OFFSET = 2
|
||||
SBI_ATTACK_DECAY_OFFSET = 4
|
||||
SBI_SUSTAIN_RELEASE_OFFSET = 6
|
||||
SBI_WAVE_SELECT_OFFSET = 8
|
||||
SBI_CONNECTION_OFFSET = 10
|
||||
|
||||
class Instrument
|
||||
FIXED_PITCH = 0x0001
|
||||
DOUBLE_VOICE = 0x0004
|
||||
attr_reader :voice1_data, :voice2_data, :feedback, :flags
|
||||
attr_reader :fine_tuning, :fixed_note
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(data)
|
||||
header = data[0,4]
|
||||
|
||||
@flags = data[0] | (data[1] << 8)
|
||||
@fine_tuning = data[2] - 128
|
||||
@fixed_note = data[3]
|
||||
@voice1_data = data[4,13]
|
||||
@voice2_data = data[20,13]
|
||||
end
|
||||
|
||||
def fixed_pitch
|
||||
(@flags & FIXED_PITCH) != 0
|
||||
end
|
||||
|
||||
def double_voice
|
||||
(@flags & DOUBLE_VOICE) != 0
|
||||
end
|
||||
end
|
||||
|
||||
def read_instrument(file)
|
||||
data = file.read(36)
|
||||
|
||||
Instrument.new(data)
|
||||
end
|
||||
|
||||
def read_name(file)
|
||||
data = file.read(32)
|
||||
data.strip
|
||||
end
|
||||
|
||||
def read_instruments(filename)
|
||||
instruments = []
|
||||
|
||||
File.open("genmidi.lmp") do |file|
|
||||
header = file.read(8)
|
||||
|
||||
if header != "#OPL_II#"
|
||||
raise "Header not found!"
|
||||
end
|
||||
|
||||
NUM_INSTRUMENTS.times do
|
||||
instrument = read_instrument(file)
|
||||
instruments.push(instrument)
|
||||
end
|
||||
|
||||
NUM_INSTRUMENTS.times do |i|
|
||||
name = read_name(file)
|
||||
instruments[i].name = name
|
||||
end
|
||||
end
|
||||
|
||||
instruments
|
||||
end
|
||||
|
||||
def data_to_s(data)
|
||||
|
||||
stringified = []
|
||||
|
||||
data.each_byte do |val|
|
||||
stringified.push(sprintf("%02x", val))
|
||||
end
|
||||
|
||||
stringified.join(",")
|
||||
end
|
||||
|
||||
def display_instruments(instruments)
|
||||
for instrument in instruments
|
||||
print instrument.name
|
||||
padding = 25 - instrument.name.length
|
||||
print " " * padding
|
||||
|
||||
print "(#{instrument.flags}) "
|
||||
|
||||
print data_to_s(instrument.voice1_data)
|
||||
print "|"
|
||||
print data_to_s(instrument.voice2_data)
|
||||
print "\n"
|
||||
end
|
||||
end
|
||||
|
||||
def lmp_voice_to_sbi(voice_data)
|
||||
operator_data = [
|
||||
voice_data[0, LMP_OPERATOR_LENGTH],
|
||||
voice_data[LMP_OPERATOR2_OFFSET, LMP_OPERATOR_LENGTH]
|
||||
]
|
||||
|
||||
result = "\0" * 11
|
||||
|
||||
for op in [0, 1]
|
||||
op_data = operator_data[op]
|
||||
|
||||
result[SBI_AM_VIB_OFFSET + op] = op_data[LMP_AM_VIB_OFFSET]
|
||||
result[SBI_KSL_LEVEL_OFFSET + op] = (op_data[LMP_KSL_OFFSET] \
|
||||
| op_data[LMP_OUTPUT_LEVEL_OFFSET])
|
||||
result[SBI_ATTACK_DECAY_OFFSET + op] = op_data[LMP_ATTACK_DECAY_OFFSET]
|
||||
result[SBI_SUSTAIN_RELEASE_OFFSET + op] = op_data[LMP_SUSTAIN_RELEASE_OFFSET]
|
||||
result[SBI_WAVE_SELECT_OFFSET + op] = op_data[LMP_WAVE_SELECT_OFFSET]
|
||||
end
|
||||
|
||||
result[SBI_CONNECTION_OFFSET] = voice_data[LMP_FEEDBACK_OFFSET]
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def pad_name(name, length)
|
||||
name = name[0, length]
|
||||
|
||||
result = "\0" * length
|
||||
result[0, name.length] = name
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def lmp_instrument_to_sbi(instrument)
|
||||
# Build header
|
||||
|
||||
result = SBI_HEADER
|
||||
|
||||
name = pad_name(instrument.name, 32)
|
||||
|
||||
# fine tuning:
|
||||
|
||||
if instrument.fine_tuning != 0
|
||||
name[28] = 64 + instrument.fine_tuning
|
||||
end
|
||||
if (instrument.flags & Instrument::FIXED_PITCH) != 0
|
||||
name[31] = instrument.fixed_note
|
||||
end
|
||||
|
||||
result += name
|
||||
|
||||
# Voice data
|
||||
|
||||
result += lmp_voice_to_sbi(instrument.voice1_data)
|
||||
result += lmp_voice_to_sbi(instrument.voice2_data)
|
||||
result += "\0\0"
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# Write an SBI format file.
|
||||
|
||||
def write_sbi(filename, instruments, start_instrument)
|
||||
File.open(filename, "w") do |file|
|
||||
start_instrument.times do
|
||||
file.write "\0" * 60
|
||||
end
|
||||
|
||||
for instrument in instruments
|
||||
data = lmp_instrument_to_sbi(instrument)
|
||||
file.write(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ARGV.length < 1
|
||||
puts "Usage: genmidi-dumper [ -sbi | -txt ]"
|
||||
exit(0)
|
||||
end
|
||||
|
||||
instruments = read_instruments("genmidi.lmp")
|
||||
|
||||
if ARGV[0] == "-sbi"
|
||||
write_sbi("std.o3", instruments[0, 128], 0)
|
||||
write_sbi("drums.o3", instruments[128, instruments.length], 35)
|
||||
elsif ARGV[0] == "-txt"
|
||||
display_instruments(instruments)
|
||||
end
|
BIN
opl/genmidi/genmidi-patches.txt
Normal file
BIN
opl/genmidi/genmidi-patches.txt
Normal file
Binary file not shown.
BIN
opl/genmidi/genmidi.lmp
Normal file
BIN
opl/genmidi/genmidi.lmp
Normal file
Binary file not shown.
BIN
opl/genmidi/std.o3
Normal file
BIN
opl/genmidi/std.o3
Normal file
Binary file not shown.
2
opl/oplinit/README
Normal file
2
opl/oplinit/README
Normal file
|
@ -0,0 +1,2 @@
|
|||
Linux test program to initialise the OPL chip and play a tone.
|
||||
|
82
opl/oplinit/oplinit.c
Normal file
82
opl/oplinit/oplinit.c
Normal file
|
@ -0,0 +1,82 @@
|
|||
|
||||
// plays a tone via the OPL chip
|
||||
// this will not work if you don't have an OPL chip.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/io.h>
|
||||
|
||||
#define PORT 0x388
|
||||
|
||||
void write_reg(int reg, int val)
|
||||
{
|
||||
int i;
|
||||
|
||||
outb(reg, PORT);
|
||||
|
||||
for (i=0; i<6; ++i) {
|
||||
inb(PORT);
|
||||
}
|
||||
|
||||
outb(val, PORT + 1);
|
||||
|
||||
for (i=0; i<35; ++i) {
|
||||
inb(PORT);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char buf[64];
|
||||
|
||||
if (ioperm(PORT, 4, 1) != 0)
|
||||
{
|
||||
printf("Failed to get permissions\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
printf("got permissions\n");
|
||||
|
||||
write_reg(4, 0x60);
|
||||
write_reg(4, 0x80);
|
||||
int val1 = inb(PORT) & 0xe0;
|
||||
write_reg(2, 0xff);
|
||||
write_reg(4, 0x21);
|
||||
sleep(1);
|
||||
int val2 = inb(PORT) & 0xe0;
|
||||
write_reg(4, 0x60);
|
||||
write_reg(4, 0x80);
|
||||
|
||||
if (val1 == 0 && val2 == 0xc0)
|
||||
{
|
||||
printf("OPL detected\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("not detected - %i, %i\n", val1, val2);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// program a sound
|
||||
|
||||
write_reg(0x20, 0x01);
|
||||
write_reg(0x40, 0x10);
|
||||
write_reg(0x60, 0xf0);
|
||||
write_reg(0x80, 0x77);
|
||||
write_reg(0xa0, 0x98);
|
||||
write_reg(0x23, 0x01);
|
||||
write_reg(0x43, 0x00);
|
||||
write_reg(0x63, 0xf0);
|
||||
write_reg(0x83, 0x77);
|
||||
write_reg(0xb0, 0x31);
|
||||
|
||||
gets(buf);
|
||||
|
||||
// turn the sound off
|
||||
|
||||
write_reg(0xb0, 0x11);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
3
opl/scale/README
Normal file
3
opl/scale/README
Normal file
|
@ -0,0 +1,3 @@
|
|||
Test MIDI file to play each note in turn to determine the appropriate
|
||||
register values to program for each note.
|
||||
|
BIN
opl/scale/e1m1-cropped.mid
Normal file
BIN
opl/scale/e1m1-cropped.mid
Normal file
Binary file not shown.
156
opl/scale/freqs.txt
Normal file
156
opl/scale/freqs.txt
Normal file
|
@ -0,0 +1,156 @@
|
|||
octave/f-numbers used by doom for note values
|
||||
|
||||
0-9
|
||||
Key on, channel 1: octave 0, freq 0x158
|
||||
Key on, channel 2: octave 0, freq 0x16d
|
||||
Key on, channel 3: octave 0, freq 0x183
|
||||
Key on, channel 4: octave 0, freq 0x19a
|
||||
Key on, channel 5: octave 0, freq 0x1b2
|
||||
Key on, channel 6: octave 0, freq 0x1cc
|
||||
Key on, channel 7: octave 0, freq 0x1e7 ___
|
||||
Key on, channel 8: octave 0, freq 0x204
|
||||
Key on, channel 1: octave 0, freq 0x223
|
||||
Key on, channel 2: octave 0, freq 0x244
|
||||
|
||||
10-19
|
||||
Key on, channel 3: octave 0, freq 0x266
|
||||
Key on, channel 4: octave 0, freq 0x28b
|
||||
Key on, channel 5: octave 0, freq 0x2b1
|
||||
Key on, channel 6: octave 0, freq 0x2da
|
||||
Key on, channel 7: octave 0, freq 0x306
|
||||
Key on, channel 8: octave 0, freq 0x334
|
||||
Key on, channel 1: octave 0, freq 0x365
|
||||
Key on, channel 2: octave 0, freq 0x398
|
||||
Key on, channel 3: octave 0, freq 0x3cf ___
|
||||
Key on, channel 4: octave 1, freq 0x204
|
||||
|
||||
20-29
|
||||
Key on, channel 5: octave 1, freq 0x223
|
||||
Key on, channel 6: octave 1, freq 0x244
|
||||
Key on, channel 7: octave 1, freq 0x266
|
||||
Key on, channel 8: octave 1, freq 0x28b
|
||||
Key on, channel 1: octave 1, freq 0x2b1
|
||||
Key on, channel 2: octave 1, freq 0x2da
|
||||
Key on, channel 3: octave 1, freq 0x306
|
||||
Key on, channel 4: octave 1, freq 0x334
|
||||
Key on, channel 5: octave 1, freq 0x365
|
||||
Key on, channel 6: octave 1, freq 0x398
|
||||
|
||||
30-39
|
||||
Key on, channel 7: octave 1, freq 0x3cf ___
|
||||
Key on, channel 8: octave 2, freq 0x204
|
||||
Key on, channel 1: octave 2, freq 0x223
|
||||
Key on, channel 2: octave 2, freq 0x244
|
||||
Key on, channel 3: octave 2, freq 0x266
|
||||
Key on, channel 4: octave 2, freq 0x28b
|
||||
Key on, channel 5: octave 2, freq 0x2b1
|
||||
Key on, channel 6: octave 2, freq 0x2da
|
||||
Key on, channel 7: octave 2, freq 0x306
|
||||
Key on, channel 8: octave 2, freq 0x334
|
||||
|
||||
40-49
|
||||
Key on, channel 1: octave 2, freq 0x365
|
||||
Key on, channel 2: octave 2, freq 0x398
|
||||
Key on, channel 3: octave 2, freq 0x3cf ___
|
||||
Key on, channel 4: octave 3, freq 0x204
|
||||
Key on, channel 5: octave 3, freq 0x223
|
||||
Key on, channel 6: octave 3, freq 0x244
|
||||
Key on, channel 7: octave 3, freq 0x266
|
||||
Key on, channel 8: octave 3, freq 0x28b
|
||||
Key on, channel 1: octave 3, freq 0x2b1
|
||||
Key on, channel 2: octave 3, freq 0x2da
|
||||
|
||||
50-59
|
||||
Key on, channel 3: octave 3, freq 0x306
|
||||
Key on, channel 4: octave 3, freq 0x334
|
||||
Key on, channel 5: octave 3, freq 0x365
|
||||
Key on, channel 6: octave 3, freq 0x398
|
||||
Key on, channel 7: octave 3, freq 0x3cf ___
|
||||
Key on, channel 8: octave 4, freq 0x204
|
||||
Key on, channel 1: octave 4, freq 0x223
|
||||
Key on, channel 2: octave 4, freq 0x244
|
||||
Key on, channel 3: octave 4, freq 0x266
|
||||
Key on, channel 4: octave 4, freq 0x28b
|
||||
|
||||
60-69
|
||||
Key on, channel 5: octave 4, freq 0x2b1
|
||||
Key on, channel 6: octave 4, freq 0x2da
|
||||
Key on, channel 7: octave 4, freq 0x306
|
||||
Key on, channel 8: octave 4, freq 0x334
|
||||
Key on, channel 1: octave 4, freq 0x365
|
||||
Key on, channel 2: octave 4, freq 0x398
|
||||
Key on, channel 3: octave 4, freq 0x3cf ___
|
||||
Key on, channel 4: octave 5, freq 0x204
|
||||
Key on, channel 5: octave 5, freq 0x223
|
||||
Key on, channel 6: octave 5, freq 0x244
|
||||
|
||||
70-79
|
||||
Key on, channel 7: octave 5, freq 0x266
|
||||
Key on, channel 8: octave 5, freq 0x28b
|
||||
Key on, channel 1: octave 5, freq 0x2b1
|
||||
Key on, channel 2: octave 5, freq 0x2da
|
||||
Key on, channel 3: octave 5, freq 0x306
|
||||
Key on, channel 4: octave 5, freq 0x334
|
||||
Key on, channel 5: octave 5, freq 0x365
|
||||
Key on, channel 6: octave 5, freq 0x398
|
||||
Key on, channel 7: octave 5, freq 0x3cf ___
|
||||
Key on, channel 8: octave 6, freq 0x204
|
||||
|
||||
80-89
|
||||
Key on, channel 1: octave 6, freq 0x223
|
||||
Key on, channel 2: octave 6, freq 0x244
|
||||
Key on, channel 3: octave 6, freq 0x266
|
||||
Key on, channel 4: octave 6, freq 0x28b
|
||||
Key on, channel 5: octave 6, freq 0x2b1
|
||||
Key on, channel 6: octave 6, freq 0x2da
|
||||
Key on, channel 7: octave 6, freq 0x306
|
||||
Key on, channel 8: octave 6, freq 0x334
|
||||
Key on, channel 1: octave 6, freq 0x365
|
||||
Key on, channel 2: octave 6, freq 0x398
|
||||
|
||||
90-99
|
||||
Key on, channel 3: octave 6, freq 0x3cf ___
|
||||
Key on, channel 4: octave 7, freq 0x204
|
||||
Key on, channel 5: octave 7, freq 0x223
|
||||
Key on, channel 6: octave 7, freq 0x244
|
||||
Key on, channel 7: octave 7, freq 0x266
|
||||
Key on, channel 8: octave 7, freq 0x28b
|
||||
Key on, channel 1: octave 6, freq 0x2b1
|
||||
Key on, channel 2: octave 6, freq 0x2da
|
||||
Key on, channel 3: octave 6, freq 0x306
|
||||
Key on, channel 4: octave 6, freq 0x334
|
||||
|
||||
100-109
|
||||
Key on, channel 5: octave 6, freq 0x365
|
||||
Key on, channel 6: octave 6, freq 0x398
|
||||
Key on, channel 7: octave 6, freq 0x3cf ___
|
||||
Key on, channel 8: octave 7, freq 0x204
|
||||
Key on, channel 1: octave 7, freq 0x223
|
||||
Key on, channel 2: octave 7, freq 0x244
|
||||
Key on, channel 3: octave 7, freq 0x266
|
||||
Key on, channel 4: octave 7, freq 0x28b
|
||||
Key on, channel 5: octave 6, freq 0x2b1
|
||||
Key on, channel 6: octave 6, freq 0x2da
|
||||
|
||||
110-119
|
||||
Key on, channel 7: octave 6, freq 0x306
|
||||
Key on, channel 8: octave 6, freq 0x334
|
||||
Key on, channel 1: octave 6, freq 0x365
|
||||
Key on, channel 2: octave 6, freq 0x398
|
||||
Key on, channel 3: octave 6, freq 0x3cf ___
|
||||
Key on, channel 4: octave 7, freq 0x204
|
||||
Key on, channel 5: octave 7, freq 0x223
|
||||
Key on, channel 6: octave 7, freq 0x244
|
||||
Key on, channel 7: octave 7, freq 0x266
|
||||
Key on, channel 8: octave 7, freq 0x28b
|
||||
|
||||
120-127
|
||||
Key on, channel 1: octave 6, freq 0x2b1
|
||||
Key on, channel 2: octave 6, freq 0x2da
|
||||
Key on, channel 3: octave 6, freq 0x306
|
||||
Key on, channel 4: octave 6, freq 0x334
|
||||
Key on, channel 5: octave 6, freq 0x365
|
||||
Key on, channel 6: octave 6, freq 0x398
|
||||
Key on, channel 7: octave 6, freq 0x3cf ___
|
||||
Key on, channel 8: octave 7, freq 0x204
|
||||
|
26
opl/scale/gen-sequence
Executable file
26
opl/scale/gen-sequence
Executable file
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
for i in 0...128
|
||||
# note on
|
||||
putc 0x0f
|
||||
putc 0x98
|
||||
putc i
|
||||
putc 0x7b
|
||||
|
||||
# delay
|
||||
putc 0x0f
|
||||
|
||||
# note off
|
||||
|
||||
putc 0x88
|
||||
putc i
|
||||
putc 0x00
|
||||
end
|
||||
|
||||
# end of track
|
||||
|
||||
putc 0x00
|
||||
putc 0xff
|
||||
putc 0x2f
|
||||
putc 0x00
|
||||
|
BIN
opl/scale/scale-processed.txt.gz
Normal file
BIN
opl/scale/scale-processed.txt.gz
Normal file
Binary file not shown.
BIN
opl/scale/scale-trace.txt.gz
Normal file
BIN
opl/scale/scale-trace.txt.gz
Normal file
Binary file not shown.
BIN
opl/scale/scale.mid
Normal file
BIN
opl/scale/scale.mid
Normal file
Binary file not shown.
Loading…
Reference in a new issue