MoonSynth Development Diary

Mimu / MoonCore

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
After spending some time listening to midi music using my beloved crusty and
dusty Sound Blaster 16, I came to a conclusion. Proper FM synthesized music
beats up lousy wavetable midi music any day. I've heard a few samples of what
a fabled Roland GS synth sounds like, and I was impressed. I myself have also
a genuine MT-32, courtesy of my kind uncle who had little use for it, and by
this too I was impressed. The reverb is dreamy and the sound is half FM and
half wavetable (if I understood the technology correctly). My only gripe is
that the preset Bell sound is horribly offkey, but then for some reason
disturbingly many sound cards have an offkey bell... or something's wrong
with my ears. In fact, recently I've even imagined hearing some detuned
sounds from the Microsoft GS synth...
  Sadly the MT-32 can only play 8 channels and the rhythm channel at any one
time, with a maximum polyphony of 32 voices, I think. Not enough for all midi
pieces I have. Also, the MT-32 isn't built following the General Midi
specifications so some instruments are totally wrong when trying to play GM
files. The SB16 likewise has restrictions on the polyphony, although thanks
to the neat Voyetra Super Sapi FM driver the card behaves rather well under
Windows environment otherwise.
  Next, I have this Sound Blaster PCI64 card. It was cheap, and it gives nice
and clear wave output under Windows. The midi instruments are bearable too,
although the percussion is too loud. It has legacy emulation for an Ensoniq
Soundscape, and the older SBPro. Ironically the Sound Blaster emulation
sucks. I don't know what kind of chip they have on that thing but the FM
sounds it produces could almost be midi instrument approximations of what an
OPL-chip is supposed to sound like.
  The Yamaha XG SoftSynth that came with Final Fantasy 7 produces very
credible sounds. The balances feel good and instruments believable. This
would be my choice for realistic midi music production.
  And I've got Timidity++ too, with EAW Patches. A software wavetable midi
synthesizer, using freely choosable GUS patches. The general feel is almost
as good as with the XG synth. However, I'm sort of fond of surreal,
synthesized sounds. Seeing as I'm not aware of any simple midi player that
can produce neat sounds with high polyphony and an oldskool interface, I
figured I'll make one myself.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

While working on this project, I found the following sites which had some
useful or at least interesting information on DirectSound.
  If you are in need of programming information on DirectSound, this handful
of links might prove handy.

Streaming Wave Files with DirectSound

the DirectX eXperience

The Enilno project used to have nice tutorials on lots of things including
DirectSound, but those seem to have been taken down.

tom@work ; two Free Pascal programs with sources, demonstrating DirectSound

MicroSoft's own DirectSound stuff... I wish it was more useful.

Exploring DirectX 5.0, DirectSound... kinda like this.

Searching on Google for "windows ddk" gives information on how to do device
drivers... useful for later date.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
First attemps
Rummaged around Win32 API documentation and the internet, found some
information on using waveOutEx for sound output. My first tests finally
resulted in some noise from the speakers and Windows crashing; not the
optimal outcome. Wrote down a scheme on channel management and planned
features. Moved to new projects soon after.

191102, Tuesday
Picked project up again, starting from scratch. The plans should be lying
around somewhere, I may need to clean the room a bit... Searched the net for
more information, decided to use DirectSound this time. Found an example
program for Free Pascal that can play .WAV files using DS. Trying to make
sense of the code and an article about Streaming Wave Files with DS...

201102, Wednesday
Tried not to sleep late, did so anyway. Then continued research and
development powered by a plate of fries.

211102, Thursday
03:00 - Wondering where El is. Where will I scavenge inspiration from when
      she's not around? Managed to put together a test program that outputs
      random noise at the desired format. Windows hasn't even crashed
      a single time! :o
13:00 - After a nourishing breakfast and some relaxation I'm starting on this
      again. I'll see if I can get the read/write pointers and display them
      now.
14:00 - The thing only returns a 0:0 pair for the pointers. For some reason
      now the program won't run at all, claiming a problem in creating the
      primary sound buffer. Same thing with secondary buffer. The returned
      error code is apparently "Invalid Parameter". I don't think I changed
      anything in that part of the code though, to cause this. I did change
      the DirectX compile version to 3, from 7. I've only got 4 installed on
      this comp anyway so I'm not sure why 7 worked in the first place. Now I
      think I'll reboot and see if it works better then. Btw, Winamp also
      reports inability to create a sound buffer when trying to use DS
      instead of waveOut. The other example program still works like always.
14:20 - Ok, scratch that. Nothing works now. A recompile of the example
      program at every DX version gives the same result, an invalid parameter
      error. The error Winamp reports is the same. I think I broke something
      in this system... and for some reason installing DX5 does nothing at
      all, the old drivers remain even after reboots.
14:50 - Hmm, so I had DX6 after all. The version numbers just said 4 so I
      thought... anyway, some drivers were from DX5 while others from DX6, so
      just to be sure I installed DX7. Now Winamp actually manages to play
      using DirectSound, and the example program works too. I set the DX
      compile version to 5. My own test program became mysteriously muted,
      but at least it doesn't give any errors.
15:00 - Actually it does give an error, I just forgot to check for it. Says
      we don't have the priority level needed to use the Play function. :p
17:55 - After an afternoon nap and a warm shower I feel refreshed and ready
      to make noise. Except that now El appeared and has had two rough days.
      Honor priorities dictate programming must be secondary for now.

221102, Friday
19:00 - Slept through all day, El disappeared as her comp broke down. Not
      feeling like programming now, so I'll do some Final Fantasy instead. ;)
19:30 - Or I would, if this comp had a few more MHz in it. Not enough power
      for smooth SNES emulation... Fine. As an avid reader of the 8-Bit
      Theater I'll go for FF1 instead of 6. :p
21:55 - Saved Mysterious Princess Sara from the clutches of the archnemesis
      Garland (you impertinent fools! I, GARLAND, will knock you all down!)
      and rescued a town from pirates... that's enough for now. It also
      appears that the SNES emulation didn't work properly because of the
      sound card, not because of lack of processing power. I guess my SB16
      never was intended to be used with DirectX so some modern DX init
      algorithms will result in the sound quality getting messed up and the
      comp slowing down to a crawl. Apparently creating the primary buffer is
      required to prevent this, while for more modern cards the buffer is
      already there and ready for use.

051202, Thursday
22:50 - Whoops, forgot about this project for a while. But well, I was doing
      another audio project, still not finished. Anyway, I'll try to see if I
      can figure out why the secondary buffer play function dislikes my
      priorities...

061202, Friday
01:40 - Aha! As I thought. I'm setting the priority level using the wrong
      handle. That's what I get for being novice in win32 programming. And I
      had the command for getting some sort of usable handle in that other
      program I had, but the sources for that are on the other HD which I
      can't access now, which is why I did this mistake. Now, I'll see if I
      can somehow get the read/write pointers from the buffy...
02:10 - Gave the test program to El, she says it works too. Produces
      unbecoming noise in full stereo. I guess I'll see more about this later
      on, now I'm feeling too tired.
20:00 - After sleeping way too late and a refreshing bout of WarCraft 2, I'm
      feeling like getting into programming again. Let's see then, where was
      I this time?
20:10 - There we go. Now I'm getting the pointers, hopefully properly. The
      test program shows the play and write pointers of the secondary buffer
      when I press enter. I am slightly confused by that if I get the values
      a few times with only a short delay in between, the pointers remain the
      same, as if no sound was played between the keypresses. I'm supposing
      this is due to the messaging nature of the OS, it maybe queues a bunch
      of keypresses and processes them only every once in a while, and
      possibly the pointers are also updated only along with the primary
      buffer a few times per low-level buffer loop, or something. I guess the
      next thing to do is to see if I can begin filling the buffy while
      playing it, instead of the pre-calculated noise.

071202, Saturday
03:00 - Ok, I have a functioning callback function. Or at least it doesn't
      crash the comp. I used the timesetevent Windows multimedia thingy, and
      told the system to call my procedure every 250 milliseconds, while the
      sound buffer is consistently a second long. I read somewhere that
      during a multimedia timer call, calling most functions will result in
      a crash. DirectX calls are exempt, likely. I'll see if I can alter
      a variable in the tentatively named MIXER function...
03:10 - Whee! I tap enter about once a second and it shows numbers
      incrementing by 3..5 each time depending on my personal timing
      resolution. :D
03:30 - Wowie, still no crashes. The output is 16-bit stereo at a juicy 44100
      frames a second. This means the buffer size is, um... 176400 bytes. The
      amount of audio to mix to keep up with the playing is pretty
      consistently 49k bytes, occasionally 32k, and at some hiccups the
      program gave even 16k and 64k but it evens out nicely. No errors appear
      to occur now, and the mixer procedure hasn't been called a single time
      if it still was mixing, which means the procedure is sleek and swift.
      It only calculates how many bytes of audio need to be mixed so it
      figures, really. ;)
03:50 - Weird... I tried to lock the buffy for writing sound to it but the
      program gave an access violation error after making some noise. This,
      even though I included error checking all along so it should've exited
      gracefully in case something went wrong. But now as I try it again, it
      seems to work. I'll see if uncommenting the actual noise-writing lines
      causes another violation...
04:00 - Hmm. I must be tired. I thought an audio size was an offset. It still
      doesn't work any better though.
04:05 - Finally it works properly. I must be really tired. I thought the
      pointer dumbly referenced the beginning of the buffer while of course
      it pointed directly at the offset I had already specified...
20:00 - Hacked up a preliminary version of the QfG4 finale audio show. I'll
      need to redo two or three of the hero's lines and pester El into
      recording the Erana lines. And the midi music needs tweaking so it'll
      fit in perfectly. At any rate, I think I've done enough work on that
      today so now (after another refreshing WarCraft 2 session) I think I'll
      see about my cutesy little synth program.
20:40 - Ok, proceeding nicely. A few more lines of code and I think I should
      be able to play some monotonous music with square waves...
22:15 - I can now synthesize a square wave at runtime and at varying
      frequencies although the melodic ranges aren't defined. However for
      some reason the output isn't smooth, there are pops now and then. The
      same routine is capable of producing smooth noise though so the problem
      must be in my square wave generator code.
23:20 - Yes, I am dumb. Noise is always smooth because it's nothing but pops
      and crackling. Anyway, still not sure what's causing the unevenness in
      the sound output.

081202, Sunday
01:30 - Can't figure it out now. I guess something might be wrong in the
      memory copy routine, will have to test that using a typed array memory
      referencing and pull the procedure apart.

261202, Thursday
22:30 - Lack of inspiration. Now I am taking a new look at the program again
      though. It appears that the break in the sound happens every time the
      secondary directsound buffer loops. I deduced this by changing the
      buffy size from 8k to 32k and witnessing a noticeable change in the
      audio crackles.

271202, Friday
00:05 - I appear to have also done an estimation error which caused the
      intended triangle wave to become a 1Hz wave instead of a few hundred or
      so. At any rate, preliminary tests produced clean sound again.
00:40 - Next tests didn't show as positive results. Presetting the buffer
      worked fine and gave smooth sound, and cutting the sound seemed
      to work perfectly in realtime. Trying to produce a little triangle wave
      in realtime using a steady tick counter worked smoothly. However as I
      try to generate square waves in a bit more complex loop, the sounds
      become as if there were several layers playing. The distortion got
      worse as I made the buffer timer polling less frequent, so that's
      probably somehow connected... maybe a rollover bug? Changing the
      playback frequency and stereo/mono had no discernible effect.
01:10 - Nope, producing anything results in audio artifacts. Still can't
      figure out why. No rollovers seem to happen either.
01:15 - In fact I'm beginning to suspect the problem is a DirectSound
      compatibility one. There was mention that older sound cards don't work
      perfectly with that thing. I'd need to stick in some other card than my
      trusty SB16 to make sure this isn't the problem...
13:00 - Ah, now I'm getting somewhere. The program appears to mostly do just
      about 300-byte mixes, occasionally getting a 20000-byte mix. The sound
      that is being played changes accordingly. Something is clearly wrong
      with either the pointers that are processed or my calculations on how
      much to mix.
13:20 - Yep. For some reason or another, Directsound happily hogs huge chunks
      of the buffy for playback, leaving my program little room to maneuver
      in. I still assume it's because of my older soundcard. DS probably uses
      a default mixing scheme which means internally using fixed-size double
      buffering or something. If I use a buffer size of a second, the
      playback becomes garbled. Two seconds gives smooth sound. My timer
      routine's been ticking away at 100 milliseconds all the time anyway.
      But this really sucks! I mean, two seconds?? We can forget about
      playing a synth instrument in realtime on the keyboard for free
      improvising... way too much lag for that. I wonder if there's a way to
      reduce the mixing size to something below half a second...
13:25 - Latency. It's called playback latency. Ok.
16:00 - And for some reason the getcaps function of the Directsound buffer
      interface doesn't want to work, so I can't even get info on the size of
      the created buffers. Invalid Parameter, it says, which could mean
      absolutely anything is wrong. Also no help was found on the net, the
      best relevant advice I could find was, if sound skips or is garbled,
      increase the buffer size, which is exactly what I did. Supposedly
      Directsound should be capable of 20ms latency, or in the worst case it
      should still be able to deliver below 200ms. But it's not actually
      about my computer not being fast enough, I think. It's about DS using
      too large transfer blocks. If I could tweak it to use smaller blocks, I
      certainly could still keep filling the buffer smoothly enough and
      everyone'd be happier. *grumble grumble*
16:20 - That was extremely stupid on behalf of whoever made DS. I had to fill
      in the size of the DSBCAPS structure myself before asking the getcaps
      method to fill in the information... I mean, seriously. That is just
      plain idiotic. And the overly helpful MSDN Library resources made no
      mention of this whatsoever.
16:30 - Right, I made a tiny mistake. A one second buffer is enough for
      smooth audio output. Due to the doubling caused by 16 bits the bitwise
      shift left of 1 doesn't make it two seconds. But even a second-long
      delay is too much for keyboard improvising. Halving the buffer size
      causes problems already, and it'd still be half a second long, which is
      still too large. Of course, I'm creating a music synth that primarily
      plays midi files so this is alright. I'm just a bit annoyed, since I
      like playing tunes myself.
16:50 - Tweaked the buffer size to two thirds of a second, and a 50 ms timed
      callback. Seems to be the lowest latency I can get.
17:55 - Upon virtual memory swapping this buffer size may be a bit too small.
      Oh well. I painstakingly calculated a bunch of frequencies for free
      improvising using the keyboard, used 32-bit fixed point precision and
      did hexadecimal conversion on 29 numbers... and they went in the wrong
      way around, or something. Didn't sound quite in tune either, in
      addition to the keyboard being inverted. I guess next I'll have to come
      up with a way to represent note and instrument info... now where were
      those original plans of mine?
18:35 - My ingenious plans are forever lost... since they weren't in any of
      the more obvious paper stacks, I'll assume it must've been one of those
      things I wrote on chocolate bar wrapper paper and threw away somewhere
      along. So... let's start over.

