Hover-Tracker 384: A chiptune-style Javascript tracker by Patrick Shaughnessy This manual describes a program I made that renders chiptune-like music using a tracker-style interface. The chiptune style is not based exactly on any one chip, but produces sounds recognizable in the general category of chips such as the SID, 2A03, and AY-3-8910, including square, triangle, saw, and noise waves. The tracker program has two minor screens with just a couple controls each and two main screens. This manual also describes a couple things you can do with the song outside of the tracker interface. This is the first version of this manual, written May 20 for the corresponding version of the tracker. ---- SCREEN MAP The program starts at the loading screen, and returns there if you refresh the browser. Loading screen -> Main screen <-> Instrument screen ^ | v JSON screen ---- LOADING SCREEN At this screen, you can start a new song or load an existing song. To start a new song, enter three numbers: "Frames per second" determines the resolution of the audio, from 8000Hz to 96000Hz. 44100 Hz is CD-quality. The program will start playback faster at lower resolutions, but higher-pitched sounds will be distorted. "Frames per row" determines the tempo at which the program steps through the song patterns. The actual time this takes depends on frames per second. For example, if there are 96000 frames per second and 12000 frames per row, then each row will take an eighth of a second. A row typically represents a sixteenth-note or thirtysecond-note, but you might for instance decide a row is a twentyfourth-note if you are using triplets in your composition. "Rows per pattern" determines how many rows form each of the patterns you'll build the song from. A pattern typically represents one, two, or four bars of music. After entering these values, click "New Song". The last song you were working on may already appear in the "Song data to load" textbox. If you want to load that song, just click "Load Song". To click a different song, delete anything that's already in the textbox, paste in the new song, and then click "Load Song". If you paste a JSON object that doesn't actually represent a song from this program, the program is likely to get confused and break. From the loading screen, you go to the main screen. Reloading the Web page will return you to the loading screen, usually with the song you were working on ready to reload. (Please do not think of persistence of the song across reloads as "guaranteed"; there are situations where it might not be there.) ---- MAIN SCREEN The main screen is where you edit the blocks and patterns that define the structure and melody of the song. Most of your editing will be done by interacting with two grids of ASCII characters. To work with a grid, click on an editable cell, then use the arrow keys to navigate and other keys to type. Only certain typed-in characters are accepted. The backspace and delete keys also function. The left-hand grid defines the song's blocks. The blocks of the song play in order. Each block specifies four patterns to play simultaneously. When you start a new song, it contains one block specifying patterns 1A, 2A, 3A, and 4A. To change which pattern is being specified, type into the grid. Pattern IDs can be any two-character combination of digits and letters. Pattern ID letters are always uppercase. A pattern ID that hasn't previously been used will start as an empty pattern. To add a block, click the grid to highlight where you want it, then click "Add block" below the grid. This will create a new block next to the highlighted one, specifying the same patterns as it. This is an insertion operation that pushes later blocks downward. To remove a block, highlight the block you want removed and click "Remove block". This is a deletion operation that pulls later blocks upward. The grid to the right of the blocks shows the four patterns of the currently selected block. Be aware that you are editing the patterns, not the block. For example, if five different blocks contain pattern 1A and you edit the part of this grid that's defining pattern 1A, all five of those blocks will now have the changed version of pattern 1A. To copy a pattern so you can work on the copy without changing the original, click the "Clone" button above the grid. This chooses an unused pattern ID, copies the pattern into it, and edits just the current block to be using that pattern ID instead of the original one. To get a new empty pattern to work with, just edit the block grid to use any pattern ID that hasn't already been used. Each row of the pattern can start a new note or modify the playback of an existing note. Each of the seven columns has a distinct meaning, and the meaning of a column can vary slightly depending on what is in the other columns. All columns in this grid can be erased with the spacebar or delete key. The labels at the top of the columns stand for "Note", "Accidental", "Octave", "Volume", "Pan", "Instrument", and "Special". A row starts a new note if the N [Note] column contains C, D, E, F, G, A, or B, and the Special column doesn't contain R or S. In a row that starts a new note: The N [Note] column determines the pitch of the note, using standard 12-tone A440 notation. The A [Accidental] column may contain a flat symbol or sharp symbol. Flat symbols appear as lowercase b and may be typed with the B or F key. Sharp symbols appear as # and may be typed as # or with the S key. For a natural note, leave the A column an empty space, which you can type with the spacebar if necessary. Usual synonyms apply: for example, E# is the same as F, F# is the same as Gb, and B# is like C but one octave higher. The O [Octave] column may contain a hexadecimal digit 0-F, setting the octave of the note. High values will mostly just produce aliasing artifacts and not actual notes, but the practical upper bound depends on the instrument and the song's frames per second. If O is blank but was specified earlier in the pattern, the closest earlier value will be used. If O is blank and not specified earlier in the pattern, then it is 4. The V [Volume] column may contain a hexadecimal digit 0-F, setting the volume of the note. If V is blank but was specified earlier in the pattern, the closest earlier value will be used. If V is blank and not specified earlier in the pattern, then it is F. The P [Pan] column may contain an L to place the note on the left speaker, an R to place the note on the right speaker, an M to place the note in the exact center, or a septendecimal digit 0-G. 0 is the same as L, 8 is the same as M, and G is the same as R. (Septendecimal is used instead of hexadecimal so the center can be an integer). If P is blank, it uses an earlier specified value, or M if not specified earlier in the pattern. The I [Instrument] column specifies the new note's instrument. It can contain a digit, uppercase letter, or lowercase letter. This is the only case-sensitive column. When you start a new song, instrument 0 is a simple square wave and other instruments are silent. If not specified earlier in the pattern, I defaults to 0. When starting a new note, the S [Special] column can be blank or can contain a hexidecimal digit 0-F. 0 is the same as blank. Any other value will phase-shift the note slightly. This usually won't have much audible effect, but if two channels are canceling or interfering in an undesired way, phase-shifting one of them may improve the situation. A row where N [Note] doesn't contain a letter from A to G, or where S [Special] contains S or R, modifies an existing note in progress. A pattern can start withrows like this; they will do nothing if there wasn't already a note playing from earlier pattern as the pattern began. If N [Note] contains a note letter, it abruptly changes the pitch of the note in progress. To do this, S [Special] must contain S or R, because otherwise using the N column this way starts a new note. N may also contain X or R. An X abruptly cuts the current note. An R releases the current note. A released note will continue to play for an amount of time that depends on its instrument, and you can define instruments to include gentle release curves instead of sudden stops. The A [Accidental] column is ignored unless N [Note] contains a note letter. When there is a note letter in N, then A can modify it with a sharp or flat, in the same way it modifies the pitch when starting a new note. The O [Octave], V [Volume] and P [Pan] columns can each cause abrupt changes in the current note, using the same range of values as when starting a new note. The I [Instrument] column can be changed in mid-note. If the two instruments have the same number of instrument rows, the same frames per row, and the same loop placement, then this is well-behaved and switches between the two sounds in-place. If the instruments differ in their structure, the result of changing instruments in mid-note may be unexpected. The S [Special] column may contain a hexadecimal digit from 0 to F, an S, or an R. A hexadecimal digit forces the waveform to a certain phase. 0 is not the same thing as empty here, since forcing the phase to 0 is different from leaving it unchanged. Putting an S in this column does nothing directly, but allows the N [Note] column to be used to modify the note; think "sustain" or "slide". R is like S but also releases the note, so you can release the note at the same time as a pitch change. A blank pattern row just allows the note to continue playing unmodified, or allows the silence to continue if no note is playing. To the right of the pattern grid is the instrument list. This list displays the letter or digit of each instrument and optionally its name, if you named it when you edited it. In a new song, all instruments except 0 are initially nameless and silent. Click an instrument's "Edit" button to go to its instrument screen. To copy an instrument so you can edit the copy while keeping the original, click its "Copy" button then the destination's "Paste" button. This "copying" and "pasting" does not use your operating system clipboard. You do not "select" instruments from this list to use them. The list shows you the instruments you've named to help you enter the appropriate digit or letter into the I [Instrument] column of the pattern grid. To actually listen to parts of your song, click any of the various "Play" or "Loop" buttons. You can play or loop just one pattern or just the current block, and you can loop the entire song starting at the current block or starting at the beginning. Looping the entire song may have a slight delay before it starts, since the complete song is written to an audio buffer before playback begins. To stop playback, click any of the "Play" or "Loop" buttons again, which now say "Stop". To get your song data in text form to save it, click "Get JSON". ---- JSON SCREEN The JSON screen just has a button to go back to the main screen, a read-only textbox, and a button that will try to copy the textbox to your clipboard and then go back to the main page. If your browser doesn't let the button work, you might have to select and copy the textbox manually. The contents of the textbox can be put into the loading screen to load the song, or used as a JSON literal in Javascript code to play the song from a program. ---- INSTRUMENT SCREEN The instrument screen is like a finer-grained tracker embedded inside the song tracker. An instrument has rows that are similar to pattern rows, but you edit them as graphical curves instead of text. The instrument may either produce a tonal waveform or noise. Use the "Wave type" dropdown to select which. The speed at which the instrument uses its rows is determined by its "Frames per row" setting. This should usually be smaller than the song's main frames per row value. It is rhythmically helpful, but not mandatory, to make it an exact divisor of the song's value. After editing the number, click "Update" or press Enter to actually apply the edit. To set the instrument's name for use on the instrument list, type in the "Name:" box and click "Update" or press Enter. The instrument is defined by seven parallel grids, which you left-click to edit. You can drag to edit multiple rows more easily than clicking them separately. The first six grids each have a white line showing the effect of interpolation. Click the checkbox above the grid to turn interpolation on or off, meaning that the value can change smoothly between rows instead of jumping abruptly. To add rows to the top or bottom, click the "Add 1/4 silence to start" or "Add 1/2s silence to end" button. To remove rows from the top or bottom, set their volume to 0 and click "Trim silence". If the volume grid has interpolation turned on, trimming might remove one fewer row than you expect because the extra row is used in the interpolation calculation. If you really want to get rid of that row, you can turn off interpolation, trim, and turn it back on. The first three grids let you curve the pitch of the note. The first grid is in units of an octave (1200 cents), the second is in units of a semitone (100 cents), and the third is in tenths of a semitone (10 cents). These values are combined together and applied simultaneously. A new instrument defaults to keeping these three grids in the center, meaning the pitch will just match what was specified in the pattern and not curve. The fourth grid lets you curve the volume of the note. The note is silent when the volume is all the way to the left, and the note is at its full volume (as specified by the pattern using it) when the volume is all the way to the right. The fifth and sixth grids work together. They behave differently depending on whether the note is a tone or noise. In a tone instrument, when Wave-Y is set to 1, the note is a pulse wave, and Wave-X is its duty cycle. (X=1/2, Y=1) is a square wave, found on almost all sound chips. (X=1/4, Y=1) and (X=1/8, Y=1) are waves found on the NES's sound chip. Smoothly interpolating X while Y=1 performs a duty cycle sweep, an effect frequently used on the Commodore 64's sound chip. (X=0, Y=1) and (X=1, Y=1) are degenerate values that hold a low or high voltage instead of creating a wave; these are useful as interpolation endpoints, but not as values to hold. In a tone instrument, when Wave-Y is set to 0, the note can be a saw wave, a triangle wave, or a skewed triangle wave. (X=0, Y=0) and (X=1, Y=0) are saw waves (of opposite signs). (X=1/2, Y=0) is a triangle wave. Other values of X when Y=0 skew the triangle wave, with values closer to the extremes of 0 and 1 being closer to the extreme skew of a saw wave. In a tone instrument, when Wave-X is set to 1/2, the note is a balanced waveform that can be thought of as an amplified and clipped triangle wave, with Y controlling the amount of amplification and clipping and a square wave as the extreme case of amplifying and clipping. Mathematically, a tone instrument's wave is divided into four sections: a rising slope, a high hold, a falling slope, and a low hold. These are determined from the waveform's period (as determined by its pitch) and the Wave-X and Wave-Y values, obeying the following equations: rise+high+fall+low=period, (rise+high)/period=X, (high+low)/period=Y, rise*low=fall*high. The specific waveforms described above can be derived from these equations. Except for (X=0, Y=1) and (X=1, Y=1), every combination of Wave-X and Wave-Y produces a functional sound waveform. In a noise instrument, Wave-X affects the pseudorandom number generator used to create the noise. There are 17 different randomizer steps, and the uninterpolated value of Wave-X decides which step to use. The Wave-X interpolation checkbox has no effect in a noise instrument. If the noise seems undesirable in some way, a different Wave-X might produce a better one. Changing Wave-X over the grid duration will have no specific or consistent effect versus keeping it at a constant value. In a noise instrument, Wave-Y affects the quantization of the pseudorandom number generator's output. There are 17 different degrees of quantization, and the uninterpolated value of Wave-Y decides which to use. The Wave-Y interpolation checkbox has no effect in a noise instrument. From left to right, the Wave-Y grid quantizes the noise to 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, or 65536 levels. The lowest few values are the most audibly distinguishable. The final grid determines the behavior of a held note. If this grid is empty or only has one mark in it, then holding a note is no different from releasing it: the instrument runs through all its rows in order once, then stops. You can mark one cell in the left column and one in the right column. When both columns are marked, the song contains a loop section, which is visibly highlighted across the entire grid. To unmark a cell and turn off the loop, click the cell again. To test the instrument without going back to the main screen, select the "Test octave", "Test pitch", and "Test duration" then click "Test". To help you figure out how the instrument will sound within the song, the test duration specifies a number of pattern rows, not seconds or instrument rows. The note is released at the end of the duration, not abruptly cut, so you can hear the release section after the loop. If the note contains no loop, the duration doesn't matter. [BUG IN MAY 20 VERSION: Clicking "Test" when the duration textbox isn't a number may break audio playback; if it does, you should still be able to get to the song JSON, save the song, and reload the page.] Click "Cut" to stop the test note early. Click "Back" to return to the main screen from the instrument screen. ---- DATA FORMAT You don't need to know how the song JSON works, but it might not hurt. You might usefully edit the framesPerRow and framesPerSecond values, and you can save file space by deleting unused patterns and unused instruments. Deleting an in-use pattern or instrment might result in a song that can't load properly. Using the default blank song as an example, the JSON looks like this: { // the song as a whole is a JSON object literal "framesPerSecond":96000, // integer, required "framesPerRow":12000, // integer, required "rowsPerPattern":32, // integer, required "blocks": [ // array, required ["1A","2A","3A","4A"], // each block is an array of four pattern IDs // could have more blocks here ], // end of blocks "patterns":{ // object, required "1A":[" ", 31 more... ], // each pattern is an array keyed from its ID "2A":[" ", 31 more... ], // with rowsPerPattern 7-character strings, "3A":[" ", 31 more... ], // using the same characters as in the "4A":[" ", 31 more... ], // editable pattern grid. // could have more patterns here } // end of patterns "instruments":{ // object, required "0":{ // each instrument is an object, keyed from its one-character code "framesPerRow":1600, // integer, required "name":"DEFAULTBEEP", // string, "" means untitled "noise":false, // boolean "loopStart":0, // integer >=0, less than rows.length "loopLength":1, // integer>=0, 0 means no loop, start+length<=rows.length "rows":[ // array, required [0,0,0,16,8,16], // each instrument row is 6 integers for the 6 curve grids. [0,0,0,0,8,16], // Legal values for these 6 integers are [0,0,0,0,8,16], // [-4..4, -12..12, -10..10, 0..17, 0..17, 0..17] [0,0,0,0,8,16], // Unlike patterns, instrument row arrays don't have to [0,0,0,0,8,16], // be any particular length, and their length doesn't [0,0,0,0,8,16], // get declared anywhere else in the JSON data. many more... ], // end of instrument rows "interpolate": [ // array, required false,false,false,false,false,false // 6 booleans, one per curve grid ] // end of instrument interpolate flags }, // end of instrument 0 // could have more instruments here } // end of instrument list } // end of song ---- EMBEDDING This section is not intended to grant a license. Do not redistribute channel.js or song.js without express consent of the author. To get your song to load into a Javascript AudioBuffer, you need to include the song.js and channel.js files from the tracker. These are written as simple top-level browser scripts, and they'll need editing if you're in node.js or some other environment that expects modules. The only function you need to call for this is blocksToAudioBuffer. blocksToAudioBuffer(object song, int startBlock, int numBlocks, number gain, object audioContext) returns AudioBuffer object song is the object defined by the song JSON. Pass the actual object, not the character string of JSON data. startBlock is the block to start looping the song from. numBlocks is the number of blocks to play. If you're just playing the song once, pass song.blocks.length. If you want a guaranteed clean loop point, pass song.blocks.length*2. gain is a multiplier on volume levels. If this is too high, your buffer may contain values outside of -1..1. 1/4 is a good default; you might also have your code go through the buffer afterward to find a peak level and then normalize to that level. audioContext is an object with a createBuffer method. This is intended to be an actual Javascript AudioContext object, but it doesn't have to be as long as its createBuffer method acts like it is. This will return a buffer containing the specified amount of the song. This will be a stereo buffer even if your song doesn't use panning, and it will use the song's framesPerSecond value as its sample rate. For a clean loop, write out twice the song's length, then loop from the end to the halfway point instead of looping the entire buffer. This ensures that, if the last block has a lingering note that is continuing into the repeated first block, your loop will jump to the repeated version of the first block and not the initial version.