A Simple Technique for Drum 'n' Bass

by SigFLUP

Good hello, hackers.

It's a pleasure to be addressing you all in this article.

If you're anything like me, you've always had a certain fascination with techno music.

In this article, I will be describing a rather simple technique for "playing" drum 'n' bass, that I've "discovered."

If you'd like to listen to this, you can download it at: hobones.dogsoft.net/2600_beat.mp3

O.K., now that you've listened to that, I'm going to describe this technique by embedding it into a program.  Now this program is a sampler of sorts - a sample that loops.

The loop is a drum loop that's 65,535 samples (or multiples of 65,535) in length.  This is so that a 16-bit number is perfect in describing where the speaker is.

Let's call this 16-bit number pos.  As far as the content of the loop is concerned, the Amen break works well.  Really anything that's drum 'n' bassy sounding works well.

In the MP3 above I use: hobones.dogsoft.net/test_loop.wav

Now if you just increment pos and wrap it around 0xFFFF (65,535) indefinitely, you get a very nice drum beat-nice, but boring.

The trick here is to imagine that the bits of pos are mapped to your keyboard.

I use, Q as the least significant bit, QWERTYUIASDFGHJK.  When you push bits, they get ORed together, the result being stored in both bits.

For example E held down with J, if E or J is "1", they both become "1".

Let's store the keyboard modifiers in an array called mod, so that mod[0] is Q.

This little routine ought to do the trick:

#define SET_BIT (x,y) (x|(1<<y))
#define TEST_BIT (x,y) ((x>>y)&1)

int mod_pos(int in)
{
  int pool, i, out;
  pool = 0;
  out = in;
  /* Get half the input */
  for (i = 0; i < 16; i++)
    if (mods[i] == 1)
      if (TEST_BIT(out, 1) == 1)
        pool = 1;
/* Compute and store where appropriate */
  for (i = 0; i < 16; i++)
    if (mods[i] == 1 && pool == 1)
      out = SET_BIT(out, i);
  return out;
}

Still increment and wrap pos but use mod_pos(pos) as the speaker position instead.

Fun isn't it!?

If your sample was ONE TWO THREE FOUR and you OR a significant bit with a lesser significant bit you get ONE THREE TWO FOUR, for instance.

Do you see what's going on here?

We're using the rhythm of binary numbers!  If you use a sample that has 2, 4, 8, 16, 32, 64, 128 ... and so on beats in it, all cuts will be done on the beat or on the half-beat or whatever.

Now let's add a couple of tricks.

Lets map the spacebar to mod[17] and add if(mod[17] == 1 && pool == 1) return SILENCE between the "Get half the input" for loop and the "Compute and store where appropriate" for loop.

We'll define SILENCE as someplace in the sample where the speaker is at rest.

Typically I find this to be byte zero of the sample.

See what happens now?  By holding down space and any combinations of bits you get a choppy sort of sound.  This is really good for producing silence breaks and coming back up on a beat.

The last trick is a little more complex but it produces a sound that is really quite acceptable to the listener.

Imagine that any change that mod_pos(pos) returns from pos is a new span.

The best way to describe what a span is is to show you:

pos            1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
mod_pos  1 2 1 2 3 4 3 4 5 6   11   12 13 14 8  7  8  9  10 9 ...
span           A A B B B B C C C C  D  D  D  D  E  F  F  F  F  G ...

Any non-linear return from mod_pos is considered a new span.

Let's introduce a new variable, j.

If we only modify j at the beginning of each new span to mod_pos and increment it by one the rest of the time it follows mod_pos:

int lpos;                       /* stores the previous return from mod_pos */
int pos2                        /* return from mod_pos */
float j, speed;                 /* j and how much we increment it, speed */

/* fill an audio buffer, buffer, with length len */
void audio(unsigned char *buffer, int len)
{
  int i;
  int j_int;
  int pos2;

  /* HERE */
  for (i = 0; i < len; i++) {
    lpos = pos2;                /* store previous return from pos */
    pos2 = mod_pos(pos);        /* get the next one */
    if ((pos2 - 1) % 0xffff != lpos)    /* if pos2 is not linear store it in j */
      j = (float) pos2;
  }

  /* HERE TOO */
  j += speed;
  pos++;                        /* increment and wrap pos */
  pos %= 0xffff;
}

Now, you may be asking yourself why j is a float.

It's a float so that we can increment it by fractions of one.  Say 0.5f.

We store how much we're incrementing it in speed.  If speed is two, we are now incrementing j by two instead of one and we get a fast beat that is still on beat.

You see what I'm talking about?

The next thing we want to do is put a boundary on j so that it can't span across one beat into another.

We can do this by replacing /* HERE TOO */ with:

j_int = (int) j;
j_int &= 0xffff;
if ((j_int - pos2) > (0xffff / NUMBER_OF_BEATS_IN_SAMPLE))
  buffer[i] = SILENCE;          /* span spans over one beat */
    else
  buffer[i] = sample[j_int];    /*sample is our sample data */

Now we need a good place to change the speed.

Imagine that keys Z and X represent a pitch-bender.  If we map Z to bend_up and X to bend_down we can replace /* HERE */ with:

int q;
q = 0;
if (bend_up == 1)
  speed *= CONSTANT;
else
  q++;
if (bend_down == 1)
  speed /= CONSTANT;
else
  q++;
if (q == 2)
  speed = 1.0f;

This will bend up or down by CONSTANT (I find 1.009f is nice) if Z or X is pressed, otherwise it will leave speed at 1.0f.

Do you see what this does?

