مقدمة عملية في الموجات الصوتية الرقمية
نشر في 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
:
شكل 1: الموجة الجيبية (sinewave)
الصورة أعلاه تحتوي على معدل تردد صغير جداً وهذا يعني أن الملف لا يمكن سماعه ولهذا السبب وضعت سطر أخر أسفل سطر تكوين القيم بالتعليق فأستبدال الأول بالثاني يعطينا:
شكل 2: الموجة الجيبية (sinewave) عالية التردد
ولكن هنا يطرح تسأول كيف يمكن أن يكون هناك موجة (أو عينة بالتحديد) سالبة؟ للإجابة على هذا السؤال علينا معرفة أن مكبرات الصوت تعمل بواسطة طبلة تتجه للخلف والأمام فالأتجاه للخلف يعني العينة السالبة وأما الأتجاه للأمام فموجة موجبة والصفر هو أستقرار الطبلة بوضعها الطبيعي ومن هنا علينا أن نعرف أن عملية الذبذبة تتم الأف أو ملايين المرات بالثانية الواحدة مكونة الصوت عبر هذه العملية الفيزيائية.
شكل 3: طبلة مكبر الصوت وهى تتفاعل مع القيم السالبة والموجبة
ما سبق عرضه أعلاه هو الموجة الجيبية (sinewave) أحدى الموجات الأربع الأساسية وهم موجة جيب التمام (sinewave) وموجة المربع (squarewave) وموجة المثلث (triangle wave) وموجة أسنان المنشار (sawteeth) وقبل أن ندخل بتفاصيل هذه الخوارزمية علينا أن نعرف أن معدل العينات بالملف السابق هو 44100 هرتز (44 غيغاهرتز) بمعنى أن كل ثانية فيها 44100 رقم (عينة أو ذبذبة) وبمثالنا أردنا أن تكون مدة الصوت 15 ثانية فضربنا 15 مع 44100 وحصلنا على ناتج 661500 ولأن الملف أعلاه بقناة صوتية واحدة (mono) وليس بقناتين (stereo) فلا حاجة لضرب الناتج بـ 2.
هنا خوارزمية موجة الجيب:
- حرف 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; }
شكل 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; }
شكل 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; }
شكل 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/
معادلة ساين مأخوذة من ويكيبيديا