module formats
2024-05-20

The S3M Format

The S3M format was introduced by ScreamTracker 3, written by Sami Tammilehto of the Finnish demogroup Future Crew. It built on the STM format used in earlier versions of the tracker, which supported a more limited set of features and effects. The naming scheme of the effects, which differs from that of ProTracker but always ensures effects can be mapped back and forth between S3M and MOD files, was also carried over from earlier ScreamTracker versions.

Like with the Amiga trackers, the capabilities of ScreamTracker are closely tied to the underlying hardware. On the PC, this hardware is more heterogeneous, with ScreamTracker supporting SoundBlaster, SoundBlaster Pro, Gravis Ultrasound (GUS) as well as Adlib-compatible cards with slightly different feature sets.

Compared to MOD files, the S3M format offers some significant improvements and innovations.

What’s new?

  • a much wider range of usable octaves – ScreamTracker 3 supported octaves 0 to 7
  • more channels – the maximum is between 16 and 32, depending on how you count
  • more instruments – the tracker will let you define up to 99 of them
  • support for higher sampling rates, 16 bit and stereo instruments
  • channel panning, if supported by hardware, and a few other new or improved effects
  • a volume column
  • support for playing FM instruments, using an Adlib or SoundBlaster card
The new volume column may be the most visible innovation when just comparing an S3M file to a MOD file in a tracker. Each cell contains an optional volume value between the instrument and effect columns, which is entered as a decimal number between 0 to 64 in ScreamTracker.

From implementation perspective, the last one of the listed new features is the biggest and scariest one. Support for FM sound generation requires a Yamaha OPL chip, which is not found in modern computers anymore. But there’s good news: First, it’s not a terribly important feature. Other trackers on DOS that supported the format did not implement it, ImpulseTracker being the most relevant example. Few modules make use of OPL sound generation. Second, excellent OPL emulators are available. OPL chips were used in all kinds of 1980s and 1990s hardware, which is why various open-source emulation projects require an OPL implementation. The most popular one seems to be the implementation created for MAME, which is also used by DOSBox and SchismTracker, a modern ImpulseTracker remake. Third, the OPL chip is not a terribly complicated piece of hardware. Writing your own emulator is quite doable, and in fact coding the relevant sound generation loop can be done in less than 200 lines. I will discuss this in more detail in a separate post.

Overview

ScreamTracker allows playing samples, just like ProTracker, as well as FM instruments synthesized by the Yamaha OPL2 (or OPL3) chip found the Adlib, SoundBlaster and Ultrasound Extreme cards. There are 16 PCM channels dedicated to samples (8 left, 8 right by default) and 9 OPL channels. I will refer to these 25 channels as physical channels.

The tracker offers a total of 32 “virtual” channels in its interface that can each be assigned to any of the 25 physical channels or be left unassigned. It is possible to assign multiple virtual channels to a single physical one, but that won’t work so well, as explained below. On ScreamTracker, it’s never possible to play more notes at a time than there are physical channels.

Individual instruments are either PCM instruments (samples) or OPL instruments. The tracker will let you enter any instrument on any virtual channel, but it will only play if it is of the same type as the physical channel that the virtual channel is assigned to.

On the SoundBlaster, all PCM samples played in mono. On the SoundBlaster Pro, channels were unchangeably assigned to the left or right stereo channel, similar to the MOD format on the Amiga, although there is now an option to disable stereo playback for the whole module. Only when using a fancy and expensive Gravis Ultrasound card could the panning be initialized to one of 16 positions for each PCM channel and changed during playback.

The 9 OPL channels are always mono. They correspond to the 9 melody channels available on the OPL2 chip. The S3M format supports, in principle, an alternative mode offered by the OPL2 chip, in which you give up 3 of the 9 melody channels in exchange for an additional five rhythm instruments (drums and such). This would make a total of 11 OPL channels available, although 5 of them would be far less useful than the others, as each of them would only support a single rhythm instrument with very limited support for effects. Anyway, ScreamTracker does not actually let you use these rhythm instruments and I’m not aware of any other tracker that supports them. That may not be a big loss, as apparently rhythm mode was never popular among people making Adlib music, who often preferred defining their own rhythm instruments for general melody channels to using Yamaha’s 5 pre-designed and less flexible ones.

File Format

The S3M file structure is described very well in the TECH.DOC document bundled with the tracker, which is available online at [S3M]. Another excellent documentation can be found at [MMW], and [MWS] provides a concise overview of the format. Instead of repeating the detailed information provided elsewhere, I will therefore just offer some comments on [S3M].

ScreamTracker runs in DOS, so in contrast to MOD files, which originate on the Amiga, all words are little-endian now. In addition to that, we see traces of the 20-bit addressing scheme introduced with the Intel 8086 line of processors and used by DOS until the end. S3M files contain “parapointers,” which are segment addresses relative to the start of the file. Just multiply them by 16 to get the byte offset from the beginning of the file.

ModuleHeader

Cwt/v: [MMW] has an updated list of tracker signatures found in this field, which can be relevant for compatibility settings.

Channel settings: This array maps the 32 virtual channels to the physical channels. If bit 7 is set, the channel is disabled, a value of $FF stands for not used. This difference does not matter much for players, in either case the channel will not be processed at all. Assigning multiple virtual channels to a single physical one results in strange behaviour.

d.p (default panning): only read and use the per-channel panning values provided in the 32-byte array at the end of the header if this value is 252. Otherwise, use the standard panning positions for the channel (3 for left, $C for right).

g.v (global volume): Initial value for global volume, which can be changed with effect Vxx. Global volume is not some sort of mixing volume that immediately affects the audio stream; it is a parameter that is used when calculating effective note volumes in individual channels. This difference matters, because changing the global volume will do nothing to the volume of a note until it needs to be recalculated for some other reason.

m.v (master volume): This is the internal mixing volume used. It affects all PCM channels and cannot be changed during playback. This value would not be interesting if it weren’t for the fact that it effectively scales the volume of PCM channels compared to OPL channels, which are never affected by this setting. However, this only worked on SoundBlasters, not on the GUS. Also notice that when stereo is turned on, master volume is effectively scaled by \(\frac{11}{8}\), but never exceeds its maximum of $7F.

Some fields like “special”, “ExtHead” and u.c are likely irrelevant to playback, except possibly for detecting the tracker the file was made with.

More importantly, note that the volume-slides compatibility flag (bit 6 of the flags register) is implicitly set for a particular tracker version, Cwt/v=$1300.

The pattern table is called “order list”. It has two new features. A value of $FF means “end of song” and $FE is “just a marker”. The end-of-song marker is an extremely useful addition, which greatly simplifies playing a module once and fully without having to analyze its structure and guessing where the intended end might be. The just-a-marker marker should be ignored when encountered in the pattern table; just continue with the following song position. Its meaning is, to the best of my knowledge, not codified. Some players use it to identify “subsongs” that can be individually selected (the MOD archive does this, for example [TMA]).

