11: Sound Programming
Audio bank API
The Commander X16 provides many convenience routines for controlling the YM2151 and VERA PSG. These are called similarly to how KERNAL API calls are done in machine language.
In order to gain access to these routines, you must either use jsrfar
from the KERNAL API:
or switch to ROM bank $0A
directly:
Conveniently, the KERNAL API still exists in this bank, and calling a KERNAL API routine will automatically switch your ROM bank back to the KERNAL bank to perform the routine and then switch back right before returning, so there's usually no need for your audio-centric program to switch away from the audio bank to perform the occasional KERNAL API call.
Audio API routines
For the audio chips, some of the documentation uses the words channel and voice interchangeably. This table of API routines uses channel for the 8 on the YM2151, and voice for the 16 on the PSG.
Label | Address | Class | Description | Inputs | Returns | Preserves |
---|---|---|---|---|---|---|
audio_init |
$C09F |
- | Wrapper routine that calls both psg_init and ym_init followed by ym_loaddefpatches . This is the routine called by the KERNAL at reset. |
none | none | none |
bas_fmchordstring |
$C08D |
BASIC | Starts playing all of notes specified in a string. This uses the same parser as bas_fmplaystring but instead of playing the notes in sequence, it starts playback of each note in the string, on many channels as is necessary, then returns to the caller without delay. The first FM channel that is used is the one specified by calling bas_playstringvoice prior to calling this routine. The string pointer must point to low RAM ($0000 -$9EFF ). |
.A = string length .X .Y = pointer to string |
none | none |
bas_fmfreq |
$C000 |
BASIC | Plays a note specified in Hz on an FM channel | .A = channel .X .Y = 16-bit frequency in Hz c clear = normal c set = no retrigger |
c clear = success c set = error |
none |
bas_fmnote |
$C003 |
BASIC | Plays a note specified in BASIC format on an FM channel | .A = channel .X = note (BASIC format) .Y = fractional semitone c clear = normal c set = no retrigger |
c clear = success c set = error |
none |
bas_fmplaystring |
$C006 |
BASIC | Plays a note script using the FM channel which was specified on a previous call to bas_playstringvoice . This string pointer must point to low RAM ($0000 -$9EFF ). This routine depends on interrupts being enabled. In particular, it uses WAI as a delay for timing, so it expects IRQ to be asserted and acknowledged once per video frame, which is the case by default on the system. Stops playback and returns control if the STOP key is pressed. |
.A = string length .X .Y = pointer to string |
none | none |
bas_fmvib |
$C009 |
BASIC | Sets the LFO speed and both amplitude and frequency depth based on inputs. Also sets the LFO waveform to triangle. | .A = speed .X = PMD/AMD depth | c clear = success c set = error |
none |
bas_playstringvoice |
$C00C |
BASIC | Preparatory routine for bas_fmplaystring and bas_psgplaystring to set the voice/channel number for playback |
.A = PSG/YM voice/channel | none | .A .X |
bas_psgchordstring |
$C090 |
BASIC | Starts playing all of notes specified in a string. This uses the same parser as bas_psgplaystring but instead of playing the notes in sequence, it starts playback of each note in the string, on many voices as is necessary, then returns to the caller without delay. The first PSG voice that is used is the one specified by calling bas_playstringvoice prior to calling this routine. The string pointer must point to low RAM ($0000 -$9EFF ). |
.A = string length .X .Y = pointer to string |
none | none |
bas_psgfreq |
$C00F |
BASIC | Plays a note specified in Hz on a PSG voice | .A = voice .X .Y = 16-bit frequency |
c clear = success c set = error |
none |
bas_psgnote |
$C012 |
BASIC | Plays a note specified in BASIC format on a PSG voice | .A = voice .X = note (BASIC format) .Y = fractional semitone |
c clear = success c set = error |
none |
bas_psgwav |
$C015 |
BASIC | Sets a waveform and duty cycle for a PSG voice | .A = voice .X 0-63 = Pulse, 1/128 - 64/128 duty cycle .X 64-127 = Sawtooth .X 128-191 = Triangle .X 192-255 = Noise |
c clear = success c set = error |
none |
bas_psgplaystring |
$C018 |
BASIC | Plays a note script using the PSG voice which was specified on a previous call to bas_playstringvoice . This string pointer must point to low RAM ($0000 -$9EFF ). This routine depends on interrupts being enabled. In particular, it uses WAI as a delay for timing, so it expects IRQ to be asserted and acknowledged once per video frame, which is the case by default on the system. Stops playback and returns control if the STOP key is pressed. |
.A = string length .X .Y = pointer to string |
none | none |
notecon_bas2fm |
$C01B |
Conversion | Convert a note in BASIC format to a YM2151 KC code | .X = note (BASIC format) | .X = note (YM2151 KC) c clear = success c set = error |
.Y |
notecon_bas2midi |
$C01E |
Conversion | Convert a note in BASIC format to a MIDI note number | .X = note (BASIC format) | .X = MIDI note c clear = success c set = error |
.Y |
notecon_bas2psg |
$C021 |
Conversion | Convert a note in BASIC format to a PSG frequency | .X = note (BASIC format) .Y = fractional semitone |
.X .Y = PSG frequency c clear = success c set = error |
none |
notecon_fm2bas |
$C024 |
Conversion | Convert a note in YM2151 KC format to a note in BASIC format | .X = YM2151 KC | .X = note (BASIC format) c clear = success c set = error |
.Y |
notecon_fm2midi |
$C027 |
Conversion | Convert a note in YM2151 KC format to a MIDI note number | .X = YM2151 KC | .X = MIDI note c clear = success c set = error |
.Y |
notecon_fm2psg |
$C02A |
Conversion | Convert a note in YM2151 KC format to a PSG frequency | .X = YM2151 KC .Y = fractional semitone |
.X .Y = PSG frequency c clear = success c set = error |
none |
notecon_freq2bas |
$C02D |
Conversion | Convert a frequency in Hz to a note in BASIC format and a fractional semitone | .X .Y = 16-bit frequency in Hz | .X = note (BASIC format) .Y = fractional semitone c clear = success c set = error |
none |
notecon_freq2fm |
$C030 |
Conversion | Convert a frequency in Hz to YM2151 KC and a fractional semitone (YM2151 KF) | .X .Y = 16-bit frequency in Hz | .X = YM2151 KC .Y = fractional semitone (YM2151 KF) c clear = success c set = error |
none |
notecon_freq2midi |
$C033 |
Conversion | Convert a frequency in Hz to a MIDI note and a fractional semitone | .X .Y = 16-bit frequency in Hz | .X = MIDI note .Y = fractional semitone c clear = success c set = error |
none |
notecon_freq2psg |
$C036 |
Conversion | Convert a frequency in Hz to a VERA PSG frequency | .X .Y = 16-bit frequency in Hz | .X .Y = 16-bit frequency in VERA PSG format c clear = success c set = error |
none |
notecon_midi2bas |
$C039 |
Conversion | Convert a MIDI note to a note in BASIC format | .X = MIDI note | .X = note (BASIC format) c clear = success c set = error |
.Y |
notecon_midi2fm |
$C03C |
Conversion | Convert a MIDI note to a YM2151 KC. Fractional semitone is unneeded as it is identical to KF already. | .X = MIDI note. | .X = YM2151 KC c clear = success c set = error |
.Y |
notecon_midi2psg |
$C03F |
Conversion | Convert a MIDI note and fractional semitone to a PSG frequency | .X = MIDI note .Y = fractional semitone |
.X .Y = 16-bit frequency in VERA PSG format c clear = success c set = error |
none |
notecon_psg2bas |
$C042 |
Conversion | Convert a frequency in VERA PSG format to a note in BASIC format and a fractional semitone | .X .Y = 16-bit frequency in VERA PSG format | .X = note (BASIC format) .Y = fractional semitone c clear = success c set = error |
none |
notecon_psg2fm |
$C045 |
Conversion | Convert a frequency in VERA PSG format to YM2151 KC and a fractional semitone (YM2151 KF) | .X .Y = 16-bit frequency in VERA PSG format | .X = YM2151 KC .Y = fractional semitone (YM2151 KF) c clear = success c set = error |
none |
notecon_psg2midi |
$C048 |
Conversion | Convert a frequency in VERA PSG format to a MIDI note and a fractional semitone | .X .Y = 16-bit frequency in VERA PSG format | .X = MIDI note .Y = fractional semitone c clear = success c set = error |
none |
psg_getatten |
$C093 |
VERA PSG | Retrieve the attenuation value for a voice previously set by psg_setatten |
.A = voice | .X = attenuation value | .A |
psg_getpan |
$C096 |
VERA PSG | Retrieve the simple panning value that is currently set for a voice. | .A = voice | .X = pan value | .A |
psg_init |
$C04B |
VERA PSG | Initialize the state of the PSG. Silence all voices. Reset the attenuation levels to 0. Set "playstring" defaults including O4 , T120 , S1 , and L4 . Set all PSG voices to the pulse waveform at 50% duty with panning set to both L+R |
none | none | none |
psg_playfreq |
$C04E |
VERA PSG | Turn on a PSG voice at full volume (factoring in attenuation) and set its frequency | .A = voice .X .Y = 16 bit frequency in VERA PSG format |
none | none |
psg_read |
$C051 |
VERA PSG | Read a value from one of the VERA PSG registers. If the selected register is a volume register, return either the cooked value (attenuation applied) or the raw value (as received by psg_write or psg_setvol , or as set by psg_playfreq ) depending on the state of the carry flag |
.X = PSG register address (offset from $1F9C0 ) c clear = if volume, return raw c set = if volume, return cooked |
.A = register value | .X |
psg_setatten |
$C054 |
VERA PSG | Set the attenuation value for a PSG voice. The valid range is from $00 (full volume) to $3F (fully muted). API routines which affect volume will deduct the attenuation value from the intended volume before setting it. Calls to this routine while a note is playing will change the output volume of the voice immediately. This control can be considered a "master volume" for the voice. |
.A = voice .X = attenuation |
none | none |
psg_setfreq |
$C057 |
VERA PSG | Set the frequency of a PSG voice without changing any other attributes of the voice | .A = voice .X .Y = 16 bit frequency in VERA PSG format |
none | none |
psg_setpan |
$C05A |
VERA PSG | Set the simple panning for the voice. A value of 0 will silence the voice entirely until another pan value is set. |
.A = voice .X 0 = none .X 1 = left .X 2 = right .X 3 = both |
none | none |
psg_setvol |
$C05D |
VERA PSG | Set the volume for the voice. The volume that's written to the VERA has attenuation applied. Valid volumes range from $00 to $3F inclusive |
.A = voice .X = volume |
none | none |
psg_write |
$C060 |
VERA PSG | Write a value to one of the VERA PSG registers. If the selected register is a volume register, attenuation will be applied before the value is written to the VERA | .A = value .X = PSG register address (offset from $1F9C0 ) |
none | .A .X |
psg_write_fast |
$C0A2 |
VERA PSG | Same effect as psg_write but does not preserve the state of the VERA CTRL and ADDR registers. It also assumes VERA_CTRL bit 0 is clear, VERA_ADDR0_H = $01 (auto increment 0 recommended), and VERA_ADDR0_M = $F9. This routine is meant for use by sound engines that typically write out multiple PSG registers in a loop. |
.A = value .X = PSG register address (offset from $1F9C0 ) |
none | .A .X |
ym_getatten |
$C099 |
YM2151 | Retrieve the attenuation value for a channel previously set by ym_setatten |
.A = channel | .X = attenuation value | .A |
ym_getpan |
$C09C |
YM2151 | Retrieve the simple panning value that is currently set for a channel. | .A = channel | .X = pan value | .A |
ym_init |
$C063 |
YM2151 | Initialize the state of the YM chip. Silence all channels by setting the release part of the ADSR envelope to max and then setting all channels to released. Reset all attenuation levels to 0. Set "playstring" defaults including O4 , T120 , S1 , and L4 . Set panning for all channels set to both L+R. Reset LFO state. Set all of the other registers to $00 |
none | c clear = success c set = error |
none |
ym_loaddefpatches |
$C066 |
YM2151 | Load a default set of patches into the 8 channels.C0: Piano (0) C1: E. Piano (5) C2: Vibraphone (11) C3: Fretless (35) C4: Violin (40) C5: Trumpet (56) C6: Blown Bottle (76) C7: Fantasia (88) |
none | c clear = success c set = error |
none |
ym_loadpatch |
$C069 |
YM2151 | Load into a channel a patch preset by number (0-161) from the audio bank, or from an arbitrary memory location. High RAM addresses ($A000 -$BFFF ) are accepted in this mode. |
.A = channel c clear = .X .Y = patch address c set = .X = patch number |
c clear = success c set = error |
none |
ym_loadpatchlfn |
$C06C |
YM2151 | Load patch into a channel by way of an open logical file number. This routine will read 26 bytes from the open file, or possibly fewer bytes if there's an error condition. The routine will leave the file open on return. On return if c is set, check .A for the error code. | .A = channel .X = Logical File Number |
c clear = success c set .A=0 = YM error c set .A&3=2 = read timeout c set .A&3=3 = file not open c set .A&64=64 = EOF c set .A&128=128 = device not present |
none |
ym_playdrum |
$C06F |
YM2151 | Load a patch associated with a MIDI drum note number and trigger it on a channel. Valid drum note numbers mirror the General MIDI percussion standard and range from 25 (Snare Roll) through 87 (Open Surdo). Note 0 will release the note. After the drum is played, the channel will still contain the patch for the drum sound and thus may not sound musical if you attempt to play notes on it before loading another instrument patch. | .A = channel .X = drum note |
c clear = success c set = error |
none |
ym_playnote |
$C072 |
YM2151 | Set a KC/KF on a channel and optionally trigger it. | .A = channel .X = KC .Y = KF (fractional semitone) c clear = trigger c set = no trigger |
c clear = success c set = error |
none |
ym_setatten |
$C075 |
YM2151 | Set the attenuation value for a channel. The valid range is from $00 (full volume) to $7F (fully muted). API routines which affect TL or CON will add the attenuation value to the intended TL on operators that are carriers before setting it. Calls to this routine will change the TL of the channel's carriers immediately. This control can be considered a "master volume" for the channel. |
.A = channel .X = attenuation |
c clear = success c set = error |
.A .X |
ym_setdrum |
$C078 |
YM2151 | Load a patch associated with a MIDI drum note number and set the KC/KF for it on a channel. Called by ym_playdrum . |
.A = channel .X = drum note |
c clear = success c set = error |
none |
ym_setnote |
$C07B |
YM2151 | Set a KC/KF on a channel. Called by ym_playnote . |
.A = channel .X = KC .Y = KF (fractional semitone) |
c clear = success c set = error |
none |
ym_setpan |
$C07E |
YM2151 | Set the simple panning for the channel. A value of 0 will silence the channel entirely until another pan value is set. |
.A = channel .X 0 = none .X 1 = left .X 2 = right .X 3 = both |
c clear = success c set = error |
none |
ym_read |
$C081 |
YM2151 | Read a value from the in-RAM shadow of one of the YM2151 registers. The YM2151's internal registers cannot be read from, but this API keeps state of what was written, so this routine will be able to retrieve chip values for you. If the selected register is a TL register, return either the cooked value (attenuation applied) or the raw value (as received by ym_write ) depending on the state of the carry flag |
.X = YM2151 register address c clear = if TL, return raw c set = if TL, return cooked |
.A = register value c clear = success c set = error |
.X |
ym_release |
$C084 |
YM2151 | Release a note on a channel. If a note is not playing, this routine has no tangible effect | .A = channel | c clear = success c set = error |
none |
ym_trigger |
$C087 |
YM2151 | Trigger the currently configured note on a channel, optionally releasing the channel first depending on the state of the carry flag. | .A = channel c clear = release first c set = no release |
c clear = success c set = error |
none |
ym_write |
$C08A |
YM2151 | Write a value to one of the YM2151 registers and to the in-RAM shadow copy. If the selected register is a TL register, attenuation will be applied before the value is written. Writes which affect which operators are carriers will have TL values for that channel appropriately recalculated and rewritten | .A = value .X = YM register address |
c clear = success c set = error |
.A .X |
A note on semitones (get it?)
It may be advantageous to consider storing note data internally as the MIDI representation with a fractional component if you want things like pitch slides to behave the same way between the PSG and YM2151.
Essentially, it can be thought of as an 8.8 fixed point 16-bit number.
The YM2151 handles semitones differently than the PSG and requires converting
the MIDI note to the appropriate KC value using notecon_midi2fm
. KF is the
fractional semitone (albeit with only the top 6-bits used)
and requires no conversion.
The PSG, by contrast, operates with linear pitch which is why
notecon_midi2psg
also takes the fractional component (y) as input.
Thus, if you manage all your pitch slides using MIDI notes along with a fractional component, you can then convert this directly over to PSG or YM2151 as required and end up with the same pitch (or close enough to it).
Direct communication with the YM2151 and VERA PSG vs API
Use of the API routines above is not required to access the capabilities of the sound chips. However, mixing raw writes to a chip and API access for the same chip is not recommended, particularly where PSG volumes and YM2151 TL and RLFBCON registers are concerned. The API processes volumes, calculating attenuation and adjusting the output volume accordingly, and the API will be oblivious to direct manipulation of the sound chips.
The sections below describe how to do raw access to the sound chips outside of the API.
VERA PSG and PCM Programming
- For VERA PSG and PCM, refer to Chapter 9.
YM2151 (OPM) FM Synthesis
The Yamaha YM2151 (OPM) sound chip is an FM synthesizer ASIC in the Commander X16.
It is connected to the system bus at I/O address 0x9F40
(address register) and at 0x9F41
(data register). It has 8 independent voices with 4 FM operators each. Each
voice is capable of left/right/both audio channel output. The four operators of each channel may be connected in one of 8 pre-defined "connection algorithms" in order to produce a wide variety of timbres.
YM2151 Communication
There are 3 basic operations to communicate with the YM chip: Reading its status, address select, and data write. These are performed by reading from or writing to one of the two I/O addresses as follows:
Address | Name | Read Action | Write Action |
---|---|---|---|
0x9F40 | YM_address |
Undefined (returns ?) | Selects the internal register address where data is written. |
0x9F41 | YM_data |
Returns the YM_status byte |
Writes the value into the currently-selected internal address. |
The values stored in the YM's internal registers are write-only. If you need to know the values in the registers, you must store a copy of the values somewhere in memory as you write updates to the YM.
YM Write Procedure
- Ensure YM is not busy (see Write Timing below).
- Select the desired internal register address by writing it into
YM_address
. - Write the new value for this register into
YM_data
.
Note: You may write into the same register multiple times without repeating a write to YM_address
. The same register will be updated with each data write.
Write Timing
The YM2151 is sensitive to the speed at which you write data into it. If you make writes when it is not ready to receive them, they will be dropped and the sound output will be corrupted.
You must include a delay between writes to the address select register ($9F40) and the subsequent data write. 10 CPU cycles is the recommended minimum delay.
The YM becomes BUSY
for approximately 150 CPU cycles' (at 8Mhz) whenever it receives a data write. Any writes into YM_data during this BUSY
period will be ignored!
In order to avoid this, you can use the BUSY
flag which is bit 7 of the YM status
byte. Read the status byte from YM_data
(0x9F41). If the top bit (7) is set, the YM may not be written into at this time. Note that it is not required that you read YM_status
, only that writes occur no less than ~150 CPU cycles apart. For instance, BASIC executes slowly enough that you are in no danger of writing into the YM too quickly, so BASIC programs may skip checking YM_status
.
Lastly, the BUSY
flag sometimes takes a (very) short period before it goes high. This has only been observed when IMMEDIATELY polling the flag after a write into YM_data.
As long as your code does not do so, this quirk should not be an issue.
Example Code
Assembly Language:
check_busy:
BIT YM_data ; check busy flag
BMI check_busy ; wait until busy flag is clear
LDA #$08 ; Select YM register $08 (Key-Off/On)
STA YM_addr ;
NOP ;<-+
NOP ; |
NOP ; +--slight pause before writing data
NOP ; |
NOP ;<-+
LDA #$04 ; Write $04 (Release note on channel 4).
STA YM_data
RTS
BASIC:
10 YA=$9F40 : REM YM_ADDRESS
20 YD=$9F41 : REM YM_DATA
30 POKE YA,$29 : REM CHANNEL 1 NOTE SELECT
40 POKE YD,$4A : REM SET NOTE = CONCERT A
50 POKE YA,$08 : REM SELECT THE KEY ON/OFF REGISTER
60 POKE YD,$00+1 : REM RELEASE ANY NOTE ALREADY PLAYING ON CHANNEL 1
70 POKE YD,$78+1 : REM KEY-ON VOICE 1 TO PLAY THE NOTE
80 FOR I=1 TO 100 : NEXT I : REM DELAY WHILE NOTE PLAYS
90 POKE YD,$00+1 : REM RELEASE THE NOTE
YM2151 Internal Addressing
The YM register address space can be thought of as being divided into 3 ranges:
Range | Type | Description |
---|---|---|
00 .. 1F | Global Values | Affect individual global parameters such as LFO frequency, noise enable, etc. |
20 .. 3F | Channel CFG | Parameters in groups of 8, one per channel. These affect the whole channel. |
40 .. FF | Operator CFG | Parameters in groups of 32 - these map to individual operators of each voice. |
YM2151 Register Map
Global Settings
Addr | Register | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Description |
---|---|---|---|---|---|---|---|---|---|---|
$01 | Test Register | ! | ! | ! | ! | ! | ! | LR | ! |
Bit 1 is the LFO reset bit. Setting it disables the LFO and holds the oscillator at 0. Clearing it enables the LFO. All other bits control various test functions and should not be written into. |
$08 | Key Control | . | C2 | M2 | C1 | M1 | CHA |
Starts and Releases notes on the 8 channels. Setting/Clearing bits for M1,C1,M2,C2 controls the key state for those operators on channel CHA. NOTE: The operator order is different than the order they appear in the Operator configuration registers! |
||
$0F | Noise Control | NE | . | . | NFRQ |
NE = Noise Enable NFRQ = Noise Frequency When eabled, C2 of channel 7 will use a noise waveform instead of a sine waveform. |
||||
$10 | Ta High | CLKA1 | Top 8 bits of Timer A period setting | |||||||
$11 | Ta Low | . | . | . | . | . | . | CLKA2 | Bottom 2 bits of Timer A period setting | |
$12 | Timer B | CLKB | Timer B period setting | |||||||
$14 | IRQ Control | CSM | . | Clock ACK | IRQ EN | Clock Start |
CSM: When a timer expires, trigger note key-on for all channels. For the other 3 fields, lower bit = Timer A, upper bit = Timer B. Clock ACK: clears the timer's bit in the YM_status byte and acknowledges the IRQ. |
|||
$18 | LFO Freq. | LFRQ | Sets LFO frequency. $00 = ~0.008Hz $FF = ~32.6Hz |
|||||||
$19 | LFO Amplitude | 0 | AMD |
AMD = Amplitude Modulation Depth PMD = Phase Modulation (vibrato) Depth Bit 7 determines which parameter is being set when writing into this register. |
||||||
1 | PMD | |||||||||
$1B | CT / LFO Waveform | CT | . | . | . | . | W |
CT: sets output pins CT1 and CT1 high or low. (not connected to anything in X16) W: LFO Waveform: 0-4 = Saw, Square, Triange, Noise For sawtooth: PM->//// AM->\\\\ |
Channel CFG Registers
Register Range | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Description |
---|---|---|---|---|---|---|---|---|---|
$20 + channel | RL | FB | CON |
|
|||||
$28 + channel | . | KC | |||||||
$30 + channel | KF | . | . | ||||||
$38 + channel | . | PMS | . | . | AMS |
Operator CFG Registers
Register Range | Operator | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Description |
---|---|---|---|---|---|---|---|---|---|---|
$40 | M1: $40+channel | . | DT1 | MUL |
|
|||||
M2: $48+channel | ||||||||||
C1: $50+channel | ||||||||||
C2: $58+channel | ||||||||||
$60 | M1: $60+channel | . | TL |
|
||||||
M2: $68+channel | ||||||||||
C1: $70+channel | ||||||||||
C2: $78+channel | ||||||||||
$80 | M1: $80+channel | KS | . | AR |
|
|||||
M2: $88+channel | ||||||||||
C1: $90+channel | ||||||||||
C2: $98+channel | ||||||||||
$A0 | M1: $A0+channel | A M E n a |
. | . | D1R |
|
||||
M2: $A8+channel | ||||||||||
C1: $B0+channel | ||||||||||
C2: $B8+channel | ||||||||||
$C0 | M1: $C0+channel | DT2 | . | D2R |
|
|||||
M2: $C8+channel | ||||||||||
C1: $D0+channel | ||||||||||
C2: $D8+channel | ||||||||||
$E0 | M1: $E0+channel | D1L | RR |
|
||||||
M2: $E8+channel | ||||||||||
C1: $F0+channel | ||||||||||
C2: $F8+channel |
YM2151 Register Details
Global Parameters
LR (LFO Reset)
Register $01, bit 1
Setting this bit will disable the LFO and hold it at level 0. Clearing this bit allows the LFO to operate as normal. (See LFRQ for further info)
KON (KeyON)
Register $08
-
Bits 0-2: Channel_Number
-
Bits 3-6: Operator M1, C1, M2, C2 control bits:
- 0: Releases note on operator
- 0->1: Triggers note attack on operator
- 1->1: No effect
Use this register to start/stop notes. Typically, all 4 operators are triggered/released together at once. Writing a value of $78+channel_number will start a note on all 4 OPs, and writing a value of $00+channel_number will stop a note on all 4 OPs.
NE (Noise Enable)
Register $0F, Bit 7
When set, the C2 operator of channel 7 will use a noise waveform instead of a sine.
NFRQ (Noise Frequency)
Register $0F, Bits 0-4
Sets the noise frequency, $00 is the lowest and $1F is the highest. NE bit must be set in order for this to have any effect. Only affects operator C2 on channel 7.
CLKA1 (Clock A, high order bits)
Register $10, Bits 0-7
This is the high-order value for Clock A (a 10-bit value).
CLKA2 (Clock A, low order bits)
Register $11, Bits 0-1
Sets the 2 low-order bits for Clock A (a 10-bit value).
Timer A's period is Computed as (64*(1024-ClkA)) / PhiM ms. (PhiM = 3579.545Khz)
CLKB (Clock B)
Register $12, Bits 0-7
Sets the Clock B period. The period for Timer B is computed as (1024*(256-CLKB)) / PhiM ms. (PhiM = 3579.545Khz)
CSM
Register $14, Bit 7
When set, the YM2151 will generate a KeyON attack on all 8 channels whenever TimerA overflows.
Clock ACK
Register $14, Bits 4-5
Clear (acknowledge) IRQ status generated by TimerA and TimerB (respectively).
IRQ EN
Register $14, Bits 2-3
When set, enables IRQ generation when TimerA or TimerB (respectively) overflow. The IRQ status of the two timers is checked by reading from the YM2151_STATUS byte. Bit 0 = Timer A IRQ status, and Bit 1 = Timer B IRQ status. Note that these status bits are only active if the timer has overflowed AND has its IRQ_EN bit set.
Clock Start
Register $14, Bits 0-1
When set, these bits clear the TimerA and TimerB (respectively) counters and starts it running.
LFRQ (LFO Frequency)
Register $18, Bits 0-7
Sets the LFO frequency.
- $00 = ~0.008Hz
- $FF = ~32.6Hz
Note that even setting the value zero here results in a positive LFO frequency. Any channels sensitive to the LFO will still be affected by the LFO unless the LR
bit is set in register $01 to completely disable it.
AMD (Amplitude Modulation Depth)
Register $19 Bits 0-6, Bit 7 clearParameters
Sets the peak strength of the LFO's Amplitude Modulation effect. Note that bit 7 of the value written into $19 must be clear in order to set the AMD. If bit 7 is set, the write will be interpreted as PMD.
PMD (Phase Modulation Depth)
Register $19 Bits 0-6, Bit 7 set
Sets the peak strength of the LFO's Phase Modulation effect. Note that bit 7 of the value written into $19 must be set in order to set the PMD. If bit 7 is clear, the value is interpreted as AMD.
CT (Control pins)
Register $1B, Bits 6-7
These bits set the electrical state of the two CT pins to on/off. These pins are not connected to anything in the X16 and have no effect.
W (LFO Waveform)
Register $1B, Bits 0-1
Sets the LFO waveform: 0: Sawtooth, 1: Square (50% duty cycle), 2: Triangle, 3: Noise
Channel Control Parameters
RL (Right/Left output enable)
Register $20 (+ channel), Bits 6-7
Setting/Clearing these bits enables/disables audio output for the selected channel. (bit6=left, bit7=right)
FB (M1 Self-Feedback)
Register $20 (+ channel), bits 3-5
Sets the amount of self feedback on operator M1 for the selected channel. 0=none, 7=max
CON (Connection Algorithm)
Register $20 (+ channel), bits 0-2
Sets the selected channel to connect the 4 operators in one of 8 arrangements.
[insert picture here]
KC (Key Code - Note selection)
Register $28 + channel, bits 0-6
Sets the octave and semitone for the selected channel. Bits 4-6 specify the octave (0-7) and bits 0-3 specify the semitone:
0 | 1 | 2 | 4 | 5 | 6 | 8 | 9 | A | C | D | E |
---|---|---|---|---|---|---|---|---|---|---|---|
C♯ | D | D♯ | E | F | F♯ | G | G♯ | A | A♯ | B | C |
Note that natural C is at the TOP of the selected octave, and that each 4th value is skipped. Thus if concert A (A-4, 440hz) is KC=$4A, then middle C is KC=$3E
KF (Key Fraction)
Register $30 + channel, Bits 2-7
Raises the pitch by 1/64th of a semitone * the KF value.
PMS (Phase Modulation Sensitivity)
Register $38 + channel, Bits 4-6
Sets the Phase Modulation (vibrato) sensitivity of the selected channel. The resulting vibrato depth is determined by the combination of the global PMD setting (see above) modified by each channel's PMS.
Sensitivity values: (+/- cents)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
0 | 5 | 10 | 20 | 50 | 100 | 400 | 700 |
AMS (Amplitude Modulation Sensitivity)
Register $38 + channel, Bits 0-1
Sets the Amplitude Modulation sensitivity of the selected channel. Note that each operator may individually enable or disable this effect on its output by setting/clearing the AMS-Ena bit (see below). Operators acting as outputs will exhibit a tremolo effect (varying volume) and operators acting as modulators will vary their effectiveness on the timbre when enabled for amplitude modulation.
Sensitivity values: (dB)
0 | 1 | 2 | 3 |
---|---|---|---|
0 | 23.90625 | 47.8125 | 95.625 |
Operator Control Parameters
Operators are arranged as follows:
name | M1 | M2 | C1 | C2 |
---|---|---|---|---|
index | 0 | 1 | 2 | 3 |
These are the names used throughout this document for consistency, but they may function as either modulators or carriers, depending on which CON
ALG is used.
The Operator Control parameters are mapped to channels/operators as follows: Register + 8*op + channel. You may also choose to think of these register addresses as using bits 0-2 = channel, bits 3-4 = operator, and bits 5-7 = parameter. This reference will refer to them using the address range, e.g. $60-$7F = TL. To set TL for channel 2, operator 1, the register address would be $6A ($60 + 1*8 + 2).
DT1 (Detune 1 - fine detune)
Registers $40-$5F, Bits 4-6
Detunes the operator from the channel's main pitch. Values 0 and 4=no detuning.
Values 1-3=detune up, 5-7 = detune down.
The amount of detuning varies with pitch. It decreases as the channel's pitch increases.
MUL (Frequency Multiplier)
Registers $40-$5F, Bits 0-3
If MUL=0, it multiplies the operator's frequency by 0.5
Otherwise, the frequency is multiplied by the value in MUL (1,2,3...etc)
TL (Total Level - attenuation)
Registers $60-$7F, Bits 0-6
This is essentially "volume control" - It is an attenuation value, so $00 = maximum level and $7F is minimum level. On output operators, this is the volume output by that operator. On modulating operators, this affects the amount of modulation done to other operators.
KS (Key Scaling)
Registers $80-$9F, Bits 6-7
Controls the speed of the ADSR progression. The KS value sets four different levels of scaling. Key scaling increases along with the pitch set in KC. 0=min, 3=max
AR (Attack Rate)
Registers $80-$9F, Bits 0-4
Sets the attack rate of the ADSR envelope. 0=slowest, $1F=fastest
AMS-Enable (Amplitude Modulation Sensitivity Enable)
Registers $A0-$BF, Bit 7
If set, the operator's output level will be affected by the LFO according to the channel's AMS setting. If clear, the operator will not be affected.
D1R (Decay Rate 1)
Registers $A0-$BF, Bits 0-4
Controls the rate at which the level falls from peak down to the sustain level (D1L). 0=none, $1F=fastest.
DT2 (Detune 2 - coarse)
Registers $C0-$DF, Bits 6-7
Sets a strong detune amount to the operator's frequency. Yamaha suggests that this is most useful for sound effects. 0=off,
D2R (Decay Rate 2)
Registers $C0-$DF, Bits 0-4
Sets the Decay2 rate, which takes effect once the level has fallen from peak down to the sustain level (D1L). This rate continues until the level reaches zero or until the note is released.
0=none, $1F=fastest
D1L
Registers $E0-$FF, Bits 4-7
Sets the level at which the ADSR envelope changes decay rates from D1R to D2R. 0=minimum (no D2R), $0F=maximum (immediately at peak, which effectively disables D1R)
RR
Registers $E0-$FF, Bitst 0-3
Sets the rate at which the level drops to zero when a note is released. 0=none, $0F=fastest
Getting sound out of the YM2151 (a brief tutorial)
While there is a large number of parameters that affect the sound of the YM2151, its operation can be thought of in simplified terms if you consider that there are basically three components to deal with: Instrument configuration (patch), voice pitch selection, and "pressing/releasing" the "key" to trigger (begin) and release (end) notes. It's essentially the same as using a music keyboard. Pressing an instrument button (e.g. Marimba) makes the keyboard sound like a Marimba. Once this is done, you press a key on the keyboard to play a note, and release it to stop the note. With the YM, loading a patch (pressing the Marimba button) entails setting all of the various operators' registers on the voice(s) you want the instrument to be used on. On the music keyboard, pitch and note stop/start are done with a single piano key. In the YM2151, these are two distinct actions.
For this tutorial, we will start with the simplest operation, (triggering notes) and proceed to note selection, and finally patch configuration.
Triggering and Releasing Notes
Key On/Off (KON) Register ($08):
This is probably the most important single register in the YM2151. It is used to trigger and release notes. It controls the key on/off state for all 8 channels. A note is triggered whenever its key state changes from off to on, and is released whenever the state changes from on to off. Repeated writes of the same state (off->off or on->on) have no effect.
Whenever an operator is triggered, it progresses through the states of attack, decay1, and sustain/decay2. Whenever an active note is released, it enters the release state where the volume decreases until reaching zero. It then remains silent until the next time the operator is triggered. If you are familiar with the C64 SID chip, this is the same behavior as the "gate" bit on that chip.
Key state and voice selection are both contained in the value written into the KON register as follows:
- Key ON = $78 + channel number
- Key OFF = $0 + channel number
Simple Examples:
To release the note in channel 4: write $08 to YM_address
($9F40) and then write $04 ($00+4) to YM_data
($9F41).
To begin a note on channel 7, write $08 into YM_address
to select the KON register. Then write $7F ($78+7) into YM_data
If the current key state of a channel is not known, you can write key off and then key on immediately (after waiting for the YM busy period to end, of course):
POKE $9F40,$08 : REM SELECT KEY ON/OFF REGISTER
POKE $9F41,$07 : REM KEY OFF FOR VOICE 7
POKE $9F41,$7F : REM KEY ON FOR VOICE 7
Remember: BASIC is slow enough that you do not need to poll the YM_status
byte, but assembly and other languages will need to do so.
The ADSR parameters will be discussed in more detail later.
Advanced:
Each channel (voice) of the YM2151 uses 4 operators which can be gated together or independently. Independent triggering gives lots of advanced possibilities. To trigger and release operators independently, you use different values than $78 or $00. These values are composed by 4 bits which signal the on/off state for each operator.
Suppose a note is playing on channel 2 with all 4 operators active. You can release only the M1 operator by writing $72 into register $08.
The KON value format:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
- | C2 | M2 | C1 | M1 | Channel |
Pitch Control
YM Registers
KC
= $28 + channel numberKF
= $30 + channel number
For note selection, each voice has two parameters: KC
(Key Code) and KF
(Key Fraction).
These are set in register ranges $28 and $30, respectively. The KC codes correspond
directly to the notes of the chromatic scale. Each value maps to a specific octave & semitone. The KF
value can even be ignored
for basic musical playback. It is mostly useful for vibrato or pitch bend effects. KF
raises
the pitch selected in KC
in 1/64th increments of the way up to the next semitone.
Like all registers in the YM, whenever a channel's KC
or KF
value is written, it takes effect immediately. If a note is playing, its pitch immediately changes. When triggering new notes, it is not important whether you write the pitch or key the note first. This happens quickly in real-time and you will not hear any real difference. Changing the pitch without re-triggering the ADSR envelope is how to achieve pitch slides or a legato effect.
Key Code (KC)
KC
codes are "conveniently" arranged so that the upper nybble is the octave (0-7) and the
lower nybble is the pitch. The pitches are arranged as follows within an octave:
Note | C♯ | D | D♯ | E | F | F♯ | G | G♯ | A | A♯ | B | C |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Low Nybble (hex) | 0 | 1 | 2 | 4 | 5 | 6 | 8 | 9 | A | C | D | E |
(Note that every 4th value is skipped.)
Combine the above with an octave to get a note's KC
value. For instance: concert A (440hz) is (by sheer coincidence) $4A
. Middle C is $3E
, and so forth.
Key Fraction (KF)
KF
values are written into the top 6 bits of the voice's KF
register. Basically the value is 0, 1<<2, 2<<2, .. 63<<2
Loading a patch
The patch configuration is by far the most complicated aspect of using the YM. If you take as given that a voice has a patch loaded, then playing notes on it is fairly straightforward. For the moment, we will assume a pre-patched voice.
To get started quickly, here is some BASIC code to patch voice 0 with a marimba tone:
5 YA=$9F40 : YD=$9F41 : V=0
10 REM: MARIMBA PATCH FOR YM VOICE 0 (SET V=0..7 FOR OTHER VOICES)
20 DATA $DC,$00,$1B,$67,$61,$31,$21,$17,$1F,$0A,$DF,$5F,$DE
30 DATA $DE,$0E,$10,$09,$07,$00,$05,$07,$04,$FF,$A0,$16,$17
40 READ D
50 POKE YA,$20+V : POKE YD,D
60 FOR A=$38 TO $F8 STEP 8
70 READ D : POKE YA,A+V : POKE YD,D
80 NEXT A
Once a voice has been patched as above, you can now POKE notes into it with very few commands for each note.
Patches consist mostly of ADSR envelope parameters. A complete patch contains values for the $20 range register (LR|FB|CON), for the $38 range register (AMS|PMS), and 4 values for each of the parameter ranges starting at $40. (4 operators per voice means 4 values per parameter). Since this is a huge amount of flexibility, it is recommended to experiment with instrument creation in an application such as a chip tracker or VST, as the creative process of instrument design is very hands-on and subjective.
Using the LFO
There is a single global LFO in the YM2151 which can affect the level (volume) and/or pitch of all 8 channels simultaneously. It has a single frequency and waveform setting which must be shared among all channels, and shared between both phase and amplitude modulation. The global parameters AMD
and PMD
act as modifiers to the sensitivity settings of the channels. While the frequency and waveform of the LFO pattern must be shared, the depths of the two types of modulation are independent of each other.
You can re-trigger the LFO by setting and then clearing the LR
bit in the test register ($01).
Vibrato
Use Phase Modulation on the desired channels. The PMS
parameter for each channel allows them to vary their vibrato depths individually. Channels with PMS
set to zero will have no vibrato. The values given earlier in the PMS
parameter description represent their maximum amount of affect. These values are modified by the global PMD.
A PMD
valie of $7F means 100% effectiveness, $40 means all channels' vibrato depths will be reduced by half, etc.
The vibrato speed is global, depending solely on the value set to LFRQ.
Amplitude Modulation
Amplitude modulation works similarly to phase modulation, except that the intensity is a combination of the per-channel AMS
value modified by the global AMD
value. Additionally, within channels having non-zero amplitude modulation sensitivity, individual operators must have their AMS-en
bit enabled in order to be affected by the modulation.
If the active operators are acting as carriers (generating output directly), then amplitude modulation will vary the volume of the sound being produced by that operator. This can be described as a "tremolo" effect. If the operators are acting as modulators, then the timbre of the voice will vary as the output level of the affected operators increases and decreases. You may simultaneously enable amplitude modulation on both types of operators.
The amplitude modulation speed is global, depending solely on the value set to LFRQ.