::: moonsynth SECRET PLAN rerevision X :::
Since I'm aiming for midi playback, how do I represent the notes? Midi has
note values from C-0:0 to G-10:127. The pitch wheel goes from 0 to 0x03FFF,
with recommended melodic range being from -2 semitones to +2 semitones,
though that's adjustable. Curiously note A-5:69 is the famous 440 Hz.
  I guess I'll use a similar system as I did in MoonTracker, only a little
finer. Each playing instrument will be given a single plain frequency and
it's up to the instrument definition to produce something fitting. There will
only be a limited number of frequencies available, and the total number of
different frequencies must fit in 16 bits, though the less frequencies are
allocated, the less memory will be spent on just the precalculated frequency
table. Not that it matters much.
  I'll give 32 finetunes per semitone. Internally C-0 will be note 16, and
C#0 will be note 48. I'll calculate a hardcoded table by hand with the
frequencies of one full octave, and at program initialization will use that
table to calculate the other octaves into a full frequency list, with all the
128*32=4096 frequencies. 16 bit precision won't be quite enough, since G-10
is defined as 12543.9 Hz which requires 14 bits, leaving only 2 bits for the
fractional part which isn't enough for the lowest notes... so each frequency
will be stored in 32 bits with the lower 16 bits being the fractional part.
The midi note numbers are easily translated using the equation ...
Internal Note = MIDINOTE * 32 + 16.

There are 16 physical channels. I'll allow up to 128 samples playing at the
same time. Each channel has a pointer to the first sample playing on it, and
each sample has a pointer to the next sample on the same channel. The last
playing sample has value 0xFF. If the channel has nothing playing, it already
points to 0xFF. The chain is easy to change when notes are cut off in the
middle.
  Seeking a free sample number for new notes is done by keeping a counter
that goes from 0 to 127, picking the first free sample slot for the new note.
The counter never resets, it only loops. This way there's always an almost
guaranteed free sample slot quickly found for use. If over 128 sounds are
to be playing at once, some sort of advanced routine should be written to
choose one of the channels to cut off.

Each instrument can have, call it, 8 oscillators. Each has its own variables:
Waveform, frequency, amplitude, frame, value, FMO, AMO.
  Waveform can be sine, saw, triangle, square, or some other more creative
    type that I will precalculate. Also available should be a realtime noise
    for which you can also define a delta.
  Frequency is the frequency of the oscillator in hertz. The precalculated
    waveform will be linearry interpolated to fit this. For the noise, this
    value will be the minimum delta for each frame of noise. Deltas below
    minimum will be averaged with the given minimum.
  Amplitude of the oscillator, 0 to 65536. This allows overamplifying to 2x,
    which will result in some clipping.
  Frame is the number of frames that have passed while this sample has been
    playing. Useful for attack/decay things.
  Value is the variable where the value of the oscillator is stored. This is
    used for modulating the following oscillators, mixing the oscillators for
    the instrument sound, and checking the deltas for noise generation.
  FMO points to the oscillator whose value modulates the frequency of this
    oscillator. Can only point to an earlier oscillator, so oscillator 1
    can't be modulated. Every 32 amp in the oscillator is a semitone's
    modulation in the frequency.
  AMO points to the oscillator whose value modulates the amplitude of this
    oscillator. Can only point to an earlier oscillator, so 1 can't be
    modulated. Is a percentage modifier, with -32768 being 0% and 32767 200%.

The instrument has an action string. It comprises of short commands that
define how to modify the variables of the oscillators. The string is in plain
ASCII probably, though I'll need to use a special pointer for it to make it
bigger than 256. 65000 sounds like enough chars for any instrument.
  Commands beginning with 1..8 alter the oscillators.
  1w* : sets waveform to *.
  1f***** : sets base frequency to *****. Internally the number will be
    converted to a straight word.
  1a***** : sets base amplitude to *****. Internally a straight word.

Other commands are instrument-global.
  S : sets the mixing string of the instrument. This is what is used to add
    together the desired oscillators into an output stream.

050203, Wednesday
23:00 - Been a while since I wrote here. I haven't just rested on my laurels,
      though. I wrote another revision of the moon synth plan and I think
      I'll need to write it down coherently here soon enough. I also did more
      work on the code, and found out something unsettling. It started nicely
      enough... I calculated some frequencies and changed the mixer routine
      to work a little simpler. I also made it produce a saw wave instead of
      a square wave. Even more importantly, I added the capability to play
      multiple channels. Thanks to the frequencies I calculated, I could then
      play some simple songs with chords, though the playback latency was
      annoying. But this also revealed that even the highly simple routine
      that is in place now, takes too much processing time. I don't quite
      understand why, since it doesn't even do anything much. But, trying to
      play several notes at the same time resulted in sound breaks due to the
      mixer not keeping up, and reducing the mixing frequency to 22kHz or
      below cured the breaks again. Of course the routine isn't
      hand-optimized in assembler yet, but it shouldn't be that heavy even
      so... perhaps some memory is swapped back and forth by the operating
      system and I need to lock it to reduce overhead? Also, I've been using
      a multimedia timer to call the mixer, but that could be changed to
      a properly DirectSound-based buffer position notification thingy. I'm
      not sure how much it would help but it's worth a try. :)

100203, Monday
00:00 - So, feeling listless and desperate, I dug up the DirectSound
      information sources again and tried to make sense of the notification
      system. In fact, I managed to incorporate the thing surprisingly
      easily. I removed the multimedia timer, and happily also can now remove
      the mmsystem unit from the program completely. I also created
      a separate thread to run in the background of the actual program, that
      handles the position notifications. It does nothing, except sit still
      and wait for DirectSound to say one of the magic offsets has been
      reached. Then it calls the mixer routine. The amount of blocks the
      buffer will be divided into can be set from 1 to 8 currently, although
      that could be increased. And what was the result?
00:05 - The result is that the sound still skips as it did before. Now it
      just does so less chaotically, since the breaks come at even intervals
      thanks to the position notification system. However, and this is very
      interesting... when I added one extra line in my main program loop,
      that just prints a ! on the screen every time the loop finishes, the
      sound became almost perfectly smooth. Removing the line made the sound
      choppy again. I do not understand this. Unless it's that accursed CRT
      unit... if spending more time in my main loop helps, I could suspect
      that the less often one time-vacuum function is called, the more
      processing time is left for everything else!
00:10 - I have a line that says... if keypressed then com:=readkey; in my
      main loop. Due to the stupid nature of Windows, translating the
      keypresses takes a lot more time than the far better implementation
      that was available under DOS, namely two simple assembler instructions.
      Of course it still seems weird how that could eat up so much time, but
      it's the first thing to blame I can think of! And this is easy enough
      to test. I'll just comment out the call and add a counter to allow
      a timed exit from the program instead of user-invoked.
00:20 - Fine, so that wasn't the culprit. Printing the !'s got almost twice
      as fast without the keypressed/readkey combo, but there still was
      a tiny break in the sound. Furthermore, removing the !'s kicked the
      sound back into sushi slices anyway. Let's see if some debugging output
      will shed any light on what is going on...
00:25 - Never mind debugging output for a moment longer. I simplified the
      notification handler procedure a bit, which had no effect, and then
      gave a try to something that had been in my mind for a while. In my
      mixer procedure, you see, I do the following things:
      1. Call DirectSound to see which part of the buffer is being played
      2. See how far ahead it is from our mixing offset, and if there are
         over 16 bytes, then proceed, otherwise break right off.
      3a. Loop through a temporary mixing buffer, doing 3b for every frame
      3b. Check every channel, and if the channel is playing, generate the
          saw wave and mix it into a variable; after all the 128 channels
          have been checked, write the variable into the temporary buffer.
      4. Lock the actual DirectSound secondary buffer which is played, and
         do a memory transfer from the temporary buffer to the real one, also
         remembering to wrap around properly, then unlock.

      Simple enough, right? There's also a flag that is set when the mixing
      routine is crunching, and it creates an overcall error if the routine
      is called while the previous call is still being processed. This error
      has never occurred despite the choppiness. We can deduce from this that
      if overly long is spent in the mixer procedure, then for some reason
      the procedure just doesn't get called again even if a notification time
      passes.
        But output had always been nearly perfectly smooth, until I made one
      add, which was step 3a. It still doesn't make sense though. All that
      that step actually does, is check 128 times if a "playing" flag is set.
      However, making the loop go through only one or two channels again
      reduced the chops to the bare minimum. This despite that the actual saw
      wave generating and mixing is in both cases done only once.
        Besides, if adding a line in the code that actually wasted processing
      time for printing exclamation marks made the sound smoother, I'm pretty
      sure that processing time isn't the issue here. What it actually is,
      then, I have no idea whatsoever. I doubt it's the DirectSound locking
      and unlocking calls either, since those are only called a few times
      every second. I don't, I just don't get it...
00:45 - Ok, now for debugging information. So to speak. That's just
      1337-speak for checking a few variables at runtime, you understand. So
      when I speak of checking debugging information, I actually mean
      analyzing a mindless string of variable snapshots. With that out of the
      way, I can make a brilliant observation from looking at the figures.
      The otherwise smooth sound only says a chop when over 30k or so bytes
      have been mixed at once. 40k and 50k resulted in a nastier aural
      artifact. My buffer is 88200 bytes large, so this means that as long as
      the program can keep up and at most only a quarter of the second-long
      buffer is mixed, the output will remain smooth. But why is it like
      this, since the amount to mix should be almost constant? I'm going to
      bed... if this turns out to be an amateur error on my part, I'll
      dislike me and punish the mangy critter appropriately.
18:05 - Reformatted this diary thingy a bit, making it prepared for
      publishing. I also did a little more testing with the program to see
      how the mixer is being called. Every time DirectSound makes
      a notification, it calls my short handler procedure. The procedure
      writes a symbol on the console screen every time it is called, and it
      then calls the mixer procedure to make noise. The mixer procedure then
      calculates the amount of audio to generate, and prints the number on
      the screen before proceeding to the mixing.
        =|=22658=|=33072=|=66144=|=21984=|=33072=|=55120=|=88192=|=77104 ...

      The buffer is still 88256 bytes large. The notification points are at
      22060, 44120, 66180 and 88240. Therefore the mixer should be called
      four times a second, and the mixing size should be a constant about
      22060 bytes. Notice also that =|= is never repeated without the mixing
      size, so the handler procedure just is not called often enough.
18:20 - Changing the channel amount from 128 to 1 again, to smoothen the
      sound, and running the same test resulted in a steady string of calls,
      with the mixing sizes being 33k at most. The somewhat varying sizes are
      likely explained by the chunk-style input of DirectSound.
18:25 - Changing the channel amount back to 128, and disabling the mixer
      calls from the handler, instead calling the mixer in the main loop,
      gave the results I'm trying to aim for. Since the main loop rolls
      through at the highest speed the computer can muster anyway, this means
      the mixer is called as often as possible. The output was a steady
      stream of a bunch of zero-length mixes, dotted now and then by a 11024
      mix. The sound was perfect. The extra zero-length mixes that were
      abundant also suggest that plenty of processing power is not used.
      Making the system play as many channels at once as possible, the mixes
      jumped to a steady 32k and the sound was broken, but this is already
      understandable in terms of processing power. Comparing to my
      non-optimized code for MoonTracker, this comp can't play more than
      maybe 8 channels simultaneously smoothly. Optimizing the mixer would
      probably triple this, or even better.
18:40 - Keeping the channels at 128 and replacing the mixer calls in the
      handler, but adding printing the !'s made the sound pretty smooth, but
      not perfectly without breaks. The exclamation marks overwrote the
      mixing size numbers but from what I managed to see, the mixing seemed
      to be around 22k, with the breaks probably occurring if the size went
      notably above that.
18:45 - Doubling the notification offset amount from 4 to 8 had little
      effect. Also, adding a conditional statement to only print !'s when the
      program is not doing mixing resulted in as many breaks as ever. It
      appears that if the main loop is printing !'s -while- mixing is
      happening, things happen somehow faster and the next notification call
      comes on time.
18:50 - Yes, altering the conditional statement to only print !'s when the
      mixing is happening in the background restored smoothness. I wonder
      what the priority level of the callback procedure thread is...
19:15 - Priority levels are normal. Maybe I could tweak the thread priority
      upwards a bit. Also, I notice that the function TerminateThread is
      apparently dangerous to use and I should use ExitThread instead. Too
      bad that gives an access violation when I try to use it.
19:35 - Now the handler thread which calls the mixer is at "highest" priority
      level. It actually helped a bit, the sound got smoother even without
      the exclamation marks and at 128 channels. It still did around 40k
      mixes on average, which is too much. More disturbing is that the
      program has mysteriously started giving access violation errors
      randomly upon exiting. I suspect that either printing stuff on screen
      from the mixer procedure causes this, or possibly my thread isn't
      terminated properly. I suppose the thread should exit first, and
      terminate afterwards, and only then release the thread handle. I'll
      take a look at this.
19:45 - I removed all the screen writes after the initialization information
      ones, and an access violation still occurred after a few tries. Then I
      added a line at the shutdown procedure, commanding it to wait until the
      "mixing" flag is clear before murdering and burying the handler thread.
      Two dozen tries later still no violations. I think what's happening, is
      that when the mixer procedure is called, it gets the normal return
      address to which it should jump after finishing. The return address is
      within the codespace of the handler procedure from where the mixer was
      called. Since the thread is terminated, the procedure is also lost, and
      trying to jump there to continue program execution is a bad thing,
      because there's nothing left there to execute. See? I'm not totally
      ignorant anymore! :p
19:50 - Also worth noting... nothing is printed at runtime from any routine,
      and now sound playback is almost smooth, even with 128 channels. So I'm
      assuming that using writeln functions while I'm supposed to be mixing
      is disliked by the system. I know, under GO32 in DOS I couldn't do it
      either but I figured Windows might be smarter. Goes to show, never do
      positive estimations of Windows. :p
19:55 - I increased the notification point maximum amount to 16 and tried it
      out right away. It gave faultless audio. I'm sooo happy. ^_^
20:00 - I also restored the capability to play using the keyboard. Of course
      the buffer is a second long so latency is heavy, but it still worked.
      Playing a single channel at a time gives good results, three or four
      may result in a break, and five or more break often. I suspect that the
      mixer calls still don't take place immediately enough. But now I'll go
      to sauna, and after that I hope El's back so I can make her test this
      thing too. :D
21:20 - Back from sauna, and trying to make El test the program. :)
21:30 - Weird... having the program run in the background makes typing in an
      IM window very lagged. In fact it makes all other actions notably
      slower. I guess that's what happens due to the higher priority level.
      I suppose I'll have to make it normal again, this lag is a bit too
      heavy for normal use.
21:40 - Normal priority level again, and other tasks run almost as smoothly
      as without the program. The sound breaks up again though.
