The world map is based on NASA's blue marble images.
The picture of the Arts Building is from the University of Saskatchewan Archives.
back |
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.
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.
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.
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.
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]).
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.
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.
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].)
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.
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.
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.
effect | name | MOD | description | D | T | M |
Axx | Set Speed | Fxx | set ticks per row to xx; for xx=0, do nothing | - | - | - |
Bxx | Position Jump | Bxx | after processing this row, set the song position to xx and continue with row 0 of the corresponding pattern | D | - | - |
Cxy | Pattern Break | Dxy | after processing this row, continue playing with row 10⋅x+y of the next song position; note that xy is decimal | D | - | - |
Dxy | (Fine) Volume Slide | Axx, EAx, EBx | see description | D | T | G |
Exy | (Fine/Extra Fine) Portamento Down | 2xx, E2x | decrease pitch by xy on every tick except the firstif (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 Up | 1xx, E1x | analogue to Exy | - | - | G |
Gxx | Tone Portamento | 3xx | set 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 slide | D | T | E |
Hxy | Vibrato | 4xy | perform vibrato with speed x and depth y using the waveform set by effect S3x on every tick except the first | D | - | E |
Ixy | Tremor | turn the volume off and on repeatedly for the duration of the effect; on-time is x+1 ticks, off-time is y+1 ticks | D | T | G | |
Jxy | Arpeggio | 0xy | on every tick, cycle between note, note+x semitones, note+y semitones | - | T | G |
Kxy | Volume Slide with Vibrato | 6xy | equivalent to Dxy with H00 | - | T | G |
Lxy | Volume Slide with Tone Portamento | 5xy | equivalent to Dxy with G00 | - | T | G |
Oxx | Set Sample Offset | 9xx | set the starting offset of the current sample to 256⋅xx | - | T | E |
Qxy | Retrigger | E9x | if y>0, retrigger the current instrument every y ticks, using x to select a tabulated volume change; for y=0 do nothing | D | T | E |
Rxy | Tremolo | 7xy | perform tremolo with speed x and depth y using the waveform set by effect S4x on every tick except the first | D | T | G |
Txx | Set Tempo | Fxx | set tempo to xx if xx>=$21 | D | - | - |
Uxy | Fine Vibrato | perform vibrato with speed x and depth y/4 using the waveform set by effect S3x on every tick except the first | D | - | E | |
Vxx | Set Global Volume | set the global volume to xx if xx<=$40 | D | T | - | |
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). | ||||||
effect | name | MOD | description | D | T | M |
S0x | (Set Filter) | E0x | unimplemented MOD command | - | - | - |
S1x | Glissando Control | E3x | enable (x=1) or disable (x=0) glissando for effect Gxx | - | T | - |
S2x | Set Finetune Value | E5x | set the MOD finetune value for the current instrument to x | D | T | - |
S3x | Set Vibrato Waveform | E4x | choose the waveform to use with effects Hxy and Uxy | D | - | - |
S4x | Set Tremolo Waveform | E7x | choose the waveform to use with effect Rxy | D | - | - |
S8x | Set Panning Position | (E8x) | set the panning position to x, where 0 is left and $F is right | D | T | - |
SAx | (Old Stereo Control) | unimplemented ScreamTracker legacy effect | - | - | - | |
SBx | Loop Pattern | E6x | set 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 | - |
SCx | Note Cut | ECx | turn the current note off after x>0 ticks; for x=0, do nothing | - | T | - |
SDx | Note Delay | EDx | delay triggering or releasing a note by x ticks | - | T | - |
SEx | Pattern Delay | EEx | increase the number of ticks in this row from T to (1+x)⋅T | - | T | - |
SFx | (FunkRepeat) | EFx | unimplemented MOD command | - | - | - |
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.
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.
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.
Moreover, it does not work properly with Adlib channels. It does turn the volume down when it should, but never turns it back up.
Whenever the note is retriggered, its volume is changed depending on x according to the following table:
x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
vol chg | 0 | -1 | -2 | -4 | -8 | -16 | *2/3 | /2 |
x | 8 | 9 | $A | $B | $C | $D | $E | $F |
vol chg | 0 | 1 | 2 | 4 | 8 | 16 | *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.
For OPL instruments, this effect does not appear to work at all.
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?
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.
x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
C4Spd | 7,895 | 7,941 | 7,985 | 8,046 | 8,107 | 8,169 | 8,232 | 8,280 |
x | 8 | 9 | $A | $B | $C | $D | $E | $F |
C4Spd | 8,363 | 8,413 | 8,463 | 8,529 | 8,581 | 8,651 | 8,723 | 8,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.
[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.
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.
This effect does not work in Adlib channels.
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.
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.
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.
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:
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,
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.
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.
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.
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.
previous | index | next |