User Manual & Technical Guide

Written by James Pujals (a.k.a. DZ-Jay)

Last Updated: March 6, 2021 Revision #4

Copyright © 2020 – 2021, James Pujals Permission is granted to reproduce and distribute this material freely, without restrictions, as long as the original content remains unchanged. i The Intellivision ! 1

Introduction...... 1

Tracker Version 1.5...... 1

Main Features...... 1

I. Overview! 2

The Instrument ...... 3 Waveform Musical Range Polyphony Envelopes Vibrato Pitch Effects Percussion

The Music Player...... 4 Channels Instruments Drum Kits Note Events Sub-Patterns Patterns Song Sequence

II. Technical Specification! 5

Tracker Engine...... 5 Execution Flow Keeping Time Minimizing Jitter

Tracker Time...... 6 Ticks Rows Tracker Rows vs. Traditional Note Values Song Tempo

Tracker Program Interface...... 8 ii III. Using The Tracker! 10

Pre-Requisites...... 10

Program Use...... 11 1. Set Up Tracker 2. Initialize Tracker 3. Load A Song 4. Update Playback

Advanced Configuration...... 15 Global Tracker Configuration

Playback Volume Control...... 18

Playback Channel Configuration...... 19 Channel Priority Active Channel State Impact Of Drum & Noise Effects

IV. Tracker Data Format! 21

Data Layout...... 21

Global Definitions...... 22 Pitch Effects Envelopes Drums

Song Definitions...... 29 Header Sequence Patterns Instruments Drum Kit Sub-Patterns (Data Record Format) Sub-Patterns (Macro Interface) Automatic Note Packing (NPK Macro Interface)

Putting It All Together...... 43 Global Music Definitions The Simple Song

iii Appendix! 45

A. Library Distribution...... 45 Public Domain Release File Structure

B. IntyBASIC Support...... 46 Overview Program Integration 1. Set Up Tracker 2. Initialize Tracker 3. Load A Song 4. Update Playback

C. IntyBASIC Supplementary API...... 49

iv Acknowledgements

First and foremost, I would like to thank Arnauld Chevallier for his work in creating the original Intellivision Music Tracker library, and for being so gracious in sharing it with the Intellivision home-brew community. Without Arnauld’s efforts, the Intellivision music we hear in home-brewed games would not be as rich and interesting. Although I do not think my contributions to his are trivial, in the end the library is still very much Arnauld’s code.

Arnauld’s original tracker showed us all that the Intellivision is capable of much more than just mere flat, bleepy tones; and that armed with the proper software, a talented musician can create some beautiful and complex compositions with a wide array of rich and interesting sounds – in spite of the many limitations of the console.

Second, I would also like to thank Joe Zbiciak and Óscar Toledo for providing and maintaining the tools that we all depend on for our Intellivision programming efforts. I can state as a matter of fact that I would not have even approached the Intellivision as a programming tool, if it weren’t for the wonderful as1600 assembler and jzIntv emulator and debugger. Even the core of my own development framework is based on, and inspired by, Joe’s SDK-1600 library, and includes many innovations originally suggested or devised by Joe. And, although I personally do not use IntyBASIC, its impact on the Intellivision home-brew community and the democratization of game development it has enabled, cannot be discounted.

Next, of course, are my mates Shazz and Zapac (Anders Carlsson), who invited me to assist on their “Voyage” demo for the @Party demoparty back in 2017. I am forever grateful to both for giving me the opportunity to work with them and produce something of which all of us are very proud. My participation on that project lead directly to this effort, and motivated me to delve deep down into the intricate guts of Arnauld’s tracker software in order to extend it and optimize it for our purposes. Several years later, it is now high time to take that work to its natural conclusion: document the enhanced tracker library exhaustively, and share it with the community.

Finally, I would like to thank the AtariAge Intellivision community in general – collectors, publishers, musicians, home-brewers, lurkers, et al. – for keeping our lovely little console alive. Let us keep at it in our own ways and share our efforts and experiences.

For my part, I hope that my humble contributions are of value to someone, and that they enrich the community in some way. Ultimately, I want to see more and better music and games made for the Intellivision in the future, and that’s what it is all about.

– j.

December, 2020

v The Intellivision Music Tracker The Intellivision Music Tracker

Introduction

Several years ago, Intellivision games designer and programmer Arnauld Chevallier implemented a music tracker library to play specially-crafted music files on the Intellivision. He was gracious enough to donate his code to the public domain so that all Intellivision programmers may benefit from it. Because it is freely available -- and because it is so exceptionally useful and easy to integrate -- it has become the de facto method to play music on Intellivision home-brewed games.

Using the Intellivision Music Tracker in your own games is a matter of including the library, defining some needed variable labels, and adding a call to update the tracker state periodically from your game loop. You also need to create your own music files in the appropriate data format.

This document describes in exhaustive detail the routines available in the library, how to use and integrate them, and the song data format.

Tracker Version 1.5

In 2017, Intellivision games designer and programmer James Pujals adapted Arnauld’s music tracker library as part of an independent collaborative project. The main goals at the time were to enhance the tracker to support the three additional sound channels available in the Entertainment Computer System (ECS) expansion module, and to add additional facilities for playing drums.

Because the support for twice as many channels essentially doubled the work the tracker had to do on every iteration, James re-factored much of the code to improve its execution performance. Along the way, he made some slight improvements and corrected a few long-standing defects.

The result is a more versatile, highly-optimized, and compact tracker library that can play rich and complex musical compositions, while still keeping up with the critical resource demands of an Intellivision game engine.

Main Features

• Pattern-based song sequencing, with support for an unlimited number of patterns.

• Supports up to six independent sound channels, updated and playing simultaneously.

• Full 6-note polyphony; 5-note polyphony, with one drum channel; or 4-note polyphony, with two drum channels.

• 64-point user-defined software envelopes.

• 4-step pitch effects (e.g., ).

• Vibrato effects configurable with 3 levels of depth.

• Simple programmable drum sound synthesizer.

• Supports up to 85 individual instrument definitions with independent envelopes, and pitch and amplitude modulation effects.

• Supports over 40 individual drum instrument definitions per song.

• Global master volume control. page 1 I. Overview I. Overview

The Intellivision Master Component includes a General Instruments AY-3-8914 , commonly referred to as the Programmable Sound Generator, or PSG. The PSG contains three separate square-wave tone generators, each of which can be programmed individually to control its frequency and volume. The chip also includes a noise generator, which can be mixed with any of the three tone channels. All three channels, potentially modulated by the noise generator, are then mixed and fed to the TV for output.

The PSG also contains a built-in envelope generator to shape the output sound-wave. Any of the three channels can be processed through the envelope generator, but only one envelope is available to all channels at a given time. The inability to shape each tone individually reduces the versatility and usefulness of this feature, often forcing the programmer to generate and manage discrete envelopes in software.

Moreover, the Entertainment Computer System (ECS) expansion module adds an additional PSG device to the system, giving it the ability to generate six voices, each programmable independently.

Below is a simple block diagram depicting the signal flow of a PSG.

Programming the PSG directly is certainly possible, but requires a significant volume of insight into the technical details of the device, and the mathematics involved in calculating musical tones and note periods from the raw frequency counter values used internally by the chip. For this reason, it is common to use library modules to abstract these properties.

The Intellivision Music Tracker is one such library. It includes a set of routines to synthesize and play music in a mostly automatic manner, with very little interaction from the main program. It also includes a library of macros that facilitate the creation of song tracks in the appropriate data format used by the tracker.

In general, the Tracker is comprised of two main components: an instrument synthesizer and a music player. These components work together to play song compositions with a rich and varied array of musical sounds.

The next sections offer an overview of these components, their major features, and their capabilities. The technical specification of the routines and the macro library included with the Intellivision Music Tracker will follow.

page 2 I. Overview

The Instrument Synthesizer

The Intellivision Music Tracker implements a rather sophisticated instrument synthesizer. It employs a combination of hardware and software features to synthesize all its sounds, using simple yet powerful techniques, resulting in a rich array of interesting musical sounds.

Waveform The synthesizer relies on the PSG to produce all its tones, and so it is limited by the hardware capabilities. All tones are therefore generated as square waves and modulated via software-generated low-frequency waves and envelopes.

Musical Range The instrument synthesizer supports a range of 84 musical notes in the equal temperament chromatic scale, comprising the seven octaves from C1, to B7. The frequencies of each note are very good approximations calculated mathematically to nearly 1 Hz precision in the middle and lower octaves; and a minimal error no greater than +/-1% on the highest octaves.

Polyphony The synthesizer dedicates each individual PSG voice to a single instrument sound channel, to support either three-note or six-note polyphony, depending on the program configuration.

Envelopes Each synthesized instrument waveform is shaped by a user-defined envelope generated in software. The envelope consists of 64 sample points describing the contour of the waveform’s volume over time. The period of the envelope – that is, the interval between one sample point and the next – can be set at four different speeds.

Envelopes are processed throughout the duration of a note. For very long, sustained notes, the envelope is recycled by repeating the last few samples in a loop. This allows for long notes to play indefinitely without restarting the attack portion of the envelope.

Vibrato The pitch of an instrument can be modulated by an optional low-frequency oscillator (LFO) to simulate a vibrato effect. The depth of the effect can be controlled by setting the amplitude of the modulating wave to one of three levels, which correspond to a maximum shift in pitch of approximately +/-7% (low), +/-15% (medium), or +/-30% (high).

Pitch Effects An additional pitch modulation effect can be applied to an instrument by defining a series of four semi-tone offsets to be applied to a note while playing. The sequence will be cycled throughout the length of the note, resulting in a rapid effect that can evoke the sound of a chord on a single channel.

Percussion Special facilities are available to synthesize drums and other un-pitched instruments, using a combination of tone and noise. The individual frequencies and mix of the tone and noise components, can be fully programmed for each of 16 sample points which describe the contour and character of the waveform over time.

Up to two voices can be dedicated to drum channels, allowing for overlapping performative effects.

page 3 I. Overview The Music Player

Not withstanding the splendid instrument synthesizer, the most important feature of the Intellivision Music Tracker is its versatile music player. Moreover, the synthesizer is integrated directly into the music player engine, allowing the user to switch instruments and apply effects to a channel as the music plays.

Channels Depending on its configuration, the music player can play three or six individual channels simultaneously. Channels, also known as tracks, are programmed independently with a monophonic musical or drum pattern, and their combined output is ultimately mixed by the hardware and fed to the TV.

Instruments To the music player, an instrument is a combination of an envelope, vibrato, and pitch effects. Once defined, an instrument can be applied to a note at any point throughout the song, on any available channel. Each song can define up to 85 different instrument instances.

Drum Kits The music player offers the ability to define drum kits – that is, sets of individual drum instruments for use in a song. Once defined, any drum in the kit can be used at any point throughout the song, in a similar manner to instruments, albeit with some restrictions.

First, for obvious reasons, there is no note associated with a drum instrument. Second, drums can be used in only one or two of the channels, depending on the tracker configuration.

Note Events The music player reads song data as a series of note and drum events that describe how the output of a channel changes over time. The duration of an event is measured in rows, a term going back to the demo scene, and specifies how long the event will play before the next one is triggered.

Sub-Patterns Sub-patterns are a sequence of note events that define a monophonic melody or drum-line. They describe a repeatable set of instrument, volume, and note changes, to be applied to a channel as it plays. The combined number of rows for all events in a sub-pattern must match the length of its encompassing pattern, which is usually 64 or 32 rows.

Patterns In the Intellivision Music Tracker, a pattern is a set of sub-patterns to play simultaneously, each one assigned to an individual channel. They represent a repeatable polyphonic section of a song.

Patterns can be of arbitrary length, but because rows are the tracker’s lowest grain of time, it is typical to use a multiple of 4, such as 32 or 64, to allow events to easily divide into standard musical note values. A song may define up to 255 patterns.

Song Sequence Songs are composed by stringing together a sequence of patterns to play in order. The music player will then play each pattern in the sequence, until it reaches the end. A song can be made to loop forever by rewinding back to any point in the sequence, or stop playing when the sequence is concluded.

A sequence may contain up to 255 patterns, including the end-of-song marker to stop or rewind.

page 4 II. Technical Specification II. Technical Specification

Tracker Engine

Execution Flow A typical Intellivision game program is composed of a series of tasks that run repetitively, processing user input and updating the state of the game. This continuous process is commonly known as the main loop or game engine.

As the game engine runs, the CPU is interrupted periodically by the Vertical Blanking Interrupt Request (VBLANK IRQ), triggered by the display hardware. The interrupt diverts the CPU to call a special-purpose Interrupt Service Routine (ISR), from which the game program can update graphics memory and access the controller; resources otherwise inaccessible at any other time.

The interrupt request is synchronized with the video signal sent to the TV, which runs at 60 Hz (NTSC) or 50 Hz (PAL/SECAM), depending on the regional standard.

Keeping Time Because the ISR runs at a constant rate, it offers a useful mechanism to keep time and maintain the pace of the game. It is therefore common for the game engine to synchronize with the ISR by waiting idly, after each iteration, for the next IRQ to arrive; at which point, the engine kicks off again and the cycle repeats.

See the image on the right for an illustration of this cyclical flow.

Minimizing Jitter As noted in the illustration, the Intellivision Music Tracker runs as just another task of the game engine. However, because the human ear is so tuned to discerning small fluctuations and irregularities in periodic or rhythmic sounds – typically known as jitter – it is recommended to call the tracker as a high-priority task, as close to the IRQ timing source as possible.

In practice, this means that the tracker should be called from the top of the ISR, with as little delay as possible, in order to minimize any fluctuations in its timing.

Note that it is not so important when in the game engine iteration the tracker is called; what really matters is that it is called at as close to the same point on every cycle. Moving the task up in the execution chain of the ISR merely reduces the chance of other tasks adding occasional timing variations.

page 5 II. Technical Specification Tracker Time

An important concept to understand is the speed at which the tracker plays and how it relates to the tempo of a song. The patterns and effects that make up the song data are described in two internal units of time used by the tracker, called ticks and rows. Understanding these concepts allows you to compose songs that take full advantage of the tracker’s versatile mode of playing.

Ticks The term ticks in this context goes back all the way to the Amiga demo scene, and it represents a discrete iteration of the tracker engine’s execution. Because the tracker is typically synchronized with the TV display signal (see the previous section for more information), its speed is normally measured at 60 Hz, or 60 ticks per second.

The tick marks the smallest unit of time recognized by the tracker, and therefore it is the minimal grain of quantization for effects, and for the instrument synthesizer in general. In fact, as we shall see below, all time- related features – including song tempo and note duration – use number of ticks in some fashion to measure time.

Rows At its most basic level, a song sequence in a typical tracker module describes a series of note events occurring over time. This is not at all different from traditional music sheet notation, wherein a melody is described as a series of musical notes in the staff, each having a specific value corresponding to its length or duration.

Similarly, note events in the Intellivision Music Tracker tell the music player which note to play and for how long. The duration of tracker events is measured in units known as rows.

A row represents the basic unit of time of the music player; it measures the relative duration of events, and therefore corresponds loosely to the note values in traditional music notation. More precisely, it corresponds to the smallest note value in your musical score.

Tracker Rows vs. Traditional Note Values To better illustrate the concept of rows, we’ll compare the typical representation of a tracker module against traditional music sheet notation. The example below shows an excerpt of the first couple of measures taken from a well-known children’s song.

Note Value: 1/4 1/8 1/4 1/8 1/8 1/8 1/8 1/4 1/8

Notice that each symbol represents the value of its corresponding note, i.e., its duration, measured in beats. In this case, the time signature of 6/8 indicates that there are six beats to a measure, and that the value of a beat is one Eighth Note.

As shown above, the first note on the staff is a Quarter Note, meaning that it lasts for twice as long as a beat, or two beats. The second one is an Eighth Note, lasting for exactly one beat. The third is another Quarter Note, followed by another Eighth Note, and so on.

page 6 II. Technical Specification Contrast this with the same song rendered in the typical tracker module representation, where note events are arranged vertically, as rows on a table, rather than horizontally as in traditional sheet music. (Incidentally, this is where rows get their name.)

Because the smallest note in the original score is an Eighth Note, it is then convenient to make one row equivalent to this value. That means that every row in the table below represents the duration of one beat in the original score.

00 C-4 1 F The table columns are (from the left): 01 --- - - 02 C-4 1 F • Row number 03 D-4 1 F 04 --- - - • Note 05 D-4 1 F • Instrument 06 E-4 1 F • Volume 07 G-4 1 F 08 E-4 1 F For this example, we are only interested in 09 C-4 1 F 10 --- - - the first two: row number and note. 11 G-3 1 F

Now we can compare the module table with the original score. Let us consider the first measure:

00 C-4 1 F 01 --- - - 02 C-4 1 F 03 D-4 1 F 04 --- - - 1 1 1/4 1/8 /4 /8 05 D-4 1 F

The measure starts with a C Quarter Note, so that’s our first event in the table, at row #00. Because a quarter note is twice as long as a beat, and we said that each row represents exactly one beat, that means that the first note will last for two whole rows. Consequently, we leave a gap in between, so the next note event starts on row #02.

The second note is a C Eighth Note, which lasts one beat. This places the next note event on the very next row, at #03. That event lasts for two rows, corresponding to the D Quarter Note on the staff, placing the next event on row #05.

The last note in the measure is a D Eighth Note. Its corresponding event in the tracker table will therefore last for one row, just in time for the next measure to start on row #06; and on it goes.

Song Tempo In this example, we have made one event represent a single beat of the song. However, it is important to understand that this is not strictly necessary. Note events can be more than just notes: they are the quantization scale of the tracker, and can be used to perform many operations on a channel, such as switch instruments, modify the volume of an active note, play a drum sound, etc.

In fact, it is typical to make the row length a very small fraction of the shortest note duration – values of 1/32th or 1/64th of a beat are very common. This allows for a high degree of control over the sound produced by a channel.

The actual duration of a row is measured in ticks, and is configured globally as part of the song data. The combination of row duration and the number of rows to use for the shortest note, is what defines a song’s actual tempo; in much the same way that the time signature does on a traditional musical score.

page 7 II. Technical Specification Tracker Program Interface

The Intellivision Music Tracker module’s public interface is comprised of three user-callable procedures, which offer methods to initialize the tracker, load and play a song, and update the tracker status for continuous playback.

The purpose and calling interface of these routines are described below:

Procedure TRKINIT

Purpose Initialize Music Tracker

Description Initializes the tracker for use, resetting the song data structure and sound devices. This routine should be called once during program initialization to get the tracker ready for loading songs to play.

It may also be called during playback to stop the tracker and reset it. Note that resetting the tracker in this manner not only halts playback, but clears all data structures and unloads any song from memory.

Inputs None

Call Formats CALL TRKINIT

Procedure TRKSNGINIT

Purpose Load & Play Song

Description Loads a song into memory and gets the tracker ready for playback. From this point, the tracker will play the song, as long as its internal state is updated periodically via the TRKPLAY routine.

Note that at any point during playback you can switch songs by just calling TRKSNGINIT again with a new song to load. The new song will start playing immediately.

There are two entry points to this procedure, to support different calling formats.

Inputs R5Pointer to invocation record followed by return address Song base address (1 DECLE)

R1Song base address.

Call Formats ; Record invocation CALL TRKSNGINIT DECLE MYSONG

; Register invocation MVII #MYSONG, R1 CALL TRKSNGINIT.1

page 8 II. Technical Specification

Procedure TRKPLAY

Purpose Update Tracker Playback State

Description Updates the tracker state to maintain playback. This routine is the core engine of the tracker and should be called periodically from the main game loop to keep playback going.

Note that playback occurs only if a song has been loaded using the TRKSNGINIT routine. If no songs have been loaded, the tracker state will remain idle.

Inputs None

Call Formats CALL TRKPLAY

Macro SET_ACTIVE_CHANNELS(count)

Purpose Set Active Channels

Description Sets the global channel state to a special code representing the number of active channels available to the tracker.

The availability of each channel depends on its priority, according to the list below, with the highest priority channels taking precedence.

1. A (highest priority) 2. B 3. D 4. E 5. F 6. C (lowest priority)

Available values range from 1 to either 3 or 6, depending on the program configuration. An invalid value will result in an error.

Inputs count The number of active channels to use.

Call Formats SET_ACTIVE_CHANNELS(5) ' Five active channels

The rest of the routines in the library are used by the tracker internally to perform its duties and manage its state, and should not be called directly.

You are encouraged to peruse the source code in the included “tracker.asm” file, where you will find a more thorough technical description of all the routines included in the tracker library, as well as plenty of comments documenting the functionality of the assembly code.

page 9 III. Using The Tracker III. Using The Tracker

Note that this new version of the Intellivision Music Tracker has been adapted to support interfacing with Assembly Language and IntyBASIC programs. The set up and configuration below pertains specifically to the Assembly interface. Incorporating the tracker in IntyBASIC programs is described in Appendix B.

Pre-Requisites

The Tracker relies on a set of internal data structures to synthesize instrument and drum sounds and to keep track of music patterns and note placements as it plays.

SDK-1600 Library The Intellivision Music Tracker has been built with the SDK-1600 library in mind, and relies on a few of its modules to work properly. Chances are good that your program already uses these.

cart.mac Used for static allocation of variables in memory.

gimini.asm Used to abstract the memory map with intuitive symbols, in particular the addresses and register structure of the PSG sound devices.

Memory The number of variables and the size in ROM required by the tracker library depend on the channel configuration.

3 channels 5 16-bit words (System RAM) 21 8-bit bytes (Scratch RAM) 544 16-bit decles (ROM)

6 channels 8 16-bit words (System RAM) (default) 36 8-bit bytes (Scratch RAM) 572 16-bit decles (ROM)

