AMPS v2.0 technical manual
1 — Introduction
AMPS is a 68k-based, SMPS-like sound driver, designed to be used with ROM hacks and Sega Mega Drive software. Although similar to SMPS, AMPS is fundamentally different in many aspects. It is also heavily customized, RAM-efficient and cycle-efficient. AMPS is additionally powered by Dual PCM FlexEd, meaning that you can use 2 DAC sample channels with pitch and volume control. AMPS was designed to be a well-documented driver with a lot of capabilities so that the music created for the sound driver can sound better, and the program and music can interact with each other. To ensure this, AMPS has a very robust codebase built to allow future expansion and customized programming for program-specific circumstances.
AMPS is open source and free to use, given that you follow the license. You can easily make your own modifications, do feature requests, and make some great music. Currently, there is a limited number of options to create music, but using tools designed for SMPS sound drivers, you can convert the files into AMPS format. More on that in part 11.
This documentation is very comprehensive and detailed, so rather than trying to read it all at once, it is recommended to focus on the most relevant parts to you at this moment. The documentation links to other parts of the documentation to help provide additional details and context for everything that is being discussed. It also contains links to external sites for their usefulness factor and embeds various tools to help with various aspects regarding AMPS. Although the documentation may seem daunting, it is broken down into reasonable chunks so you can focus on what you need to know at any time. Although I've done my best to make the documentation accurate, it is possible there are problems here and there. If you see something clearly wrong, please let us know.
1.1 — Undefined Behaviour
This document will talk a lot about undefined behaviour. But what is undefined behaviour? Simply, undefined behaviour is things that you can do from a technical sense, and there is nothing stopping you, but it is not intended to be done and therefore the result can be whatever. It can change, or stay the same, depend on something, and change between versions. While it is possible to do, you really shouldn't. It is never guaranteed to work the way you expect, want, or desire. Furthermore, trying to support someone's preferences of things that weren't ever supposed to exist is not possible, and I won't waste my time trying. In the future, safe mode will be more careful about checking for undefined behaviour, but right now often that is not the case.
2 — AMPS Overview
AMPS is based on sound data files, perhaps erroneously referred to as tracker files. These files define each music and sound effect you may hear in a game or a program. They do not specify samples themselves, as those are handled by a different system. They are the backbone of AMPS and without them no sound would be generated. Tracker files tell AMPS what notes to play, how long to play them, and can also tell AMPS about other aspects of how hardware should act. For example, you can manipulate YM2612 registers or tell Dual PCM how to play samples. Additionally, AMPS has a long list of built-in features that help the programmer to make cool effects without needing to have in-depth programming ability.
AMPS includes a number of customizable flags, that allow the programmer to decide which features they are going to need for their project. The more flags are enabled, generally, the more system RAM and processor cycles are required to execute AMPS, though there are also exceptions. Here is a list of currently available flags:
- FEATURE_SAFE_PSGFREQ — When enabled, the PSG frequency behaviour is made safer. When disabled, it is possible that a PSG frequency overflows and messes with other PSG channels. This behaviour, for whatever reason, is required to make the insta-shield sound effect from Sonic 3 & Knuckles work correctly.
- FEATURE_SFX_MASTERVOL — When enabled, sound effect channels will be affected by master volume.
- FEATURE_MODULATION — When enabled, software modulation can be used. This allows he programmer to define a linear change for frequency over time, which can also go back and forth between rising and falling.
- FEATURE_PORTAMENTO — When enabled, the portamento command can be used. Portamento makes the notes smoothly change over specified number of frames.
- FEATURE_MODENV — When enabled, modulation envelopes can be used. Similarly to volume envelopes (also known as PSG instruments or PSG tones), they can be used to create more complex patterns for changing channel frequency. Every frame a value is read from a list specified by the modulation envelope ID, and that is used to calculate the frequency displacement.
- FEATURE_DACFMVOLENV — When enabled, volume envelopes can be used in DAC and FM channels along with PSG channels. They can be used to create more complex patterns for changing channel volume. Every frame a value is read from a list specified by the volume envelope ID, which is then used to calculate the volume displacement.
- FEATURE_UNDERWATER — Allows, underwater mode to be enabled. This makes it so that the program can request underwater mode to be activated, and when it is, all FM voices will sound slightly muffled. This is intended to emulate how music would sound if it were played underwater. Although the effect is not perfect, it creates an additional "depth" to how your program will sound when implemented right.
- FEATURE_BACKUP — When enabled, music can be "backed up" so it can be later started from the same place. This is useful if you have a temporary music you want to play for a small moment, but don't want the previous music to start from the beginning again. In Sonic games this is commonly used for the 1-UP sound. The one big downside of this flag is that it requires nearly double the amount of RAM as the driver regularly needs.
- FEATURE_BACKUPNOSFX — When enabled, this prevents sound effects from playing when a song is backed up.
- FEATURE_FM6 — When enabled, FM6 can be used as a music channel in addition to DAC1 and DAC2 as sample channels. Because FM6 and DAC cannot play at the same time, whenever any DAC is playing in any channel, FM6 is temporarily disabled. FM6 keeps playing as normal in the background, but no sound is output. This feature is useful when you want to use FM6 for any song, but still want to have access to DAC channels too.
- FEATURE_SOUNDTEST — When enabled, additional channel flags are present that are updated with the latest frequency and volume sent to hardware. Also, few other differences exist to make integration with sound test engines easier.
AMPS also uses additional data, such as volume and modulation envelope data, sample tables, and other miscellaneous include files. This data is all defined in the code/Data.asm file, which is the file you will be editing most. Additionally, AMPS Includer.exe is used to pre-process this file so it works correctly in both ASM68K and AS. There are also a number of customizable things, such as volume fades, frequency tables, and PCM volume filters.
3 — Sound System
AMPS uses sound IDs to differentiate each sound. There can be at most 255 sounds (from $01 to $FF), and they are split into 3 main groups: Commands, music, and sound effects. All of these have different behaviors, for a very specific reason: sound effect channels override music channels, due to a lack of hardware channels. Effectively, sound effects have to correctly inform music that a channel is in use, and therefore has to be differentiated. By default, commands come first, then music, and finally sound effects. There is no specific reason for this, other than making the organization easier. Below is a list of sound commands:
- mus_Reset — Resets various things about AMPS: the underwater mode, speed shoes mode, and any backed-up song status.
- mus_FadeOut — Fades out the current song.
- mus_Stop — Stops the current song and sound effects immediately. Also clears sound driver memory and mutes hardware.
- mus_ShoesOn — Enables the speed shoes mode. This changes the tempo from using normal tempo, to the speed up tempo defined for the song.
- mus_ShoesOff — Disables the speed shoes mode. This changes the tempo from using speed up tempo, to the normal tempo defined for the song.
- mus_ToWater — Enables underwater mode.
- mus_OutWater — Disables underwater mode.
- mus_Pause — Pauses the execution of AMPS and mutes hardware. AMPS will ignore everything except sound commands at this point.
- mus_Unpause — Unpauses the execution of AMPS. Some notes will not play for a moment after the unpause, due to limitations with the hardware.
- mus_StopSFX — Safely stops every sound effect. This will correctly release all hardware channels back to music.
AMPS uses a sound queue for playing different sounds. The sound queue has 3 bytes and they can be used for any sound. mQueue is the equate name for this RAM space, and slots can be referred by mQueue, mQueue+1 and mQueue+2. Only a single slot can be loaded per frame, and the earlier slots have priority over later slots. It is recommended that sound commands go to slot 0, music in slot 1 and sound effects in slot 2, though the programmer may choose to configure this differently depending on their needs. When there is no sound queued, the slots will be set to $00. This can be used to check if a slot already contains a queued sound.
3.1 — Music Files
Music is mainly used as background music or as a jingle. Generally, whenever you want to make something that can be overridden by sound effects or be the main focus, music is a good choice. Music has access to all the features in AMPS and is generally more versatile than sound effects. Music is affected by the current tempo. There are 2 tempos: Speed shoes tempo and normal tempo. Normal tempo always affects music, while the speed shoes tempo is used to speed up music every so often (according to tempo value). There are 2 tempo modes, overflow and counter. Overflow tempo delays or speeds up music by a single frame each time the tempo accumulator overflows (becomes >$FF). Each frame, the tempo value is added to the accumulator. Counter tempo instead holds a counter value, and it is counted down each frame. When it becomes 0, a single frame of delay is added, or the sound driver is ran twice per frame. Then the counter value is also read back into the accumulator. You can change the tempo algorithm in code/macro.asm. You can see a visualization of the two methods in the below image (thanks to MarkeyJester!)
Below is a tool to help you convert between different tempo algorithms.
counter | ||
overflow | ||
Sonic 2 |
Music also has an additional variable: Tick multiplier. For sound effects and often music, this value is set to $01, but you can use other values if you want to. When a delay is read from the tracker, that value is multiplied by the tick multiplier amount. Tick multiplier of $01 means it is the same as read, but $02 means it's twice as long, etc. There is a small issue though. If the total amount overflows the 8-bit range, you start getting smaller values again. As a special case, a delay of $00 is considered undefined, though in most cases it will act as if the delay was $100 frames. This is a limitation with the engine.
Additionally, the tick multiplier field may contain extra flags. These flags tell AMPS to treat the music in special ways. They are mostly just for extra features. Here is a list of the special modes. I've listed the bit it detects along with the value that bit represents:
- bit 6, $40 — Tells AMPS that the previous song should be backed up. This is useful for temporary sounds that should not restart the previous song from the beginning.
- bit 7, $80 — In PAL regions, Mega Drives run at 50hz as opposed to 60hz. The driver combats this by running twice every 6th frame. This causes timing issues with things that run for a specific amount of time. This flag disables that behavior, meaning the music runs slower, but takes the correct amount of time.
The header also defines the channels that should be used for music. Currently, DAC1 and DAC2 must always be defined, even if they go unused. After DAC channels, FM channels and finally PSG channels are defined. FM and PSG channels always go in order and no channel can be skipped. This works differently to sound effects, which can be defined in any order the programmer wants. This is by design however, to reduce the size of tracker files and load times. Finally, the FM voice list comes right after the last channel. It does not necessarily have to contain any voices, but if some were needed that is where they should be placed. The inflexibility of this system is unfortunate and there are future plans to mitigate it.
Here is an example music header:
Including your music is also pretty easy. Music file names must not contain spaces or other special characters. The only valid characters are A-Z, a-z, 0-9 and _. Music files will be assigned an ID, and in program code, you can refer to this specific id with a special equate, named the music file name, prefixed with mus_. For example, you may have mus_Something. To include your music, simply open code/Data.asm, and locate MusicIndex. You will see lines starting with ptrMusic, with a name and a number. The name should simply be the file name of your music file, while the number is the speed shoes tempo value. Speed shoes tempo should always come right after the music file name. Here is an example:
3.2 — Sound Effect Files
Sound effects are useful when you want to play a small sound or a short jingle. They are most commonly used as feedback for the player or for enemy sounds. Sound effects are not affected by current tempo or by the 50hz "fix", and generally do not use the tick multiplier. Additionally, sound effects cannot use the channel stack or gate command. These were deemed unnecessary for sound effects and are therefore not available. Sound effects share a voice bank, so the voices are not located after the header. Instead, they are loaded from Voices.asm.
Sound effects have a priority system, which allows some sound effects to override others, or not, depending on the priority setting. Priorities go from $00 to $FF, where $00 is the lowest possible priority and $FF the highest. Lower priority channels are always overridden by ones with higher priority. The programmer can decide which priority values to give to each sound effect, but it is recommended to use $80 as the midway point, with priority values fairly close to it. In AMPS, priority values are stored per channel and therefore stick for the duration the channel exists. This means that priority values are computed per channel and stick for longer than a frame. Compared to SMPS drivers, this will make sound effects sound different and they cannot override each other at a later time.
Sound effects can define channels in any order, and with a standardized set of parameters. The first parameter is a set of flags that should always be the same according to channel type (other values have undefined behaviour). The second parameter is the actual channel type. It is preferred you use the equates provided in code/macro.asm to keep consistency. third parameter is the tracker data location, fourth parameter is pitch offset, and finally, fifth parameter is the channel volume. Here is an example of a sound effect header:
Currently, sound effects may use the following channels: FM3, FM4, FM5, PSG1, PSG2, PSG3, and DAC1. FM3 may be later changed to FM2.
Including sound effect files is pretty easy: each sound effect file name must not contain spaces or other special characters. The only valid characters are A-Z, a-z, 0-9 and _. Sound effect files will be assigned an ID, and in program code, you can refer to this specific ID with a special equate, named the sound effect file name, prefixed with sfx_. For example, you may have sfx_Example. To include your sound effects, simply open code/Data.asm, and locate SoundIndex. You will see lines starting with ptrSFX and a number, then a list of names. The name should simply be the file name of your sound effect file, while the number is a bit-field of flags. Each different set of flags should be on its own ptrSFX line. Here is a list of the flags. I've listed the bit it detects along with the value that bit represents:
- bit 0, $01 — Tells AMPS this sound effect should switch to the next ID every other time it is played. This is used to implement the ring speaker switching behaviour from Sonic games, although it could be used for any number of sound effect behaviours.
- bit 7, $80 — Tells AMPS this sound effect is continuous. These sound effects do not restart each time they are played. Instead, they continue in a loop inside of the tracker file. They are stopped only when either the sound effect is no longer being played, or some other sound effect overrides it. This effect was particularly popular in Sonic 3 & Knuckles, but individual sound effects in Sonic 1 and Sonic 2 use similar behaviours.
Here is an example of including your sound effect:
4 — Trackers
In part 3, 3.1 and 3.2 we talked about the specific differences between sound commands, music, and sound effects. In this part, we'll instead talk about the similarities, and dive deeper into how trackers work.
4.1 — Trackers Overview
In AMPS, a tracker is channel-specific data that tells AMPS what to do. AMPS will interpret this data and perform the actions requested by the programmer. Trackers in concept are quite simple, but internally are not very simple. AMPS aims to abstract much of this complexity, so the programmer has to worry about it as little as possible. Trackers are split into 3 types of data: Tracker commands, notes, and delays. For this section, we will focus on notes and delays, and we'll dive deeper into tracker commands in the next part.
AMPS uses delays to know for how long to not read from the tracker. Each channel has a stored note duration and active note duration. Together with delays, tick multipliers, and tempo (sound effects ignore tempo), the note duration is calculated. Every frame, channels will decrease this timer, and if it reaches 0, the tracker is processed again. To make FM, DAC, and PSG compatible, different initial values are loaded into the delay when channels are started (such as when a new music or sound effect is loaded). A delay of $00 frames is considered undefined behaviour, but the engine properly supports any delays between $01 and $7F frames. Most songs will use $0C frames per beat and multiples or divisions of $0C for other delays, but any other value can also be used. It is important to remember that if the tick multiplier is high and the delay value is also high, an overflow may happen, where the delay would be >$FF frames. This, in reality, will lead to a delay less than the intended amount. This is considered bad practice and should be avoided.
AMPS uses notes as a means of representing frequency values. PSG, FM, and DAC all have different tables for defining the intended frequency based on the note, but that is rarely something the programmer has to worry about. Instead, it's common to use the provided equates in code/smps2asm.asm as guidelines for note values. There are 12 notes per octave for each type of hardware, and they should roughly sound accurate to real instruments, although it can also depend on the instrument and what kind of hardware voice is used. The programmer can decide to create their own pitch tables or even use a different number of notes per octave, but this is not officially supported with AMPS. Notes go from $80 to $DF, and $80 always represents a rest, where no note will be played, but instead, the note is set to release (when applicable). Playing notes is affected by the transposition of the channel. Transposition will be added to the note value read from the tracker, so another note may actually be played. This is very useful when multiple channels may share routines, or the same routine is used multiple times at different keys. It is important to keep this fact in mind, as it can be both useful and confusing.
There is a very specific order of operations when it comes to trackers in AMPS. First off, all commands are executed in order. Few commands will stop the tracker completely, in which case the rest of the process is abandoned. However, if that does not happen, AMPS expects either a note or a delay to happen at some point. If a delay is read, the last note is used to play again. If no note was played yet, the result is undefined behaviour. AMPS will also correctly remember if a rest was played. AMPS will stop reading the tracker, process the delay, and continue to do standard note-on behaviour unless a hold command was played. However, if a note is played instead, AMPS will read its frequency from the appropriate table, and then check the next byte. If this byte is a delay, the delay is also read and processed. There must be no other commands or notes before the delay for this to happen. Again, if no delay was ever played, this is considered undefined behaviour. AMPS will stop reading the tracker, process the delay, and continue to do standard note-on behaviour unless a hold command was played. Although these three approaches may seem confusing and unnecessary, it actually helps to avoid repeating yourself and save some ROM space. It may not be necessary, but it will help those who want it. More about frequencies can be read from part 5. Below, you can see a demonstration of how this works:
A small note here is that when a DAC channel is in sample mode, instead of using the note to read from the frequency table, it instead reads the note as a sample to play. This is very useful when the channel does not need to change the frequency often, and is instead primarily used to play different samples. By default, DAC channels use this mode, but the user can change it depending on preference. More about this is explained in the next section.
4.2 — Tracker Commands
AMPS has a large variety of tracker commands. Although not all of them will be useful for every person, but they exist nonetheless to help people make music in their way. Tracker commands go from $E0 to $FF. $FF is a special command, so called "meta command". This command reads another byte from tracker, to act as an extra routine. This way, there is more space for commands, at the cost of extra ROM space required. Below, I will list all the tracker commands in AMPS, their numerical codes, and the parameters they use. All parameters enclosed <like this> are required, while parameters enclosed [like this] are optional. I will also explain how each tracker command works:
$E0 — ssPan <pan>, [ams], [fms]
This command is used to set the YM2612 panning for the current channel. This command only works for FM and DAC channels. YM2612 uses channel panning to determine which speaker to play audio on for different sound channels. There is a flaw however: Since both DAC channels and FM6 are technically the same channel for YM2612, they can only pan together. This is why, both active DAC channels or their panning value together. If DAC1's panning is set to $80, and DAC 2's $40, the resulting panning value is $C0. DAC1 sound effects override the panning value for DAC1 music. Currently, panning on FM6 when any DAC channel is running, is considered undefined behaviour, and is therefore disabled in safe mode. This command is skipped when condition is false.
The three arguments for ssPan all are used to set the panning value. Since the panning register is shared with the AMS/FMS datafor LFO, you can set both using this command. However, if LFO is not enabled by the tracker, setting AMS/FMS values is considered undefined behaviour. The <pan> parameter can be used to either only set the panning values, or the entire byte. [ams] and [fms] can also be used to set the AMS/FMS values too. They're shifted in place correctly. Below is a table of pre-defined equates for panning values:
- $00, spNone — Sets panning to neither speaker. This effectively mutes the channel. However, setting DAC2 music channel to panned to neither is actually recommended. Using DAC1, it is then possible to pan the channel in the appropriate speaker at any time.
- $40, spRight — Sets panning to the right speaker.
- $80, spLeft — Sets panning to the left speaker.
- $C0, spCentre — Sets panning to both of the speakers, effectively making it centered.
- $C0, spCenter — Silly Americans =U
$E1 — ssDetune <detune>
Sets the channel detune to <detune>. Detune is used to change the frequency of the channel slightly. You can read more about how this works here. This command is skipped when condition is false.
$E2 — saDetune <detune>
Adds <detune> to the channel detune. Detune is used to change the frequency of the channel slightly. You can read more about how this works here. This command is skipped when condition is false.
$E3 — ssTranspose <transp>
Sets the channel transposition to <transp>. Transposition is used to change which votes are played, adding channel pitch to the note being played. This is very useful when you want to share routines between FM, PSG and DAC, or for reusing a routine but just transposed. This command is skipped when condition is false.
$E4 — saTranspose <transp>
Adds <transp> to the channel transposition. Transposition is used to change which votes are played, adding channel pitch to the note being played. This is very useful when you want to share routines between FM, PSG and DAC, or for reusing a routine but just transposed. This command is skipped when condition is false.
$E5 — ssTickMulCh <tick>
Sets channel tick multiplier to <tick>. Tick multiplier is used to multiply the delay read from tracker by the tick multiplier value. This is used for some songs. See the previous part for more details. This command is skipped when condition is false.
$E6 — ssTickMul <tick>
Sets tick multiplier for every channel to <tick>. Tick multiplier is used to multiply the delay read from tracker by the tick multiplier value. This is used for some songs. See the previous part for more details. This command is skipped when condition is false.
$E7 — sHold
Toggles the channels hold flag. The hold flag prevents AMPS from processing the normal note on/note off behaviour and resetting internal flags, for example, modulation. The result is that the sound will continue uninterrupted, but you can also add extra tracker commands or delay to your notes. This allows delays longer than $100 frames to be set in the tracker as if it was a continuous note. If you play another note the frequency will be updated but note will not be restarted.
$E8 — sVoice <voice>
Sets FM voice to <voice> or DAC sample to <voice>. Using this command on a PSG channel is considered undefined behaviour. Additionally, using this command on a DAC channel when pitch mode is enabled is considered undefined behaviour. Read more about how FM voices work at part 4.4. This command is skipped when condition is false.
$E9 — ssTempoShoes <tempo>
Sets the speed shoes tempo to <tempo>. You can read more about tempos and how they work at part 3.1. This command is skipped when condition is false.
$EA — ssTempo <tempo>
Sets the normal tempo to <tempo>. You can read more about tempos and how they work at part 3.1. This command is skipped when condition is false.
$EB — sModeSampDAC
Sets the channel mode to sample mode. Using this command on a FM or PSG channel is considered undefined behaviour. Sample mode makes it so that each note read is treated as a sample instead of a note. This way, you can easily play different samples in quick succession. This mode is most useful when you want to play multiple different samples at the same pitch. You can use ssFreq and ssFreqNote to change the pitch of samples. This command is skipped when condition is false.
$EC — sModePitchDAC
Sets the channel mode to pitch mode. Using this command on a FM or PSG channel is considered undefined behaviour. Pitch mode makes it so that each note read is treated as a note instead of a sample. This way, you can easily play different samples at different pitches. This mode is most useful when you want to play samples many times at different pitches, or you want to play most of your samples at varying pitches. You can use sVoice to change the samples you are playing. This command is skipped when condition is false.
$ED — saVol <volume>
Adds <volume> to the channel volume. AMPS will update the channel volume at the end of the channel update process. You can read more about how volume works here. This command is skipped when condition is false.
$EE — ssVol <volume>
Sets the channel volume to <volume>. AMPS will update the channel volume at the end of the channel update process. You can read more about how volume works here. This command is skipped when condition is false.
$EF — ssLFO <reg> <ams> [fms] [pan]
Enables LFO and sets up AMS, FMS and enable bits for the channel. LFO is a YM2612 effect that changes the volume and frequency of the sound being generated. Unlike software effects, LFO can change volume and frequency many times a frame, with some values sounding much like shouting into a spinning fan. LFO speed settings are global for each channel, but every channel can define how strongly they are affected. Changing the global settings while multiple channels have LFO enabled affects each channel. The <reg> parameter is used to control which operators to enable AMS for, and the LFO frequency. See the table below:
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
val | $80 | $40 | $20 | $10 | $08 | $04 | $02 | $01 | |
use | Enable op 1 | Enable op 3 | Enable op 2 | Enable op 4 | Enable LFO | LFO Frequency |
The <ams>, [fms] and [pan] parameters can be used together to define the AMS and FMS sensitivity, and channel panning. The <ams> parameter can be used to set the entire byte value, or only the AMS sensitivity. <ams> [fms] are shifted in the correct places when the <ams> parameter is not used alone, and [pan] parameter expects normal values for it. Below is a table of pre-defined equate values for panning:
- $00, spNone — Sets panning to neither speaker. This effectively mutes the channel. However, setting DAC2 music channel to panned to neither is actually recommended. Using DAC1, it is then possible to pan the channel in the appropriate speaker at any time.
- $40, spRight — Sets panning to the right speaker.
- $80, spLeft — Sets panning to the left speaker.
- $C0, spCentre — Sets panning to both of the speakers, effectively making it centered.
- $C0, spCenter — Silly Americans =U
This command is skipped when condition is false.
$F0 — sModAMPS <wait> <speed> <step> <count>
Updates software frequency modulation information. Modulation is useful for creating an effect where frequency moves up and/or down repeatedly, which can be used either for detuning on instruments or for custom effects. They are often used for sound effects to make them sound interesting, realistic or fun. They are also use in music to create bigger immersion. Modulation in AMPS is slightly different from SMPS games, but in general it works similarly. The <wait> parameter delays the start of the modulation by that number of frames, where 0 frames means it starts immediately. <speed> is the number of frames to delay until the next step is executed. 0 frames is considered invalid. <step> is the frequency offset per step. Values are sign extended, so anything above $7F is a negative value. The resulting frequency offset is 16-bit and can also be negative. <count> is the number of steps to perform before negating the <step> parameter. 0 steps will behave as infinite steps. For a more in-depth explanation of the algorithm, see the next part. This command is skipped when condition is false.
$F1 — ssPortamento <frames>
Sets the portamento speed to <frames> frames, where 0 frames disables the effect. Portamento is an effect where the frequency of the previous note is blended smoothly with the current note. The <frames> parameter is used to control how long this transition takes. A speed of $01 means there is 1 frame where the note is transitioning to the next. In that example, the frequency will be in the middle of the 2 notes. Portamento only occurs when the next note is played, and therefore will delay the notes slightly. This also affects note on/off behaviour, which will happen whenever the note is played rather than when it transitions. This can be worked around with the sHold tracker command or by creating complex tracker code to execute the note-on when you want it to happen. This is usually not noticeable enough to matter however. Read more about portament in this part. This command is skipped when condition is false.
$F2 — sVolEnv <env>
Sets the channel volume envelope to <env>. Volume envelopes are used to change the volume of a channel per frame, reading from a list. This is particularly useful for PSG channels, since they do not inherently have any capability to change the volume in a meaningful way otherwise. However, if FEATURE_DACFMVOLENV is enabled, DAC and FM channels can also use this functionality. More about volume envelopes in part 6.1. This command is skipped when condition is false.
$F3 — sModEnv <env>
Sets the channel modulation envelope to <env>. Modulation envelopes are used to change the frequency of a channel per frame, reading from a list. This works similarly to volume envelopes, but has some differences as well. SMPS games have used this feature to create more dynamic sounding instruments. This feature is only available when FEATURE_MODENV is enabled. More about modulation envelopes in part 5. This command is skipped when condition is false.
$F4 — sCont <loc>
Is used to make continuous sound effects work. Playing a continuous sound effect multiple times does not restart the sound effect, but instead make it continue for longer time. This makes it so that the sound effect can loop smoothly, and yet can be extended as necessary. This command allows continuous sound effects to check if the sound effect was played again, and if so, jump back to <loc> in tracker code. AMPS assumes that every sound effect channel will loop, so not looping every sound effect channel is considered undefined behaviour, though most of the time some channels will loop more times than others. If you do not wish a channel to play any sound after some time, loop them inside a bunch of rest notes. Additionally, all the channels should loop at the exact same tick. This command is skipped when condition is false.
$F5 — sStop
Will stop the current tracker from running, and release all the resources needed by the channel. Additionally, when sound effect channels are stopped, the music channels they were interrupting are correct restored. After this command, no tracker code will be executed. This command is skipped when condition is false.
$F6 — sJump <loc>
Sets the tracker address to be at <loc>. After this command, all tracker code is read starting from <loc>. This command is skipped when condition is false.
$F7 — sLoop <index> <loops> <loc>
Creates a loop between the command and the address at <loc>. This loop will be executed for <loops> times. Due to technical reasons, <loops> must be 2 or greater. Finally, the <index> parameter is used to identify which loop is being used. AMPS supports 3 different loop indexes, primarily used for multiple loops inside of each other. Here is a list of how the loop indexes are available for different channels:
- $00 — Always available.
- $01 — Available for every channel except PSG3, if FEATURE_DACFMVOLENV is disabled.
- $02 — Available only for music channels.
This command is skipped when condition is false.
$F8 — sCall <loc>
Is used to temporarily run another routine, while remembering the last location we were in. The tracker address at <loc> is jumped to, while the location of the next tracker command at the previous address is saved to a stack. This allows us to run another routine, or even multiple other routines, before eventually returning back to the original routine. The sRet command is used to return back a level in the stack. This command is very useful for general routines that you may want to reuse multiple times, or even between multiple different trackers. By default, 3 different routines can be called before the stack runs out of space. This command is not available for sound effect channels, and attempting to use it will result in undefined behaviour. This command is skipped when condition is false.
$F9 — sRet
Is used to return from a routine called by sCall. This immediately returns to the next tracker code after the corresponding sCall command. If there are no calls present in the stack, the operation is invalid and results in undefined behaviour. This command is not available for sound effect channels, and attempting to use it will result in undefined behaviour. This command is skipped when condition is false.
$FA — sComm <index> <val>
Sets the communications byte <index> to <val>. Since AMPS only supports 16 communications bytes, using indexes higher than that will result in undefined behaviour. There are only 8 communications flags installed by default however. If you extend the number of available communications bytes, then they may be used. Read more about communications bytes in part 7.
$FB — sCond <index> <cond> <val>
Checks the value of communications byte <index> against <val>. The command reads the byte at <index>, compares its value to <val>, and then uses <cond> to decide whether to set condition mode to false. When condition is false, execution of some tracker commands is disabled. To read more about this, see part 7. Below is a table of all valid conditions:
- $00, dcoT — Sets condition to true regardless.
- $01, dcoF — Sets condition to false regardless.
- $02, dcoHI — Sets condition to true, if <val> is greater than the communications value. This is an unsigned comparison.
- $03, dcoLS — Sets condition to true, if <val> is lower than the communications value. This is an unsigned comparison.
- $04, dcoHS — Sets condition to true, if <val> is greater than or equal as the communications value. This is an unsigned comparison.
- $04, dcoCC — Sets condition to true, when subtracting <val> from the communications value results in carry being clear.
- $05, dcoLO — Sets condition to true, if <val> is lower than or equal as the communications value. This is an unsigned comparison.
- $05, dcoCS — Sets condition to true, when subtracting <val> from the communications value results in carry being set.
- $06, dcoNE — Sets condition to true, if <val> does not equal the communications value.
- $07, dcoEQ — Sets condition to true, if <val> does equal the communications value.
- $08, dcoVC — Sets condition to true, when subtracting <val> from the communications value results in overflow being clear.
- $09, dcoVS — Sets condition to true, when subtracting <val> from the communications value results in overflow being set.
- $0A, dcoPL — Sets condition to true, when subtracting <val> from the communications value results in a positive value.
- $0B, dcoMI — Sets condition to true, when subtracting <val> from the communications value results in a negative value.
- $0C, dcoGE — Sets condition to true, if <val> is greater than or equal as the communications value. This is a signed comparison.
- $0D, dcoLT — Sets condition to true, if <val> is lower than the communications value. This is a signed comparison.
- $0E, dcoGT — Sets condition to true, if <val> is greater than the communications value. This is a signed comparison.
- $0F, dcoLE — Sets condition to true, if <val> is lower than or equal as the communications value. This is a signed comparison.
$FC — sCondOff
Sets condition to true.
$FD — sGate <frames>
Sets the gate timeout to <frames>. Value of 0 disables this effect. Gate cuts the sound out after a specific number of frames (not ticks!), commencing regular note off behaviour for the channel. This is very useful when you have a lot of notes that should stop few frames/ticks before the actual note length. Instead of placing a bunch of notes with delays, and rests with delays, you can optimize it to use sGate instead. Not only is this faster, but also more space-efficient. This command is not available for sound effects, and trying to use it is considered undefined behaviour. This command is skipped when condition is false.
$FE — sCmdYM <reg> <val>
Sends an YM command into YM2612. This command allows you to send any arbitrary YM2612 commands from the tracker. This command is somewhat dangerous as you can accidentally mess something up, so being extra careful is warranted. Additionally, your changes will NOT be saved, so if you use this in a channel that can be interrupted by sound effects, by the time it is no longer interrupted, the changes you've made are lost forever. This is why you should make only non-essential changes for these FM channels. The <reg> parameter is used to choose the YM2612 register to write to, while <val> is loaded into it. For channel-specific registers, the channel type is also added into it, so you can always get the correct register you want. This happens for registers $30 to $A7 and $A8 tp $FF. Attempting to write to another channels registers is considered undefined behaviour. Additionally, using this command from DAC and PSG channels are considered undefined behaviour. For a breakdown of what each register does, read this page. This command is skipped when condition is false.
$FF — META
Reads the next byte from the tracker, and calls the following list of commands by that byte. The values are always multiples of 4 to make the code faster.
$FF $00 — sModOn
Enables modulation. Unless modulation was initialized before using ssMod68k, using this command is considered undefined behaviour.
$FF $04 — sModOff
Disables modulation. The modulation data is still remembered, but no modulation will take place until it is enabled again.
$FF $08 — ssFreq <freq>
Sets the channel frequency to 16-bit value <freq>. This is only useful when a DAC channel is in sample mode, where you can use this command to set the precise frequency of the channel. In any other case, using this command is considered undefined behaviour.
$FF $0C — ssFreqNote <note>
Sets the channel frequency to the frequency represented by <note>. Channel pitch also is added to <note>. This is only useful when a DAC channel is in sample mode, where you can use this command to set the precise frequency of the channel. In any other case, using this command is considered undefined behaviour.
$FF $10 — sSpinRev
Increases spindash counter by 1, and adds the counter value to the pitch of the channel. Only really useful for making the spindash rev sound effect work.
$FF $14 — sSpinReset
Resets spindash counter. This is used by the spindash sound effect, and various other sound effects to reset the rev counter. This makes it behave like Sonic games. Really useful for making the spindash rev sound effect work.
$FF $18 — saTempoShoes <tempo>
Adds <tempo> the speed shoes tempo. You can read more about tempos and how they work at part 3.1.
$FF $1C — saTempo <tempo>
Sets <tempo> to the normal tempo. You can read more about tempos and how they work at part 3.1.
$FF $20 — sCondReg <index> <cond> <val>
Checks the value of a RAM address indexed from a table (dcCondRegTable) at <index> against <val>. The command reads the byte at <index>, compares its value to <val>, and then uses <cond> to decide whether to set condition mode to false. When condition is false, execution of some tracker commands is disabled. To read more about this, see part 7. Below is a table of all valid conditions:
- $00, dcoT — Sets condition to true regardless.
- $01, dcoF — Sets condition to false regardless.
- $02, dcoHI — Sets condition to true, if <val> is greater than the communications value. This is an unsigned comparison.
- $03, dcoLS — Sets condition to true, if <val> is lower than the communications value. This is an unsigned comparison.
- $04, dcoHS — Sets condition to true, if <val> is greater than or equal as the communications value. This is an unsigned comparison.
- $04, dcoCC — Sets condition to true, when subtracting <val> from the communications value results in carry being clear.
- $05, dcoLO — Sets condition to true, if <val> is lower than or equal as the communications value. This is an unsigned comparison.
- $05, dcoCS — Sets condition to true, when subtracting <val> from the communications value results in carry being set.
- $06, dcoNE — Sets condition to true, if <val> does not equal the communications value.
- $07, dcoEQ — Sets condition to true, if <val> does equal the communications value.
- $08, dcoVC — Sets condition to true, when subtracting <val> from the communications value results in overflow being clear.
- $09, dcoVS — Sets condition to true, when subtracting <val> from the communications value results in overflow being set.
- $0A, dcoPL — Sets condition to true, when subtracting <val> from the communications value results in a positive value.
- $0B, dcoMI — Sets condition to true, when subtracting <val> from the communications value results in a negative value.
- $0C, dcoGE — Sets condition to true, if <val> is greater than or equal as the communications value. This is a signed comparison.
- $0D, dcoLT — Sets condition to true, if <val> is lower than the communications value. This is a signed comparison.
- $0E, dcoGT — Sets condition to true, if <val> is greater than the communications value. This is a signed comparison.
- $0F, dcoLE — Sets condition to true, if <val> is lower than or equal as the communications value. This is a signed comparison.
$FF $24 — sPlayMus <id>
Is used to play sound effects from tracker. The sound effect <id> will be placed in the first queue slot. Unless it is overridden before the next frame, it will be played.
$FF $28 — ssModFreq <freq>
Sets modulation frequency offset to 16-bit value <freq>. This allows to make more interesting or complex modulation patterns.
$FF $2C — sModReset
This command does the same as when note is played, where modulation parameters are loaded from the last sModAMPS command, and initialized. This is only really useful when notes are held, but this behaviour should be preserved, because as of v2.1, this does not happen in sModAMPS command anymore. This command replaces that behaviour.
$FF $30 — sSpecFM3
Not implemented.
$FF $34 — ssFilter <bank>
Sets the Dual PCM volume filter bank address to <bank>. Volume filters allow PCM to sound different by replacing actual PCM values with values from the volume table. Each volume filter is $8000 bytes, where there are $80 volume levels with $100 possible values for each (for 8-bit PCM audio). By default, AMPS uses the logarithmic volume filter. Warning: AMPS has no logic to change volume filters by itself, and can only do using this command. This means, any sound effects or song changes may break things. Use this command with caution.
$FF $38 — sBackup
Stops the current music from playing, and if a song was backed up, loads it instead and start a fade in. When a song that enables the back-up flag is played, AMPS moves the old song data into another memory location before loading the song in. sBackup does the opposite, loading the song data from the memory location into active channel memory. It also initializes the hardware to continue correctly from where it left off. Fade in is done to make the transition easier, and to hide some of the sound oddities that can happen whenever this is done. Backing up songs is very useful when you have temporary songs that need to play, such as the 1UP music from Sonic games. This is only available if FEATURE_BACKUP is enabled. Otherwise, it is considered undefined behaviour.
$FF $3C — sNoisePSG <mode>
Is used to set the PSG4 noise mode. PSG4 noise is useful for creating hi-hat-like effects. There are additional modes that generate different kind of noise. The command sends the PSG command <mode> into the PSG, and sets the channel mode to PSG4, muting PSG3. Additionally, using value 0 disables this effect and restores the channel to PSG3. This is only valid for PSG3 and PSG3 SFX channels.
$FF $40 — sFreeze
Freezes the 68k whenever this tracker command is read. It will also allow you to step over the instruction into an rts. This is very useful for debugging the 68k code at the specific position. Only available in safe mode, it is ignored in normal mode.
$FF $44 — sCheck
When the entire sound driver has executed, it will bring up the debugger and show various statistics about AMPS. Useful for debugging something possibly going wrong with the sound effects, music, other aspects of AMPS, or the programming itself. Only available in safe mode, it is ignored in normal mode.
4.3 — SN76496
The SN76496, better known as PSG, is the simplest sound chip on the Mega Drive. It really only has the capability to control volume, frequency, and channel 4 noise. Although the chip is hilariously barebones, it can still be used to generate really interesting sounds. PSG is a square wave generator chip, which means that each noise goes from mute to max volume nearly instantly. Due to limitations with filtration and the sound chip itself, it looks less like a square and more of a spiky square-like structure. The details are not overly important, and you can read more in detail about PSG here. What really is important, is how AMPS interacts with the PSG chip.
Although PSG has 4 channels, AMPS only supports 3 at a time. AMPS can switch between PSG3 or PSG4 mode for PSG3 channel. PSG3 mode is a normal tone channel, much like PSG1 and PSG2. This does not allow PSG4 to be used (though if you REALLY wanted you could do it - please don't), but is very good if you dont need it anyway. Since PSG4 allows for relatively unconvincing but reasonable hats, using PSG4 mode is more common. In this mode, PSG4 can use one of a few settings to enable noise. Below is a list of the PSG commands and their meanings:
PSG command | $E0 | $E1 | $E2 | $E3 | $E4 | $E5 | $E6 | $E7 |
Equate name | snPeri10 | snPeri20 | snPeri40 | snPeriPSG3 | snWhite10 | snWhite20 | snWhite40 | snWhitePSG3 |
Mode | Periodic noise | White noise | ||||||
Frequency | $0010 | $0020 | $0040 | PSG3 | $0010 | $0020 | $0040 | PSG3 |
Most commonly, PSG4 mode is used for songs, to add some extra depth. In AMPS, you can also enable and disable PSG4 mode with the sNoisePSG command. Using values on the table above you can enable it for the specific mode, and using $00 disables it. PSG4 has its own channel volume, but in case of using PSG3 mode, it does not have its own frequency. This is particularly problematic because usually PSG hi-hats use frequency of $0000, which is not pre-defined in the modes. It is possible to use another frequency instead and also use PSG3 at the same time, allowing PSG to output 4 different sounds at once, but currently AMPS does not have support for that. Both sound effects and music can use PSG4 mode, and it will be correctly restored when sound effects end.
PSG has a very small range of possible frequencies. Though this does not usually present an issue, the range of notes available in AMPS is limited. Below is a table of all notes, their raw values, and frequencies. There is also a special equate in AMPS, nHiHat, that corresponds to nA6 so long as there is no transposition.
octave | nC | nCs | nD | nEb | nE | nF | nFs | nG | nAb | nA | nBb | nB |
0 | $81, $03FF | $82, $03FF | $83, $03FF | $84, $03FF | $85, $03FF | $86, $03FF | $87, $03FF | $88, $03FF | $89, $03FF | $8A, $03F7 | $8B, $03BE | $8C, $0388 |
1 | $8D, $0356 | $8E, $0326 | $8F, $02F9 | $90, $02CE | $91, $02A5 | $92, $0280 | $93, $025C | $94, $023A | $95, $021A | $96, $01FB | $97, $01DF | $98, $01C4 |
2 | $99, $01AB | $9A, $0193 | $9B, $017D | $9C, $0167 | $9D, $0153 | $9E, $0140 | $9F, $012E | $A0, $011D | $A1, $010D | $A2, $00FE | $A3, $00EF | $A4, $00E2 |
3 | $A5, $00D6 | $A6, $00C9 | $A7, $00BE | $A8, $00B4 | $A9, $00A9 | $AA, $00A0 | $AB, $0097 | $AC, $008F | $AD, $0087 | $AE, $007F | $AF, $0078 | $B0, $0071 |
4 | $B1, $006B | $B2, $0065 | $B3, $005F | $B4, $005A | $B5, $0055 | $B6, $0050 | $B7, $004B | $B8, $0047 | $B9, $0043 | $BA, $0040 | $BB, $003C | $BC, $0039 |
5 | $BD, $0036 | $BE, $0033 | $BF, $0030 | $C0, $002D | $C1, $002B | $C2, $0028 | $C3, $0026 | $C4, $0024 | $C5, $0022 | $C6, $0020 | $C7, $001F | $C8, $001D |
6 | $C9, $001B | $CA, $001A | $CB, $0018 | $CC, $0017 | $CD, $0016 | $CE, $0015 | $CF, $0013 | $D0, $0012 | $D1, $0011 | $D2, $0010 | $D3, $0000 |
Each of these notes can be converted to the following format: xy, where x is the identifier of the note (for example nEb), and y is the octave (for example 4). This would result in equate nEb4, which can directly be used in tracker. These note names are largely arbitrary and only theoretically correspond to real instruments. Indeed, many other sound drivers on the Mega Drive have different frequencies for the notes anyway. These frequencies and notes are heavily based on SMPS for consistency's sake, and for no real need to change them.
PSG uses a 7-bit volume in AMPS as opposed to 4-bit that the SN76496 uses. This is to make PSG more aligned with FM and DAC, but at the cost of making the PSG volumes not match hardware levels. AMPS lazily converts the volume simply by dividing by 8, which results in a difference to the volume curve, but is accurate enough to work. There is also special behaviour with frequency of PSG, where you can disable safety measures intended to stop invalid frequencies being loaded. This is, oddly, necessary for the insta-shield sound effect in Sonic 3 & Knuckles! The problem with not capping the frequency is that it can write commands instead of frequencies into PSG, leading to undefined behaviour. For whatever reason, this does not seem to break anything. It is still not recommended to be used for any other reason!
4.4 — YM2612
The YM2612, also known as FM (frequency modulation), is the more complex sound chip in the Mega Drive. It is used to produce most sounds, as it has the most channels and the most rich sound output. It allows for a large number of parameters that change how music sounds, and more complex sinewave mixing. Although there could be a lot said about it, there are already a lot of documentation out there. Instead, how FM works within AMPS is more important for this document.
AMPS has support for all 6 channels at once, even while DAC is playing. Although, when DAC is not playing the mute sample (if the sample itself is mute, it does NOT count!), FM6 will be automatically disabled. Dual PCM is responsible for this, which means the enabling/disabling happens immediately, not at the end of the frame. In theory, this should never lead to artifacts in sound, but it can sound weird if samples go in and out all the time. You need to enable FEATURE_FM6 in order to use FM6. Additionally, panning in FM6 is currently not implemented correctly. It overrides panning for both DAC channels, and vice versa too. The results of that are considered undefined behaviour, so panning on FM6 is not recommended.
AMPS uses FM voice tables to load FM instruments. Music has a single table for all its channels, and sound effects use a common table for all voices. Unlike in SMPS, AMPS uses a more efficient format where loading any voice is as fast as any other. Therefore, a global voice table for sound effects actually improves performance overall. Instruments can be given unique names so that removing or adding them can be done without breaking anything. The global voice table for sound effects is in Voices.S2A. Below is an example of a voice, along with the more common names for each parameter:
Each voice in AMPS is 32 bytes in size, and has 3 unused bytes. This is done for the sake of alignment and efficiency. The above parameters are used to define each instrument, and they are sent to YM2612 via Dual PCM. This means, that the timings between voices and notes are different to SMPS and therefore some things will sound different. This sometimes causes perfectly good sound effects from SMPS games to sound absolute thrash in AMPS. This is largely unavoidable and there seems to be no real solution to this issue. Potential fixes are being investigated but there is no guarantee for it.
FM has $80 total volume levels per operator and depending on the algorithm, different operators act as the volume (slot operators), and others as modulators (nonslot operators). SMPS2ASM will correctly tell AMPS which operators are used for which purpose, but this can be changed by the programmer if they wish to have different behaviour. It is not recommended, unless the programmer knows what they are doing. All the slot operators can have different total level values, as they are simply just added into the calculated volume. Additionally, underwater mode changes the slot and nonslot total levels too.
You can send arbitrary YM2612 commands from tracker as well. This is useful for editing single instrument parameters or doing some more uncommon tricks. This is dangerous, because if a sound effect interrupts the channel, all your changes are lost. Indeed, relying on changes to stick is a terrible idea. Additionally, songs that use the backup system will also make these changes be lost, this time in every FM channel. Some other settings, such as LFO, will neither stick around correctly. However, because SSG-EG is in the instrument data, it will always be restored.
YM2612 has an interesting frequency range. It has frequencies from $000 to $7FF, and octaves from $00xx to $38xx. Each octave, well, is an octave higher than the previous. However, the frequency between octaves also has some overlap. A lower octave frequency may sound the same as higher octave frequency, but earlier from the frequency range. To put it more simply, $4C0 may sound the same as $A5C. The point where this happens precisely is not known yet, but the estimate provided is close enough. Below is a table of all notes, their raw values, and frequencies.
octave | nC | nCs | nD | nEb | nE | nF | nFs | nG | nAb | nA | nBb | nB |
-1 | $80, $025E | |||||||||||
0 | $81, $0284 | $82, $02AB | $83, $02D3 | $84, $02FE | $85, $032D | $86, $035C | $87, $038F | $88, $03C5 | $89, $03FF | $8A, $043C | $8B, $047C | $8C, $0A5E |
1 | $8D, $0A84 | $8E, $0AAB | $8F, $0AD3 | $90, $0AFE | $91, $0B2D | $92, $0B5C | $93, $0B8F | $94, $0BC5 | $95, $0BFF | $96, $0C3C | $97, $0C7C | $98, $125E |
2 | $99, $1284 | $9A, $12AB | $9B, $12D3 | $9C, $12FE | $9D, $132D | $9E, $135C | $9F, $138F | $A0, $13C5 | $A1, $13FF | $A2, $143C | $A3, $147C | $A4, $1A5E |
3 | $A5, $1A84 | $A6, $1AAB | $A7, $1AD3 | $A8, $1AFE | $A9, $1B2D | $AA, $1B5C | $AB, $1B8F | $AC, $1BC5 | $AD, $1BFF | $AE, $1C3C | $AF, $1C7C | $B0, $225E |
4 | $B1, $2284 | $B2, $22AB | $B3, $22D3 | $B4, $22FE | $B5, $232D | $B6, $235C | $B7, $238F | $B8, $23C5 | $B9, $23FF | $BA, $243C | $BB, $247C | $BC, $2A5E |
5 | $BD, $2A84 | $BE, $2AAB | $BF, $2AD3 | $C0, $2AFE | $C1, $2B2D | $C2, $2B5C | $C3, $2B8F | $C4, $2BC5 | $C5, $2BFF | $C6, $2C3C | $C7, $2C7C | $C8, $325E |
6 | $C9, $3284 | $CA, $32AB | $CB, $32D3 | $CC, $32FE | $CD, $332D | $CE, $335C | $CF, $338F | $D0, $33C5 | $D1, $33FF | $D2, $343C | $D3, $347C | $D4, $3A5E |
7 | $D5, $3A84 | $D6, $3AAB | $D7, $3AD3 | $D8, $3AFE | $D9, $3B2D | $DA, $3B5C | $DB, $3B8F | $DC, $3BC5 | $DD, $3BFF | $DE, $3C3C | $DF, $3C7C |
4.5 — Dual PCM
Dual PCM FlexEd is not an actual sound chip, but rather a piece of software that is responsible for dealing with PCM audio and YM2612 commands. Dual PCM is heavily integrated with AMPS to give the best possible user experience. It allows playing back 2 PCM samples at once, with the ability to change pitch and volume as well. Although Dual PCM is "only" approximately 14KHz 8-bit PCM audio per channel, it still leads to excellent audio quality. There is in-depth documentation available for Dual PCM, so I will not dive too deeply into how it works. Instead, I will focus more on how AMPS interacts with it.
Dual PCM uses SWF format for samples. This is a custom PCM format specifically for use with Dual PCM. AMPS stores raw samples in the dac folder, and ConvPCM.exe is used to convert general sample formats to SWF. After this, SWF files should be stored in dac/incSWF. AMPS will access files from there. Samples are included in code/Data.asm, at the bottom of the file. There should be a bunch of incSWF statements with names on them. These are file names of the individual samples. They should only contain A-Z, a-z, 0-9 and _, otherwise they won't be assembled correctly. AMPS will generate the necessary structures to include the file into the disassembly. You need to add samples into the bottom of the file before you can use them in your trackers. Also, you need to define data for them. At SampleList, you will see a list of defined tracker samples. These define the settings and names for the samples. Here is an example:
This defines a sample, with equate name dHiTom, with base frequency of $180. This frequency is added to calculated frequency whenever it is updated. Frequency of $0100 is the neutral frequency, where the sample speed/frequency is at 100%. The higher the frequency goes, the faster the sample is, same for lower. Negative values make it go backwards (from the end to start). The sample name Tom is loaded from memory to start the sample, and the sample Stop is loaded as the looping point. This makes the Tom sample play fully before the sound is stopped. You could insert another sample as the loop sample, and you'd get an infinitely looping sample (so long as a note is played that is). Defining a 3rd name, in this case HiTom is not required. Omitting it means the first name, Tom in this case, uses that name for the sample equate. That would result in dTom. Here is an example of that:
The looping happens internally in Dual PCM rather than with AMPS, so it can be very useful for creating samples that repeat in quicker than per frame. Making the samples too short will lead to a lot of quality loss, so sometimes repeating the sample a few times is a good idea. Adding a lot of blank space at the end of the sample can inflate the ROM size however, so use these tricks very carefully.
DAC channels have 2 modes: Sample mode and pitch mode. These can be selected using tracker commands sModeSampDAC and sModePitchDAC. In sample mode, each note represents a sample to be used at the current channel pitch. AMPS will also remember what sample was last used and it will work like channels normally with the exception that you're changing the sample, not the pitch. Pitch mode is how channels work normally, every note represents a frequency from the frequency table. The last used sample is used to play the new note. You can use sVoice to change the sample in pitch mode, and ssFreq & ssFreqNote to change the frequency of the channel in sample mode.
Panning for DAC channels is bit of an exception. The 2 currently active channels add the panning value together to get the final value. If SFX DAC1 channel is active, it will override DAC1. If one channel is panned to the centre and other is to left, the panning will be centre anyway. It is a good idea to pan one channel to none and the other controls panning for both. This may be a problem when using SFX DAC1, as it cannot be guaranteed to have control over panning and may cause music to sound strange. You should be careful when using panning like this.
Dual PCM has $80 volume levels for samples, and it uses a table to load these volumes from. AMPS by default comes equipped with a logarithmic volume filter that mimics the perceived volume levels of YM2612 as closely as possible. It will never be a perfect match but its usually not noticeable. AMPS also includes a linear volume table but using this isn't really recommended. You can also create custom tables and use them with AMPS, but this is not properly supported at the moment, and you'd have to figure it out yourself. Because of limitations with Dual PCM, attempting to update volume more often than every 4 frames will lead to quality loss. In AMPS, this is done by wrapping the PCM buffers when they run out of data, so that a part of the PCM data will play twice over. This has been considered less distracting than stopping the sample playback outright. This is a good idea to remember for channel volumes, volume fading, volume envelopes and others.
DAC has a wide range of possible frequencies, including backwards frequencies. Many of these frequencies are not particularly useful, because they go quite fast, but they are included because someone might find them useful or cool to experiment with. The frequency table is $100 elements long, so any note and transposition value can be used. This is done mostly to support the backwards frequencies correctly. It is technically possible to go over $FFF or below -$FFF in frequency, but this is not supported by the Dual PCM configuration and may lead to glitches. Below is a table of all notes, their raw values, and frequencies:
octave | nC | nCs | nD | nEb | nE | nF | nFs | nG | nAb | nA | nBb | nB |
? | $80, $0000 | |||||||||||
0 | $81, $0010 | $82, $0011 | $83, $0012 | $84, $0013 | $85, $0014 | $86, $0015 | $87, $0017 | $88, $0018 | $89, $0019 | $8A, $001B | $8B, $001D | $8C, $001E |
1 | $8D, $0020 | $8E, $0022 | $8F, $0024 | $90, $0026 | $91, $0028 | $92, $002B | $93, $002D | $94, $0030 | $95, $0033 | $96, $0036 | $97, $0039 | $98, $003C |
2 | $99, $0040 | $9A, $0044 | $9B, $0048 | $9C, $004C | $9D, $0051 | $9E, $0055 | $9F, $005B | $A0, $0060 | $A1, $0066 | $A2, $006C | $A3, $0072 | $A4, $0079 |
3 | $A5, $0080 | $A6, $0088 | $A7, $0090 | $A8, $0098 | $A9, $00A1 | $AA, $00AB | $AB, $00B5 | $AC, $00C0 | $AD, $00CB | $AE, $00D7 | $AF, $00E4 | $B0, $00F2 |
4 | $B1, $0100 | $B2, $010F | $B3, $011F | $B4, $0130 | $B5, $0143 | $B6, $0156 | $B7, $016A | $B8, $0180 | $B9, $0196 | $BA, $01AF | $BB, $01C8 | $BC, $01E3 |
5 | $BD, $0200 | $BE, $021E | $BF, $023F | $C0, $0261 | $C1, $0285 | $C2, $02AB | $C3, $02D4 | $C4, $02FF | $C5, $032D | $C6, $035D | $C7, $0390 | $C8, $03C7 |
6 | $C9, $0400 | $CA, $043D | $CB, $047D | $CC, $04C2 | $CD, $050A | $CE, $0557 | $CF, $05A8 | $D0, $05FE | $D1, $0659 | $D2, $06BA | $D3, $0721 | $D4, $078D |
7 | $D5, $0800 | $D6, $087A | $D7, $08FB | $D8, $0983 | $D9, $0A14 | $DA, $0AAE | $DB, $0B50 | $DC, $0BFD | $DD, $0CB3 | $DE, $0D74 | $DF, $0E41 | $E0, $0F1A |
8 | $E1, $0FFF | $E2, $0FFF | $E3, $0FFF | $E4, $0FFF | $E5, $0FFF | $E6, $0FFF | $E7, $0FFF | $E8, $0FFF | $E9, $0FFF | $EA, $0FFF | $EB, $0FFF | $EC, $0FFF |
9 | $ED, $0FFF | $EE, $0FFF | $EF, $0FFF | $F0, $0FFF | $F1, $0FFF | $F2, $0FFF | $F3, $0FFF | $F4, $0FFF | $F5, $0FFF | $F6, $0FFF | $F7, $0FFF | $F8, $0FFF |
10 | $F9, $0FFF | $FA, $0FFF | $FB, $0FFF | $FC, $0FFF | $FD, $0FFF | $FE, $0FFF | $FF, $0FFF | |||||
-10 | $00, -$FFF | $01, -$FFF | $02, -$FFF | $03, -$FFF | $04, -$FFF | $05, -$FFF | $06, -$FFF | $07, -$FFF | ||||
-9 | $08, -$FFF | $09, -$FFF | $0A, -$FFF | $0B, -$FFF | $0C, -$FFF | $0D, -$FFF | $0E, -$FFF | $0F, -$FFF | $10, -$FFF | $11, -$FFF | $12, -$FFF | $13, -$FFF |
-8 | $14, -$FFF | $15, -$FFF | $16, -$FFF | $17, -$FFF | $18, -$FFF | $19, -$FFF | $1A, -$FFF | $1B, -$FFF | $1C, -$FFF | $1D, -$FFF | $1E, -$FFF | $1F, -$FFF |
-7 | $20, -$F1A | $21, -$E41 | $22, -$D74 | $23, -$CB3 | $24, -$BFD | $25, -$B50 | $26, -$AAE | $27, -$A14 | $28, -$983 | $29, -$8FB | $2A, -$87A | $2B, -$800 |
-6 | $2C, -$78D | $2D, -$721 | $2E, -$6BA | $2F, -$659 | $30, -$5FE | $31, -$5A8 | $32, -$557 | $33, -$50A | $34, -$4C2 | $35, -$47D | $36, -$43D | $37, -$400 |
-5 | $38, -$3C7 | $39, -$390 | $3A, -$35D | $3B, -$32D | $3C, -$2FF | $3D, -$2D4 | $3E, -$2AB | $3F, -$285 | $40, -$261 | $41, -$23F | $42, -$21E | $43, -$200 |
-4 | $44, -$1E3 | $45, -$1C8 | $46, -$1AF | $47, -$196 | $48, -$180 | $49, -$16A | $4A, -$156 | $4B, -$143 | $4C, -$130 | $4D, -$11F | $4E, -$10F | $4F, -$100 |
-3 | $50, -$0F2 | $51, -$0E4 | $52, -$0D7 | $53, -$0CB | $54, -$0C0 | $55, -$0B5 | $56, -$0AB | $57, -$0A1 | $58, -$098 | $59, -$090 | $5A, -$088 | $5B, -$080 |
-2 | $5C, -$079 | $5D, -$072 | $5E, -$06C | $5F, -$066 | $60, -$060 | $61, -$05B | $62, -$055 | $63, -$051 | $64, -$04C | $65, -$048 | $66, -$044 | $67, -$040 |
-1 | $68, -$03C | $69, -$039 | $6A, -$036 | $6B, -$033 | $6C, -$030 | $6D, -$02D | $6E, -$02B | $6F, -$028 | $70, -$026 | $71, -$024 | $72, -$022 | $73, -$020 |
-0 | $74, -$01E | $75, -$01D | $76, -$01B | $77, -$019 | $78, -$018 | $79, -$017 | $7A, -$015 | $7B, -$014 | $7C, -$013 | $7D, -$012 | $7E, -$011 | $7F, -$010 |
Each of these notes can be converted to the following format: xy, where x is the identifier of the note (for example nA), and y is the octave (for example 2). This would result in equate nA2, which can directly be used in tracker. These note names are largely arbitrary and only theoretically correspond to real instruments. Majority of these notes are only accessible with transposition. After all, you can only play $81 to $DF using the tracker by default. Although, with ssFreqNote, you can actually play any of these notes at any time in sample mode.
5 — Frequency
In AMPS, frequency depends entirely on the sound hardware used. You can see the actual tables used in previous parts for FM, PSG and DAC. This is why AMPS abstracts actual frequencies as notes, which are mostly consistent with each other. Transposition is also used to shift the notes around to read different entries from the frequency table. This is most useful for DAC which has negative frequencies not accessible otherwise. Attempting to read an invalid note from these tables is considered undefined behaviour. The frequency read along with detune is used to calculate the final frequency. Additionally, modulation, portamento and modulation envelope are added into the frequency every frame (when active).
5.1 — Modulation
Software modulation in AMPS is similar but not quite the same to SMPS. In SMPS, the 68k versions and Z80 versions have a slightly different algorithm to each other, and AMPS is also slightly different in other ways. The differences will be noted as I explain the algorithm. Modulation has 4 user-defined values: wait, speed, step and count. These help define the behaviour of the modulation process. There is also a flag that enables and disables modulation independent of setting it up, and can be controlled in tracker with sModOn and sModOff. These only set the state of the flag rather than modifying modulation data. Additionally, sModReset will forcibly reset modulation parameters immediately. ssModFreq can be used to set modulation frequency offset to a specific value. Below is the explanation of the algorithm:
There can be some delay before the modulation starts. If wait is not 0, the modulation gets delayed by that many frames instead. The speed parameter defines how often modulation is stepped forwards. Value of $00 is invalid, and $01 is the minimum amount (gets stepped every frame). When a step occurs, step is added to the modulation frequency offset (which is later added to channel frequency!). This value is a signed 8-bit value, -$80 to $7F, and gets extended to a 16-bit value for the purposes of the frequency offset. This allows for a great dynamic frequency range with relatively little effort. There is also the count variable that is the number of steps. When modulation is initialized, this value is halved (rounded down) and set as the initial value. This allows the modulation to go back and forth around the channel frequency, however it doesn't have to. When the count is 0, the modulation speed is negated, and step is reloaded. This makes the frequency offset go backwards, to approximately the negative value of it. There is an exception, if count parameter has value of $00, step is not negated.
There are few interesting differences between SMPS versions and AMPS. In SMPS 68k, after the count and step parameters are reloaded, the rest of the modulation code is skipped. This includes decrementing the counter and processing frequency. This makes it so that the last step lasts 2 frames and the modulation is slightly longer. This can be simply fixed by adding 1 to the count parameter in your tracker code. SMPS Z80 is a bit more difficult. For some odd reason, it decrements modulation step counter every frame (after modulation wait delay has passed), and therefore can negate the direction even in the middle of the step. Also, the step parameter is therefore multiplied by the speed parameter. This is a problem particularly because its technically impossible to correctly recreate all modulation from SMPS Z80 games. In practice, this does not matter because you don't have to get the modulation matching 100% to be good enough.
There is also a difference between how SMPS Z80 and SMPS 68k handle modulation when note is held. If the sModAMPS command (or driver equivalent) is used, SMPS 68k will immediately reload modulation parameters, also resetting modulation frequency offset. SMPS Z80, and AMPS, do not do this, meaning that if sModAMPS, some values are reloaded and some are not. SMPS Z80 will not load any values, meaning that wait and step are ignored, speed and count are only reloaded when these values count down completely. In AMPS, wait and step are reloaded when the command is used. To help emulate SMPS 68k behaviour, sModReset can be used right after sModAMPS.
5.2 — Portamento
Portamento is an effect which allows the frequency between notes to be blended smoothly over time. FEATURE_PORTAMENTO must be enabled to use the portamento effect. Portamento effect has a certain speed, where $00 disables the effect, any other number indicates the number of between frames that are used for blending. Portamento uses a displacement value for changing offset each frame, and a frequency offset that keeps track of how far from the current note the actual frequency is supposed to be. This works irrespective of any other frequency effects so the results are correct every time.
The algorithm for portamento simply remembers the difference of the 2 notes, and calculates using the portamento value how much to change it per frame. The algorithm skips some frequency ranges for FM however, because they actually overlap. This makes the effect sound almost perfect. If the difference is too small, the difference is the smallest possible so the note does eventually change. Previous portamento offsets are also considered, so it is safe to change the note while portamento is in progress.
Every frame, portamento is processed and calculated. The portamento displacement is added to portamento offset, until it is 0, or changes sign. At this point, portamento is disabled until next note-on. This process again skips the FM ranges that are equivalent. Portamento from or to rests is not possible.
6 — Volume
Internally, AMPS uses 7-bit volume for all software channels. This makes the editing of volumes consistent between different sound hardware. This does mean, that PSG volume is actually multiples of 8 of the real hardware volume. AMPS lazily shifts it down instead of using a look-up-table to be more accurate. Volume levels go from $00 to $7F, where $00 is the loudest possible and $7F is muted. Any values outside of the range are treated as $7F. Volumes depend on the hardware and other settings so comparing volumes 1:1 between hardware types is difficult. The volume is merely a relative measure to help the programmer adjust the music to their liking. Volume is also useful to make music fade in/out easily.
There are a few factors that come into the final volume. There is master volume for each hardware type (sound effects may ignore this depending in settings), per-channel volume, volume envelopes, and for FM channels, total level values too. Additionally, the PCM volume levels also affect DAC channels.
6.1 — Channel Volumes
Channels exclusively handle the volume of hardware channels. Some code may mute the hardware channels, but if a volume needs to be updated, the channel will be requested to do it. This flag is used by the tracker, channel itself, and outside influences to correctly update the channel volume when required. This simplifies the complex process of volume updates. AMPS processes volume in the following order:
- channel volume — Channel-specific volume. Can be used to easily change the volume of the sound but you have to be careful about it as well.
- master volume — For all applicable channels, master volume is loaded. Sound effects may disable master volume checks. Depending on channel type, volume is loaded from either mMasterVolFM, mMasterVolDAC or mMasterVolPSG
- volume envelopes — Volume envelopes are processed every frame. Read more in the next part.
- total level in FM channels — Each FM channel has 4 total level values for each operator. Slot operators are affected by volume calculation, while non-slot aren't.
- underwater mode for FM — Underwater mode also affects the volume calculation and non-slot total level value.
- PCM audio for DAC channels — The PCM audio data itself is basically just a list of volumes, so it makes sense it can affect how loud the volume is.
- PSG audio filtering on real hardware — PSG audio is also somewhat different relatively to FM and DAC audio in real systems. This is because of inconsistent filters and buggy implementation. AMPS cannot do anything about this problem however.
The final step of this process is to send the volume to hardware. In case of PSG, this is a single PSG command containing the volume. For FM, it is 4 total level operators per channel. For DAC, however, it is a bit more complex. Dual PCM uses a volume filter bank with $80 levels of volume to scale the PCM input volume. This allows for very complex filters or a simple filter that AMPS comes with. By default, AMPS uses a logarithmic filter that closely mimics FM volume (but not 100%). However, custom filters may be applied. There is a tracker command, ssFilter, that allows to change the bank. AMPS is very lazy with handling these filters and they can also affect sound effect channels (a filter loaded with DAC1 on music will affect SFX DAC1, and vice versa), so you have to be careful. AMPS requests a volume update from Dual PCM in order to load the correct volume table in Z80 RAM, which then acts as the current filter for the current volume level. It takes some time to load the volume table in RAM, so loading a new volume only every 4 frames or more is recommended. If you load it more often, quality loss is likely to happen. AMPS tells Dual PCM to wrap the buffer around, so a part of the sample will repeat on itself. This is less distracting than the default behaviour of Dual PCM where the samples will delay for a moment. The best thing to do is avoid updating PCM volume too often.
Updating PSG and DAC volume is fairly fast for the 68k to do (even given the amount of work required), but unfortunately FM volume is noticeably slower. This is mainly because of the 4 operators that need updating, and the fact we've got to take time from Dual PCM for a while. AMPS optimizes this but at the cost of more 68k time. Avoid updating volume unless it is necessary!
Below you can see a chart comparing the effective loudness for each volume level for all the channel types. This is only an approximation, as there are many factors effecting effective and perceived volume. This chart is meant to illustrate the differences of how each channel sounds at different volume levels (Thanks to Markeyjester!):
6.2 — Underwater Mode
Underwater mode is a software effect intended to emulate the music playing under water. This is especially useful for adding more depth for water-based levels. The effect is not perfect but it often sounds convincing enough. This feature can be enabled/disabled with FEATURE_UNDERWATER. Underwater mode currently only affects FM volumes. It works by using the algorithm of the voice to read a value from the table (below), and adding part of it into slot operators (volume), and adding it fully to non-slot operators (modulator/sharpness). The table explains it below:
algorithm | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
slot offset | $00 | $00 | $00 | $00 | $02 | $06 | $06 | $07 |
nonslot offs | $08 | $08 | $08 | $08 | $0A | $0E | $0E | $0F |
This can cause issues with some instruments, that do not sound good when this algorithm is applied. For underwater levels, you may have to consider how music and sound effects sound when underwater mode is enabled.
6.3 — Volume Fading
AMPS provides a custom and extensible way to create custom volume fades. By default, only a fade in and fade out are defined. AMPS uses a list of volumes to define how to do fades. Technically, a volume fade does not need to be limited to ending or starting at a muted volume, it can be fade from any volume to any other, and can use any values. The only things that matter is that there is a starting point, and it ends with a command. The table below explains what each command does:
- $80, fEnd — Simply ends the volume fade.
- $84, fStop — Stops the current song and sound effects immediately and ends the volume fade. Also clears sound driver memory and mutes hardware.
- $88, fResVol — Resets master volume and asks channels to update their volume.
- $8C, fReset — Does the same as both fStop and fResVol.
Volume fading can be enabled programmatically from 68k by calling dLoadFade, where a4 is the volume fade data address. AMPS will automatically use the current FM volume and search the volume fade to find the closest spot to that. This helps make volume fades smooth and consistent, but it also makes it possible that your volume fade is cut short. Overall its not a good idea to rely on volume fades being consistent or taking certain amount of time, instead allowing it to take longer or shorter.
Volume filters are read 3 values per frame. The format is FM volume, DAC volume, PSG volume. Instead, a command may be used as well. Channel volumes are added to music volumes and updated with the channel after volume changes (if the volume does not change, no update happens!). Any value $80 or above is treated as a command, as it is not a valid volume anyway. Updating DAC volume more often than once per 4 frames is not recommended because this will result in audible quality loss. Volume fades are processed before sound channels. A word of warning: If any volume fade data is located before ROM address $10000, the fading will never occur. This is simply a limitation with AMPS.
7 — Envelopes
AMPS has 2 types of envelopes: Modulation envelopes and volume envelopes. Volume envelopes can be enabled for only PSG channels, or all channels, using FEATURE_DACFMVOLENV. Modulation envelopes can be enabled or disabled using FEATURE_MODENV. These two assembly flags allow to choose which settings you will need, where them being disabled is faster and uses less RAM, but also allows for less features. The algorithm for both work very similarly, but modulation envelopes have an additional step. This will be mentioned later.
Essentially, the algorithm simply looks for the next value in sequence every frame. There are volumes/frequencies and commands, and each envelope must end on a command. When a value is read from the volume envelope, if it's between $00 and $7F, or $8C to $FF. Negative values apply a negative offset to volume or frequency. Values read from the envelope data signify the end of the envelope processing for the frame. The next byte is read next frame, and the current byte is used to calculate output volume or frequency offset. In case of volume envelopes, it is simply added to channel volume for the current frame (result is not saved), and modulation envelopes are slightly more complex. For modulation envelopes, the modulation sensitivity is used to multiply the read value. A sensitivity of $04 with read value of $07 results in offset of $23. Since the sensitivity read used is actually from $01 to $100 instead of $00 to $FF. This can be set with a separate command. Below is the list of commands available:
- $80, eReset — Restart to the start of the volume envelope. Continues execution from the beginning.
- $82, eHold — Holds the volume or frequency offset at the last read value. Technically, the position is always decremented and value is read again.
- $84, eLoop x — Set offset in volume envelope data to x. A value of 0 is equivalent to eReset. Continues execution from the loop point.
- $86, eStop — Note off the channel and stop envelope processing.
- $88, esSens x — Sets the modulation envelope sensitivity to x. Only valid for modulation envelopes! Continues execution afterwards.
- $8A, eaSens x — Adds x to the modulation envelope sensitivity. Only valid for modulation envelopes! Continues execution afterwards.
Some commands stop processing the envelope processing while others continue afterwards. Envelopes are defined in code/Data.asm, in VolEnvs and ModEnvs. They define a list of envelopes and afterwards their data. You can look at examples in these lists. Volume envelopes get the vd prefix for the data and v prefix for the equate. Modulation envelopes get the md envelope for data and m prefix for equates. Special equates, vNone and mNone refer to non-existent envelopes, that just skip the envelope process entirely. They have no effect to volume or frequency.
8 — Communication Bytes
AMPS allows trackers to communicate with the program using communications bytes. By default, 8 bytes are defined that are accessible to both read & write from the tracker. You can conditionally execute tracker code when checking the communications byte with a condition mode. Whenever condition is set to false, number of commands are skipped instead of executing them. This can be used to skip commands, for example jumping or changing voice, to split the single path into multiple paths. This could be used for all kinds of neat effects, dynamic songs, songs that have ending point as opposed to just fading out, things like this. It is entirely up to the programmer on how they decide to accomplish this. This earlier part also lists which commands will be skipped.
The 8 bytes can be accessed through mComm to mComm+7. The program is free to manipulate these in any way and the programmer is responsible for deciding which communications bytes are used for what purpose. Communications bytes are not initialized to any specific value, so relying on them being, for example, 0 is not advised. Communication bytes do not get reset ever with AMPS, so sharing them between songs, sound effects or other is possible. It is a good idea to reset all the communications bytes you intend to use for songs with tracker code. Here is an example of reading and writing to the communications flags:
Communications bytes can be extended up to 256 bytes if the programmer needs that, but you have to remember, only 16 are available using the sCond command. Additionally, sCondReg command can be used to read from pre-defined RAM addresses or channel offsets. dcCondRegTable contains the table of pointers that are used for reading with this command.
9 — Memory Map
Below is the memory map for every individual channel and the entire sound driver. You can select various modes depending on which flags you have enabled, and the correct sequence is computed for you. This memory map is merely for reference purposes and you should never use the offsets provided here for actual work. The equates exist to make portability and consistency in mind and therefore are highly recommended to be used at all times.
Global
Channel
10 — Creating Custom Music
To be written in the future.
11 — Porting Music And Sound Effects
Due to the difficulty of porting music from non-SMPS games, it will not be covered here at all. Although I could cover it, it would be massively complicated and largely unhelpful. Instead, this part will list the kind of information you need to port music. Since AMPS is SMPS2ASM format exclusive, this tutorial will only talk about the aspects of SMPS2ASM. For more information to correctly converting music, see MDMusicPlayer. Since AMPS uses a custom version of the standard SMPS2ASM format created by Aurora, using older versions (Cinossu's S1SMPS2ASM or Flamewing's SMPS2ASM) is not recommended, as those require manual conversion into AMPS format. Unlike most SMPS drivers, AMPS does not generally use separate tracker commands for adjusting various things about channels (for example, no adding FM or PSG volume with different commands), so these are to be converted by hand. Here is a full list of common commands and their conversions, and various notes about other commands:
SMPS command | AMPS command | Additional notes |
saVolFM | saVol | |
saVolPSG | saVol | You must remember to multiply the volume by $08 for every instance. |
ssVolS3K | ssVol | Volume is negated in a strange way in S3K, but Aurora's SMPS2ASM will disassemble it correctly. If the volume sounds weird, it may be an older file without correct conversion applied. |
sPatFM | sVoice | |
sVolEnvPSG | sVolEnv | Previously, this would have been converted as sVoice, but since FEATURE_FMDACVOLENV was implemented, it should no longer be used. |
ssMod68k | sModAMPS | The resulting modulation will be slightly different. See documentation about modulation for more info. |
ssModZ80 | sModAMPS | The resulting modulation will be significantly different. See documentation about modulation for more info. |
sYMcmd | sCmdYM | sCmdYM can correctly take in account the current channel type. |
sYM1cmd | sCmdYM | There are some differences in how these commands function. |
sNoteTimeOut | sGate | sNoteTimeOut was the previous name for sGate, and may still be in use with your file. This is not supported for sound effects. |
sSSGEG | Currently you cannot set SSG-EG from tracker. This must be enabled in FM voice. | |
sModEnv | This command with 2 parameters is not supported in AMPS. You could use sCond to check for channel type bits, and execute code based on that. | |
saVolFMP | Not supported in AMPS. You could use sCond to check for channel type bits, and execute code based on that. | |
sComm | The first parameter in AMPS is the command byte number. SMPS only supported 1 of them, which is why that parameter does not exist. Usually, you would use 0 as the first parameter in this case. | |
sPanAni | Not supported in AMPS. | |
sSpecFM3 | Not supported in AMPS currently. | |
sMusPause | Not supported in AMPS. | |
sFadeSpec | Not supported in AMPS. | |
sMuteStopFM | Not supported in AMPS. | |
sPlayDAC | Not supported in AMPS. | |
sPitchSlide | Not supported in AMPS. | |
sRawFreq | Not supported in AMPS. | |
sCopyMem | Not supported in AMPS. | |
saTimerA | Not supported in AMPS. | |
ssTimerA | Not supported in AMPS. | |
ssTimingSFX | Not supported in AMPS. |
The header of music and sound effects is different in AMPS than SMPS. Since AMPS uses 2-channel playback, most of the time another channel needs to be added. Also, there is no pointer to the voice table, so it needs to be moved right below the header. Sound effects do not have separate voice tables, so they have to get their voices from the global table. Here is an example music header, and below that is what it should look like when converted. Remember to observe the comments:
Now, the converted version:
You need to keep in mind that since sound effects put the voices into the global voice table, you need to relocate them there and change all references to it in the tracker code. Additionally, below is how you would change the sfx header:
Here is the edited version
Here is the table of different channel types:
Equate name | ctFM1 | ctFM2 | ctFM3 | ctFM4 | ctFM5 | ctFM6 | ctPSG1 | ctPSG2 | ctPSG3 |
Type value | $00 | $01 | $02 | $04 | $05 | $06 | $80 | $A0 | $C0 |
You must also keep in mind that different SMPS games use different volume and modulation envelope tables. Therefore, to fully port songs between games, these envelopes must be ported to AMPS. You may also look for one that is similar enough to not be very noticeable. Indeed, this is often done if the difference is already minimal. It depends on the case how you acquire the data, but you should convert it to ASM (eg, series of dc.b statements). I cannot help with this because it largely depends on the case. In the case of volume envelopes used for PSG, all the volumes must be multiplied by $08. There are various commands for envelopes as well, they are usually between $80 and $A0 (sometimes negative values are also used as part of the envelopes), and they must be converted correctly. It depends on the game which values correspond to what. These often can be found in SMPS Research Pack. Below is a table of common commands and their AMPS equivalents:
command id | SMPS research pack name | AMPS equate | notes |
80 | RESET | eReset | |
81 | HOLD | eHold | |
82 | LOOP | eLoop | |
83 | STOP | eStop | This one and the below one are equivalent for volume envelopes. |
83 | VOLSTOP_MODHOLD | eStop for volume envelopes and eHold for modulation envelopes. | |
84 | CHG_MULT | esSens | |
eaSens | Not supported by SMPS by default. |
To insert this data into AMPS, have a look in this part.
It is possible you may have to port samples between games as well. This, again, can be very difficult feat. If you wish to port samples, you will have to find the file for the sample. This can be disassembled from the game or may exist somewhere already. You will have to figure out whether it is DPCM or PCM format, and the sample rate. Often, drivers have specific "base rate", and modifiers that add to this rate. This is very specific and therefore difficult to explain. If you are unsure of how to port these samples, you may ask someone more knowledgeable to help.
However, if you have both the sample file, its format, and sample rate, it is very easy to convert it to a wav file. Simply, convert any DPCM format samples to PCM first (there are various tools for this available), and then load it into Audacity, and import as raw. You get some options after you've selected the file. Simply, the file needs to be "uncompressed 8-bit PCM", at the sample rate you found out, 1-channel mono and big-endian. In theory, the sample should be imported correctly. Then, change the sample rate to the sample rate of Dual PCM (typically this would be 14000Hz), and export the sample as 8-bit wav. This will ensure that the minimal amount of quality loss happens. However, Audacity has some options that can affect the sample quality negatively as well. It is a good idea to try playing around with them to find the most optimal settings. Next, drag & drop the new wav file into ConvPCM, and the SWF sample file will be generated, ready to use. You can follow these instructions to add your sample.
Within the tracker file, you can change references to a sample name to the name of the new sample you added, or any sample you want to replace it with. This in most cases should work as a simple text replacement over the entire file.
There are a few differences in various SMPS versions, that need to be considered when porting music. These have been taken from the SMPS research pack, and adapted to be specific to AMPS:
- There are minor differences between frequency tables between 68k SMPS, Z80 SMPS and AMPS. Most of the time this does not matter but can lead to some unintended glitches.
- In SMPS Z80, playing negative PSG frequencies can lead to PSG commands being sent unintentionally. This leads to strange behaviour, because of the way the PSG commands are calculated.
- In Z80 SMPS, playing a note, rest, delay, delay will play the note again at the last delay. In AMPS and 68k SMPS, rest will be played twice.
- Tick multiplier only affects delays in 68k SMPS and AMPS, but also affects gate in Z80 SMPS.
- In SMPS 68k and SMPS Z80, volumes between $80 and $FF are treated as the maximum channel volume, while in AMPS is treated as a negative (minimum) volume.
- Modulation envelope sensitivity is signed in AMPS and 68k SMPS, but unsigned in Z80 SMPS. Additionally, in AMPS and Z80 SMPS, the multiplier is from 1 to $100, while in 68k SMPS it is 0 to $FF.
- The bit 7 (value greater than or equal to $80) is used to determine the slot operators for total level values in AMPS and Z80 SMPS, while in 68k SMPS uses another table to determine it.
- AMPS, 68k SMPS and Z80 SMPS all have different modulation algorithms.
- Modulation parameters are not reset in SMPS Z80, in AMPS only <wait> and <step> are reset, and in SMPS 68k all are reset when the modulation command (sModAMPS, ssMod68k or ssModZ80) is played. This makes a difference when modulation command is used when note is held. While normally, the parameters eventually get reset anyway, in SMPS 68k, they are also reset when note is held.
- When <count> is $00 in AMPS, the length of modulation is infinite. The <count> is never negated, which makes the modulation only go in one direction.
- In SMPS Z80 and AMPS, the same frame as note is played, the modulation is updated, while in SMPS 68k, it is not.
12 — Installing AMPS
This part will not tell you how to install AMPS into a pre-existing ROM hack or fully completed project, as the method for converting a pre-existing sound driver to be AMPS instead is difficult to estimate or give advice on. There are reference implementations available for many of the most common disassemblies already. Instead, this tutorial will cover the basics you need to know to successfully implement AMPS into a new project of yours. This could be a homebrew game or an application for example.
There are a few files you need to include into the main assembly file. This part will use ASM68K syntax but should be easily applicable to AS too. These following files need to exist at the very start of assembly (beginning of your main asm file):
The following code includes AMPS itself and an uncompressed version of Dual PCM. To use compression, you have to come up with your own solution:
This is the equivalent code for AS:
code/macro.asm needs to be edited in order for the installation to be successful. Specifically, you can toggle the various flags at the top to enable the features you want, and you need to scroll down to the line starting with mFlags. The line above it may have a RAM address or an equate, that you need to change to suit your software. This will redefine the RAM addressing for AMPS so that it uses a desired slot of RAM for its operations. You will need to be aware, that any RAM address below $FF8040 is not valid for AMPS because of short addressing requirements.
Finally, your build scripts need to be updated. First, you need this line at the beginning:
And this at the end:
For AS, simply change ASM68K to AS. Now, your project should build correctly. If it does not, you may have to figure out the bug report by yourself or ask for help in for example, Discord.
Next, we need to enable AMPS running. When the program initializes, we must load Dual PCM into memory. Simply, call LoadDualPCM from the initialization code when you are sure Z80 can be started correctly. If the driver is compressed or the initialization code needs to be changed, open code/68k Initialize.asm. Finally, we need to run AMPS during vertical interrupt. Simply, calling UpdateAMPS. This should NOT be done at the beginning of vertical interrupt handler however; Due to various reasons, Dual PCM needs to some time to finish completing various tasks during V-int. Attempting to run AMPS before this is finished, is a dangerous idea. Doing a small DMA transfer before running AMPS should be enough, however. Also, there is an equate we need to load a value to. ConsoleRegion needs to have the region bits from register $A10001 loaded into it. AMPS only cares about bits 6 and 7, and will not attempt to read any other bits. If your program already stores the region somewhere, you can make ConsoleRegion point to this variable safely.
It is possible, while playing music, that AMPS does not actually work correctly. It is a good idea to first run the mus_Stop command, as this ensures that driver memory gets cleared correctly before any music is played. While in reality this should not be necessary, it's good to be safe than sorry. Additionally, checking that there is no RAM collisions or any suspicious behaviour is a good idea. If the driver still does not work and you cannot figure out some error, contact the developers for help.
13 — Frequent Questions & Answers
Do you have a Discord server?
We do indeed! If you want to get the latest AMPS news and exclusive updates, talk to the developers, ask questions and get help, do join in! We also host other related projects under the same server. Join here.
Where should I report bugs?
Preferably, you should submit an issue to the Github page of the project. However, you can also report them on our Discord or by directly messaging Aurora on SSRG, Retro, or Discord.
I have a good idea! But how should I ask for it to get implemented?
Similarly to reporting bugs, you can in the same way also do feature requests or other improvements. There is no guarantee your feature request will get implemented but it will be considered for a future version.
Why is there no installation tutorial for my preferred disassembly?
It would be impossible to make tutorials for every single possible disassembly. Instead, there are pre-made disassemblies available for many common setups. IT is advised to follow the commits for these disassemblies to port the sound driver to your specific setup. If no pre-made disassembly exists for your setup, you may request for it to be created by messaging Aurora.
How often is AMPS updated?
It depends entirely on the changes being made and my personal time available. There is no set timeline for updates. The Github repositories will update more often and a release will only be made when there is something stable to show.
Can I edit AMPS and use it for my project?
Yes, AMPS is open source and anyone can edit it to their liking. The source code is made to be as easy to modify as possible while still being optimal. You are free to use AMPS for any project as well. Although AMPS is open source and free to use for commercial purposes, because it is technically based on SMPS, which is property of SEGA, it may not be a good idea to use for commercial purposes.
Can you make a custom version of AMPS for me?
No I cannot. The source code is documented there for you, so you can figure it out by yourself.
Why does AMPS use so much ROM space?
AMPS code is highly efficient, and therefore a lot of code is actually repeated multiple times in order to save on processor cycles. This means that a lot more ROM space is used to save on processor cycles. Additionally, Dual PCM requires data for volume tables, samples, the driver itself, and various other things in order to function efficiently. AMPS attempts to reduce on ROM space requirements at times, but rarely at the cost of extra processor cycles.
14 — Changelog
This section contains information about what changes were done between different AMPS and this document revisions. This should help users be informed regarding what differences there are between any release, such as for updating their AMPS release and navigating this document to see what changes they wish to learn about.
14.1 — AMPS v1.0
Initial AMPS release.
14.2 — AMPS v2.0
Second major AMPS release. Info to be added here.
14.3 — AMPS v2.1
Revision to AMPS v2.0. This change fixes various bugs, and changes the modulation algorithm to allow a bit more freedom for music creators, and to be (nearly) compatible with SMPS Z80. Here is a list of changes:
- sFreqOn and sFreqOff are no longer tracker commands.
- New tracker commands: ssModFreq and sModReset.
- Changes to sModAMPS: Only the <wait> and <step> parameters are loaded initially. This only makes a difference when note is held. The modulation parameters are not reset, which allows the user to change how modulation works mid-note without resetting the offset. In order for the old behaviour to work, sModReset can be used after sModAMPS is used. The <count> is also changed. Now, the value is subtracted before being checked, meaning value of $00 will not start by negating the <step>. Additionally, now it is treated as infinite, meaning modulation up and down is possible for unlimited time. If <count> is $01, the value initially is set to $01 instead of $00, making short modulations still possible. When <wait> is counting down, modulation frequency is still accounted for.
- The frame a note is played, modulation, portamento and modulation envelope are all updated.
- mfbNoPAL flag is used to check whether or not PAL mode is enabled, instead of checking for the region. The flag is updated based on song header and PAL region by dPlaySnd_Music.
- Fixed a bug where DAC panning was not reset correctly for music channels, leading to issues with panning.
- Fixed a bug with modulation envelopes not working as intended.
- Added a new PSG frequency to match the PSG table exactly with SMPS Z80.
- Added a new FM frequency to help with SMPS Z80.
- Added a feature that allows easier integration with sound test engines.
- Speed shoes tempo algorithm now works by running the sound driver twice when the timeout occurs. This also means speed shoes has its own timeout instead of using the main tempo.
- AMPS Includer can list the names of all music and sfx tracks (not commands however!) now.