Table of Contents

Weasel audio library

Welcome to the Web Enabled Audio and Sound Enhancement Library, or Weasel for short!

The  W.e.a.s.e.l.  Audio Library!

Version 1.8.0

A GPL 3 JavaScript library for playing music from Amiga Soundtracker modules, supporting:

The project home page is located on SourceForge here: https://sourceforge.net/projects/weaselaudiolib/

Change log is here.

Replay library

The replay library is a real-time low latency JavaScript library for playing Ultimate Soundtracker modules from a web browser, it requires your browser, at the least, to support HTML5 Audio.

The replay library can be located in the 'release/' directory of the 7zip file.

Assuming that the web server has its gzip compression enabled the library can be as small as 27KiB.

gzip compressed Uncompressed
Developer 81KiB 474KiB
Minimized 27KiB 155KiB

Library JSDoc documentation

The 'release/' directory also contains the JSDoc generated against the library and can be viewed with a web browser.

Ultimate Soundtracker

In August 1987, some 25 years ago, Karsten Obarski created the first Soundtracker on the Amiga, a commercial product released in Germany entitled “Ultimate Soundtacker” became available in December 1987. The concept was simple, the timing perfect (the lowered cost version of the Amiga had just been launched - the Amiga 500) and Ultimate Soundtracker allowed non-programmers to create music easily and, just as importantly, see how others made their music. The snowball effect was massive, currently (2012) the Amiga Music Preservation (AMP) contains over 120,000 sound modules (in various derived formats).

At a time when commercial samplers and sequencers cost more than a family car Ultimate Soundtacker, combined with the audio hardware of the Amiga, gave people to the opportunity to experience musical instruments using samples as opposed to synthesized sound. Although previous home micros, such as the Commodore 64, could play samples the limited amount of ram on these 8-bit machines usually meant you were restricted to using low quality drum kit samples.

Although Ultimate Soundtracker was not a commercial success, it was instantly take up by game developers, shareware and public domain authors, the demo scene and even the cracking community. Within 12 months over a dozen Soundtracker clones had appeared, some even using Karsten own code base!

So a massive thank you to Karsten Obarski for creating Ultimate Soundtracker and starting the ball rolling that would influence and inspiring many people to compose their own music and write their own software.

The Jungle Command Soundtracker 2

The second Soundtracker to appear, created by The Exterminator and released March 1st 1988, it was a heavily binary patch of Ultimate Soundtracker 1.21 that added lots of new Effect Commands, some taking advantage of the Amiga audio hardware (such as hardware based frequency modulation and amplitude modulation). Many of these commands would not make it into the M.K./Protracker format.

Def Jam Soundtracker 3 extended TJC Soundtracker 2 by added Tick Speed support.

Many Soundtrackers are based upon TJC Soundtracker 2/Def Jam Soundtracker 3 series and are compatible, such as:

D.O.C. Soundtracker 9

In July of 1988 Michael Kleps is the first to introduced the ability to save Soundtracker modules in version 9 of the DOC Soundtracker series. It also starts to standardise some of the Effect Commands, which are different from Ultimate Soundtracker, and also supports bigger sample sizes (up to 32K in size). Being based upon the Ultimate Soundtracker 1.8 editor it supports the BPM Speed settings.

Master Soundtracker 1.0 by TIP of TNM (The New Masters) also shares the same format and Effect Commands as DOC Soundtracker 9, there is a single difference in that TIP removed BPM Speed support from the editor and is set to 125bpm (50Hz). The editor for Master Soundtracker 1.0 is greatly improved and defines the GUI of Soundtrackers that follow.

D.O.C. Soundtracker 2.2

Released in December 1988, D.O.C. Soundtracker 2.2 represents the last of the 15 instrument Soundtrackers and is an enhancement of Master Soundtracker 1.0/D.O.C. Soundtracker 9 supporting two new commands (pattern break and sequence jump).

After this Soundtracker Michael Kleps releases the M.K. format with 31 instrument support.

Spreadpoint Soundtracker 2.3

The is the first time a Soundtracker is released with 31 instrument support and is the origin of the M.K. (Michael Kleps) format that is synonymous with Soundtracker. Spreadpoint Soundtracker 2.3 was created by Mnemotron of Spreadpoint and is basically D.O.C. Soundtracker 2.2 with support for the new format. Spreadpoint Soundtracker 2.4 is identical (other than bug fixes) to 2.3 in that the format and supported commands are the same.

Noisetracker 1.1

Released on the 7th of August 1989 Noisetracker by Mahoney & Kaktus (Pex Tufvesson & Anders Berkeman) represented another major step forward in Soundtrackers, pushing the quality of the editor much higher. The replay routine, although similar to Soundtracker 2.3 adds some significant changes which remained in future versions of Soundtracker, most notably:

(Noisetracker 1.0 was released on 1st of August 1989, superseded by Noisetracker 1.1 six days later.)

As a side note because of the initials of Mahoney & Kaktus being M & K they are often confused as the originators of the “M.K.” Soundtracker format, which is not the case - it was Michael Kleps!

Spreadpoint Soundtracker 2.5

Released on 27th of August 1989, MnemoTroN liking Noisetracker included lots of features into the Spreadpoint Soundtracker 2.5 Editor and Mahoney & Kaktus provide a new replay routine which is compatible with Noisetracker 1.1 with the exception of the Song Sequence Restart Position and the Set Tick Speed command.

The maximum sample size is increased to 64k and uses the new sample looping modes from Noisetracker.

Noisetracker 2.0

Released on the 4th of April 1990, the next and sadly last public release of Noisetracker by Mahoney & Kaktus (Pex Tufvesson & Anders Berkeman). Most of the efforts had gone into improving the editor with Midi-in support, printing, sample editing. There are some additional commands added:

The Vibrato command was also changed slightly making it more finer, this has the unfortunate side effect for some modules that are Noisetracker 2.0 but get identified by the Module Sniffer as compatible with Noisetracker 1.1, if they use the Vibrato command the module may sound incorrect. The Jukebox (demo1) gives you the ability to change the Vibrato mode via “Settings→Song→Vibrato”, allowing the module to sound correct.

Startrekker

Bjorn Wessen's Startrekker series is know for being one of the earliest 8 channel Soundtrackers, it can also be used in 4 channel mode, which is identical to Noisetracker 2.0 - as the code base is a branch of Noisetracker 2.0.

Currently 8 channel modules are not supported but 4 channel modules are played as Noisetracker 2.0 modules, as only the module ID field is different (“FLT4” instead of “M&K!”).

Protracker 1.1

Released on 27th of December 1990 by Lars “Zap” Hamre of the Amiga Freelancers was yet another big step forward for Soundtrackers, over a dozen new commands added, support for 128K samples, sample fine tuning and an accurate BPM Tempo clock. The editor was also greatly enhanced. It would become by far the most dominant Soundtracker on the Amiga.

Interestingly although there are lots of revisions to Protracker (version 1.0c to 3.15) the actual replay routine is near identical. The only major change is the removal of the “FunkIt” command (Version 1.0c) which is changed to “Invert Loop” (in Version 1.1a) and the Vibrato Command depth was changed to be compatible with Noisetracker 2.0 (instead of Noisetracker 1.1).

Also of interest is that Peter “Crayon” Hanning would take over development for Version 2.1 to Version 2.3.

In order to play very early Protracker modules (1.0a-1.0c) the Vibrato mode needs to be set to Noisetracker 1.1 mode. It should be noted that the “FunkIt” command is currently not supported, however its not entirely clear if any modules actually used this command.

Protracker 3 series arrived in January 1993, written by Ivar Olsen, Bjarte Andressen and Tom Bech, with slight changes to the replay routine (the Sample Offset command can behave differently and the elimination of DMA Wait so that the replay runs correctly on different speed CPUs). Most of the changes are associated with the Editor which has been rewritten to use a higher screen resolution.

Quick examples

A small collection of example code using the replay library, they open in a popup window.

Demo 1 - Jukebox

A play list for modules, contains rudimentary visuals (oscilloscopes, volume bars etc). Drag and drop support that allows the addition of modules from your own desktop and URLs to modules from the same web server the player is located on.

Demo 1 - The Jukebox

Demo 2 - bare minimum

Demo 2 shows off the simplicity of the Easy Player, taking approximately five lines of JavaScript to start playing a module and displays the Easy Menu. A Soundtracker module has been base64 encoded and embedded into the web page.

 Demo 2 - The bare minimum.

Demo 3 - Realtime Fractal Landscape

Demo 3, an experiment to see if the module player could be attached to existing code easily.

Demo 3 - Fractal landscape

Only an initialiseAudio() function was added to the code base and a single call to oBrowserAudio.feedAudio() in the main loop to play the audio. However if the frame rate drops very low (less than 10fps) better results can be achieved by separated out the audio player into its own interval handler, or use the Easy Player.

JsUnit tests

Link to unit test runner.

Regression Checker Results Page

Link to the Weasel Library Regression Checker Results Page.

Weasel Task Profiler Result Viewer

Link to the Weasel Task Profiler Result Viewer.

The Weasel Task Profiler Result Viewer.

What is a Soundtracker?

A music composition tool that bears a striking resemblance to a spreadsheet crossed with a hex editor!

A music sequence is constructed from a list of patterns, each pattern consists of columns and rows. Each column (track) represents a sound channel (of which there are four). Each of the sixty four rows contains a note for each sound channel to play.

The sequence list allows you to reuse patterns more than once, the sequence list can contain a maximum of 128 patterns:

Sequence table 0 1 2 3 4 5 6 7 etc.. 127
Pattern number 0 1 1 3 2 3 0 0 etc.. 0

So assuming a song length of 6, four patterns are used (numbered 0 to 3 inclusive), pattern 0 is played once at the start, pattern 1 twice, pattern 2 once (notice is it played out of sequence, pattern 3 is played first) and pattern 3 is played twice. After sequence 5 is played the song will restart from sequence zero.

Typically a pattern is displayed as follows:

Row Channel 1 Channel 2 Channel 3 Channel 4
Note SPL# Effect Note SPL# Effect Note SPL# Effect Note SPL# Effect
00 C-1 6 147 --- 0 000 --- 0 000 F#2 9 000
01 --- 0 147 C-3 f 201 --- 0 000 --- 0 000
02 F-1 6 147 --- 0 000 --- 0 000 F#2 9 000
….
63 C-1 6 147 C-3 f 201 --- 0 000 F#2 9 000

Each “cell” consists of a note (if one is to be played), the sample to use (numbered 1 to 15 in hexadecimal), an effect and the value to be used by the effect:

Channel 1
Note Sample # Effect # Effect Data
C-1 6 1 47

In the above example it is note C in octave 1 that plays sample number 6 and has effect 1 applied to it, which is arpeggio. The Effect Data is 47 which will play the sample at both 4 and 7 semi-tones above the note, creating a kind of chord (more of a trill as the note is switched between C, E and G very fast, they do not actually play at the same time).

Resulting in a music editor which is very simple, as well as being quick to edit.

Hexadecimal (often referred to as hex) values, which is base 16, were used as they are easier to compute by the CPU than decimal values, which are base 10, and take up less room to display on the screen. Hex counts from 0 to 9 like decimal but then uses the alphabet A to F, hex has the ability to display single nibble of data (half a byte) with one character as opposed to two in decimal and an entire byte with just two characters, decimal would take three: 0-255.

Decimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Hex 0 1 2 3 4 5 6 7 8 9 A B C D E F

Unfortunately this spreadsheet/hex editor format soon becomes unfriendly when you start adding more channels, by the time 32 or 64 columns are added it quite honestly is a nightmare!

An on-line video of Ultimate Soundtracker 1.21 playing the title music from Rallye Master is available for viewing.

Ultimate Soundtracker itself was greatly inspired by a 1,500 line hex dump that appeared in the October 1986 issue of a German Commodore 64 magazine called 64'er, published by Markt & Technik. The program was Soundmonitor by Chris Hülsbeck.

Song Speed

The speed of the song is determined by two factors:

  1. The Song BPM (Beats Per Minutes) speed, which for Ultimate Soundtracker 1.21 is fixed at 125bpm (version 1.8 allows adjustment of the BPM).
  2. The Tick speed, which is how long to spend on each pattern row before moving onto the next row. This value is 6 and cannot be changed, basically Ultimate Soundtracker counts up to 6 then fetches the next row.

Why 125bpm?

This has to do with the refresh rate of European televisions timing often refereed to as PAL which occurs 50 times a second (50hz). Almost all home micros are able to generate an interrupt synchronized to the vertical refresh rate of the TV/Monitor and was naturally used to play music.

Interestingly almost all European computer music is 125bpm based.

Of course this does lead to problems, what happens if the television is not a PAL based TV and does not run at 50Hz. The answer is that the music is played fast or slower depending on the refresh rate, for example a North American TV runs at 60hz, resulting in a BPM of 150, a 20% increase in speed.

Karsten would fix this issue in Ultimate Soundtracker 1.8 by using one of the Amigas programmable timers interrupts instead. Unfortunately a lot of other Soundtracker developers ignored this and carried on using the vertical interrupt.

Number of rows to a Quarter Note

From a music score point of view it is possible to calculate the number of rows per quarter note (or crotchet):

50hz interrupts per second
* 60seconds per minute
= 3000 interrupts minute
/ 6Ticks per row.
= 500 rows per minute
/ 125Beats per minute
= 4 rows “per beat” or Quarter Note

Effect Commands

Ultimate Soundtracker has only ever supported two different effect commands:

Command Effect
1Arpeggio.
2Portamento, often referred to as pitch bend.

Arpeggio

Arpeggio is used to create a chord like effect which is very similar in style to that used on 8-bit machines such as the Commodore 64 (by the likes of Rob Hubbard, Martin Galway, Chris Hülsbeck and Jeroen Tel et al.).

The effect data is broken into two parts (the higher and lower nibble) and treated separately, each being the semi-tone added to the base note. Remember the values are in hex, so a range of 0-15 semi-tones can be added to the base note (which is over a octave).

For example:

Note Sample # Effect # Effect Data
A-2 1 1 5A
+ 5 semi-tones +10 semi-tones

The arpeggio command is strongly tided to the Tick Speed of each row, which is 6, on every tick it will play a different semi tone.

Tick Note Semi Tone
0 A-2 + 0
1 D-2 + 5
2 G-2 +10
3 A-2 + 0
4 D-2 + 5
5 G-2 +10

As you can imagine this creates a sort of trill effect as it switches very fast between each note of the cord.

It is also possible to just enter the command and effect data on a row cell without a note in it. Assuming that there is a note in the same column somewhere above the entry the command will still be applied to the sample at the same base note (as no new note has been give).

Note Sample # Effect # Effect Data
C-1 2 1 47
--- 0 1 58
--- 0 1 5A

Here the arpeggio command is still applied to sample 2, its base note of C-1 is maintained through out, but the chords played changes per row.

Portamento

Portamento or pitch bend is handled in a similar manner as Arpeggio, the command data is treated as two separate values (the upper and lower nibble). The first nibble is the pitch bend down value and the second nibble is the pitch bend up value.

Note Sample # Effect # Effect Data
A-2 1 2 34
pitch bend down pitch bend up

Obviously pitch bend up and down cannot happen at the same time, pitch bend down takes precedence over pitch bend up. Technically the value provided in the Effect Data is added to the period value of the note every Tick.

This is not very convenient, or indeed very accurate as the period values are not linear, thus a pitch bend for C octave 1 will not change the pitch as much as the same value for C octave 2.

Care must also be take as the pitch bend is not clamped at the end of the note ranges, pitch bend up from B-3 will have random results (depending on player). Pitch bend down from C-1 is typically identical (the pitch goes down as expected below C-1). However these values are clamped in other Soundtrackers, so using this quirk for effects will result in them missing in other Soundtrackers and sound differently.

This might explain why pitch bend was never really used in Ultimate Soundtracker modules.

The Jungle Command Soundtracker 2 Effect Commands

The Jungle Command Soundtracker 2 supports more Effect Commands that Ultimate Soundtracker, they are also differently numbered (some would become used in the M.K. 31 sample module format).

Command Effect
0Arpeggio.
1Pitch bend up.
2Pitch bend down.
CSet Volume of sample.
DSlide Volume of sample.
EAuto Slide Volume of sample.
FSet tick speed.*
*A Def Jam Soundtracker 3 command.

The Weasel Audio Library does not currently support The Jungle Commands Effects 3 through to B, these are the hardware based modulation commands. The reason they are currently not supported is that, surprisingly, no modules using these commands where found. This may change in the future.

Arpeggio

Arpeggio behaves the same as in Ultimate Soundtracker other than its Command Effect number has changed from 1 to 0.

Pitch bend up.

Pitch bend up now has its own command number and its range is considerably larger, 0-255 in decimal (0 to 0xFF in hex) than Ultimate Soundtracker.

Note Sample # Effect # Effect Data
A-2 1 1 14
pitch bend up

The Effect Data value 0x14 is in hex (20 in decimal) and is subtracted from the period value of the note every tick of the current row (except tick 0 when the note is first fetched). Unlike Ultimate Soundtracker the pitch bend up command is clamped at the maximum note possible (B-3) and will not go any higher.

Pitch bend down.

Pitch bend down has its own command number and its range is 0-255 in decimal (0 to 0xFF in hex).

Note Sample # Effect # Effect Data
A-2 1 2 11
pitch bend down

The Effect Data value 0x11 is in hex (17 in decimal) and is added to the period value of the note every tick of the current row (except tick 0 when the note is first fetched). Unlike Ultimate Soundtracker the pitch bend down command is clamped at the minimum note possible (C-1) and will not go any lower.

Set Volume.

Set Volume sets the volume of the channel immediately, the range is 0 to 64 in decimal (0 to 0x40 in hex) where 64 is the maximum and 0 is the minimum (off). This overrides the volume set by the instrument.

Note Sample # Effect # Effect Data
A-2 1 C 40
--- - C 30
--- - C 20
--- - C 10
--- - C 00
Set Volume

The Effect Data on each row reduces the volume on this channel (that is playing sample 1) to zero.

Volume Slide.

Slide the current volume level every tick by the given amount, the range is 0 to 15 in decimal (0 to 0xf in hex) and is stored in the upper or lower nibble of the Effect Parameter depending whether your require to slide the volume level up or down.

Note Sample # Effect # Effect Data
A-2 1 D 34
Slide volume up Slide volume down

The Volume slide command can be used by itself to fade in/out a note.

Note Sample # Effect # Effect Data
A-2 1 D 01
--- - D 01
--- - D 01
--- - D 01
--- - D 01
Fade the current note out slowly.

The Effect Data on each row reduces the volume on this channel (that is playing sample 1), this is done per tick (except for tick 0) as opposed to the Set Volume command which is only applied once per row.

Auto Slide.

Auto Slide is similar to Volume slide with the advantage of being applied whilst other Effect Commands are used. The range is 0 to 15 in decimal (0 to 0xf in hex) and is stored in the upper or lower nibble of the Effect Parameter depending whether your require to slide the volume level up or down. Once set it is remember and applied whilst other commands are used, only a Effect Command of 0 and Effect Parameter of 0 will halt the Auto Slide.

Note Sample # Effect # Effect Data
A-2 1 E 34
Slide volume up Slide volume down

The Auto slide command can be used by to fade in/out a note whilst playing other commands.

Note Sample # Effect # Effect Data
A-2 1 E 01
--- - 0 47
--- - 1 10
--- - 2 02
--- - 0 00
Fade the current note out slowly, whilst playing the other effects but ends when a 0-00 Command is used.

The Effect Data on each row reduces the volume on this channel (that is playing sample 1), this is done per tick (except for tick 0) as opposed to the Set Volume command which is only applied once per row. On the second row the Arpeggio Command is used WHILST STILL applying the Volume Slide, on the third and forth rows a Pitch Bend Command is applied whilst the Volume Slide is still used. The Auto Slide command stops being used on the fifth row when it encounters a Effect Command and Effect Data of zero.

Set Tick Speed.

Set the Tick Speed is introduced with Def Jam Soundtracker 3 and is not present in TJC Soundtracker 2. Setting the Tick Speed sets the speed at which each row will be processed from now on (until another Tick Speed command is encountered). The Tick Speed has a range between 1 to 15 in decimal (1 to 0xf in hex). The default Tick Speed is the same as Ultimate Soundtrackers (which is 6). A value of 12 (0x0C in hex) would make the song play twice as slow, a value of 3 (0x03 in hex) would make the song play twice at fast.

Tick Speed does not reset back to the default once the song has finished, and will play at the last set Tick Speed until it encounters a new Tick Speed command.

Note Sample # Effect # Effect Data
A-2 1 F 0C
--- - F 06
--- - F 03
Set Tick Speed

The Effect Data on each row changes the Tick Speed, doubling the speed on each row.

Any Effect which is applied per Tick is affected by this command. For example pitch bend will only bend half the distance that was intended if the Tick Speed is changed from 6 to 3, because it is now only spending 3 Ticks on each row instead of 6.

You cannot change the BPM Speed which is set to 120 in the file but expected to play at PAL 50hz.

DOC Soundtracker 9 Effect Commands

DOC Soundtracker 9 supports more Effect Commands that Ultimate Soundtracker, they are also differently numbered (they would become used in the M.K. 31 sample module format).

Command Effect
0Arpeggio.
1Pitch bend up.
2Pitch bend down.
CSet Volume of sample.
EEnable/Disable the Filter on later Amigas
FSet tick speed.

Arpeggio

Arpeggio behaves the same as in Ultimate Soundtracker other than its Command Effect number has changed from 1 to 0.

Pitch bend up.

Pitch bend up now has its own command number and its range is considerably larger, 0-255 in decimal (0 to 0xFF in hex) than Ultimate Soundtracker.