InstrumentHeader

An instrument can either be a sample or an OPL instrument. In both cases, the instrument header is very similar. Importantly, both have a volume and a C4Spd (called C2Spd in [S3M]), which is required for period calculation.

Samples: The sample format (now allowing for stereo, signed/unsigned and 16-bit samples) as well as the looping info should be straightforward. There’s a lot of “internal” data saved here, which can generally be ignored. However, if one is interested in whether a module was saved on a system with a SoundBlaster or a GUS, which may be of interest for authentic playback, these fields are truly a treasure trove of information as explained in [SMD]. Note to self: Implement decompressor when encountering the first “packed” sample.

OPL: Don’t expect to find instrument types 3-7 here, as they don’t seem to be supported in practice. Bytes D0 through DA contain the data that is sent to the OPL chip to define an instrument. Details will be discussed below. No idea what the field Dsk is supposed to be.

Patterns

In comparison to the MOD format, pattern cells contain an additional value (the volume column), the effect number is now a full byte, and the note is stored differently. Rather than directly providing a period value, the the S3M format uses a note byte that normally contains the octave in the high nibble (0 to 7) and a note index in the lower nibble (0 to 11 for C to B). A note byte of $FE means note-off. Also note that there are special null values of $FF (note, volume effect) and $00 (instrument), which comes in handy. The patterns are packed. Initialize a pattern array with null values, then unpack into that data structure as described in [S3M].

Playing the Module

The existence of a tracker version field in S3M files makes it easy, in principle, to know what tracker to emulate during playback. Apart from ScreamTracker, ImpulseTracker is another important tracker from the era that supports creating S3M files. Its characteristics will be discussed in another post in the context of the IT module format. OpenMPT as current tracker attempts to recreate as authentic a playback environment for S3M modules as possible. SchismTracker, another tracker that is still in active development, intends to faithfully reimplement ImpulseTracker and is based on the same tracker engine as OpenMPT. This means that for all practical purposes, there's really just two “correct” ways of playing an S3M module: the ScreamTracker way and the ImpulseTracker way.

This section explains how ScreamTracker wants us to play S3M modules. Some features and characteristics differ between OPL and PCM channels, and even depend on whether a PCM channel is played on a SoundBlaster or GUS card. I am assuming that most people are less interested in OPL-specific information, which is why the section on using OPL instruments is at the end of this post. If you do want to know how to play OPL channels, I suggest reading that section now. Then some of the information provided below will make more sense.

Periods and Frequencies

In MOD files, the period of a C2, the “middle C” in the available range of octaves 1 to 3, is 428. An instrument played at this note has a “speed” or sample playback rate of \(\frac{3.58\text{ MHz}}{428}\approx 8063\text{ Hz}\) on an NTSC Amiga. This value has become the reference point for Amiga sample rates.

Running on a PC, ScreamTracker 3 is not subject to the same sample rate limits and offers a much-increased spectrum of playable notes spanning 8 octaves. When loading a MOD file, ScreamTracker relabels all notes by adding 2 octaves. The note that was called C2 in ProTracker is now called C4. As mentioned in the introduction, the naming of these notes is not particularly meaningful anyway, as notes only control the rate at which samples are played.

In addition to remapping the ProTracker notes roughly to the middle of its own note spectrum, the S3M format also increases the resolution of the period variable, scaling it by a factor of four. This opens up higher octaves (with low period values) without losing too much precision and also enables finer frequency adjustments through effects. All of the familiar pitch-changing effects (portamento, fine portamento, tone portamento, vibrato) continue to work as before – effect parameters that affect periods are internally multiplied by 4; but there are now new versions of some effects (extra fine slides, fine vibrato) that take advantage of the higher precision.

ScreamTracker also abandons the MOD method to tweaking sample pitches, which was based on “finetune” adjustments by eighths of a semitone, for simpler and more general approach, where the sample rate for the reference note of C4 is directly specified. This is the C4Spd value found in all instrument headers (the official docs [S3M] call it C2Spd, but do point out that it should have been called C4Spd). The sample rate can now be pretty much any value, and rates of for example 44.1 kHz are not uncommon in S3M modules. Higher sample rates for an instrument mean that lower period values will be used for the same note.

The formula to calculate the period should be \(p_\text{note}=428\cdot 4\frac{8363}{\text{C4Spd}}\frac{f_\text{C4}}{f_\text{note}}\), where \(f_\text{note}\) stands for the frequency of a note; then, if an Amiga sample with a C4Spd of 8,363 is used, the period of a C4 is indeed 428⋅4. ScreamTracker actually tabulates the period values for one octave to recreate the original ProTracker periods, which feature some creative rounding as discussed in the post on the MOD format. The resulting formula for an integer-only calculation of the period is then

p=((8363*periodTable[noteIndex]*16)>>octave)/C4Spd
for the 12-element period table {1712, 1616, ...} provided in [S3M]. (Note that the parentheses in the period formula are different from the ones in [S3M]. Applying the bit shift to the tabulated periods only results in large rounding errors that will lead to notes in high octaves being terribly off-pitch.)

The sample playback rate is then calculated such that a sample is in fact played at its C4Spd for a note of C4, \(\varphi=\frac{\Phi}{p}\), where \(\Phi\)=1,712⋅8,363 Hz=14,317,456 Hz is approximately four times the Amiga NTSC chroma clock rate. (Careful, there is a potential typo in that constant in [S3M].)

Processing Channels

ScreamTracker 3 processes the physical channels in ascending order (8 left, 8 right, 9 OPL). The processing order of the virtual channels depends on what physical channel they are assigned to. If they are deactivated or unassigned, they are not processed at all, i.e. even global effects defined in these channels do nothing.

Here is what happens if multiple virtual channels are assigned to the same physical channel. For the note, instrument and volume columns, the information from rightmost (highest index) virtual channel with a non-empty field is taken. The effect always comes from the rightmost channel, even if it has an empty effect field. Similarly, the channel panning position is determined by the highest virtual channel. Here are some examples ([note inst vol eff param], dot (.) means no value):

[C4 13 50 D02][C5 14 60 C03][.. .. .. .00] -> [C5 14 60 .00]
[.. .. 50 D02][C5 .. 60 C03][D4 .. 40 A09] -> [D4 .. 40 A09]
Modules made on other trackers liberally assign multiple virtual channels to physical channels as they see fit – the main effect of this assignment being to determine whether a channel is for PCM or OPL instruments and what the default panning should be. This makes it possible to have up to 32 PCM channels, compared to 16 on ScreamTracker. It may therefore not be ideal to emulate the ScreamTracker channel processing too strictly for modules with signatures other than $13xx in the Cwt/v field.

Processing Order in a Cell

