mirror of
https://github.com/chocolate-doom/research.git
synced 2024-11-25 05:21:15 +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