21:55 - El finished testing. For her the sound apparently worked smoothly all
      over, although using the higher priority level version of the program.
      No wait... she noticed a break of some sort? *interrogates* Oh, it's
      just that the main loop only processes one keystroke at a time and
      sometimes even if you press keys simultaneously, some are only
      processed later and the chord isn't played properly. I think it's
      linked to the sound break-up problem, since the main loop should be
      running smoother, instead of going in large pulses like that. The mixer
      procedure isn't supposed to eat up so much processing time, and as far
      as I can tell it doesn't.

110203, Tuesday
17:00 - Winamp and Modplug player both use second-long DirectSound buffers,
      and they manage to play very smoothly. If MP3 decompression can be done
      on this comp, as well as playing 8 module channels at under 10% CPU
      processing time, I don't see why my mixer could take so much more time.
      I think I'll try to disable interrupts for the duration of the mixing,
      and see if that helps. Under DOS it would be necessary, under Windows
      it may not even be possible...
17:30 - Actually now I made the maximum channel amount a variable, and
      setting it to 16 gave smooth sound as it is, unless several notes were
      to play at once. I also tried playing at the same time as Modplug
      player is playing one of my module songs... it actually worked.
      Although Modplug started breaking the sound and the latency of my
      program made it only an interesting experience, not to be repeated.
17:55 - Apparently it's not possible to block interrupts as it used to be.
      Let's see if another test explains the slowness any...
18:10 - I'm not sure what to say about this. Making the mixer call from the
      main loop again and displaying the mixing sizes, disabling the mixer
      call from the notification handler, gave about 4 to 6 zero-mixes when I
      had 128 maximum channels. Reducing that to 8 resulted in 14-20, that is
      triple the amount.
18:25 - Playing 8 channels simultaneously didn't give any trouble, as the
      mixer is called from the main loop. The zero-mixes didn't even lessen
      noticeably. I'm going to assume a for-loop is extremely slow to use,
      and see if it helps to use a repeat loop instead. :D
20:55 - Also, 8 simultaneous channels using notifications still gives no
      trouble. Randomly, rarely, a tiny break will occur, but I think that
      has to do rather with the OS than my program, since I was only playing
      two notes simultaneously at that.
21:10 - Well, I guess any loop is slow. 16 is still ok, 32 starts being too
      much. Optimizing will help this plenty, of course. I wonder why it gave
      an access violation error again as I tested it...
21:50 - Probably a coincidence, the violation thingy. I wonder what I should
      do next. Maybe decide on the final plan for storing and using the note
      frequencies, and add in some code to fill out the rest of the frequency
      table using my calculations for the single octave...

130203, Thursday
23:25 - Changed the mixing flag setting slightly, I'm hoping this would
      remove the access violations that rarely pop up at exit. Also, changed
      the frame counter code in the mixer, so that the volume of a sample
      fades at the same speed regardless of the mixing frequency. I'll need
      to come up with some exact scheme for the timing counter anyway later.
      I also keep wondering what causes DirectSound to cough occasionally and
      cause a break in the sound, even at light mixing load. I noticed that
      changing the mixer thread priority to above normal (+1) doesn't seem to
      induce any noticeable performance hit in other running applications,
      yet it seems to help a little in keeping the mixer up to speed. I'll
      likely make this a user-definable option later. I took a moment to
      think of the frequency calculations, and saw that the current scheme is
      good and functional certainly on short-length wavetable data, but I'll
      need to see what the highest frequency allows for wavetable length. If
      not at least a few thousand, I'll probably scale all the frequencies
      down by a multiple of two or even four. Right not I'm not really in the
      mental state to calculate such things, having work tomorrow and not
      having had enough sleep last night. To top it off, my net connection
      doesn't work properly.

040303, Tuesday
22:40 - The same state has continued, I don't get enough sleep, and still
      spend upwards of 10 hours at work daily. I tried SNES emulation again
      yesterday and indeed, Snes9X gave crackly sound no matter what I tried.
      However ZSnes played the sound beautiful and clear. Of course, this was
      the DOS version, and the program is the best optimized of its kind
      around. Trouble is that ZSnes crashes constantly on me so I can't use
      it anyway, even though the music was great for the time it was playing.
      I suppose I can't go making too many more conclusions about sound
      breakups until I've had a chance to try this MoonSynth test program
      myself on some other sound card and on a quicker computer. I guess I'll
      have a look at the notes now.

050303, Wednesday
00:40 - Yikes, I should be asleep by now. Long day ahead tomorrow. But on the
      bright side, I managed to write in a little code that should make
      a nice frequency table. Using it requires me to recalculate by hand the
      hard-coded values though, since the values currently are from octave 5,
      where 440 Hz resides. For ease of coding, and maximal accuracy, I'll
      need to make those into octave 10. I needed to calculate the
      intermediary values too anyway, so it's not a huge trouble. So, beside
      deciding that, I wrote the frequency mapping code in inline assembler.
      It's been a while since I've had the pleasure of using that, and it was
      very refreshing and fun to write the little program. It compiles, too,
      and I typed all the stuff from my memory. I didn't test it yet, since
      I'm saying a long good night to El and if it crashes, the good night
      wishes would be abruptly broken. But it should work. I hope I'll be up
      to testing it tomorrow, and if it works, I'll make the program capable
      of playing more than one octave and then finally upload this whole
      thing on my site! Of course it's unlikely anyone will give it a try,
      though such testing on multiple computer setups would be important...
23:30 - Exhausting day, indeed... and tomorrow won't be any better. I gave
      a try to that code I wrote and it didn't work properly. The new system
      of playing sounds seems to be efficient though, so I'll use it and
      describe it in a moment. I tried out the system, but got only snaps
      from the speakers. Checking the converted note frequency values showed
      that something's wrong, and calculating one directly outside my tiny
      assembler routine gave a proper result, allowing sweet waves to be
      heard. I'll revise the asm routine maybe tomorrow.
23:45 - This is how the frequency system works: Final sound output is mixed
      from individual voices. Each voice has a single playback rate value,
      which is used while mixing to produce a desired frequency. There are
      only 32 frequencies per semitone possible, and octaves range from C-0
      to B-10. There's a precalculated set of frequencies for the highest
      octave 10 (which I haven't done yet). These are simply the real
      frequency values in hertz for their respective tones, converted to
      hexadecimals and multiplied by 65536. This means I have fixed point
      values with 16 bits for the integer and 16 bits for the fraction parts.
      At the start of the program, or if mixing frequency is altered on the
      fly, these values are used to calculate the lower octaves. Since each
      descending octave is precisely at half the frequency of the previous
      one, this is easy. Each frequency value is further divided by the
      mixing frequency, resulting in what I call playrates. Adding the
      playrate to the wave offset for every frame that is mixed will thus
      increment the offset by the real tone frequency in Hertz every second.
      When setting the playrate for an individual voice, you just look at
      which note is to be played and choose the appropriate playrate from the
      frequency table. This playrate is finally multiplied by the desired
      wavelength. For example, if you have a 256-frame long precalculated
      sine wave, you want the playrate to move 256 times as quickly so that
      the number of completed wave cycles gives the same frequency. All too
      easy! :D Or, if you have trouble understanding it, then wait until I
      write a proper technical documentation of the synth. I'll try to
      explain this and many other things more understandably then.

060303, Thursday
21:45 - I made a little pascal-language loop to calculate the playrate table
      instead of the assembly routine, and it worked just fine. Except, of
      course, the sounds are too low. And for some reason, trying to play the
      highest E note by pressing the P on the keyboard, resulted in
      ridiculously high and out-of-place playrates. Apparently an incorrect
      memory reference, as the playrate kept changing now and then.
23:55 - Yes, that was a little mishap. I thought there were 13 notes per
      octave, while in fact there are 13 notes -in- an octave, and 12 per.
      The note system works like a dream now, and it's possible to play a bit
      over two octaves in lagged realtime using the keyboard. I also typed in
      a little constant array with something not entirely like a sine wave,
      whose 16-bit signed values I typed in freely improvising. I changed the
      good old saw wave to the quasi-sine, which has 32 frames for every
      cycle, and liked the smooth sound that was produced.
      --[Explanation of the quasi-sine! A normal sine wave has its gradient 0
         at two places: one quarter, and three quarters. At two and four
         quarters the gradient is at its steepest, 1 IIRC. Imagined that?
         Now, my quasi-sine is otherwise the same, but it also has
         zero-gradients at two and four quarters, giving 1 at the eighths
         between the quarters.]
      What is left before I'll upload this thing? I'll do the frequency
      calculations, at least, and add some keys to change the octave of the
      keyboard. I also want to get that inline assembly routine to work. I
      might include a few different waveforms you can choose between at
      runtime, it'd be really easy. I feel I'm also close to actually getting
      to the synthesizing part... I'm burning to try out some frequency
      modulation! But first I'll need to test these on the two or three
      people who actually visit my site once a month. :p For now, however,
      ruuvaantukaa keskenänne, I'm going to bed. ^_^

070303, Friday
21:30 - Right, pizza and lemoncola-powered, starting frequency calculations.

080303, Saturday
13:30 - Last night I couldn't concentrate on the calculations, trying to
      comfort a depressed girl. I did notice that it'll take lots of time to
      do these, since I only managed to calculate frequencies to about E-.
      And once I have all the frequencies calculated, I'll need to go over
      them all again and convert them to 16.16 fixed point hexadecimal
      values. Let's get working... this'll take an hour or two.
14:50 - Whew, done! 384 hertz values, and apparently they're all correct. Now
      for the second phase... entering the values in the correct format...
18:15 - Finished at last! My head is drowning in hexadecimals... Even with
      that twenty-minute break I kept in between. I hope I won't have
      nightmares of having to do all this again. I'll do a little frequency
      test run now, playing through all the frequencies, and checking if it
      does sound properly smooth...
19:00 - Awesome! :D At first it didn't sound very smooth at all, but that was
      because of the very low quality of my quasi-sine. Then it started
      rising, and at the highest end really weird aural artifacts started
      showing up. Extremely scifi sounds! I'm supposing this was also due to
      the low quality of the quasi-sine. I have only a little time left now,
      so I'll try to include final touches and upload this...

090303, Sunday
21:15 - Just went over the source and added some comments. In case someone
      takes a look at it, it should be a tiny bit easier to understand now.
      Of course I'll keep insisting well-written instructions are much better
      to use than trying to make sense of program code some geek's written.
22:00 - Ah, complication. It appears that actual frequencies have only been
      defined for parts of 10 octaves in the midi range. Although this table
      that I dug up at the net claims that 440 Hz, midi note 69, would be in
      fact A-4 instead of A-5 as I said earlier, the fact remains that it's
      possible to use midi notes that go one octave lower from what this
      table lists as octave 0. So the span is still 11 octaves, with a few
      notes not supported at the highest end since 7 bits ran out for the
      midi specifications. MoonSynth of course supports the whole octave,
      just for kicks. I also noticed my frequencies were being scaled one
      octave too low, and I'll still need to fix this whole mess... just
      popped by to say hi to you all. :)
22:45 - Ok, I think it's fine now. 11 octaves and correct frequencies. Or,
      almost correct. C-0 should be about 8.175, but my calculations have
      some roundoff in them, and it becomes 8.075. C-1 should be around 16.35
      and mine is 16.15. This gives a 1.2% error, at least for these low
      notes. The higher ones seemed to be about at the same error margins. In
      fact I'll check this... I want accurate values!

100303, Monday
00:20 - I introduced a simple rounding scheme to the playrate expanding code.
      This means that the largest single error can be +/- 0.5 Hz. I think...
      I guess it's accurate enough. Now, for some reason my monitor may be
      breaking down... it's old already. The screen size is shivering
      somewhat sometimes. Currently it's been bouncing continuously for the
      last hour or so.

110303, Tuesday
22:35 - Noticed my quasi-sine sounds awful on my headphones, so changed it to
      a twice as accurate real sine wave. Still could use better quality, but
      sounds pretty good.
23:30 - Added the octave adjusting. I notice that B-3 is about the lowest
      note that doesn't start to suffer from the moderately low quality of my
      precalculated real sine wave. Also, I can hear C-3, barely B-2, and
      just a tiny bit of A#2 or A-2. Below that the sounds go too low to make
      out, at least from among the high frequency aural artifacts that
      appear. A really high ticking noise. A-2 is supposedly 55 Hz... Funny,
      I would've thought I would be able to hear down to A-1. Perhaps
      a little linear interpolation would remove the noise...

120303, Wednesday
23:10 - One thing I forgot to mention: I cut the buffer size in half just to
      see if the lower channel count would help with that too. In fact, it
      did, and half a second long buffer at 44kHz was rolling along smoothly,
      even as I was playing a module on Modplug Player in the background and
      playing along. This is starting to seem more like it. And the final
      versions will have an assembly-language mixer which will improve the
      speed even further.

130303, Thursday
00:15 - Ah, rewrote the frequency table calculations assembly bit, and now it
      works like it should.

23:59 - Made up a linear interpolation scheme in assembler, to see how the
      sine would sound like then. I wrote the whole thing in one go, then
      compiled and tested, and was surprised that it almost worked. And I say
      almost because it for some reason refused to play any sound when only
      one channel was playing, and it never played the first note that I hit
      after silence. Instead some pops could be heard at the start of the
      silent note. However, any second, third, or so on notes that I played
      simultaneously, came out as intended. The quality was notably improved,
      since I used 65536x-interpolation. Now I could actually hear, or
      perhaps more like -feel-, notes going even to C-2 or so. An interesting
      experiment! I'll remove the interpolation code though and rewrite it to
      work properly some other time. Now I guess I'll need to start laying
      down the synth framework and planning things...

140303, Friday
23:00 - Fixed the assembly thingy, now all the waves play as should. But I'll
      still remove and rewrite it later on.

290303, Saturday
22:50 - I've been sick, busy with work, barely gotten any sleep, and been
      deprived of Cola and Sugar Power. But during that time I also came up
      with a great improvement idea for MoonSynth! Since checking channels if
      they're not playing causes unnecessary jumps several times while
      mixing, I'll use a linked list for keeping track of which channels have
      something playing. Likewise I'll have linked lists for each of the 16
      midi channels for easy tracking of which logical channels are affected
      by midi commands on each midi channel.
23:30 - Due to the way frequencies are calculated, I've decided to do two
      minor changes... firstly, I'll limit the mixing frequency to 8363 Hz at
      the lowest. This'll allow for cool lo-fi output, but will still leave
      room for the waveforms. Since the frequency is used to calculate the
      playrate, and this is done at maximum accuracy, the playing rate needs
      to be divided by the mixing frequency first before multiplying it by
      the single pulse size of the used waveform. The accuracy is all used up
      already so the waveform pulse size can't be much bigger than the mixing
      frequency. Limiting the mixing frequency to 8363 Hz allows for a juicy
      8192 frames of waveform, which should be enough... of course, square
      wave is just two frames and no interpolation. A triangle wave is also
      just two frames, but with linear interpolation. Sine and saw waves, and
      some more exotic ones, will need to be sampled in more frames.