ECS The Entertainment Computer System (ECS) expansion module is required in order to enable the 6‑channel configuration and 6-voice polyphony.

Note that if the 6-channel configuration is enabled and the ECS is not available, the tracker will still expect and use data for all six channels. The upper three will still be sequenced and played as normal by the software, they just won’t produce any sound.

page 10 III. Using The Tracker Program Use

Using the Intellivision Music Tracker in your own programs involves four simple steps, each one requiring no more than a few lines of code.

1. Set Up Tracker Include the library in your source code.

2. Initialize Tracker Initialize the tracker during your program initialization.

3. Load A Song Initialize and load a song to play.

4. Update Playback Update the tracker periodically from your main game loop.

1. Set Up Tracker Setting up the tracker is very easy, and involves only including the library modules in your program. Firstly, if you haven’t done so already, you must include the “cart.mac” and “gimini.asm” libraries at the very top of your program.

; Include SDK files before anything else: INCLUDE “sdk/cart.mac” INCLUDE “sdk/gimini.asm”

; ...

For more information on what these modules do, how they work, and what they offer, please refer to the SDK-1600 documentation or review the source codes files themselves.

Secondly, you need to include the tracker files that contain the macro library, the memory allocations, and the actual tracker source file. To facilitate this step, the library includes an interface file that is responsible for including all the necessary modules in their relative order of dependency.

The Assembly Language interface file is called “asm-interface.asm,” and it can be included anywhere in your program, as long as it comes before any song data files.

; ...

; Include the tracker Assembly Language ; interface anywhere in your program, ; along with any other libraries you use. INCLUDE “lib/asm-interface.asm” ; Set up the interface

; ...

page 11 III. Using The Tracker Lastly, you need to include any song data you plan to use in your program. It is not really important where you place the song data, as long as it occurs after the inclusion of the library files. It is typical to include all data structures at the very end of the program.

; ...

; Include songs at the end of your program, ; or wherever other data structures are ; included. INCLUDE “songs/mysong1.asm” INCLUDE “songs/mysong2.asm” INCLUDE “songs/mysong3.asm”

; ...

2. Initialize Tracker Initializing the tracker clears the song data structure, resets the sound devices, and prepares the tracker for playback. All this is accomplished simply by calling a single procedure.

You call the tracker initialization procedure, TRKINIT, at the start of your program or from your own initialization routine, like this:

;; ======;; ;; MY PROGRAM INITIALIZATION ;; ;; ======;; MYPROG PROC

; Your program’s initialization ; goes here ...

CALL TRKINIT ; Initialize the tracker

; The rest of your program ; continues here ...

You only need to call TRKINIT once at the start of your program, after which you are ready to load a song.

Note that calling TRKINIT will completely reset the tracker state to its starting point, discarding any loaded song data and playback progress, and resetting the sound devices. Consequently, calling it while playing a song will halt playback and silence the tracker. This may be useful if you need to stop playback prematurely at any point in the game.

page 12 III. Using The Tracker 3. Load A Song To play a song, you need to load it into the tracker’s memory. This is done by calling the TRKSNGINIT procedure with the base address of the song data. You may call TRKSNGINIT at any point after initializing the tracker.

There are two entry points to the TRKSNGINIT procedure, each supporting a different calling format. The most common one is to pass the song address as part of the invocation record, as illustrated below:

; Somewhere in your program ...

CALL TRKSNGINIT ; Load and initialize “MYSONG1” DECLE MYSONG1 ; passed in invocation record.

; The rest of your program ; continues here ...

As shown in the previous example, the song address is hard-coded in ROM as a data record following the call to the routine.

An alternative calling format accepts the song address in a register, which allows you to load songs dynamically from data tables, etc., as shown below:

; Somewhere in your program ...

MVII #SONG_TBL, R4 ; Get song address from data table MVI@ R4, R1 ; “SONG_TBL” and store in R1.

CALL TRKSNGINIT.1 ; Load and initialize song ; passed in R1. ; The rest of your program ; continues here ...

As illustrated in this last example, the song base address makes its way to register R1 prior to invoking the loading routine using the alternative entry point, TRKSNGINIT.1, with no additional data or arguments.

Once the song is loaded, playback will start immediately, and will continue for as long as the tracker playback state is updated periodically.

page 13 III. Using The Tracker 4. Update Playback In order for a song to play, the tracker engine must be called periodically by the running program. This is done by invoking the TRKPLAY procedure from your program’s main game loop or Interrupt Service Routine (ISR), as illustrated below:

;; ======;; ;; MY PROGRAM ISR ;; ;; ======;; ISR PROC BEGIN

; Any time-critical tasks go here ; to be executed on every interrupt ; request ... MVO R0, $20 ; Enable active display

; ...

CALL TRKPLAY ; Update tracker playback state

; The rest of the ISR continues ; here ...

RETURN ENDP

Procedure TRKPLAY will take care of advancing the tracker state, synthesizing the instrument sounds, and feeding the necessary information to the PSG sound devices to play the song.

Playback will continue indefinitely for as long as song pattern data is available and TRKPLAY is called.

page 14 III. Using The Tracker Advanced Configuration

This enhanced version of the Intellivision Music Tracker library offers a few assembly-time options to let you control its behaviour to some degree. These special configuration options may alter the way in which the tracker processes song data, and so careful attention must be had when modifying them.

For this reason we recommend that you only change the global configuration if you really know what you are doing. For most applications, the default values selected should suffice, and may even be preferable.

Global Tracker Configuration To access the advanced configuration options, open the file “global-config.asm” and look for the section headed with the comment “GLOBAL TRACKER CONFIGURATION” near the top, as shown below. The five configuration symbols will follow, with their default values assigned.

;; ======;; ;; GLOBAL TRACKER CONFIGURATION ;; ;; ------;; ;; WARNING: ;; ;; The tracker library supports a configuration of either 3-channels ;; ;; or 6-channels, set at assembly time via the assembler symbol ;; ;; "TRK_ENABLED_6_CHN" as defined below: ;; ;; ;; ;; TRK_ENABLED_6_CHN = 0 : 3-channels ;; ;; TRK_ENABLED_6_CHN = 1 : 6-channels ;; ;; ;; ;; Consequently, if you opt for 3-channels support, you must make sure ;; ;; that your music data includes only three channels per pattern ;; ;; definition, or else Bad Things will happen. ;; ;; ;; ;; Also, when defining your song's drums and pitch effects, be sure to ;; ;; use the correct number steps specified in the constants below, or ;; ;; else more Bad Things will happen. ;; ;; ;; ;; YOU HAVE BEEN WARNED. ;; ;; ======;; TRK_ENABLED_6_CHN EQU 1 ; Enable six channels TRK_GLOBAL_FADER EQU 0 ; Global volume attenuator TRK_DRUM_STEPS EQU 16 ; Number of steps in drums TRK_ENABLE_LONG_DRUMS EQU 0 ; Enable long drums TRK_PITCH_FX_STEPS EQU 4 ; Number of steps in pitch effects

The same file includes a brief description of the configuration constants and their supported values, which we reproduce in full below.

Note that if you decide to modify any of these advanced options, you should pay close attention to the requirements and caveats stated in their description to ensure that your song data will work correctly with the chosen configuration.

page 15 III. Using The Tracker

TRK_ENABLED_6_CHN Name Enable Six-Channel Configuration

Description Indicates whether the tracker should support 3 or 6 channel songs. Note that the tracker will play 6 channels whether the ECS is available or not, but they won’t be heard unless it is.

You must make sure that your music data includes the correct number of channels per pattern.

Values 1 6 channels enabled 0 6 channels disabled (use only 3)

Default 1 6 channels enabled

TRK_GLOBAL_FADER Name Global Volume Fader

Description Defines a default volume attenuation to apply to songs. The song’s master volume will be reduced by this amount as it plays.

Values 0 to 15 Amount to attenuate master volume

Default 0 No attenuation (normal song volume)

TRK_DRUM_STEPS Name Number Of Drum Steps

Description Indicates the number of steps, or sample points, that define a drum instrument. Each step corresponds to a “tick” of the music tracker.

You must make sure to use the correct number of steps specified when defining drum sounds.

Values 1 to 255 Number of steps to play a drum sound

Default 16 Drums are defined in 16 steps

TRK_ENABLED_LONG_DRUMS Name Enable Long Drums

Description Indicates whether “long drums” are enabled. Long drums play each step, or sample point, for two “ticks” instead of one, doubling the length of the drum sound.

Values 1 Long drums enabled 0 Long drums disabled

Default 0 Long drums disabled (1 step = 1 tick)

page 16 III. Using The Tracker

TRK_PITCH_FX_STEPS Name Number Of Pitch-Effects Steps

Description Similar to “TRK_DRUM_STEPS,” this value indicates the number of steps that define a pitch effect; i.e., the number of semi-tone changes to repeat for each arpeggio. Each step corresponds to a “tick” of the music tracker.

You must make sure to use the correct number of steps specified when defining pitch-effects.

Values 4 to 256 Number of steps in a pitch effect. Must be a power of 2.

Default 4 Arpeggios are defined in 4 steps

TRK_ENV_REWIND_COUNT Name Envelope Rewind Count

Description Indicates the number of steps to backtrack when an instrument envelope is exhausted while a note is sustained. This allows for long notes to play indefinitely. Each step corresponds to an envelope sample point.

Values 0 to 63 Number of samples to rewind envelope.

Default 8 Envelopes repeat their last 8 steps

page 17 III. Using The Tracker Playback Volume Control

The Intellivision Music Tracker offers direct control over the playback volume via the master volume fader. When a song is loaded, the master volume fader is initialized to the default global volume fader specified in the constant TRK_GLOBAL_FADER (see the previous section). The master volume of all channels is then attenuated accordingly.

Additionally, the program can adjust the master volume fader at any point during playback, overriding the global volume fader. This allows for dynamic song volume control during the game, or even to silence the music altogether at a specific point. By simply decreasing the volume periodically over a few seconds, the program can simulate a typical “ out” effect before halting playback.

The master volume fader is accessed via the variable MASTER_FADER, which can be set to any value from 0 (no attenuation, normal playback volume) to 15 (full attenuation, silence).

Below is an example of the master volume fader in use. In this case, the program is lowering the master volume by about 50%:

; Somewhere in your program ...

MVII #8, R0 ; Set the master volume fader MVO R0, MASTER_FADER ; to level 8 (~50%).

; The rest of your program ; continues here ...

Note that setting the master volume fader to 15 (full attenuation) will silence the tracker’s output, but will not stop playback. The instrument synthesizer and music player will remain engaged and performing their duties as normal, although no sound will be heard.

If you intend to simulate a “fade out” effect, you should halt the tracker once the master volume reaches zero. This can be accomplished by calling TRKINIT at the conclusion of the effect.

page 18 III. Using The Tracker Playback Channel Configuration

Most games utilize one or more PSG voices to generate sound effects. In such cases it is useful to reserve a number of voices for program use and exclude them from processing by the Intellivision Music Tracker. You can accomplish this by configuring the active channel state, which instructs the tracker to skip any number of reserved channels during playback.

