Deciphering and understanding the .SMS files in MSTS

by Ralf Hagen

Version 1.0; 20 June 2002
(translated from German by Yuri Sos 10 Oct 2002)

Overview

"Kuju Entertainment once built a Locomotive Simulator which met everyone’s wishes,  ran stably under Windows, Linux and Solaris and had outstanding documentation.  When Microsoft wanted to release the product, the simulator had to be adapted to the well-known "look and feel" of Microsoft.

The result is what we see today in Train Simulator."

All jokes aside, the .SMS files are the most poorly documented configuration files in Microsoft Train Simulator (MSTS). SMS officially means "Sound Management System", but without proper documentation these files can rather be regarded as the "S&M" thing. What follows are the parts I have been able to decipher (incomplete, I know).

 

.SMS files

  • Locomotives: there are two SMS files for each locomotive: xxxcab.sms for the sounds within the loco cab and xxxeng.sms for the sounds audible from outside the loco;
  • Rail motors and passenger cars have xxxpas.sms for the passenger view (key 5);
  • All wagons require xxxwag.sms for external sounds.

 

Structure of the SMS file

Header

To create an SMS file, one needs a header and an enclosure. The header is

SIMISA@@@@@@@@@@JINX0x1t______

Tr_SMS ( )

All instructions are enclosed within the brackets of Tr_SMS.

You can place comments using the following commands:

Skip ( )

Or

Comment ( )

or

#

Please comment your files frequently and clearly.  While even some of the default files are unfortunately incorrectly commented, we’re lucky nonetheless to have any comments at all.

Scalability Group (SL)

The Tr_SMS is divided into three parts, the Scalability Groups. The Keyword is thus (guess!) ScalabilityGroup().

And in it all the sounds of an SL must be contained.

The Scalability Groups, the three of which are SL5, 3 and 1, determine which sounds are played during which sound attitude of the TS: SL5 high, SL3 medium and SL1 low.

For the sake of clarity place comments describing the Streams contained in a Scalablity Group BEFORE the Scalability Groups themselves.  A good series of comments of an SL looks like this:

Skip ( ** SL 5 sounds                                                               ** )
Skip ( **            - One One shot Steam audio stream               ** )
Skip ( ** - Two looping audio streams, playing an pitch shifting loops by Speed {wheel rot} **)
Skip ( **            - One Whistle audio stream                            ** )
Skip ( **            - One Bell Toggle audio stream                &&bsp;     ** )
Skip ( **            - One Injector 1 audio stream                         ** )
Skip ( **            - One Injector 2 audio stream                         ** )
Skip ( **            - One Sander audio stream                             ** )
Skip ( **            - One TBrake audio stream                             ** )
Skip ( **            - One Water Trough Loop audio stream          ** )
Skip ( **            - One Fire box Loop audio stream                  ** )
Skip ( **            - One Fire box shovelling stream                    ** )
Skip ( **            - One Control audio stream with individual Control sounds   **)
Skip ( **            - One Coupling audio stream                         **)

These are the comments of SL5 of the Gölsdorf 380 as they should actually be written. For each Stream (s.u.) there is a Skip line in the same order as the Streams.

We now still come to the SL header. The meaning of some of the entries is still unclear to me.

The header of a typical cab.sms file looks like this:

Activation (
           CabCam ()
           Distance (100)
 
Deactivation
             ExternalCam ()
             PassengerCam ()
             Distance (100)
 )
Stereo ()

...and an eng.sms file looks like this:

Activation (
           ExternalCam ()
           Distance (1000)
)
Deactivation
            CabCam ()
            PassengerCam ()
            Distance (1000)
)

... a pas.sms file:

Activation (
           PassengerCam()
)
Deactivation (
            ExternalCam()
            CabCam()
)
Stereo()

...and a wag.sms file:

Activation (
            ExternalCam ()
            Distance (55)
 )
Deactivation (
            PassengerCam ()
            CabCam ()
            Distance (55)
 )