Within the same tick, the instrument is changed first (if present), then the period is calculated (if a note is present) for triggering a note or to store as a slide target, then the volume effect is applied and finally the general effect is processed (with the exception of a few effects that are handled early, as discussed below).

Triggering Notes

As in MOD files, a cell containing an instrument and a note will trigger that instrument at its default volume and its instrument-specific period for this note. Also as in MOD files, things get more complicated when notes and instruments do not show up together.

A note in a cell, with or without an instrument, will always calculate the period for the active instrument and update the target period for the Gxx effect, whether this effect is playing or not. If there is no Gxx or Lxx effect in the cell, the active instrument will also be triggered (if it can be played on that channel).

There is a code of $FE for a note-off instruction that can be placed in a channel instead of a note. It was really introduced for OPL instruments, which can make a note fade when released. When encountered in a PCM channel, it immediately cuts the note (this is not done by setting the volume to zero or freezing playback as with the SCx command; the note cannot be restarted, it seems).

Setting an instrument even without a note changes the active instrument and immediately applies its default volume. What else happens depends on the hardware handling the instrument. PCM instruments on SoundBlaster swap out the sample immediately. If the previous sample was still playing, the new one continues at the same offset. If the old sample was finished and does not loop, nothing noticeable happens. When using the GUS, the old sample keeps on playing as it would have (except for the volume change), but the new instrument is set as active and will be used when the next note is triggered. For OPL instruments, the data for the new instrument and volume are sent to the OPL chip immediately. If a note was still playing, it changes to the new one.

There are lots of inappropriate things one can do when setting an instrument. Setting an empty (type 0) instrument on a PCM channel does nothing, the old instrument remains active. Trying to set an OPL instrument on a PCM channel will not have any immediate effect (on volume or playing sample, if any), but triggering new notes won’t work as long as the OPL instrument is active (even triggering a new note in this state will not stop a still playing valid PCM instrument in that channel). Playing anything in an OPL channel that cannot be handled (empty or PCM instrument) appears to immediately reduce the volume by about 30, probably by adding a value to the “total level” field of this instrument on the OPL chip. The volume cannot be raised anymore later. But otherwise, the previous instrument remains active, keeps on playing and can be triggered again, albeit at the reduced volume.

[MMW] explains that, even though volume values can be entered in the range from 0 to 64, they are actually clamped to \([0,63]\), with the possible exception default Adlib volumes.

Effects

The S3M format introduces a volume effect column, which replaces the “set volume” effect present in the MOD format. This means that setting a new volume can be combined with any other effect. FastTracker II and ImpulseTracker greatly enhance the capabilities of this additional data field, almost turning it into a secondary effects column.

Since its earlier versions, ScreamTracker has used a different numbering scheme for its effects, while broadly replicating the set of commands familiar from the MOD format, albeit with some tweaks and enhancements. As of version 3, all important MOD commands are available in ScreamTracker and are therefore part of the S3M format.

Quite a few commands show some rather idiosyncratic behaviour (some might call it buggy). In the discussion of the effects below, I will mention all the strange properties I am aware of and either provide some information or a reference. However, the goal is not to document every detail of every unintended feature.

When discussing the MOD format, I suggested thinking of effects as either manipulating the persistent value of a variable or a temporary version that is only valid for the current tick. ScreamTracker handles the problem of lasting vs. short-lived adjustments to the volume or period differently, and much of strange behaviour of effects and their complex and surprising interactions can be explained by this. It appears that most effects entirely operate on current, temporary values of variables, while some effects use the persistent value as a kind of backup variable, but in a sufficiently inconsistent way to lead to some seriously surprising outcomes. [MMW] refers to these current and backup values as “active” and “stored”, and provides a more complete picture of which effect changes what value, while I will focus more on the symptoms.

In the S3M file, effect A is represented as $01, B as $02, etc., up to $16 for V.

In addition to the original ScreamTracker effects documented below, there are numerous extensions to the format, many but not all of them originating from ImpulseTracker. They are listed in [MPT], and most are discussed in detail in the post on the IT format.

As in the MOD format, some effects have memory, i.e. in the case of an effect parameter of $00, the last non-zero parameter for this effect is remembered. Unlike in ProTracker, both nibbles must be zero for a memory value to be used, even for effects where they are considered separate parameters. A new feature is that many effects have “global” parameter memory, where a value of zero will recall the last non-zero effect parameter seen in this channel (whether that was combined with the same effect, a different one or no effect at all).

The following tables list all effects defined by ScreamTracker 3, including the ones that are not implemented, but reserved to map to particular MOD commands. Any remaining gaps in the list of effects were later filled in by other trackers such as ImpulseTracker and OpenMPT. For each effect, the third column lists the corresponding MOD command(s), if any. As in the post on the MOD format, the second and third column from the right show if there is more detailed information on the effect below. I avoid repeating descriptions already given in the MOD post. In the last column of the table, "G" indicates global memory and "E" effect-specific memory.

effectnameMODdescriptionDTM
AxxSet SpeedFxxset ticks per row to xx; for xx=0, do nothing---
BxxPosition JumpBxxafter processing this row, set the song position to xx and continue with row 0 of the corresponding patternD--
CxyPattern BreakDxyafter processing this row, continue playing with row 10⋅x+y of the next song position; note that xy is decimalD--
Dxy(Fine) Volume SlideAxx, EAx, EBxsee descriptionDTG
Exy(Fine/Extra Fine) Portamento Down2xx, E2xdecrease pitch by xy on every tick except the first
if (tick>0) && (x<$E) p=p+xy*4
or finely decrease pitch by y on the first tick (effect EFx)
if (tick==0) && (x==$F) p=p+y*4
or extra finely decrease pitch by y on the first tick (effect EEx)
if (tick==0) && (x==$E) p=p+y
D-G
Fxy(Fine/Extra Fine) Portamento Up1xx, E1xanalogue to Exy--G
GxxTone Portamento3xxset portamento target if note is provided and slide towards the target by xx on every tick except the first; glissando (effect S1x) modifies the type of the slideDTE
HxyVibrato4xyperform vibrato with speed x and depth y using the waveform set by effect S3x on every tick except the firstD-E
IxyTremorturn the volume off and on repeatedly for the duration of the effect; on-time is x+1 ticks, off-time is y+1 ticksDTG
JxyArpeggio0xyon every tick, cycle between note, note+x semitones, note+y semitones-TG
KxyVolume Slide with Vibrato6xyequivalent to Dxy with H00-TG
LxyVolume Slide with Tone Portamento5xyequivalent to Dxy with G00-TG
OxxSet Sample Offset9xxset the starting offset of the current sample to 256⋅xx-TE
QxyRetriggerE9xif y>0, retrigger the current instrument every y ticks, using x to select a tabulated volume change; for y=0 do nothingDTE
RxyTremolo7xyperform tremolo with speed x and depth y using the waveform set by effect S4x on every tick except the firstDTG
TxxSet TempoFxxset tempo to xx if xx>=$21D--
UxyFine Vibratoperform vibrato with speed x and depth y/4 using the waveform set by effect S3x on every tick except the firstD-E
VxxSet Global Volumeset the global volume to xx if xx<=$40DT-
Xxx(MOD 8xx)(8xx)unimplemented unused MOD command---
Despite having a hugely increased space of possible effect numbers available, ScreamTracker follows the ProTracker approach of cramming a number of different short-parameter effects into a single major effect, mapping most of MOD’s Exy effects to S3M’s Sxy. Some MOD Exy effects are not implemented but still assigned effect number, allowing ScreamTracker to load, edit and save MOD files without losing information.