23:55 - The second change is that I added three more octaves to the lower end
      of the range. The reason is that I'm going to need some low-frequency
      oscillators for vibratos and stuff, and the three additional octaves
      give enough range to drop the lowest notes below one hertz. The
      relative accuracy suffers somewhat, but since these are sub-aural
      frequencies, it's not such a big deal. This means I've got a grand
      total of 14 octaves at my disposal now. :D Now I'll try to implement
      that playing channels linked list before I collapse.

300303, Sunday
01:30 - I've got 400 lines of code so far. :)
      And now I'm going to test the linked list channel scheme...
01:35 - Well, worked just fine. Except that when I tried to play more
      channels than my channel maximum is, and then one of the already
      playing ones was reallocated, it messed up my list and caused an
      infinite loop in the mixer thread. I'll just need to add the code to
      remove a channel from the list into the reallocation bit.
03:05 - I just lost an hour thanks to the daylight savings thing. Anyway, I
      added the code and the thing works beautifully. I even can improvise at
      250ms latency with only a few sound breaks. This looks better and
      better all the time! Now I ponder how to continue... I guess I should
      finish doing that rerevision X. After that... Hum. I've got two areas
      that I could work on. Either synthesizing... which would mean working
      based on rerevision X. Or, starting the separate tracking part, which
      keeps track of playing instruments and varies them according to playing
      instructions. If I worked on that, then I could get the program to play
      basic midi files and would have lots of fun listening to my midi
      collection...

310303, Monday
00:50 - I'm kinda stuck with the timing. I'd prefer a scheme that allows
      reading and translating midi files with relative ease, but after trying
      to understand the midi timing method I've come to the decision that
      it's too complex to bother with. I'll just make MoonSynth use
      internally a framerate of 1024 frames every second... corresponds
      nicely to milliseconds but is easier to divide. Also it means I won't
      have to check the tracking system for every mixed frame. Except for the
      instrument internal tracking which I'll do once for every two frames.
      This thus because I'll try to make the optimized mixer mix two frames
      with a single pass... should reduce memory fetches and stuff. I guess
      every mixed frame adds 65536 to a dword counter, and every time the
      counter surpasses (mixing frequency div 1024) shl 16... the tracking
      frame counter is incremented. Each logical channel will also have
      a variable telling how many 1024ths the note has been playing. These
      are all incremented by one, as well as the global 1024ths counter,
      every time the dword counter thingy does its thing, as detailed above.
      Wow, I just solved the stupid time thingy! :D I guess this also means
      that trying to get midi to play properly would be a serious agony, so
      I'll concentrate on the synth part first. ^_^

280503, Wednesday
01:55 - I've been just writing the rerevision X amendments and stuff that you
      can see below, and I prefer not to write stuff here because I'm in fact
      already writing beyond this point. Anyway, I noticed one serious bug in
      my program that was really easily fixed. The high frequencies sounded
      horrible, and 8363 Hz output sounded practically useless. I didn't
      quite get what the trouble was and figured I'd add in an averaging
      scheme for all the sample frames that are skipped hoping it'd help even
      out the artifacts. Actually some of the artifacts sounded extremely
      cool, all kinds of neat mathematical sounds. So as I was starting to
      write this new scheme alongside the linear interpolation hack, I
      noticed something weird. The bit of code that was supposed to increase
      the integer part of the sine offset counter didn't work right. Instead
      of doing the increasing loop every time there was more than $10000 in
      the counter, it did the loop only if bit $10000 was set. As you can
      imagine, as the playing rate went past double the mixing frequency, the
      sound got really weird. Anyway, it's fixed now, and all sound is so
      smooth... well, you just have to hear the highest frequencies to get
      your ears to bleed. Whatever floats your boat. :D
02:05 - I guess I'll summarise what the SECRET PLAN below has so far...
      1) There are general explanations to start with. 2) There is a bit
      where I ponder how FM works. 3) Same thing for AM. 4) Waveforms are
      considered. 5) Channels variables are suggested, with further thoughts
      on implementing MIDI commands. 6) How to do pitch changes and vibratos.
      7) Oscillator properties are suggested. 8) A rough schematic of how the
      mixer will work, some points explained in more detail. 9) Internal
      instrument commands are mentioned. 10) Instrument definition files are
      thought over. 11) Details on how to do frequency, volume, and panning
      slides follow.

260603, Thursday
21:35 - I got a new motherboard, and a new processor, a P3 ticking a billion
      times a second. This was right at the start of this month. Very soon
      thereafter I went to visit El and got back several days later. Then
      I've just not been in the state of mind to do more stuff with this,
      what with having all my cool old stuff to play with. But let's see...
      What am I missing before I get back to coding? Ah yes, the mixing and
      tracking stuff is pretty well thought out already, but one core thing
      is still missing: the oscillator envelopes! There's of course the good
      old way of using an Attack/Decay/Sustain/Release envelope but it's too
      constricting. I was thinking along the lines of a little interpreted
      programming language to give maximal freedom, but I couldn't decide how
      to go about it properly.
22:25 - Now I guess I'll use a scheme alike Impulse Tracker's. Hmmm, yes...

160703, Wednesday
00:35 - Yay, new version of Free Pascal is out! :D I'll still need to go over
      this SPrrX to incorporate the envelope stuff. It appears I wrote the
      whole thing keeping in mind the more complex pseudo-programmable system
      and made numerous references to it. Bleh. :p Have I complained yet
      about how there are occasional tiny breaks in the sound output caused
      by unknown reason, which hopefully wouldn't persist with bigger buffer
      sizes, but which effectively puts a damper on soloing? Oh, I have?
      Well, let me cry a bit. ... OK that's enough. Actually it may be that
      there is some way to make DirectX respond smoother, I accidentally read
      about it somewhere on the net but now I've forgotten all about it. *_*
      Hey, I was also struggling with the Win console, hoping to get my super
      cozy MWRITE routine to work with it, with very limited success. MWrite
      is something I did for DOS text mode work, it's able to write a string
      straight into the video buffer, but it also parses the string looking
      for the ascii char 255 and if finds that, can change the colors of the
      text from that point on. Naturally a Win console requires something
      more to work. Just now I happened to find what seems to be exactly the
      function I need, in the Windows API! WriteConsoleOutput can be used to,
      if I understand correctly, copy an area from my own buffer right into
      the console's buffer... _outstanding_. ^_^
18:30 - I also think I managed to snag a console output buffy handle all to
      myself. This handle is required for manipulating the bugger. And now,
      the randy rascal is tied down by virtue of this handle... let the
      frolicking begin! >=D
18:50 - It has been done! ^.^ Now I can write stuff on the console window,
      having figured out its inner workings for the important part. It's also
      curious to note that as I turned off all dumb options for console
      output parsing that Win does, Pascal's own writeln function adds two
      weird symbols after every line. Yes, CR and LF, as I figured out. I'm
      still not sure what characters this WriteConsoleOutput wants me to use,
      since according to the Win API help I've acquired, it uses Char_Info
      for each character displayed, and Char_Info has some sort of structure
      union of a WCHAR for unicode and a plain CHAR for ascii. WCHAR is
      a wide 16 bit char, I'd imagine. In addition there's one WORD for the
      character color attributes. How come then Pascal's SizeOf function says
      Char_Info is four bytes large? But well, as long as it displays what I
      expect it to display, I won't complain. Soon I'll have a whole new
      MWrite32 available... woot!
21:10 - The output routine of MWriteX is done. I'll convert some of it to asm
      later on, and I didn't bother to add the color parsing yet. I'll get
      back to that once the audio side is better under control! And now, I'll
      go through SECRET PLAN and hopefully will finalize this draft!

200703, Sunday
21:00 - Hurt my ankle somehow, couldn't walk for a day. Was too tired to
      finish the revision back then, but have been working on it still. I've
      taken some stuff out, such as pitch envelopes, and added other stuff
      that midi and FM standards desire, such as sensitivity, scaling both
      amplitude and envelope length according to frequency of note, and some
      stuff about adding it all up. Get this: Even a midi Note Off command
      has velocity data! I'll incorporate it neatly but there's so many
      little things to take into consideration... It's terribly hot in here
      and I'm sweating like a pig! -- korrektion: pigs do not sweat.

::: moonsynth SECRET PLAN rerevision X amendments and stuff :::

Each oscillator of every instrument needs to have a unique sensitivity to the
actual velocity of the played note. This is how my DX-11 synth does things,
and it's good for adding some realism to sounds, as the sound's dynamics
change depending on how hard you're pressing on your midi key. For example
a hard piano hit will be much sharper than a soft, melodically harmonious
one. This is not to be confused with aftertouch, which I'm not sure how to
implement, if at all. Also there is a midi thing to do something by varying
the pressure while you're holding a key down that is different from
aftertouch, and I might have a look at that later.
  Since the various frequency harmonics change their balance depending on the
height of the note played, a scaling scheme also needs to be implemented.
Consider a high and a low piano hit, the high one is purer and clearer, while
the low one has a more complex structure. Therefore an additional velocity
control needs to be added to scale the volume of an oscillator depending on
how high it is played.
  The interesting part in synthesizing would go something like this...

Within a single instrument, you have can have a maximum of eight oscillators.
Each has a frequency, an amplitude and a waveform. Primarily an oscillator
should modulate only the oscillators after itself. An oscillator's frequency
or amplitude or both can be modulated.
  Each oscillator has two variables, FMO and AMO. Both have 8 bits, referring
to respective oscillators. If osku 7 has FMO 00001010 and AMO 00110000 then
its frequency is modulated by the sum of 2 and 4, and its amplitude by the
sum of 5 and 6. You could then make oskus 2 and 4 be modulated by 1 and 3
respectively. An oscillator can modulate itself as well.
  The instrument output comes from an additional Additive byte. 11000000 for
example would add the values from 7 and 8 for the output. Note to self: keep
this in a memory var, when finishing an oscillator ROR it to the appropriate
oscillator slot, AND by 1, NOT, and DEC. Then AND value of oscillator with
this and add to the final output as you go along!
  Each oscillator can start either always at the same offset, or at a random
offset. The instrument definitions set this. This should yield some nice
naturality for sounds. Imagine playing a strings chord with a single
instrument, and they all start their vibration identically. It can easily
cause clipping problems if you have loads and loads of such.

Both the frequency and the amplitude of the modulator affect the amount and
volume of the resulting mess of frequencies in the modulated wave. Apparently
the amplitude should be linear, so that an amplitude of 400 will make a wave
playing at 700 Hz vary between 300 and 1100 Hz. It's difficult for me to
understand why it's linear while pretty much all other sound-related things
are logarhitmic, so I will also include a logarithmic modifier. This will of
course use the note playrate table, so that an amplitude of 64 in the
modulating oscillator will vibrate the frequency up and down one semitone.
12 semitones would mean an amplitude of 768 would vibrate a complete octave
away from the center in both directions. The logarhitmic modifier gives the
total variance, not just in one direction. The oscillator frequency needs to
be over 20 Hz or so to actually get FM instead of a vibrato.
  Since the note played is just a single frequency, a way is needed to set
an oscillator up nicely to play any frequencies. If the frequency should be
constant, say a 10 Hz vibrato, then it's easy to just set the frequency by
pointing at your choice in the note frequency table. However, often the tone
of the sound needs to change depending on the note, and then the frequency
for an oscillator may need to be, say, half of the main note frequency.
Coincidentally that is exactly one octave lower. For this purpose you can
also define a number of notes to move up or down from the main note, for any
oscillator. There are 32 mininotes to a semitone, and 12 seminotes to drop
one octave, so playing an oscillator at -1 octave would come from -384.

Amplitude Modulation is simpler in some way. The oscillator's amplitude is
used as a divider for the modulated wave's amplitude. Apparently amplitude
modulation can add frequency sidebands like frequency modulation, but I don't
quite see how that works. In any case, the more obvious use for AM is using
it for a volume envelope. The oscillator values are 16-bit, so a value of
32767 multiplies the modulated frame by 32767/32768, which is more or less 1.
AM by -32768 would mean a multiplier of -1. Negative values in fact cause
something called Ring Modulation, but I'll call it AM anyway just to be
annoying. Values from 0 to 1 should be the primarily useful ones anyway.
  You can choose a waveform for any of the oscillators. I'll do
a precalculated high-quality sine wave, and a few similar waves. I'll also do
a square wave, which is just two frames large, and a triangle wave, which is
totally identical to the square wave except uses linear interpolation. Likely
you'll get to choose whether you want to use linear interpolation for each
waveform or just use it as it is. You'll also be able to make your own
waveforms, I think, possibly they'll all be stored in an external file... All
waveforms have to have a size in the powers of two for mixing efficiency.
  It's also possible to use a high silent wave, just a single frame of 32767.
When used with internal oscillator volume altering, this is great for
an amplitude envelope. Of course you won't have to waste an oscillator for
it, since there will be envelopes for such stuff. Like if you want a volume
vibrato (tremolo), I might add that as an instrument global variable. It
could also be done using a sine oscillator for AM. :)

The waveforms are stored this way... first there is a header, a single word.
The number of frames in the waveform pulse must be a power of two, that is
1 SHL x, where x is from 0 to 13, so the largest waveform is 8192 frames
large. The x number is stored in the first byte of the word. The second byte
contains the desired interpolation for this waveform. If you want to do
a square wave, you don't want any interpolation, so you set this to 0, and
have a two-frame waveform with values -32768 and 32767. A triangle wave uses
the same waveform but with linear interpolation, type 1. In addition to those
I'd like cubic and Differential interpolations as 3 and 7, to be added later.
  While mixing, the user can set a maximum interpolation level to avoid her
computer melting. If you're content with linear, set the max level to 1 and
all waveforms desiring better interpolation are reduced to linear. For
horrible sound quality and messed up instruments use 0. If no special
interpolation is needed for the waveform, it should ask for the highest
available, 7.
  Then there's the matter of noise. Producing plain white noise would be
quite easy, but sample and hold or something is likely required to produce
anything suitable for sweet percussion. Applying a lagger filter on totally
random noise would also have a lowpass effect, and using a minimum delta
booster would have a highpass effect... I'll get back to this later. I'll
just need to generate noise waveforms in mixtime.

