Close
0%
0%

Beverly-Crusher: bit crushing toy for 1-bit audio.

I had been looking for a tool to convert audio down to 1-bit depth but gave up and wrote my own. Supports export for Arduino sketch.

Similar projects worth following
Here I am offering an audio crushing program which also makes exporting to arduino sketch extremely easy so that one can experiment with 1-bit audio and manipulate sample rates rapidly.

The github has source to the crusher which compiles under Linux and can allow you to preview your compressed audio through ALSA prior to generating the Arduino sketch.

The array of samples that is generated can be used in other embedded projects without specifically being an arduino but you will need to modify the playback code accordingly.

# ./crusher --input filename.raw --alsa --bitrate 10

beverly-crusher (build 200) [http://electronoob.com]

Released under the terms of the MIT license.
Copyright (c) 2014 electronoob.
All rights reserved.

aliceazzo has awesomely granted me permission to use her artwork for my project.

This image is not public domain.
Image is © aliceazzo [http://aliceazzo.deviantart.com/].

This project is not quite complete but very close to completion.

I wanted to play about with generation of sound with a microcontroller so I wrote a program to do the conversion and generate a working Arduino sketch automatically.

I was fortunate to find what I consider to be a fantastic audio sample to work with on freesound.org.

The audio sample

This work is licensed under the Attribution Noncommercial License.
http://www.freesound.org/people/Speedenza/sounds/203975/


Demonstration


In the demo it says --input filename doesnt work but thats now out of date, I just pushed a commit to github a few minutes ago providing code for input source file. Saves the hassle of opening your raw audio file in a hex editor and exporting to a header file.. You can now skip that step.

Here I demo the fruits of my labour with the worlds most tiny embedded youtube video (which I think hackasay should fix). This is where I explain in detail how this project works and what one needs to do in order to reproduce the project using their own audio samples.

  • 1 × atmega 328 Microcontroller with Arduino bootloader.
  • 1 × speaker junkbox speaker.
  • 1 × linux compile the crusher source code.
  • 1 × an audio sample something you want to be played back through the microcontroller.
  • 1 × audacity wave file editor for modifying your audio sample and export to 8-bit unsigned raw-headerless.

View all 7 components

  • Music and remixing it at compile time

    Electronoob10/10/2014 at 15:35 0 comments

    Here you can see three functions which all playback the sound samples, just in different ways...

    playback(); plays back a sample forwards.

    playback_r(); plays the sample backwards.

    playback_s(); plays the sample forward but at a reduced speed.

    void playback(prog_uchar *sample_ptr, unsigned int length) {
            unsigned char bite; int col; col = 0; int i;
            for(i=0;i<length;i++){
              bite = pgm_read_byte_near(sample_ptr + i);
              unsigned char mask = 1; unsigned char copy = bite; int z;
              for (z=0;z<8;z++) {
                digitalWrite(SPK_PIN, copy & mask);
                copy = copy >> 1;
                delayMicroseconds(25);
              }
            }
    }
    void playback_r(prog_uchar *sample_ptr, unsigned int length) {
            unsigned char bite; int col; col = 0; int i;
            for(i=0;i<length;i++){
              bite = pgm_read_byte_near(sample_ptr + length - i);
              unsigned char mask = 1; unsigned char copy = bite; int z;
              for (z=0;z<8;z++) {
                digitalWrite(SPK_PIN, copy & mask);
                copy = copy >> 1;
                delayMicroseconds(25);
              }
            }
    }
    void playback_s(prog_uchar *sample_ptr, unsigned int length,unsigned int speed) {
            unsigned char bite; int col; col = 0; int i;
            for(i=0;i<length;i++){
              bite = pgm_read_byte_near(sample_ptr + length - i);
              unsigned char mask = 1; unsigned char copy = bite; int z;
              for (z=0;z<8;z++) {
                digitalWrite(SPK_PIN, copy & mask);
                copy = copy >> 1;
                delayMicroseconds(25*speed);
              }
            }
    }

    By mixing up and fooling around with chopping up the samples, I was able to create some interesting variations with just the 4 basic samples that I started with.

    int z;
    for (z = 0; z < 4; z++){
      playback(onebitraw_1, BC_BYTE_COUNT_1 /4);
      playback(onebitraw_2 + (BC_BYTE_COUNT_1 /4), BC_BYTE_COUNT_1 /4);
      playback_r(onebitraw_3 + (BC_BYTE_COUNT_1 /2), BC_BYTE_COUNT_1 /4);  
      playback(onebitraw_2 + ((BC_BYTE_COUNT_1 /4) + (BC_BYTE_COUNT_1 /2)) , BC_BYTE_COUNT_1 /4);
    }

    That code block allowed me to cut up the samples, maintain quantization and make the pattern far more interesting.

  • --input filename, is now supported

    Electronoob10/06/2014 at 23:47 0 comments

    In the demo it says --input filename doesn't work but thats now out of date, I just pushed a commit to github a few minutes ago providing code for input source file. Saves the hassle of opening your raw audio file in a hex editor and exporting to a header file.. You can now skip that step.

  • Crusher main.c description.

    Electronoob10/06/2014 at 16:45 0 comments

    Click here to view the complete source on github.


    All I do here to convert from 8 bit to 1 bit samples is to decide a cut off point approximately midway in the value between 0 and 255 depending on how it sounds when I preview.

    In this example, we check is this current sample greater than decimal 128 ?

    If it is we say then that this bit is on and off otherwise.

    This is stored in an array which is used later for playback and dumping for microcontroller compatible C.

    for (i = 0; i < rawDataSize; ++i)
    {
      for (j = 0; j < BUFFER; j++)
      {
        if (rawData[offset] > 128)
        {
          buf[j] = 255;
        }
        else
        {
          buf[j] = 0;
        }
        int z;
        for (z = 0; z < output_bitrate_divisor; z++)
        {
          offset++;
        }
      }
      /* snip snip snip */
    }
    

    In order to offer a sample rate down conversion I used a naive approach of just skipping samples in the source array.

    Once the samples have been converted to a 1-bit resolution it's now simply a case of going through those converted samples and joining them together to make a string of 8 bits; aka 1 byte of storage.

    if (mode == P_STDOUT)
    {
      /* get 8 bytes from buffer */
      for (j = 0; j < BUFFER; j = j + 8)
      {
        if (process_begun)
        {
          printf(", ");
        }
        else
        {
          process_begun = 1;
        }
        if (!(obsample_bytes % 15))
        {
          printf("\n");
        }
        /* move through those 8 bytes and convert to binary. 0x00 = 0, 0x255 = 1. */
        unsigned char converted_bits;
        converted_bits = 0;
        for (k = 0; k < 8; k++)
        {
          if (k >= BUFFER)
          {
            /* trying to read past buffer? */
            fprintf(stderr, "Critical error: Tried to exceed BUFFER.\n");
            exit(1);
          }
          converted_bits = converted_bits << 1;
          converted_bits = converted_bits + (buf[j + k] == 255);
          obsample_bits++;
        }
        printf("%#04X", converted_bits);
        obsample_bytes++;
      }
    }

  • Arduino example source code

    Electronoob10/06/2014 at 15:48 0 comments

    /*
    
      beverly-crusher (build 82) [http://electronoob.com]
    
      Released under the terms of the MIT license.
      Copyright (c) 2014 electronoob.
      All rights reserved.
    
    */
    
    #include <avr/pgmspace.h>
    
    /* truncated example sample see github for working and complete example */
    prog_uchar onebitraw[] PROGMEM = {
      0XFF, 0XFF, 0XEF, 0XFF, 0XFB, 0XFF,...
    }
    
    #define BC_BIT_COUNT 23040
    #define BC_BYTE_COUNT 2880
    #define SPK_PIN 5
    
    void setup(){ pinMode(SPK_PIN, OUTPUT); }
    
    void loop(){
    	unsigned char bite;
            int col; col = 0;
            int i;
            for(i=0;i<BC_BYTE_COUNT;i++){
    	  bite = pgm_read_byte_near(onebitraw + i);
              /* let's break up our byte into it's constituent parts. */
              unsigned char mask = 1;
              unsigned char copy = bite;
              int z;
              for (z=0;z<8;z++) {
                digitalWrite(SPK_PIN, copy & mask);
                copy = copy >> 1;
                delayMicroseconds(227);
              }
            }
    }

    https://github.com/electronoob/beverly-crusher/blob/master/arduino/example/example.ino

View all 4 project logs

  • 1
    Step 1

    git clone https://github.com/electronoob/beverly-crusher.git

    cd beverly-crusher

    make all



    Syntax

    To output file direct to alsa default device:

    ./crusher --input mysing.raw --alsa 

    To write conversion to file:

    ./crusher --input mysing.raw --output crushed.raw 

    To dump output to standard output:

    ./crusher --input mysing.raw --stdout 

    To set the output bitrate, enter a positive integer to divide the bitrate by:

    ./crusher --input mysing.raw --alsa --bitrate 2

View all instructions

Enjoy this project?

Share

Discussions

James Newton wrote 08/11/2022 at 17:27 point

You might find this interesting:
https://romanblack.com/BTc_alg.htm

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates