Instructions

You can think of a workout as a series of intervals, each interval lasting one minute long. Thus, a 25-minute workout would have 25 intervals. You can make
your settings changes (speed, incline, RPM, or resistance) at the start of each interval. Optionally, you can also include .wav files to be played in various
intervals during the workout. Let's get started with a simple, complete script for a bike workout:

This is just a short and easy workout to show some of the basic features. Highlight the above code with your mouse, press
CTRL + C, then click on the script box above and press CTRL+V. This will copy and paste the contents of the script into the
script box. (Alternatively, you can type into the script box, drag and drop files from Windows Explorer directly on to the
script box, or use the Choose File button above to load a file from your hard drive.)
Now click the Generate Zip button. Congratulations, you've just created your first workout. Now, you need to download
the .zip archive to your hard drive. Right-click on the link that appeared to the right of the Generate Zip button and select
the Save As... option presented by your browser in the popup context menu. Depending on your browser it might say save link as,
save target as, save file as, or just save as. When you name the file be sure to give a .zip extension, for example myworkout.zip
would work. Some browsers, notably Edge, will append a .txt extension to the file no matter what you name it. Select open folder
or view in folder and rename the file, if necessary, so that it has the .zip extension. Now you can extract it to a compatible SD
card if you have one and do the workout on your iFit bike.

Breaking down the scripting language

The first line in the script must be one of the following words: bike, treadmill, incline, or elliptical.
You can also use shortened versions: tread and elliptic if you prefer. The meaning of this first line is self-explanatory.
The second line must be workout. The workout keyword tells the compiler this is a new workout. You may have multiple
workouts in the same script. Just add a workout line after the end line in the script to start a new workout. All
workouts in the script must be for the same machine type, e.g. treadmill or elliptical or whatever.

The interval keyword creates the interval. (You can use int as a shortcut for interval.)

interval 0 45 1

The syntax here is: interval [interval number] [RPM] [Resistance]

You must have an interval number to tell the compiler which interval you are creating because it's permissible to skip intervals
and have the compiler insert the missing ones for you automatically. Here is an example of how we might have written the above
script example using the skip interval feature:

Notice how we left out intervals 3, 4, 6, and 7. The compiler creates the missing intervals for us, using the most recent interval
settings. (It's a bit more complicated than that, but it's sufficient to think of it this way for now.)

Using mid-intervals

Each interval is one minute long. Interval 0 starts at 0:00 (minutes:seconds) and goes through to 0:59. Interval 1 goes from 01:00
through to 01:59. Interval 2 goes from 02:00 to 02:59, and so on... If a settings change is being made during an interval it happens
at the start of the interval. If you want to make a change sooner than that, you can use what's called a mid-interval with the midinterval
keyword. It works very similarly to the interval keyword except the first parameter instead of the interval number is an offset in
seconds from the start of the previous interval. Let's say we wanted to go to 60 RPM at the 01:30 mark (between intervals 1 and 2), but
only for 30 seconds. We can modify the above code as follows to accomplish this:

Differences between workouts for bikes/ellipticals and treadmills/inclines

Making a workout for an elliptical is exactly the same as making one for a bike. In fact, the compiled code is identical in every
way but for one line of code telling the iFit system the program is for an elliptical rather than for a bike. Treadmills and incline
trainers are also virtually identical to one another from a programming standpoint. The script is identical except for using tread
or treadmill at the top of the script instead of incline. But there is a slight difference in the syntax for the interval
and midinterval keywords between bikes/ellipticals and treadmills/incline trainers. With a treadmill/incline we are dealing with
speed as measured in miles/hour or kilometers/hour whereas with bikes/ellipticals we are dealing with speed as measured in RPM's. Here
is an example script for a short treadmill workout:

Notice I used 0 for the kph metric speed. When the compiler sees 0 in the kph position it will automatically compute the correct
kph value based on the value specified in the mph position. To convert miles-per-hour to kilometers-per-hour just multiply
the mph by 1.60934 and that will give you the equivalent kph. Conversely, if the compiler sees 0 in the mph position it will
use the value in the kph position to compute the appropriate mph value. The formula there is mph = kph * 0.6214. In practical
terms, programming the treadmill/incline just means adding another 0 in either the kph or mph position, depending on which you
prefer to use.

Treadmills should always have the final interval incline set to 0 for safety purposes with foldable storage designs.

Same keyword

Before we look at .wav file use, there's one more important thing we need to cover: the same keyword. If you wish to
keep the same settings from the previous interval you can use the same keyword instead of simply repeating the value
in the interval. Consider the following code snippet:

interval 5 3 0 2
interval 8 2.5 0 0

We can accomplish the same *exact* thing in the compiled output by replacing the above code with:

interval 5 3 0 2
interval 6 same same same
interval 7 same same same
interval 8 2.5 0 0

There is a subtle difference between doing the above and doing the following:

interval 5 3 0 2
interval 6 3 0 2
interval 7 3 0 2
interval 8 2.5 0 0

The difference is if the user makes a settings change between intervals 5 and 6, let's say by changing from 3 mph to 4 mph,
the treadmill will change the speed back to 3 mph when it reaches the 6:00 minute mark, but if we had used the same
keyword as in the former example when the user changes to 4 mph the new setting will persist until we reach interval #8,
at the next literal speed change. So, if you want to give the user (probably yourself) more flexibility to change the
settings on the fly while doing a workout, you should use the same keyword whenever you want to keep the same settings
from the previous interval. This works for mph, kph, incline, RPM, and Resistance settings. (I believe.) This feature
is implemented by the compiler by using a special magic value (0xfa = 250, base 10) whenever the same keyword is
encountered. When the iFit equipment reads 0xfa for that settings byte value it basically just ignores it and leaves the
setting alone. The end result is whether it was programmed to be at 4 mph in the previous interval or whether the user
changed it manually, it doesn't matter, the equipment will not change the setting.

Working with .wav files

Now, let's turn to using .wav files. If you've ever used one of the iFit professionally produced workouts you will be familiar
with the use of .wav files during the workouts. The compiler here supports the use of .wav files. It will even help you create
files using a text-to-speech engine, but at this time this only works under Windows, Mac OS X, and Linux operating systems. Here is another very short
workout featuring the use of .wav files:

treadmill
workout
play intro001.wav
interval 0 2 0 2
play autoname say this is a 10 minute workout at a very slow pace
interval 10 0 0 0
end

As you can see, the play keyword is used to play .wav files. You can use your own pre-recorded file as in the first
example where we used one called "intro001.wav" or you can have the compiler help you generate one from text using the say
keyword. Notice also the autoname keyword tells the compiler to go ahead and make up a name for the file for us. (You
can substitute the word auto in place of autoname if you don't want to do so much typing.) There
is also another variation on the play keyword which uses an offset parameter to tell the compiler when to play the file.
The default is to just play the file at the start of the interval. If multiple files are listed in the same interval, they are
played one after the other automatically, so you don't need to specify an offset to prevent overlapping. An offset would be
used if you want to play the file right before the next interval, such as to notify the user of an impending settings change or
if you want to space out the .wav files, for example 30 seconds apart instead of right after one another. The following example
shows how to incorporate the offset into the play keyword script lines:

treadmill
workout
play intro001.wav
interval 0 2 0 2
play autoname say this is a 10 minute workout at a very slow pace
play intro002.wav 30
interval 10 0 0 0
end

If you want to incorporate both the offset feature and the say keyword, you would put the offset *before* the say
keyword:

treadmill
workout
play intro001.wav
interval 0 2 0 2
play autoname say this is a 10 minute workout at a very slow paceplay intro002.wav 30 say this will be played at 2 minutes 30 seconds into the workout
interval 10 0 0 0
end

Just remember to use the offset *before* the say keyword or else the compiler will think you intended to say
the offset value instead of use it as an offset.

More details on .wav files

The compiler doesn't create the .wav files for you during compilation. It creates and supplies the necessary scripting files
to create the .wav files and places these files inside the .zip archive. After you download and extract the .zip archive file
you can then create the .wav files by double-clicking on the runme.bat file in Windows or by executing the runme.sh shell script in
Linux.

Creating the .wav files in Windows

Let's talk about the Windows procedure first, and then we'll cover the Linux procedure. The runme.bat script file calls a second file
called makewavs.bat, which uses a third file called ttw.vbs. The latter file (ttw.vbs) is the one that does the actual .wav
file creation. makewavs.bat supplies ttw.vbs with the necessary parameters (namely: the desired filename and the desired text
to convert into the .wav image). If all this seems complicated, just remember all you need to worry about is double-clicking on
runme.bat to make the .wav files. ttw.vbs is a visual basic script file, designed for Windows Scripting Host (WSH), which is
why it won't work under other operating systems, such as Android or IOS. You might be able to get it to work under Linux
using wine. You would need to download and install the WSH from microsoft dot com and get it installed into the wine system32
folder. Read the readme.txt (also included in the .zip archive) for a link containing more details on this subject. But there's
a better way with Linux, which we'll get to in the next section.

Create the .wav files in Android

If you have an android device you might want to download the ttwhelper app to help generate the .wav files. The app
can be used to generate the .wav files you specified with the say keyword or you can use it to record your own files. Ttwhelper can also be used
as a frontend for the ffmpeg file conversion utility in Android. See the
ttwhelper webpage for more details.

Create the .wav files in Linux

With Windows you would need to execute the runme.bat file, but with Linux it's the runme.sh file. With Windows it's a little
bit easier because you can just double-click runme.bat from Windows Explorer file manager, but with Linux you have to first make
the runme.sh file executable by giving it execute permission. You will also need to install a couple packages: espeak and
ffmpeg. espeak will be used to create the .wav files, and ffmpeg will be used to convert them to the necessary format
for use with the iFit system. When you install espeak, it will also bring in another package called portaudio. If it doesn't
bring in portaudio as a dependency you might need to install portaudio separately. In Arch Linux you would do this:

# pacman -S espeak
# pacman -S ffmpeg

Note: the # means you need to be doing this as root or at least with elevated privileges using sudo. With Arch Linux you can
also use su, which will ask for the root password, and then you'll have root privileges. Once you have those packages installed
you can use the runme.sh script, which will be in the same folder inside the .zip as the runme.bat file. Before you can execute
runme.sh you have to give it execution permission. This can be done as such:

# chmod +x runme.sh

And then to execute it:

# ./runme.sh

Be sure to add the "./" before the "runme.sh".

If you want to create the .wav files yourself and use ffmpeg to convert them to the appropriate format, here is the command line
to use with ffmpeg:

ffmpeg -i inputfile.wav -ar 8000 -acodec pcm_u8 outputfile.wav

ffmpeg does a great job of discovering the format of the input file, so you should be able to use any file format as the input file,
such as mp3, wav, wmv, etc. By the way, you can also get ffmpeg for Windows and use the same command line interface as described
above for Linux. If you install WinFF, it will come with ffmpeg, and you can find it in the c:\program files (x86)\winff folder.

Creating the .wav files in Mac OS X

I just recently acquired a Mac to test this on, and can confirm it works. The Mac (OS X 10.11) has a built-in "say" command that can be used to create the text-to-speech files, but it saves them in .aiff
format instead of .wav, so we'll again have to use ffmpeg to do the file conversions. The first thing you have to do is to install
ffmpeg. Here is a step-by-step guide to installing ffmpeg
on the Mac OS X system. I had to modify those instructions just a bit, by creating the /usr/local/bin folder on my machine. If you run into the
same issue, here is how I fixed it: It was easy enough,
just go:
cd /usr/local
mkdir bin

(then you'll need to navigate back to the folder where you downloaded and extracted the workout .zip file.)
Once you have ffmpeg installed as per the above-linked instructions, you're ready to begin making the .wav files. In the root folder
of the .zip archive is a file called runme-mac.sh, which will be used to create the .wav files. From the Mac terminal you will first need
to make this file executable by giving it execute privileges:

$ sudo chmod +x runme-mac.sh

And then to execute it:

$ sudo ./runme-mac.sh

The runme-mac.sh script uses the default system voice, so if you want a different voice you will need to change your system default text-to-speech
voice or you can edit the runme-mac.sh file and add -v voice to the say command lines in runme-mac.sh, where voice is the name
of the preferred voice to use, e.g. alex or kathy. Changing the default voice is the preferred way, just go to Settings, Dictation and Speech,
Text-To-Speech, and you'll find the default system voice setting.

Another option for .wav files is to simply create them yourself using a third-party text-to-speech application or alternatively
using a microphone to record them with a sound recording utility. The iFit system is picky about which format of .wav files it
will play. They MUST be PCM RIFF 8000hz 8-bit MONO 64kbps uncompressed .wav format. Do NOT select any a-law or u-law
compression algorithms when setting up your recording software if you elect to go this route. If you can't find the exact settings
you can use ffmpeg, as described above, to convert them to the correct format. My personal opinion is the Windows text-to-speech voice
is more pleasant than the one espeak uses. In Windows you can change the default voice through the Control Panel if you don't like the
one it uses. In espeak you'd have to go in and modify the runme.sh file (using kate or a similar text editor). Use espeak --help for
more information on changing voices with that software package.

Compatible SD cards

Believe it or not, finding a compatible SD card will be the biggest challenge you will face, even bigger than learning the simple
scripting language used by the compiler. Not just any SD card will work. In fact, most SD cards WILL NOT work. Here is a
list of cards that have been reported to be compatible:

Notice all of these are 2GB and under, so you almost certainly will need SD and NOT SDHC cards, but don't let this fool you
into thinking SD versus SDHC is the only issue to overcome. Also, bear in mind just because my 512MB Kodak card works doesn't
necessarily mean your 512MB Kodak card will work. Trial and error, perseverance and determination will be called for in this endeavor.
If I can find a good supplier of compatible cards I might start selling them here, but until then I'm afraid you are on your own. If
you find a card that works, drop me a line at mwganson at gmail and I will add your card to the list. A good source is eBay for used
cards. If you can find an assortment of various brands and sizes that might be your best bet to hopefully get one of them in the lot
that works with your machine.

The only piece of iFit equipment I have access to is a treadmill. I can verify the compiler output works with treadmills, but I can't
test the other equipment since I don't have any of them. Thus, you might create a workout, try your new SD card with it, and erroneously
blame the card being incompatible when the actual problem might be a bug in the compiler causing your machine to reject the workout. Luckily,
I happen to have a few professionally-produced sample workouts for you to download and use to try out your SD cards. If the sample workouts
function, but the compiler output does not function, then you know the issue is with the compiler (or possibly an error in your script) that
is keeping it from working rather than the incompatible SD card. Here are the workouts:

All of the above samples (sorry, I don't have an incline trainer sample) were professionally produced by personal trainer par excellence, Heather,
and are worth trying out just for the pleasure of using a professionally produced workout. The samples are the first workouts in the weight-loss
series she is (or maybe was) selling for the iFit devices. If you like them you might consider purchasing the rest of the workouts in the series
if you can find them.

Background on hacking the iFit system

The above sample files were indispensible in cracking the iFit mystery format. Without them, it simply would never have been done because
the retail iFit workouts on the SD cards are copy-protected in some manner making them un-readable on a computer (or so I'm told). But,
in an effort to sell the workouts, iFit made the mistake of releasing these sample workouts, which I and a few even cleverer cohorts were
able to use to decipher the format. Despite our success, there yet remains parts of the format that are not yet fully understood. If you
want to help crack the code, so to speak, you can perform some experiments on your equipment to potentially help figure out some of the
rest of the format.

Experimenting with the iFit format

I have provided 2 keywords for experimentation: exp and remove. Using exp you can experiment by modifying the bytes
in a command block that are suitable (meaning, the ones we don't fully understand) for experimentation. Using remove you can also
remove some blocks to see what, if any, effect that might have on the workout.

After extracting the .zip archive file, you will find a file called workouts.txt in the root folder of the extracted archive. This same
information can be found by pressing F12 in most browsers to bring up the developer tools, in the console tab in said tools. You might
need to have the console open prior to clicking Generate Files to see the output. The workouts.txt file describes the binary workout
file the compiler produced, command block by command block. The workout binary is basically just a series of command blocks. Each block
is a series of bytes, ranging from 2 or 3 bytes to 9 bytes in length. The first 2 bytes in the file form together a 16-bit (2 byte) value
giving the number of command blocks in the workout (not counting that first block, which isn't really a command block). The rest of the
blocks are in the general form of: [code byte] [parameter bytes, if any] [checksum byte]. Consider the following line from a workouts.txt
file:

7 0 0 f9;BlockType.UnknownBlock08 (exp1 = 0, exp2 = 0)

In this example, the code byte is 0x07 (all of the bytes are in hexadecimal, base 16 notation). When the iFit device sees that 0x07 as
the first byte in a command block it knows this is the UnknownBlock08 block type, and presumably thus knows how to interpret the 2 0x00
values that follow. I have no idea what the two 0x00's are used for, but by experimenting on them you might be able to figure it out
and let me know (mwganson at gmail). If you do, I'll list it here and give you credit for it if you want. So, the 0x00 and the other 0x00
we know are parameters of some type for some purpose. The 0xf9 is the checksum byte. The way the checksum byte works is it takes what
value it needs to take in order for the sum of the entire block of bytes to be evenly divisible by 0x100 (256 base 10). To wit, 0xf9 +
0x07 = 0x100 = 256 (base 10). The sum of the bytes can be any multiple of 256 (base 10). So, let's try a little mental experiment here,
by changing the first 0x00 to 0x01 and the other one to 0x03. Here's how we would do that:

end
exp unknownblock08 0x01 0x03

Take note how the exp keyword can ONLY be used AFTER the end keyword. You would use exp and/or remove AFTER
the end line and BEFORE the next workout line, if any. If you did the above with a treadmill workout, and then
checked the workouts.txt information, you'd see this for the same line:

7 1 3 f5;BlockType.UnknownBlock08 (exp1 = 1, exp2 = 3)

Notice how the checksum byte changed from 0xf9 to 0xf5 to reflect the new checksum value. The compiler handles the checksum byte
for you, so it's not anything you have to worry about. You can also try to remove the command block to see what effect, if any,
that might have on your workout using remove:

end
remove unknownblock08

Take note the fact that not all equipment types even *have* the unknownblock08 command block type. You need to review your workouts.txt
file in order to see which command blocks are available for experimenting with. Attempting to remove or experiment on a block that doesn't
exist will just result in a compile time error, so no biggie if you make that mistake. Some command lines support using exp in the
script on the same line. Both interval and play support this type of experimentation (but NOT midinterval). Let's
take a look at play and what's actually going on behind the scenes when it is used.

When you enter something like:

play wav0023.wav

the compiler actually creates 2 command blocks to handle that one play line. The two command blocks are:

First, the file is fetched, and then it is played. 0x5a is the code byte for fetching a .wav file. The first parameter (0x04) is
the index of the file (WAV0023.wav), based on the order in which it is listed in the sound index file (default: S0000000.fit). If
you opened the sound index file for this workout in notepad you'd see WAV0023 as the 4th file in the list. The next 3 parameters
have unknown purposes: 0xfa (250 base 10), 0xfa (250 base 10) and 0x00. The final byte (0xae) we already know is the checksum byte.
The 0x5b in the PlayFetchedWaveFile command block is the code byte. 0xff, 0xfb, and 0x01 are the 3 unknown purpose parameters for
this command block type. Because these play lines are broken down into and executed with 2 separate command blocks per
play line we have to provide the experimental values for both of them in one go. This is done as follows:

play wav0023.wav exp 0xfa 0xfa 0x00 0xff 0xfb 0x01

Note I just provided the default values instead of changing them. You would change the one(s) you wanted to experiment with and leave the
other ones the same default values. If you find it more convenient you can use base 10 notation instead of base 16, as follows:

play wav0023.wav exp 250 250 0 255 251 1

or mix and match, as you please. If you use the say keyword on the play line you need to provide the exp parameters
AFTER the say keyword text parameter. Just remember, exp is always LAST. Here is an example:

play autoname say this is a long example exp 250 0

Do you see what I did there? I only specified 2 experimental parameters even though I could have done all 6. What happens in this case is
the defaults are used for all the unspecified experimentals. The only ones that get changed are the first 2, and actually the first
value is the default, so it just gets "changed" back to what it already was. The 2nd 0xfa in the fetch command block gets changed to 0x00 in
this example. You'd get a pair of lines in workouts.txt similar to the following:

If you use an offset parameter with the play keyword you only get one command block in workouts.txt, the PlayWaveFile command block
type. The fetch and play command block pair only take the single file index parameter, but the single playwavefile command block type
needs to both fetch and play all in one go *and* it needs to know *when* to play the file, so it also gets a time parameter. Let's take
a gander at a line from workouts.txt:

0x60 (96 in base 10) is the byte code for this type of command block. There are 4 bytes with unknown purposes: the 2nd byte (1st 0x00)
and the 6th, 7th, and 8th bytes: 0xfa 0xfa 0x00. 0x74 is the checksum byte. Bytes 2 and 3, (0x00 and 0x35 here) are the time stamp
bytes, which together form a 16-bit word. 0x00 is the high order byte, 0x35 is the low order byte. The combined word value is the
number of seconds from the beginning of the workout. 0x35 = 53 (base 10). In this example, this file is being played :53 seconds past
interval #0. 0x03 is the file index byte, which we already covered in the above paragraphs. To experiment with this form of the play
line we can do something like this:

In this example, we'd be replacing the 2nd byte (0x00) with 0x01, the 6th byte with 0x14 (why not 0x20?), the 7th byte with 0x03, and the 8th byte
with 0x04. You'd get a line in the workouts.txt file like this:

The blocks produced by the interval and midinterval lines are the same, but I've only put exp support
into the interval lines. For treadmills and inclines the underlying command block type is AdjustSpeedAndIncline. For bikes and
ellipticals it is called BikeAdjustRPMAndResistance. Both command block types are very similar, but the Bike block has one fewer bytes
in it because of not needing separate mph and kph bytes and instead using a single RPM byte for speed control. I won't go into details of
the implementation of those two command block types because you can study the workouts.txt file and figure that out with the information I've
already supplied you about these other command block types. Each of them has a single experimental byte (byte #2, default=0x00). One thing
I will say about them is they use a very clever way to encode the speeds (mph and kph). For example, if the speed is 2.0 mph, the
mph byte would have a value of 0x14. Note that 0x14 = 20 (base 10). If the speed were 3.2 mph, the byte value would be 0x20. 0x20 = 32 (base 10).
Do you see the pattern? Note that the bike block doesn't do this with RPM or Resistance bytes. A value of 0x20 = 32 (base 10) would mean
32 RPM, for example. One more thing you might find of interest about the AdjustSpeedAndIncline block type is the byte #8 is used. Its
value depends on the number of 0x60 code type command blocks were in the previous interval. Formula for determining the value of this
byte is 0xf7 - (9 * numberOf0x60BlocksInPreviousInterval). For the Bike block you would use 0xf8 in place of 0xf7 and do it on the 7th byte.

I can be reached at mwganson at gmail dot com for any questions/comments/suggestions. Put WorkoutGenSD Online in the subject and attach your
script file, if applicable to your inquiry.