UP | HOME
جناوي

السيد مذهل
mr. fantastic

طالب هندسة برمجيات - مهتم بالرسوميات والهندسة الصوتية

Software engineering student - interested in graphics and audio engineering

مقدمة عملية في الموجات الصوتية الرقمية
نشر في Feb 19, 2021 بقلم السيد مذهل.

كما ذكرت مسبقاً في مقالة البكسل من وجهة نظر المبرمج وليس الخيال العلمي فأننا نعاني من جهلنا لتفاصيل عمل الكثير من الصيغ الرقمية التي نستخدمها يومياً وهذا بسبب الطبقات (abstractions) التي بنيت على مر السنين وتزعم المناهج النظرية فوق العملية وأنا في هذه المقالة سأتخذ منهج مغيار لكثير من المصادر التعليمية.

حتى نتمكن من كتابة أول موجة صوتية رقمية لنا هناك العديد من الطرق ومنها أستخدام مكتبة برمجية ولكن هذا سيخفي الكثير من التفاصيل وهذا ما لا نريده ولهذا الغرض سنعمل على أخرى مثل كتابة البيانات مباشرة إلى جهاز الصوت (وهذا سنستبعده لأن غير أمن وقد يعطل جهازك مع عدم الحذر) أو أستخدام معيار للإضافات الصوتية (Audio Plugins) كما هو مستخدم ببرامج الأستديوهات (وهذا صعب على المبتدئ) أو نستخدم صيغة صوتية بسيطة وغير مضغوطة فنكتب الموجات في ملف صوتي (وهذا ما سنعمل عليه في هذه المقالة).

صيغة wav

هذه صيغة معيارية لها عدة أوجه فيمكن لها دعم أكثر من عمق صوتي مثل 8 بت و16 بت و32 بت بأستخدام عديد صحيح أو كسري وهنا وقع أختيارنا على صيغة 16 بت بالعدد الصحيح وهذا لشيئين أولهما هو أن بالصوت صيغة 8 بت أقل مقبولية وشيوعاً من ما هو عليه في صيغ الصور والثاني هو لأن معالجة الأعداد الصحيحة أسرع من الكسرية.

ولنكتب ملف بهذه الصيغة علينا كالعادة كتابة رأس الملف الذي يحوي على معلومات الملف الصوتي مثل التردد ومعدل العينات الصوتية وبعض المعلومات التي تحتاج إلى بعض الحسابات المسبقة مثل حجم البيانات ولهذا سنقوم بإنشاء هيكل بيانات struct يحوي كل هذه المعلومات حتى نستطيع تخصيص الذاكرة مرة واحدة وكتابة رأس الملف دفعة واحدة.

تحذير مهم: عند تجربة إي مثال من الأمثلة أدناه تأكد من أستخدام برنامج معالجة موجات (wave editor) مثل أوداسيتي Audacity والأهم من ذلك وضع مستوى الصوت بإقل ما يمكن وزيادته تدريجياً إلى أن تتمكن من سماع النتيجة وإلا فأنا غير مسؤول عن الأضرار الناجمة عن الصوت الصاخب

/* -*- compile-command: "gcc -o sinewave sinewave.c -lm && ./sinewave"; -*- */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

typedef struct 
{
  char           ChunkID[4]; // 4 byte
  unsigned int   ChunkSize;  // 4 byte
  char           Format[4];  // 4 byte

  char           SubChunk1ID[4]; // 4 byte
  unsigned int   SubChunk1Size;  // 4 byte
  unsigned short AudioFormat;    // 2 byte
  unsigned short NumChannels;    // 2 byte
  unsigned int   SampleRate;     // 4 byte
  unsigned int   ByteRate;       // 4 byte
  unsigned short BlockAlign;     // 2 byte
  unsigned short BitsPerSample;  // 2 byte

  char           SubChunk2ID[4]; // 4 byte
  unsigned int   SubChunk2Size;  // 4 byte // this is the size of data array
  // total (+ 4 4 4     4 4 2 2 4 4 2 2         4 4) = 44 
  // data comes after

} WAVE_header;