I will try to explain and document the entries; I'd welcome anyone explaining these items from a different perspective:

  • Activation () and Deactivation () indicate when the sounds can be heard in the various camera views (external, cab, passenger below);
  • ExternalCam () is the External View (Keypress 2 3 4);
  • CabCam () in the cab (Keypress 1);
  • PassengerCam () in the passenger view (Keypress 5);

Example: to hear the crossing bells at a level crossing from inside the loco cab, you would move cabcam() from deactivation () to activation() in crossing.sms (which controls sounds at level crossings) thus (before in green, after in blue):

  • Activation (
              ExternalCam ()
                  Distance    ( 100 )
                  )
       Deactivation (
                    PassengerCam ()
                    CabCam   ()
                    Distance     ( 100 )
                    )

      Activation (

                 ExternalCam ()
                  CabCam   ()
                  Distance    ( 100 )
                  )
       Deactivation (
                    PassengerCam ()
                    Distance     ( 100 )
                    )
  • Distance () indicates up to what distance from the car the sound is able to be heard; for example one can hear the locomotive still up to a certain distance in the external view, even if one is at the other end of the train;
  • Stereo () means that the required .wav file is a stereo file. (see below .wav files). The stereo () parameter must be set thus with empty brackets. If stereo () is not set, mono files are expected. If one reverses the files (stereo () in the .sms file and mono files or vice versa) the files will be played at the wrong speed (mono instead of stereo at half speed and stereo, if mono is expected, at double speed). And the sound is grotesque because the conversion does not happen cleanly.

In regards to other header instructions, please run some experiments and send me the results.  As I said, some are wild assumptions. One can however use the above headers as a template as they have originated from the 380\Sound Folder.

Enough of the header, now we come to the actual sounds. These are in the section entitled

Streams ( )

Streams() is relatively simple: place the number of streams after the bracket (similar to Lights() in an .Eng/.Wag file) and to it the individual Streams introduced by Stream().

Stream ()

Now things are getting interesting, at last we're getting to the actual sounds. A simple stream looks like this:

Stream(
      Skip( **** Whistle sounds. **** )
      Priority( 6 )
      Triggers( 2
      <...>
)

First a comment, a description of the sound, is placed in Skip(); in the example here the whistle of the 380. The next line indicates a priority. I haven’t experimented with this yet, but I’d guess the higher the number the the better (a Stream with Priority(6) will override one with Priority(5)). The actual sounds are then blended in the Trigger () section. Either/or logic applies here: one stream – one sound. If one wants to have several sounds play at the same time (horn and Bell or flowing transitions in the engine noises (PowerCruise)), one must also define several Streams.

Triggers ()

Same structure as Streams () and Lights (): the number of triggers followed by each individual trigger. Here there are two triggers: Discrete_Trigger and Variable_Trigger.

Example of Discrete_Trigger:

Discrete_Trigger ( 8
                  StartLoopRelease ( 1 File( "a380_whistle1.wav" -1
                                   SelectionMethod ( SequentialSelection )
                )
)
Discrete_Trigger ( 9
                  ReleaseLoopReleaseWithJump ()
)

Discrete_Trigger requires an event in order to be released. These events are numbered. A table is in the appendix of this document. The methods to play back a sound will be clarified below.  In the example, the first entry does becomes something if the horn is actuated (Discrete_Trigger 8), and the second does something else when the horn is released (Discrete_Trigger 9).

Simple, yes? Well then, let’s move onto more complicated issues.

Variable_Trigger und Initial_Trigger

The relevant section in the 380cab.sms looks like this:

Triggers ( 3
           Initial_Trigger (
                 StartLoop (
                    1 File ( "a380_power_cruise0.wav" -1 )
                             SelectionMethod ( SequentialSelection )
                 )
          )
  Variable_Trigger (
                   Speed_Inc_Past 2.0
                              ReleaseLoopRelease ()
                   )
  Variable_Trigger (
                   Speed_Dec_Past 2.0
                              StartLoop ( 1 File ( "a380_power_cruise0.wav" -1 )
                              SelectionMethod ( SequentialSelection )
                   )
          )
)
VolumeCurve (
      SpeedControlled
            CurvePoints ( 4
                           0.0, 0.25
                           0.4, 0.2
                           1.6, 0.1
                          2.0, 0.0
                        )
          Granularity ( 0.01 )
)

Anyone still with me? Great!

First of all we have here an

Initial_Trigger:  this is quite simple to explain. All other triggers are released by events.  An Initial_Trigger becomes active without an associated event.   Undisturbed, the resultant sound is repeated over and over.

A Variable_Trigger needs an event to activate it, this is however certainly a variable.
Details of variables follow below.

Let’s content ourselves with this one variable: Speed is obvious – the speed of trains in MSTS. For the Variable_Trigger there are two other events apart from the variables: Inc_Past and Dec_Past, Inc for Increase and Dec for Decrease Past means "as soon as is over/under".

Thus In the example above: Speed_Inc_Past 2.0 means "as soon as the rising speed is or climbs over 2.0, do something". Likewise Speed_Dec_Past 2.0 means "as soon as the falling speed here is less than 2.0, do something else".

Random_Trigger is released intermittently, for example it is used in steam locomotives in generating the "Shoveling sound".  I have not yet investigated this further.

Dist_Travelled_Trigger  depends on the distance travelled, probably in meters or feet. Is used in wag.sms, may well be the reason why some trains still have movement sounds even while stationary. Here I have not yet investigated this extensively.

Variables

  • Speed is obvious, the speed of the train in MSTS in metres/second as we have already determined above;
  • Distance may well be the distance to the train in meters: I haven't experimented here yet;
  • Variable1 should be have been useful, but unfortunately appears of no use at all.  It is the wheel rotation in m/s and depends on the diameter of the wheel and also almost certainly the number of animation frames in the model: I have not yet deciphered the format of this variable;
  • Variable2 is a measure for the number of revolutions of the train and/or its load resulting in very "remarkable" behaviour in Steam locos.  Hence it also remains unused;
  • Variable3 in steam locos is certainly the “Shovel Rate” in x100 pound per hour (ie 1 = 100 pounds coal shovelled).  In Diesels it is a measure for the dynamic brake and goes from 0.0 to 1.0,  0.0 being off,  0.5 50% and 1.0 is 100% applied.

Curves

As you can see in the example with Variable_Triggers, the Curve is placed in the Stream() section directly after the Triggers ().  There are two Curves: VolumeCurve and FrequencyCurve.  Here we venture some little way into that wide open field of acoustics: a sound consists of an air oscillation and has two characteristics, frequency and amplitude.  And to complicate matters, distorted noise results from mixture of different oscillations. In MSTS we need both sizes as well: the frequency provides the pitch (eg standard tuning tone A is. 440 cycles/oscillations per second); the amplitude indicates the volume.  VolumeCurve and FrequencyCurve are provided to enable you change these two characteristics.

Each of these curves can be dependent on one of the two variables (x-axis): Speedcontrolled or Variable2Controlled.

Here is the format of a simple VolumeCurve:

VolumeCurve (
        SpeedControlled
                   CurvePoints ( 4
                                  0.0, 0.25
                                  0.4, 0.2
                                  1.6, 0.1
                                  2.0, 0.0
                                  )
        Granularity ( 0.01 )
)

First of all the curve between the brackets of VolumeCurve () or FrequencyCurve () is played (see below for details of which is played).  This determines the y-axis.  Next the x-axis is determined by SpeedControlled or Variable2Controlled (for example).   Next the points of the curve are placed within the brackets of CurvePoints ().   The number of points is defined (in the above example 4) in a format is similar to Streams, Triggers, Lights, Then the points themselves are listed in the format x, y.  After the last point the bracket of CurvePoints is simply closed.

The next specification is the Granularity (). This indicates the incrementation with which the computer calculates the x-values.

This is perhaps confusing at first sight for people who are not familiar with mathematics, but the answer is simple. If we draw a curve, we first need some values which we transfer as points into a coordinate system.  If we do that, then we can already see how the curve will look and then we can draw a curve which goes through these points.

A computer cannot draw; instead it determines the function and creates for itself a value table. The Granularity indicates now in which x-steps the computer calculates for each x value and then a y-value from the smallest to the largest x-value. The computer calculates thus in the above curve the y-values for x = 0,00, 0,01, 0,02, 0,03…, 1,96, 1,97, 1,98, 1,99, 2.00.

Volume and Frequency

In the previous section we saw that we could alter Volume and Frequency.  This section will now concern us with "how".

Volume:

The Volume-Value supposedly goes from 0 to 1 however people have already successfully experimented with values over 1.  I think that the Volume-Value indicates, with which volume in fractions of the original volume of the wav File, the file is played, thus with 0.5 half so loud, with 1.0 at original volume and with 2.0 at double volume.

Frequency

This is where things get a little complicated.
Here's a FrequencyCurve from the default gp38cab.sms:

CurvePoints ( 8
              0.000 12025
              0.150 12025
              0.450 13000
              0.500 13000
              0.501 12025
              0.550 12025
              0.850 13000
              1.000 13000
            )

At first the y-values seem confusing. Once you know that MSTS requires .wav files with a Sampling Rate of 11.025kHz or 11025Hz, the y-values become much clearer: everything above 11025 heightens the sound in relation to the original .wav file, everything below deepens it.  More experimentation is needed here because not all values offer a satisfactory sound - there are limits.

Play Commands (looped or simple)

So far we have discovered how we alter .wav file as we play it:  what's still missing is the art and science of how to play it.

MSTS understands the following commands:

  • PlayOneShot ()
    Example:

    PlayOneShot ( 1 File ( "a380_reverserf.wav" -1
                 SelectionMethod ( SequentialSelection ) )

    This command plays the .wav file from beginning to end once.

  • StartLoop ()
    Example:

    StartLoop ( 1 File ( "a380_power_cruise0.wav" -1 )
             SelectionMethod ( SequentialSelection ) )

    This command starts a loop in which the .wav file is played  over and over again.  This command is used with .wav Files without Cue Markers (see under .wav files) since it does not need markers for the loop.

  • ReleaseLoopRelease ()

  • Terminates a loop begun with StartLoop.

     
  • StartLoopRelease ()

  • Example: StartLoopRelease ( 1 File( "a380_whistle1.wav" -1  )
                     SelectionMethod ( SequentialSelection ) )

    This command commences playing a loop in which the .wav file is played up to the last Cue-Marker then loops back to the first Cue Marker and so on. The whistle and sanders in locomotives provide good examples.

  • ReleaseLoopReleaseWithJump()
    Terminates a loop begun with StartLoopRelease.  If this command didn't exist, the loop would keep running infinitely. Comment: At the moment, it seems that ReleaseLoopRelease() and ReleaseLoopReleaseWithJump() have the same function. Nevertheless one should use the appropriate commands for later (enhanced?) MSTS versions. How the commands are used is explained in the next section.

Play Commands (Play File)

The last commands to examine are the playing commands themselves.  File() and FileList() are listed in loadstr.hdr (a file in the /Utils/ffedit folder) where all the configuration commands are listed by MSTS (but unfortunately the commands are not explained); I haven't seen FileList() used anywhere yet.  File() command is placed within the Playing Commands StartLoop, StartLoopRelease or PlayOneShot.

The format of these commands is the uniform:

StartLoopRelease (
                   1
                   File( "a380_sandf.wav" -1  )
                   SelectionMethod ( SequentialSelection )
)

Within the bracket is the number of files, followed by the list of files themselves.  "-1" means to play the loop from first to last Marker, at least that's what it does for me.  Omitting the "-1" the file is played only up to the first marker, then recommences from the beginning; values starting at "-2" create a sound error.  Other users report a computer lock-up when anything except "-1" is used.

Finally, the SelectionMethod command is listed.  There are two options: SequentialSelection plays all files in order.  RandomSelection selects one file at a time at random. Obviously, if one file is to be played the SelectionMethod is the same; that is not the case in this example:

PlayOneShot ( 3
              File ( "a380_airb_auto1.wav" -1 )
              File ( "a380_airb_auto2.wav" -1 )
              File ( "a380_airb_auto3.wav" -1 )
              SelectionMethod ( RandomSelection )
)

Here the computer selects one of the three named files and plays it.

Programming of .sms Files (and all source texts in general)

So that you can step through everything that is done in an .sms file, I recommend that you take good heed of the following guidelines:

  • always type the complete expression immediately when programming bracketed terms, thus e.g..

  • PlayOneShot (
    )
    and then insert the contents of the bracket:  it is very easy to omit a bracket or to lose sight of the whole expression;
     
  • format the expressions in such a way that one always can see which bracket belongs where and where an expression starts and stops, for example:

  • Instruction(
         SubInstruction(
              DoNothingLoop ()
              DoSomethingLoop(
                    SubSubInstruction()
              )
         )
         AnotherSubInstruction(
              AnotherSubSubInstruction()
              YetAnotherSubSubInstruction()
         )
    )

    Here one can see clearly which part belongs to which Instruction and where an instruction stops and another begins.   Since MSTS wants to "phone home" with any missing or incorrectly set bracket without reason, you do yourself an immense favour in error debugging if set out your file thus;

  • comment, comment, comment!
    Did I mention you should write lots of comments?

  •  
    • for each SL Group, list the Streams contained within it in correct order and give each Strean its own line (in the case of engine sounds (power cruise) which are written in two overlapping streams, place comments for both streams together to avoid creating "holes" in the streams);
    • insert a comment line with each Stream as to what the Stream does;
    • Follow the standard MSTS .wav file nomenclature format of type_function.wav  (e.g. gp_sand.wav) for inside (Stereo) files and x_type_function.wav (e.g. x_a380_whistle1.wav) for outside (Mono) files;
    • remain consistent with your naming of files; not like this:  gp_horn.wav, gp38_sander.wav and  x_diesel_horn.wav, but rather same names for file of same type.

.wav-Files

If you want to provide your own sounds within MSTS, you need not only a good .SMS file , but most importantly a good recording. For this one needs first of all a good Wave Editor, and the best ones cost money. There are good Wave Editors such as Ulead Media Studio; good shareware editors include the popular GoldWave (http:www.goldwave.com).

For a file to be useful in MSTS, it must adhere to the following formats:

  • External Sounds:
    • x_type_function.wav
    • Sampling Rate 11,025 Hz
    • 16bit Mono Signed
  • Internal Sounds:
    • type_function.wav
    • Sampling Rate 11,025 Hz
    • 16bit Stereo Signed

If the file is to be used in a StartLoopRelease() / ReleaseLoopReleaseWithJump() loop, it also needs to contain Cue-Markers.

Cue-Markers in .wav Files

If you sound the horn in a locomotive, you can operate it very for much shorter or longer times than suggested by the size of the original .wav file. This is accomplished by using Cue-Markers. Cue markers are inserted by Wave editors and are identified with an ID and a name. In Wave Editors, the ID starts with 00000, the name is simply Marker (original MSTS wavs have the name format Marker mm:ss:hh, the default name assigned by Ulead Media Studio).

So why keep it simple when more complicated systems work?" Using ID 00000 in a .wav file will cause MSTS to crash.  Therefore when working on the .wav files, place the first marker at the beginning where it is not relevant. Once you have completely finished your wave editing, delete the 00000 marker.

CAUTION: During re-editing .wav files, the editor always sets a the first marker to 00000 if none is there.  Thus always set a marker at the very beginning if you are planning on further editing.

The marker 00001 is now the starting point for the loop (this is comparable to the repeat music symbol "||:"  in a sheet of music).

Next are the central markers in an arbitrary quantity (any number of markers from 1 to 16 can be used).  This relates to the fact that MSTS plays a file with ReleaseLoopReleaseWithJump up to a central marker, then jumps then to the final marker. Completely I have not completely tested that yet (and/or to no correct result came).

At the end an EndMarker is placed (this corresponds to ":||" repeat signal on music sheets) and, if StartEvent is set, lets the file being played jump again to the start marker 00001.   If ReleaseEvent is set, the file being rendered jumps to this marker, and everything beyond this marker is played.

The trick is then to set all markers in such a way that the same frequencies lie close together so that one can set the marker sections one behind the other at will, without hearing a jump on playback.

A .wav file is thus composed of

  • a starting piece of sound, for example the rising note of a horn;
  • a StartMarker;
  • several arbitrarily connectable central markers in the center section;
  • an EndMarker;
  • an end piece of sound, for example the fading away notes of a horn.

 

Conclusion

So here you have the total wonder of .SMS files. A blank heading has been placed where I wasn't able to decipher its meaning. If you do discover something new or wish to correct anything in this document, please email me on aranea-diademata@gmx.net.

The documentation should be the starting point for a (hopefully) blossoming German (and international - YS) Freeware-Sound-Scene.  With this documentation in hand one should go now at once through the original .SMS files and repeatedly try to understand them. Tip: Text markers in different colors truly work miracles in order to mark Streams and to make the whole thing clear(er).

It is disappointing that that the people of Kuju did not adhere to formatting (actually derived from good programming practice); however, as you examine the the original files, you realise how important it is to adhere to it.

This file is an ongoing living document. In parts this document is only a few days old but I wanted to publish this incomplete document so that a starting point for Freeware sounds is there. If you have suggestions or questions, please write to me at my email address at aranea-diademata@gmx.net.

I am trying to to convince Sebastian Frey to establish a Sound forum. If it is established, you will find it at http://train-simulator. sebastianfrey.de/.

Have lots of fun creating and providing sounds!

 

Acknowledgements

This document could not have been written without the invaluable assistance of the following people:

 

Copyright Notice

  • This document is © 2002 Ralf Hagen.
     
  • Alteration or changes to this document, inclusion in other archives only with my prior permission.
     
  • A pre-condition for publication is that I am to be informed about proposed location of the offer. This permission is at any time revocable by me.
     
  • This file is Freeware. Commercial sale is not permitted (or placement in Download Archives) until a fee of €1500.00 is paid.
     

 

Appendix

Discrete_Trigger Table
No guarantees of data accuracy!

Number
Meaning
1
~
2
~
3
~
4
Sander On
5
Sander Off
6
Windscreen
Wiper On
7
Windscreen
Wiper Off
8
Horn On
9
Horn Off
10
Bell On
11
Bell Off
12
Compressor On
13
Compressor Off
14
Train brake applied (brake sounds)
15
Reverser Forwards
16
Reverser Reverse
17
Brake Handle forwards (Train brake)
18
Brake Handle forwards (Loco brake)
19
~
20
Dynamic Brake Handle forwards
21
Release locomotive brake
22
Apply locomotive  brake
23
~
24
~
25
Vigilance Alarm tracer (??)
26
Operate Sander (toggles)
27
Steam Ejector 2 on
28
Steam Ejector 2 off
29
~
30
Steam Ejector 1 on
31
Steam Ejector 1 off
32
~
33
Auxiliary blower Toggle
34
Cylinder Cocks Toggle
35
~
36
Firebox Door open/close toggle
37
Light switch toggle
38
Water Scoop
39
~
40
Firebox door open
41
Firebox door close
42
~
43
~
44
Central heating Steam Valve
45
Pantograph Up
46
Pantograph Down
47
Pantograph toggle
48
Econtrol???
49
~
50
~
51
Locomotive Brake Operate? (EB Brake)
52
~
53
Apply train brake, normal application
54
Apply trainbrake, emergency
55
~
56
Vigilance Alarm On
57
Vigilance Alarm Reset ("Z" key)
58
Couple
59
Couple
60
Couple
61
Uncouple
62
 Uncouple
63
 Uncouple
64
~
65
~