In many ways, Sxy is a regular effect, and it uses global memory. Executing S00 will run the sub effect given by the previous non-zero effect parameter in the channel. For example, DC1 followed by S00 will result in a note cut (effect SCx). For the SDx effect (note delay), which needs to be checked prior to the usual effect evaluation, using S00 leads to unusual outcomes (see below).
effectnameMODdescriptionDTM
S0x(Set Filter)E0xunimplemented MOD command---
S1xGlissando ControlE3xenable (x=1) or disable (x=0) glissando for effect Gxx-T-
S2xSet Finetune ValueE5xset the MOD finetune value for the current instrument to xDT-
S3xSet Vibrato WaveformE4xchoose the waveform to use with effects Hxy and UxyD--
S4xSet Tremolo WaveformE7xchoose the waveform to use with effect RxyD--
S8xSet Panning Position(E8x)set the panning position to x, where 0 is left and $F is rightDT-
SAx(Old Stereo Control)unimplemented ScreamTracker legacy effect---
SBxLoop PatternE6xset the current row as the loop target (x=0) or jump back to the loop target x times from the current row (x>0)-T-
SCxNote CutECxturn the current note off after x>0 ticks; for x=0, do nothing-T-
SDxNote DelayEDxdelay triggering or releasing a note by x ticks-T-
SExPattern DelayEExincrease the number of ticks in this row from T to (1+x)⋅T-T-
SFx(FunkRepeat)EFxunimplemented MOD command---

Bxx – Position Jump

This effect works for any value of xx; when jumping beyond the end of the song, song position 0 is played. As is the case with the MOD format, position jump (Bxx) and pattern break (Cxy) can be combined on the same row to jump to any row at any song position. In contrast to MOD files, the order of the two commands does not matter for the combined effect to work as expected.

Cxy – Pattern Break

See Bxx for the combined effect. The parameter xy is decimal.
Tracker-specific:
If an illegal row number greater than 63 is specified, the effect is ignored.

Dxy – (Fine) Volume Slide

The behaviour of this effect is way too complicated.

Dx0 increases the volume by x on every tick except the first; if x=$F or fast slides are enabled, the effect operates on every tick.

D0y decreases the volume by y on every tick except the first; if y=$F or fast slides are enabled, the effect operates on every tick.

DxF finely increases the volume by x on the first tick only.

DFy finely decreases the volume by y on the first tick only.

Remember that fast slides are active if the corresponding flag is set in the header or the tracker version is $1300. The [MPT] effect description states (emphasis added): “If y is Fh and Fast Volume Slides are enabled, volume decreases on every tick.” This is not the behaviour I observed on ScreamTracker 3.21.

Tracker-specific:
See [MMW] for details on the testing order of the effect parameter.

Exy - (Fine/Extra Fine) Portamento Down

As indicated in the table, the upper nibble y of the effect parameter selects finer versions of the effect, similar to Dxy.

The regular slide and the fine slide use the same units as the MOD format, i.e. E12 in an S3M file leads to the same change in pitch as the effect 212 in a MOD file. As the period is 4 times finer than in the MOD format, the effect parameter gets scaled by a factor of 4 in ScreamTracker. Extra fine slides do not scale the parameter and thus take advantage of the more precise pitch representation. DE8 has the same effect as DF2.

Gxx - Tone Portamento

As with the less fine Exy and Fxy effects, the effect parameter is specified in MOD-compatible Amiga units, i.e. needs to be scaled by 4 before adding to or subtracting from the period.
Tracker-specific:
Any note that appears in the channel sets a new slide target, whether it is in the same cell as a Gxx effect or not. In contrast to ProTracker, ScreamTracker does not clear the slide target after reaching it. You can slide to the target, then modify the pitch using Exy/Fxy, then slide to the same target again. The slide target is, as one would expect, stored as a period, not a note. This means that switching to a sample with a different C4Spd after specifying the target period will result in that sample effectively sliding to the wrong note.

I noticed that Gxx did not update the pitch in Adlib channels while active; after Gxx is done, it seems to play the target note, whether it would have been reached or not.

Hxy – Vibrato

Vibrato is executed on every tick except the first and works very much like in the MOD format; the speed and depth parameters have the same meaning. A speed of 0 is possible and works as expected (in MOD files, zero speed cannot be entered due to the way effect memory works). Hxy produces maximum pitch changes corresponding to those achieved with (regular fine) portamento of 2⋅y. Hxy shares its parameter memory with the Uxy (fine vibrato) effect.

Ixy – Tremor

Tremor operates on every tick of the row. The underlying counter is never reset, so it is possible to perform this effect consistently across rows.
Tracker-specific:
This effect is noticeably buggy. If the tremor volume is off when the effect ends, it is not turned back on. [MMW] has all the details.

Moreover, it does not work properly with Adlib channels. It does turn the volume down when it should, but never turns it back up.

Jxy – Arpeggio

Tracker-specific:
In principle, this effect works exactly like in ProTracker. In practice, it is buggy in that it does not interact well with portamento effects. For example, if I change the pitch of a note with Exx/Fxx prior to using arpeggio, the Jxy effect itself is performed as if the pitch change had never happened. Once arpeggio is over, the note again plays at the correct frequency. If, however, Jxy is followed by an Exy of Fxy effect, those will start their slide at whatever note was played last by arpeggio (this could be the last note triggered in the channel, or that note +x or +y semitones, depending on the number of ticks per row). There may be more strange things going on, but this is where I stopped investigating.

Kxy/Lxy – Volume Slide with Vibrato/Tone Portamento

Tracker-specific:
This is really just the combined volume slide/pitch change effect familiar from the MOD format, where the pitch-related parameters are taken from their effect memory. Only, it is another buggy effect. It does not operate on the first tick and does not work at all with fine volume slides. [MMW] has the details.

Oxx – Set Sample Offset

Tracker-specific:
What this effect really does is it changes the starting offset for the current instrument to 256⋅xx, whether or not a note is being triggered on the row. A currently playing note is not affected. This offset is remembered until a new instrument is set. This means that the instrument can be triggered repeatedly at the selected offset as long as no new instrument shows up in the channel.

Qxy – Retrigger