// (* 15 44100) = 661500 // to get number of samples multiply sampling
                         // rate with number of seconds you want
#define N_SAMPLE_PAIRS 661500

#define BITS_PER_BYTE 8
#define BIT_DEPTH 16

int main(int argc, char **argv)
{
  // allocations before file IO
  WAVE_header head =
    {
      .ChunkID[0]='R',.ChunkID[1]='I',.ChunkID[2]='F',.ChunkID[3]='F', //always RIFF
      .ChunkSize = 36 + /*SubChunk2Size*/(N_SAMPLE_PAIRS * BIT_DEPTH / BITS_PER_BYTE),  // chunk size is file size but
                                                                                        // without ChunkID and ChunkSize
      .Format[0]='W',.Format[1]='A',.Format[2]='V',.Format[3]='E', //always WAVE


      .SubChunk1ID[0]='f',.SubChunk1ID[1]='m',.SubChunk1ID[2]='t',.SubChunk1ID[3]=' ', //always 'fmt '
      .SubChunk1Size = 16,
      .AudioFormat = 1, // pcm, see wav specs
      .NumChannels = 1,
      .SampleRate = 44100, // 44100 hz == 44khz (frequency)
      .ByteRate = 44100 * BIT_DEPTH / BITS_PER_BYTE /*8 bite per byte */,
      .BlockAlign = BIT_DEPTH / BITS_PER_BYTE,
      .BitsPerSample = BIT_DEPTH,

      .SubChunk2ID[0]='d',.SubChunk2ID[1]='a',.SubChunk2ID[2]='t',.SubChunk2ID[3]='a', //always data
      .SubChunk2Size = N_SAMPLE_PAIRS * BIT_DEPTH / BITS_PER_BYTE,
    };

  /* sound data: */
  short *sound;
  sound = malloc( sizeof(short) * N_SAMPLE_PAIRS);

  float amplitude = 20000; // معدل التضخيم
  float frequency = 0.00001; // التردد
  float time = 0.0;
  for (int i = 0; i < N_SAMPLE_PAIRS; i++)
    {
      time = (float) i;
      sound[i] = (short) (amplitude * sinf( 2.0 * M_PI * frequency * time )); // this can be seen
      //sound[i] = (short) (amplitude * sinf( 2.0 * M_PI * 0.009 * time )); // this can be heard
    }

  FILE* fp = fopen("sinewave.wav", "w+b");

  fwrite (&head, sizeof (WAVE_header), 1, fp);
  fwrite(sound, sizeof(short), N_SAMPLE_PAIRS, fp);

  free(sound);
  fclose(fp);
  return 0;
}

نلحظ هنا أستخدام مكتبة الرياضيات المعيارية للسي math.h وهذا شيء سنعتاد عليه ونستخدمه كثيراً في أمثلتنا ولهذا على التأكد من تمرير خيار lm- إلى gcc كما هو مدون داخل الشفرة المصدرية.

ويجدر بنا الإشارة إلى أستخدامنا إلى مصفوفة أرقام قصيرة (short) ولكن مع الإشارة إي أنها تحوي قيم سالبة وموجبة في مجال يبدأ من 32,768- إلى 32,767.

وهذه نتيجة البرنامج في ملف sinewave.wav:

ssinewave.png

شكل 1: الموجة الجيبية (sinewave)

الصورة أعلاه تحتوي على معدل تردد صغير جداً وهذا يعني أن الملف لا يمكن سماعه ولهذا السبب وضعت سطر أخر أسفل سطر تكوين القيم بالتعليق فأستبدال الأول بالثاني يعطينا:

hsinewave.png

شكل 2: الموجة الجيبية (sinewave) عالية التردد

