• Order of the Butterfly
    Order of the Butterfly
    BalrogSoft
    Posts: 171 from 2006/10/6
    From: Spain
    Once upon a time, a 4k intro that didn't have music, it was a very sad intro, because it couldn't dance...

    To solve this problem and bring some fun and dance to our intro, I made a small and simple 4k synth.

    How it works?

    All the song is defined on two arrays, seq, and patterns.

    Patterns contains all patterns with notes to build the song, it's similar to patterns on nanoloop or protracker, numeric values are used as notes, start from 1 to 254, 255 means that the sound is stopped on this position note, and 0 means that last note remain played.

    Patterns array for this piece of a familiar song to all C64 users, looks like this (SILENCE notes are not used):

    First some defs need:

    Code:

    #define TONEFREQ 220.0f
    #define TONEBASE 1.059463094359f

    #define CHANNELS 2
    #define PATTERNS 5
    #define PATTERNLEN 16
    #define SONGLEN 4

    #define NOTELEN 2048
    #define PATLEN 32768

    #define SILENT 255


    Code:

    static char patterns[PATTERNS][PATTERNLEN]=
    {
    {13, 18, 20, 18, 25, 18,20,18,13, 18, 20, 18, 25, 18,20,18,},
    {22, 0, 0, 0, 0, 0, 20,0, 23,22,0, 20, 0,0,22,18},
    {0,0, 0,0,0,0, 18, 18, 18,17,0,15, 0, 0, 17, 18,},
    {0, 0, 0,0, 0, 0, 0, 0, 0,13,15,13,20,18,13,20},
    {18, 0, 0,0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0},
    };


    And this is our song, very simple:

    Code:

    static char seq[CHANNELS][SONGLEN] =
    {
    {0, 0, 0, 0},
    {1, 2, 3, 4}
    };


    The song uses two channels with four patterns. The first channel repeats pattern 0 continously (first row on patterns) and the second channel plays patterns 1, 2, 3 and 4 (the rest of patterns defined on the array)

    The synth apply an envelope filter with 4 phases, attack, decay, sustain and release, this filter is defined for every channel here:

    Code:

    static float env_chan[CHANNELS][8] =
    {
    {
    0, 0, // a
    256, 1, // d
    2048, 0.2, // s
    3072, 0.6, // r
    },
    {
    0, 0, // a
    500, 1, // d
    7000, 0.8, // s
    8000, 0.7 // r
    }
    };


    Every phase have two values, the first value, is the pointer of the current note when this phase starts, and second is the volume applied to this phase, from 0 to 1.

    And here is where magic happens, the code are commented, so if you have any question, go ahead!

    Code:

    static short pos[2];
    static float freq;
    static char note[2];
    static short note_pos;

    static byte ch;
    static float env,hz;
    static float *env_val;
    static char pat_ptr;

    // Two oscillators are availabe, sin and saw
    // first parameter are hertzs and second parameter is time

    static float osc_sin(float f, int tm)
    {
    int d = tm*720*f/8000;
    return (sinus[(d%720)]+1.0)/2.0;
    }

    static float osc_saw(float f, int tm)
    {
    int r=(2*FREQUENCY / f);

    return (tm % r) * (1.0/r);
    }

    static void synth4k(void)
    {
    // Fill the audio buffer
    for (n = 0; n < BUFFERSIZE; n++)
    {
    // Set a pointer to the current buffer
    // and set the current buffer to zero
    byte *pb = &p1[n];
    *pb = 0;

    // Process our song to generate a wave for every channel
    for (ch = 0; ch < CHANNELS; ch++)
    {
    // Set some pointers to save some bytes,
    // note contains the current note played on this channel
    // po is the current position of the wave note for filling our buffer
    // pat_ptr contains the current pattern note to be played on this channel
    char *not = &note[ch];
    short *po = &pos[ch];
    note_pos = (t>>11)%PATTERNLEN;
    pat_ptr = patterns[seq[ch][(t>>15)%SONGLEN]][note_pos];

    // If note is silence, don't process the song
    if (pat_ptr < 255)
    {
    // Change the current note to be played,
    // if it's zero the last note is preserved
    if (pat_ptr > 0 && t%NOTELEN == 0)
    {
    *not = ((pat_ptr&63)-1);
    *po = 0;
    }

    // Apply the envelope filter
    env_val = env_chan[ch];
    env = env_val[7];
    for (i=0; i<8; i+=2)
    {
    if (env_val[i] > *po)
    {
    float pr = ((*po-env_val[i-2])/(env_val[i] - env_val[i-2]));
    env = pr * (env_val[i+1] - env_val[i-1]) + env_val[i-1];
    }
    }
    // Set base frequency
    freq = TONEBASE;
    // Get the frequency for the note to be played,
    // it is a simple power function, but it is not available.
    for (j = 1; j < *not; j ++)
    freq *= TONEBASE;
    // Get the herzs need to play the specific note
    hz = TONEFREQ*freq;

    // Here we define the instruments for the channels,
    // Play with oscillators and apply more to the time,
    // or hz parameters, add different oscillators, use
    // your imagination to obtain different sounds!
    if (ch == 0)
    *pb+=env*(osc_saw(hz,t))*48;
    else
    *pb+=env*(osc_sin(hz,t+osc_sin(5,t)*10))*24;
    }
    // Increment the buffer position
    *po+=1;
    }
    // Increment the time position
    t++;
    }
    }


    The instruments are defined on synth4k, this is for channel 0:

    *pb+=env*(osc_saw(hz,t))*48;

    and for channel 1:

    *pb+=env*(osc_sin(hz,t+osc_sin(5,t)*10))*24;

    As you can see, instruments are composed of 3 parts, env*osc*volume, osc is the part where you can use your imagination to build your instruments, applying some oscillators on time, hertzs, adding different oscillators, ...

    To modify the octave, you should change TONEFREQ, it have an initial value of 220, 110 is an octave down, and 440 is an octave up.


    And here are all files available to build the startup code and synthetizer.

    http://www.amigaskool.net/download/4k_intro_2.lha

    [ Edited by BalrogSoft 18.05.2015 - 18:10 ]
    Balrog Software - AmigaSkool.net
    Mac Mini - MorphOS 3.8 - G4 1.5Ghz - Ati 9200 64 Mb
    Efika - MorphOS 3.6 - Ati 9200 64Mb - 80 Gb HD
    Amiga 1200D - OS 3.9 - Blizzard 603e/240mh
  • »14.05.15 - 00:04
    Profile Visit Website