Retriggers the note every y ticks. If the previous row does not contain a Qxy effect, the retrigger counter starts at 0 (i.e. the first retrigger happens on tick x, but triggering may happen on tick 0 if there is a note in the cell). If there are contiguous Qxy effects on multiple rows, the counter is not reset, allowing for consistent use of the effect across rows, see [MMW].

Whenever the note is retriggered, its volume is changed depending on x according to the following table:

x01234567
vol chg0-1-2-4-8-16*2/3/2
x89$A$B$C$D$E$F
vol chg0124816*3/2*2

This volume change is relative to the currently active volume in the channel, not a default instrument volume or something like that. The point is that the note can be made louder or quieter with every retrigger.

Tracker-specific:
Retrigger triggers the PCM instruments from offset 0, not the offset chosen by an Oxx effect. Also, for x=6, the volume is changed by a factor of \(\frac{5}{8}\) rather than \(\frac{2}{3}\). Why? So that the change can be calculated with two shifts and an addition rather than an expensive division.

For OPL instruments, this effect does not appear to work at all.

Rxy – Tremolo

This effect works much like the MOD version, except that with an amplitude of 2y, it is only half as strong. The speed x is still such that it takes 64/x ticks to step through a full waveform period. In contrast to MOD files, it is practically possible to select a speed of 0 using the Rxy command.
Tracker-specific:
The effect does nothing if the “stored” volume is 0 (or 64, which is possible only in Adlib channels), see [MMW]. It only operates on non-zero ticks. On ProTracker, skipping tick 0 leads to abrupt and unpleasant volume changes around that tick. On ScreamTracker, this is not an issue because the volume of the instrument is left wherever it happens to be when the effect ends, making this another effect that does not clean up after itself (similar to tremor). [MMW] has the details.

It seemed to me that this effect only works on Adlib channels if the note volume has been explicitly set prior, no matter what the instrument volume is; maybe instrument changes don’t affect “stored” values?

Txx – Set Tempo

Set the current tempo for xx>=$21. For compatibility with other trackers, allowing xx=$20 may be reasonable. For lower values of xx, do nothing. The T0x and T1x tempo slides mentioned in [MPT] are ImpulseTracker effects that do not work on ScreamTracker.

Uxy – Fine Vibrato

This works exactly like Hxy, except that the effect on the period value is 4 times finer; it is not rescaled to match Amiga/MOD period values. The meaning of the speed parameter x is the same and the waveform settings and effect memory are shared with Hxy. So, what’s the point? The ScreamTracker manual suggests using this effect with higher octaves, when the regular vibrato effect is too crude due to short period lengths. It may also come in handy when working with higher sample rates.

The effect memory stores the effect parameter, not the actual vibrato depth. This matters because of the shared effect memory. Hx2 followed by U00 will play a fine vibrato of Ux2, not Ux8.

Vxx – Set Global Volume

This effect changes the global volume that was initially set to the value provided in the module header. For values of xx above 64, the effect does nothing.
Tracker-specific:
This effect does not change the volume of playing notes. As mentioned before, global volume is a parameter that is used to calculate the effective volume in a channel. Unless the volume of the note is recalculated for some reason (such as a new instrument, a volume effect, a Dxy effect), a change in global volume has no immediate effect. Moreover, Vxx is applied on tick 1, i.e. after instruments are updated and volume effects are processed, so it typically won’t have the desired effect on the same row. [MMW] discusses lots of possible scenarios.

S1x - Glissando Control

Tracker-specific:
ScreamTracker disables glissando for x=0, enables it otherwise.

S2x – Set Finetune Value

In MOD files, the finetune setting is used to adjust the playback frequency of a sample by a few eighths of a semitone. S3M files don’t need that, as their C4Spd can be chosen more flexibly. This command is provided only for MOD compatibility, allegedly setting the C4Spd of the current instrument to the Amiga standard value of 8,363 adjusted by a finetune value. Here’s the table provided in the ScreamTracker 3 docs ([STM]):

x01234567
C4Spd7,8957,9417,9858,0468,1078,1698,2328,280
x89$A$B$C$D$E$F
C4Spd8,3638,4138,4638,5298,5818,6518,7238,757

For x=8, there is no adjustment. The values are oddly spaced, possibly to replicate ProTracker’s finetune values, possibly because there are errors or typos.

This effect is really only provided for ProTracker compatibility, so that ScreamTracker can correctly play back MOD files that use the E5x effect. It will not work as expected on samples with a C4Spd different from 8,363. And, in fact, it may not work at all.

Tracker-specific:
I’d love to say more about this command, but I could not get it to do anything at all in ScreamTracker 3.21. It does not even seem to work after loading a MOD file, so this is not a matter of compatibility settings. The effect is officially unimplemented in ImpulseTracker, but works as expected in OpenMPT. The fact that no one seems to have complained about the missing S2x effect tells us something about its importance.

S3x/S4x – Set Vibrato/Tremolo Waveform

Like ProTracker, ScreamTracker lets you choose between different waveforms to use for vibrato and tremolo. As in ProTracker, the length of a whole period 64 steps, i.e. when using a speed of 1 in the Hxy, Uxy or Rxy commands, it takes 64 non-zero ticks to go through one waveform period. Note that speeds of 0 work perfectly fine and can produce a non-time-varying effect on the period or volume.

[MMW] insists that ScreamTracker uses 256-element tables for the waves, vaguely implying that fine vibrato Uxy uses finer steps. This is not what I observed. I think there is some confusion with ImpulseTracker’s Yxy (panbrello) command, which does use a 256-element table.

Like for ProTracker, the waveforms should be interpreted as real-valued functions with a codomain of [-1,1], with the understanding that they are really implemented as tabulated fixed-point values with a precision of at least 5 bits (6 bits including the sign), but more likely 7+1 bits.

As usual, the lowest two bits of the effect parameter x select the waveform (default is 0), if bit 2 is set, the wave index is not reset to zero upon triggering a note (default: bit not set), bit 3 is ignored.

The four supported waveforms are:

0:sine wave:wave0[i]=sin(2*pi*i/64)
1:ramp down:wave1[i]=i/32-1
2:square wave:wave2[i]=i<32?1:0
3:random:wave3[i]=?

Two things to note: First, in contrast to ProTracker's "ramp down," this one starts at -1 rather than 0. Second, the square wave never drops below zero.

ScreamTracker does offer a functional “random” waveform, which is more irregular than the other ones. I quite like it. It is composed of pseudo-random numbers that are distributed in a way such that their autocorrelation depends on the speed parameter in a meaningful way, generally making the wave sound faster and more random for higher speed settings. All this is calculated on the fly in a very economical manner by ... drum roll ... stepping through the wave0 sine table at a rate of 14+speed rather than speed.

S8x – Set Panning Position