ولكن هنا يطرح تسأول كيف يمكن أن يكون هناك موجة (أو عينة بالتحديد) سالبة؟ للإجابة على هذا السؤال علينا معرفة أن مكبرات الصوت تعمل بواسطة طبلة تتجه للخلف والأمام فالأتجاه للخلف يعني العينة السالبة وأما الأتجاه للأمام فموجة موجبة والصفر هو أستقرار الطبلة بوضعها الطبيعي ومن هنا علينا أن نعرف أن عملية الذبذبة تتم الأف أو ملايين المرات بالثانية الواحدة مكونة الصوت عبر هذه العملية الفيزيائية.

membrane.gif

شكل 3: طبلة مكبر الصوت وهى تتفاعل مع القيم السالبة والموجبة

ما سبق عرضه أعلاه هو الموجة الجيبية (sinewave) أحدى الموجات الأربع الأساسية وهم موجة جيب التمام (sinewave) وموجة المربع (squarewave) وموجة المثلث (triangle wave) وموجة أسنان المنشار (sawteeth) وقبل أن ندخل بتفاصيل هذه الخوارزمية علينا أن نعرف أن معدل العينات بالملف السابق هو 44100 هرتز (44 غيغاهرتز) بمعنى أن كل ثانية فيها 44100 رقم (عينة أو ذبذبة) وبمثالنا أردنا أن تكون مدة الصوت 15 ثانية فضربنا 15 مع 44100 وحصلنا على ناتج 661500 ولأن الملف أعلاه بقناة صوتية واحدة (mono) وليس بقناتين (stereo) فلا حاجة لضرب الناتج بـ 2.

هنا خوارزمية موجة الجيب:

sinewave-math.png

  • حرف A بالمعادلة يعني مقدار التضخيم وهنا سيكون أعلى قيمة تصل إليها الدالة
  • و f تعني معدل التردد
  • ويرمز t إلى الوقت (أجزاء من الثانية) (أو بالصح رقم العينة)
  • ρ يرمز لطور أو مرحلة الموجة إي حيث كانت عند t = 0 ونحن بمثالنا تركناه بأعتبار أنه صفر
  • ω = 2πf

موجة المثلث

/* -*- compile-command: "gcc -o triangle-wave triangle-wave.c -lm && ./triangle-wave"; -*- */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

typedef struct 
{
  char           ChunkID[4]; // 4 byte
  unsigned int   ChunkSize;  // 4 byte
  char           Format[4];  // 4 byte

  char           SubChunk1ID[4]; // 4 byte
  unsigned int   SubChunk1Size;  // 4 byte
  unsigned short AudioFormat;    // 2 byte
  unsigned short NumChannels;    // 2 byte
  unsigned int   SampleRate;     // 4 byte
  unsigned int   ByteRate;       // 4 byte
  unsigned short BlockAlign;     // 2 byte
  unsigned short BitsPerSample;  // 2 byte

  char           SubChunk2ID[4]; // 4 byte
  unsigned int   SubChunk2Size;  // 4 byte // this is the size of data array
  // total (+ 4 4 4     4 4 2 2 4 4 2 2         4 4) = 44 
  // data comes after

} WAVE_header;

// (* 15 44100) = 661500 // to get number of samples multiply sampling
                         // rate with number of seconds you want
#define N_SAMPLE_PAIRS 661500

#define BITS_PER_BYTE 8
#define BIT_DEPTH 16