There are 16 midi channels, but all the midi commands given on those are then
mapped into my 128 logical channels. Usually a midi instrument is mapped to
play as a real instrument, but percussion is further divided from the single
midi percussion to a wide set of stuff. Each logical channel has the
following variables:
  playing : boolean - One way of tracking if channel is used currently
  instrument : word - which instrument is used for synthesizing sound
  volume : byte - reference to 0..32768 volumetable having 0..255 entries
  volumeto : byte - volume slide target, vol=(volto+(totime-1)*vol)/totime
  volumetotime : byte - how many tracking passes left for slide, 0..100
  filter : ???
  filterenv : envelope - envelope for filtering
  pan : byte - 0 is left, 128 middle, 255 right
  panenv : envelope - envelope for panning
  note : integer - which frequency is the main one to use, 0..5375 available
  enote : integer - effective note adjusted by vibrato and pitchdelta
  time : dword - the number of 1024ths of a second this channel has been
                 playing since 'note on' event
  noteoff : boolean - set true when the envelope is released and fade starts
  faderate : word - 0..512, speed of fadeout after noteoff
  pitchdelta : integer - frequency adjustment is stored here, and always used
                         when calculating the playrate
  pitchdeltato : integer - target which pitchdelta approaches by averaging
  pitchrange : word - number of mininotes from min to max of pitchdelta
  vibradepth : byte - depth of vibrato, midi modulator wheel
  vibrarate : dword - vibe freq multiplied by sinewave pulse size as 16.16
  vibraofs : dword - high word is integer offset to sinewave vibra location
  oskucount : byte - how many oscillators this instrument uses, 1 to 8
  oskuaddout : byte - bit-coded, which oskus to mix into output
  osku : [1..8] of oscillator - array of oscillator-related variable records

Possible additions... Portamento Time (midi controller 5) for doing automatic
slides from one note to the next, could be done so that the oscillators of
a new note always start at the note of the previous one and get an immediate
sliding command up to its real note.
also Portamento Flag (65) that is true or false to show if portas are in use.
Modulator Wheel (1 and 33) will yield the depth of vibrato.
Balance (8) for limiting the stereo panning controls of an instrument...
Expression (11) for volume adjustments without velocity commands...
Hold Pedal (64) and other pedals around, but these shouldn't be used much...
Effect Level (reverb? 91), tremolo level (92)...
  The pitchdelta value is simply the note difference to be added to the
actual playing note. This is calculated from the midi pitch wheel message.
The midi pitch bend range is 16384, 8192 being the center. The default range
is two semitones up and down, which translates to 128 mininotes. Therefore,
midi pitch values are stored in pitchdelta. This is then decremented by 8192
to make it signed, multiplied by the pitchrange, divided by 16384, and the
result is used for calculating the playrate. All changes in the pitch wheel
are placed in the pitchdeltato variable which drags the real pitchdelta
smoothly along.
  VIBRATO is applied on the instrument playrate before synthesizing the sound
from the oscillators. To cut corners, every tracking pass the playrate must
be calculated again, with vibrato and pitch figured in. The pitchdelta
variable is easy, just add it to the note straight out. The vibrato requires
reading an amplitude-adjusted value from the sine waveform and adding that
value.
  Every time the tracker is called, each instrument's vibraofs is incremented
by its vibrarate, and the offset is ANDed to fit the waveform size. If the
vibradepth variable is 0, this is skipped and only the pitchdelta is used.
Otherwise a temporary note value becomes sinewave[vibraofs] imul vibradepth
idiv 32768. Vibrarate, or the speed of vibration, is a constant 16.16 freq
multiplied by the sine wave pulse size, and should result in maybe 3-7 Hz
vibration. Experiment on this. Vibradepth is controlled by the midi
modulation wheel, and gives the number of mininotes to move away from the
center. 32 would move one octave up and one down. Likely preferrable values
will be around 16. This resulting temporary note value "enote" is given to
the oscillators when synthesizing.
  FADING is fun and easy. Once note-off command is given the noteoff flag is
set and then once every 64 tracking frames, 16 times a second, osku[].volume
is multiplied by faderate, and SHR'd by 9. Faderate has a range of 0..512.
If it's 512 then no volume fading happens and the volume envelopes need to
bring the note to an end to prevent it from hanging. (mathematically, going
from 8192 to 1 would require x multiplications... (511 / 512) ^ x * 8192 = 1 ...
x = (1 / 8192)log / (511 / 512)log ... about 4609 muls) Since there will be
only truncated integers, a more likely amount would come from 512 added to
(512/8192)log/(511/512)log, about 1930, yielding at longest a 2-minute fade.
===NOTE: Noteoff also gives a velocity 0..127, 127 being the quickest fadeout
desired. Faderate must be altered somewhat to respond to this, and possibly
every osku's envelope x-scaling should respond as well.

ENVELOPES.
There are envelopes for controlling the amplitude of oscillators, and the
panning and filtering of the whole instrument. Filtering will only be
implemented later on. Juno effects and such can easily be made anyway by
varying modulator amplitude. I won't probably bother to make a pitch envelope
since you can do that by FM from a modulator of a single high sample and
a nice amplitude envelope.
  First variable will contain the current integer value of the envelope, and
it is only updated by the tracker 1024 times a second. This can be grabbed
quickly for applying. All envelopes range from 0 to 2048 for efficiency, but
can be shifted to a desired range afterwards.
  Each envelope can also have a sort of horizontal scaling factor depending
on the note of its parent channel. xscalea and xscaleb are the variables,
both dwords and ranging 0..1023:65535, 10.16 bit fixed point. The weighted
average of these is counted at noteon and used as envelope.speed, the number
of frames the envelope advances at each tracking pass.
  The offset is stored in 16.16 format. It is increased by the speed on every
pass. When env.ofs reaches env.point[env.progress].next, env.ofs is decreased
by the .next amount, and env.progress becomes env.movingto.
  Every time env.progress is set to any value, env.movingto is set to that
value + 1. Unless, cloopend or sustend is equal to env.progress, in which
case env.movingto is set to cloopstart or suststart respectively. The Sustain
Loop only works as long as the channel's noteoff is false. Also, if
env.point[env.movingto].height is $FFFF then this channel must be added to
the kill list and volume must be returned as zero.
  The envelope value at each pass is updated to ([prog].height * ofs
+ [movingto]height * ([prog].next-ofs) ) div [prog].next.
  It is additionally edited for envelope type!
  For volume envelopes ... = (osku[].Volume * env.value) SHR 11
  For panning envelopes ... = env.value - 1024 ???
  For filter envelopes ... = ???

envelope.value : integer - current value is placed here
envelope.progress : byte - which point we are moving on from
envelope.movingto : byte - which point we are moving to
envelope.cloopend : byte - constant loop end point
envelope.cloopstart : byte - constant loop start
envelope.sustend : byte - sustain loop end point, loops until noteoff
envelope.suststart : byte - sustain start
envelope.speed : dword - 10.16 speed, added to offset every tracking pass
envelope.ofs : dword - 16.16 distance from previous envelope point onward
envelope.point : [0..63] of
  .height : word - actual envelope point 0..2048, or $FFFF for end of env
  .next : dword - high word only definable, tells when next is due

Each logical channel can be playing an instrument, and each instrument can
have up to eight oscillators running simultaneously, combined in various ways
to synthesize the instrument sound. The instrument definitions tell the
amount of oscillators needed, and the number is stored in the channel record.
The oscillator variable record is:
  value : integer - calculated frame is placed here for mixing and modulating
  value2 : integer - for asm optimizing, two frames in one pass
  offset : dword - high word adds to waveform ptr, loop: AND waveformsize
  playrate : dword - freq of oscillator * waveform size, simply added to ofs
  waveform : pointer - memory location of oscillator's waveform data, +1 word
  waveformsize : dword - 1st byte of data has shl bit amount. Unpack here, -1
  waveformbitsize : byte - 1st waveform byte is the shl bit amount, 0..13
  interpolation : byte - 2nd byte has desired interpolation for waveform
  freqconstant : boolean - constant playrate regardless of pitch?
  notix : integer - note number, or note number delta
  displace : integer - how much waveform is displaced from centre
  volume : word - (0..8192), amplitude for waveform frames
  volenv : envelope
  ampscalea,ampscaleb : byte - volume balance depending on note, 0..255
  FMO : byte - which oscillator values are used to alter this one's playrate
  AMO : byte - which osku values alter this one's volume; one bit per osku
  FMlinear : boolean - linear or logarhitmic frequency modulation

Upon "note on" this is done...
- Pick next free channel, add to midi channel list and plain channel list
- Reset all vars
- Get some starting values from selected instrument definition
- check all oskus: if FMO = 0 and freqconstant = true, calculate playrate
- osku[].volume = (midinotevelocity^2 - 2048383) * sensitivity / 322580 + 127
             * 8192 / 127
             * ((ampscaleb * note + ampscalea * (5373 - note)) / 5373) / 255