Note Sample # Effect # Effect Data
A-2 1 1 14
pitch bend up

The Effect Data value 0x14 is in hex (20 in decimal) and is subtracted from the period value of the note every tick of the current row (except tick 0 when the note is first fetched). Unlike Ultimate Soundtracker the pitch bend up command is clamped at the maximum note possible (B-3) and will not go any higher.

Pitch bend down.

Pitch bend down has its own command number and its range is 0-255 in decimal (0 to 0xFF in hex).

Note Sample # Effect # Effect Data
A-2 1 2 11
pitch bend down

The Effect Data value 0x11 is in hex (17 in decimal) and is added to the period value of the note every tick of the current row (except tick 0 when the note is first fetched). Unlike Ultimate Soundtracker the pitch bend down command is clamped at the minimum note possible (C-1) and will not go any lower.

Set Volume.

Set Volume sets the volume of the channel immediately, the range is 0 to 64 in decimal (0 to 0x40 in hex) where 64 is the maximum and 0 is the minimum (off). This overrides the volume set by the instrument.

Note Sample # Effect # Effect Data
A-2 1 C 40
--- - C 30
--- - C 20
--- - C 10
--- - C 00
Set Volume

The Effect Data on each row reduces the volume on this channel (that is playing sample 1) to zero.

Set Filter.

The original Amiga did not have a filter, it was introduced with the Amiga 2000 and Amiga 500. Somewhat annoyingly it is enabled by default on these machines and so aggressively filters any software that written for the original Amiga unless you manage to turn it off. The filter is of the low pass verity and starts at 4Khz and attenuates down to 7Khz for full cut-off and affects all channels at the same time.

The Filter is connected to the power LED of the Amiga, on = filter on, off = filter off.

Note Sample # Effect # Effect Data
A-2 1 E 00
--- - E 01
Set Filter

The Effect Data only has two values, 00 and 01, 00 = Filter & power LED on, 01 = Filter and power LED off. (Yes the opposite of what you would expect, 00=on & 01=off). So in the example above on the first row the filter is switched on and then on the second row the filter is switched off.

Ideally it is best to ignore this command as few players support it (unless its on an Amiga).

Set Tick Speed.

Set the Tick Speed sets the speed at which each row will be processed from now on (until another Tick Speed command is encountered). The Tick Speed has a range between 2 to 15 in decimal (2 to 0xf in hex). The default Tick Speed is the same as Ultimate Soundtrackers (which is 6). A value of 12 (0x0C in hex) would make the song play twice as slow, a value of 3 (0x03 in hex) would make the song play twice at fast.

Tick Speed does not reset back to the default once the song has finished, and will play at the last set Tick Speed until it encounters a new Tick Speed command.

Note Sample # Effect # Effect Data
A-2 1 F 0C
--- - F 06
--- - F 03
Set Tick Speed

The Effect Data on each row changes the Tick Speed, doubling the speed on each row.

Any Effect which is applied per Tick is affected by this command. For example pitch bend will only bend half the distance that was intended if the Tick Speed is changed from 6 to 3, because it is now only spending 3 Ticks on each row instead of 6.

(This does not happen if you change the BPM Speed, as you are just changing the speed at which the Ticks occur - not the number of Ticks per row.)

DOC Soundtracker 2.2 Effect Commands

DOC Soundtracker 2.2 has two additional Effect Commands than DOC Soundtracker 9, the existing commands are identical with the exception of Set Tick Speed.

Command Effect
0Arpeggio.
1Pitch bend up.
2Pitch bend down.
BSequence Position Jump.
CSet Volume of sample.
DPattern Break.
EEnable/Disable the Filter on later Amigas
FSet tick speed.

Sequence Position Jump.

This is a rather odd command as it has the potential to mess up the Song Sequence List. It has two legitimate uses:

  1. Used as Song Restart allowing you to restart your music, once its ended, at a different sequence position other than zero. For example there may be a few intro patterns that allow the tune to build up, but are only meant to be played once.
  2. Allow Sub Songs to be present in the module, for example the song length might be 20 but patterns 0-9 are song one and 10-19 are song two. Using this command in Sequence 9 to loop back to Sequence 0 and also a command in sequence 19 to loop to sequence 10.

These are both rather hacked uses, the future versions of the M.K. format would have a Song Restart attribute added. Sub Songs have never officially been supported so there is no way to know if a module actually contains a sub song.

Note Sample # Effect # Effect Data
A-2 1 B 02
Sequence Position Jump

The Effect Data tells it to jump to Sequence Position number 2. The jump actually occurs once the current row has finished (after 6 ticks at default speed). The command can be placed in any row and will result in the new pattern being played from row 0.

Any attempt to jump past the end of the song results in a jump to sequence position 0.

The Sequence Position Jump command also overrides any Pattern Break commands that may occur in its row, the Pattern Break command gets ignored.

The danger comes in that it is possible to create a module that never ends (in the traditional sense of playing from sequence 0 through to the song length in the sequence table). It also makes a mockery of the Song Length attribute as it no longer represents the length of the song. This makes life difficult for module players to report the length of the song correctly (it never ends or is mismatched with the song length).

Pattern Break.

The Pattern Break command ends the current pattern and move onto the next Sequence Position.

Note Sample # Effect # Effect Data
A-2 1 D 00
Pattern Break

The Effect Data column is ignored. The Pattern Break occurs once the current row has finished (after 6 ticks at default speed). The command can be placed on any row and the new pattern will be played from row 0.

The Pattern Break command is ignored if there is a Sequence Position Jump command in the same row.

Set Tick Speed.

Set the Tick Speed sets the speed at which each row will be processed from now on (until another Tick Speed command is encountered). The Tick Speed has a range between 0 to 15 in decimal (0 to 0xf in hex), where a Tick Speed of 0 is ignored. The default Tick Speed is the same as Ultimate Soundtrackers (which is 6). A value of 12 (0x0C in hex) would make the song play twice as slow, a value of 3 (0x03 in hex) would make the song play twice at fast.

It behaves the same as the DOC Soundtracker 9 command but with support for a Tick Speed of 1.

Tick Speed does not reset back to the default once the song has finished, and will play at the last set Tick Speed until it encounters a new Tick Speed command.

Note Sample # Effect # Effect Data
A-2 1 F 0C
--- - F 06
--- - F 03
Set Tick Speed

The Effect Data on each row changes the Tick Speed, doubling the speed on each row.

Any Effect which is applied per Tick is affected by this command. For example pitch bend will only bend half the distance that was intended if the Tick Speed is changed from 6 to 3, because it is now only spending 3 Ticks on each row instead of 6.

BPM Speed changes are not supported by the DOC Soundtracker 2.2 editor and is expected to be played at 125BPM (50Hz PAL) speed.

Spreadpoint Soundtracker 2.3 Effect Commands

Spreadpoint Soundtracker 2.3 has identical commands and behaviour to DOC Soundtracker 2.2 with one additional command, Volume Slide.

Command Effect
0Arpeggio.
1Pitch bend up.
2Pitch bend down.
AVolume Slide.
BSequence Position Jump.
CSet Volume of sample.
DPattern Break.
EEnable/Disable the Filter on later Amigas
FSet tick speed.

Volume Slide.

Slide the current volume level every tick by the given amount, the range is 0 to 15 in decimal (0 to 0xf in hex) and is stored in the upper or lower nibble of the Effect Parameter depending whether your require to slide the volume level up or down.

Note Sample # Effect # Effect Data
A-2 1 A 34
Slide volume up Slide volume down

The Volume slide command can be used by itself to fade in/out a note.

Note Sample # Effect # Effect Data
A-2 1 A 01
--- - A 01
--- - A 01
--- - A 01
--- - A 01
Fade the current note out slowly.

The Effect Data on each row reduces the volume on this channel (that is playing sample 1), this is done per tick (except for tick 0) as opposed to the Set Volume command which is only applied once per row.

Noisetracker 1.0/1.1 Effect Commands

Noisetracker 1.0/1.1 has identical commands to Spreadpoint Soundtracker 2.3 with two additional commands, Tone Portamento and Note Vibrato.

Command Effect
0Arpeggio.
1Pitch bend up.
2Pitch bend down.
3Tone Portamento.
4Vibrato.
AVolume Slide.
BSequence Position Jump.
CSet Volume of sample.
DPattern Break.
EEnable/Disable the Filter on later Amigas
FSet tick speed.*

It should be noted that Noisetracker fixes some bugs in Arpeggio and Pitch Bend that appear in most of the previous trackers, so its behaviour is slightly different (it is as you would expect the commands to be):

  1. Previously Arpeggio stopped working if Tick Speed was set greater than 6 (or more precisely the arpeggio was only being applied to the first 6 ticks). This is fixed in Noisetracker.
  2. Previously Combining a Arpeggio followed by a Pitch Bend results in them ignoring each others note periods (a pitch bend up followed by a arpeggio would result in the arpeggio being played at the original note, not the note pitch bended too. Continuing the pitch bend would be from where the pitch bend ended, not from the arpeggio!). This was fixed in Noisetracker.

Changes to the Sample Loop behaviour

Sample Loops behave quite differently from Spreadpoint Soundtracker 2.3 (and Ultimate Soundtracker), in that the sample is always played from the beginning until the loop end point is hit, then the sample plays from the loop start point.

There are also 2 hidden modes of operations to the sample looping, or features (bugs that have been accepted as normal operation).

  1. Sample Chaining.
  2. Loop Offset Zero.
Sample Chaining

The ability to seamlessly switch to playing another looped sample as soon as the current loop has finished. This has many uses, probably the most prominent is allow to switch waveforms in “Chip tune” (its the heart of Matthew “4Mat” Simmonds M.T.S. technique - Multi Tone System used by many composers to create chip tunes in Noisetracker/Protracker). Switching samples is achieved by changing the sample number in sample column without a new note.

Note Sample # Effect # Effect Data
A-2 1 0 00
--- 2 0 00
--- 3 4 82

Will switch to sample 2 during row 1 ONCE sample 1 has reaching its loop ending, in row 2 it will switch to sample 3 if sample 2 loop ending is hit and it applies vibrato to the channel as well.

Technically the very first sample played (with a note) does not have to be looped, but subsequent samples do.

Loop Offset Zero

If the Sample Loop starting offset is set to 0 then WHOLE sample is played first before playing the sample loop as normal.

It should perhaps be noted that not all modern trackers support this mode, as its rather unknown.

Tone Portamento.

Slide the currently playing note to the new note set in the pattern every tick by the given amount, the range is 0 to 255 in decimal (0 to 0xff in hex) once set the destination note is stored along with the Effect Parameter (so that just using the Command by itself will use the previously stored value).

Note Sample # Effect # Effect Data
A-3 1 3 10
Slide speed

Once a note has been played, a subsequent destination note with the Tone Portamento Command start the portamento off.

Note Sample # Effect # Effect Data
A-2 1 0 00
--- - 0 00
B-3 1 3 01
--- - 3 00
--- - 3 00
Will pitch bend the note A-2 to B-3.

On the third row onwards the note will be pitch bending to the destination note every tick (except tick 0).

Vibrato.

Plays the current note with a sine wave being added to the note's period every tick (making the note change pitch up and then down). The Effect Parameter contains two values, the Speed and Size of the vibrato both values are ranged 0 to 15 in decimal (0 to 0xf in hex) the values are also stored for later use with just the Effect Command.

A Vibrato Size of two makes the range covered by the vibrato twice as big compared to using a Vibrato Size of one. Similarly a Vibrato Speed of two makes it vibrate twice as fast compared to a Vibrato Speed of one.

Note Sample # Effect # Effect Data
A-2 1 4 34
Vibrato Speed Vibrato Size

Once a note has been played, the Vibrato Parameters can be changed.

Note Sample # Effect # Effect Data
A-2 1 4 11
--- - 4 00
--- - 4 00
--- - 4 21
--- - 4 31
Will apply vibrato every row, and then increase the Vibrato Speed.

Using the Vibrato Command with an empty Effect Data will result in it using the last stored values for its Channel. Vibrato is applied every tick except tick 0.

Set Tick Speed.

Setting the Tick Speed sets the speed at which each row will be processed from now on (until another Tick Speed command is encountered). The Tick Speed has a range between 1 to 31 in decimal (1 to 0x1f in hex). The default Tick Speed is the same as Ultimate Soundtrackers (which is 6). A value of 12 (0x0C in hex) would make the song play twice as slow, a value of 3 (0x03 in hex) would make the song play twice at fast.

The Tick Speed range is clamped so trying to use a Tick Speed of 99 would be actually set a speed of 31. A value of 0 is ignored.

Tick Speed does not reset back to the default once the song has finished, and will play at the last set Tick Speed until it encounters a new Tick Speed command.

Note Sample # Effect # Effect Data
A-2 1 F 0C
--- - F 06
--- - F 03
Set Tick Speed

The Effect Data on each row changes the Tick Speed, doubling the speed on each row.

Any Effect which is applied per Tick is affected by this command. For example pitch bend will only bend half the distance that was intended if the Tick Speed is changed from 6 to 3, because it is now only spending 3 Ticks on each row instead of 6.

Spreadpoint Soundtracker 2.5 Effect Commands

Spreadpoint Soundtracker 2.5 has identical commands to Noisetracker 1.0/1.1, with the exception of Set Tick Speed being slightly different.

Command Effect
0Arpeggio.
1Pitch bend up.
2Pitch bend down.
3Tone Portamento.
4Vibrato.
AVolume Slide.
BSequence Position Jump.
CSet Volume of sample.
DPattern Break.
EEnable/Disable the Filter on later Amigas
FSet tick speed.*

Set Tick Speed.

Set the Tick Speed is different from Noisetracker in that the value is not clamped to the maximum value (of 31) instead a modulus is applied allowing the value to wrap round. Setting the Tick Speed sets the speed at which each row will be processed from now on (until another Tick Speed command is encountered). The Tick Speed has a range between 1 to 31 in decimal (1 to 0x1f in hex). The default Tick Speed is the same as Ultimate Soundtrackers (which is 6). A value of 12 (0x0C in hex) would make the song play twice as slow, a value of 3 (0x03 in hex) would make the song play twice at fast.

A Tick Speed of 0 is ignored, including AFTER the modulus is applied, so a trying to use a value of 32 (0x20 in hex) would be ignored as 32 MOD 32 = 0. Where a Tick Speed of 40 would actually set a Tick Speed of 8 as 40 MOD 32 = 8 which is a legal value.

Tick Speed does not reset back to the default once the song has finished, and will play at the last set Tick Speed until it encounters a new Tick Speed command.

Note Sample # Effect # Effect Data
A-2 1 F 0C
--- - F 06
--- - F 03
Set Tick Speed

The Effect Data on each row changes the Tick Speed, doubling the speed on each row.

Any Effect which is applied per Tick is affected by this command. For example pitch bend will only bend half the distance that was intended if the Tick Speed is changed from 6 to 3, because it is now only spending 3 Ticks on each row instead of 6.

Noisetracker 2.0 Effect Commands

Noisetracker 2.0 has identical commands to the Noisetracker 1.0/1.1 series with two additional commands, Tone Portamento with Volume Slide and Note Vibrato with Volume Slide.

Command Effect
0Arpeggio.
1Pitch bend up.
2Pitch bend down.
3Tone Portamento.
4Vibrato*.
5Tone Portamento with Volume Slide.
6Vibrato with Volume Slide.
AVolume Slide.
BSequence Position Jump.
CSet Volume of sample.
DPattern Break.
EEnable/Disable the Filter on later Amigas
FSet tick speed.

Changes to Vibrato*.

The Vibrato command is slightly different from Noisetracker 1.0/1.1 in that now the Vibrato Size is half the size. So in order to get exactly the same effect from Noisetracker 1.0/1.1 you will have to double the Vibrato Size parameter.

Tone Portamento with Volume Slide.

Slide the current tone AND the volume level every tick, the Effect Parameter is used for the Volume Slide, the range is 0 to 15 in decimal (0 to 0xf in hex) and is stored in the upper or lower nibble of the Effect Parameter depending whether your require to slide the volume level up or down.

Note Sample # Effect # Effect Data
A-2 1 5 34
Slide volume up Slide volume down

The Tone Portamento with Volume Slide requires that a Tone Portamento Speed be set first using the Tone Portamento command, this does not have to have been done in the current pattern.

Note Sample # Effect # Effect Data
B-1 1 3 04
A-2 1 - --
B-3 1 5 01
--- - 5 01
--- - 5 01
--- - 5 01
--- - 5 01
Tone Portamento from A-2 to B-3 at speed 4 and fade out.

Tone Portamento from A-2 to B-3 at speed 4 and fade the current note out slowly, the Effect Data on each row reduces the volume on this channel (that is playing sample 1), this is done per tick (except for tick 0).

Vibrato with Volume Slide.

Plays the current note with Vibrato added AND slide the volume level up or down.

This command requires that the Vibrato Command has already been used in order to store its Command Parameter for use with the Volume Slide.

Note Sample # Effect # Effect Data
A-2 1 6 34
Slide volume up Slide volume down
Note Sample # Effect # Effect Data
--- - 4 32
A-2 1 6 01
--- - 6 01
--- - 6 01
--- - 6 01
Will apply vibrato every row, and then Fade out.

The Vibrato Command sets a Speed of 3 and a Size of 2 which is used for every row.

Protracker 1.1+ Effect Commands

Protracker 1.1 and above have identical commands to the Noisetracker series with additional commands, most notably the “E” Extended Commands that allow for an additional 14 commands whilst still retaining backwards compatibility for turning On/Off the Amiga Filter.

Command Effect
0Arpeggio.
1Pitch bend up.
2Pitch bend down.
3Tone Portamento.
4Vibrato.
5Tone Portamento with Volume Slide.
6Vibrato with Volume Slide.
7Tremolo.
9Set Sample Offset.
AVolume Slide.
BSequence Position Jump.
CSet Volume of sample.
DPattern Break*.
EExtended Commands.
FSet Tick/BPM speed*.

Tremolo.

Tremolo is very similar as Vibrato but instead of changing the pitch of the note it adjust the volume.

By default the waveform used for Tremolo is a sine wave, this can be changed with the E Command “Set Tremolo Waveform”.

Tremolo plays the current note with a sine wave being added to the channels volume every tick (making the note fade in and out). The Effect Parameter contains two values, the Speed and Size of the tremolo both values are ranged 0 to 15 in decimal (0 to 0xf in hex) the values are also stored for later use with just the Effect Command.

A Tremolo Size of two makes the range covered by the tremolo twice as big compared to using a Tremolo Size of one. Similarly a Tremolo Speed of two makes it the volume change twice as fast compared to a Tremolo Speed of one.

Note Sample # Effect # Effect Data
A-2 1 7 34
Tremolo Speed Tremolo Size

Once a note has been played, the Tremolo Parameters can be changed.

Note Sample # Effect # Effect Data
A-2 1 7 11
--- - 7 00
--- - 7 00
--- - 7 21
--- - 7 31
Will apply tremolo every row, and then increase the Tremolo Speed.

Using the Tremolo Command with an empty Effect Data will result in it using the last stored values for its Channel. Tremolo is applied every tick except tick 0.

Set Sample Offset.

Allows you to change the starting position when playing the current sample.

The Effect Data contains the desired offset divided by 256, it should also be noted that this value is in hex and that only the first 64K of the sample is reachable.

Note Sample # Effect # Effect Data
A-2 1 9 9f
Sample Offset / 256

The Effect Data (0x9f in hex which is 159 in decimal) is multiplied by 256 to provide the new offset to play from (0x9f00 in hex this is just a simple matter of moving the Effect Data two nibbles to the left) when a new note is issued. Thus the maximum offset obtainable is 0xff00 if a Effect Data of 0xff is used.

Using the Sample Offset Command with an empty Effect Data will result in it using the last stored values for its Channel. Sample Offset is applied once on tick 0 for the current sample before it starts play and, oddly, once afterwards which does not affect the playing sample only subsequent samples.

There are lots of Quirks and undocumented features associated with this command, which unfortunately many tracker routines are not aware of and play incorrectly.

Row Note Sample # Effect # Effect Data Offsets used in PT 1 & 2 Protracker 3
01 A-2 1 9 0f 0f00 0f00
02 B-2 - - -- 1e00 0f00
03 C-2 - - -- 1e00 0f00
04 D-2 - 9 00 2d00 0f00
05 E-2 - - -- 3c00 0f00
06 F-2 - E D3 3c00 0f00
07 --- - E 93 3c00 0f00
Sample Offset / 256 Actual sample offsets

Quirk 1 Row 1: The note A-2 is played as expected with sample 1 starting at Offset 0xf00 BUT the Sample Offset is moved on again AFTER the sample has started.

Quirk 2 Row 2: will play sample 1 again but NOT from Offset 0 or Offset 0xf00, but 0x1e00 as the Set Offset Command is added twice, once before the sample starts and once again after the sample has started. Row 3 also plays sample 1 from Offset 0x1e00.

Quirk 3 Row 4: The use of command 9 with 00 (900) causes the last value used to be recalled (90f) which gets added to the existing Offset 0x1e00 + 0xf00 = 0x2d00 is the used Offset for sample 1. This value is again added after the sample starts as well, causing the Offset to move onto 0x3c00, which is used in row 5.

Quirk 4 Row 6: The Note Delay command will play the current sample from the Offset value IF the sample number is omitted.

Quirk 5 Row 7: The Retrigger Note command will retrigger the current sample from the Offset IF the sample number is omitted. Each retrigger uses the Offset value.

Quirk 6 Rows 1-7: The Protracker 3 series has a slightly modified replay routine which does not contain the Offset Quirks and will play ALL rows from Offset 0xf00 as expected.

