Add OPL research data to version control.

Subversion-branch: /research
Subversion-revision: 1480
This commit is contained in:
Simon Howard 2009-03-15 19:43:27 +00:00
commit 58512f18d4
28 changed files with 1508 additions and 0 deletions

17
.gitignore vendored Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
Patch to DOSBox to trace the register writes to the OPL chip while running
Doom.

View 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);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

376
opl/dosbox-trace/opl-parser Executable file
View 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
View file

@ -0,0 +1,2 @@
Test program for playing back DOSBox Raw OPL files.

BIN
opl/droplay/doom_000.dro Normal file

Binary file not shown.

110
opl/droplay/droplay.c Normal file
View 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
View 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

Binary file not shown.

202
opl/genmidi/genmidi-dumper Executable file
View 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

Binary file not shown.

BIN
opl/genmidi/genmidi.lmp Normal file

Binary file not shown.

BIN
opl/genmidi/std.o3 Normal file

Binary file not shown.

2
opl/oplinit/README Normal file
View file

@ -0,0 +1,2 @@
Linux test program to initialise the OPL chip and play a tone.

82
opl/oplinit/oplinit.c Normal file
View 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
View 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

Binary file not shown.

156
opl/scale/freqs.txt Normal file
View 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
View 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

Binary file not shown.

Binary file not shown.

BIN
opl/scale/scale.mid Normal file

Binary file not shown.