Azasel music engine

hu, cd, scd, acd, supergrafx discussions.
Post Reply
tomaitheous
Posts: 88
Joined: Mon Jun 23, 2008 1:58 pm

Azasel music engine

Post by tomaitheous »

Ok, here's the isolated music/sound engine based on the work with Air Zonk engine. The sample format has been changed to uncompressed version. Instead of RLE compression for size, the samples now have range compression. In Azasel, the channel volume is treated as internal (similar to TL on FM chips) for the volume envelope system. Channel volume by the musician instead is routed through pan volume. The samples take advantage of this by taking the upper 3bits of the 8bit sample and subtracting that from TL (0x1F) for range compression. This means older samples used by PCE games and such play fine as long as they don't have the top 3 bits set. Also, there's is no sample length - a termination value of 0x2E is used instead (unless cut short by the length argument).

Anyway, the engine itself is now separate module in which (hopefully) you can incorporate into your own code/design. For now, there is no mml ascii converter or tracker. All songs must be composed in a pseudo MML structure using data defines. It's not so bad actually. I've include 3 examples songs and equates to facilitate the process. I've included the channel waveforms from Air Zonk which are located in the .wf file. Air_Zonk_WF_view.pce is a waveform viewer so you can see what the waveforms look like. I've only included up to 38 different waveforms. There's also a document describing the functions of this engine - Az_mml.txt. The document is meant for an up and coming MML ascii converter, but you can still use the information about the functions and FX (envelopes, vibrato, portamento,etc). MML.equ holds the additional info you'll need.

I've included a batch file and PCEAS just in case.

azasel.asm - the sound/music engine
mml.asm - and interface to Azasel engine. Provided gamepad support and onscreen display. Also contains the song file includes.

Things to note about the data define format:

".loop" = using a "." tells the assembler that it's a local label. Local labels exist inbetween regular labels. With local labels, you can re-use the label one.
"label:" = a regular label is defined with a ":". You can use these if you want, but you can't re-use the name once you declared it.
".db" = define byte. It defines a byte in a series/string of data. Use the "," to separate bytes. Some commands are WORD size, they must be stored as LSB,MSB format
"low()/high()" = low(some label or value) returns the LSB of a WORD data types. High returns the MSB. You only need this for jump style commands and loading a waveform.
"$" = if you want to use a hex number instead of a decimal number, prefix the value with a "$". You probably want to do this for negative numbers, especially WORD sized negative numbers.
The names of functions in the song examples are equates. They equate to a value. You can add/subtract/multiply/divide/use logic operations, etc on any value or function. The only function that you need to modify though, is the "rest" function. You add the rest period value to the function itself (it's an embedded operand in the command).

Also, PCEAS doesn't seem to like long lines of equates. I've broken my examples up into lines of less than 94 characters long. You'll get a syntax error on that line of it's too long. You don't need to end a line with a ",". I think that about covers it.

Oh, one more thing. Notes. PCEAS doesn't like "#" in the equate system, so all sharps use an "x". A normal note is "C2." and a sharp is "C2x". The middle number signifies the octave.

Oh, one more more thing. A command string ends with either: a "note" or a "rest" function. If you jump in a loop that has neither, the engine will lock in an infinite loop and freeze playback.

Edit: new url

Ver 1.0 - alexandria66.2mhost.com/~pcengine/music/Azasel.zip




.
Last edited by tomaitheous on Mon Nov 09, 2009 7:28 pm, edited 1 time in total.
User avatar
Gravis
Kitten Blaster
Posts: 79
Joined: Sun Jun 22, 2008 3:52 pm
Location: Deadman Wonderland

Re: Azasel music engine

Post by Gravis »

this is cool but to be honest, i was looking forward to the .mod player you talked about making: viewtopic.php?f=5&t=22#p69

btw, did you realize that that "Azasel" sounds like "asshole". if you dont believe me, walk up to someone and call them an "Azasel" and hear what they say.
tomaitheous
Posts: 88
Joined: Mon Jun 23, 2008 1:58 pm

Re: Azasel music engine

Post by tomaitheous »

Gravis wrote:this is cool but to be honest, i was looking forward to the .mod player you talked about making: viewtopic.php?f=5&t=22#p69
MOD schmod. Chiptunes rule. MOD format itself isn't really optimal for PCE. A variant of MOD, or just custom wavetable synth engine would be needed. The problem is, as with Azasel, is that you'd have to write a 'tracker' for it and you definitely want to be able to hear what you're working in realtime.
btw, did you realize that that "Azasel" sounds like "asshole". if you dont believe me, walk up to someone and call them an "Azasel" and hear what they say.
Uhm.. it sounds like Azazel. Ah-zai-zel.
tomaitheous
Posts: 88
Joined: Mon Jun 23, 2008 1:58 pm

Re: Azasel music engine

Post by tomaitheous »

another double post?
Last edited by tomaitheous on Thu Mar 19, 2009 5:52 pm, edited 1 time in total.
User avatar
MooZ
Site Admin
Posts: 402
Joined: Sun Jun 22, 2008 3:19 pm
Location: Lvl 3
Contact:

Re: Azasel music engine

Post by MooZ »

Here's a commented version. There're some still some uncommented constants (for example in the Azasel routine).
Attachments
contrib.zip
(148.91 KiB) Downloaded 284 times
User avatar
MooZ
Site Admin
Posts: 402
Joined: Sun Jun 22, 2008 3:19 pm
Location: Lvl 3
Contact:

Re: Azasel music engine

Post by MooZ »

I'm trying to generate triangle shaped tremolo using Bresenham line algorithm.
It's specified by 3 arguments : phase, rate and depth.
triangle.png
(734 Bytes) Downloaded 197 times
Following Bresenham with time as x and volume delta as y, we have at the initialization phase:

Code: Select all

deltaX = phase << 1;
deltaY = depth << 1;
x  = 0;
y  = 0;
sY = 1;

if(deltaX < deltaY)
	error = deltaX - (deltaY >> 1);
else
	error = deltaY - (deltaX >> 1);
Between x=0 to phase, y slope is positive. Whereas from phase to rate, it's negative. Also note that x slope is always positive.

Code: Select all

if(x == phase)
{
    y = depth;
    sY = -1;
    deltaX = (rate - phase) << 1;
    
    if(deltaX < deltaY)
    {
        error = deltaX - (deltaY >> 1);
    }
    else
    {
        error = deltaY - (deltaX >> 1);
    }
}
else if(x == rate)
{
    x = 0;
    y = 0;
    sY = 1;

    deltaX = phase << 1;

    if(deltaX < deltaY)
        error = deltaX - (deltaY >> 1);
    else
        error = deltaY - (deltaX >> 1);
}
And then we have :

Code: Select all

if(deltaX < deltaY)
{
    while(1)
    {
        if(error < 0)
        {
            ++x;
            error -= deltaY;
            break;
        }
        y += sY;
        error += deltaX;
    }
 }
else
{
    if(error > 0)
    {
        y += sY;
        error -= deltaX;
    }
    ++x;
    error += deltaY;
}

out_volume = volume - y;
What's bothering me is the loop for the case where deltaX < deltaY. I don't know if it'll kill performances :( The only way to avoid it is using standard DDA algorithm but we'll have to make a div and use 8:8 fixed point math.
Oh and the loop can be rewritten in a more readible way :

Code: Select all

while(error <= 0)
{
    y += sY;
    error += deltaX;
}
++x;
error -= deltaY;
User avatar
MooZ
Site Admin
Posts: 402
Joined: Sun Jun 22, 2008 3:19 pm
Location: Lvl 3
Contact:

Re: Azasel music engine

Post by MooZ »

Tadam, I finally had square wave and triangle tremolo working. I used the Boss TR-2 tremolo pedal. So like the previous post, I'm using depth/rate/phase values.
Here's the code: And here're 2 test roms: Values used are :
  • depth = 12
  • rate = 48
  • phase = 60
User avatar
MooZ
Site Admin
Posts: 402
Joined: Sun Jun 22, 2008 3:19 pm
Location: Lvl 3
Contact:

Re: Azasel music engine

Post by MooZ »

The next effect in the pipe in the good old arpeggio.
An arpeggio is a quick alteration of the note pitch between the base note, a semitone offset x and a semitone offset y. Each tone is played for 1 tick. So if we have A-2 as base note, x=3 and y=7 as offsets, we'll consecutively play A-2, C-3 and E-3.
According to MilkyTracker doc, if the speed is greater than 3 ticks the sequence is looped. It's turned off by setting the semitone offsets to 0.

The code for this effect is pretty simple :

Code: Select all

setArpeggio:
    semitoneOffset[0] = 0;
    semitoneOffset[1] = data[offset++];
    semitoneOffset[2] = data[offset++];
    currentSemitone = 0;
    nbSemitone      = 3;

Code: Select all

msxUpdate:
    note += semitoneOffset[currentSemitone];

    ++currentSemitone;
    if(currentSemitone >= nbSemitone)
        currentSemitone = 0;

    if(note > MAX_NOTE)    /* maybe unececessary. If it sounds like crap, blame the musician :) */
        note = MAX_NOTE;
    else if(note < 0)
        note = 0;

    freqLo = freqTableLo[note];
    freqHi = freqTableHi[note];

We can also define an extended arpeggio effect which takes a semitone offset table (and its size), a delay (in ticks) between each note and maybe a flag to tell if we are looping or not.
User avatar
Gravis
Kitten Blaster
Posts: 79
Joined: Sun Jun 22, 2008 3:52 pm
Location: Deadman Wonderland

Re: Azasel music engine

Post by Gravis »

MooZ wrote:I'm trying to generate triangle shaped tremolo using Bresenham line algorithm.
It's specified by 3 arguments : phase, rate and depth.

Code: Select all

if(deltaX < deltaY)
{
    while(1)
    {
        if(error < 0)
        {
            ++x;
            error -= deltaY;
            break;
        }
        y += sY;
        error += deltaX;
    }
 }
else
{
    if(error > 0)
    {
        y += sY;
        error -= deltaX;
    }
    ++x;
    error += deltaY;
}

out_volume = volume - y;
im not sure how you implemented this code but i think this version may require fewer instructions.

Code: Select all

if(deltaX < deltaY)
{
    while (!error & 0x80)
    {
        y += sY;
        error += deltaX;
    }
    error -= deltaY;
}
else
{
    if(!error & 0x80)
    {
        y += sY;
        error -= deltaX;
    }
    error += deltaY;
}
++x;
out_volume = volume - y;
Post Reply