The panning position is valid for the channel (not just the note). This effect changes the channel-specific panning setting defined in the header or the channel’s default panning position of either 3 or $C. Presumably, this should not work for modules designated as mono. With 16 possible values, there is no real centre; a value of 7 is used to approximate central panning.
Tracker-specific:
On ScreamTracker, panning only works for PCM channels playing on a GUS card. Adlib channels are always mono.

SBx – Loop Pattern

Tracker-specific:
Like ProTracker, ScreamTracker 3 uses global variables to store the counter and jump target. This means no nesting loops across channels. Unlike ProTracker, ScreamTracker 3 prevents infinite loops by setting the loop target to the current row+1 after completing the lat iteration of a loop. If two SBx (x>0) commands follow each other with no SB0 in between, the second one will not loop back beyond the first one, thus avoiding interference with its counter.
if (x==0) loopTargetRow=row
else
    if (globalLoopCounter<=0)
        globalLoopCounter=x
        row_next=loopTargetRow
    else
        globalLoopCounter-=1
        if (globalLoopCounter>0) row_next=loopTargetRow
        else row_next=(row+1) & $3F
Moreover, the tracker resets the jump target to zero when starting a new pattern.

Otherwise, the implementation seems equivalent to that on ProTracker.

SCx – Note Cut

Tracker-specific:
ScreamTracker appears to be doing this by setting the rate at which it is stepping through the sample to zero, rather than changing the volume. The current note can be revived by executing a command that leads to the effective period or sample playback rate being recalculated. See [MMW].

This effect does not work in Adlib channels.

SDx – Note Delay

Tracker-specific:
This also delays the volume effect or the release of a note (note=$FE). As mentioned before, if the note delay is created with an S00 effect, this will not prevent the triggering or volume effect on tick 0 but will still trigger again on tick x.

SEx – Pattern Delay

Tracker-specific:
The SEx command appears to only update the global delay variable if that has a value of zero. In the case of multiple SEx effects on a row, the command with a parameter of x>0 in the lowest-number physical channel thus determines the delay on this row.

Adlib Limitations

Effects may not work on OPL channels, or work differently. Some don’t make sense on Adlib channels, some are not supported due to hardware limitations, some are simply not implemented and some are buggy. Here’s a list:
won’t work:
The one effect that makes no sense without a sample is Oxx, set sample offset.
unsupported in ScreamTracker for a good reason:

S8x, set panning: On the sound cards in common use when the tracker was made, OPL sound was largely a mono affair, so panning would not work. It’s possible to provide this functionality as an extension. OpenMPT does that to a certain extent, enabling the limited control available on the OPL3 chip used on later SoundBlaster cards. When allowing this, it may make sense to think about what implications it has for the relative volume of OPL vs PCM sound.

Vxx, set global volume: Global volume does not affect Adlib channels at all. This seems like an arbitrary limitation that’s easy to fix, but there is a possible justification for this. All OPL “volumes” are used to modify attenuation values on the OPL2 chip, which means that they are really on a log scale compared to PCM volumes. It therefore makes some sense to keep them separate from the PCM volume values.

unsupported:

SCx, note cut: This effect does nothing. Not a big deal, one can use the note-off command when desired or just turn off the volume.

Qxy, retrigger: This did nothing for me on ScreamTracker 3.21. No idea why.

buggy:

Gxx, tone portamento: This does not update the pitch continuously.

Ixy, tremor: This effect does turn down the volume a lot when it should, but never turns it back up. It’s more like a delayed note-off command in OPL channels, so maybe this is where SCx went.

Rxy, tremolo: This works in principle, but only if the note volume has been changed after setting the instrument, it seems. There may be other things going on there, I did not investigate much.

Using the OPL2 chip

I will only provide enough information here to use the OPL chip for the purpose of playing S3M files. A more complete introduction can be found in [ADL].

The OPL2 chip is an FM or frequency modulation chip, which generates sounds by combining two waves. The carrier wave determines the frequency of the tone we want to generate, while the modulator wave is used to tweak the phase of the carrier, giving it a more interesting sound. This means that for the purpose of playing one instrument, in addition to any general parameters such as pitch, there are two wave-specific sets of parameters specifying the behaviour of two “operators” (or “slots”), each of which produces of one of these waves.

As mentioned before, the OPL2 provides 9 channels, each of which can either play a note or be silent. So, in addition to some global data, the chip can be fed 9 sets of channel-specific and 18 sets of operator-specific information, for two operators per channel.

The following table, taken from page 8 of the application manual [OPL] of the YM3812 chip used on Adlib and early SoundBlaster cards, shows the memory map of the chip. The register bank is one byte wide, so many of the fields shown only contain one or a few bits.

In the address ranges from $20 to $95 and $E0 to $F5, there are 5 sets of operator-specific registers, whereas $A0 to $B8 and $C0 to $C8 contain channel-specific information. When playing a note on a particular channel, appropriate values need to be written to the registers that hold operator or channel data corresponding to the channel number of interest. These values are either provided in the OPL instrument header of the S3M file (values D00 to D0A; D0B is unused) or calculated.

The description of D00 to D0A ([S3M]) corresponds closely to what is shown in the table, so it’s fairly clear where things go. The register addresses for operator-specific data are somewhat scrambled on the OPL2 (one might expect that there should be a simple way of calculating a register offset from the channel and operator number, but this is not the case; the order is weird, and there are gaps; see [OPL], section 2-5 for details), but the address of the carrier register is always the corresponding modulator address plus 3. In what follows, I will assume that we want to use channel 0. In this case, the data from D00 to D0A should be written to OPL registers $20, $23, $40, $43, $60, $63, $80, $83, $E0, $E3, $C0, respectively.

For the most part, ScreamTracker leaves the OPL values given in the instrument header alone. The only exception are volume-related fields. In the table above, these are referred to as TL or “total level” and are specified as attenuation values on a logarithmic scale. ([S3M] appears to suggest that it also does something to SL, or SUSTAIN LEVEL, but that’s only about how the data is presented in the instrument editor in the tracker.) ScreamTracker adds \(63-v\) to TL, ensuring the result is clamped to the proper 6-bit range. Here, \(v\) is the current instrument “volume”. Normally, this is only done for the carrier (address $43), as the modulator solely serves as an input to calculating the carrier wave. This normal behaviour of OPL tone generation can be changed, however. If bit 0 of D0A (the “connection” bit C in register $C0) is set, the channel is in additive mode, in which the modulator does not actually modulate the carrier phase, instead it is added to the sample output. In this case, the total level of the modulator (register $40) is changed as well by adding \(63-v\).

When setting a new instrument, ScreamTracker writes D00 to D0A to the proper OPL registers, adjusting the TL values as needed.

