Memainkan suara 4-bit ADPCM langsung dari arduino

Rancangan bangun berbasis arduino kurang lengkap tanpa adanya suara. Fungsi suara tergantung kebutuhan, yang paling diutamakan adalah informasi audio. Perangkat audio arduino yang umum digunakan adalah mp3 shield, dan data suara mp3 disimpan dalam kartu memori/SDCard.

Untuk memainkan file suara dengan Arduino atau musik arduino memiliki permasalahan :

  1. Arduino memiliki memori flash yang kecil (Uno 32KB, Mega 256KB)
  2. Port yang hanya mampu mengeluarkan (source/sink) arus sebesar 40mA setiap pin.
  3. Kecepatan clock arduino yang hanya 16MHz, membatasi sampling rate dan ukuran sample.

Untuk menyimpan data suara lebih panjang kita bisa menggunakan data terkompresi, Salah satu sistem kompresi sederhana adalah 4-bit ADPCM (Adaptive differential pulse-code modulation), yaitu kompresi data suara kedalam 4-bit sehinggan file suara PCM yang umumnya 8-bit atau 16-bit bisa diperkecil menjadi 4-bit. Arduino adpcm juga bisa digunakan sebagai alternatif penggunaan modul suara WTV020SD yang juga bisa memainkan file dalam format 4-bit adpcm (.ad4)

Dalam contoh ini saya menggunakan sample rate 8KHz dan ukuran(size) sample 9 bit, alternatif yang disediakan 16KHz – 8 bit. Dengan properti 8KHz-9Bit, kompresi 4-bit ADPCM hanya menggunakan 4Kb setiap detiknya, sehingga uno mampu menyimpan 6 detik data suara (ruang memory lainnya digunakan oleh program), sedangkan mega mampu menampung 62 detik data suara.

untuk memaksimalkan arus port Arduino, saya menggunakan speaker 32Ohm.

Urusan memperhalus suara yang akan dihasilkan saya menggunakan metode bridge (tanpa kapasitor kopling) dengan menggunakan pin 9 dan 10 yang terhubung ke Timer1.

komponen:

  1. Arduino Uno
  2. Speaker 32ohm/head phone

skema:

Sketch / program :

 
#include "Selamat datang 8000.h"

#define speakerA 9
#define speakerB 10

const int8_t IndexTable[16] = {
    -1, -1, -1, -1, 2, 4, 6, 8,
    -1, -1, -1, -1, 2, 4, 6, 8
};
int16_t StepSizeTable[89] = {
    7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
    19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
    50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
    130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
    337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
    876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
    2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
    5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};
struct AD4Header
{
  byte ad4Code;
  char ad4Sign[4];
  uint16_t ad4SampleRate;
};
#define ad4DataOffset 6

long predsample;
int8_t index;
uint16_t panjangSuara;
uint16_t sampleCounter;
byte *dataSuara;
bool PWMUpdate;
bool swap;
AD4Header ad4Header;
bool suaraDimainkan;

void setup(void)
{
  pinMode(speakerA, OUTPUT);
  pinMode(speakerB, OUTPUT);

  Serial.begin(9600);
  Serial.println("Memainkan suara 4-bit ADPCM dengan arduino");
  Serial.println("https://www.semesin.com/project/");
}

void loop(void)
{
  if(!suaraDimainkan)
  {
    mainkanSuara(dataSuaraSelamatDatang8000, sizeof(dataSuaraSelamatDatang8000));
  }
  else
  {
    lanjutkanSuara();
  }
}

void mainkanSuara(const byte *fileSuara, uint16_t ukuranFileSuara)
{
  dataSuara = (byte*)fileSuara;
  
  ad4Header.ad4Code = pgm_read_byte_near(dataSuara + 0);
  ad4Header.ad4Sign[0] = pgm_read_byte_near(dataSuara + 1);
  ad4Header.ad4Sign[1] = pgm_read_byte_near(dataSuara + 2);
  ad4Header.ad4Sign[2] = pgm_read_byte_near(dataSuara + 3);
  ad4Header.ad4Sign[3] = 0;
  ad4Header.ad4SampleRate = pgm_read_word_near(dataSuara + 4);

  Serial.println();
  Serial.println("Memainkan file suara");
  Serial.print("Sample Rate : ");
  Serial.print(ad4Header.ad4SampleRate);
  Serial.println("Hz");
  Serial.print("Ukuran : ");
  Serial.println(ukuranFileSuara);

  if(ad4Header.ad4SampleRate == 16000)
  {
    ICR1 = 512;
  }
  else if(ad4Header.ad4SampleRate == 8000)
  {
    ICR1 = 1024;
  }

  TCCR1A = _BV(COM1A1) | _BV(COM1B1);
  TCCR1B = _BV(WGM13) | _BV(CS10);

  panjangSuara = ukuranFileSuara;
  sampleCounter = ad4DataOffset;
  predsample = 0;
  index = 0;
  swap = false;
  suaraDimainkan = true;
}

void lanjutkanSuara()
{
  if(TIFR1 & _BV(TOV1))
  {
    TIFR1 |= _BV(TOV1);
    if (sampleCounter > panjangSuara)
    {
      stopPlayback();
    }
    else
    {
      byte data = pgm_read_byte_near(dataSuara + sampleCounter);
      if(!swap)
      {
        data >>= 4;
      }
      else
      {
        sampleCounter++;  
      }
      swap = !swap;
  
      int16_t sample;
      if(ad4Header.ad4SampleRate == 16000)
      {
        sample = (ADPCMDecoder(data & 0x0F)/128) + 256;
        OCR1B = 512 - sample;
      }
      else if(ad4Header.ad4SampleRate == 8000)
      {
        sample = (ADPCMDecoder(data & 0x0F)/64) + 512;
        OCR1B = 1024 - sample;
      }
      OCR1A = sample;    
    }            
  }
}
int16_t ADPCMDecoder(byte code)
{
  uint16_t sample;
  int16_t diffq;
  uint16_t step;

  step = StepSizeTable[index];

  diffq = step >> 3;
  if( code & 4 )
    diffq += step;
  if( code & 2 )
    diffq += step >> 1;
  if( code & 1 )
    diffq += step >> 2;
  
  if( code & 8 )
    predsample -= diffq;
  else
    predsample += diffq;

  index += IndexTable;

  if( index < 0 )
    index = 0;
  if( index > 88 )
    index = 88;

  if( predsample > 32767 )
    predsample = 32767;
  else if( predsample < -32768 )
    predsample = -32768;

  return predsample;
}

void stopPlayback()
{
  TIMSK1 &= ~_BV(OCIE1A);
  TCCR1B &= ~_BV(CS10);
  if(ad4Header.ad4SampleRate == 16000)
  {
    OCR1A = 256;
    OCR1B = 256;
  }
  else if(ad4Header.ad4SampleRate == 8000)
  {
    OCR1A = 512;
    OCR1B = 512;
  }
  digitalWrite(speakerA, LOW);
  digitalWrite(speakerB, LOW);

  suaraDimainkan = false;
}

contoh file suara (copy-kan ke folder sketch arduino)

Selamat datang 8000.h

untuk mengkonversi file .wav atau .mp3 ke file .ad4 bisa menggunakan software 4D-SOMO-Tool (tidak direkomendasikan menggunakan ad4Converter karena software ini tidak menyertakan header). kemudian file .ad4 tersebut dijadikan .h dengan software bin2c.