If you press QWERTYUIASDFGHJK so that you have one repeating beat and bend up or down, you get a really nice effect.

You can download this complete program from: hobones.dogsoft.net/dnb.tgz

It loops the first 65,535 samples of the audio file you provide as an argument.  You need Sound Exchange (SoX), Simple DirectMedia Layer (libsdl), and you need to be able to compile things.  This is a script, so you can run it directly.

Good luck, and send me an email, if you make any music with this or improve the technique, to pantsbutt@gmail.com.

Shouts to Citadel, RaDMAN, Jason Scott and the BlockParty crowd!


dnb.c:

#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>

#define CONSTANT 1.009f

SDL_Surface *d_screen;

int white, black;
unsigned char *sample;
int pos, lpos;
int pos2;
float beat_pos, speed;
int bend_up, bend_down;
int mods[17];
char bit_codes[] = {
  "qwertyuiasdfghjk"
};

void q_input(void)
{
  int i;
  SDL_Event event;
  while (SDL_PollEvent(&event)) {
    switch (event.type) {
      case SDL_KEYDOWN:
        if (event.key.keysym.sym == 'z') {
          bend_down = 1;
          bend_up = 0;
          speed = 1.0f;
        }
        if (event.key.keysym.sym == 'x') {
          bend_up = 1;
          bend_down = 0;
          speed = 1.0f;
        }
        for (i = 0; i < 16; i++)
          if (event.key.keysym.sym == bit_codes[i])
            mods[i + 1] = 1;
        if (event.key.keysym.sym == ' ')
          mods[17] = 1;
        break;
      case SDL_KEYUP:
        if (event.key.keysym.sym == 'z') {
          bend_down = 0;
          speed = 1.0f;
        }
        if (event.key.keysym.sym == 'x') {
          bend_up = 0;
          speed = 1.0f;
        }
        for (i = 0; i < 16; i++)
          if (event.key.keysym.sym == bit_codes[i])
            mods[i + 1] = 0;
        if (event.key.keysym.sym == ' ')
          mods[17] = 0;
        break;
    }
  }
}

#define SET_BIT(x,y) (x|(1<<y))
#define TEST_BIT(x,y) ((x>>y)&1)

int mod_pos(int in)
{
  int out, pool, i;
  pool = 0;
  out = in;
  for (i = 0; i < 16; i++)
    if (mods[i + 1] == 1)
      if (TEST_BIT(out, i) == 1)
        pool = 1;
  if (mods[17] == 1 && pool == 1)
    return 0;
  for (i = 0; i < 16; i++)
    if (mods[i + 1] == 1 && pool == 1)
      out = SET_BIT(out, i);
  return out % 0xffff;
}

void draw_vline(int x, signed char h, int color)
{
  int i, j;
  unsigned char *pix, *src;
  pix =
    (unsigned char *) ((int) d_screen->pixels + ((h + 128) * d_screen->pitch) + (d_screen->format->BytesPerPixel * x));
  src = (unsigned char *) &color;
  for (i = 0; i < abs(h); i++) {
    for (j = 0; j < d_screen->format->BytesPerPixel; j++)
      pix[j] = src[j];
    if (h < 0)
      pix += d_screen->pitch;
    else
      pix -= d_screen->pitch;
  }
}

void audio(void *userdata, Uint8 * stream, int len)
{
  int i, j;
  int qq;
  SDL_FillRect(d_screen, NULL, white);
  if (bend_up == 1)
    speed *= CONSTANT;
  if (bend_down == 1)
    speed /= CONSTANT;
  SDL_LockSurface(d_screen);
  for (i = 0; i < len; i++) {
    lpos = pos2;
    pos2 = mod_pos(pos);
    if (((pos2 - 1) % 0xffff) != lpos) {
      beat_pos = (float) pos2;
    }
    qq = (int) beat_pos;
    qq &= 0xffff;
    if (abs(qq - pos2) > 0x7ff)
      stream[i] = 0x7f;
    else
      stream[i] = sample[qq];
    beat_pos += speed;
    pos++;
    pos %= 0xffff;
    draw_vline(i, (signed char) stream[i], black);
  }
  SDL_UnlockSurface(d_screen);
  SDL_UpdateRect(d_screen, 0, 0, 0, 0);
}

SDL_AudioSpec audio_spec;

int main(int argc, char **argv)
{
  int i;
  FILE *fp;
  signed char j;
  SDL_AudioSpec spec;
  if (argc < 2)
    exit(-1);
  if (!(fp = fopen(argv[1], "rb"))) {
    perror(argv[1]);
    exit(-1);
  }
  sample = (unsigned char *) malloc(0xffff);
  fread(sample, 1, 0xffff, fp);
  fclose(fp);
  SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_TIMER);
  spec.freq = 22050;
  spec.format = AUDIO_S8;
  spec.channels = 1;
  spec.samples = 512;
  spec.callback = audio;
  if (SDL_OpenAudio(&spec, &audio_spec) < 0) {
    printf(SDL_GetError());
    exit(-1);
  }
  if ((d_screen = SDL_SetVideoMode(512, 256, 16, 0)) < 0) {
    printf(SDL_GetError());
    exit(-1);
  }
  white = SDL_MapRGB(d_screen->format, 255, 255, 255);
  black = SDL_MapRGB(d_screen->format, 0, 0, 0);
  pos = 0;
  lpos = 43;
  speed = 1.0f;
  bend_up = 0;
  bend_down = 0;
  bzero(mods, 256);
  SDL_PauseAudio(0);
  for (;;)
    q_input();
}

Code: dnb.c

Return to $2600 Index