Channels are reserved for non-tracker use by limiting the number of active channels available to the tracker. The active channel state is accessed via the variable CHN_STATE, which encodes in it the set of channels available for playback. Which of the channels are active for a given code depends on their relative priority.

Channel Priority The tracker treats the full set of channels in both PSG sound devices as a prioritized list of voices, giving the lower voices of each device a higher priority than the rest.

When you limit the number of active channels, say, to reserve one or more for sound effects, the tracker will use the PSG voices with the highest priority first. This has the ultimate effect of leaving the inactive voices untouched for program use.

The following list enumerates the channels in their order of priority, from highest to lowest.

1. Channel A (Highest Priority) 2. Channel B 3. Channel D

4. Channel E 5. Channel F 6. Channel C (Lowest Priority)

As you can see, the lowest priority is assigned to channel “C,” the third voice on the main sound device. This allows programs to enrich their musical playback by taking advantage of additional voices when the ECS is available, while still reserving one channel on the main PSG for its own exclusive use.

Any number of channels can be made active for playback by setting the active channel state to a corresponding code. As the number of active channels increases, the voices with the next highest priority will be made available.

The next sub-section discusses how the active channel state code is computed and how it relates to channel priority.

Active Channel State As mentioned in the preceding sections, the active channel state codes are derived from the number of active channels available to the tracker. The formula used is:

StateCode = ((6 - ActiveChannels) × 3)

The formula terms are defined as:

StateCode The active channel state code for the number of channels active. ActiveChannels The number of active channels available to the tracker.

page 19 III. Using The Tracker The ActiveChannels term can be any number of channels enabled by the current program configuration, from one to either three or six. The inactive channels would then be reserved for program use.

The valid active channel state codes are listed in the table below, along with the PSG voices corresponding to each one.

State Code Active Channels Main PSG ECS PSG

A B C D E F

0 6 √ √ √ √ √ √

3 5 √ √ – √ √ √

6 4 √ √ – √ √ –

9 3 √ √ – √ – –

12 2 √ √ – – – –

15 1 √ – – – – –

For example, if you wish to reserve a single channel, leaving five active channels for tracker use, you set the active channel state to a value of 3, as indicated by the above formula:

StateCode = ((6 - 5) × 3) = 3

With that configuration, channels “A” and “B” – along with the extended ECS channels “D,” “E,” and “F” – will be available for tracker playback, leaving channel “C” untouched for non-tracker program use.

For convenience, the library includes the macro SET_ACTIVE_CHANNELS() that will compute the corresponding active channel state code for a given number of active channels:

SET_ACTIVE_CHANNELS(ActiveChannels)

Where the parameter ActiveChannels represents the number of active channels available to the tracker.

Impact Of Drum & Noise Effects It is not a coincidence that channels “A” and “D” are given higher priority than most of the rest: these are the only channels on which the tracker supports playing drum and percussion instruments, which is a core feature (and a key differentiator) of the Intellivision Music Tracker.

Because drum sounds are typically synthesized using the noise generator of each PSG, they can interfere with its use on any reserved channels. This is a limitation of the PSG device itself, which offers a single noise generator to mix with any of its three voices.

To avoid any potential conflict, if you plan to use the noise generator outside the tracker, say, to create explosion or thunder sound effects, you should avoid playing any drum instruments that use noise in the active channels.

Conversely, you could avoid using the noise generator outside the tracker, and reserve its use for drum instruments.

page 20 IV. Tracker Data Format IV. Tracker Data Format

The song data format used by the Intellivision Music Tracker has a lot in common with the classic MOD format of old Amiga tracker modules: A song defines a series of patterns, which describe musical sections, and specifies the sequence in which they are to be played.

More precisely, the data layout is closer to the later XM format introduced by Triton’s Fast Tracker II for MS-DOS in 1994, which added the notion of instruments. The most notable difference is, of course, that the Intellivision Music Tracker does not use digital instrument samples; instead, all instruments are tones synthesized in software.

Data Layout

Below is the general structure of the tracker data layout. Each part will be described in detail in the following sections.

The first thing you’ll notice is that some parts seem to exist outside the Song section. This is by design: pitch effects, envelopes, and drums are defined globally, and therefore available to any song. This allows, for instance, the creation of a common set of drum sounds to be used in all songs, maintaining a coherent sound or style among them.

The other thing you’ll notice is that the Drums and Drum Kit definitions are separate from Instruments. As will become clear in the coming sections, instruments are defined as a set of modulation effects applied to a musical tone; whereas drums are treated more like sound effects, with fine-grained control over their use of tone and noise throughout their duration. page 21 IV. Tracker Data Format Global Definitions

The following sections are defined globally and made available to all songs in a program. In turn, each song references them as needed.

NOTE: Unless expressly stated otherwise, the descriptions below pertain to the standard tracker configuration. Nonetheless, it should be rather straightforward to understand how they apply to alternate configurations as well.

Pitch Effects Pitch effects are applied one step per tracker tick. They are defined in the following format:

DECLE S[0], S[1], S[2], S[3]

Where S[0], S[1], S[2], and S[3] are 16-bit values representing the number of semi-tones to add to the current note being played, at each step. The synthesizer keeps a counter of ticks as each note plays and computes the pitch of the note using the the following formula:

FinalNote = CurrentNote + S[Count % 4]

The formula terms are defined as:

FinalNote The adjusted note after applying the semi-tone offset. CurrentNote The currently active note playing in the channel, as defined in the pattern. S[n] The semi-tone offset on the nth step in the arpeggio effect series. Count The current value of the tick counter for the channel.

As the formula above shows, after every fourth tick the tracker recycles the effect back to S[0], repeating the sequence throughout the duration of the note.

The result is that the tone is modulated by shifting the number of semi-tones specified for each tick, producing a rapid arpeggio or tremolo effect. For instance, the following example will alternate between two octaves (12 semi- tones) of the same note:

DECLE 0, 12, 0, 12

Another examples plays a broken chord by raising the base tone by three semi-tones, then seven, and finally up a whole octave, before repeating:

DECLE 0, 3, 7, 12

The values can also be negative, in which case the number of semi-tones will be subtracted from the current note. For example, the definition below will alternate between raising and lowering the note by a whole octave:

DECLE 0, 12, 0, -12

page 22 IV. Tracker Data Format Envelopes Envelopes are defined as 64 4-bit values, each one indicating the volume that a tone will have at a given time. The values are packed four at a time in 16-bit words for a total of 16 words. The format is as follows:

DECLE SPEED DECLE WORD[0], WORD[1], WORD[2], WORD[3], ..., WORD[15]

In the definition above, SPEED is a value from zero (slowest) to four (fastest) and specifies the period of the envelope – that is, the interval between the volume changes the synthesizer will apply to the playing tone. Based on this period, the synthesizer advances through the envelope sampling points by computing an index using the following formula:

Index = Count ÷ 2(3 - Speed)

Where the terms are:

Index The index of the sampling point within the envelope matrix. Speed The speed assigned to the envelope definition. Count The current value of the tick counter for the channel.

From the above formula we can deduce that the minimum interval – i.e., the fastest envelope speed – is one tick (1/30th of a second); and that the maximum interval – i.e., the slowest speed – is 16 ticks (slightly over a 1/4 of a second). The actual derived interval durations for all available envelope speeds are listed on the table below.

Speed Interval Envelope Duration (Approx.)

0 16 ticks 17.06 seconds

1 8 ticks 8.53 seconds

2 4 ticks 4.26 seconds

3 2 ticks 2.13 seconds

4 1 ticks 1.07 seconds

The synthesizer will use the internal channel tick counter to compute the interval and advance through the envelope sampling points as indicated.

You can think of an envelope as a 64 x 16 matrix: a two-dimensional graph that traces the contour of the volume of a note as it plays. The y and x axes plot the changes in volume over time, respectively, as illustrated below:

Highest volume

V o l u m e Lowest volume

Note Duration (time)

page 23 IV. Tracker Data Format Each point on the graph represents the volume of the note at a given sampling point. The envelope data is then all 64 volume samples packed into 16 data words, where each nybble is a sampling value on the graph, in big‑endian sequence – that is, the most-significant nybble contains the first value, and so on.

Let us consider the following short envelope. It simulates a percussive instrument with a sharp attack and a short decay. The red line in the graph shows the desired contour of the volume, while the green discs plot the actual sampling points.

Although the graph shows 17 sampling points, only the first 15 are significant: The last two, as well as the rest of the points in the envelope, are zero and indicate that the instrument is at rest and the note has concluded.

The table below shows the values for each sample point in both decimal and hexadecimal notation. To the right of it is the assembly code containing the final packed data. Notice how the first data word contains the initial four samples, packed from left-to-right. This continues for all sampling points, padding with zeros at the end to complete the 64 points.

7 $7 ; X Position 15 $F ; ------14 $E DECLE $7FED ; 0 .. 3 DECLE $CCCC ; 4 .. 7 13 $D DECLE $BBA8 ; 8 .. 11 12 $C DECLE $4210 ; 12 .. 15 12 $C DECLE $0000 ; 16 .. 19 12 $C DECLE $0000 ; 20 .. 23 DECLE $0000 ; 24 .. 27 12 $C DECLE $0000 ; 28 .. 31 11 $B DECLE $0000 ; 32 .. 35 11 $B DECLE $0000 ; 36 .. 39 10 $A DECLE $0000 ; 40 .. 43 DECLE $0000 ; 44 .. 47 8 $8 DECLE $0000 ; 48 .. 51 4 $4 DECLE $0000 ; 52 .. 55 2 $2 DECLE $0000 ; 56 .. 59 1 $1 DECLE $0000 ; 60 .. 63

The next page shows a couple of examples illustrating additional envelopes for common instruments.

page 24 IV. Tracker Data Format Longenvelope instrument soft withfor medium andattack long, wavering decay. Shortenvelope percussivefor instrument with medium decay.

page 25 IV. Tracker Data Format Cycling Envelopes

When the end of an envelope is reached while a note is still playing, the envelope is recycled by backtracking eight sample steps from the end, and looping over them. Thus, notes can continue playing for very long durations, repeating the amplitude pattern at the end of the contour. This feature is of particular value for emulating real-life instruments with typically long, sustained notes, such as certain wind and string instruments.

Consider for example the graph below, which illustrates a simulation of a woodwind instrument, such as a flute, or an oboe. As shown, the envelope has a slow attack phase, then decays gradually, until it reaches a moderate sustained level.

However, instead of dropping off completely, the contour emulates the subtle tremolo effect characteristically of long notes in such instruments, by wavering its amplitude rapidly at the tail end.

64 - 8 = 56

8 steps

If the note is sustained for very long, enough to overrun the length of the envelope, the instrument synthesizer will automatically backtrack and loop through the last eight samples, repeating them for as long as the note endures. By accounting for this feature in the envelope design, the transition can be made to appear seamless.

The end result is a rather close approximation of the pleasant characteristic sound of the original natural instrument.