int main(int argc, char **argv)
{
  // allocations before file IO
  WAVE_header head =
    {
      .ChunkID[0]='R',.ChunkID[1]='I',.ChunkID[2]='F',.ChunkID[3]='F', //always RIFF
      .ChunkSize = 36 + /*SubChunk2Size*/(N_SAMPLE_PAIRS * BIT_DEPTH / BITS_PER_BYTE),  // chunk size is file size but
                                                                                        // without ChunkID and ChunkSize
      .Format[0]='W',.Format[1]='A',.Format[2]='V',.Format[3]='E', //always WAVE


      .SubChunk1ID[0]='f',.SubChunk1ID[1]='m',.SubChunk1ID[2]='t',.SubChunk1ID[3]=' ', //always 'fmt '
      .SubChunk1Size = 16,
      .AudioFormat = 1, // pcm, see wav specs
      .NumChannels = 1,
      .SampleRate = 44100, // 44100 hz == 44khz (frequency)
      .ByteRate = 44100 * BIT_DEPTH / BITS_PER_BYTE /*8 bite per byte */,
      .BlockAlign = BIT_DEPTH / BITS_PER_BYTE,
      .BitsPerSample = BIT_DEPTH,

      .SubChunk2ID[0]='d',.SubChunk2ID[1]='a',.SubChunk2ID[2]='t',.SubChunk2ID[3]='a', //always data
      .SubChunk2Size = N_SAMPLE_PAIRS * BIT_DEPTH / BITS_PER_BYTE,
    };

  /* sound data: */
    short *sound;
  sound = malloc( sizeof(short) * N_SAMPLE_PAIRS);

  float amplitude = 20000; // معدل التضخيم
  //  float frequency = 0.00001; // التردد
  float frequency = 0.001;
  float time = 0.0;
  float val = 0.0;
  //  #define ivp 30.0
#define ivp 50.0
  float increase = ivp;
  float limit = 23767;
  for (int i = 0; i < N_SAMPLE_PAIRS; i++)
    {
      time = (float) i;
      sound[i] = (short) val;
      if ((short) val >= limit)
        {
          increase = -ivp;
        }
      if ((short) val <= -limit-1)
        {
          increase = ivp;
        }
      val+=increase;
    }

  FILE* fp = fopen("triwave.wav", "w+b");

  fwrite (&head, sizeof (WAVE_header), 1, fp);
  fwrite(sound, sizeof(short), N_SAMPLE_PAIRS, fp);

  free(sound);
  fclose(fp);
  return 0;
}

triwave.png

شكل 5: صورة مقربة لموجة المثلث

هنا في هذا المثال لم نستخدم إي معادلة أو خوارزمية ولكن صممنا واحدة قد لا تكون دقيقة علمياً لكن تؤدي الوظيفة (بأستخدام دالة الإشارة لتحديد قيم مجال واحد وقيم مجال أخر).

الموجة المربعة

/* -*- compile-command: "gcc -o square-wave square-wave.c -lm && ./square-wave"; -*- */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

typedef struct 
{
  char           ChunkID[4]; // 4 byte
  unsigned int   ChunkSize;  // 4 byte
  char           Format[4];  // 4 byte

  char           SubChunk1ID[4]; // 4 byte
  unsigned int   SubChunk1Size;  // 4 byte
  unsigned short AudioFormat;    // 2 byte
  unsigned short NumChannels;    // 2 byte
  unsigned int   SampleRate;     // 4 byte
  unsigned int   ByteRate;       // 4 byte
  unsigned short BlockAlign;     // 2 byte
  unsigned short BitsPerSample;  // 2 byte

  char           SubChunk2ID[4]; // 4 byte
  unsigned int   SubChunk2Size;  // 4 byte // this is the size of data array
  // total (+ 4 4 4     4 4 2 2 4 4 2 2         4 4) = 44 
  // data comes after

} WAVE_header;

// (* 15 44100) = 661500 // to get number of samples multiply sampling
                         // rate with number of seconds you want
#define N_SAMPLE_PAIRS 661500

#define BITS_PER_BYTE 8
#define BIT_DEPTH 16