The Weasel Audio Library handles these Quirks correctly and has the ability to set a Protracker 3 mode, in case the module was written using Protracker 3.

Pattern Break.

The Pattern Break command has been enhanced so that it now ends the current pattern and move onto the next Sequence Position at the given row.

Note Sample # Effect # Effect Data
A-2 1 D 00
Pattern Break

The Effect Data column now contains the row to jump to in the next pattern, using a value of 00 makes it behave exactly as defined in Doc Soundtracker 2.2. The Pattern Break occurs once the current row has finished (after 6 ticks at default speed). The command can be placed on any row and the new pattern will be played from the provided row number.

Note Sample # Effect # Effect Data
A-2 1 D 32
A-2 1 D 63
Pattern Break

It should also be noted that the row number in the Effect Data column is in fact entered in decimal, which is markedly different from every other command in Soundtracker. So in the above examples the Pattern Break will send it to Row 32 of the next pattern and Row 63 (the maximum) of the next pattern.

The Pattern Break command can be combined with a Sequence Position Jump command to jump to ANY sequence and row within the entire song, its even possible to play a pattern backwards! (But not recommended!)

Quirk: There is a Quirk associated with this command IF it is placed on the same row as a Pattern Row Delay command (EEx). This causes the Pattern Break command to jump to the row + 1, so for D32 it will actually skip row 32 and play from 33. If you used a value of D63 with a Pattern Row Delay command then the ENTIRE next pattern is skipped (63 + 1 is beyond the end of next pattern causing the song sequence to move on again).

Set Tick/BPM speed.

The normal Set Tick Speed command is extended to include adjusting the BPM speed on ANY Row of the song.

Effect Data Timing
00 Signals the End of the Song.
01-1F Adjusts the Tick Speed (1 to 31)
20-FF Adjust the BPM Speed.
Set Speed

The BPM speed is far more accurate than the original Ultimate Soundtrackers BPM system and can be taken as the actual BPM speed of a song IF the Tick Speed is the default speed of 6. Setting the BPM speed is in fact setting the speed of the CIA Timer (Complex Interface Adapter) which determines the speed at which the replay routine is called to process each Tick. The default speed of 125BPM is 50Hz, doubling this value to 250BPM results in 100Hz between Ticks and ALL Command Effects are processed at this new rate, meaning Pitch Bends are twice as fast.

Protracker VBL Mode

Protracker also provided a VBL mode, VBL stands for Vertical Blank which can be used to synchronise the music to a TV/Monitor exactly (which is typically PAL 50hz).

You may be wonder what's the difference between VBL mode and using a BPM of 125? None..

The difference is down to the behaviour of the Set Tick Speed command, which now ignores any BPM changes and uses the value to set the Tick Speed instead. This means it is possible to have a Tick Speed of up to 255.

Effect Data Timing
00 Is ignored.
01-FF Adjusts the Tick Speed (1 to 255)
Set Speed