page 26 IV. Tracker Data Format Drums A drum instrument definition is a series of 16 sampling records describing the sound of the drum over time. At each sampling point you define the mixture of tone and noise, their respective frequencies, and the volume of the sample. The synthesizer advances through the drum samples on every tick of the tracker, completing the entire instrument sound in 16 ticks; just slightly above a quarter of a second.

The data format for each sampling record is:

DECLE TONE, NOISE, MIX, VOLUME

Where TONE and NOISE are the raw period values desired for the tone and noise generators of a PSG voice, respectively; VOLUME is a value from 0 to 15 indicating the volume of the sampling point; and MIX is a 6-bit bitfield specifying the value for the tone/noise enabled flag register of the PSG voice, to indicate whether the sampling point includes tone, noise, or both.

Note that the period values for TONE and NOISE are the raw counter values used by the PSG to produce sound, and not actual sound frequencies. You can calculate the PSG period for a given frequency using the following formula:

Period = CpuClock ÷ (32 × Frequency)

Where the terms are:

Period The PSG tone period computed for the frequency. CpuClock The clock rate of the Intellivision CPU (3.579545 MHz for NTSC). Frequency The tone frequency for which to compute the PSG period value, in Hertz.

The MIX bitfield is defined as follows:

Noise Tone

Channel: C B A C B A

1 1 T 0 0 N

Bit Number: 5 4 3 2 1 0

Be aware that although the field comprises 6-bits, you should only modify bits #0 and #3, highlighted above. The rest should be left as shown in order for the instrument synthesizer to work correctly.

Another important thing to consider is that the raw flags are set to the complement of their state: That is, a 0 enables the oscillator, while a 1 disables it.

Below is an example of the first sampling record for a bass drum instrument. We are going to use a low tone, say, 175 Hz, and modulate it over time. On this first sample, we will add a noise to the mix, in order to give the drum some initial character. We will also start at full volume to simulate the sharp attack of the instrument.

DECLE $380, $10, $30, $F ; $30 = 110 000 (tone & noise enabled in “A”)

All values were computed using the formulas and formats described above.

page 27 IV. Tracker Data Format To simplify the definition of drums, the library includes the macro DRUM() to compose drum sample records:

DRUM(TonePeriod, NoisePeriod, EnableTone, EnableNoise, Volume)

Where each parameter is defined as follows:

TonePeriod The PSG tone period. NoisePeriod The PSG noise period. EnableTone Whether to enable (1) or disable (0) tone. EnableNoise Whether to enable (1) or disable (0) noise. Volume The volume of the sample, a value from 0 to 15.

Note that the macro reverses the enable tone and noise flags in order to make the interface more intuitive.

What follows is an example to illustrate the use of the macro, and the definition of drum instruments in general. For aid in understanding the example, below is an overview of the sound we will attempt to synthesize.

Overview of a natural drum sound:

• A bass drum typically starts with a very sharp attack, lasts briefly, decaying rather quickly.

• It has a low frequency response, typically below 200 Hz, which tends to detune as the sound decays.

• The entire drum tends to resonate at the initial strike, distorting the frequency response briefly.

Overview of the synthesized drum sound:

• To simulate the envelope, our drum will start at full volume for a few steps, then decay quickly.

• We will start with an initial frequency of approximately 175 Hz, then lower it to about 80 Hz, to simulate the natural detuning of the instrument sound.

• To simulate the initial distortion of the drum, we will replace the tone with a low frequency noise on the very first sample, then play the tone alone afterwards.

@@bassdrum: DRUM($000, $10, 0, 1, $F) ; Step #1: Start with noise DRUM($380, $00, 1, 0, $F) ; Step #2: Settle on tone DRUM($380, $00, 1, 0, $F) ; DRUM($480, $00, 1, 0, $E) ; Step #4: Start decaying ... DRUM($480, $00, 1, 0, $D) ; and detuning DRUM($580, $00, 1, 0, $C) ; DRUM($580, $00, 1, 0, $A) ; DRUM($580, $00, 1, 0, $8) ; DRUM($000, $00, 0, 0, $0) ; Step #9: End instrument sound DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; Step #16: End definition

page 28 IV. Tracker Data Format Song Definitions

The following sections are defined for each individual song. The global sections are referenced by using symbolic pointers to their definitions.

NOTE: Unless expressly stated otherwise, the descriptions below pertain to the standard tracker configuration. Nonetheless, it should be rather straightforward to understand how they apply to alternate configurations as well.

Header The song header specifies the speed of the song, and instructs the tracker where to find the rest of the sections. The data format of the header is as follows:

DECLE SPEED, PATTERNS, INSTRUMENTS, DRUMKIT

Where each parameter is:

SPEED The playing speed of the song, i.e., the actual duration of a row, in ticks. PATTERNS Pointer to the starting address of the Patterns section. INSTRUMENTS Pointer to the starting address of the Instruments section. DRUMKIT Pointer to the starting address of the Drum Kit section.

As mentioned, SPEED is the duration of rows, in ticks, and controls the tempo of the song. (See Section II, Tracker Time for more information on the relationship between ticks, rows, and note durations). While the music player counts down to the next event row, the instrument synthesizer continues to process the channel as necessary to produce the intended sound.

If you know the desired tempo and time signature of a song, you can compute the SPEED value using the following formula:

Speed = (3,600 ÷ Tempo) × (Beats ÷ Rows)

Where the formula terms are:

Speed The computed speed value for the desired song tempo, in ticks. Tempo The desired song tempo, in beats per minute (BPM). Beats The length of a song measure, in beats. Rows The length of song patterns, in rows.

For example, given a typical module pattern of 64 rows, where we treat each pattern as a four-beat measure (i.e., 1/16th note = 1 beat); we can approximate a song tempo of 120 BPM by setting the song speed to a value of 2.

Note that the actual playback speed may not match exactly the target tempo. This is a consequence of the tracker being coupled to the TV signal, which necessarily quantizes all processing to multiples of the frame rate.

page 29 IV. Tracker Data Format From that same formula we could derive the approximate tempo of the song if we already know the speed and beats per row. All we need to do is switch around the Tempo and Speed terms:

Tempo = (3,600 ÷ Speed) × (Beats ÷ Rows)

For example, a song with a speed of six, defined with eight beats per row will approximate a tempo of 130 BPM.

Sequence This section defines the actual sequence of patterns to be played in a song. Only those patterns included in the sequence will be played, irrespective of how many patterns were actually defined.

The format of the sequence is as follows:

DECLE P[0], P[1], P[2], P[3], ..., ENDSEQ

Where P[n] is the ordinal value of the pattern within the Patterns section, starting from zero; and ENDSEQ is the sequence terminator, which indicates whether to end playback or loop the song.

The pattern value represents its index in the patterns array. The order of patterns in the sequence, from left to right, is the order in which they will be played.

The music player will continue playing for as long as there are patterns in the sequence. To signal that the song should end and the music player stop, you must terminate the sequence with the constant $F000. This magic value is interpreted by the tracker as the end-of-song terminator.

As a convenience, you can use the symbol constant TRK_END_SONG defined in the library to signal the end‑of‑song.

Alternatively, you can loop the song indefinitely by ending the sequence with a negative value. This will be interpreted by the music player as the number of patterns to backtrack, so -1 means, “repeat last pattern”; -2 means “repeat last two patterns”; etc. You can backtrack any number of patterns, all the way to the beginning of the song.

A song sequence may include up to 255 patterns, including the ENDSEQ terminator.

Below is an example of a song sequence. Notice how some patterns repeat in a few places, and do not necessarily have to preserve their original order. Also notice that the song ends by backtracking 16 patterns, to repeat the song from the 5th spot in the sequence, pattern #3:

DECLE 0, 1, 2, 2, 3, 4, 3, 4, 5, 6, 7, 8, 3, 4, 3, 4, 9, 10, 11, 12, -16

It is important that you ensure that the sequence is always terminated by either the TRK_END_SONG sentinel or a looping offset. Otherwise, the tracker will enter an unpredictable state, which may result in the program crashing.

Likewise, note that if you intend to loop the song, you must make sure not to backtrack beyond the start of the sequence.

page 30 IV. Tracker Data Format Patterns A pattern is defined by its length, followed by pointers to sub-patterns for all channels. The length of a pattern is expressed in rows. The format of patterns is as follows:

DECLE LENGTH, SUB[A], SUB[B], SUB[C], SUB[D], SUB[E], SUB[F]

Where LENGTH is the number of rows in the pattern, and SUB[A] through SUB[F] are pointers to sub-patterns defined in the Sub-Patterns section, one for each channel, respectively. When the pattern is encountered in the sequence, the music player will play the sub-pattern assigned for each channel simultaneously.

Note that you must include patterns for exactly the number of channels configured. Otherwise, the tracker will not work correctly and may cause the program to crash.

Also be aware that, although the length assigned to a pattern may be arbitrary, the music player expects all its sub-patterns to be of that same length. You are advised to ensure this is the case.

The order in which the patterns are laid out in this section of the data structure is not significant, and need not resemble any particular musical or channel order. The Sequence section takes care of organizing the patterns in the actual playback order and channel assignments.

Below is an excerpt of code from an actual song data file, illustrating its patterns definition section. In this example, each sub-pattern is referenced via a local procedure label, but any numeric or symbolic pointer will work as well.

; A B C D E F ; ------; Drums Bass Seq Chords Top Mel Sub Mel ; ------@@patterns: DECLE 64, @@p0_00, @@p1_00, @@p2_00, @@p3_00, @@p0_00, @@p0_00 ; #0 DECLE 64, @@p0_01, @@p1_01, @@p2_00, @@p3_01, @@p0_00, @@p0_00 ; #1 DECLE 64, @@p0_02, @@p1_02, @@p2_00, @@p3_00, @@p4_02, @@p0_00 ; #2 DECLE 64, @@p0_02, @@p1_03, @@p2_00, @@p3_00, @@p4_03, @@p0_00 ; #3 DECLE 64, @@p0_02, @@p1_02, @@p2_00, @@p3_00, @@p4_02, @@p5_04 ; #4 DECLE 64, @@p0_02, @@p1_03, @@p2_00, @@p3_00, @@p4_03, @@p5_05 ; #5 DECLE 64, @@p0_02, @@p1_02, @@p2_00, @@p3_00, @@p4_06, @@p5_06 ; #6 DECLE 64, @@p0_02, @@p1_07, @@p2_00, @@p3_00, @@p4_07, @@p5_07 ; #7

Some final considerations on pattern definitions:

• Patterns define re-usable musical sections for each channel to play simultaneously.

• A pattern must include assignments for all channels supported by the chosen program configuration.

• Patterns can vary in length, even within the same song, but all sub-patterns must match the length of the respective patterns in which they are used.

• The order in which patterns are defined is not significant, but once laid out, their position in the list becomes their ordinal value to use in the Sequence section, starting at index #0.

• Only channels A and D support drum sub-pattern assignments.

• A sub-pattern may be assigned to multiple channels within the same pattern, and re-used as many times as needed.

