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.) =====Implementation Concepts===== Tracker is "tuned" to an 88-key keyboard and everything that goes with it (A4=440Hz, equal temperament). 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. Patterns start at 1 and go to maxpattern. Orders contain the particular pattern to play. If an order of 0 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: (5: slide up, slide down, tone portamento, vibrato, pattern break are the only ones planned for now) effect data: ?? There are commands that alter playing Hz through effects, setting new note, etc. but these results are not cached. That's because it is not critical that the playback routine take as little time as possible, but rather it is more critical that we know how long the player routine takes. 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 nnnnnnnv dddddeee -------- -------- 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 (0-7) and a=amp 5 = 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: - 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 (may alter hz) - set speaker - if channel not playing, turn speaker off - if channel playing, set to Hz value - Hz is converted to timer ticks with DIV every time b/c we never know if we're sliding or something. We could optimize by caching results of prior DIVs, but complex songs will not benefit from this and we only care about optimizing the worst case since we only have a fixed amount of time to execute in. - 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... =====Development milestones===== - initial framework init - basic user input - define list of user-requested events (switch to help screen; move up; etc.) - write input object (function GetUserInput:type or something) - input object should be able to save/load state - basic user output - abstract screen object with init, clear, relative positioning, show, text output - specific screen objects for each screen (help, tracker, etc.) - deliverable: - switch between various screens using a mainloop input dispatcher - (keep track of which screen has focus) - init song, add some notes via song.SetNote code, display song - navigate song in tracker interface using song.moveto and repainting (also implement pattern switch) - enter notes via tracker-like interface - 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): - use state load/save to write keyboard customization screen - write "player piano" screen