Annoyingly there is no flag in the M.K. file format to indicate when to use VBL mode, it is extremely rare to come across modules that use the VBL mode but it is usually obvious, such as the BPM speed being set to 53 and the song length being over 13 minutes (such as Jogeir Liljedahl's “Slow Motion”).

Extended Commands.

The Extended commands use the first digit (nibble) of the Effect Data to select the Extended Command:

Extended Command Effect
E0Enable/Disable the Filter on later Amigas.
E1Fine pitch bend up.
E2Fine pitch bend down.
E3Tone Portamento Glissando Control.
E4Set Vibrato Waveform.
E5Set Fine Tune.
E6Row Loop.
E7Set Tremolo Waveform.
E9Retrigger Note (multiple times).
EAFine Volume Slide Up.
EBFine Volume Slide Down.
ECNote Cut.
EDNote Delay.
EEPattern Row Delay.
EFSample Invert Loop.

Fine pitch bend up.

Fine pitch bend up only applies the pitch bend once on the current row (on tick 0). Only the lower nibble of the Effect Data is used, allowing a value of 0-15 (0-f in hex).

Note Sample # Effect # Effect Data
A-2 1 E 14
Fine pitch bend up

The Effect Data's lower nibble has a value 0x4 in hex (4 in decimal) and is subtracted from the period value of the note once for the current row (this occurs on tick 0 when the note is first fetched). The pitch bend up command is clamped at the maximum note possible (B-3) and will not go any higher.

Fine pitch bend Down.

Fine pitch bend down only applies the pitch bend once on the current row (on tick 0). Only the lower nibble of the Effect Data is used, allowing a value of 0-15 (0-f in hex).

Note Sample # Effect # Effect Data
A-2 1 E 28
Fine pitch bend down

The Effect Data's lower nibble has a value 0x8 in hex (8 in decimal) and is added to the period value of the note once for the current row (this occurs on tick 0 when the note is first fetched). The pitch bend down command is clamped at the minimum note possible (C-1) and will not go any lower.

Note Portamento Glissando Control.

Glissando Control is only used with the Tone Portamento command, and it affects how the portamento is applied. Once active it causes Tone Portamento to slide a semitone at a time instead of smoothly.

This simulates the behaviour of trying to portamento between two notes on an instrument that has physically defined keys and no pitch bend facility, such as a piano, in that only the physical keys can be used.

Note Sample # Effect # Effect Data
A-2 1 E 31 Turn on Glissando
A-2 1 E 30 Turn off Glissando
Glissando is turned on/off for this channel

Set Vibrato Waveform.

In Protracker by default the Vibrato Command is set to using a sine wave so as to maintain compatibility with Noisetracker. However using the Set Vibrato Waveform Extended Command it is possible to change both the behaviour and the type of waveform to use during Vibrato.

Effect Data Type
0Sine wave (default)
1Ramp down (Sawtooth)
2Square wave.
3Does not exist*.

By adding 4 to the values in the above table you can stop the waveform from re-triggering from the start when a new note is played.

Effect Data Type
4Continue Sine wave
5Continue Ramp down (Sawtooth)
6Continue Square wave.
7Does not exist*.

* Protracker's documentation defines this waveform type as “Random” waveform, however its not implemented in the source code and the Editor uses Square wave instead. It should be note that other trackers implemented the Random waveform function and so may sound different, it is best not to use this value at all for best compatibility.

Note Sample # Effect # Effect Data
--- - E 45
A-2 1 4 82
C-2 1 4 82
Set Vibrato Waveform.

The Vibrato waveform for this channel is set to continuous sawtooth in the first row, on the second row the vibrato command is used. Because the re-trigger has been disabled, on row three the vibrato is not reset to play from the beginning but instead continues on from the position it was in on row 2 but is applied to the new note (C-2).

Set Fine Tune.

Samples in Protracker have a fine tune property, the Set Fine Tune command allows you to temporary override this value with another.

Interestingly Ultimate Soundtracker's original note period values are actually calculated from a NTSC clock constant and used on a PAL system, thus they are already slightly out of tune. For example take the A-1 period value of 508 and a NTSC clock constant of 3,579,545.

Using a PAL clock constant of 3,546,895 only gives:

There are 16 available fine tune values, in the range -8 to 7. The default is fine tune 0, which produces exactly the same period values as Ultimate Soundtracker.

Fine Tune Hex Value Frequency
77 458.0Hz
66 455.2Hz
55 451.5Hz
44 447.8Hz
33 445.1Hz
22 441.6Hz
11 439.0Hz
00 436.4Hz
-1F 432.1Hz
-2E 429.6Hz
-3D 426.3Hz
-4C 423.1Hz
-5B 419.9Hz
-6A 416.7Hz
-79 414.4Hz
-88 412.0Hz

To use the Set Fine Tuning Extended Command is as follows:

Note Sample # Effect # Effect Data
A-2 1 E 52
C-2 1 E 5F
Set Fine Tune.

On row 1 sample 1's own fine tuning is ignored and the sample is played with a fine tune of 2 instead. On row 2 Sample 1's own fine tuning is again ignored and the sample is played with a fine tune of -1.

Row Loop.

Row Loop allows you to set a loop point within the current pattern and loop to it n number of times. Each channel can have its own separate loop points defined and can even be nested. The loop starting point is defined by setting zero in the Effect Data on any given row, the loop end and its loop counter is defined by a non-zero value in the Effect Data on a subsequent row.

It should be noted that the loop counters work like tumblers on a lock, in that only once the counter reaches zero will the pattern continue on as normal. This is important when a row has more than one loop counter defined as ALL loop counters have to be zero before the pattern continues.

Row Note Sample # Effect # Effect Data
02 A-2 1 E 60
03 C-2 1 E 62
04 D-2 1 - --
Row Loop.

In the above example on Row 2 the Pattern Row Loop Start is defined with the Extended Command E60, the zero defining that this is the loop starting position. On Row 3 the Loop End is defined with the command E62, which has a loop counter of two. Thus Rows 2 & 3 will be played three times in total, once as a normal pattern row would be (which defines the loop positions) then twice more because the loop counter is set to two. Then Row 4 will be played.

Set Tremolo Waveform.

By default the Tremolo Command is set to using a sine wave, using the Set Tremolo Waveform Extended Command it is possible to change both the behaviour and the type of waveform to use during Tremolo.

Effect Data Type
0Sine wave (default)
1Ramp down (Sawtooth)
2Square wave.
3Does not exist*.

By adding 4 to the values in the above table you can stop the waveform from re-triggering from the start when a new note is played.

Effect Data Type
4Continue Sine wave
5Continue Ramp down (Sawtooth)
6Continue Square wave.
7Does not exist*.

* Protracker's documentation defines this waveform type as “Random” waveform, however its not implemented in the source code and the Editor uses Square wave instead. It should be note that other trackers implemented the Random waveform function and so may sound different, it is best not to use this value at all for best compatibility.

Note Sample # Effect # Effect Data
--- - E 75
A-2 1 7 82
C-2 1 7 82
Set Tremolo Waveform.

The Tremolo waveform for this channel is set to continuous sawtooth in the first row, on the second row the Tremolo command is used. Because the re-trigger has been disabled, on row three the tremolo is not reset to play from the beginning but instead continues on from the position it was in on row 2 but is applied to the new note (C-2).

It should be noted that there is a bug in the Ramp Down (Sawtooth) waveform in Protracker (both the Editor and replay code) which under certain circumstances (which requires Vibrato on Continuous waveform to have been played a certain amount on the current channel) will cause the Ramp Down Sawtooth to become a Ramp Up Sawtooth. (This bug is emulated correctly in the Weasel Audio Library.)

There is also an additional bug in (all) Protracker Editors that cause the Tremolo function to click on a tick of 0. This does not appear in the replay source code (just the editors).

Retrigger Note (multiple times).

Allows re-triggering of the note in a given channel. Re-trigger is allowed multiple times per row as it is based on the current tick. The Effect Data has a range of 0-f in hex (0 to 15) and represent the modulus and causes the note to be re-triggered when:

So a Tick Speed of 6 with a Re-trigger value of 3 will cause the note to be restart twice in the current row, on Tick 0 and on Tick 3.

Tick Retrigger Value Modulo
0 3 0 = Retrigger Note
1 3 1
2 3 2
3 3 0 = Retrigger Note
4 3 1
5 3 2

A Re-trigger value of 2 causes the note to restart three times with a Tick Speed of 6.

Tick Retrigger Value Modulo
0 2 0 = Retrigger Note
1 2 1
2 2 0 = Retrigger Note
3 2 1
4 2 0 = Retrigger Note
5 2 1

A Re-trigger value of zero is ignored and a re-trigger value greater than the Tick Speed will have no effect.

Row Note Sample # Effect # Effect Data
01 A-2 1 E 94
02 --- - E 93
03 --- - E 92
04 --- - E 91
Re-trigger note.

Will cause the note (A-2) to start twice on Row 1, assuming a Tick Speed of 6, at Tick 0 and Tick 4. On Row 2 it will restart twice, on Tick 0 and Tick 3. On Row 3 it will restart three times on Ticks 0, 2, 4. On Row 4 it will restart six times, on every Tick.

Quirk: If the Set Sample Offset command (9xx) has been used in a previous cell AND the Sample Number is omitted from the Re-trigger command the sample will be played with both the Retrigger command AND from the previously set Sample Offset each time.

Row Note Sample # Effect # Effect Data
01 A-2 1 9 20
02 --- - - --
03 B-2 - E 92
Row 3 Retrigger will play Sample 1 from Sample Offset 0x4000 each time.

Fine Volume Slide Up.

Fine Volume Slide Up slides up the volume once on Tick 0 in the current row by the value in the Effect Data that has a range of 0-f in hex (0 to 15). The volume range is clamped to 0-64.

Note Sample # Effect # Effect Data
A-2 1 E A1
--- - E A1
--- - E A1
Fine Volume Slide Up.

The volume level is slowly faded in. Compared to the normal Volume Slide Command if we assume a Tick Speed of 6 then the Fine Volume Slide will achieve the same level of volume but take six times longer.

Fine Volume Slide Down.

Fine Volume Slide Down slides down the volume once on Tick 0 in the current row by the value in the Effect Data that has a range of 0-f in hex (0 to 15). The volume range is clamped to 0-64.

Note Sample # Effect # Effect Data
A-2 1 E B1
--- - E B1
--- - E B1
Fine Volume Slide Down.

The volume level is slowly faded out. Compared to the normal Volume Slide Command if we assume a Tick Speed of 6 then the Fine Volume Slide will achieve the same level of volume but take six times longer.

Note Cut.

This command will cut the current note on the desired Row at a given Tick, the Tick to cut the note is provided in the Effect Data that has a range of 0-f in hex (0 to 15).

Note Cut is actually setting the Channels Volume to zero on the desire Tick, so it is possible to continue from a Note Cut by setting the Volume Level to a non-zero value (creating a stuttering effect).

Note Sample # Effect # Effect Data
A-2 1 E C3
--- - C 40
--- - E C5
Note Cut.

The note will be stopped on Tick 3 of Row 1, on Row 2 the volume level is set to maximum so we can hear Sample 1 again (it will have been silence for 3 Ticks) and then on Row 3 the note will be stopped again at Tick 5.

If the Tick Speed is smaller than the value of the Note Cut then the Note Cut is ignored.

Note Delay.

The Note in the current Pattern Cell is delayed by a given number of Ticks as provided in the Effect Data that has a range of 0-f in hex (0 to 15).

Any value that is larger than the Tick Speed is ignored.

Row Channel 1 Channel 2 Channel 3 Channel 4
Note SPL# Effect Note SPL# Effect Note SPL# Effect Note SPL# Effect
00 C-1 6 ED3 --- 0 000 --- 0 000 F#2 9 000
01 --- 0 147 C-3 f 201 --- 0 000 --- 0 000
02 F-1 6 147 --- 0 000 --- 0 000 F#2 9 ED4
03 C-1 6 ED2 C-3 f ED4 --- 0 000 F#2 9 000

Assuming a Tick Speed of 6, on Row 0 note C-1 is delayed for 3 Ticks on Channel 1, but does not affect the other channels so that Channel 4 plays F#2 on Tick 0 as normal. On Row 2 Channel 4 delays its Note F#2 by 4 Ticks, Channel 1 is not affected. On Row 3 Channel 1 delays its Note C-1 by 2 Ticks, Channel 2 delays its Note C-3 by 4 Ticks but Channel 4 is unaffected.

Quirk: If the Set Sample Offset command (9xx) has been used in a previous cell AND the Sample Number is omitted from the Note Delay command the sample will be played with both the Note Delay AND from the previously set Sample Offset.

Row Note Sample # Effect # Effect Data
01 A-2 1 9 20
02 --- - - --
03 B-2 - E D3
Row 3 Note Delay will play Sample 1 from Sample Offset 0x4000.

Quirk: Use of Note Delay with a value of 0 is considered bad, although valid it does in fact cause a software pause in Protracker 1 & 2 (the DMA Wait pause value, its duration is unreliable as its dependant on the CPU type and whether the code is executing in Chip or Fast RAM). This pause is ~0.5ms before the Note Delay occurs, there is then an additional wait of ~1ms before all channels not using a Note Delay will start playing. Each ED0 pause is considered separate and the delay for channels not using Note Delay is accumulative. In Protracker 3 the DMA Wait is removed and can be considered zero. The Weasel library emulates the ED0 pause and is disabled when Protracker 3 Sample Offset mode is used.

Pattern Row Delay.

Unlike Note Delay, Pattern Row Delay pauses the entire row for ALL channels AFTER the notes are played, Effect Commands are processed as normal (e.g. a Pitch Bend will continue to bend for the duration of the pause). Its timing is also different in that it counts in Row Fetches. The Effect Data contains the value to use for the Pattern Row Delay and has a range of 0-f in hex (0 to 15).

To calculate the actual delay multiply the value by the current Tick Speed. Or an easier way too see it is as the number of Rows that are additionally played before continuing.

Row Note Sample # Effect # Effect Data
01 A-2 1 E E4
02 --- - E E1
03 B-2 - - --
Pattern Row Delay.

On Row 1 the entire row is delayed by 4 Rows (assuming a Tick Speed of 6 then 4 * 6 = 24 Ticks are spent on Row 1). All other effects are processed as normal, if there is a Pitch Bend Command it will Pitch Bend for 24 Ticks. On Row 2 the delay is for an additional Row, so 12 Ticks before continuing to Row 3.

Quirk: There is a Quirk associated with this command IF it is placed on the same row as a Pattern Break command (Dxx). This causes the Pattern Break command to jump to the row + 1, so for D32 it will actually skip row 32 and play from 33. If you used a value of D63 with a Pattern Row Delay command then the ENTIRE next pattern is skipped (63 + 1 is beyond the end of next pattern causing the song sequence to move on again).

Sample Invert Loop.

Sample Invert Loop is a very dangerous command, once enabled it physically modifies any looped samples on the current channel. This is a destructive non-recoverable process, once as sample is changed its changed for good. This leads to problems when the module restarts as by that stage the samples have been modified and are different, although in reality most people probably wont notice the difference.

It should also be noted that this command is often, wrongly, referred to as “FunkIt” which is in fact the name of the previous Effect Command in Protracker 1.0 which was replaced in Protracker 1.1 and above. This confusion stems from the source code which, for reasons unknown, did not change the function name or labels and so still contain “FunkIt” or “Funk” even though the actual code has been changed.

There is also a difference in implementation between the Protracker's Editor and its accompanying stand alone replay source code. The Weasel Audio Library follows the Protracker's Editor implementation (which is considered correct). Under certain circumstances the original stand alone replay source code would call “Invert Loop” twice on Tick 0.

This may result in different Soundtracker replay routines playing the module differently if this command is used.

How the Invert Loop function actually works is that it goes through the current channels sample, which if looped, will start changing the sign on each individual sample byte until it reaches the end of the samples loop point, at which point it goes back to the beginning of the sample loop and continues changing the sign of each samples (so on the second pass its changing the sign back to what they were originally). It in effect turns the sample upside down.

The Effect Data of this command controls the speed at which it changes each sample byte, only one sample byte can be changed at a time. This is slightly confusing, but is relatively simple, once this command is activate there is an internal counter which is set to 128, once this counter reaches zero it moves onto the next sample byte and inverts it then resets the counter to 128.

The Effect Data is cross referenced with a table which contains the value to subtract each Tick from this internal counter. So using a value of 0xF, will result in a sample byte changing EVERY Tick as 0xF means subtract 128 every Tick from the internal counter, which is 128. If a value of 0xE is used then a sample byte is changed every other Tick, as 0xE has a Time Decrementor of 64 and it takes 2 * 64 to get 128. A value of 0xC results in a sample byte changing every 4 Ticks as 0xC has a Time Decrementor of 32 and it takes 4 * 32 to get 128.

A value of zero turns off this Command for this Channel.

Effect Data Time Decrementor
0 0
1 5
2 6
3 7
4 8
5 10
6 11
7 13
8 16
9 19
A 22
B 26
C 32
D 43
E 64
F 128

So what on earth can this complex command be used for? It is typically used to modify “Chip Tune” samples, those short single cycle samples, on the fly. It can also be used to create a perfect Square Wave Pulse Width Modulated sample (just like Robb Hubbard et al used on the C64).

To create this pulse width square wave first you need to create a short blank sample of 66 samples (for example, although you may need to adjust the length to tune the sample correctly), where each sample byte has the same non-zero value (a value of 127 can be used, but it is very loud). You can do this with most sample editors, a hex editor or even a text editor. Load the sample into Protracker and set the sample starting loop point to 2 and the loop length of 64 bytes (which is 0x40 in hex), the Loop Starting point is important as the first 2 bytes of a sample automatically get set to zero (and so wont be effected by Invert Loop as -0 = 0 and you will no longer have a proper square waveform). If you now play this sample you will hear….. NOTHING!! (Alright you may hear a click when you press a key but other than that..)

At this point its worth remember to save your module, as you'll want a copy of it BEFORE you destroy the actual sample data.

Now enter the following into a pattern on Channel 1, assuming that the loaded sample is in sample 1.

Row Channel 1 Channel 2 Channel 3 Channel 4
Note SPL# Effect Note SPL# Effect Note SPL# Effect Note SPL# Effect
00 C-1 1 EFF --- 0 000 --- 0 000 F#2 9 000
01 --- 0 147 C-3 f 201 --- 0 000 --- 0 000
02 F-1 1 147 --- 0 000 --- 0 000 F#2 1 000
03 --- 2 000 C-3 f 000 --- 0 000 F#2 9 000

On Row 0 Channel 1 the Invert Loop Command is enabled (with a Time Decrementor of 0xF which is 128). From now on ANY LOOPED SAMPLE that is played in Channel 1 will have its sample data inverted, every Tick (which is quite different from the other commands). As this change is permanent it means playing the same sample on another channel is also affected, in this case Row 2 Channel 4 will play the modified sample 1. Now it get interesting when Row 3 is played as Channel 1 switches to Sample 2, this means Sample 1 is now no longer modified (leaving it in the state it was in) and starts modifying Sample 2 instead. This is because we did not turn off the Invert Loop Command with 0xEF0. As you can see it can get very confusing and again you needed to save the module before playing or it will sound different each time you play it (or reload the samples).

You can also enable Invert Loop on more than one channel and have each channel modifying the same sample at the same time!

Sound Module

Originally Ultimate Soundtracker music was stored in a song file and samples stored on a separate disk. This kept the file sizes small (typically around 15k in size) and allowed samples to be shared between songs. However this became rather messy when including music into your own program (juggling both the song and all required samples separately it was easy to make a mistake and include a wrong sample).

Michael Kleps would take up the mantel and develop the Soundtracker series further, creating the first “Module” file in DOC Soundtracker 9, a module is a single file containing both the song and all the required samples. Later he would create the definitive version of the file format, the 31 sample “M.K.” module. A typical module is between 100K and 200K in size (uncompressed) and averages at 4-5 minutes of music.

Karsten added saving modules to Ultimate Soundtracker V2.0.

ST-01 Sample Disk

Before the Time + Space catalogue was available quality copyright/royalty free samples where difficult to obtain. Accompanying Ultimate Soundtracker was a sample disk with 114 samples on it ready for use, ranging from percussion instruments through strings and bass to lead instruments.

The distinctive sound of the ST-01 sample disk can be heard in early Soundtracker modules.

This is also the beginning of the Soundtracker sample disk series.

Dan Wilson of Hideaway Studio started a project (entitled Project ST-01) to identify all of the instruments used on the ST-01 sample disk and recreating high quality samples.

Corrupt Samples

Interestingly modules with corrupt samples can and do get past the Module Sniffer, this is unfortunate and not a great deal can be done about it. After all a “sample” by its very own definition is just a collection of data. What has become apparent though is that a lot of these corrupt samples are in fact the original IFF Sample Headers (as generated by the Aegis Audio Master software and hardware).

Of course just to make things difficult the whole IFF header is rarely present, but most of it usually is. Typically the “FORM” chunk ID is missing, this is because the Ultimate Soundtracker replay routine clears the first 4 bytes of each sample during initialisation and is an indication that the module was either memory ripped (after the software has run) or that its saved as a module directly from a Soundtracker Editor.

Initially I assumed that the headers where just because of bad rips, this is a mistake as the whole process of creating samples needs examining. Firstly when someone has created a sample (almost certainly done using Audio Master) the sample is saved to disk. The default file format is that of an IFF file (which stands for Interchange File Format created by Electronic Arts in 1985, but most people will known it as a .wav file as Microsoft ripped off the file format).

However the person creating the sample has almost certainly assumed the sample data has been saved as a raw format (just the sample data, no headers at all) which is what Ultimate Soundtracker requires. This sample file will have been copied over to a custom ST-nn sample disk (usually ST-01 as it happens, having deleted some lesser used files to make room). The Soundtracker Patch List is then updated with the details of the new Sample - these details will almost certainly be taken from Audio Master (the length and the loop points) and entered into the patch list. Of course now the file lengths in the Patch List do not match the length of the file (because of the IFF header) and the loop points are now off.

So now you have a sample that gets loaded into ram which is bigger than expected, and what happens when it is saved as a “module” (assuming DOC Soundtracker 9)? The file ends up bigger than it should be and can be detected as the sample sizes + pattern total mismatches.

However what happens if it is used in “song” format, where the samples are stored separately? Assuming that one of the SEKA Assembler replay routines is used, each sample is included off the disk (including the IFF headers). The Length and Loop Points of the sample are taken from the song file (which in turn is from the Patch List), BUT the starting position is taken from where the sample has been loaded (so it includes the IFF header). Which means the sample length is wrong and the loop points are wrong, as the IFF header needs stepping over.

Now its entirely possible that the programmer is aware of the IFF headers and calculates a new starting position so that the length and loop points are correct. But I don't think this happened very often.

In fact you can hear affected modules on the Amiga too, released on their original music disk (NightLight's “NoDrugs” and SLL's “Surprice” for example both play the IFF Header).

It should also be noted that with looped samples the IFF header probably wont get played as it will be outside the loop points. But the loop will not be in the correct place, which may then cause pops to occur when ever the sample is looped.

Now enter the ripper, whose job it is to extract the music. A good one will be able to locate to replay routine and find the starting positions of the samples from the code. They then get the length of the samples from the “song” format (which is of course the values entered into the Patch List) and saves them out individually. Of course the problem is now they contain the IFF headers and each affect sample gets truncated because the length does not include the IFF header.

The lot gets glued back together into a module, but now it contains the IFF headers too. :(

The good news is that if the original software/demo can be located, a full recovery should be possible and the IFF headers removed correctly. This, unsurprisingly, turns out to a bit more tricky, as you may need to find the demo/software where the affected sample was first used (this is due to sample ripping, as the sample itself may have been incorrectly ripped from another module). In most cases the original/first use can be traced.

So how many modules may be affect? (I hear you ask). Well, from the smallish collections of 281 modules I test against 73 of them contained one or more corrupt samples. Which is over 25% :O

The corruption also correlates with custom samples. However some of these affected samples made their way onto “official” ST-nn sample disks. Which means any module that uses them is affect too.

Detection

A simple method to find out if any of your (uncompressed) modules contain samples with a IFF Header in them is to use the grep command from the command prompt :

$ grep --recursive 8SVX ./sound_modules

This will search, recursively, through all sub directories in the “sound_module” directory and list all files containing “8SVX”, which is part of the IFF Header.

Solution

A better rip of the module is the best solution, but may not be immediately available (using automated ripping tools may not remove the IFF headers either).

What the Weasel Audio Library is now able to do is scan each sample for IFF headers and remove them. It has three modes, two are removal modes:

  1. Do not scan for IFF Header.
  2. Clear the IFF Header.
  3. Remove the IFF Header.

Clearing the header results in the sample position and loop points not changing, it just zaps the header.

Removing the header, removes the data from the sample and shifts all samples along by the length of the header. Resulting in the loop points (hopefully) being in the correct place. This of course means it has to introduce new data at the end of the sample, which is just the last sample played.

The other mode available disables scanning for IFF Headers, in case the scanning is incorrectly identifying them (or making the sample sound weird, perhaps the loop points now contains blank/zero'd sample data).

The IFF header is typically 100 bytes in length, and on occasion there can be more than one present in the same sample.

Ultimate Soundtracker versions

There are four known versions of Ultimate Soundtracker.

Version 1.0 Not publicly available, used in house at reLINE.
Version 1.21 The first commercial version of Ultimate Soundtracker sold by reLINE.
Version 1.8 Added song speed support (BPM).
Version 2.0 Added the ability to save modules, multiple ST-nn sample disk support.

Why JavaScript?

Good question, JavaScript is one of the few languages that runs on many different operating systems and closed platforms without the need for additional plug-ins. It does not need compiling for individual target machines or CPUs and is very close to the “write once, run everywhere” philosophy when written correctly.

Although as noted currently (2012) there is no ratified standard for producing/playing audio created by JavaScript. Both the Mozilla's solution and WebKit's have not been ratified yet by the W3C and the HTML5 DataURI hack is a poor solution at best (it is not fully cross browser compatible either).

How to use

To use the library simply include the either the development or minimized version of the library file in your web page. The developer version of the library file still contains line breaks and JsDoc function comments, so its easier to develop with that the minimized version of the file.

Simplest playback - Easy Player

The easiest method for playing back a module is via the Helper Easy Player functions, which requires 5 lines of JavaScript to start a module playing.

See demo2 for working example.

There are 3 simple functions associated with the Easy Player method:

Function name Description
weasel.Helper.easyPlay(xModuleData) Does all the hard work of initialising the module and the audio subsystem, returns either the BrowserAudio object upon success or an error message upon failure. It defaults to 44.1Khz playback with a 300ms audio buffer and a 200ms update interval rate (5 times a second).
weasel.Helper.easyStart() Start playing/unpause the module.
weasel.Helper.easyStop() Stop/pause playing the module.

It is also strongly advised to add the Easy Menu to the page so that users can adjust the audio settings, which is done with the following function:

Function name Description
weasel.Helper.addEasyMenu(oBrowserAudio,oMenuElement,bAdvancedMenu) Adds the Easy Menu to the page, attaching to Element oMenuElement and associate a BrowserAudio object with it.

If the (LGPL 3) JSXCompressor library is found then .gz and .zip compressed modules are decompressed automatically (assumes one module per archive). This includes base64 embedded .gz and .zip files. The JSXCompressor library is not part of this project and will need to be downloaded separately.

Minimal example

A minimal example is as follows (you may need to change the location and file name of the library):

<!DOCTYPE html>
<html>
<head>
	<title>Play Ultimate Soundtracker Module</title>
 
	<script type="text/javascript" src="release/weaselaudiolibrary-1.8.0.js"></script>
 
	<script type="text/javascript">
 
	function initAudio()
	{
		var oBrowserAudio = weasel.Helper.easyPlay( document.getElementById( 'Module' ) );
 
		if( !(oBrowserAudio instanceof weasel.BrowserAudio) )
		{
			alert( oBrowserAudio );
			return;
		}
 
		weasel.Helper.addEasyMenu( oBrowserAudio, document.getElementById( 'AudioSettings' ), false );
	}
 
	</script>
</head>
<body onload="initAudio();" >
 
	<input type="button" value="Start" onclick="weasel.Helper.easyStart();" title="Restart the player, if you previously stopped it."/>
	<input type="button" value="Stop" onclick="weasel.Helper.easyStop();" title="Stop the JavaScript dead, frees up all that CPU again!"/> 
 
	<span id="AudioSettings" style="display: inline-block;" ></span>
 
	<span id="Module">Base64 encoded module data goes here.</span>
</body>
</html>

Walk through

Once the page has loaded, the onload() function of the Body element calls the initAudio() function. This function firstly tries to initialise the Easy Player system with a module that has been Base64 encoded and embedded within the page (its in the span with the id of “Module”, although in the above example the Base64 data has been removed to save space). If successful this function will return a weasel.BrowserAudio object, which is needed for attachment to the Easy Menu. If the easyPlay() function fails for some reason (not a supported module type, mismatch file size, garbled data etc) it returns an error message instead describing why it failed.

	var oBrowserAudio = weasel.Helper.easyPlay( document.getElementById( 'Module' ) );

Next it checks to see if an error message occurred, if so display the error message via an alert dialogue box and go no further:

	if( !(oBrowserAudio instanceof weasel.BrowserAudio) )
	{
		alert( oBrowserAudio );
		return;
	}

If no errors occurred then an Easy Menu is created and attached to the page, the Easy Menu requires the created weasel.BrowserAudio object so it can control the audio settings and an HTML element (typically of span type) to attach to:

	weasel.Helper.addEasyMenu( oBrowserAudio, document.getElementById( 'AudioSettings' ), false );

The Easy Menu is a typical CSS drop-down-and-to-the-right variety and should preferably be places in the top left of the page, else it will enlarge the page upon expansion. The styling can be overridden if the defaults are unsuitable:

	<style type="text/css">
		ul.weaselMenu li{
			background: white !important;
		}
	</style>

The above changes the menu background to white, via the !important CSS declaration which overrides the existing value.

The playing of music does not start automatically, as this is considered bad etiquette on the web, it is controlled by two buttons on the page, one to start the other to stop. The Start button's onclick() function consists of:

	weasel.Helper.easyStart();

And the Stop button's onclick() function consists of:

	weasel.Helper.easyStop();

Advantages and Disadvantages with Easy Player

JavaScript is single threaded, its event queue can only process one event at a time and has no concept of priority.

The Easy Player method updates the audio using its own event driven by the setInterval() function 5 times a second. If you are intending smooth animation, e.g. 60 frames per second, Easy Player may interfere and it is better to update the audio yourself (within your main loop) and not use the Easy Player.

If you flood the event queue with time consuming tasks (for example each taking half a second to complete) this will causes the event queue to become saturated and stall, the audio may start to break up. Remember that the Easy Player is trying to create audio in real-time, so all your tasks need to complete quickly. The audio buffer, by default, is set to 300ms, although it may contain 400ms of audio due to the interval rate of 200ms.

Creating and delete lots of objects causing the Garbage Collector to take a longer time to run, in effect stalling the JavaScript Engine. So careful attention is needed to eliminate unnecessary object creation.

(Hint: In Firefox you can enable “javascript.options.mem.log” in the about:config page and see how long the Garbage Collector has taken in the Error Console.)

DOM access is very slow, it might be necessary to defer a partial update as a separate event, giving the audio event the opportunity to run.

You can retain the weasel.BrowserAudio object and have more direct control over module and audio, change module etc, but be aware that this may clash with the Easy Player.

Advanced playback

The replay library is intended for use from within the main loop of your software, which is assumed to be running as a setInterval() event.

This is on purpose so that it does not interfere with any animation or add any additional events in the event queue and not cause any judder/variance in the interval period.

(Note: No additional events added to the event queue is only valid for MozAudio, AudioContext [webkit] browsers cause audio events to occur. The DataURI HTML5 Audio needs an event every 2 seconds in order to synchronize its audio to HTML5.)

During runtime no additional objects or variables are created to minimise Garbage Collection, unfortunately the HTML5 DataURI audio is the exception here as it does create lots of string (during Base64 encoding).

Namespace

In order that the JavaScript global namespace does not get polluted the namespace weasel has been used, all classes associated with this library are stored within it, much like a package for organising classes in Java - however it does differ in that variables can be stored within it. Which is exactly where Easy Player stores it needed variables, and not in global variable space.

weasel.BrowserAudio

The heart of the replay library is the weasel.BrowserAudio object, and is the first thing that needs creating.

	var iReplayFrequency = 44100;
	var iIntervalMsRate = 16;
 
	var oBrowserAudio = new weasel.BrowserAudio( iReplayFrequency, iIntervalMsRate );

During construction it sniffs out what audio subsystem it should use (AudioContext, MozAudio, HTML5 DataURI in that order).

Constructor Description
weasel.BrowserAudio( iReplayFrequency, iIntervalMsRate ) Object to abstract the different methods for playing audio in different browsers.

This requires two parameter, Replay Frequency and the expected Interval Rate.

Replay Frequency is an integer value in hertz and a value of 44100 is a good starting point as all modern sound cards support this (indeed some sound cards do not support any other value). Chances are your Audio Server (such as Pulse Audio) is set to run at this frequency as well.

When the AudioConext subsystem (Webkit browsers) is used this value is actually ignored as you cannot set the replay frequency, AudioConext determines that value for you. So do not expect the chosen value to be the actual value used, do not hard code a value, always read the current replay frequency with weasel.BrowserAudio.getPlaybackFrequency().

What happens if you choose a replay frequency that is not supported? Typically no audio is heard at all, audio production ceases as the current audio is not going anywhere. If a Audio Server is present it will usually re-sample your chosen frequency into what ever value it is using (which is why its better to match these values). Of course finding out what frequency your Audio Server is actually running at is a whole separate kettle of fish..

The Interval Rate is a bit more tricky, but the idea is simple. It is the estimated amount of audio to create each call. So if you have your main loop running at 60fps then this value is 16 milliseconds (1000ms / 60fps = 16.67ms) . At 60fps there is a 16ms wait before the next frame, so 16ms of audio is needed to cover the gap.

Below is a table of values for easy reference:

Frame Rate Milliseconds of audio
20 50
30 33
40 25
50 20
60 16
75 13
100* 10
120* 8
150* 6
200* 5
* Most browsers don't support low ms setInterval()
values, may also be dependant on O/S.

Notice that the word estimated is used, as the timing accuracy is JavaScript is wacky at best, it may be that the frame rate drops by half (due to slow processor, garbage collection or any other external event) or the event queue is full and needs processing first before the interval is even allowed to trigger. So a interval timer may not be able to maintain a steady frame rate (its almost guaranteed), this is not a problem for the audio library as it will make more audio if it detects that it has fallen behind (or less if it has run ahead).

The point of this value is that you don't want to make more audio that you have to, thus freeing up as much CPU time as possible. Even if you choose the wrong value its not the end of the world, but what would happen is that on some frames the library will consume more CPU time and suddenly free up lots (i.e. choppy CPU usage). This would not create audio stutters or break up in playback unless a very small audio buffer is used (in which case timing is critical).

Its worth noting that a default audio buffer size of 270ms is used, as this value seems to work well across all browsers and different O/S's.

Also that just like the Easy Player method you can now associate the Easy Menu with the newly created weasel.BrowserAudio object, or create your own menu system.

weasel.ModuleSniffer

Next you need a way of converting the binary data that is a Soundtracker Module file into a usable object. Because there are lots and lots of slightly different Soundtracker Module formats it is quite tricky to identify exactly which Soundtracker editor actually created the module file. For this purpose the weasel.ModuleSniffer object exists, this takes the module as an array of bytes (how you load the module data into JavaScript is up to you) and checks that its data is valid and can be played.

Upon sniffing completion and if the data is identified as a valid module you can then create a Soundtracker Module object out of the data for use. If the sniffer fails then an error message is returned identify what the problem is (size mismatch, unsupported effects used - a sign that it is not a Ultimate Soundtracker module).

Continuing from the code above and assuming that our module has already been loaded and converted into an array (aModuleData) the sniffer would be invoked thus:

	var oModule = null;
	var oModuleSniffer = new weasel.ModuleSniffer();
	oModuleSniffer.sniff( aModuleData, false );
 
	if( 'ok' == oModuleSniffer.getReason() )
	{
		oBrowserAudio.init();
 
		oModule = oModuleSniffer.createModule( aModuleData, oBrowserAudio.getPlaybackFrequency(), weasel.Sample.prototype.SampleScannerMode.Remove_IFF_Headers );
		oBrowserAudio.setModule( oModule );
		oBrowserAudio.start();
	}
	else
	{
		alert( oModuleSniffer.getReason() );
	}

The Module Sniffer's method sniff() is called to actually sniff the data and the result is check with the getReason() method which returns 'ok' upon success, if it has failed this same method will return the error message (in the above example it is displayed within an alert box). (There is also the getAllReasons() method, which will return an Object containing the reason for failure against each module type sniffed for.)

Assuming 'ok' the Browser Audio object is initialised next, which only needs calling once, this sets up all the required attributes and object needed for what ever audio subsystem is being used.

Next the Module Sniffer's createModule() method is called, notice that the current replay frequency is passed in as a second parameter, this value should not be hard coded as mentioned above although we asked for 44.1Khz it does not mean we actually got it! createModule() will return the correct Module type object for use with the weasel.BrowserAudio object. The third parameter determines whether IFF Headers should be scanned for in the sample data and what to do with them if found.

Next the module is attached to the weasel.BrowserAudio object, so that it can be played, this is done with the setModule() method.

To play the module method start() is called. Multiple module objects can simultaneously exist, although only one is ever attached to the weasel.BrowserAudio object at a time. Each song's current position is stored independently in its own module object, switching between modules with setModule() allows each song to be played from the position it was previously at. (Calling module.restartSong() will play the module from the beginning again if required.)

A more complete version can be found in demo3, which contains the following initialisation function:

function initialiseSound()
{
	var iReplayFrequency = 44100;
	var iIntervalMsRate = 16;
	var iPreBufferMS = 300;
	var iInterpolationType = weasel.Channel.prototype.SupportedInterpolationTypes.Linear_Interpolation;
 
	oBrowserAudio = new weasel.BrowserAudio( iReplayFrequency, iIntervalMsRate );
	oBrowserAudio.setPreBufferingInMS( iPreBufferMS );
 
	var oBase64Module	= document.getElementById( 'Module' );
	var aModuleData		= weasel.Helper.base64Decode( oBase64Module.innerHTML );
	aModuleData		= weasel.Helper.checkForCompression( aModuleData );
	var oModuleSniffer	= new weasel.ModuleSniffer();
	oModuleSniffer.sniff( aModuleData, false );
 
	if( 'ok' == oModuleSniffer.getReason() )
	{
		oBrowserAudio.init();
 
		var oModule = oModuleSniffer.createModule( aModuleData, oBrowserAudio.getPlaybackFrequency(), weasel.Sample.prototype.SampleScannerMode.Remove_IFF_Headers );
		oBrowserAudio.setModule( oModule );
		oBrowserAudio.setInterpolation( iInterpolationType );
 
 
		if( weasel.BrowserAudio.prototype.AudioType.HTML5Audio == oBrowserAudio.getAudioType() )
		{
			// Switch to 16Khz playback due to HTML5 Audio being so slow (or rather slow at a time critical moment).
			//
			oBrowserAudio.changeReplayFrequency( 16000 );
		}
 
		oBrowserAudio.start();
	}
	else
	{
		alert( oModuleSniffer.getReason() );
	}
 
	weasel.Helper.addEasyMenu( oBrowserAudio, document.getElementById( 'AudioSettings' ), false );
}

Note: That it is up to you change the replay frequency if HTML5 DataURI audio is used, whose behaviour is different per browser vendor. Also that the module data has been embedded in the web page in Base64 format.

It is also recommended to add the Easy Menu, as it allows users to change the settings.

Loading a module via the web

A major problem is that a Soundtracker Module is in binary format, HTML does not support binary directly. There are several ways to embed or load a module from a web page:

There are various combination of above that can be used, especially if server side scripting is used, although it's rather a waste of server resources just to load a binary file, encode it into base64 and serve it up as JSON for each request - certainly pay attention to page caching with this method.

Base64

There are various tools to help you base64 encode files, or use almost any server side script to generate it for you (such as base64_encode in PHP). To encode a file from the command line is quite simple in GNU/Linux (the base64 command is part of the coreutils package in Debian/Ubuntu), chose the module and pipe the results to a file:

$ base64 --wrap=0 mod.amegas > mod.amegas.base64

This converts the file 'mod.amegas' and pipes the output to file 'mod.amegas.base64'. Which can then be opened in any text editor and cut and pasted into your web page, inside a DIV or SPAN tag that has its visibility set to hidden (so it does not display to the user in the web browser).

The option '–wrap=0' has been added as normally base64 encoding is supposed to line wrap every 76 characters, it was designed for email multi-media content attachments (see RFC 2045) , which is not needed.

Base64 encoding expands all files by 33%, for every 3 bytes of input 4 bytes are outputted, although this does not really effect the size of the content-encoding when gz compressed via most web servers (such as Apaches' mod_gzip & mod_deflate extensions).

Method 'mod.amegas' file size (bytes) % Ratio of original
uncompressed 68,864 100%
Base64 encoded 91,820 133%
Binary .gz compressed 46,629 67%
Base64 .gz compressed 52,616 76%

As can be seen there is a 9% loss of compression due to Base64 encoding when comparing Binary .gz to Base64 .gz.

Mod Packers

At the time of Ultimate Soundtracker's creation not a lot was known about compressing samples as a result module files do not compress well, although there is plenty of repartition in the song part of the module, which compressed well, the sample data does not. As it would turn out delta encoding on 8 bit samples works well, and resulted in lots of different compressed module formats (called mod packers) in order to shrink modules down to their smallest possible size.

Base64 decoding

Using Base64 encoding does leave you with the problem of decoding it JavaScript. Although the function window.atob does exist, it is not present in all browsers (most notably the Internet Explorer series but there may be many mobile browsers as well).

It is for this reason the the helper function weasel.Helper.base64Decode() exists, which has the benefit of returning a binary array (as opposed to a string). So it is recommended to use this function over window.atob(), although you can roll your own as long as it decodes correctly.

.gz & .zip support via the JSXCompressor library

The Weasel Audio Library checks to see if the (LGPL 3) JSXCompressor library is available for use, if it is then compressed modules are support.

The LGPL 3 JSXCompressor library is part of the (JXG) JSXGraph project is not part of or included in this project and needs to be downloaded separately.

Decompression is done automatically with the EasyPlay method. For those using more advanced methods, there is a helper function to decompress the module:

Function Description
weasel.Helper.checkForCompression( aModuleData )Decompress the module if the JSXCompressor library is available.

Calling this function with a uncompressed module returns the module unaffected, calling this function with a compressed module but without the JSXCompressor library available returns the compressed module back (there is no way to decompress it).

This means it is safe to call the function with both compressed & uncompressed modules before handing the data over to the Module Sniffer:

	var oBase64Module	= document.getElementById( 'Module' );
	var aModuleData		= weasel.Helper.base64Decode( oBase64Module.innerHTML );
	aModuleData		= weasel.Helper.checkForCompression( aModuleData );
	var oModuleSniffer	= new weasel.ModuleSniffer();
	oModuleSniffer.sniff( aModuleData, false );
 
	if( 'ok' == oModuleSniffer.getReason() )
	{ 
etc.....

In the above example it is safe to assume that a base64 encoded gzipped module that is embedded in the web page is decoded then decompressed and then played and that it will also work if the embedded module is uncompressed.

Including the JSXCompressor library

Simply include the minimized version of the JSXCompressor library, which is about 12K, in your web page for it to become available for use. No need to call any initialisation functions, it is also contained within its own namespace (JGX) so should not clash with any of your existing code.

<!DOCTYPE html>
<html>
<head>
	<title>Play Ultimate Soundtracker Module</title>
	<script type="text/javascript" src="3rdparty/JSXCompressor/jsxcompressor.min.js"></script>
	<script type="text/javascript" src="release/weaselaudiolibrary-1.8.0.js"></script>
etc...

Override the mime type

Overriding the mime type makes it possible to load binary resources from a web server directly via Ajax, unfortunately not all web browsers support this (Internet Explorer series again). it is better to sniff for support and provide some fall back method if not found.

The XMLHttpRequest object has the method overrideMimeType, present in most old browsers. Newer browsers also support XMLHttpRequest.responseType which if set to “arraybuffer” can be cast into a unsigned byte array upon successful loading.

For overrideMimeType() function to load binary data accessible from JavaScript mime type 'text/plain; charset=x-user-defined' is needed. Which will load the binary data as a string, which can then be convert to binary. The Helper function weasel.Helper.convertStringToArray() exists for this purpose and returns a byte array containing the data.

Here is a cut down example to load and start a module with overrideMimeType(), NOTE: not all browsers support XMLHttpRequest :

	function loadAndInitAudio( sURL )
	{
		if( !window.XMLHttpRequest )
		{
			alert( 'Unable to load resource as XMLHttpRequest is unsupported, sorry.' );
			return;
		}
 
		var oRequest = new XMLHttpRequest();
 
 
		// onload Event handler (once the module has loaded).
		//
		oRequest.onload = function( oEvent )
		{
			var aModuleData		= weasel.Helper.convertStringToArray( oRequest.responseText );
			var oBrowserAudio	= weasel.Helper.easyPlay( aModuleData );
 
			if( !(oBrowserAudio instanceof weasel.BrowserAudio) )
			{
				alert( oBrowserAudio );
				return;
			}
 
			weasel.Helper.addEasyMenu( oBrowserAudio, document.getElementById( 'AudioSettings' ), false );
		};
 
		// Override the MIME type to load binary data as a string.
		//
		if( oRequest.overrideMimeType )
		{
			oRequest.overrideMimeType( 'text/plain; charset=x-user-defined' );	
		}
		else
		{
			alert( 'Unable to load resource via XMLHttpRequest as overrideMimeType is missing, sorry.' );
			return;
		}
 
		// Start loading the data.
		//
		oRequest.open( 'GET', sURL,  true ); 
		oRequest.send( null );
	}

To load a module via XMLHttpRequest.responseType is almost identical, but Typed Arrays need to be supported:

	function loadAndInitAudioAsBinary( sURL )
	{
		if( !window.XMLHttpRequest )
		{
			alert( 'Unable to load resource as XMLHttpRequest is unsupported, sorry.' );
			return;
		}
 
		// If typed arrays are unsupported can't load via 'responseType' method.
		//
		if( !window.Uint8Array )
		{
			alert( 'Unable to load resource via XMLHttpRequest as typed arrays (Uint8Array) are unsupported, sorry.' );
			return;
		}
 
		var oRequest = new XMLHttpRequest();
 
 
		// onload Event handler (once the module has loaded).
		//
		oRequest.onload = function( oEvent )
		{
			var aModuleData		= new Uint8Array( oRequest.response );
			var oBrowserAudio	= weasel.Helper.easyPlay( aModuleData );
 
			if( !(oBrowserAudio instanceof weasel.BrowserAudio) )
			{
				alert( oBrowserAudio );
				return;
			}
 
			weasel.Helper.addEasyMenu( oBrowserAudio, document.getElementById( 'AudioSettings' ), false );
		};
 
 
		// Start loading the data.
		//
		oRequest.open( 'GET', sURL,  true ); 
		oRequest.responseType = 'arraybuffer';
		oRequest.send( null );
	}

Using the Jukebox (demo 1) in a web site

The Weasel Jukebox now has an API specifically for the aid of integrating it into a web site.

The 'demo1/WeaselJukeboxAPI.js' file that has two simple helper functions for talking to the Jukebox:

Function Name Description
jukebox.openJukebox( sURLToJukebox, sURLToPlayList, bAutoStart )opens the Jukebox in a popup window.
jukebox.addToPlayList( sComposer, sMusicName, sURL )Add a tune to the playlist, if the Jukebox is open.

jukebox.openJukebox

Will open the Weasel Jukebox in its own pop-up, the parameters passed in allow you to pre-load an existing playlist and to auto start playing music.

The following example has two links on the page, one opens the Weasel Jukebox without a playlist and does not start playing any music. The other loads a previously exported playlist and starts playing the music.

<!DOCTYPE html>
<html>
<head>
<title>Test page to open the Weasel Jukebox.</title>
	<meta charset="UTF-8">
	<script type="text/javascript" src="js/demo1/WeaselJukeboxAPI.js"></script>
</head>
	<hr />
	<a href="demo1.html" onclick="return jukebox.openJukebox( '', '', false )">Open the Jukebox, without a playlist and do not start any music.</a>
	<hr />
	<a href="demo1.html" onclick="return jukebox.openJukebox( '', 'WeaselJukeboxPlayList.json', true )">Open Jukebox with a playlist and start playing music.</a>
<body>

Notice that the sURLToJukebox is empty as the Weasel Jukebox (currently named 'demo1.html') is in the current URL/directory and so does not need a value, in the second example the playlist ('WeaselJukeboxPlayList.json') is located in the same directory. The playlist is loaded via a AJax request once the Jukebox has loaded and can be dynamically created by server side script if so desired.

Currently there are disadvantages to this function, which should be noted:

  1. If the Jukebox is already open, the window is re-opened (this does not create multiple windows), however all audio settings are lost due to the page reloading (for example if you've selected 96Khz, on reload it will be set to the default).
  2. Local drag and dropped files are lost.
  3. The module cache is cleared (meaning the browser will reload them from the server).

jukebox.addToPlayList

In order for this function to work the web browser must have enabled and support for Local Storage (also known as DOM Storage), unfortunately some browsers also require Cookies enabled in order to allow access to Local Storage (Firefox and Chromium/Chrome) although Cookies are not actually used by the Weasel Jukebox.

Any modern desktop browser will support these features (Firefox/Chromium/Opera), however they may not be enable due to the users preference.

Using Local Storage as the means to communicate with the Weasel Jukebox allows multiple windows/tabs to add music to the playlist, it also allows navigation around the website more easily (as opposed to some crazy scheme of nested iframes) you can leave the website and revisit it at a later time and you can still add music to the playlist. Neither does it use extra bandwidth as no Cookies get communicated back and forth between the server, or indeed any communication with the server, its all done client side.

Building on from the previous example:

<!DOCTYPE html>
<html>
<head>
<title>Test page to open the Weasel Jukebox.</title>
	<meta charset="UTF-8">
	<script type="text/javascript" src="js/demo1/WeaselJukeboxAPI.js"></script>
</head>
	<hr />
	<a href="demo1.html" onclick="return jukebox.openJukebox( '', '', false )">Open the Jukebox, without a playlist and do not start any music.</a>
	<hr />
	<a href="demo1.html" onclick="return jukebox.openJukebox( '', 'WeaselJukeboxPlayList.json', true )">Open Jukebox with a playlist and start playing music.</a>
	<hr />
	<a href="sound_modules/AndreasStarr/STK.riff" onclick="return jukebox.addToPlayList( 'Andreas Starr', 'Riff', this.href )">Add Riff to playlist</a><br />
	<hr />
	<a href="sound_modules/Eike Steffen (Romeo Knight)/STK.fcg.gz" onclick="return jukebox.addToPlayList( 'Eike Steffen (Romeo Knight)', 'FCG tune from the Coral demo', this.href )">Add FCG from the Corel demo to playlist</a><br />
<body>

Two links have been added, each has a href attribute pointing to the module (this way the link actually points to the module and can be downloaded). When the link is clicked on the onclick event occurs and the tune is added to the playlist (if the playlist is open).

In each case the first parameter is the name of the composer (this appears as the tooltip in the playlist), the second is the display name used and the third is the location of the module, as anchors are being used it just passes the href of the link as the URL. The function jukebox.addToPlayList() returns false (notice the lack of a semicolon at the end of the statement) which is used to stop the event propagating.

Playlist support

The Weasel Jukebox supports playlists:

The playlist also has support for grouping, for example group all Nightlight modules under one heading.

It is worth noting that due to cross domain security that a playlist created from an instance of the Jukebox running on one site will not have access to the modules in another playlist generated from a different site. Even if the full URL is used in the playlist.

A playlist JSON file contains three types of object:

  1. The Weasel Jukebox playlist Container object, which contains versions numbers, date stamp etc and the array containing the playlist itself.
  2. A playlist Entry object, which holds three attributes; Composer, DisplayName, URL.
  3. An optional Group object, used to group a collection of playlist entries together under a heading (such as the composers name).

An empty playlist would look like the following:

{"WeaselJukebox":"Exported Playlist","APIVersionMajor":1,"APIVersionMinor":0,"DateExported":1353428649108,"PlayList":[]}

Minor Version Number revisions are expected to compatible with each other, Major Versions are not. The DateExported field is in milliseconds elapsed since 1970 (typical date stamp field). The PlayList field is an array of Playlist Entry objects and Group Objects, basically the contents of the playlist.

A single Playlist Entry object (by itself):

{"Composer":"Sten Lysholm Larsen","DisplayName":"SLL 1","URL":"sound_modules/SLL/STK.sll1"}

A Group object (by itself, without any Playlist entries):

{"GroupName":"S.L.L. (Sten Lysholm Larsen)","Group":[]}

The Group field is an array of all Playlist Entries to appear under the GroupName heading.

All three combined into a working playlist:

{"WeaselJukebox":"Exported Playlist","APIVersionMajor":1,"APIVersionMinor":0,"DateExported":1353428649108,"PlayList":[
{"GroupName":"S.L.L. (Sten Lysholm Larsen)","Group":[
	{"Composer":"Sten Lysholm Larsen","DisplayName":"SLL 1","URL":"sound_modules/SLL/STK.sll1"}]}]}

You han have multiple Group objects along with entries not associated with any group:

{"WeaselJukebox":"Exported Playlist","APIVersionMajor":1,"APIVersionMinor":0,"DateExported":1353428649108,"PlayList":[
{"GroupName":"S.L.L. (Sten Lysholm Larsen)","Group":[
	{"Composer":"Sten Lysholm Larsen","DisplayName":"SLL 1","URL":"sound_modules/SLL/STK.sll1"}]}
, {"Composer":"Tomas Dahlgren","DisplayName":"Stereo Beat","URL":"sound_modules/UncleTom/STK.stereobeat"} ]}

In the above example, “SLL 1” in displayed under the group “S.L.L. (Sten Lysholm Larsen)”, however “Stereo Beat” is shown by itself.

Nested Groups are not supported, as the html <select> element in current browsers do not nested <optgroup> tags.

JavaScript generated Audio

Has been an issue for years, how to play audio that is generated by JavaScript. Until recently you simply could not and it was necessary to passing on the data to a Java Applet/Flash/etc for it to be played (by which point you might as well have written it in said technology).

The situation is improving, there are three currently semi-supported methods for playing generated audio:

  1. Mozilla Audio API Extension, which is a few very useful function added to the HTML5 Audio elements. Also know as MozAudio.
  2. Web Audio API, more commonly know as AudioContext or WebkitAudio (as currently its only implemented in some Webkit browsers such as Chromium). This is a full featured (complex) API that one would expect from an operating system.
  3. HTML5 Audio hack, by dynamically creating snippets of audio files and presenting them to an HTML5 Audio element encoded as a DataURI it is possible to “stitch” together a audio stream, however it is unreliable and very slow. Gaps between audio snippets maybe noticeable.

Unfortunately none, as yet, have been ratified by the W3C and are still working drafts of their Audio Working Group.

Mozilla Audio API Extension

NOTE: Since Firefox version 21 Mozilla Audio Data API is now deprecated :( a warning message is displayed in the JavaScript console “The Mozilla Audio Data API is deprecated. Please use the Web Audio API instead.”. Although it should be noted that the Audio Data API has not yet been removed and is still enabled by default. It is also possible to enable the Web Audio API instead, which at the time of writing (Firefox 24) is a bit buggy but works, by visiting the about:config screen anda set the Preference “media.webaudio.enabled” to true and restart your browser. To disable set the Preference to false.

Out of the three the Mozilla Audio API Extensions is the easiest to use and provide very low latency audio. It does suffer from the behaviour of different operating systems audio sub system (for example Pulse Audio Server quite happily blocks audio writes once it has 500ms of audio, using a pure ALSA system does not). It is a audio stream that you are responsible for updating regularly, it is not event driven and does not suffer at the hands of the event queue or create additional objects (no garbage collection). It is possible to have more that one stream, by just creating an new Audio object. Audio format is in floating point (of range -1.0 to 1.0) but interestingly gets converted instantly into 16bit audio (Firefox 12, 32-bit float support has been added in Firefox 15 via ALSA) and then given to the o/s audio sub system.

Web Audio API

The Web Audio API is more complex, you do not (in 2012 - it is still a working draft) get to select the output replay frequency, a set value is provided to you. Audio is driven via Event Queue requests, which in practice does not lend itself well to low latency audio, the JavaScript event queue is single threaded and has no priority, and so can be populated with other events that need processing first before the time critical audio ones. Each event audio frame is of the size 2^n ( 256, 512, 1024, 2048 etc) so in theory low values would produce lower latency, but values may not be supported - worse is that no error message is generated just silence (the values or 256, 512 and 1024 do not work for me). You can only have one AudioContext object active in page (so you can't have multiple modules playing at the same time for example). Audio format is in floating point (of range -1.0 to 1.0).

HTML5 Audio hack

The HTML5 DataURI hack is unreliable and process heavy, first you have to convert all audio into a pcm encoded wave file, then the file needs Base64 encoding into a string, which gets passed to the Audio object at regular intervals. However it takes some time to decode each snippet and play it, this causes the browser to pause by 20ms+ or so. This means the audio snippets need to be small, but unfortunately small audio snippets do not stitch back together well and introduce pops and crackles. So currently within the replay library audio snippets are fixed at 2 seconds, which means there is a 2 second lag between the audio created and heard. By default the HTML5 audio is set to 8-bit mono (and a lower sampling rate) in order to help the situation and eliminate pausing, but ultimately its a browser and o/s combination problem.

For example on Windows XP using Firefox 3.6 using 44.1Khz caused less pauses that at a lower rate of 16Khz.

Latency

Latency is determined by four factors, the actual sound chip used, the operating systems audio server, the audio implementation used by the browser (which the replay library then uses) and your chosen interval rate. JavaScript does not have direct access to audio setting for the operating system or sound chip, neither does it have much control over the browsers implementation - only that which is presented in the JavaScript layer.

This means we can only adjust the interval rate and audio pre-buffering value to change the latency. The smaller the pre-buffer the lower the latency and the more regularly it has to be updated. A low interval value of 10ms would almost certainly not work with a pre-buffer of 10ms in size. Why? because the interval rate in JavaScript and its event queue is not that accurate, in practice the pre-buffer size should be 2 or 3 times the interval rate.

By comparison, the Amiga (assuming the current sound channel is not already playing audio) has a latency of around 0.064ms, as audio is played on every scanline (1000ms / (50hz * 312.5 scalines) = 0.064ms).

Surprisingly a low latency of around 32ms mark is possible with MozAudio, although this does require a reasonably fast machine to keep the event queue empty and it is dependent on Operating System and Audio Server . The replay library does not default to a low value, it uses a buffer size 270ms as this works well across different browser and Operating System.

It is best practice to allow the user to adjust the audio buffer size so that they can avoid audio glitches that can occur with lower latency values. Which is why it is recommended to add the Easy Menu to the page, or implement your own.

The lowest latency AudioContext appears to achieve is 85ms (there seems to be a double buffer for audio, so the latency is twice that of the buffer size). At 48Khz the latencies are as follows:

AudioContext (WebKit audio)
Buffer size (bytes) 256* 512* 1024* 2048 4096 8192 16384
Latency (@48Khz) 10ms 21ms 42ms 85ms 170ms 341ms 682ms
*The AudioContext API allows these values but the implementation currently (2012) does not.

As you can see if the audio rate increases the latencies of each value reduces (for example IF AudioContexts chooses 96Khz, a buffer size of 2048 would have a latency of 42ms, at 192Khz it would be 21ms).

The HTML5 Audio hack has awful latency, in the replay library it is currently set to 2 seconds simply because through trial and error this value appeared to be the best to use. By not overloading the browser to much it allows the browser to decode the audio data and play it without causing pauses (larger values cause pauses) and long enough to hide the gap between sample stitches. Ideally it would be best to pre-compute the entire song, unfortunately this would be rather a long pause (to generate the audio) and assume the browser does not crash handling string many megabytes in length (which they usually do).

The latency can be changed by adjusting the size of the audio pre-buffer with function weasel.BrowserAudio.setPreBufferingInMS( fPreBufferingInMS ) the value is in milliseconds and is clamped to the range 0-1000. The HTML5 Audio hack currently ignores this value entirely.

It should also be noted that it is also possible to use a value too large, this is again down to Operating System and Audio Server, for example Pulse Audio combined with MozAudio does not seem to like values larger that 500ms and causes the replay library to behave slightly oddly as Pulse Audio informs MozAudio to block any new audio as it already has enough to currently play.

Firefox 15b3 appears to introduce some changes to their underlining audio system that change the behaviour and latency of MozAudio, latency is considerably higher (a bad thing), the lowest reported audio latency is around the 120ms mark, compared to 50ms for Firefox 14 on the same system. PulseAudio Manager is reporting that a buffer is being used as well as a sink, the latency total difference is less than 1ms, but at the JavaScript front end its an additional 60ms. 15b4 seems to make matters even worse.

PulseAudio Manager reports Demo1 lowest latency value reported without breakup
Firefox 14 29050 μs (= buffer: 0 μs + sink: 29050 μs) 50ms
Firefox 15b3 28774 μs (= buffer: 4479 μs + sink: 24295 μs) 120ms
Firefox 15b4 42604 μs (= buffer: 0 μs + sink: 42604 μs) 280ms

Using a pure ALSA system (without PulseAudio) is also affected, which normally reports an lower latency that PulseAudio but now breaks up at 120ms. Of course these values are dependent upon the mozCurrentSampleOffset() function, which may not be an accurate method for calculating audio latency but it does appear to be fairly good on the same system (I am not comparing values across different operating systems).

Hopefully this wont be present when Firefox 15 goes live, but this behaviour is also present in the Aurora builds of Firefox 16. Which is a shame as it destroys what was a low latency system (alright its not uber low like 2ms but what did you expect for JavaScript).

Update: Firefox 15 has been released, and the lowest audio latency possible without breakup in Demo 1 is ~240ms, compared to ~32ms in Firefox 12. Also Demo 3 runs ~20% slower in Firefox 15 vs Firefox 12 (X-Ray mode off and hog cpu on, gives ~60fps vs ~49fps). I've trawled through the about:config settings to see if there are any major differences, layer hardware “acceleration” is off in both, it may be just down to compilation differences (perhaps its just PGO used on one and not the other). Had to change default audio buffer size to 270ms (from 170ms) to stop break ups :(.

Browser Support

The best audio playback method is automatically selected by the library upon detection, here is a table of what to expect:

Browser Supported Audio API
MozAudio AudioContext(Webkit) HTML5 Audio via DataURI
Firefox (Ice Weasel) 3.5+ No No Yes
Firefox 4.0+ Yes No/Yes** Yes
Chromium 17+ No Yes Yes
Opera 10+ No No Yes
Midori 0.2.2* No No Yes
Epiphany or “Web” Browser 2.30.6* No No Yes
IE 9 No No No
Selected audio api is in bold.
*Midori & Epiphany play audio but in the CSS menu selection boxes are broken using the mouse, using tab and cursor keys does work instead.
**Firefox 20 and below do not support AudioContext, Firefox 21+ currently have experimental support (enabled via about:config set preference “media.webaudio.enabled” to true).

There may be others versions or different browsers that work but these have been tested against. HTML5 support is a must (although in theory AudioContext can exist without HTML5, it is extremely unlikely).

Internet Explorer 9 does not support any means of JavaScript generated audio, oddly its Audio element does not even support wave files. But even if it did IE9 does not support the playing of audio via a DataURI. IE10 is just around the corner, perhaps that will.

Performance is very similar across browsers, although with MozAudio the handler trades some speed for lower latency (if this is not necessary it can be turned off to run faster).

Interpolation

Several different kinds of sample interpolation are available with the replay library, although it should be noted this list is not fixed different kinds may be added or removed in the future (for example cubic spline will probably be removed, although fast for a spline it produces noticeable distortion).

Why is sample interpolation useful? Interpolation is useful in reducing aliasing and/or quantization during re-sampling, producing a clearer output signal. However interpolation is basically a guess at what the sample data might be at a given position, it may not actually be correct!

It should also be noted that some samples rely on alias noise to be present, so turning on interpolation for those samples may make the sample sound incorrect and not how the composer intended. Interpolation may not work to well against instruments sampled at a very low sampling rate, simply because to much data has been lost to recreate what the sample originally sounded like. There is also the matter of different wave forms preferring different interpolation types, for example a sine wave looses it shape with linear interpolation (which starts to introduce signal noise very similar to quantization error) but likes cubic interpolation. However a square waveform does not like cubic interpolation as it, in effect, rounds off the corners which softens the sample. Square waveforms often don't like linear interpolation either, if at a low sampling rate, and only sound correct when interpolation is switched off.

*Graph generated with Scilab.

Above is a typical graph showing the different interpolation types, although it is quite easy to see the difference it is not always easy to hear the difference. Between no interpolation and linear interpolation it is quite obvious, alias noise has been removed. Between linear and Catmull-Rom Spline Interpolation its a little more tricky, its more obvious on bass instruments which seem to have a layer of “dirt” removed from them. You would have a very good ear if you can hear the difference between C-R Spline and the 6 Point Spline, which is only really noticeable on stringed instruments played at a low frequency, if any percussion instruments are playing at the same time it is impossible to tell the difference.

The graph above is also quite unrealistic, as in effect 10 interpolation points are being generated per sample point. So if you have 44.1Khz output the note would be 4.41Khz, which is pretty much the lowest note possible in Ultimate Soundtracker. The majority of note fall into the second octave, around A-2 which is ~14Khz, resulting in only 3 interpolated samples.

We can also see from the graph that Linear interpolation is clamped within the values in the sample data, this is markedly different from the cubic spline, which is capable of going outside those ranges (even outside the minimum and maximum values allowed).

Efficiency of interpolation

Interestingly the bigger the difference between the note played and the output frequency the faster interpolation runs, as only when the sampling points shift is new values required from the sample data, this is when the interpolation weightings are calculated.

The inverse is also true through, the closer you get to the output frequency the slower interpolation gets, once you have exceeded the sampling rate interpolation would start to introduce errors (the replay library stops using interpolation if this occurs). So there is little point in using cubic spline or above with an output frequency of 22khz, or arguably the highest note played in the module (the maximum note played in Ultimate Soundtracker is 31,678hz).

Once the interpolation rate goes below 1 input sample for 2 output samples it is probably worth switching to using linear interpolation, as in effect throwing cpu cycles away needlessly by calculation the weightings for each sample. Currently the different interpolation types do not align up with each other, so this interpolation blending has not been implemented yet.

On a side note, modern x86 and AMD64 processors are very good at running bad code (over the years trillions of dollars have been spent on making processors better and optimizing compilers), so it is very difficult to spot the cpu load difference between different interpolation types. This is not the case with old cpu (for example the PIII) where it makes a big difference between the type of interpolation used (along with the mixer). Likewise different models of ARM processor can have different FPU performance (VPF vs VPFLite vs NEON Floating-Point).

So it is best to give the user control over these settings, as opposed to hard coding them.

Project Goals

The intentions of this project is to create:

Using Sound effects

There is nothing stopping you from hijacking a channel and getting it to play your own sample instead for sound effects use in games/menu navigation etc. Which could be achieved via the weasel.BrowserAudio.getModule() function and weasel.{Module}.getChannel() to access a current sound channel.

However due to the levels of latency involved between you wanting the sound effect and it actually playing it is better to just use a Audio object to play the sound effect. Once preloaded they can be used to play the effect immediately, although there may be an actual limit (depending on browser) to the number of Audio object you can use simultaneously.

Composing a Ultimate Soundtracker Module

As it is now rather difficult to find the original version of Ultimate Soundtracker along with an Amiga containing Kickstart ROM v1.2 you will find a script file called 'convertmodule.py' in the base directory of the project.

This script file can convert a M.K. module (often referred to as a Protracker module) into a :

This allows you to compose modules in a modern day tracker (such as Milky tracker or Open Modplug Tracker).

In order to use the script Python is required, version 2.6.5 or above (it may work with earlier versions).

For best results using the 'convertmodule.py'' script against your module consider the following rules, when converting to a Ultimate Soundtracker module:

When converting to a DOC Soundtracker 9/Master Soundtracker 1.0 module:

When converting to a DOC Soundtracker 2.2 follow the rules for making a DOC Soundtracker 9/Master Soundtracker 1.0 module with the additional:

When converting to a TJC/Def Jam Soundtracker 3:

The converter script is invoked from the command line:

$ python convertmodule.py --help
Simple script to convert M.K. 31 instrument Soundtracker Modules into:
	- Ultimate Soundtracker 15 instrument modules.
	- DOC Soundtracker 9/Master Soundtracker V1.0 module.
	- DOC Soundtracker 2.2 module.
by Warren Willmey 2012.

For best results converting to a Ultimate Soundtracker module:
	- In the tracker make sure you select a M.K. module type, 
		almost certainly listed as Protracker 2.xx (Protracker 3.xx is 
		not compatible, its a different format).
	- Only use the first 15 instruments (16 to 31 will be ignored).
	- Tick Speed should always be 6.
	- BPM Speed change is only picked up once and set globally for the song.
	- Only use Arpeggio & Pitch Bend effect (commands: 
		0 = Arpeggio.
		1 = Pitch bend up.
		2 Pitch bend down.)
	- Pitch bend effect value is limited to 0-F range.
	- Samples length should be 9900 bytes or less (or some players
		may not identify it as a Ultimate Soundtracker module).
	- Looped samples are played slightly differently, the beginning of
		the sample is ignored (only the actual loop is played).
	- Loops must be forward looping only (not ping-pong looping like 
		on the Gravis Ultrasound).
	- Sample fine tuning is removed.
	- Only the original 3 octaves can be used (you may need to refer 
		to the tracker user manual/help file for exact information) 
		in Open Modplug Tracker they are C-4 to B-6.
	- Only use 4 channels.
	- Patterns must have 64 rows.
	- 8 Bit samples.

For best results converting to a DOC Soundtracker 9/Master Soundtracker 1.0 module:
	- In the tracker make sure you select a M.K. module type, 
		almost certainly listed as Protracker 2.xx (Protracker 3.xx is 
		not compatible, its a different format).
	- Only use the first 15 instruments (16 to 31 will be ignored).
	- Tick Speed can be in the range 2 to 15.
	- BPM Speed change is only picked up once and set globally for the song.
	- Supported effect commands:
		0 = Arpeggio.
		1 = Pitch bend up.	(0-FF range)
		2 = Pitch bend down.	(0-FF range)
		C = Volume.		(0-40 range)
		E = Global Filter on/off(0 = on, 1 = off)
		F = Tick Speed.		(2-F range)
	- Samples length should be 32768 bytes or less.
	- Looped samples are played slightly differently, the beginning of
		the sample is ignored (only the actual loop is played).
	- Loops must be forward looping only (not ping-pong looping like 
		on the Gravis Ultrasound).
	- Sample fine tuning is removed.
	- Only the original 3 octaves can be used (you may need to refer 
		to the tracker user manual/help file for exact information) 
		in Open Modplug Tracker they are C-4 to B-6.
	- Only use 4 channels.
	- Patterns must have 64 rows.
	- 8 Bit samples.

For best results converting to a DOC Soundtracker 2.2:
	- Following instructions for a DOC Soundtracker 9 module.
	- Only use a BPM Speed of 125BPM (50Hz PAL).
	- Supports DOC Soundtracker 9 Effect Commands plus the following:
		B = Sequence Position Jump.
		D = Pattern Break.
		F = Tick Speed.		(1-F range)

For best results converting to a Def Jam Soundtracker 3:
	- In the tracker make sure you select a M.K. module type, 
		almost certainly listed as Protracker 2.xx (Protracker 3.xx is 
		not compatible, its a different format).
	- Only use the first 15 instruments (16 to 31 will be ignored).
	- Tick Speed can be in the range 1 to 15.
	- Only use a BPM Speed of 125BPM (50Hz PAL).
	- Supported effect commands:
		0 = Arpeggio.
		1 = Pitch bend up.	(0-FF range)
		2 = Pitch bend down.	(0-FF range)
		A = VolumeSlide		( 0-F range, Axy : x=up speed, y=down speed)
		C = Volume.		(0-40 range)
		F = Tick Speed.		(1-F range)
	- Samples length should be 32768 bytes or less.
	- Looped samples are played slightly differently, the beginning of
		the sample is ignored (only the actual loop is played).
	- Loops must be forward looping only (not ping-pong looping like 
		on the Gravis Ultrasound).
	- Sample fine tuning is removed.
	- Only the original 3 octaves can be used (you may need to refer 
		to the tracker user manual/help file for exact information) 
		in Open Modplug Tracker they are C-4 to B-6.
	- Only use 4 channels.
	- Patterns must have 64 rows.
	- 8 Bit samples.
Usage:
  python convertmodule.py [options] [source M.K. module filename] [output filename]

Options:
	--version		To display version number of this script.
	--help			To display this help message.
	--UltimateSoundtracker	To convert to a Ultimate Soundtracker module.
	--DOCSoundtracker9	To convert to a DOC Soundtracker 9/Master Soundtracker module.
	--DOCSoundtracker2.2	To convert to a DOC Soundtracker 2.2.
	--DefJamSoundtracker3	To convert to a TJC/Def Jam Soundtracker 3.

Example:
	python convertmodule.py --DOCSoundtracker2.2 killvector.mod killvector.stk

So the usage is simply a source file and a output file such as:

$ python convertmodule.py --UltimateSoundtracker sound_modules/mod.pretend.protracker sound_modules/pretend.stk
sound_modules/mod.pretend.protracker file size is: 25704
sound_modules/pretend.stk
 
Loading and converting, please wait...
Setting BPM to default Ultimate Soundtracker 1.21 speed.
Done.
Writing Ultimate Soundtracker file, please wait...
Finished.

If multiple BPM or Tick Speed changes are detected, they will be displayed and you will be asked to enter a value to use. The BPM value is not a straight conversion, Ultimate Soundtracker uses a different formula from a M.K. module which may result in a slightly different speed. A BPM Speed of 125 is considered 50hz VBL refresh rate and will be set explicitly in the resultant Ultimate Soundtracker 1.21 module.

Tick Speeds are unsupported in Ultimate Soundtracker, however they can be emulated by adjusting the BPM speed, the script does this automatically. The down side to this is that arpeggio and pitch bend effects will sound different - the script will try and correct the pitch bend values in these cases.

Build & Testing Environment

For those wishing to play around with the source code, the build environment can be recreated easily with any open source GNU/Linux distribution (such as Debian/Ubuntu) and the following ingredients:

Ingredients

The following components are required for a full recreation of the replay library, the developer library does not require all of the ingredients (and have been marked appropriately).

HTTP server

Any HTTP web server will do, as no server side scripting is involved at all. The Apache httpd web server has been used, which is typically available in your GNU/Linux distribution repository, it may already be installed on your system.

NOTE: The HTTP server is only required for unit testing, not construction of the release or developer libraries.

Make

The build script is a standard makefile, which should also be available in your GNU/Linux distribution repository, if not already installed.

Java

Java is required by proxy as both the JsDoc Toolkit and Closure Compiler have been written in it. Any version of Java can be used, including the open source OpenJDK/IcedTea version 6, which is available in most repositories.

NOTE: This is not required if you just want to build the developer library version.

JsDoc Toolkit

The JsDoc Toolkit is used to generate the source code documentation which uses the JavaDOC style syntax and can be located here version entitled JsDOC Toolkit V2.4.0.

Placement of the directory is critical, the directory '/jsdoc-toolkit/' must be in the same location as the project directory (one level down from the makefile). This is so that the makefile can locate the Java executable 'jsrun.jar'. You can change/override the makefile variable 'jsDocLocation' if it is not to be found in this location.

No error messages should be produced by the JsDOC toolkit during execution.

NOTE: This is not required if you just want to build the developer library version.

Closure Compiler

The Closure Compiler is used to minimise the JavaScript code for the release library. It also has the benefit of checking syntax, variable references and types, it also gives warns about common JavaScript mistakes.

There are some disadvantages, in that it can modify the source code which is undesirable as the unit tests currently cannot be run against the resultant code. The Closure Compiler is a powerful tool and has several different compilation levels producing different result. The release library only uses the 'SIMPLE_OPTIMIZATIONS” setting, which mainly shortens local variable names, instead of the ADVANCED_OPTIMIZATIONS as this is really meant for use when compiling your application with any used libraries to reduce the overall size still further.

Placement of the directory '/closure-compiler/' must be in the same location as the project directory (one level down from the makefile). This is so that the makefile can locate the Java executable 'compiler.jar'. You can change/override the makefile variable 'closureLocation' if it is not to be found in this location.

No error messages should be produced during the compilation stage.

If any bugs occur using the release version of the library it is best to reproduce the error against the developer library (which has not been through the Closure Compiler) to help identify the error.

If you want to compile your application use the “ADVANCED_OPTIMIZATIONS” it is better to compile against the developer build of the library that the release version. WARNING: Please be aware that using the “ADVANCED_OPTIMIZATIONS” method may clash with the project's license.

NOTE: This is not required if you just want to build the developer library version.

PHP

PHP is only used by the Weasel Task Profiler (not the library) to transfer data back and forth to the Database (to store and view results).

NOTE: This is not required if you just want to build the developer library version.

PostgreSQL

PostgreSQL is only used by the Weasel Task Profiler (not the library) to store its results in.

NOTE: This is not required if you just want to build the developer library version.

Build Script

The makefile build script is located in the base directory of the project, all output files are created in 'release/' directory, all JsDOC files are placed in the 'release/jsdoc/' directory.

The developer and release libraries are marked with the current version number (which is taken from the 'FormatUltimateSoundTracker121.js' source file automatically).

The 'makefile' also contains the list of JavaScript source file dependencies, that need to be maintained.

List of Targets

There are several targets that are selectable via make and can be listed at the shell prompt with:

src$ make help
Target Description
all default Make target, builds all releases.
help display a list of all available targets.
banner display a description of what this make file is for.
jsDoc generate the latest JsDoc documentation from the source code.
devLibrary Concatenate the source files into the developer release build.
minifyLibrary Minify the developer release, currently using the JavaScript Closure Compiler.
clean remove the generated make files (the release and JsDoc files).

This makes it possible to build just the developer library without all the additional release documentation being created with: 'make devLibrary'.

Running just make by itself causes make to build all the releases.

In order to remove any previously generated versions of the library or documentation, the clean target is provided. This deletes all the JavaScript and HTML files in the 'release' directory.

A successful build would look as follows:

src$ make clean
Cleaning release directory - all build and release files are to be removed in directory: 'release/'
find "release/" -type f -name "*.js" -delete
find "release/" -type f -name "*.html" -delete


src$ make all

		-= Web Enabled Audio and Sound Enhancement Library. =-
					(Aka the Weasel audio library)
							Version 1.8.0.

	Release directory is set to: 'release/'
	Developer library file name: 'weaselaudiolibrary-1.8.0.js'
	Minified  library file name: 'weaselaudiolibrary-1.8.0.min.js'

Concatenating source code - into a single file for developer release build...
Minimizing - white spaces being removed and local variables being mangled to reduce the size of the source code for release build...
0 error(s), 0 warning(s)
JsDoc - creating documentation from source code for release build...

Unit Testing environment

JsUnit 2.2 is used for all unit tests, although a slight variation of it is used which combines JSCoverage V0.5.1, for code coverage statistics, and can be located here:

Download the latest version (V0.5.1) into a directory and follow their instructions.

This version conveniently contains a modified version of JSUnit 2.2 Alpha 11, with an additional button to open the JSCoverage report. Create a directory called “jsunit2.2” in your web root (assuming ”/var/www”) and copy the directory “jscoverage-0.5.1/doc/example-jsunit/jsunit” into it. Although I had to change the url location when clicking on the coverage button in line 90 of ”/jsunit2.2/jsunit/app/main-data.html” from ”'../../jscoverage.html\'” to ”'../../../jscoverage.html\'”.

JsUnit (without JSCoverage) should now load from your web server, with the url http://localhost/jsunit2.2/jsunit/testRunner.html (replace localhost with the appropriate ip address if web root is not running locally). JSCoverage will not be accessible from this location, although the third button entitled “Coverage report” should now be present. In order for the coverage reports to work JSUnit has to load the JavaScript files to test through the JSCoverage Server, although it is simpler if all the files (including JSUnit) are loaded though the JSCoverage Server this way the url stays the same and only the port number changes.

The actual entry point for running the tests is via the test suite page, from which you can select one (or more) tests to run. Some tests are listed twice as they are run again but use Typed Arrays instead of normal arrays, these tests need Typed Arrays support in your browser. The test suite page is located in the unit tests directory: http://localhost/WeaselAudioLibrary/unit_tests/javascript/_TestSuite.html.

NOTE: Because JSCoverage adds a lots of instrumentation code it is better to debug scripts without using JSCoverage, it is also possible to crash the JSCoverage Server with malformed expressions (missing braces etc).

ADDITIONAL: Relative paths are used, that may be slightly different on different machines, the test suite page is being loaded and run from the JSUnit directory but needs to point to the unit tests directory. These paths may need correcting in file “WeaselAudioLibrary/unit_tests/javascript/_TestSuite.html”.

Starting the JSCoverage Server

This server is expected to run on port 8080, but can be moved. When making requests through the JSCoverage Server (instead of the normal web server) it adds instrumentation code to any JavaScript so that it can keep track of the code executed. Using the ”--no-instrument“ switch allows the JSCoverage server to ignore adding instrumentation code to JsUnit itself.

From a terminal window enter the follow (the path to jscoverage-server may need correct depending on where it has been installed):

~$ ~/jscoverage-0.5.1/jscoverage-server --verbose --ip-address=0.0.0.0 --no-instrument=/jsunit2.2/  --no-instrument=/WeaselAudioLibrary/unit_tests/ --document-root=/var/www
Starting HTTP server on 0.0.0.0:8080
..... etc

Parameters used:

–verbose Display all file names processed by the JSCoverage server.
–ip-address=0.0.0.0 Means any IP address can be used to access the HTTP server, otherwise only localhost can be used and it wont be accessible from out side a VM.
–no-instrument=/jsunit2.2/ Means don't add the Instrument/Code Coverage report code to any file loaded from this directory. In this case we want to ignore JSUnit so that it does not appear in the report, there is no need to add instrument code to the unit tests either.
–document-root=/var/www This is the document root for where to load the HTTP files from.

Other commands can be found here.

To stop the server, just hit Crtl+C in the terminal window or use:

~jscoverage-0.5.1/jscoverage-server --shutdown

Running JSUnit with JSCoverage

Unit tests in JSUnit are run exactly the same way as normal, no tests need modifying, no code is changed. Except now it runs though port 8080, the default port assigned to JSCoverage HTTP Server.

http://localhost:8080/WeaselAudioLibrary/unit_tests/javascript/_TestSuite.html

Now the web pages come though JSCoverage with the Instrumentation Code added.

Once a tests has run click on the “Coverage report” button to see the generated Code Coverage Report.

NOTE: Each test should be run individually for the code coverage report to be of any value, as the code coverage is accumulative. Therefore to check the effect of a test against a file it needs to be run in isolation. On occasion constructor tests have been separated from their main file because the tests override the tested objects prototype, JSUnit is unable to restore the state of the object once the prototype has been changed.

ADDITIONAL: The refresh button should be used to rerun a test file within JSUnit for code coverage reports to be generated correctly as JSCoverage caches the file. If the source file changes then the results of JSCoverage will be wrong (strange NULLs may appear in the report file). This is not necessary if running JSUnit without JSCoverage.

The convertmodule.py Script

The “convertmodule.py” script is written in Python (version 2.6.5). Python, by default, contains its own unit testing framework but not a code coverage tool which needs installing.

To install coverage.py (version 3.5.2) is achieved most simply through easy_install which installs the Python package for you. Although easy_install itself may not be installed:

$ sudo apt-get install python-setuptools

To install “coverage.py” is as follows:

$ sudo easy_install coverage
Searching for coverage
Reading http://pypi.python.org/simple/coverage/
Reading http://nedbatchelder.com/code/modules/coverage.html
Reading http://nedbatchelder.com/code/coverage
Reading http://nedbatchelder.com/code/coverage/3.4b1
Reading http://nedbatchelder.com/code/coverage/3.4b2
Reading http://nedbatchelder.com/code/coverage/3.5.1b1
Reading http://nedbatchelder.com/code/coverage/3.5b1
Reading http://nedbatchelder.com/code/coverage/3.5.2b1
Best match: coverage 3.5.2
etc..

(The python-dev package may be required to avoid error messages.)

To execute the unit tests, from the directory containing the “covertmodule.py” file:

$ python unit_tests/python/test_convertmodule.py

To execute the unit tests with coverage:

$ coverage erase
$ coverage run unit_tests/python/test_convertmodule.py
$ coverage html

Will generate a directory called “htmlcov” containing a “index.html” report.

$ coverage erase ; coverage run unit_tests/python/test_convertmodule.py ; coverage html
test_checkNotePeriods (__main__.TestConvertModule) ... ok
test_computeBPM (__main__.TestConvertModule) ... ok
test_convertToDOC22Module (__main__.TestConvertModule) ... ok
test_convertToDOC9Module (__main__.TestConvertModule) ... ok
test_convertToDefJam3Module (__main__.TestConvertModule) ... ok
test_convertToUltimateModule (__main__.TestConvertModule) ... ok
test_convertToUltimateSoundtrackerBPM (__main__.TestConvertModule) ... ok
test_correctLoopStart (__main__.TestConvertModule) ... ok
test_displayHelp (__main__.TestConvertModule) ... ok
test_getBPM (__main__.TestConvertModule) ... ok
test_getBPMSpeedFromUser (__main__.TestConvertModule) ... ok
test_getMaxPatternNumber (__main__.TestConvertModule) ... ok
test_getNumberFromUser (__main__.TestConvertModule) ... ok
test_getTickSpeedFromUser (__main__.TestConvertModule) ... ok
test_main (__main__.TestConvertModule) ... ok
test_makeUltimateModule (__main__.TestConvertModule) ... ok
test_recalculatePitchBend (__main__.TestConvertModule) ... ok
test_remapDOC22Effects (__main__.TestConvertModule) ... ok
test_remapDOC9Effects (__main__.TestConvertModule) ... ok
test_remapDefJam3Effects (__main__.TestConvertModule) ... ok
test_remapEffects (__main__.TestConvertModule) ... ok
test_removeFineTune (__main__.TestConvertModule) ... ok
test_setDefaultBPM (__main__.TestConvertModule) ... ok
test_sniffModule (__main__.TestConvertModule) ... ok
test_try (__main__.TestConvertModule) ... ok
test_write (__main__.TestConvertModule) ... ok
 
----------------------------------------------------------------------
Ran 26 tests in 0.097s
 
OK

Weasel Library Regression Checker

The Regression Checker is a automated means of detecting changes in a library revision from Soundtracker modules. This was introduced as a “belt-and-braces” precaution in an attempt to catch any regressions that may occur during development. More specifically the regression that occurred in version 1.7.1 (Protracker Quirk - Row Delay (EEx) combined with Pattern Break (Dxx) causes a row to be skipped, so it jumps to row Dxx + 1 instead) also accidentally introduced a bug that occurred when a Pattern Break and a Row Delay occur in the same pattern (not just on the same row). A duff unit test allowed this bug to go undetected and annoyingly a manual sanity check of various module prior to release failed to expose this bug too.

The Regression Checker is also used to verify that the minimized versions of the library work as expected (in that some behaviour has not been optimized out).

The Weasel Library Regression Checker requires:

The Weasel Library Regression Checker is a Bash shell script which generates a .raw sample dump from a list of selected modules, gathers the file size, module type, generates a SHA-1 checksum and compares them to a previous Weasel library version to see if there is any change.

The results are generated into a HTML results web page for viewing where results are colour coded, Green for pass, Red for failure (signifies a difference).

If there are any unexpected changes then a regression will have occurred somewhere.

The Regression Checker is smart enough to cache the results of the file size, checksum and module type so that if the checker is rerun it does not have to regenerate a previously generated file. The Regression Checker also detected if the original module has been modified since the results were cached, if it has then it regenerates the checksums. These cached results can either be removed manually (by deleting individual files from the “project/WeaselLibRegressionChecker/ResultsCache” directory. Or can be flushed when starting the Regression Checker with the “–clearcache” switch.

$ ./WeaselLibRegressionChecker.sh --clearcache

The Regression Checker is run from the root directory of the project, and has a typical output as follows (cut down example, only testing “amegas.mod” against libraries 1.5.0 to 1.7.1):

$ ./WeaselLibRegressionChecker.sh

Welcome to the.. -= Weasel Lib Regression Checker =- 
(Used to check for playback regressions.)
                   by Warren Willmey (c)2013

 
Reading module test list file: ./WeaselLibRegressionChecker/testlist.cfg
Temporary directory used:'/tmp'
Results Directory:'/tmp/WeaselRegressionCheck'
sound_modules/KarstenObarski/amegas.mod
Welcome to -= The Weasel Soundtracker module to Raw Converter! =-
..
Source module file name: sound_modules/KarstenObarski/amegas.mod
Destination output file name: /tmp/WeaselRegressionCheck/amegas.mod-1.6.0-none-44100-16bit-le.raw
JXG.Util.Unzip decompression library FOUND!
Input filesize (bytes actually read): 68864
Sniffing module type..
Module type: Ultimate Soundtracker 1.21
Title (inside module):amegas
Generating stereo mixed audio using 16bit signed integers little endian.
Creating raw sample file, please wait....
Done!                                   
Welcome to -= The Weasel Soundtracker module to Raw Converter! =-
..
Source module file name: sound_modules/KarstenObarski/amegas.mod
Destination output file name: /tmp/WeaselRegressionCheck/amegas.mod-1.6.0.min-none-44100-16bit-le.raw
JXG.Util.Unzip decompression library FOUND!
Input filesize (bytes actually read): 68864
Sniffing module type..
Module type: Ultimate Soundtracker 1.21
Title (inside module):amegas
Generating stereo mixed audio using 16bit signed integers little endian.
Creating raw sample file, please wait....
Done!                                   
Welcome to -= The Weasel Soundtracker module to Raw Converter! =-
..
Source module file name: sound_modules/KarstenObarski/amegas.mod
Destination output file name: /tmp/WeaselRegressionCheck/amegas.mod-1.7.0-none-44100-16bit-le.raw
JXG.Util.Unzip decompression library FOUND!
Input filesize (bytes actually read): 68864
Sniffing module type..
Module type: Ultimate Soundtracker 1.21
Title (inside module):amegas
Sniffer reasons:
{ MKModules: 'One of the "M.K." ID markers [M.K.|M&K!|X:-K|GLUE|FLT4] is missing from the file indicating it is not a 31 sample Soundtracker, Noisetracker or Protracker module.' }
Generating stereo mixed audio using 16bit signed integers little endian.
Creating raw sample file, please wait....
Done!                                   
Welcome to -= The Weasel Soundtracker module to Raw Converter! =-
..
Source module file name: sound_modules/KarstenObarski/amegas.mod
Destination output file name: /tmp/WeaselRegressionCheck/amegas.mod-1.7.0.min-none-44100-16bit-le.raw
JXG.Util.Unzip decompression library FOUND!
Input filesize (bytes actually read): 68864
Sniffing module type..
Module type: Ultimate Soundtracker 1.21
Title (inside module):amegas
Sniffer reasons:
{ MKModules: 'One of the "M.K." ID markers [M.K.|M&K!|X:-K|GLUE|FLT4] is missing from the file indicating it is not a 31 sample Soundtracker, Noisetracker or Protracker module.' }
Generating stereo mixed audio using 16bit signed integers little endian.
Creating raw sample file, please wait....
Done!                                                        


Regression results for module: amegas.mod 
    Lib     	File Size	SHA1 Checksum		Type
1.6.0       	46216800	04e61dc8faf17477ce8bf05d7b23f30e8b4dcee7	Ultimate Soundtracker 1.21
1.6.0.min   	46216800	04e61dc8faf17477ce8bf05d7b23f30e8b4dcee7	Ultimate Soundtracker 1.21
1.7.0       	46216800	7f417327e809431d89cafea62329dad37e1dd8dd	Ultimate Soundtracker 1.21
1.7.0.min   	46216800	7f417327e809431d89cafea62329dad37e1dd8dd	Ultimate Soundtracker 1.21
$

A HTML results page is also generated for easier viewing.

Regression Test List

The list of Soundtracker modules to regression test are stored in a config file located in the directory “project/WeaselLibRegressionChecker/testlist.cfg”. This is a simple text file where each line is the path to a module to test. Comments are also allow when the line begins with a # hash character.

Abbreviated example config file:

# Module list used for regression checking.
# It would be better to create your own comparable list of modules in case
# there is a regression not picked up by this list.
# Modules should be chosen on FX usage and abusage, along with weird behaviour
# of sample loop/quirks etc. Not so easy to find. 
# 
# Ultimate Soundtracker 1.21 modules.
# 
sound_modules/KarstenObarski/amegas.mod
sound_modules/SLL/STK.sll7
#
# Ultimate Soundtracker 1.8 modules
#
sound_modules/KarstenObarski/telephone.mod
#
# Jungle Command Soundtracker 2
#
sound_modules/SLL/SoundInfection/digital touch.mod

It is not recommended using .gz/.zip compressed modules as Weasel Lib versions prior to 1.3.1 don't use it, you will also need the JSX Compression library in the directory “3rdparty/JSXCompressor/” so that the “jsxcompressor.min.js” file can be found.

The list of Weasel Audio Libraries to test each module against are currently found at the top of the bash script “WeaselLibRegressionChecker.sh”, line 24.

You can also change the default interpolation and sampling rates from this bash script.

Investigating Regressions

To investigate a potential regression a digital audio editor can be used (such as Audacity, an open source audio editor available on all major operating systems). Import the generated audio dump of the module and library version prior to the error as a .raw file (in Audacity its “Menu→File→Import→Raw Data” as signed 16bit little endian, starting offset 0, stereo and 44100hz). Then import the generated audio dump of the problematic module and library version.

Generated audio dumps of the test modules are located in your temp directory (or where ever your $TMPDIR environmental variable points to). On most linux systems this points to “/tmp”. A directory will be created named “WeaselRegressionCheck” to store the dumps, this directory gets automatically deleted upon reboot.

File names are in the format:

So for “mod.amegas” using library “1.0.0.min” and the default settings the generated file name would be:

Zoom into the beginning of the audio and make sure the starting samples are aligned correctly (different versions of the library may have different size delays before the music starts). Make sure you are deleting the samples from the stereo pair of just one of the audio dumps.

Once the audio is aligned select the entire stereo pair for one of the dumps and invert it (in Audacity this is found “Menu→Effect→Invert”). Then select ALL the tracks (both dumps) and combine and render the stereo pairs together (Audacity “Menu→Tracks→Mix and Render”). IF both dumps are the same then absolute silence is generated. If they are not the same, then the differences will be show.

You will then need to examine each difference to check why it was created. Typical reasons are as follows:

DMA Wait changes typically introduce a small blip where the beginning of each sample occurs (because the sample is starting at a slightly different time).

By-product Tools

As a side effect the Regression Checker produced a couple of useful command line tools:

Both need Node.js to run.

Weasel Module Sniffer Command Line

Execute the Module Sniffer from the project root with:

$ node ./WeaselLibRegressionChecker/WeaselModuleSniffer.js --help
Weasel Soundtracker Module Sniffer, part of the: 
		-= Web Enabled Audio and Sound Enhancement Library. =-
					(Aka the Weasel audio library)
This scripts returns the type of Soundtracker module.
Usage:
	--help		This usage screen.
	--src=modfile	The input Soundtracker module file name,
			can be .gz|.zip compressed if the JSXCompressor
			library is present.
	--lib=[1.7.2|1.7.2.min|1.7.1]	The Weasel library version
			to use.

To find the module type of a given module:

$ node ./WeaselLibRegressionChecker/WeaselModuleSniffer.js --src="sound_modules/KarstenObarski/telephone.mod" --lib="1.8.0"
Ultimate Soundtracker 1.8
$

The module type is returned or “unknown type” if not.

Note: That a source file and library version are compulsory.

Weasel Soundtracker Module To Raw Converter

Able to directly record a Soundtracker Module to .raw sample file. The output can be set to stereo 16 bit mode or each channel outputted separately as a 32bit floating point.

$  node ./WeaselLibRegressionChecker/WeaselSoundtrackerModuleToRawConverter.js --help
Welcome to -= The Weasel Soundtracker module to Raw Converter! =-
..
Converts a Soundtracker Module into a raw sample file, part of the: 
		-= Web Enabled Audio and Sound Enhancement Library. =-
					(Aka the Weasel audio library)
This scripts primary use is for regression testing.
Usage:
	--help		This usage screen.
	--src=modfile	The input Soundtracker module file name,
			can be .gz|.zip compressed if the JSXCompressor
			library is present.
	--dest=rawfile	The dumped raw sample file, the existing
			file is replaced.
	--lib=[1.7.2|1.7.2.min|1.7.1]	The Weasel library version
			to use.
	--replayhz=integer	The sampling rate to use, defaults
			to 48000 if omitted.
	--perchannel	Record separate channels instead of
			the final mixed audio.
	--interpolation=[none|rlmd|linear|cubic|crcubic|6cubic]
			the interpolation type to use, default crcubic.
	--volume=float	Range 0.0 to 2.0, default of 1.0
			the master volume to use.
	--mixer=[linear|RLMDFAT]	The type of mixer to use for
			stereo output (per channel mode ignores this).
 
Raw output is 16bit Little Endian format for stereo mixed audio.
In --perchannel mode it is 32bit Float Little Endian format PER channel.
$

Note: Source file, destination file and library version are compulsory.

$ node ./WeaselLibRegressionChecker/WeaselSoundtrackerModuleToRawConverter.js --src="sound_modules/KarstenObarski/telephone.mod" --dest="telephone.mod-96khz-32bitfloat-le-crcubic-4channels.raw" --lib="1.8.0" --replayhz=96000 --interpolation="crcubic" --perchannel
Welcome to -= The Weasel Soundtracker module to Raw Converter! =-
..
Source module file name: sound_modules/KarstenObarski/telephone.mod
Destination output file name: telephone.mod-96khz-32bitfloat-le-crcubic-4channels.raw
JXG.Util.Unzip decompression library FOUND!
Input filesize (bytes actually read): 45544
Sniffing module type..
Module type: Ultimate Soundtracker 1.8
Title (inside module):telephone
Sniffer reasons:
{ MKModules: 'One of the "M.K." ID markers [M.K.|M&K!|X:-K|GLUE|FLT4] is missing from the file indicating it is not a 31 sample Soundtracker, Noisetracker or Protracker module.',
  UltimateSoundtracker121: 'Song Speed is not 120 BPM.' }
Generating 4 channels using 32 bit floating point little endian numbers.
Creating raw sample file, please wait....
Done!
$

Progress is displayed on the screen and will change to “Done!” when completed.

This will generate a file “telephone.mod-96khz-32bitfloat-le-crcubic-4channels.raw” in the current directory at 96Khz sampling rate using 32 bit floating point numbers, Catmull & Rom cubic spline interpolation and each channel is stored separately (the data is extracted before mixing in per channel mode, so clipping may occur).

Note: In per channel mode the file sizes are huge, a 7 minute module takes over 650MB!

Weasel Task Profiler

The Weasel Task Profiler is currently in the Prototype stage of development.

The Weasel Task Profiler exists to check for performance regressions in common tasks within the Weasel Audio Library, such as mixing audio channels, interpolation etc.

The Weasel Task Profiler requires:

Ideally the best solution for a Task is one that:

Unfortunately Garbage Collection (GC) activity (at the time of writing) can't be monitored from within JavaScript at run time and neither can memory usage. It is possible to view this information but they require third party tools (such as WebKit's Web Inspector or Firebug) and manual intervention.

The individual profiler tasks can be run manually, stand alone (without the need for PHP & PostgreSQL), the time taken (in milliseconds) to run will be displayed in the JavaScript Console of the browser. It should be noted that you probably wont get very accurate timing as even just wiggling the mouse can interfere with the tests (moving the mouse adds events to the JavaScript event queue which in turn causes garbage which needs collecting etc).

The Weasel Task Profiler is a Bash shell script which opens the browser, runs a single task test and then closes the browser. The script iterates through all the test parameters for each task and the list of browsers to test against. Each task is run three times against each browser, in order to reduce the amount of error due to external factors and helps check for consistent results.

When each task is launched it has 30 time recorded runs, the first 15 runs are run with a 5 second gap between them (in order to allow any garbage collection to occur) and the final 15 are run consecutively in a loop. The browser is also give 10 second pause before starting the tests (to allow the browser to settle down and complete any additional tasks it may be doing).

Tasks

There are currently five tasks being monitored:

  1. Audio Buffer Mixer
  2. Base64 Decoding
  3. Module Sniffer
  4. Sample Interpolation
  5. Sample Loops

Each task has its own set of test parameters to iterate through, the more test parameters the long it takes to complete the testing for a given task (in the the case of Sample Interpolation this can take quite some time).

Time taken to test.

This is a major problem with the Weasel Task Profiler, once the browser has started there is a 10 second delay (on purpose) to allow the browser to settle, then a task test is run (which typically takes less than a second). This is followed by a 5 second delay and then the task test is run again with the same parameters. This is repeated 15 times, so:

The start up and between task pause values have been chosen as they were the lowest values that produce consistent results on a typical PC desktop. You may have to increase these values on slower machine/hardware, the values used should be consistent across testing platforms.

The Audio Buffer Mixer Task has 2 test parameters each with 2 possible values (Filter On/Off, use Float Typed Array Yes/No). So the Audio Buffer Task takes 100s * 4 = 400 seconds (~6 1/2 minutes) to complete. The test script runs it 3 times against 7 different library version (the Developer lib, the minimised lib and the original stand alone code). So thats 400s * 3 * 7 = 8400 seconds which is 2:20 hours!.

Then it is run against a selection of different browsers (typically 4). So your looking at 9 hours to check just one simple task, the Sample Interpolation task has 3 test parameters, with 6, 5 and 2 possible values:

Cache impact.

No attempt is made to clear the CPU's data or instruction cache between timed runs. Which is a bit of a problem if the entire test and data all fit within the cache, as opposed to when the Weasel Audio Library is used with an application that maybe many times larger and thus squeeze the library out of the cache. Attempts were made to defeat the both caches but on modern CPU's caches are not so dumb and proved difficult to get a consistent result indicating the cache had been flushed, combined with creating a lot of garbage for the GC to tidy up. It is possible to sync the execution of the Weasel Task Profiler script with the browser so it should be possible to execute a external program to do this at a later date.

On occasion the results graphs do show caches having an impact, for example the Audio Buffer Mixer in Firefox (19 32-bit mode) when not using Typed Arrays you can see that during the “intense Runs” (when the task is run repeatedly in a loop) the execution time is quicker as most/all the data stays in the cache. Whether this is the instruction cache or data cache playing a role is unknow at the time of writing.

CPU Throttling & Scaling Governor

When running the Weasel Task Profiler it will display the current CPU Throttling value (if available on your CPU), the Scaling Governor and the current CPU Khz value (annoyingly to access this value requires the SUDO/SU access, it should be noted that the rest of the script does not need SUDO/SU).

ACPI CPU Throttling

Most Linux distributions allow access to the ACPI CPU Throttling via the file ”/proc/acpi/processor/CPU0/throttling”. Which, if available on your CPU (typically mobile platforms), allows the the adjustment of T-states (T for Throttling) ideally this should be switched off, state T-0 (a value of 100%), as it may interfere with the test.

$ cat /proc/acpi/processor/CPU0/throttling
 
state count:             8
active state:            T0
state available: T0 to T7
states:
   *T0:                  100%
    T1:                  87%
    T2:                  75%
    T3:                  62%
    T4:                  50%
    T5:                  37%
    T6:                  25%
    T7:                  12%
CPU Frequency Scaling Governor

The CPU Frequency Scaling Governor should also be set to the maximum value, certainly a value of “ondemand” will produce erratic results. Values your system support are displayed with:

$ sudo cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
 
conservative ondemand userspace powersave performance

I did have problems testing with any other value than “performance”, as the CPU speed would mysteriously twitch up to full speed even when it should not have.

Just to add confusion to the issue some CPU's allow different cores to be on different scaling governors, so ideally ALL cores should be set to performance.

CPU Current Frequency

This is the current frequency value of your CPU, which can be read with:

$ sudo cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
 
2201000 2200000 1600000 1200000 800000

Again this ideally should be set to maximum as a lot of chipsets also slow down the speed of the RAM as well.

Memory usage & GC time

Sadly the Weasel Task Profiler cannot tell how much memory is being used during each test, which is an important metric, ideally the best solutions would be the fastest execution and smallest memory foot print that causes the least amount of Garbage Collection (or some derived value of the three although first run performance may also be important for some tasks). As there is the danger that performance is being hidden as Garbage Collection time is not being taken into account for the test time. E.g. A Task that is 5 milliseconds quicker but generates loads of Objects that need collecting by the GC causing the GC to use 10ms of time would actually result in a Task that is 5ms slower but the Weasel Task Profiler doesn't know this, the closest it gets is when visualising the graph during the “intense” part of the run a fluctuation of the test duration *may* indicate GC activity - which is far too Heuristic.

Browsers do not give access to memory information at run time, few give access to this information during debugging. Firebug did have it as an experimental Memory Profiler feature but it seems to have been removed. Webkit's Web Inspector does have a memory profiler but, as far as I am aware, cannot be used from automated testing.

The same also applies to the Garbage Collector, important metrics are not available at runtime (such as how long its been active, how much memory was reclaimed). Firefox has a third party plugin called MemChaser (by Henrik Skupin) that allows GC activity to be logged to a file. WebKit's Web Inspector also has GC activity monitoring.

Memory usage is a major reason to use the Type Arrays over JavaScript generic arrays (which is more of a list-like data structure) as they are a contiguous data structure of a predefined data type. So storing a unsigned byte does not require the use of a IEEE754 floating point number which is 64-bits in size. It should also speed up the GC's job as the Typed Array should be considered a single object as opposed to a collection of objects. The down side of Typed Arrays is that under certain browser & CPU combinations they can be slower (most notably ARM processors and byte arrays during Task Base64 Decode the normal window.Array type is just over 2.5 times quicker than window.Uint8Array, Raspberry PI Rasbian Wheezy 2012-12 with IceWeasel 10).

Which leads onto another problem of deciding when to use Typed Arrays really needs to be done at runtime in the users browser.

Plugins effecting tests

It should be noted that plugins can and do effect the test results, Firebug being an important one to note which when active turns off Firefox's JIT compilation.

Ideally testing should be done in a clean profile.

Browser setup changes

Occasionally a default setting needs changing in order to get the browser to run correctly in a continuous environment.

Firefox

Seemed to need the most changes. Changes are done via the About:config page, keys altered:

As the bash script terminates the browser causing Firefox to think it has crashed, even though it is asked nicely to end with -TERM.

Browser time outs were also a issue on the Raspberry PI

Both needed changing to something considerably larger (for reasons unknown the JavaScript console would also time out which is a chrome component).

Opera

Opera Cross Origin policy gets in the way (needed to indicate to the Weasel Task Profiler script when the browser has finished its current test):

Although I had to add it to the 'opera/profile/operaprefs.ini' file manually.

Starting the Task Tester

Tests are run via the Bash script 'WeaselTaskProfiler.sh' (located in the base directory of the project) from the command prompt.

As the Weasel Task Profiler is in the Prototype stage you will need to edit this file in order to run the desired Task Tests against the desired versions of the Weasel Audio Library and your own selection of browsers.

You need to set the location of your web server containing the Weasel Profiler JavaScript, Task Tests and the required .php files.

The Test Cases are listed at the top of the file (lines 27-31) in the array “aTestCases” and the individual browser and launch strings are listed at the bottom of the file.

The array “aTestLibraries” (line 25) defines the list of Weasel Audio Libraries to test against, notice that each entry is a pair of the library number and a yes/no parameter which indicates whether the Task Test should be performed against the stand alone files (not the developer or minimised library). When using stand alone mode it is important to set the library number to that stored in '/js/FormatUltimateSoundTracker121.js' as this is the version number used to store the results under in the database.

The reason for having a stand alone mode is to check that the developer and minimised version have not had their code modified and reduce performance (many code minimisers modify and reorder the code, in fact that was one of the reasons to rewrite the Base64Decode function as the Closure Compiler was actually making the code bigger!).

Once the 'WeaselTaskProfiler.sh' is started the typical runtime out put is as follows:

:src$ ./WeaselTaskProfiler.sh
 
Welcome to the.. -= Weasel Task Profiler =- 
(Used to check for performance regressions.)
                   by Warren Willmey (c)2012
 
CPU Throttling should be turned off (which is state T0), if available:
state count:             8
active state:            T0
state available: T0 to T7
states:
   *T0:                  100%
    T1:                  87%
    T2:                  75%
    T3:                  62%
    T4:                  50%
    T5:                  37%
    T6:                  25%
    T7:                  12%
 
The CPU Frequency Scaling Governor (power saving) should be set to 'performance' to give a accurate test result:
performance
 
Current CPU speed: 2200000 Khz
midori "http://192.168.1.2/SoundModulePlayer/unit_tests/javascript/WeaselProfilerTasks/Base64Decode.html?lib=1.4.1&standAlone=yes&TypedArray=no&AutoRun=yes&CPUMhz=2200000&tempResult=yes" &
The Browser 'midori ' PID is: 22520.
Waiting for signal from Browser indicating it has finished test.
Trying to close browser.
Browser should be closed.
Waiting for child processes to finish...
Child processes finished.
Sleeping 5 seconds to stop any disk chatter.

The script will end once it has iterated through all the combinations of Task Tests, parameters and browser combinations.

Gathered Test Data

The gathered test data consists of:

This information is stored in the database.

Measurement accuracy and precision

The Weasel Task Profiler uses the High Resolution Timer (window.performance.now()) if available, dropping back to millisecond accuracy (Date.now()) if unavailable.

The high resolution timer is essential on some Task Tests as they complete their task in under a millisecond.

As there is no guarantee of the resolution of the High Resolution Timer, the spec only says sub-millisecond resolution, it may vary from implementation to implementation and on different operating systems/hardware combinations. There is also no guarantee to the accuracy or precision either.

Assuming that the uncertainty of a measurement is 50% of the time taken to to complete two sequential reads of the High Resolution Timer it is possible to to calculate the percentage of uncertainty in the Task's test result, allowing you to spot and ignore 'fishy' results. Ideally it would be nice to increase the running time of the test until the uncertainty percentage is so small that it can be ignored. However this often means that the Task is now running abnormally (for example with large amounts of data when in fact the task may only work with small amounts at a time).

In practice it is quite easy to spot when a Task Test's uncertainty is in question, as typically this is the result of using a browser with only millisecond accuracy and a test that completes in under a millisecond (such as the Sample Loop Task Test and Opera 12.12 which does not have the High Resolution Timer yet). In fact the individual recorded times may be 0ms (zero), as the system clock has not incremented yet. This means its impossible to tell whether any changes made are faster (although slower times can be spotted).

The uncertainty percentage is computed in Weasel Task Profiler Results Viewer.

Weasel Task Profiler Results Viewer

The Results Viewer is a means of viewing the results.

Median not Averages

The median is my preferred measure of the test result set, as opposed to the average as it represent a more realistic value that does not get distorted by one off slow or fast values (such as the first run).

The median is simply the middle value of the result set once sorted (or the average of the middle two values in an data set containing an even number of elements), so a data set in milliseconds of:

The median would be 1ms (sorted data set is [ 10, 2, 1, 1, 1 ] were the middle element contains the median). The average would be 3ms ( (10+2+1+1+1)/5 = 3ms) but it would be wrong to assume that the expected run time duration of the Task is 3ms when over half the data record a value of 1ms.

The median also tells us that the lower half of the data set ([ 10, 2, 1, 1, 1 ]) is this value or lower and that the upper half is this value or higher ([ 10, 2, 1, 1, 1 ]).

Standard deviation

As we have seen the average value by itself is meaningless, its value can be reinforced when provided with a Standard deviation value.

Standard deviation represents how much variance from the average there is in the result set. The total is the square root of the total of each element's value minus the average squared. So:

So we have an average of 3ms with a Standard Deviation of 7.874ms, although realistically you are never going to have a value of 3ms-7.874ms.

I had hoped this would be an indication of how often the Garbage Collection was active, however examining the data its value typically represents the influence of the first run cost.

First Run

The value of the Task's first run turned out to be very interesting and quite important as some Tasks may only actually be run once, such as Base64 Decoding or Module Type Sniffing if there is only one Soundtracker module used. As these Tasks are often run once and never again it makes more sense to optimise these Tasks for single use rather than multiple use.

The first run value also varies massively between browser's JavaScript Engine implementation. For example Webkits V8 compiles directly to native machine code whereas Mozilla IonMonkey first compiles into byte codes before native machine code. Both perform runtime optimization.

It also looks like first run execute speed benefits from calling to the JavaScript API as opposed to rolling your own functions, for example in 1.4.0 the Base64 Decode make a lot of use of String.indexOf() whereas 1.4.1 does not use it (its replace with a JavaScript function). However the first run is only ~15% slower that the median in Chromium 25 for 1.4.0, it can be ~300% slower for 1.4.1 even though 1.4.1's first run is 2.3 times quicker that 1.4.0. This will obviously require some further testing for confirmation, it certainly suggesting when these JavaScript engines are mapping the control flow they stop at JavaScript API functions (this would certainly be the case with native functions). Although this may purely be the result of code complexity (more branches = slower mapping of the control flow).

It may also be possible to speed up the first run by giving the task a very small (dummy) dataset upon first load so that control flow is not mapped out with the actual large (and slow) module data.

Unit Tests

Unit tests are currently lacking for the prototype of Weasel Task Profiler, a modified version of EPIC for PostgreSQL is used for Unit Testing on the database side. It modifies the assertions to allow a assertion comment parameter along with minor changes to get it to work with PostgresSQL 8.4, the patch .diff file is located in the 'WeaselTaskProfiler/createDatabase' directory. Download EPIC with Subversion and apply the patch thus:

WeaselTaskProfiler/createDatabase$ svn co http://svn.epictest.org/trunk/epic epic
A    epic/test
A    epic/test/test_globals.sql
A    epic/test/test_asserts.sql
A    epic/test/test_timing.sql
A    epic/test/test_core.sql
A    epic/test/test_results.sql
A    epic/LICENSE.txt
A    epic/epic.sql
Checked out revision 35.
 
WeaselTaskProfiler/createDatabase$ cd epic
WeaselTaskProfiler/createDatabase/epic$ ls
epic.sql  LICENSE.txt  test
 
WeaselTaskProfiler/createDatabase/epic$ patch -p0 -i ../patch_for_epic\(35\)_to_include_assertion_comment_parameter.diff 
patching file test/test_results.sql
patching file test/test_asserts.sql
patching file epic.sql

Notice that this is for revision 35 of Epic, which as far as I know is the latest.

Installing EPIC is done via psql (PostgreSQL's interactive terminal) when installing the Weasel Task Profiler unit tests with:

weaseltaskprofiler=# \i ./__testData.sql
CREATE FUNCTION
 _epic_init 
------------
 t
(1 row)
 
DROP FUNCTION
CREATE VIEW
CREATE FUNCTION
CREATE FUNCTION
 _ensure_globals 
-----------------
 t
(1 row)
 
DROP FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION
CREATE FUNCTION

You can the run all the tests from the follow SQL command:

weaseltaskprofiler=# SELECT * FROM test.run_all();
           name           |       module        | RESULT | errcode | errmsg |            runtime            
--------------------------+---------------------+--------+---------+--------+-------------------------------
 test_addprofileresult    | addProfileResult    | [OK]   |         |        | 2013-03-29 16:41:06.857334+00
 test_arraymedian         | arrayMedian         | [OK]   |         |        | 2013-03-29 16:41:06.857334+00
 test_deleteprofileresult | deleteProfileResult | [OK]   |         |        | 2013-03-29 16:41:06.857334+00
 test_getprofileresults   | getProfileResults   | [OK]   |         |        | 2013-03-29 16:41:06.857334+00
 test_getprofileruns      | getProfileRuns      | [OK]   |         |        | 2013-03-29 16:41:06.857334+00
(5 ROWS)

And individual tests with:

weaseltaskprofiler=# SELECT * FROM test.run_test('test_arrayMedian');
       name       |   module    | RESULT | errcode | errmsg |            runtime            
------------------+-------------+--------+---------+--------+-------------------------------
 test_arraymedian | arrayMedian | [OK]   |         |        | 2013-03-23 17:14:51.401986+00
(1 ROW)

A nice feature of Epic is that all the tests and unit testing framework are located within its own scheme called 'test'. So your can remove both the Epic framework and the unit tests by simply using the SQL DROP command:

weaseltaskprofiler=# DROP SCHEMA test CASCADE;
 
NOTICE:  DROP cascades TO 51 other objects
DETAIL:  DROP cascades TO TABLE test.results
DROP cascades TO VIEW test.testnames
DROP cascades TO FUNCTION test.statement(text)
DROP cascades TO SEQUENCE test._global_ids
DROP cascades TO FUNCTION test.global(text,text)
DROP cascades TO FUNCTION test.global(text)
DROP cascades TO FUNCTION test.GET(text,INTEGER)
DROP cascades TO FUNCTION test.GET(text)
DROP cascades TO FUNCTION test.constructor(text)
DROP cascades TO FUNCTION test.len(text)
DROP cascades TO FUNCTION test.iter(text)
DROP cascades TO FUNCTION test.attributes(text)
DROP cascades TO FUNCTION test.typename(anyelement)
DROP cascades TO FUNCTION test.run_test(text)
DROP cascades TO FUNCTION test.run_module(text)
DROP cascades TO FUNCTION test.run_all()
DROP cascades TO FUNCTION test.finish(text,text)
DROP cascades TO FUNCTION test.pass(text)
DROP cascades TO FUNCTION test.pass()
DROP cascades TO FUNCTION test.fail(text)
DROP cascades TO FUNCTION test.fail()
DROP cascades TO FUNCTION test.todo(text)
DROP cascades TO FUNCTION test.todo()
DROP cascades TO FUNCTION test.skip(text)
DROP cascades TO FUNCTION test.skip()
DROP cascades TO FUNCTION test.assert_void(text)
DROP cascades TO FUNCTION test.assert(BOOLEAN,text)
DROP cascades TO FUNCTION test.assert_equal(anyelement,anyelement,text)
DROP cascades TO FUNCTION test.assert_not_equal(anyelement,anyelement,text)
DROP cascades TO FUNCTION test.assert_less_than(anyelement,anyelement)
DROP cascades TO FUNCTION test.assert_less_than_or_equal(anyelement,anyelement)
DROP cascades TO FUNCTION test.assert_greater_than(anyelement,anyelement)
DROP cascades TO FUNCTION test.assert_greater_than_or_equal(anyelement,anyelement)
DROP cascades TO FUNCTION test.assert_raises(text,text,text)
DROP cascades TO FUNCTION test.assert_raises(text,text)
DROP cascades TO FUNCTION test.assert_raises(text)
DROP cascades TO FUNCTION test.assert_rows(text,text)
DROP cascades TO FUNCTION test.assert_column(text,anyarray,text)
DROP cascades TO FUNCTION test.assert_column(text,anyarray)
DROP cascades TO FUNCTION test.assert_values(text,text,text)
DROP cascades TO FUNCTION test.assert_empty(text[])
DROP cascades TO FUNCTION test.assert_empty(text)
DROP cascades TO FUNCTION test.assert_not_empty(text[])
DROP cascades TO FUNCTION test.assert_not_empty(text)
DROP cascades TO FUNCTION test.timing(text,INTEGER)
DROP cascades TO FUNCTION test.timing(text)
 
DROP cascades TO FUNCTION test.test_arraymedian()
DROP cascades TO FUNCTION test.test_addprofileresult()
DROP cascades TO FUNCTION test.test_getprofileresults()
DROP cascades TO FUNCTION test.test_deleteprofileresult()
DROP cascades TO FUNCTION test.test_getprofileruns()
 
DROP SCHEMA

Installing the Database

The database required by the Weasel Task Profiler is PostgreSQL 8.4, it may work with earlier version of PostgreSQL but I have not tested against them.

The whole database is installed via the script '__createDatabase.sql' located in the directory 'WeaselTaskProfiler/createDatabase' and should be run from this location in PostgreSQL Terminal with:

postgres=# \i ./__createDatabase.sql
CREATE DATABASE
psql (8.4.16)
You are now connected to database "weaseltaskprofiler".
  current_database  | current_user |                                               version                                               
--------------------+--------------+-----------------------------------------------------------------------------------------------------
 weaseltaskprofiler | postgres     | PostgreSQL 8.4.16 on i486-pc-linux-gnu, compiled by GCC gcc-4.4.real (Debian 4.4.5-8) 4.4.5, 32-bit
(1 row)
 
CREATE LANGUAGE
psql:tables/table.ProfileRun.sql:26: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "profilerun_pkey" for table "profilerun"
CREATE TABLE
CREATE SEQUENCE
ALTER TABLE
psql:tables/table.ProfileResult.sql:14: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "profileresult_pkey" for table "profileresult"
CREATE TABLE
CREATE SEQUENCE
ALTER TABLE
psql:storedFunctions/func.addProfileResult.sql:106: NOTICE:  type reference profilerun.libversion%TYPE converted to character varying
psql:storedFunctions/func.addProfileResult.sql:106: NOTICE:  type reference profilerun.testname%TYPE converted to character varying
psql:storedFunctions/func.addProfileResult.sql:106: NOTICE:  type reference profilerun.browseruseragent%TYPE converted to character varying
psql:storedFunctions/func.addProfileResult.sql:106: NOTICE:  type reference profilerun.suspectedcpumhz%TYPE converted to integer
psql:storedFunctions/func.addProfileResult.sql:106: NOTICE:  type reference profilerun.temporaryresult%TYPE converted to boolean
psql:storedFunctions/func.addProfileResult.sql:106: NOTICE:  type reference profilerun.timerresolution%TYPE converted to double precision
psql:storedFunctions/func.addProfileResult.sql:106: NOTICE:  type reference profilerun.switches%TYPE converted to character varying
psql:storedFunctions/func.addProfileResult.sql:106: NOTICE:  type reference profilerun.id%TYPE converted to integer
CREATE FUNCTION
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.id%TYPE converted to integer
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.libversion%TYPE converted to character varying
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.librarynumber%TYPE converted to integer
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.libtype%TYPE converted to character varying
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.switches%TYPE converted to character varying
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.testname%TYPE converted to character varying
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.daterun%TYPE converted to timestamp without time zone
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.browseruseragent%TYPE converted to character varying
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.suspectedcpumhz%TYPE converted to integer
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.median%TYPE converted to double precision
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.average%TYPE converted to double precision
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.standarddeviation%TYPE converted to double precision
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.minimumvalue%TYPE converted to double precision
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.maximumvalue%TYPE converted to double precision
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.timerresolution%TYPE converted to double precision
psql:storedFunctions/func.getProfileRuns.sql:54: NOTICE:  type reference profilerun.temporaryresult%TYPE converted to boolean
CREATE FUNCTION
psql:storedFunctions/func.getProfileResults.sql:26: NOTICE:  type reference profileresult.id%TYPE converted to integer
psql:storedFunctions/func.getProfileResults.sql:26: NOTICE:  type reference profilerun.id%TYPE converted to integer
psql:storedFunctions/func.getProfileResults.sql:26: NOTICE:  type reference profileresult.timetakenms%TYPE converted to double precision
psql:storedFunctions/func.getProfileResults.sql:26: NOTICE:  type reference profileresult.message%TYPE converted to character varying
CREATE FUNCTION
CREATE FUNCTION
psql:storedFunctions/func.getSimplifiedAverages.sql:50: NOTICE:  type reference profilerun.testname%TYPE converted to character varying
psql:storedFunctions/func.getSimplifiedAverages.sql:50: NOTICE:  type reference profilerun.librarynumber%TYPE converted to integer
psql:storedFunctions/func.getSimplifiedAverages.sql:50: NOTICE:  type reference profilerun.libtype%TYPE converted to character varying
CREATE FUNCTION
CREATE FUNCTION
CREATE ROLE
GRANT
GRANT
GRANT
GRANT
GRANT

This will create a database called 'weaseltaskprofiler' containing all the tables and stored procedures. The script will also create a limited access user 'limitedweaselprofileruser' for connection from the web server DON'T forget to change the password!.

It is possible to install the database manually by following the install script.

To remove the database and the created user is a small case of:

weaseltaskprofiler=# \c postgres
psql (8.4.16)
You are now connected TO DATABASE "postgres".
postgres=# DROP DATABASE weaseltaskprofiler;
DROP DATABASE
postgres=# DROP OWNED BY limitedweaselprofileruser;
DROP OWNED
postgres=# DROP ROLE limitedweaselprofileruser;
DROP ROLE
postgres=#

License

The W.e.a.s.e.l. Audio Library is covered by the GNU General Public License version 3. A copy of the license can be located here https://www.gnu.org/licenses/.

Next Step

Resources

In order to check the replay routine it was often necessary to go back to the original releases containing the music as “just grabbing” a mod from the web was not enough, as many of the early tunes have been tampered with (converted or ripped) resulting in incorrect files. Rips of early Soundtracker modules often have the sample beginnings corrupted as technically the “sound module” does not exist at this point it time, they are songs and the sample data is stores separately (often just after the song with a data table in between the two - hence the corruption). Interestingly even the original releases could be wrong - such as the case of “The return of SLL” music disk (it uses a very early replay routine which plays back at a slower speed compared to the editor).

Ultimate Soundtracker modules are typically listed with the .stk extension (to indicate its a 15 sample Soundtracker file, as opposed to .mod which is typically a M.K. 31 sample soundtracker file).