• A song may define up to 255 individual patterns.

page 31 IV. Tracker Data Format Instruments An instrument is a specific configuration of an envelope, a pitch effect, and vibrato. Its definition takes the following format:

DECLE PITCHFX, VIBRATO, ENVELOPE

Where the parameters used are:

PITCHFX A pointer to a pitch effect definition. VIBRATO The level of vibrato to apply, from 0 (no vibrato) to 3 (maximum) ENVELOPE A pointer to an envelope definition.

See Section IV, Global Definitions for information on pitch effects and envelopes. The tracker implementation of vibrato is described below.

Vibrato

The tracker applies vibrato to an instrument by modulating the frequency of a tone with a software-generated low- frequency oscillator (LFO). The amplitude of the LFO can be set to one of three values, resulting in varying depths of the modulating effect. The three values are:

Value Level Modulation Depth

3 High +/- 30%

2 Medium +/- 15%

1 Low +/- 7.5%

0 None No modulation

As illustrated below, the LFO modulates the pitch of the original tone by approximately +/- 30% at its highest level. The medium level reduces the amplitude of the modulating wave by half, and the lowest level by another half.

High ~30% pitch modulation Medium ~15% pitch modulation Low ~7.5% pitch modulation

page 32 IV. Tracker Data Format

The frequency of the LFO wave is statically defined at 7.5 Hz (i.e., 1/8th the 60 Hz frame rate), and sampled on every tick. The synthesizer uses the global tick counter to quantize the LFO samples at eight points per cycle, and applies the effect throughout the duration of the note.

The graphs below illustrate the quantization of the LFO wave at its three levels. As shown, the wave phase is offset by 270º degrees, always starting at its trough. It should be noted that because the software oscillator is synchronized to the global tick counter, the modulation waves for all channels with an active vibrato will always be in phase.

High Medium Low

Below is an example of an instruments definition section. The order in which the instruments are defined is not really important, but once laid out, their position in the list becomes their ordinal value to use when referring them within sub-patterns. Unlike pattern definitions, instrument indices start at #1.

; Pitch FX Vibr Envelope Instr ; ------@@instr DECLE MUSIC.pitch01, 1, MUSIC.env01 ; #1 DECLE MUSIC.pitch01, 2, MUSIC.env02 ; #2 DECLE MUSIC.pitch02, 2, MUSIC.env01 ; #3 DECLE MUSIC.pitch01, 0, MUSIC.env01 ; #4

Once defined, an instrument can be used at any point during the song by including it in a sub-pattern note event. Up to 85 instruments may be defined for a song.

Drum Kit Drum kits are defined in a similar way to instruments, except that they do not have any effects or envelope. The format is:

DECLE DRUM

Where DRUM is a pointer to a drum definition elsewhere in the data. See Section IV, Global Definitions for information on how drums are defined.

Below is an excerpt of a Drum Kit section taken from a sample song:

@@drums: DECLE MUSIC.bassdrum ; #1 DECLE MUSIC.snare ; #2 DECLE MUSIC.hihat ; #3 DECLE MUSIC.openhihat ; #4

Like instruments, the position of a drum in the definitions list becomes their ordinal value when referenced, starting at index #1.

You can include up to 43 individual drums in a drum kit, although it is common to use only a handful in a song. page 33 IV. Tracker Data Format Sub-Patterns (Data Record Format) A sub-pattern defines the series of actual note events that occur on a channel over the course of a number of rows; and indicate the instrument, note, and volume to be played at a given time.

Although the length of a sub-pattern is determined by the number of rows it describes, the Intellivision Music Tracker uses a simple form of Run Length Encoding (RLE) to reduce the size of the data structure. Rather than describe each individual row in the sub-pattern as a data record – including idle rows with no events – only rows with actual note events are recorded, along with how many rows to wait until the next event.

Caution should be taken to ensure that the number of event rows defined in a sub-pattern (and the total rows consumed by their collective durations) matches the length of the patterns in which it will be used.

An event data record is stored in one 16-bit data word. When an instrument change is invoked, an additional 16‑bit word follows with the instrument information. The general format of a note event data record is as follows:

DECLE feee eeee llll vvvv ; Event record DECLE 0000 0000 iiii iiii ; Additional instrument record

Where the letters represent bitfields conveying specific information as described below:

f A flag indicating whether the event calls for an instrument change. 0..1 e Event-specific information (see below). Varies per type l Length of event, i.e., how many rows to wait until the next event. 0..15 v New volume to set for the channel. 0..15 i New instrument to set for the channel. 0..83

There are three types of note events: note, drum, and null; and each one has slight variations and constraints on how their data records are defined. These will be described in detail below.

Event Type Note

Description Plays a new note on the channel, and optionally changes the currently active instrument.

When the event includes an instrument change, the “f” flag is set to 1 and an additional 16-bit word follows with the instrument information. Otherwise, the flag is set to 0 and the currently active instrument is used.

Note event types are used to play a new note on the channel.

Data Record Format DECLE fnnn nnnn llll vvvv DECLE 0000 0000 iiii iiii

Event-Specific Information f Instrument change indicator 0: No change 1: Change

n Note index number 1..84

i Instrument index number 0..252 : ((inst − 1) × 3)

page 34 IV. Tracker Data Format

Event Type Drum

Description Plays a new drum sound on the channel.

Because the drum instrument is part of the event data, there is no additional instrument record, and consequently the “f” flag is always 0.

Drum event types are used to play a new drum on the channel.

Data Record Format DECLE 0ddd dddd llll vvvv

Event-Specific Information d Drum index number 85..127 : (drum + 84)

Event Type Null

Description Continues the currently active note, if any, and optionally changes the instrument or volume of the channel.

Just like with note event types, the “f” flag indicates whether an instrument change is required; and if so, an additional instrument data record follows.

Null event types are used to change the channel volume or instrument without affecting the currently active note. They can also be used to introduce musical rests by silencing the channel for a given duration; or to extend the duration of an active note for longer than 15 rows.

Data Record Format DECLE f000 0000 llll vvvv DECLE 0000 0000 iiii iiii

Event-Specific Information f Instrument change indicator 0: No change 1: Change

i Instrument index number 0..252 : ((inst − 1) × 3)

The values for the event and instrument data records are computed using the following formulas:

EventData = [(Flag << 15) | (Event << 8) | (Length << 4) | (15 − Volume)]

InstrumentData = [(Instrument − 1) × 3]

The Event term depends on the event type and will be described further on. The rest of the terms are:

Flag The instrument change indicator flag, 0 or 1. Length The length of the event, from 0 to 15 rows. Volume The volume for the channel, from 0 to 15. Instrument The index of the instrument in the Instruments definition section, starting from 1.

page 35 IV. Tracker Data Format Note that the channel volume is stored as the complement of the actual amplitude – that is, it is treated as an attenuation level; where a value of 15 is converted to an attenuation level of 0 (full volume), and 0 is converted to the maximum attenuation level of 15 (silence).

The formulas to compute the Event term for each event type are described below:

Note Event:

Event = [SemiTone + (12 × Octave)]

SemiTone The ordinal value of the semi-tone, from 1 (C) to 12 (B). Octave The ordinal value of the octave, from 1 (lowest) to 7 (highest).

The semi-tone ordinal value is a number from 1 to 12 indicating a semi-tone in the chromatic scale, as illustrated below. Seven octaves are supported, with 12 semi-tones each, for a total musical range of 84 notes.

Drum Event:

Event = (84 + Drum)

Drum The index of the drum in the Drum Kit definition section, starting from 1.

As can be inferred from the previous descriptions, the tracker distinguishes between event types by the values in the event-specific information field:

• A value of Zero – Indicates a null event type, so the note or drum remain unchanged for the channel.

• Values from 1 to 84 – Indicate a new note for the channel.

• Values greater than 84 – Indicate a new drum sound for the channel.

In order to better illustrate how to compose the tracker data structure, in the following pages we will encode a simple excerpt of a song, step by step, using the formulas and formats described above.

page 36 IV. Tracker Data Format A Practical Example

Let us consider the example used in Section IV, Global Definitions, when describing Tracker Time. It’s the first measure of a popular children’s song, expressed in the typical tracker module notation as a sub-pattern:

R# N I V ------00 C-4 1 F 01 --- - - 02 C-4 1 F 03 D-4 1 F 04 --- - - 05 D-4 1 F

We will now go row by row and encode each one into a properly formatted event data record.

Row #00: C-4 1 F

Flag 1 Because it is the first event, it will introduce a new instrument to the channel, so we set the indicator flag.

Note 1 + (12 × 4) = 49 Note C-4 is the 1st semi-tone on the fourth octave.

Length 1 The next event starts on row #02, so the tracker must wait one row before the next event.

Volume (15 − 15) = 0 Full volume is equivalent to an attenuation level of zero.

Instrument (( 1 − 1) × 3) = 0 The new instrument has an index of 1.

Now, we put the terms together in our event and instrument formulas to get the data records:

DECLE $B110 ; 1011 0001 0001 0000 Evnt: (1 << 15) | (49 << 8) | (1 << 4) | 0 DECLE $0000 ; 0000 0000 0000 0000 Inst: ((1 − 1) × 3)

Row #02: C-4 1 F

Flag 0 No instrument change, so we clear the indicator flag.

Note 1 + (12 × 4) = 49 It’s the same note, but we want to play it again, restarting the envelope and effects anew.

Length 0 The next event is on the very next row, so no wait needed.

Volume (15 − 15) = 0 Full volume is equivalent to an attenuation level of zero.

Instrument N/A No instrument change information.

page 37 IV. Tracker Data Format The composed data record is:

DECLE $3100 ; 0011 0001 0000 0000 Evnt: (0 << 15) | (49 << 8) | (0 << 4) | 0

Row #03: D-4 1 F

Flag 0 No instrument change, so we clear the indicator flag.

Note 2 + (12 × 4) = 50 Note D-4 is the 2nd semi-tone on the fourth octave.

Length 1 The next event starts on row #05, so delay one row.

Volume (15 − 15) = 0 Full volume is equivalent to an attenuation level of zero.

Instrument N/A No instrument change information.

The composed data record is:

DECLE $3210 ; 0011 0010 0001 0000 Evnt: (0 << 15) | (50 << 8) | (1 << 4) | 0

Row #05: D-4 1 F

Flag 0 No instrument change, so we clear the indicator flag.

Note 2 + (12 × 4) = 50 It’s the same note, but we want to play it again, restarting the envelope and effects anew.

Length 0 The next event is on the very next row, so no delay.

Volume (15 − 15) = 0 Full volume is equivalent to an attenuation level of zero.

Instrument N/A No instrument change information.

The last composed data record is:

DECLE $3200 ; 0011 0010 0000 0000 Evnt: (0 << 15) | (50 << 8) | (0 << 4) | 0

The final code for the sub-pattern we have just defined would then be included in the data file as follows:

@@p001: DECLE $B110 ; 1011 0001 0001 0000 DECLE $0000 ; 0000 0000 0000 0000 DECLE $3100 ; 0011 0001 0000 0000 DECLE $3210 ; 0011 0010 0001 0000 DECLE $3200 ; 0011 0010 0000 0000

The symbolic label allows us to refer to the sub-pattern by name when including it in a pattern definition.

Data records for null and drum event types are composed similarly, using their respective formulas and formats. page 38 IV. Tracker Data Format Sub-Patterns (Macro Interface) Notwithstanding all the information described above for sub-patterns, there is no real need to go through the effort of manually computing each data record.

For convenience, the Intellivision Music Tracker library includes a set of easy-to-use macros to facilitate the definition of sub-patterns. These macros allow you to define note events in an intuitive notation similar to that employed in typical tracker modules.

The first and most important macro is NOTE(), used to define note events:

NOTE(NoteEvent)

The NoteEvent parameter is a string representing a compacted form of the typical module notation. It takes three forms, depending on the event type, as shown below:

DRM IVL Indicates a drum event type. NUL IVL Indicates a null event type. NnO IVL Indicates a note event type.

With the exception of the static labels DRM and NUL used for the first two event types, each character represents a field conveying specific information:

I New instrument to set for the channel. 0 / 1..9 / A..Z V New volume to set for the channel. 0..F L Length of event, i.e., how many rows to wait until the next event. 0..F N The letter representing the note in the chromatic scale. A..G n A symbol indicating a sharp note. # or –

O The octave of the note. 1..7

As depicted above, all numeric values are in Hexadecimal, with the exception of the I instrument field. In order to support more than 15 instruments, the NOTE() macro has been extended to accept all letters of the alphabet for instrument indices greater than 9.

To the macro, the ordinal value of letters starts at 10 for “A” and ends on 35 for “Z.” This limits the number of instruments that the macro supports to 35, which is still a substantial number of instruments per song.

Bear in mind that instrument number zero is reserved to indicate that there is no change of instrument. Thus the values supported by the I instrument field are:

0 No change in instrument for the channel, continue using previous one. 1..9 Instrument indices lower than 10. A..Z Instrument indices 10 or greater.

Moreover, for note even types, an index of zero will result in the instrument getting reset for the new note, restarting the envelope and all effects. For all other event types, the current instrument will just continue playing, extending the envelope and effects for the duration of the event.

page 39 IV. Tracker Data Format Below are a few examples of the NOTE() macro in use:

NOTE("DRM 4F0") ; Play drum #4; full volume; no delay NOTE("DRM 3F0") ; Play drum #3; full volume; no delay NOTE("G-5 HFB") ; Play note G-5; instrument #17; full volume; wait 11 rows NOTE("F#4 0F2") ; Play note F#4; same instrument; full volume; wait 2 rows NOTE("NUL 0A4") ; Extend last note for 4 rows; change volume to level 10 NOTE("NUL 00F") ; Silence channel for 15 rows

As an alternative to defining each individual event with the NOTE() macro, the library offers the macro NOTES() that allows the definition of up to four events at a time. This is useful in organizing the song sub-pattern data, especially considering that musical sections are typically divided in multiples of four.

The calling format of the NOTES() macro is as follows:

NOTE(NoteEvent[1], NoteEvent[2], NoteEvent[3], NoteEvent[4])

Where each NoteEvent[n] follows the exact same format used by the NOTE() macro, described before. If any of the event arguments is the empty string (""), it will be ignored. Thus, the macro can be used to define less than four events.

Below is a sub-pattern definition excerpted from a song, illustrating the use of the NOTES() macro:

@@p005: NOTES("A-5 4F3", "G-5 4F1", "A-5 4F1", "C-6 4F1") NOTES("B-5 4F3", "C-6 4F0", "D-6 4F0", "E-6 4F1") NOTES("C-6 4F1", "B-5 4F1", "G-5 4F1", "C-6 4F1") NOTES("A-5 4F1", "F-5 4F1", "G-5 4F1", "")

Notice how the last macro only defines three events, leaving the last one blank. This is because the length of the pattern has already been reached, and no further events are needed. Nonetheless, it is still useful – and potentially more clear and easier to read – to define the events with the NOTES() macro horizontally, than to list them vertically, one at a time.

page 40 IV. Tracker Data Format Automatic Note Packing (NPK Macro Interface) The macro library includes yet another way to define sub-patterns easily without having to worry about the length of individual events, called Automatic Note Packing (NPK). The NPK family of macros allows you to define patterns one row at a time. It then automatically packs events spanning multiple rows into single data records.

NPK patterns are especially useful for creating drum tracks, which are easier to manage as individual events placed on a regular scale of discrete rows. Unrolling the pattern into individual rows makes it easier to visualize its full scope, allowing you to add syncopated accents and other rhythmic features, while maintaining a steady beat.

In an NPK pattern, events are defined in blocks surrounded by the directives NPK.Begin() and NPK.End. Each individual row is then defined using the NPK.Note() macro, in much the same way as with NOTE(). The calling format for the macros is:

NPK.Begin(PatternLength)

NPK.Note(NoteEvent[0]) ; Note event for row 0 NPK.Note(NoteEvent[1]) ; Note event for row 1 NPK.Note(NoteEvent[2]) ; Note event for row 2 ; ... NPK.Note(NoteEvent[n]) ; Note event for row n

NPK.End

Where each parameter is defined as follows:

PatternLength The length of the pattern, in rows; or zero to disable length validation. NoteEvent[n] The note event for a row, following the same notation as the NOTE() macro.

If the PatternLength argument is greater than zero, the total number of rows identified in the pattern will be validated against the value. A value of zero disables length validation.

Notice that the format of the NoteEvent[n] argument still expects a length, in rows. This is to maintain compatibility with the other macros. Although NPK.Note() is expected to be used for discrete rows, any valid length can be given, offering utmost flexibility. You can always rely on the pattern length validation to detect any inconsistencies in row length.

The NPK macros will automatically combine note and drum event types with corresponding null event types used to extend them, identify actual event boundaries, and pack them into individual data records. If an event overruns the 16 row maximum, it will be automatically split accordingly. This provides a more natural way of defining patterns.

For example, consider a 16-row drum pattern representing four beats, each lasting four rows. Using the standard NOTE() macro could look like this:

@@p001: NOTE("DRM 1F3") ; Beat #1, delay three rows NOTE("DRM 2F3") ; Beat #2, delay three rows NOTE("DRM 1F3") ; Beat #3, delay three rows NOTE("DRM 2F3") ; Beat #4, delay three rows

This works, and would be compacted into four data words, one for each of the individual drum events. It is also rather inflexible. Any alteration to the pattern – like, say, to add a syncopated snare in between beats #3 and #4 – requires re-flowing the rows, and re-calculating their lengths. This becomes increasingly cumbersome as the length of the pattern increases and as the complexity of events grows.

page 41 IV. Tracker Data Format A more natural way to define the same pattern would be to unroll each row as a separate event, using null event types to extend the original one. For example:

@@p001: NOTE("DRM 1F0") ; Beat #1 NOTE("NUL 0F0") ; - NOTE("NUL 0F0") ; - NOTE("NUL 0F0") ; - NOTE("DRM 2F0") ; Beat #2 NOTE("NUL 0F0") ; - NOTE("NUL 0F0") ; - NOTE("NUL 0F0") ; - NOTE("DRM 1F0") ; Beat #3 NOTE("NUL 0F0") ; - NOTE("NUL 0F0") ; - NOTE("NUL 0F0") ; - NOTE("DRM 2F0") ; Beat #4 NOTE("NUL 0F0") ; - NOTE("NUL 0F0") ; - NOTE("NUL 0F0") ; -

In this way, you can visualize the full scope of the pattern as a table. Finding your place in the pattern is easy (the number of event definitions never changes), and it is rather trivial to add or remove events and still maintain an overall view of the beat.

Unfortunately, unrolling the pattern in this way will result in unnecessary wasted space. What used to consume four data words, now takes a full 16 – one for each individual row.

With Automatic Note Packing, you can retain the advantages of a fully unrolled pattern, while avoiding the unnecessary waste of encoding each row individually as a data record.

Reconfiguring the unrolled pattern above using the NPK macros, will look like this:

@@p001: NPK.Begin(16) ; Ensure exactly 16 rows in pattern

NPK.Note("DRM 1F0") ; Beat #1 NPK.Note("NUL 0F0") ; - NPK.Note("NUL 0F0") ; - NPK.Note("NUL 0F0") ; - NPK.Note("DRM 2F0") ; Beat #2 NPK.Note("NUL 0F0") ; - NPK.Note("NUL 0F0") ; - NPK.Note("NUL 0F0") ; - NPK.Note("DRM 1F0") ; Beat #3 NPK.Note("NUL 0F0") ; - NPK.Note("NUL 0F0") ; - NPK.Note("NUL 0F0") ; - NPK.Note("DRM 2F0") ; Beat #4 NPK.Note("NUL 0F0") ; - NPK.Note("NUL 0F0") ; - NPK.Note("NUL 0F0") ; -

NPK.End

Yet, the above will result in exactly the same four data records as in the original example. NPK.Note() will take care of identifying the rows that belong to the same event, and of detecting the actual event boundaries; offering optimal results with minimal hassle.

page 42 IV. Tracker Data Format Putting It All Together

In this section we will illustrate how all the sections of a tracker module data structure come together in a song. The example below is an ultra-simplified version of the “Demo Song,” tracked by Arnauld Chevallier, and originally included with his tracker. Both versions are included in the latest Intellivision Music Tracker library.

Global Music Definitions ;; ======;; ;; GLOBAL MUSIC DEFINITIONS ;; ;; Typically these elements are defined globally and made available to ;; ;; all songs, but for this example we include it here to illustrate a ;; ;; complete an self-contained song. ;; ;; ======;; SIMPLEMUSIC PROC

;; ------;; ;; Pitch effects ;; ;; ------;; @@pitch01: DECLE 0, 0, 0, 0 ; No effect @@pitch02: DECLE 0, 0, 12, 12 ; Alternate octaves every 2 ticks

;; ------;; ;; Envelopes ;; ;; ------;; @@env01: DECLE 1 ; Speed #1: 4 ticks per sample DECLE $FFFF, $EEEE, $DDDD, $CCCC ; Sharp attack, slow decay DECLE $BBBB, $AAAA, $9999, $8888 DECLE $7777, $6666, $5555, $4444 DECLE $3333, $2222, $1111, $0000

@@env02: DECLE 2 ; Speed #2: 8 ticks per sample DECLE $FEDC, $BA98, $7654, $3210 ; Sharp attack, fast decay DECLE $0000, $0000, $0000, $0000 DECLE $0000, $0000, $0000, $0000 DECLE $0000, $0000, $0000, $0000

;; ------;; ;; Drums ;; ;; ------—- ;; @@bassdrum: DRUM($000, $10, 0, 1, $F) ; Step #1: Start with noise DRUM($380, $00, 1, 0, $F) ; Step #2: Settle on tone DRUM($380, $00, 1, 0, $F) ; DRUM($480, $00, 1, 0, $E) ; Step #4: Start decaying ... DRUM($480, $00, 1, 0, $D) ; and detuning DRUM($580, $00, 1, 0, $C) ; DRUM($580, $00, 1, 0, $A) ; DRUM($580, $00, 1, 0, $8) ; DRUM($000, $00, 0, 0, $0) ; Step #9: End instrument sound DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; DRUM($000, $00, 0, 0, $0) ; Step #16: End definition

;; ------—- ;; ENDP ;; ------—- ;; page 43 IV. Tracker Data Format The Simple Song ;; ======;; ;; SONG: Simple Song ;; ;; These are the elements used in “Simple Song,” which includes ;; ;; references to the global elements defined in the “SIMPLEMUSIC” ;; ;; data structure. ;; ;; ======;; SIMPLESONG PROC

; ------; ; Song Header ; ; ------; DECLE 6, @@patterns, @@instr, @@drums ; Speed: 6 ticks per row (~131 BPM)

; ------; ; Sequence ; ; ------; @@sequence: DECLE 0, 1, 1, 2, 3, 2, 3, 0, -8 ; Play patterns, repeat

; ------; ; Channel Patterns ; ; ------; ; Len A B C D E F ; ------@@patterns: DECLE 32, @@p000, @@pXXX, @@pXXX, @@pXXX, @@pXXX, @@pXXX ; #0 DECLE 32, @@p000, @@p002, @@pXXX, @@pXXX, @@pXXX, @@pXXX ; #1 DECLE 32, @@p000, @@pXXX, @@pXXX, @@p001, @@pXXX, @@pXXX ; #2 DECLE 32, @@p000, @@p002, @@pXXX, @@p001, @@pXXX, @@pXXX ; #3

; ------; ; Instruments & Drum Kit ; ; ------; ; Pitch Effect Vibr Envelope ; ------@@instr: DECLE SIMPLEMUSIC.pitch01, 2, SIMPLEMUSIC.env01 ; #1 DECLE SIMPLEMUSIC.pitch02, 2, SIMPLEMUSIC.env02 ; #2

@@drums: DECLE SIMPLEMUSIC.bassdrum ; #1

; ------; ; Sub-Patterns ; ; ------; ; Drums pattern (we only defined one drum in our kit) @@p000: NOTES("DRM 1F7", "DRM 1F1", "DRM 1F5", "DRM 1F7") NOTES("DRM 1F1", "DRM 1F5", "", "")

; Melody #1 @@p001: NOTES("A-3 1E3", "C-4 1E2", "E-4 1E0", "F-4 1E1") NOTES("E-4 1E1", "C-4 1E3", "A-3 1E3", "C-4 1E2") NOTES("E-4 1E0", "F-4 1E1", "A-4 1E1", "G-4 1E3")

; Melody #2 @@p002: NOTES("A-5 2F7", "G-5 2F7", "E-5 2F7", "D-5 2F1") NOTES("E-5 2F5", "", "", "")

; Silence channel throughout entire pattern @@pXXX: NOTES("NUL 10F", "NUL 10F", "", "")

;; ------—- ;; ENDP ;; ------—- ;; page 44 Appendix Appendix

A. Library Distribution

Public Domain Release The Intellivision Music Tracker distribution, including all source code, example songs, and documentation files, is released to the Public Domain, for anybody to use in any manner they desire. You are encouraged to attribute the authors and composers if you use any of the included material in your projects.

File Structure The Intellivision Music Tracker distribution includes the following library modules, examples, and other files:

+-- trk-distro Intellivision Music Tracker | +-- bin AS-1600 assembler output | |-- trk-demo.rom -> Program binary in ROM format | |-- trk-demo.bin -> Program binary in BIN format | `-- trk-demo.cfg -> Configuration file for BIN binary | +-- src Source Files | +-- lib Tracker Library Modules | | |-- asm-interface.asm -> Assembly Language interface | | |-- asm-memmap.asm -> Assembly Language memory map | | |-- bas-callapi.bas -> IntyBASIC procedure call API | | |-- bas-interface.bas -> IntyBASIC interface | | |-- bas-memmap.asm -> IntyBASIC memory map | | |-- global-config.asm -> Global configuration constants | | |-- tracker.asm -> Tracker module source file | | `-- tracker.mac -> Tracker macro library | | | +-- sdk SDK-1600 Library Modules | | |-- cart.mac -> Cartridge macro library | | |-- gimini.asm -> Environment constants | | `-- print.asm -> Screen display library | | | +-- songs Example Songs | | |-- global-music.asm -> Global music definitions | | |-- beatit-remix.asm -> "Beat It" (Remix) | | |-- butterfly.asm -> "Flight Of The Butterfly" | | |-- demo-remix.asm -> "Demo Song" (Remix) | | |-- drums-demo.bas -> "Drums Demo (Funk My Drum Machine)" | | |-- simple-song.asm -> "Simple Song" | | |-- space-6ch.asm -> "Journey Through The Stars" | | `-- voyage-6ch.mac -> "Voyage Theme" | | | `-- trk-demo.asm -> Assembly Language demonstration program | +-- utils Additional Utility Programs | |-- ibn2imt.pl -> Tool to convert IBN files to IMT format | `-- ibn2imt-manual.txt -> Manual for the “IBN-2-IMT” tool | `-- Tracker v1.5 User Manual & Technical Guide.pdf

* – Used only for the IntyBASIC interface (see the next section). page 45 Appendix B. IntyBASIC Support

This version of the Intellivision Music Tracker has been adapted to support a more flexible programming interface in order to integrate both Assembly Language and IntyBASIC programs.

Overview The integration with IntyBASIC is not perfect, mostly due to limitations in its interface with Assembly Language programs. The most salient of which is a lack of access to the assembler pre-processor symbols from IntyBASIC, and a converse lack of access to IntyBASIC constants from the assembler.

Because the song definition macros rely heavily on the assembler pre-processor to manipulate symbols and compute conditional output, these cannot be translated into IntyBASIC language constructs. Consequently, songs must still be defined using Assembly Language structures and macros, and included as such in your IntyBASIC source code.

Program Integration Notwithstanding the limitations described above, integrating the Intellivision Music Tracker with your IntyBASIC program is rather simple and straightforward, and follows the same steps as described in Section III, Using The Tracker – Program Use.

To facilitate integration, the library includes the following modules specific to IntyBASIC:

bas-interface.bas IntyBASIC interface module. bas-memmap.asm IntyBASIC memory map module. bas-callapi.asm IntyBASIC procedure call API module.

Together, these modules take care of adapting the Assembly Language library to work with IntyBASIC.

For information on the tracker routines referenced below, see Section II, Technical Specification – Tracker Program Interface.

1. Set Up Tracker The first step is to include the IntyBASIC Interface module. This module sets up the environment, allocates the necessary variables, and includes all the rest of the library modules in their order of dependency.

The IntyBASIC Interface module can be included anywhere in your program, as long as it comes before any song data files.

' ======' Game Data ' ======

' Include the tracker IntyBASIC Interface Library ' anywhere in your program, along with any other ' libraries you use. INCLUDE “lib/bas-interface.asm”

page 46 Appendix Lastly, you need to include any song data you plan to use in your program. It is not really important where you place the song data, as long as it occurs after the inclusion of the IntyBASIC Interface module.

Because song data files are still created as assembler structures, there are a few important caveats to keep in mind:

• You need to include the song data as Assembly Language modules, using the ASM directive.

• You should assign a label prior to including the song data. This will enable you to refer to the song by name from IntyBASIC, rather than by its absolute address in memory.

' ======' Game Data ' ======

' ...

' Include songs at the end of your program, or ' wherever other data structures are included. ' Note that assigning a label before including ' the song allows us to address the structure ' using VARPTR when we load it. MYSONG: ASM INCLUDE “songs/mysong.asm”

2. Initialize Tracker To initialize the tracker for use, include a call to the TRKINIT procedure at the start of your program or from your own initialization routine, like this:

' ======' Game Program ' ======

' INITIALIZATION: ' Initialize the tracker as part of your ' game initialization routine. CALL TRKINIT

' MAIN PROGRAM: ' ' <... YOUR CODE GOES HERE ...> '

page 47 Appendix 3. Load A Song To play a song, you need to load it into the tracker’s memory using the TRKLOADSONG procedure, with the base address of the song data as an argument. You may call TRKLOADSONG at any point after initializing the tracker, as illustrated below:

' ...

' LOAD & PLAY SONG: ' At any point after initialization, you may ' load a song ... ' ' The song will start playing immediately. CALL TRKLOADSONG( VARPTR MYSONG(0) )

' ' The rest of your program continues here … '

Noticed that we use VARPTR with the label we assigned to the song structure, in order to pass the address of the song to the routine.

4. Update Playback In order for a song to play, you need to call the procedure TRKPLAY periodically from your main game loop. The simples way to do this is by using the ON FRAME GOSUB directive provided by IntyBASIC. This ensures that the tracker is updated on every frame. See below for an illustration:

' Set up a recurring task to update ' the tracker on every frame. ON FRAME GOSUB UpdateTracker

' ======' Game Program ' ======

' ' <... YOUR CODE GOES HERE ...> '

' UPDATE TRACKER STATE: ' The state of the tracker needs to be updated ' once per frame, ideally at the same place ' every time. UpdateTracker: PROC CALL TRKPLAY END

page 48 Appendix C. IntyBASIC Supplementary API

In addition to the standard programming interface described in Section II, Technical Specification – Tracker Program Interface, the IntyBASIC Interface module includes a supplementary set of procedures to facilitate integration.

The purpose and calling interface of these routines are described below. For more information on how the procedures work, see the source code in the included “bas-callapi.asm” file.

Procedure TRKLOADSONG

Purpose Load & Play Song (IntyBASIC Interface)

Description Loads a song into memory and gets the tracker ready for playback. This is an alternative entry point to the TRKSNGINIT procedure specific to the IntyBASIC interface. The only difference is that it accepts the song argument in R0.

See the entry for TRKSNGINIT for more details.

Inputs R0Song base address.

Call Formats ' Invocation from IntyBASIC CALL TRKLOADSONG( VARPTR MYSONG(0) )

Procedure SET_ACTIVE_CHANNELS

Purpose Set Active Channels (IntyBASIC Interface)

Description Sets the global channel state to a special code representing the number of active channels available to the tracker. This procedure takes the place of the SET_ACTIVE_CHANNELS() macro from the standard interface.

Note that if the number of channels given is not within the valid range of 1 to 6, the call will be ignored, and the previous active channel state will remain.

Inputs R0Number of active channels to set.

Call Formats ' Invocation from IntyBASIC CALL SET_ACTIVE_CHANNELS(5) ' Sets five active channels

page 49 My most sincerest thanks go to Arnauld Chevallier for his hard work in developing the original Intellivision Music Tracker, and for his generosity in sharing it with the world.

Copyright © 2020 – 2021, James Pujals Permission is granted to reproduce and distribute this material freely, without restrictions, as long as the original content remains unchanged.