int main(int argc, char **argv)
{
  // allocations before file IO
  WAVE_header head =
    {
      .ChunkID[0]='R',.ChunkID[1]='I',.ChunkID[2]='F',.ChunkID[3]='F', //always RIFF
      .ChunkSize = 36 + /*SubChunk2Size*/(N_SAMPLE_PAIRS * BIT_DEPTH / BITS_PER_BYTE),  // chunk size is file size but
                                                                                        // without ChunkID and ChunkSize
      .Format[0]='W',.Format[1]='A',.Format[2]='V',.Format[3]='E', //always WAVE


      .SubChunk1ID[0]='f',.SubChunk1ID[1]='m',.SubChunk1ID[2]='t',.SubChunk1ID[3]=' ', //always 'fmt '
      .SubChunk1Size = 16,
      .AudioFormat = 1, // pcm, see wav specs
      .NumChannels = 1,
      .SampleRate = 44100, // 44100 hz == 44khz (frequency)
      .ByteRate = 44100 * BIT_DEPTH / BITS_PER_BYTE /*8 bite per byte */,
      .BlockAlign = BIT_DEPTH / BITS_PER_BYTE,
      .BitsPerSample = BIT_DEPTH,

      .SubChunk2ID[0]='d',.SubChunk2ID[1]='a',.SubChunk2ID[2]='t',.SubChunk2ID[3]='a', //always data
      .SubChunk2Size = N_SAMPLE_PAIRS * BIT_DEPTH / BITS_PER_BYTE,
    };

  /* sound data: */
  short *sound;
  sound = malloc( sizeof(short) * N_SAMPLE_PAIRS);

  float amplitude = 20000; // معدل التضخيم
  //  float frequency = 0.00001; // التردد
  float frequency = 0.001;
  float time = 0.0;
  for (int i = 0; i < N_SAMPLE_PAIRS; i++)
    {
      time = (float) i;
      short x = (short) (amplitude * sinf( 2.0 * M_PI * frequency * time ));
      if (x == 0)
        {
          sound[i] = 0;
        }
      if (x > 0)
        {
          sound[i] = 12767;
        }
      if (x < 0)
        {
          sound[i] = -12767;
        }
    }

  FILE* fp = fopen("sqrwave.wav", "w+b");

  fwrite (&head, sizeof (WAVE_header), 1, fp);
  fwrite(sound, sizeof(short), N_SAMPLE_PAIRS, fp);

  free(sound);
  fclose(fp);
  return 0;
}

sqrwave.png

شكل 6: صورة مقربة لموجة المربع

موجة سن المنشار

/* -*- compile-command: "gcc -o sawteeth-wave sawteeth-wave.c -lm && ./sawteeth-wave"; -*- */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

typedef struct 
{
  char           ChunkID[4]; // 4 byte
  unsigned int   ChunkSize;  // 4 byte
  char           Format[4];  // 4 byte

  char           SubChunk1ID[4]; // 4 byte
  unsigned int   SubChunk1Size;  // 4 byte
  unsigned short AudioFormat;    // 2 byte
  unsigned short NumChannels;    // 2 byte
  unsigned int   SampleRate;     // 4 byte
  unsigned int   ByteRate;       // 4 byte
  unsigned short BlockAlign;     // 2 byte
  unsigned short BitsPerSample;  // 2 byte

  char           SubChunk2ID[4]; // 4 byte
  unsigned int   SubChunk2Size;  // 4 byte // this is the size of data array
  // total (+ 4 4 4     4 4 2 2 4 4 2 2         4 4) = 44 
  // data comes after

} WAVE_header;

// (* 15 44100) = 661500 // to get number of samples multiply sampling
                         // rate with number of seconds you want
#define N_SAMPLE_PAIRS 661500*2

#define BITS_PER_BYTE 8
#define BIT_DEPTH 16