Each tracking pass does this...
  3. Process any "note on"
  5. Update all envelopes
  6. Kill old notes according to linked hit list!
  7. Figure out current effective note
  | 7a. Update PitchDelta <-- PitchDelta = (PitchDelta + PitchDeltaTo) shr 1
  | 7b. Enote = Notix + (PitchDelta - 8192) * PitchRange iDiv 16384
  | 7c. If VibraDepth = 0 then skip to f.
  | 7d. VibraOfs = (VibraOfs + VibraRate) AND sinewaveform pulse size
  | 7e. Enote = Enote + sinewave[VibraOfs] iMul VibraDepth iDiv 32768
  | 7f. For every Osku of this channel, if FMO = 0 and freqconstant=false,
  |     osku.Playrate = (freq[Enote] SHL osku.wformbitsize) SHR 2
  `""""""'

Each mixing pass does this...
  1. Loop through active channels!
  2. Do oscillators, starting from 1, ending at the oskucount one
  | 2a. If FMO > 0, recalculate playrate! Else use playrate in memory.
  | | :1. If freqconstant is true, Note=Notix, else Note=Enote.
  | | :2. Add up the output of [FMO] oscillators into a temp var.
  | | :3. If FMlinear is false, Note=Note+FMtemp.
  | | :4. Retrieve the Hz frequency of this note, stored as 14.18. The freq
  | |     has DIV mixfreq factored in at startup.
  | | :5. If FMlinear is true, adjust frequency by FMtemp.
  | | :6. SHL this freq by waveformbitsize, and SHR by 2.
  | `""""""'
  | 2b. Increase Offset by Playrate, AND Offset by waveform wordsize
  | 2c. Retrieve waveform frame <- WaveForm[Offset], interpolate
  | 2d. tempvol = volEnv. If AMO > 0, do AM:
  |     go through AMO and for every osku chosen do
  |     tempvol = tempvol iMul osku.value iDiv 32768
  | 2e. frame = frame iMul tempvol iDiv 8192
  | 2f. frame -> oscillator's Value variable.
  `""""""'
  3. Add together appropriate oscillators from oskucount[Value] vars.
  4. Perform filtering on channel output.
  5. Mix channel output into temp variable, or variables if more than mono.

These all are defined along with the instrument definition, a bunch of more
or less human-readable text lines in an ASCII instrument definition file.
Like those cute Angband data files.
  The player can choose from several instrument definition files. One should
have chip instruments (chip.msx), another should be an excellent all-out
best-FM-can-offer (mooncore.msx), and perhaps a simpler version of that for
older comps (moonlite.msx). I'd also love an MT-32 approximation definition,
but the LA synthesis may not be doable so this still won't work as an MT-32
emulating softsynth... maybe if I could find out how exactly it's supposed to
work... The definition of an instrument starts with constant or initial
information:
  instrument - instrument ID number, maybe some cool name too
  pan - default panning for stereo mixing, 0..255
  faderate - 0..512, how quick release does instrument have
  oskucount - how many oscillators this instrument uses actively, 1 to 8
  oskuaddout - which oskus to add up for the output
  sensitivity[] - sensitivity to velocity, 0..31, 31 loudest, 1 softest
  ampscalea,ampscaleb[] - for refitting high and low note volumes
  waveform[] - which waveform to use for each oscillator
  offset[] - where each waveform starts, 0 to 8191 or random
  displace[] - how much each waveform is displaced from center, -32768..32767
  freqconstant[] - should the playrate be altered by mixtime things
  notix[] - note number, or note number delta
  FMO[], AMO[] - modulations
  FMlinear[] - linear or logarhitmic FM for each oscillator
  volenv[] - volume envelopes of oscillators
  panenv - for panning the instrument
  filterenv - for filtering, or something...

All midi commands, as well as envelopes, are processed in the tracking code
which is called 1024 times a second. Once the system is otherwise working,
implementing the midi playing will be relatively easy. Most common midi
messages will be very easy to patch in, only translating the delta times will
present notable headache.

250703, Friday
01:40 - Finished, finished. I've revised that thing to death. Not only that,
      I've also written a bunch of formulas and pseudocode for handling all
      the important stuff except the most important stuff that I've no doubt
      conveniently forgotten. I'll get cola and candy and pizza tomorrow and
      start PROGRAMMING. :D

260703, Saturday
17:35 - After a case of wrist pain I've now incorporated the variables into
      the code, and bonked out the previous mixer code in favor of a new one
      using the oscillators. It doesn't work, naturally. At first I did this
      funny error, forgetting to update the channel counter in the tracking
      code, so the mixer thread locked up when trying to play a note. After
      fixing that, I do get sound but it's too high and doesn't even seem to
      get different frequencies depending on the note. Some playrate
      calculation mistake, probably.

270703, Sunday
18:50 - Fixed the playrate thing. I forgot to SHL 16 at some point to account
      for the fixed point stuff. I also added the kill list, into which it is
      easy to enter channels that are to be turned off. For some reason the
      sounds seems to be oddly silent though, I'll check to see if I've used
      all the volume things correctly.
19:40 - Yes, due to only half-implementing, some stuff was divided too much.
      Then I added a skvare waveform and some other stuff like that and
      played with it and it sounded nice. ^_^ Except of course the sound gets
      broken now and then still. Must be a DirectSound thing... Next I guess
      I'll see if I can do preliminary FM.
20:20 - Wooo! The pseudo code works as it is. :D Now I have two oscillators,
      the second one is Frequency Modulated by the first. The second is
      a square wave, the first is a sine. I toned the sine wave down into
      a lovely vibrato and it simply works! Now... I wonder if it would be
      possible to come up with some nice values that would give me an FM
      sound, even though envelopes aren't in yet...
20:35 - Some FM indeed is possible. I guess I should make another site
      update. Soon enough...

310703, Thursday
11:50 - Me sick today, so not at work. Tried to redo the linear interpolation
      thing but it's not working too well. Getting the offsets right took
      notable effort and now it's at least not making white noise anymore,
      but it still doesn't play the sine waves as what they are.

080703, Friday
23:20 - Turns out I've been enjoying a nice wave of mononucleosis. Anyway,
      now I changed the waveforms into unsigned format so the asm code is
      easier, the linear interpolation almost works finally except there is
      a silly crackling in the sine sound. Delving into it.

090703, Saturday
14:45 - Fixed it, I was doing the fraction inverting from 65536 while I was
      supposed to do it from 65535. It happens. So far the FM sounds I'm
      getting aren't very exciting, just a spacey sound.
15:00 - Hacked together the linear FM in addition to the logarhitmic one, the
      linear one seems to give a more interesting sound. Probably should get
      to doing the envelopes next thing. But now me hungry!
21:50 - Patched in basic volume envelope code, it seems to work. I can't say
      for sure since now as everything is hard-coded and I turned FM off so
      that only the second oscillator which I usually use as the carrier is
      mixed into the output while the first is pretty much ignored... and for
      some reason the normal sine wave it should output sounds very silent.
      Again I'm not sure why this is so, but obviously something in the
      envelope code needs tweaking.

130903, Saturday
17:45 - Yes, got disinterested for some time, the envelope stuff seeming
      unfun and annoying. I gave a few half-hearted tries in understanding it
      all before but gave up. Now I went through the thing again and figured
      the problem must come from not having enough variable space in the
      arithmetic that calculates the envelope value. Since the offsets use
      a 16:16 bit setup, and those are used for a multiply and a divide to
      get the linear movement from point to point... the calculations require
      over a dword, so I'd best do it by hand. And so I wrote some more asm,
      with a screenful of code required just for getting the memory addresses
      and variables loaded. The arithmetics themselves were simpler, although
      I ran out of registers at one point. A division by zero error was fixed
      by making sure nothing is calculated once the end of the envelope is
      reached, and... imagine my joy as the sine faded in and out smoothly!
      Now I guess I should return to integrating a better solution for using
      DirectSound... the current one is not acceptable.

300903, Tuesday
23:30 - I read about this interesting mixing idea which I seriously should
      have thought of myself. I'll rewrite the mixer to do one playing
      channel at a time for the whole desired mix length and mix the channels
      one by one, instead of mixing all the channels for every frame. Since
      many of the variables do not change within one mixing period, this
      would actually give a notable speed increase, I believe. Naturally
      doing some asm conversion would do this as well.

211003, Tuesday
23:30 - The previously described idea has some difficulties to it... namely,
      taking care of the tracking. Tracking and updates of the envelopes is
      currently done 1024 times a second. (this could be reduced, or
      increased, technically...) Anyway, since the mixer is called only like
      16 times a second currently (already easily changeable), that means the
      tracking code needs to be gone through 64 times per each mix, per
      channel. The actual tracking work doesn't change notably when this
      mixing technique change is committed, but it means the mixer will use
      up an extra check and won't be quite as efficient as I dreamed it would
      be. Still, once I get around to the ASM conversion, this new model will
      have more efficiency than the previous one. Always a good thing. ^_^

221103, Saturday
00:30 - Well, had a house move. Feeling sleepy.
01:00 - Unable to concentrate. Listening to Final Fantasy music.
01:15 - Oh, did I mention? I finally got Final Fantasy 7 and 8 and have been
      playing 7 with elation. That, and some other factors, keep adding to my
      sleep debt. Anyway, back on topic... since I'll be mixing one channel
      at a time, it means...
01:30 - um, it means, that the channels can't actually be "mixed" in that
      single pass. I used to have one mixing buffer into which they'd all be
      mixed one frame at a time, every channel being looped through to do it.
      But now it's supposed to generate a longer string of audio for each
      channel and mix those longer strings together. One solution would be to
      have a separate buffer for each. *yawns* But that'd be stupid. Since I
      have a maximum of 128 channels available if my memory functions, it'd
      mean 128 times as much memory used. The buffer for one second would be
      44100 frames, which can be 88200 bytes since all is in 16 bits, and if
      you want stereo, double that. The current buffer is 22272 bytes large,
      don't ask me why though since I can't think at this hour...
01:45 - So er, it's mono, so in stereo having 128 buffers like that would eat
      5.7 megs of memory. Not very nice, I'm sure you agree. But actually
      it's much simpler to just use a 32-bit buffer instead for the mixing
      and mix each frame into the buffer sequentially, channel at a time, and
      only do the clipping once in the end. I'm sure sooner or later I'll get
      around to coding this thing. I already did some changes, but I forget
      what...

231103, Monday
02:45 - Spent some time going through the program, managed to implement the
      clipping thing and I think the mixer should be okay now. Except that
      the program didn't work. I think I've got byte and frame sizes mixed up
      somewhere, it appears that DirectSound does byte sizes while I was
      thinking of frames somewhere along... not sure how it worked before.
      I'll have to go over the whole code and make sure everything is
      correct. Of course, I'll need to get to work in a few hours' time... so
      I'll get back to this sometime soon.

291103, Saturday
02:20 - Right, so I've now gone over the code and fixed the most blaring size
      mishaps. Now it seems to initialize okay, but the mixer still doesn't
      run. I'm starting to be annoyed again as the program seems to enjoy
      crashing if I try as much as to run a bit of code from the start of the
      mixer and break off then, that just fills the 32-bit mixing buffer with
      zeroes, and then hit ESC to quit. I think I've left something
      unfinished with the thread stuff and all. *sigh*
02:25 - *blink* Um. *blink, blink* No. *blink* No. No. No.
02:26 - *blink*
02:27 - No.
02:28 - What's wrong with this code: LEA EDI,pointerthingy; MOV [EDI],stuff
02:30 - Indeed. Now it is fixed and crashes are gone, again. That's what one
      gets when one stays away from programming for a while and then returns
      and fills the programs variable space with a long string of unsigned
      doubleword values of hex 8000 0000. For those of you who aren't up to
      speed on coding, I'll tell what is wrong with the code above. Pointers
      are variables in memory that contain the memory location of something
      else. LEA is a command that gives you the variable's memory location.
      This is great if you want to set for example Variable X = 1. But with
      a pointer you need to read in the memory location, not get the memory
      location of the memory location! The correct code would thus start with
      MOV EDI,pointerthingy. No, I don't care about C.
02:40 - Right, the whole mixer routine is online again. Needs some debugging
      though. I can hear sound again but it's crackly and some parts are
      dropped. In addition it crashed again at exit, but that seems to be
      random. :p
02:45 - Yay. One trouble was that I wasn't reformatting my 32-bit mixed
      material down to 16 bits before copying it to the output buffer. Now
      the sound is somehow SIDish, but something's still wrong.
02:55 - Perfection is attained. ^_^ While switching over to this new method,
      I forgot to reset the temporary mixing variable, resulting in a lot of
      feedback to the sound. Actually it sounded kind of neat. ;)

181203, Thursday
00:00 - Upon further playing around I detected an annoying scratchiness in
      the sound. I wrote in a bit of code that outputs the sound to a raw
      binary file, and took a look at it. While the envelope works perfectly
      otherwise, as in a totally smooth rise from 0 to maximum amplitude in
      a quarter second, then there's also two half seconds where the envelope
      descends back to zero. They move at the same speed, that is quarter as
      steep as the rise. The latter half is smooth as it should be, but the
      first half in the middle of the sound... has these weird jagged edges.
      Otherwise it goes down perfectly, but there's 16 pairs of what would be
      a fortress battlement, descending lightly. The difference leap is small
      but it doesn't change the phase, only the amplitude, so it has to be
      an envelope thing. I think it can happen in any part of the sound,
      randomly.
22:00 - It doesn't happen in every part, curiously. Seems to only take place
      in the middle segments on the envelope. When I used just three points,
      0 to 2048 to 0, it's smooth as ever. But when adding a fourth one or
      more, the battlement effect happens. If the voice is near maximum
      amplitude, the battlement is a slim one, as in has a high frequency.
      With a more silent sound there are audibly long gaps in the sound. This
      is most odd behavior.

241203, Wednesday
23:15 - Have been trying to figure it out. 2048 to 0 to 1024 is also
      flawless. Also, overriding the volume envelope removes the effect, so
      it would appear that somewhere in the envelope tracking code,
      particularly some bit in the interpolating part, is likely bugged...

190104, Monday
02:55 - Lovely holiday. Me refreshed. ^_^ Now back to this issue... the
      jagged edges appear at both rising and falling slopes. However, it
      seems that... *checks* yes. The distance between the points affects the
      severity of the amplitude cut blocks. With all distances being 256, the
      amplitude in the affected parts was reduced by 256. However, with all
      distances changed to 128, the amplitude reduction changed to 512 at
      every part! Using distances of 512 causes an amplitude reduction of
      128. Still, why doesn't it happen between every point? And why such
      a battlement effect?
03:00 - The distance between two points directly only affects the battlement
      effect between the same points. If the battlement isn't triggered for
      some slope, then the distance can be anything without problems.
03:05 - An envelope of 2048-1700-512-1700-2048 resulted in all four slopes
      getting battlemented. Looks formidable, sure, but sounds awful. Whereas
      an envelope 0-512-2048-512-0 gave battlements only on the two center
      ones. This seems like a pattern recognition task...

   0 ->  512 -> 2048 <-  512 <- 0
            ^^^^    ^^^^
2048 <- 1700 <- 512  -> 1700 -> 2048
    ^^^^    ^^^^    ^^^^    ^^^^
   0 ->  512 -> 1024 -> 1536 -> 2048
            ^^^^    ^^^^    ^^^^
2048 <- 1536 <- 1024 <-  512 <- 0
    ^^^^    ^^^^    ^^^^
   0 -> 2048 <- 0    -> 1024 <- 0 is perfectly smooth.

03:15 - Right. So the interpolation thingy performs as it should as long as
      at least one of the points is 0, otherwise it gets confused... whew.
      And the higher one of the points is, the more disruption is caused.
      Even values under 16 cause problems that are almost inperceptible but
      can be seen on the graphic wave output.
04:00 - WOOOOO. I figured it out. My interpolation thingy uses two 32-bit
      offsets multiplied by the amplitude, and adds those together. Of course
      I was smart enough to make use of the 32:32 register combos
      appropriately, but for one tiny detail at the end. When adding to 32:32
      values together, the lower dwords may overflow. In this case you need
      to Carry The One. You do that simply by checking the Carry Flag by
      a LAHF command that copies the basic flags to register AH. Now, whoever
      infiltrated into and broke my Perfect ASM Code made it so the program
      thought mistakenly they went to AL instead. This was of course easy to
      fix with a bit shift, and now the system works as Promised. ^_^

200104, Tuesday
00:45 - I think I figured out one crash... when having another application
      playing at the same time, like ModPlug Player, and it controls playback
      to some extent, MoonSyn crashes randomly when starting or stopping
      playback in that other program. I guess DirectX doesn't handle the
      primary buffer stuff for SB16 all that well, it being an older sound
      card and all. Not that I know exactly why it crashes, of course. The
      buffers must keep running in MoonSynth or it would just stall and look
      sheepish. Perhaps then one or another buffer becomes invalid somehow...
01:25 - However, neither ModPlug or Winamp crash when I run MoonSynth and
      stop and start it even lots of times. So I guess I'm doing something
      not entirely correct. :\

210104, Wednesday
00:25 - Performed an interesting experiment. I ran this system monitoring
      program, standard windows sysadmin thingie, and checked how much the
      processor gets used. I was slightly surprised to see processor usage
      being at 100% when MoonSynth was running. Then I realized that it's the
      main program that I've made in good old DOS style, to keep polling the
      keyboard while constantly updating some debug info on the screen. So of
      course all the time that isn't spent on something else like mixing or
      other inferior applications, is fully used by the main loop. The sound
      engine itself doesn't actually need the main loop, since it will
      contain tracking of midi data as well as the mixing. For now though the
      main loop is necessary. I added a 40-millisecond sleep for each loop if
      no key has been pressed and witnessed processor usage dropping to
      nearly nothing while no notes were playing. Interestingly still playing
      some song with merry abandon could cause processor usage to peak at
      100% again. I wonder if this would allow me to reduce the buffer size
      for lower latency...
01:00 - Indeed it does. Now, I'm not sure how much processing power actually
      the mixer eats and how much is eaten by the main loop. Of course, this
      should be easy enough to check now that I have a functioning envelope;
      I just make a never-ending sound and see how much power generating it
      eats over ten seconds or so. Then try with two sounds... since, even as
      I'm typing this using good old DOS Edit, the processor keeps peaking at
      100%. It must be one of these dos thingies. So...
01:30 - Here's the results. I observed each bit for up to a minute to make
      sure. Running without notes playing or anything varied; I turned off
      the firewall and every other program that might interfere notably. In
      general running dry took 0-3%. Upon one test it was 0-1%, then although
      I didn't change much it went to 2-3% on a subsequent try. Strange,
      perhaps, but I suppose that could happen from simply making the
      instrument a little more complicated, as in turning on linear
      interpolation and using a sine instead of square, and not displaying
      debug data...
        Playing one note: 21% -> 3,5%
        Playing two notes: 22% -> 4%
        Playing three notes: 23% -> 4,7%
        Playing four notes: 23% -> 5%
        Playing five notes: 24% -> 5,5%
        Hitting + to change octave: 24% momentarily
        Playing six notes: 24% -> 6%
        Playing seven notes: 25% -> 6,5%
        Playing eight notes: 25% -> 7%
        Octave up again: 26%
        Playing nine notes: 26% -> 7,5%
        Playing ten notes: 26% -> 8%
      The results speak for themselves. What, you can't hear its voice inside
      your head? Fine, I'll explain. :p Pressing any key uses momentarily
      about 18,5% of processing power. I did this in three second cycles so
      perhaps pressing a key eats 100% for a half second ?.. Apart from that,
      stacking simultaneous notes was quite cheap, only 0,5% each. Of course
      I'm running a 1GHz system, and at 27000 Hz output, but still it's very
      nice for unoptimized code. I wonder if this means that each note eats
      5 megahertz... that's about 5 million clock cycles a second. Not
      counting overhead, it would mean that each generated frame would eat
      about 185 cycles... sounds feasible. :D Besides my SB16 is an ISA card
      so a modern PCI one would output stuff lighter too. With optimizations,
      this should be quite okay! As long as nobody even thinks of touching
      the keyboard... :p
21:10 - Tested again, this time using a 1-second refresh rate instead of
      3-second, for the processor load graph. Indeed the keypresses now ate
      up to 50% each. In fact, I just noticed that even holding down any key,
      like shift or control, will still cause the processor load to peak up
      to 100%. Inside the command prompt, that is. I can hit any keys I like
      on the desktop, or anywhere like the processor burden program, and it
      doesn't affect a thing.
21:30 - Of course, DOS programs are given 100% processor power since they
      aren't built for multi-tasking, and will happily use up everything the
      OS can spare them. MoonSynth is not a DOS program though, yet it still
      eats plenty of power when any keys are pressed. Unless it is caused by
      running in a console, which would be sort of weird, a silly keyboard
      procedure is to blame. I presume in more recent Windowses this should
      be handled better all over.
22:20 - Changed the instrument to do some FM again. Now I thought of
      a possible problem... I was hoping to have a single long buffer where
      all the NoteOn and NoteOff et al midi stuff are stuffed, and which
      would be read to trigger the notes. This triggering needs to be done
      in the tracker code, and it would be easy to distribute the sounds to
      their channels if all channels were processed simultaneously. This is
      no longer the case, of course.
22:30 - I guess I'll keep the single note data buffer concept. I'll just need
      to process as much of it as will be needed for each mix before starting
      the tracking/mixing loop. Sort of, split it apart for the individual
      channels so each has their own minidatabuffy... and starting new notes
      would be handled by the premix tracking code, setting the new channel
      up appropriately but leaving the "playing" flag false, only enabling it
      by a NoteOn in the minidatabuffy for that channel.

220104, Thursday
00:00 - The main data stream is a pointer thingy, the stream consisting of
      concurrent entries:
        tick:dword; (2048 ticks/second: upon splitting to ministreams will be
          converted to mix freq speed which can be checked by the tracking
          code 4-2048 times/sec)
        com:byte; (midi command or other such);
        chn:byte; (if command requires a midi channel it's here)
        data:whatever; (any possible data for com)
      The tracking code will run once for every frame to mix. Each time it
      checks if new channel commands are due for that frame, and increases
      a counter. The counter is used to time the channel tracking 4-2048
      times a second. The channel tracking bit updates the envelopes. The
      ministreams are like the mainstream only without the midi channel, and
      the global tick changed to coincide with the mixing frames.
04:00 - Now I have a sort of working player. Of course I have to hardcode the
      notes into the megastream first. This version also only puts stuff on
      one channel, and uses the hardcoded instrument thing. It should be
      relatively easy to expand, of course. ^_^ While exiting on one of the
      test tries it again gave an error. I wish I could figure out where
      those come from. :p

230104, Friday
02:20 - Now I notice that MoonSynth is offkey... the notes should all be one
      seminote higher, apparently.
02:30 - Simple enough. My note calculation was off by one due to using
      an array starting from 0 for checking which key was pressed, but to get
      proper results, C must start from 1. At least now it's correct, and I
      could play some familiar songs just like on a real keyboard, only more
      clumsily and with slightly mean latency.
04:05 - I should be asleep tightly by now. Anyway, I made MoonSynth play the
      bass line of the first part of the Giana Sisters Theme, and loop it
      infinitely. It sounds great, except that it's almost as if the notes do
      not fall in place exactly at right times. Will look into this. I also
      checked the processor load and as one could guess it was the same old.
      Having MoonSynth play a riff on one channel affects very little, and
      playing lead over it with the keyboard clogs things up. Oddly the kb
      latency varied, now and then a sound break would occur and the latency
      would change either to 0.2 or 0.4 seconds, as I'd estimate.

250104, Sunday
05:30 - Not yet dawn. :p Now I fixed the note buffer tracker code so that it
      doesn't make every note play on channel 1, but gets the next free
      channel instead. The current bass riff has a little overlap between the
      notes so it sounds sort of fuller now. I also looked at the slight
      mistiming of the NoteOns. Turned out I wasn't correctly calculating the
      offset within the current mixing block, but that was easily fixed. Now
      there is just a minor timing error when looping but I'll improve the
      looping bit later on anyway. Looking great! Next step: leave the thing
      running overmorning as I go to sleep and see if it hasn't crashed on
      its own when I wake up again. ^_^ 815 lines of code now, btw.
14:00 - Still running. :D
15:00 - Okay, next thing to do would be to adjust the tracking code so that
      it's possible to call it a variable number of times a second. Since the
      per-channel tracking, doing the envelope updates and maybe something
      else, can be kinda heavy to do 1024 times a second, and I like
      providing customizability. It would help in improving the speed too
      especially for slower computers. And for file rendering it could well
      be higher than 1024. Currently I made the internal ticking go at 2048
      frames per second.

290104, Thursday
09:05 - Just before leaving for work, I'll write down the ToDo list!
      * go over DirectSound code
      * adjust tracker speed
      * check and finish FM synth
      * implement a config file
      * write own keyboard handler
      * write AM synth code
      * add stereo mixing and panning envelope
      * implement multiple instruments
      * implement midi command converter
      * implement midi channel management
      * implement more commands in tracker
      * make a sound editor, create lots of cool instruments
      * implement filters and reverb

020204, Monday
01:55 - I implemented a toggle key for the hardcoded playback, then sent the
      program to El for evaluation. Refreshingly it crashed on her. It also
      went into some sort of infinite loop, evidently, dragging processor
      usage to 100% and making everything sluggish. When running MoonSynth
      normally it didn't eat much power. From 5-6%, running MoonSynth brought
      it to 8-10%. Turning on the playback increased that to 15-20%. I gave
      her a new version that plays about four notes simultaneously, which
      caused processor use to go up to 35-40%. So it would seem that one
      channel eats about 6-8% on her comp. El has a 300MHz processor, if I
      remember correctly. Now, if she went ahead and pressed a key, processor
      use of course peaked. Often this would also cause an access violation
      and an unhandled exception error. I presume this could be due to the
      too high processor load caused by the Free Pascal keyboard reading
      function, even though it certainly uses windows functions. The
      processor consumption was also higher than I expected, but hopefully
      optimization will help with that. There aren't that many heavy things
      to add into the mixer/tracker now, anyway.

020304, Tuesday
01:50 - I made some changes in the tracker speed. Now it's changeable from
      a measly four times a second to 2048 times a second. Below 512 you
      already start noticing a quality degradation though. But well, it sure
      should save on processing power if needed. As I tried to move the midi
      command processing code (that currently only handles Note Ons) into the
      tracking loop proper instead of being run separately at every pass,
      some strange lockups started occurring. Otherwise my code was flexible
      enough to actually work almost as it was.
20:45 - Okay, I keep making these stupid mistakes. Again, confused
      an effective memory address and its contents. With that fixed, I can
      again play notes, but trying to get the tracker to trigger the
      preprogrammed note sequence causes a hang...
21:40 - Progress. ^_^ I forgot to update the note sequence offset variable in
      a loop. Having fixed that, even the sequence plays nicely for a moment,
      but hangs inexplicably after a while... and curiously this seems to
      depend on the tracking frequency. At high tracking speed hanging
      doesn't occur, while at a low speed it hangs straight away. I guess
      I've left in some hack that only allows one command per channel or
      something...
22:30 - The hangs seem to happen somewhere in the sequence breakdown routine,
      the one that splits the data stream to ministreams for each channel. It
      appears that a hang only occurs when the last 2048th frame to mix is
      close to the 2048th of the command. When the tracking frequency is 1024
      times a second, as before, it only seems to hang when the last frame to
      mix is just one more than the command. Going down in the tracking
      frequency appears to increase the dangerous frames so that at 64 times
      a second hangs occur when the last frame to mix is at most 16 frames
      past the command frame.
22:50 - Changing the internal note on offset to just zero instead of
      somewhere midway removed hangs altogether when tracking at 64 fps or
      higher. However, 32 fps or lower will always hang even now.
23:00 - And if the mixer gets to process more frames at once, the hangs are
      reduced so that 32 fps doesn't always hang...

030304, Wednesday
00:00 - So I implemented a system where the ministream buffers wrap around
      instead of being filled from the start every time. This did not help.
      Oddly the whole thing works perfectly as long as the midi command
      processing is done once for every mixing frame. If the processing gets
      moved into the tracking loop where it runs only 64 times a second,
      problems arise. However, there's never more than one command in queue
      waiting for handling anyway so the stacking can't really be the
      dilemma.
00:40 - The problem appears to be that if the channel isn't set to play the
      sound soon enough, the hang happens. I have no idea why this is so,
      since as long as the playing flag is false there should be little
      happening at all. Therefore, with sufficiently rapid tracking the
      channel is set to Play and the hang is avoided. This is proved by
      changing the keyboard playing routine to initialize the channel
      otherwise but leaving it stopped, which gives one okay sound but the
      next one will hang.
01:05 - Alright, I believe I figured it out. The routine that finds the next
      free channel makes use of the play flag. Since it decides to re-use
      a channel that's already added in the active channel list, the same
      channel gets added again pointing to itself.
01:20 - Finally fixed! I'd have a party with myself to celebrate but I'm too
      sleepy. Not to mention the added inconvenience of a sugar hangover in
      the morning. I'm going to go get a haircut before work anyway. ^_^
      Anyway, this means that one item on the To-Do list can now be ticked as
      Done! * Adjust Tracker Speed - 100% complete. What next, Massster ?...
08:35 - A full-night endurance test also passed, MoonSynth was still playing.

080304, Monday
00:55 - Now I'm trying to add an instrument type you could use for easily
      storing a number of different instruments and copying that into the
      channel variables for synthesizing. While typing in the instrument
      variables as detailed in MoonSynth Sekret Plan ReRevision X I noted
      that the effect of sensitivity on volume was rather unclear. Therefore
      I wrote a new version of how volume will be calculated. Here it is...

      There are three things to note in calculating an oscillator's volume.
      In addition to those there is a channel volume which is the same as the
      midi channel volume, and a global volume. For now however, we have the
      midi note Velocity 0..127, sensitivity of this particular oscillator
      which can be 0 for N/A or 1..31, and the amplitude scale values,
      a linear progression from A to B both being 0..255. This last one is
      used for adjusting the volume depending on the note being played.
      Sensitivity can be 0 and the oscillator uses full volume regardless of
      the midi velocity. Otherwise it will either soften or harden the
      linearry increasing velocity. The output volume needs to be 0..8192.

      If sensitivity is 1..16, let V = (velocity^4 + 15878) / 31756
      If sensitivity is 17..31, V = (8192 - ((127 - velo)^4 + 15878)) / 31756
      The +15878 is for rounding purposes. V is now 0..8192.
      If sensitivity is 1..16, Volume = ( (Sensitivity - 1) * ( [velo shl 13]
        div 127 ) + (16 - Sensitivity) * V ) div 15
      If sensitivity is 17..31, Volume = ( (31 - Sensitivity) * ( [velo shl
        13] div 127 ) + (Sensitivity - 16) * V ) div 15
      This makes a balanced average between the exponential curve and
      a straight linear increase. Finally, the scaling by note: Volume =
      Volume * ((ampscaleb * note + ampscalea * (5373 - note)) / 5373) / 255

01:50 - Also I decided to skip the waveform displacement. It would save some
      memory in some cases but it's really more efficient to just make
      a separate waveform if you need such. Now I have a nice typecast for
      instruments. Maybe tomorrow I'll write some test instruments and the
      code to copy that into a channel's own variablespace... and after that,
      I could make a piece of code to generate 128 slightly different
      instruments... and then, work on midi file reading because it would be
      really motivating to be able to play those things. ;)

090304, Tuesday
00:25 - Right, I wrote some instrument initialization code and it appears to
      work. Now to write something that copies it into the channel vars...
01:55 - Getting it to work was fairly simple. Technically I now have multiple
      instruments working. If only someone created a bunch of cool
      instruments for me now... I noted also that, although one channel seems
      to eat 1-2% of processing power on my comp, running the hardcoded
      sequence hoggles about twice that. I believe the heavily typecasted
      instrument copying code I put in place is kinda slow to use, but I'm
      reluctant to convert that to asm until I feel more certain I won't be
      adding new variables to the instrument definition. Not that it'd cause
      major hassle to fix things afterward, though. I guess next I'll try to
      create some instrument generation code so I won't need to bother typing
      in 128 of those typecast heaps. And after that, why not see if I can
      decode midis and play them... :D

180304, Thursday
19:55 - I wrote some code to generate funky FM instruments but it didn't work
      quite as well as I hoped. Some very frightening sounds were produced,
      including several composed of crackling and a strange rising "whup" at
      the end.
20:50 - Okay, rewrote it so it's simpler. Only does two-operator stuff, one
      modulates the other, which other is played out. However, in half of the
      instruments the modulating operator also modulates itself for some
      extra effect. This combined with a small number of waveforms, and eight
      modulation amplitudes yields 128 instruments, all subtly alike. Most
      annoyingly my headphones can't handle all the sound and have started to
      resonate with some frequencies.

190304, Friday
00:05 - Sniffle! It be lonely when El is away, though rest good for her.
      Okay, I started deciphering a midi file. So far so good, I read headers
      and made sense of the chunk system, and variable length values. The
      difficult bit was the delta timing system, but I think I actually
      figured that out as well! Technically there's just two things I need to
      be concerned about: Pulses per Quarter Note, and Tempo. These two are
      related, as Tempo is microseconds per Quarter Note. The delta times are
      in pulses. One pulse thus takes Tempo / PpQN millionths of a second.
      This is easy to convert to MoonSynth ticks, of which there are 2048 in
      each second. An example: Tempo is 400000 microsecs per quarter note, or
      0,4 seconds. Pulses per quarter note is 120. Microseconds per Pulse is
      400000/120 = 3333,3. Quarter Notes per Second is 1000000/400000 = 2,5.
      Pulses / Second = Pulses / Quarter Note * Quarter Notes / Second = 300.
      MoonSynth Ticks per Pulse = 2048 / 300 = 6,82666.
00:25 - So... that means... Ticks per Pulse is...
      32 divided by ( PpQN * 15625 / Tempo ).
01:55 - Okay again, now I'm implementing fake midi command processing so that
      I could get even one simple song to play. I'm ignoring most commands,
      just reading them in, except Note On events. Of course, I couldn't get
      very far before crashing into that infamous Running Status trick.
      Briefly, all midi commands use seven bits, the highest bit is always
      set and the lowest four tell the channel to use. So there are only
      three bits' worth of commands, but to make it more interesting and
      efficient, you can insert a new command data byte without the actual
      command if it's the same command as the previous one... because all
      data except metadata has the highest bit always clear. This isn't too
      difficult to implement of course, but it's getting too late to deal
      with this kind of aggravation! Night-night...

270304, Saturday
14:10 - My eyes grew moist as success flooded my immediate surroundings.
      Getting running status to work was a piece of cake. After that I needed
      to recall that a note on with a velocity of 0 is actually a note off
      and I can ignore it for the time being... and, I spent an hour trying
      to figure out what is wrong with my delta times. The reason was of
      course that I'd forgotten to keep a running count and instead sort of
      used the delta time itself as the time offset. And then... it finally
      worked! MoonSynth actually can now play a song! I picked Matoya's theme
      from FF1 as the suitable midi, in part due to it being the only type 0
      midi file which is the simplest to read.

280304, Sunday
18:45 - Oh dear, there seems to be a problem. For some reason, after extended
      playing, all channels were stuck and the music was only playing on one
      channel. That must mean the Playing flag is stuck on some channels...

080404, Thursday
21:20 - The reason was of course the hacky datastream looping. I moved the
      stream end check inside the main stream dividing loop and after a few
      trial runs and fixes that fixed it. Superb. ^_^

260404, Monday
20:20 - What I need now is to be able to read more midi file types. Most are
      probably type 1, and as such contain multiple tracks. I can read
      a single track already, but multiple ones need to be combined somehow.
      I guess I'll do it so that each track is read into a temporary
      datastream, which is then mixed into the main datastream. One problem
      arises from my handling of the tempo, since I currently do the tempo
      changes while reading and not while playing. Probably that must be
      changed so that the tempo changes are effected as technically they
      should have been all along.

290404, Thursday
00:15 - Okay, the inner data streams are now going to be built as follows...
      The whole stream is qword-aligned. Each entry starts with the trigger
      time in MoonSynth internal ticks, of which there are 2048 to a second.
      Then there is the command. If the command requires a midi channel, that
      comes next. Otherwise there's two or three data bytes available. If
      more data space is needed, the following qword block is available by
      setting the first byte to FF. A normal tick count must always have the
      highest bit clear. (so max song length is one megasecond, or 12.1 days)
        tick : dword;
        command : byte;
        [channel \ data] : byte;
        data : word;
        [more data : qword;]

      Since midi files can have multiple tracks, they are read in one at
      a time into a ministream, which is then mixed into a superstream. Since
      there are some midi commands that can be simplified for internal use,
      that superstream is then parsed once more while copying to the final
      playback datastream.

      The commands available go as follows...
9x: 2: Note On: Channel, Note, Velocity. Note becomes a word value at
        playtime. Zero-velocities are converted to noteoffs while
        pre-processing. If the same note is already playing, it gets
        an automatic noteoff before the new noteon.
8x: 3: Note Off: Channel, Note, Faderate/Velocity. A noteoff will scan the
         desired chn and issue fadeout+sustoff for the voice playing this
         note.
Ax:    Aftertouch - not implemented for now.
Bx:    Controller commands - in midi two data bytes: controlnumber and value.
 121: 0: Controller Reset. No data. Puts all controllers to default state.
 1:   1: Vibrato: Channel, coarse value. Fine adjustment from controller 33
         is ignored. This might affect vibrarate as well as vibradepth.
         Usually modulation only affects depth, and around values of
         half-octave up and down. Vibradepth is internally stored as maximum
         value to move away from the center note in mininotes. Since the
         coarse midi values go from 0 to 127, that can be divided by 7,
         resulting in a vibration of 0..18 mininotes. Vibrarate should be
         around 3-7 Hz, and maybe that could also increase depending on the
         depth.
 6:   6: Data Entry Coarse. Channel, Coarse Number, Fine Number.
 38:  6: Data Entry Fine.
         When reading the midi tracks, the value half that isn't entered gets
         the highest bit set. Compressing into datastream allows for both
         coarse and fine data entry to be combined into one if they are
         consecutive commands and on the same channel.
 101: 5: Set Data Parameter Number Coarse. Channel, Coarse Number, Fine Num.
 100: 5: Set Data Parameter Number Fine.
         When reading the midi tracks, the value half that isn't entered gets
         the highest bit set. Compressing into datastream allows for both
         coarse and fine parameter number setting to be combined into one if
         they are consecutive commands and on the same channel.
 7:   7: Channel Volume Coarse. Channel, Volume:byte. The fine volume from
         #39 is ignored. The midi volume is 0..127, but the internal channel
         volume uses a juicy 0..32768 range. Linearity won't be good, so the
         0..127 are mapped first into 0..255 for datastream storing, and then
         translated to 0..32768 with a nicely curved lookup table.
 10:  A: Panning Coarse. Channel, Pan. Fine Panning from #42 is ignored. The
         midi pan gets SHL 1 to range 0..254, and values >= 129 get +1.
 123: F: All Notes Off. No data.
 120: F: All Sound Off. No data. These both issue noteoffs to all playing
         notes on all channels.
Cx: C: Program Change. Channel, New Instrument number.
Dx:    Channel Pressure - Not implemented.
Ex: E: Pitch Wheel. Channel, Pitch [word]. This value is placed in the
         channel's PitchDeltaTo variable. PitchDelta draws closer to the
         target at each tracking pass: Delta = (3*DeltaTo+Delta+2) / 4. Pitch
         wheel range is set with command 6, using parameter number 0.
         PitchRange gets the number of mininotes the total range should be;
         coarse data entry is in multiples of 32 for one seminote each, and
         fine entry should be cents, whatever they are. The default range is
         4 semitones, or 128 mininotes. The actual pitch is calculated at
         every tracking pass into ENote from (Delta*Range)/16384 - (Range/2).
F0:    SysEx - Not sure what to do with all these. For now this can be
         skipped, reading past data until the end value F7.

010504, Saturday
03:45 - Yes, finally! MoonSynth compiles and runs again, although midi
      playback is half broken. Technically it should now be able to read in
      type 1 midi files. I made some stupid novice mistakes as usual and that
      drew out this programming effort into late night. But hey, it's the
      first of May! Since I changed the datastream format slightly I'll need
      to update the tracker as well to handle the new quadword format. One
      more note before I collapse in bed: some instruments, the very first
      one in particular, start out with an annoying pop. Must find out why.
10:30 - Now it plays the same old Matoya midi again.
14:10 - And now it does so using a separate instrument for each channel! :D
      What I've accomplished is a basic midi channel scheme. The instrument
      thing is actually a hack since I was too lazy to make it work properly,
      so now it only sets the channel instruments as the song is loaded.
      However, it still doesn't load multiple tracks. Crashed as I attempted
      that. The chunk length appeared to not be read correctly.

040504, Tuesday
00:40 - Wowie, I even got it to load type 1 midi files with multiple tracks!
      Just forgot to read in a signature before the chunk length. And it can
      play them... however, the annoying pop is a bit more weird. It appears
      as though the volume envelope still has a bug, as the very first value
      it gives seems to be a ridiculously high one, and the desired fade in
      only works from the second value on...
01:00 - Easy enough to fix, just needed to preset the envelope value to 0 at
      instrument initialization. Not sure why as I thought the envelope value
      would always be calculated before it got used, the tracking code being
      before the mixing code, but well... it works so I won't complain. :p
01:30 - It plays midi files really nicely now! ^_^ Of course, still using
      just the lousy hardcoded instruments, and there's some nasty sound
      overlapping or something causing clipping. And for some reason, a small
      number of midi files appear to hang the tracking code. Like right at
      the start of the third Orc music of WarCraft 2. Oh, and it might just
      be my instruments but it's almost as if all the voices are an octave
      too low... didn't I fix something like this at some point?
02:50 - The strange hangups with some pieces were due to my broken hack that
      was supposed to ignore the percussion channel. Fixed that. I carefully
      checked my frequencies and octaves and: MIDI has 11 octaves specified,
      the lowest C-0, note 0, being about 8 Hz. Since I wanted extra room
      under 8 Hz I added three octaves long ago, bringing my C-0 to around
      one Hertz. This is not a problem as I calculate the frequencies
      starting from the highest, which for me is the same frequency as for
      midi. I listened carefully to some midis and it would seem that the
      sounds are at the right height, the bass sounds just aren't as audible
      since they all have the same amplitude but low and high sounds should
      be bigger to be perceived as loud relatively. I'll have to give another
      listen still, trying what if it sounds better at an octave higher. I'm
      pretty exhilarated that even the Quest For Glory 4 soundtrack midi from
      Quest Studios loaded and played, though it fell out of synch during the
      title theme.
23:05 - The processor usage is also not excessive. The falling out of synch
      that happens with some songs is likely due to tempo changes, since I'm
      not handling those properly. Guess I should try to fix that now.

060504, Thursday
01:45 - Trying to implement a new scheme for the tempo stuff. Tempo changes
      will now be implemented during playing and the datastream uses midi
      pulses which will be converted to MoonSynth ticks only while playing.
      The tempo change becomes internal command #4, with a tri-byte value.
02:50 - Functions like a dream! Which is appropriate since I'm in need of
      sleep. Now songs seem to stay in good synch. I am slightly confuzzled
      by an anomaly in the QfG4 soundtrack though. As I understand, the
      default tempo should be 500000, and midi files should change the tempo
      to whatever they like right at the start of the song. The Space Quest 3
      midi files I have for example affirm the tempo at pulse 0. The QfG1
      soundtrack only affirms the tempo after a few seconds, but it actually
      sounds like playing at the right speed with the default value before
      setting the tempo. However, the QfG4 title starts notably too fast, and
      the tempo is only set properly a few seconds into the file! Oddly other
      midi players seem to start out with the right tempo too.
03:30 - Need sleep even more. But well, while many songs play correctly,
      a few do not. Notably, a midi transcription of Bach's Toccata and Fugue
      has some incorrect tempos, mainly too slow. In addition I wrote a quick
      bit of code that adjusts oscillator volume linearry according to the
      note velocities but that started muting some notes. Perhaps it was just
      because of the linearity as analogically volumes must be exponential...

070504, Friday
02:05 - Agh. Implementing dummy handling of SysEx messages solved the tempos;
      simply ignoring those messed up the delta times at the start of the
      song a little. Strangely two major midi files still won't load. One is
      the QfG3 soundtrack, which works just fine until the file is loaded -
      then it states that an overcall has happened and freezes. This means
      that for some reason the mixer is called before the previous call has
      finished. I don't see why this is so. Another file is the Silpheed GM
      soundtrack, which crashes the whole loader after reaching a "subliminal
      message" in the file. *rushes back to code, returns in a few minutes*
      Okay, it was just a string variable overrun, easy to fix. Now Silpheed
      loads up properly too. Still, I'm unsure of the QfG3 anomaly.

080504, Saturday
01:30 - As I thought, the QfG3 problem was likewise a silly memory overflow.
      With that fixed the file loads glitchlessly. I'll implement a few
      things now as I'm on a roll, starting with instrument changes...
01:35 - Done. Now to add some sort of volume. I'll let global volume wait
      until later, but that can be applied at the final mixing phase of the
      cycle. Apart from that we've got a channel volume, 0..255, default 180,
      mapped to a nice 0..32768 curve, applied after mixing oscillators
      together... the volume envelope already works, being linear values,
      affecting each oscillator... and there's the velocity, which is
      calculated at note on as specified earlier, mapping 0..255 to the same
      32768 curve. One site suggested this formula for the curve:
      40 log (Volume/127). I think this means 40*ln(vol/127). The result is
      in decibels, each ten of which halving the amplitude. I'll precalculate
      a table for these, though using (Volume+2)/129 instead so the lowest
      volumes are not all zero. The range used is higher than midi volumes,
      0..255, since volume sliding must be used to make sure no nasty pops
      happen.
15:35 - Fixed a bug in the oscillator velocity formula that made some
      velocities way wrong... and now I'm calculating the volume table. This
      is going to take a few hours probably. I can't think of any better way
      of doing this while retaining perfect values. I use the windows
      calculator... ln(volume/259)... swap +/-... *4... Memory Set... 0.5 to
      the power of Memory Recall... *32768. Check the hex value, write it in
      my table. Repeat 250 times.
16:00 - Ah, actually there is a better way. It's called QBasic. :p
17:25 - Well, that was done quickly. The volumes work nicely too. Now to see
      about the pitch wheel.
21:45 - I recalculated the sine wave so it uses the whole 16-bit range. Much
      better now. Also rebalanced the instrument generation volumes, as it
      appears that a sine wave has an average amplitude of 20844, while the
      skvare has 32768 and the triangle is maybe 15360. I also think the
      pitch wheel functions, even with the range setting. Perhaps I should
      implement the note offs now...
23:00 - Easier than I thought, actually. Of course, even my best instruments
      sound like organs. The worst ones just break the ears. And percussion
      is ignored. Still, it sounds surprisingly good! ^_^

090504, Sunday
15:15 - I implemented a new layer in the midi file reading, now it reads
      bigger chunks into memory rather than reading from the file byte by
      byte. This of course made loading ten times quicker. Also added a pause
      button. :)

100504, Monday
21:55 - Then I added a file loading command, and that worked just fine too
      except that the QfG4 soundtrack crashed on loading. Now I figured that
      out and it was caused by a hacky handling of running status, which
      worked when reading from a file but not when reading from memory.

110504, Tuesday
02:05 - Now I added a nicer file loading system: it uses the standard windows
      file open box. Very handy for a console application. For now it only
      allows opening one file at a time though, no playlists. I think I need
      to add more waveforms too, so I changed the system so that technically
      a large number can be supported easily. However, there was a strange
      and noticeable detuning of instruments that have a large amount of FM
      on them, as generated by my creation code. There shouldn't be, since
      I'm not using linear FM that modulates the Hz, but logarhitmic FM that
      moves along my mininotes. I'll need to write some code again that would
      dump the output into a file, and take a look at the graphic result...
      Ah, and it seems that one Super Mario 3 midi file doesn't load
      properly. I guess it's some sort of command I'm not handling with the
      right length or stuff like that.

120504, Wednesday
01:45 - Weird but true. This site talks about FM in a useful way, and it says
      that Linear FM is the true form, while exponential FM - modulation by
      musical intervals, the way I primarily used - makes the sound drift
      toward higher frequencies as the modulation gets louder. Anyway, I
      removed the square wave from my instrument generator since it was too
      loud and unbalancing. I also removed a cut-up sinewave that was too
      soft. Now it appears I have a few nice instruments that actually sound
      like kind of brassy in a surprisingly good way.
02:00 - I guess I'll upload this. Will have to work the midi loading bug out
      later since most seem to load just fine.

190504, Wednesday
15:00 - Caught a slight cold. :( I noted that the pitch wheel isn't supposed
      to affect notes that have received noteoff and are fading out. That was
      quick to fix, and songs like the QfG4 intro and the Attack music from
      Princess Maker 2 sounds much nicer now. Also the Mario midi file loads
      just fine now. I must've fixed that at some point. A number of FF7 midi
      files appear to have an unusual header though and crash upon loading.
22:00 - El tested and said even running the program made her comp very
      sluggish. This at 44kHz output and a 0.25 second mixing buffer, called
      six times a second. I made the buffer 0.5 seconds long, called four
      times a second, and output 33kHz, and it ran much smoother for her. I
      assume mixing too little too often wastes the resources causing this.
      I added three new modulation ratios for the instrument generation code,
      now there are 1:1, 1:2, 1:3.5 and 2:1. I also put in some dummy
      handling of various controllers whose effects I haven't implemented.
      The All Controllers Off command was important enough so I made it work
      properly. That makes Quest Studios soundtracks play better.

210504, Friday
19:20 - I introduced a tiny bug while implementing the new channel status
      bitflag: a note was played even if it hadn't properly got a note on if
      a note off was received within the same mixing length. Fixed it. Now
      songs like the WarCraft 2 music play properly again.

220504, Saturday
18:10 - Mixing seems somehow heavier now? I went through the mixing loop to
      add some proper comments for clarification, and while doing that noted
      I could improve the calculations by combining a few. That seems to have
      given a 5% speed increase. Now each playing channel appears to eat
      about 2% processing power on my 1GHz system. Not very refreshing. Let's
      see if changing mixing settings helps.
18:30 - Lowering mixing frequency is the biggest help now, I'm afraid. I'll
      try to get to the optimization part soon...
20:30 - So I tried profiling the program using gprof but as people who've
      attempted this might guess, of course it didn't work. Piece of junk
      profiling program.

181105, Friday
22:45 - Just revising a bit for the website update. ^_^ The program is almost
      exactly the same as before. In fact, few of my programs even agree to
      compile now that I got Free Pascal 2.0, since it doesn't allow
      tampering with a FOR..THEN variable inside the loop, and some other
      stuff. So I need to go over the errors and change FOR-loops to
      WHILE-loops... anyway, I'm quite happy to let MoonSynth rest as it is
      until I need a sound system for one of my too numerous other projects.
      Then I'll get around to putting in correct AM, percussion support, and
      hand-coded instruments.