Stream-of-conciousness unorganized Development notes (no pun intended): =====Basic Design Goals===== - Write a PC tracker targeted for old/slow PCs - Initial output device support is the pc speaker - Design of tracker should be flexible enough to allow other output devices to reproduce the song (Tandy/PCjr, Adlib, realtime synthesis, etc.) - Design of data format should lend itself to a fast/efficient playback routine suitable for use in demos Again -- let's stress the fact that the first revision of the tracker is to support low-resource music playback in a demo. Later versions of the tracker can do more stuff, but for the first release, keep it simple. This tracker fills the void of supporting PC sound devices that nobody else wanted to support. For example, there are many FM trackers for Adlib, and traditional Amiga MOD trackers for Sound Blaster and GUS, but MONOTONE supports the PC speaker and will also support the poor stepchildren of the Tandy/PCjr 3-voice chip, the CMS 12-voice chip, and others without complex abilities. =====Terminology===== track = individual line of music data; more than one track = polyphony. channel = individual sound-producing device (ie. PC has one channel, PCjr has three, Adlib has 9, etc.) While both tracks and channels are represented internally with a count starting from 0 for programming ease, both are represented to the user as starting from 1. =====Implementation Concepts===== Tracker is "tuned" to an 88-key keyboard and everything that goes with it (A4=440Hz, equal temperament). Octaves are centered around C (C-1 through B-1, then C-2 starts, etc). For maximum acceptance with experienced trackers, keyboard note entry must match that of original Protracker, which Z and Q on C4 and C5 respectively. C4=note 40=261Hz Note index "0" is "null" and is used for the following: - note display: blank space - playback: "do nothing" Notes 1 through 88 are A0 to C8. Note index "127" is "OFF" and is used for the following: - note display: "OFF" - playback: turn the channel off (This is how you stop a note from sounding, since the initial release of the tracker does not support volume control due to the output device.) For the purpose of vibrato and portamento, there are 8 intervals between notes. Song file format can be 1 to 4 tracks. PC speaker default is 4 (any more and you can't make out individual notes in the arpeggio). Actually, most concepts/features were designed with the PC speaker specifically in mind so that's why many things are missing or limited or weird... Playback arpeggios engine works under a "last track priority" mode, ie. if a track is silent/no change, then the previous track continues to sound. (update: actually 2nd behavior "always arpeggiate" possible) Patterns start at 0 and go to maxpattern-1. Orders contain the particular pattern to play. If an order of maxpattern is hit, that's the end of the song. Each track can have the following info per row: note: (0-88) (0 = nul note, which means "no change"; 127 = note off) effect: (slide up, slide down, tone portamento, vibrato, pattern break, etc. are the only ones planned for now; limited to 8 total for version 0.1) Misc. note: We are only interested in the player's worst-case performance because we must optimize it to run in a few scanlines or else it is useless to us. =====Memory format===== suggested storage: AH AL FEDCBA98 76543210 nnnnnnne eedddddd -------- -------- nnnnnnn =note number (0-127) e ee =effect (0-7) (portaup, portadwn, tone port, etc. see below) 111222 =effect data (0-63, or 0-7,0-7) (that may seem weird but it's arranged that way so that the player can process notes as quickly as possible, using zero flags/tests, etc.) effects: 0 = no effect 1 = slide up, data=how many intervals per tick to slide 2 = slide down, data=how many intervals per tick to slide 3 = slide to note, data=how many intervals per tick to slide 4 = vibrato, params are fffaaa where f=freq and a=amp (both 0-7) 5 = pattern break (jump to row xxxxxx (0-63) in next order) 6 = arpeggio (follow amiga mod example) 7 = set speed (number of ticks until row advances) =====Playback Handling===== Playback engine basic concept: Playback engine is essentially a software-emulated tone generator with (in the stock PC speaker config) four channels with the following maintained states: 1. channel has a HZ 2. channel is either ON or OFF (ie. sounding or not) 3. channel has an EFFECT with EFFECTDATA that alters the Hz on every TICK (not row) 4. channel has HOUSEKEEPINGDATA and HOUSEKEEPINGDATA2 which is optionally used to process the effect and is typically checked on every tick. (typically a Hz to stop sliding to, or a looping index into vibrato sine table and an original Hz as a base, etc.) Playback sequence: Speed is set to a positive integer that represents how many ticks to go by before advancing to the next row. On player/engine start: - numticks=0; current channel=0 On every TICK (single-voice pc speaker only, others will differ) - if current track sets volume to off via "note" 127 - set channel to "not playing" - if current track has non-null note data - set hz to note data - set channel to "playing" - if current track has effect - process effect - set speaker - if channel playing, set to Hz value - if ALL channels not playing, turn speaker off - inc numticks - if (numticks >= speed) - advance to next song row - numticks=0 - advance audible channel to n+1 (or 0 if n+1>max n) =====Interface===== Planned tracker interface features: - track swap, copy, erase, transpose, octave - mute/unmute tracks while playing - restore pattern (dumbass undo) - play: - current row->end of pattern - entire pattern->end of song - entire song starting from beginning - manual step one tick at a time - entire pattern (looping) - need a SHUT UP key (mute speaker) Display will look something like this for each channel: nnn edd --- --- +++------Note ("C#5", "G-3", "OFF" etc.) +----effect ++--effect data Effects and data will display like a single hex digit except the interface will automatically adjust the input to match the capabilities (usually with a shift op). example: A#4 --- (note on) G-2 462 (note, with vibrato freq of 6 and amp of 2) OFF --- (set "volume" to "0" to turn note off) full display: ÕÍÍÑÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÑÍÍÍÍÍÍ͸ ³rw³nnn edd³nnn edd³nnn edd³nnn edd³ ÃÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄ´ ³00³--- ---³--- ---³--- ---³--- ---³ ³01³--- ---³--- ---³--- ---³--- ---³ ³02³--- ---³--- ---³--- ---³--- ---³ ³03³--- ---³--- ---³--- ---³--- ---³ ³04³--- ---³--- ---³--- ---³--- ---³ ³05³--- ---³--- ---³--- ---³--- ---³ ³06³--- ---³--- ---³--- ---³--- ---³ ³07³--- ---³--- ---³--- ---³--- ---³ ³08³--- ---³--- ---³--- ---³--- ---³ ³09³--- ---³--- ---³--- ---³--- ---³ ³0a³--- ---³--- ---³--- ---³--- ---³ ³0b³--- ---³--- ---³--- ---³--- ---³ ³0c³--- ---³--- ---³--- ---³--- ---³ ³0d³--- ---³--- ---³--- ---³--- ---³ ³0e³--- ---³--- ---³--- ---³--- ---³ ³0f³--- ---³--- ---³--- ---³--- ---³ ÔÍÍÏÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÏÍÍÍÍÍÍ; ...etc. Possible key layout: up/dn/lft/rit - navigate the pattern home/end - beginning/end of pattern pgup/pgdn - prev/next pattern keypad +/- - =====Program structure===== Trixter's first OOP program, hope I don't screw this up massively: song object: song data in/out, very traditional except for one function to burp out a pointer to the requested row so that the player can move as fast as possible. Keeps track of current row/track/pattern location data; editor can set or query that location player object: calculates channel freq/vol from song. Also takes "commands" (like, sounding out just one note or track) which it also converts into freq./vol./etc. data just like calcing a single row. outputdevice object: (abstract) pulls calc'd song data from player, sets hardware directly to play sound screen object: (abstract) updates screen pages, will descent to trackerscreen, songinfoscreen, helpscreen, playerpianoscreen, etc. must have update and show, should use existing screen ram if possible tracker (editor) object: main editor interface, gets user input, acts on it will keep track of which screen player is on to maintain focus, etc. player object should be able to request a row of song data outputdevice object should be able to request the channel data it needs to make sound Every 1/60th of a second, outputdevice needs to call player.calcrow to get a new set of channel data. How outputdevice does this is up to outputdevice. Most of the time, !!! MT format conventions: Effect data calls into two camps: two individual hex digits for two params, or one hex number for one param MT song format only has enough bits to store 3 bits per nybble, so how to handle input and display?!? Answer: for dual param effects, clip/saturate each nybble before storing for single par effects, clip/saturate 8-bit input to 6-bit DO NOT SHR ON INPUT AND SHL ON OUTPUT, that will only confuse everyone!! all input is done through "events". 99.9% of events will be from the keyboard, but hey, I've got joysticks and a mouse hooked up to this thing... misc. completely random implementation thoughts: Before initalizing the player, the editor needs to query the number of tracks in the song and the number of channels available in the output device and init the player using the LESSER of the two. This will solve a lot of problems (can compose with more tracks than you have available to play; won't have to worry about how to map tracks to channels (it will be 1:1), etc.). keyboard mapping: one possible way is to implement array 0..255 of scancodes and 0..15 of keyboard flags and map that to an action; that way it's a simple lookup but would take 4K :-P Could use a linked list but that would require scanning through the entire list on every keypress; could use a binary tree to cut down on that... but maybe better way is order a lookup table based on the action, which would be about 66 actions so that would be an array scan on 132-byte array which is fast but has the downside of only allowing one key per action. update: decided to go with last idea; people can just deal with one key per action. Default key layout thoughts: - pgup/pgdn should match protracker, ie. 16 rows at a time. In fact, all navigation should match protracker where practical. note input MUST match protracker (Z=C and Q=C+oct) or else nobody will bother using it. - shift-ins and shift-del for inserting/deleting rows - O=off, < and > lower/raise default octave (displayed on tracker screen) keyboard config util must Artificial volume envelopes should be incredibly easy; just have a table of 0-15 volume steps and let user "draw" the envelope (use bank street music writer for inspiration). Envelope can have 64 steps; can also have a speed for how quickly index pointer runs through it. Once pointer hits past the end, doesn't advance any more, but volume is left playing at last level of envelope (or, envelope can be marked as looping and then pointer can be ANDed after INC which will loop). "Genius!" "Thank you!" To make the user docs, load Microsoft Word for DOS and use it to generate the ASCII dox!! (does word for dos support line drawing?) =====Development milestones===== X initial framework init X basic user input X define list of user-requested events (switch to help screen; move up; etc.) X write userinput object (function GetUserInput:type or something) X small object that maps input to requestedactions X input object should be able to save/load state X uses keyboard object X use all of the above to write keyboard config utility X basic user output X abstract screen object with init, clear, relative positioning, show, text output X specific screen objects for each screen (help, tracker, etc.) X same object must be able to pop-up a window then restore after ack. N static helpscreen uses thedraw? X deliverable: X switch between various screens using a mainloop input dispatcher X (keep track of which screen has focus) X init song, add some notes via song.SetNote code, display song X keep track of default octave X navigate song in tracker interface using song.moveto and repainting (also implement pattern switch) X enter notes via tracker-like interface X song save/load routines - start/stop current pattern play (simple speaker only) - implement orders (on tracker screen, use tab to switch) - alter player to follow song order list Optional (highly recommended): X use state load/save to write keyboard customization screen - write "player piano" screen - add support for joystick object - add support for adlib, tandy/pcjr, cms, others.