int main(int argc, char **argv)
{
  // allocations before file IO
  WAVE_header head =
    {
      .ChunkID[0]='R',.ChunkID[1]='I',.ChunkID[2]='F',.ChunkID[3]='F', //always RIFF
      .ChunkSize = 36 + /*SubChunk2Size*/(N_SAMPLE_PAIRS * BIT_DEPTH / BITS_PER_BYTE),  // chunk size is file size but
                                                                                        // without ChunkID and ChunkSize
      .Format[0]='W',.Format[1]='A',.Format[2]='V',.Format[3]='E', //always WAVE


      .SubChunk1ID[0]='f',.SubChunk1ID[1]='m',.SubChunk1ID[2]='t',.SubChunk1ID[3]=' ', //always 'fmt '
      .SubChunk1Size = 16,
      .AudioFormat = 1, // pcm, see wav specs
      .NumChannels = 2,
      .SampleRate = 44100, // 44100 hz == 44khz (frequency)
      .ByteRate = 44100 * BIT_DEPTH / BITS_PER_BYTE /*8 bite per byte */,
      .BlockAlign = BIT_DEPTH / BITS_PER_BYTE,
      .BitsPerSample = BIT_DEPTH,

      .SubChunk2ID[0]='d',.SubChunk2ID[1]='a',.SubChunk2ID[2]='t',.SubChunk2ID[3]='a', //always data
      .SubChunk2Size = N_SAMPLE_PAIRS * BIT_DEPTH / BITS_PER_BYTE,
    };

  /* sound data: */
  short *sound;
  sound = malloc( sizeof(short) * N_SAMPLE_PAIRS);

  float val = 0.0;
  //  #define ivp 30.0
#define ivp 50.0
  float increase = ivp;
  float limit = 23767;
  for (int i = 0; i < N_SAMPLE_PAIRS; i+=2)
    {
      sound[i] = (short) val;
      sound[i+1] = (short) val;
      if ((short) val >= limit)
        {
          val = -limit;
        }
      val+=increase;
    }


  FILE* fp = fopen("sawteeth-wave.wav", "w+b");

  fwrite (&head, sizeof (WAVE_header), 1, fp);
  fwrite(sound, sizeof(short), N_SAMPLE_PAIRS, fp);

  free(sound);
  fclose(fp);
  return 0;
}

sawteeth-wave.png

شكل 7: صورة مقربة لموجة سن المنشار

في هذه المرة قمنا بأستخدام قناتين صوتين بمعنى أن أحدى هاتين القناتين سيكون مسؤول عن أصدار الصوت من السماعة اليسرى لحاسوبك والأخرى ستكون مسؤولة عن اليمنى.

قراءة صيغة wav

وأما بالنسبة للقراءة فهذا موضوع سهل مع توفر الهيكل وهنا سنقوم بقراءة كافة معلومات الملف الصوتي وعرض قيم أول عشر عينات صوتية:

// -*- compile-command: "gcc -o read-wave read-wave.c -lm && ./read-wave sawteeth-wave.wav"; -*-
#include <stdio.h>
#include <stdlib.h>

typedef struct 
{
  char           ChunkID[4]; // 4 byte
  unsigned int   ChunkSize;  // 4 byte
  char           Format[4];  // 4 byte

  char           SubChunk1ID[4]; // 4 byte
  unsigned int   SubChunk1Size;  // 4 byte
  unsigned short AudioFormat;    // 2 byte
  unsigned short NumChannels;    // 2 byte
  unsigned int   SampleRate;     // 4 byte
  unsigned int   ByteRate;       // 4 byte
  unsigned short BlockAlign;     // 2 byte
  unsigned short BitsPerSample;  // 2 byte

  char           SubChunk2ID[4]; // 4 byte
  unsigned int   SubChunk2Size;  // 4 byte // this is the size of array
  // total (+ 4 4 4     4 4 2 2 4 4 2 2         4 4) = 44 
  // data comes after

} WAVE_header;
// since this header is 44 byte (even) compiler will not fill it with extra bytes
// so no need for struct packing