The frequency of a note is specified by the fields F-NUMBER (FNUM henceforth) and BLOCK at addresses $A0 and $B0. The 10-bit value FNUM (spanning two registers) is a counter increment that determines how fast the operators in the channel play a wave; BLOCK is octave information that scales the frequency exponentially. The frequency of a note can be calculated as \(f_\text{note}=\text{FNUM}\cdot 2^{\text{BLOCK}-20}\cdot 49715.9\text{ Hz}\). The constant 49,715.9 Hz is the sample rate of the OPL chip. There are typically multiple combinations of FNUM and BLOCK that can be used to approximate a target frequency. When picking one, it should be done such that BLOCK is chosen to be small, so that at least one of the two most significant bits of FNUM is non-zero, if possible. Also, higher notes should not have lower BLOCK values. This helps with matching the target pitch accurately and is also expected by the chip when calculating some pitch-dependent characteristics of the note.

The following code is one way of obtaining the required values for a given period value in a module channel.

noteFrequency=Phi/p*C4Freq/defC4Spd
FNUM=round(noteFrequency*(1<<20)/49715.9)
BLOCK=0
while (FNUM>=(1<<10))
    FNUM>>=1
    BLOCK+=1
if (BLOCK>7)
    BLOCK=7
    FNUM=$3FF
In the code above, Phi=14,317,456 is the format-specific frequency (about 4x the corresponding value in MOD files on NTSC Amigas, as explained above) used to convert periods into data rates, p is the period as usual, C4Freq=261.626 is the frequency of the reference note C4 in Hz and defC4Spd=8363 is the default data rate for C4 of a sample, which was used to calculate the period length in the first place. (Giving Adlib instruments a different C4Spd in their instrument definition than the default will result in notes being played at a different frequency. I have not seen that done in S3M modules, but it does have reasonable applications, for example when changing the MULTI value of the carrier wave or to play notes outside of the range supported by the S3M format.)

The bit KON or KEY-ON in the same register as BLOCK signals whether a note is on or off (on an electric piano with an OPL chip, KEY-ON=1 would literally signal a key being pressed down).

When triggering a note, the procedure is as follows: Reset KEY-ON in register $B0 if necessary to indicate that the previous note is off. Then write the values representing the desired frequency into FNUM and BLOCK while setting KEY-ON to 1. That’s it, the note will play as it should.

When the note should be turned off (a note value of $FE in the S3M channel), simply reset KEY-ON. The OPL chip will handle the rest, for example by letting the note fade.

When using a physical OPL chip, writing to its internal registers involves the following steps:

  1. write the address of the target register to the chip’s base address
  2. wait
  3. write the data to the chip’s base address+1
  4. wait some more if you plan to do another write to the chip
The base address is system-dependent. For an Adlib or SoundBlaster card, its value is I/O port $388.

The Adlib manual prescribes wait times in microseconds to allow the chip to process the address and data. Originally, this waiting could be accomplished by performing a specific number of reads from the Adlib card, which were known to take a particular amount of time.

The whole point of this exercise is to accommodate a chip that will only reliably absorb one new data value per sample period of about 20 microseconds. One would think that on an emulated chip, that’s nothing to worry about. But care should be taken here. To trigger a note, we reset and then set the KEY-ON flag, which signals that the old note has ended and a new note should be processed. For this to work, the OPL channel needs to observe the KEY-ON=0 value for at least one sample. When using an emulated OPL, a sensible approach may be to buffer asynchronous writes coming from the module player or tracker and feeding them to the virtual chip later one-by-one as samples are processed.

When first using the OPL on a physical sound card, it should be initialized to make sure that it’s not actually playing anything or is in some unexpected state. A somewhat crude approach that was popular on the PC and works in practice is to write zeros to every register on the chip. On an emulator, this might not be necessary. Then, to set the card up for the work at hand,

  • write $20 to register $00 to deactivate OPL1 compatibility mode; this will allow waveform selection in registers $E0 to $F5, which is an expected feature; also
  • if you calculate BLOCK and FNUM as suggested above, I recommend writing $40 to register $08; this will tell the chip that it can expect the highest bit in FNUM to be 1.

Finally...

Volume

With PCM and OPL, ScreamTracker 3 modules support two independent audio sources, and one important question is how to mix them. There is no definite answer, but some good reference points.

On the original SoundBlaster, the relative volume of PCM and OPL signals was fixed. Starting with the SoundBlaster Pro, sound cards came with a built-in mixer that allowed software to (initially crudely) change the volume of sound sources. The original GUS did not have an OPL chip, so to get the best sound quality on ScreamTracker, one would have to combine a GUS for PCM with a SoundBlaster or Adlib card for OPL, which would leave mixing to the user’s external audio equipment.

People have measured the relative volume of OPL vs PCM output on physical sound cards. The results reported in [VVB] suggest that on a SoundBlaster, PCM is about 8.5 dB louder than a single OPL instrument in additive mode (in additive mode, the output of both operators is added together, making the note twice as loud; in regular frequency modulation mode, the difference would therefore be 14.5 dB). Note that these measurements are for mono output. Results reported for similar sound cards are in the same ballpark. Other people may have measured slightly different values. DOSBox makes OPL a little louder, with a difference of 5.9 dB. OpenMPT is comparable to DOSBox.

With stereo, things get a little more complicated. Stereo PCM signals mapped to only either the left or right channel will be less loud, and more generally the volume of a channel may depend on the panning position. ScreamTracker automatically increases the master volume for stereo, but this is only supposed to work on SoundBlaster cards. In the case of mono output, the default master volume of $30 scales PCM volume by \(\frac{3}{8}\) or -8.5 dB, making it comparable to OPL volume.

My impression was that anything in the range reported above, with a delta between 5.5 dB and 8.5 dB, sounds good. When picking a level to target, one may want to consider that most modules made in the past 20 years would not have been created on physical SoundBlaster or GUS hardware, and that DOSBox, SchismTracker and OpenMPT are relevant reference points.

Panning

Panning is about the distribution of the audio signal to the left and right stereo channels.

The simplest form is hard panning, where a channel is played either left or right. That is how things worked on the Amiga and in the MOD modules created by ProTracker.

For a more general and flexible approach, it is desirable to distribute the signal between the left and right channels in a continuous way. A pan law maps the panning position \(P\in[0,1]\) to the volume of the left and right stereo channels, \(L(P)\) and \(R(P)\). The position \(P\) is often interpreted as an angle, ranging from -45° to 45°. \(P=0\) means left, \(P=1\) is right.

The simplest pan law is linear panning, \(L(P)=1-P\) and \(R(P)=P\). Due to its simplicity, this is the most likely approach to have been used in earlier trackers whenever there was software mixing without tabulation. While the sum of the left and right volume remains constant with changing \(P\) when using linear panning, the total power of the signals does not. As power is quadratic in volume, its total value drops to 50% for the centre position of \(P=\frac{1}{2}\) compared to the hard left or right positions, \(L(\frac{1}{2})^2+R(\frac{1}{2})^2=\frac{1}{2}\left(L(0)^2+R(0)^2\right)=\frac{1}{2}\left(L(1)^2+R(1)^2\right)\).

Constant power panning addresses exactly this issue. The formula that is widely used to achieve constant power across all pan positions is \(L(P)=\frac{1}{\sqrt{2}}\left(\cos(\alpha(P))+\sin(\alpha(P))\right)\) and \(R(P)=\frac{1}{\sqrt{2}}\left(\cos(\alpha(P))-\sin(\alpha(P))\right)\), where \(\alpha(P)=\frac{\pi}{2}\left(P-\frac{1}{2}\right)\) is the angle from \(-\frac{\pi}{4}\) to \(\frac{\pi}{4}\). The Pythagorean identity ensures that \(L(P)^2+R(P)^2\) is constant.

Constant power panning is not per se better. Opinions on what’s the right approach differ and depend on the application and context.

What about S3M modules? ScreamTracker only supports panning on the GUS, which used constant power panning with 16 positions.

The S3M format provides for stereo samples. The best way to deal with them in the context of panning is probably to adjust their balance according to the panning position, i.e. scale the left sample channel with \(L(P)\) and the right one with \(R(P)\). This is what OpenMPT does. ScreamTracker itself does not appear to actually recognize stereo samples as such; it only uses their left channel.

Filters and Sound Quality

ScreamTracker 3 was written to run on a SoundBlaster or SoundBlaster Pro. These cards filtered their PCM output rather strongly, see for example [SBF] for a summary. The filter measurements usually reported are consistent with 2nd-order Butterworth filters with cut-off frequencies between 3.2 and 3.8 kHz, making them comparable to the infamous Amiga “power LED” Butterworth filter. OPL output was not filtered nearly as strongly, applying only a first-order low-pass filter with cut-offs between 8kHz and 12 kHz. With the SoundBlaster 16, the filters were gone.

The GUS would always have provided high quality audio output, using 16 bit mixing at high sample rates as well as sample interpolation. No filtering was needed to hide shortcomings of the audio hardware.

The (emulated) OPL chip generates samples at a rate of just under 50 kHz. This is about 10% off typical sampling rates used for audio output today. Without further attention, this is bound to create significant aliasing effects that would not have been present when playing the same sound on physical OPL chip. These artefacts turn out to be quite noticeable in some circumstances. Resampling the OPL output addresses this issue. I found that simple linear interpolation was sufficient deal with the problem in a satisfactory way.

Which OPL Chip?

The original SoundBlaster from 1989 had a single YM3812 OPL2 chip. This is clearly what ScreamTracker 3 supports. The SoundBlaster Pro (1991) featured stereo for both PCM and FM output. It had two OPL2 chips, one for left and one for right. ScreamTracker does not explicitly support such a setup, but that may have been a planned or at least possible feature for a future release, and all that’s required to make it work is to send the notes of different Adlib channels to different chips depending on their panning assignment.

A revision of the SoundBlaster Pro made the transition to the YMF262 OPL3 chip, which became a mainstream feature with the wide adoption of the SoundBlaster 16 after 1992.

The OPL3 offers an increased number of channels and new sound generation features. Most of the new FM-related options are not readily available to S3M modules given the definition and limitations of the format. Allowing for additional channels is always a possibility today if a tracker supports it, especially when the limitations for mapping virtual to physical channels are relaxed. Such channel-related extensions are not directly tied to the OPL 3 chip.

There are two new capabilities of the OPL3 chip that are useable and accessible through the data stored in S3M files, albeit not through the ScreamTracker interface. One is the availability of 4 additional waveforms, which are selected through a previously unused bit in the D08 and D09 bytes in the Adlib instrument data. The other one is some very limited stereo control, allowing the instrument to choose whether to play on the left or right channel, possibly with double volume. It is controlled through the D0A data byte. Using this for panning would likely require some tracker support to work in a reasonable way, and should probably be exposed through the S8x (set panning position) effect rather than instrument properties. If there had been a tracker supporting this on a SoundBlaster 16 in the 90s, it could have trivially offered 3 OPL panning positions, and, with a little bit of attenuation tweaking, 5 panning positions.

OpenMPT does support both the additional waveforms and the selection of three panning positions using the S8x effect or alternatively the Xxx or volume column panning effects supported by ImpulseTracker.

Doing this does not require a full OPL3 implementation; it can be achieved with some rather simple adjustments to an OPL2 emulator.

References:

This is the module format documentation included with ScreamTracker 3. Here's one place where it can be found online:
->https://github.com/reznet/S3MParser/blob/master/TECH.DOC
For convenience and better readablility, I've converted it to UTF and made it available here:
->/b/Modules/docs/TECH.txt
A detailed wiki page on the S3M file format, effects, and SreamTracker idiosyncrasies:
->https://wiki.multimedia.cx/index.php?title=Scream_Tracker_3_Module
An overview of the file format:
->https://moddingwiki.shikadi.net/wiki/S3M_Format
A discussion of how to use internal ScreamTracker data stored in S3M files to improve playback of modules:
->https://sagamusix.de/de/blog/2021/08/21/s3m-format-shenanigans/
An excellent introduction to working with the OPL2 chip found on Adlib cards:
->https://cosmodoc.org/topics/adlib-functions/
This is Yamaha's user manual for the YM3812 OPL2 chip:
->https://archive.org/details/YamahaYM3812ApplicationManual
The OpenMPT manual contains some detailed information on S3M effects. The focus is naturally on the OpenMPT implementation:
->https://wiki.openmpt.org/Manual:_Effect_Reference#S3M_Effect_Commands
Here's an example of a song using the $FE markers. The Mod Archive online player alows you to jump to the "subsongs" that begin at these markers.
->https://modarchive.org/index.php?request=view_by_moduleid&query=34654
This is a UTF version of the general ST3 user manual. It explains the effects, among other things.
->/b/Modules/docs/ST3.txt
A forum discussion of the relative volume of PCM vs. OPL sound on early PC sound cards. It includes measurements on various cards.
https://www.vogons.org/viewtopic.php?f=62&t=49683
Some information on the analogue filters found on SoundBlaster cards for implementation in DOSBox. This page briefly summarizes the conclusions from related forum discussions, which should be easy to find, too.
->https://github.com/dosbox-staging/dosbox-staging/issues/1674

Here are some links that are not referenced above, but may be helpful.
The OpenMPT compatibility settings for S3M. They list a number of known glitches.
->https://wiki.openmpt.org/Manual:_Compatible_Playback#S3M_compatibility_settings
Another introduction to OPL programming, specifically the OPL3.
->https://moddingwiki.shikadi.net/wiki/OPL_chip
Yet another introduction to OPL programming. This is a contemporary one, discussing Adlib/SoundBlaster cards in particular.
->http://www.shipbrook.net/jeff/sb.html


<- previous^ index-> next