int main(int argc, char **argv)
{
  if (argc < 2)
    {
      printf("Usage: %s soundfile.wav\n", argv[0]);
      exit (1);
    }
  WAVE_header head;
  FILE *fp = fopen(argv[1], "rb");

  fread(&head, sizeof (head), 1, fp);

  printf("Filename: %s\n", argv[1]);
  printf("ChunkID \t= %c%c%c%c\n",
         head.ChunkID[0],head.ChunkID[1],head.ChunkID[2],head.ChunkID[3]);
  printf("ChunkSize \t= %u\n",head.ChunkSize);
  printf("Format \t\t= %c%c%c%c\n",
         head.Format[0],head.Format[1],head.Format[2],head.Format[3]);

  printf("SubChunk1ID \t= %c%c%c%c\n",
         head.SubChunk1ID[0],head.SubChunk1ID[1],head.SubChunk1ID[2],head.SubChunk1ID[3]);
  printf("SubChunk1Size \t= %u\n",head.SubChunk1Size);
  printf("AudioFormat \t= %u\n",head.AudioFormat);
  printf("NumChannels \t= %u\n",head.NumChannels);
  printf("SampleRate \t= %u\n",head.SampleRate);
  printf("ByteRate \t= %u\n",head.ByteRate);
  printf("BlockAlign \t= %u\n",head.BlockAlign);
  printf("BitsPerSample \t= %u\n",head.BitsPerSample);

  printf("SubChunk2ID \t= %c%c%c%c\n",
         head.SubChunk2ID[0],head.SubChunk2ID[1],head.SubChunk2ID[2],head.SubChunk2ID[3]);
  printf("SubChunk2Size \t= %u\n",head.SubChunk2Size);

  if (head.Format[0]!='W' &&
      head.Format[1]!='A' &&
      head.Format[2]!='V' &&
      head.Format[3]!='E') {
    printf("This is not wav file, or not valid wav file\n");
    fclose (fp);
    exit (1);
  }

  short sound[head.SubChunk2Size];
  fread (&sound, head.SubChunk2Size, 1, fp);

  printf("\nFirst 10 Samples:\n");
  if (head.NumChannels == 1)
    {
      for (int i = 0; i < 10; ++i)
        {
          printf("sample#%d: %d\n", i, sound[i]);
        }
    }
  else if (head.NumChannels == 2)
    {
      int c = 0;
      for (int i = 0; i < 10; ++i)
        {
          printf("sample#%d: ch1:%d, ch2:%d\n", i, sound[c], sound[c+1]);
          c+=2;

        }
    }

  fclose (fp);
  return 0;
}

نتيجة البرنامج:

$ gcc -o read-wave read-wave.c -lm && ./read-wave sawteeth-wave.wav

Filename: sawteeth-wave.wav
ChunkID 	= RIFF
ChunkSize 	= 2646036
Format 		= WAVE
SubChunk1ID 	= fmt 
SubChunk1Size 	= 16
AudioFormat 	= 1
NumChannels 	= 2
SampleRate 	= 44100
ByteRate 	= 88200
BlockAlign 	= 2
BitsPerSample 	= 16
SubChunk2ID 	= data
SubChunk2Size 	= 2646000

First 10 Samples:
sample#0: ch1:0, ch2:0
sample#1: ch1:50, ch2:50
sample#2: ch1:100, ch2:100
sample#3: ch1:150, ch2:150
sample#4: ch1:200, ch2:200
sample#5: ch1:250, ch2:250
sample#6: ch1:300, ch2:300
sample#7: ch1:350, ch2:350
sample#8: ch1:400, ch2:400
sample#9: ch1:450, ch2:450

ملاحظات أخيرة

وحدة قياس حدة الصوت هي الديسيبل (decibel db) وهو مقياس يحتاج إلى معامل ضبط لمعرفة كم هو مقدار حدة أو مستوى الصوت لأن كل مكبر صوت يختلف عن الأخر ولكن عموماً في حال عدم توفر هذا المعامل تستخدم هذه معادلة البسيطة عبر تحويل قيمة العينة إلى خطية ومن ثمة أستخدام ما يلي:

dB = 20 * log10(amplitude)

amplitude = 14731 / 32767
	  = 0.44

dB = 20 * log10(0.44)
   = -7.13

المصدر: https://stackoverflow.com/questions/2445756/how-can-i-calculate-audio-db-level

لغاية جعل كل ملف برنامج مستقل بوحده لم أقم بوضع الهيكل WAVEheader في ملف رأسي (header file .h).

صورة مكبرات الصوت مأخوذة من مسلسل سبونج بوب بتصرف لأغراض غير تجارية وتعليمية ولدواعي حرية التعبير ضمن ظروف الأستخدام العادل.

شرح ومواصفات الصورة مأخوذ من

http://soundfile.sapp.org/doc/WaveFormat/

معادلة ساين مأخوذة من ويكيبيديا

https://en.wikipedia.org/wiki/Sine_wave

comments . التعليقات