هناك وفرة من الأدوات والمكتبات التابعة لجهات خارجية لمعالجة وتحليل ملفات WAV الصوتية في بايثون. وفي الوقت نفسه، تأتي اللغة مع وحدة wave غير المعروفة في مكتبتها القياسية، مما يوفر طريقة سريعة ومباشرة لقراءة وكتابة مثل هذه الملفات. يمكن أن تساعدك معرفة وحدة wave في بايثون على التعمق في معالجة الصوت الرقمي.
إذا كانت مواضيع مثل تحليل الصوت، أو تحرير الصوت، أو توليف الموسيقى تثير حماسك، فأنت على وشك الاستمتاع بها، لأنك على وشك تجربتها!
في هذا البرنامج التعليمي، سوف تتعلم كيفية:
- قراءة وكتابة ملفات WAV باستخدام بايثون الخالص
- التعامل مع ترميز PCM 24 بت لعينات الصوت
- تفسير ورسم مستويات السعة الأساسية
- تسجيل تدفقات الصوت عبر الإنترنت مثل محطات الراديو عبر الإنترنت
- تحريك التصورات في مجالات الوقت والتردد
- تركيب الأصوات وتطبيق المؤثرات الخاصة
على الرغم من عدم إلزامك بذلك، فستحصل على أقصى استفادة من هذا البرنامج التعليمي إذا كنت على دراية بـ NumPy وMatplotlib، اللذين يبسطان العمل مع بيانات الصوت إلى حد كبير. بالإضافة إلى ذلك، فإن معرفة المصفوفات الرقمية في بايثون ستساعدك على فهم تمثيل البيانات الأساسية في ذاكرة الكمبيوتر بشكل أفضل.
فهم تنسيق ملف WAV
في أوائل التسعينيات، طورت شركة Microsoft وشركة IBM بشكل مشترك تنسيق ملف الصوت Waveform، والذي يُختصر غالبًا باسم WAVE أو WAV، والذي ينبع من امتداد الملف (.wav). وعلى الرغم من عمره الأقدم من حيث المصطلحات الحاسوبية، إلا أن التنسيق لا يزال ذا أهمية اليوم. وهناك عدة أسباب وجيهة لاعتماده على نطاق واسع، بما في ذلك:
- البساطة: يتمتع تنسيق ملف WAV ببنية واضحة، مما يجعله سهل الفك في البرامج وفهمه من قبل البشر.
- القدرة على النقل: تدعم العديد من أنظمة البرامج ومنصات الأجهزة تنسيق ملف WAV كمعيار، مما يجعله مناسبًا لتبادل البيانات.
- الدقة العالية: نظرًا لأن معظم ملفات WAV تحتوي على بيانات صوتية خام غير مضغوطة، فهي مثالية للتطبيقات التي تتطلب أعلى جودة صوت ممكنة، مثل إنتاج الموسيقى أو تحرير الصوت. من ناحية أخرى، تشغل ملفات WAV مساحة تخزين كبيرة مقارنة بتنسيقات الضغط التي تفقد البيانات مثل MP3.
تجدر الإشارة إلى أن ملفات WAV هي أنواع متخصصة من تنسيق ملف تبادل الموارد (RIFF)، وهو تنسيق حاوية لتدفقات الصوت والفيديو. تتضمن تنسيقات الملفات الشائعة الأخرى المستندة إلى RIFF تنسيقات AVI وMIDI. اما RIFF هو نفسه هو امتداد لتنسيق IFF الأقدم الذي طورته شركة Electronic Arts في الأصل لتخزين موارد ألعاب الفيديو.
قبل الخوض في هذا الموضوع، سوف تقوم بتفكيك تنسيق ملف WAV نفسه لفهم بنيته بشكل أفضل وكيفية تمثيله للأصوات. لا تتردد في الانتقال إلى الجزء التالي إذا كنت تريد فقط معرفة كيفية استخدام وحدة الموجة في بايثون.
الجزء الموجي من WAV
إن ما تدركه على أنه صوت هو عبارة عن اضطراب في الضغط ينتقل عبر وسط مادي، مثل الهواء أو الماء. وعلى المستوى الأكثر جوهرية، فإن كل صوت هو عبارة عن موجة يمكنك وصفها باستخدام ثلاث سمات:
- السعة هي مقياس لقوة الموجة الصوتية، والتي تشعر بها على شكل ارتفاع الصوت.
- التردد هو معكوس الطول الموجي أو عدد التذبذبات في الثانية الواحدة، والذي يتوافق مع درجة الصوت.
- الطور هو النقطة في دورة الموجة التي تبدأ عندها الموجة، والتي لا يتم تسجيلها بواسطة الأذن البشرية مباشرة.
تشير كلمة waveform، التي تظهر في اسم ملف WAV، إلى التصوير البياني لشكل الإشارة الصوتية. إذا سبق لك فتح ملف صوتي باستخدام برنامج تحرير الصوت، مثل Audacity، فمن المحتمل أنك رأيت تصورًا لمحتوى الملف يبدو على هذا النحو:

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

كما ترى، يبدأ ملف WAV برأس يتألف من بيانات وصفية تصف كيفية تفسير تسلسل الإطارات الصوتية التي تتبعه. يتكون كل إطار من قنوات تتوافق مع مكبرات الصوت، مثل مكبر الصوت الأيسر والأيمن أو مكبر الصوت الأمامي والخلفي. في المقابل، تحتوي كل قناة على عينة صوتية مشفرة، وهي قيمة رقمية تمثل مستوى السعة النسبية عند نقطة زمنية معينة.
أهم المعلمات التي ستجدها في رأس WAV هي:
- التشفير: التمثيل الرقمي لإشارة صوتية تم أخذ عينات منها. تتضمن أنواع التشفير المتاحة تعديل شفرة النبض الخطي غير المضغوط (PCM) وبعض التنسيقات المضغوطة مثل ADPCM أو A-Law أو μ-Law.
- القنوات: عدد القنوات في كل إطار، والذي عادة ما يكون مساويًا لقناة واحدة للمسارات الصوتية الأحادية واثنتين للمسارات الصوتية الاستريو، ولكن يمكن أن يكون أكثر بالنسبة للتسجيلات الصوتية المحيطة.
- معدل الإطارات: عدد الإطارات في الثانية، والمعروف أيضًا بمعدل أخذ العينات أو تردد أخذ العينات المقاس بالهرتز. وهو يؤثر على نطاق الترددات القابلة للتمثيل، مما يؤثر على جودة الصوت المدركة.
- عمق البت: عدد البتات لكل عينة صوتية، والذي يحدد الحد الأقصى لعدد مستويات السعة المميزة. وكلما زاد عمق البت، زاد النطاق الديناميكي للإشارة المشفرة، مما يجعل الفروق الدقيقة في الصوت مسموعة.
تدعم وحدة wave في بايثون ترميز Pulse-Code Modulation (PCM) فقط، وهو الأكثر شيوعًا، مما يعني أنه يمكنك عادةً تجاهل التنسيقات الأخرى. علاوة على ذلك، يقتصر بايثون على أنواع البيانات الصحيحة، بينما لا يتوقف PCM عند هذا الحد، حيث يحدد عدة أعماق بت للاختيار من بينها، بما في ذلك البتات ذات الفاصلة العائمة:
نوع البيانات | تم التوقيع | البتات | القيمة الدنيا | القيمة القصوى |
---|---|---|---|---|
عدد صحيح | لا | 8 | 0 | 255 |
عدد صحيح | نعم | 16 | -32,768 | 32,767 |
عدد صحيح | نعم | 24 | -8,388,608 | 8,388,607 |
عدد صحيح | نعم | 32 | -2,147,483,648 | 2,147,483,647 |
الفاصلة العائمة | نعم | 32 | ≈ -3.40282 × 1038 | ≈ 3.40282 × 1038 |
الفاصلة العائمة | نعم | 64 | ≈ -1.79769 × 10308 | ≈ 1.79769 × 10308 |
في الممارسة العملية، تعد أنواع البيانات ذات الفاصلة العائمة مبالغًا فيها بالنسبة لمعظم الاستخدامات، لأنها تتطلب مساحة تخزين أكبر مع توفير عائد ضئيل على الاستثمار. ربما لن تفتقدها إلا إذا كنت بحاجة إلى نطاق ديناميكي إضافي لتحرير الصوت بشكل احترافي حقًا.
ملاحظة: على الرغم من التعامل مع نكهة عددية صحيحة لترميز PCM، فغالبًا ما ترغب في تمثيل عينات الصوت الأساسية كأرقام ذات فاصلة عائمة داخليًا في الكود المصدر الخاص بك. سيتيح لك هذا التعامل مع أعماق بت مختلفة بشكل موحد وتبسيط العمليات الحسابية وراء معالجة الصوت.
بعد قراءة عينة صوتية في بايثون، ستقوم عادةً بتطبيع قيمتها بحيث تقع دائمًا بين -1.0 و1.0 على المقياس، بغض النظر عن النطاق الأصلي لقيم PCM. ثم، قبل كتابتها مرة أخرى في ملف WAV، ستقوم بتحويل القيمة وتثبيتها لجعلها تتناسب مع النطاق المطلوب.
إن ترميز الأعداد الصحيحة المكون من 8 بتات هو الوحيد الذي يعتمد على الأرقام غير الموقعة. وعلى النقيض من ذلك، تسمح جميع أنواع البيانات المتبقية بقيم العينة الإيجابية والسلبية.
هناك تفصيلة مهمة أخرى يجب أن تأخذها في الاعتبار عند قراءة عينات الصوت وهي ترتيب البايتات. يحدد تنسيق ملف WAV أن القيم متعددة البايتات يتم تخزينها في نهاية صغيرة أو تبدأ بالبايت الأقل أهمية أولاً. لحسن الحظ، لا تحتاج عادةً إلى القلق بشأن ذلك عند استخدام وحدة wave لقراءة أو كتابة بيانات صوتية في بايثون. ومع ذلك، قد تكون هناك حالات حدودية عند القيام بذلك!
تحتوي الأعداد الصحيحة ذات 8 بتات و16 بتات و32 بتات على تمثيلات قياسية في لغة البرمجة C، والتي يعتمد عليها مُفسِّر CPython الافتراضي. ومع ذلك، فإن العدد الصحيح ذي 24 بتًا هو قيمة شاذة بدون نوع بيانات C مُضمَّن مُقابل. إن أعماق البت الغريبة مثل هذه ليست غير مألوفة في إنتاج الموسيقى، لأنها تساعد في إيجاد توازن بين الحجم والجودة. لاحقًا، ستتعلم كيفية محاكاتها في بايثون.
لتمثيل الموسيقى بأمانة، تستخدم معظم ملفات WAV ترميز PCM الاستريو مع أعداد صحيحة موقعة مكونة من 16 بت يتم أخذ عينات منها بمعدل 44.1 كيلوهرتز أو 44100 إطار في الثانية. تتوافق هذه المعلمات مع جودة الصوت القياسية للأقراص المضغوطة. ومن قبيل المصادفة، فإن تردد أخذ العينات هذا يبلغ ضعف أعلى تردد يمكن لمعظم البشر سماعه تقريبًا. ووفقًا لنظرية أخذ العينات نيكويست-شانون، فإن هذا يكفي لالتقاط الأصوات في شكل رقمي دون تشويه.
الآن بعد أن تعرفت على محتويات ملف WAV، فقد حان الوقت لتحميله إلى بايثون!
تعرف على وحدة wave
في بايثون
تتولى وحدة wave
قراءة ملفات WAV وكتابتها، ولكنها بخلاف ذلك بسيطة للغاية. تم تنفيذها في حوالي خمسمائة سطر من كود بايثون الخالص، دون احتساب التعليقات. ولعل الأهم من ذلك، أنه لا يمكنك استخدامها لتشغيل الصوت. لتشغيل صوت في بايثون، ستحتاج إلى تثبيت مكتبة منفصلة.
ملاحظة: على الرغم من أن وحدة الموجة نفسها لا تدعم تشغيل الصوت، فلا يزال بإمكانك الاستماع إلى ملفات WAV أثناء العمل على هذا البرنامج التعليمي. استخدم مشغل الوسائط الذي جاء مع نظام التشغيل الخاص بك أو أي تطبيق تابع لجهة خارجية مثل VLC.
كما ذكرنا سابقًا، تدعم وحدة wave أربعة أعماق بتات فقط للترميز PCM غير المضغوط القائم على الأعداد الصحيحة:
- عدد صحيح غير موقّع مكون من 8 بتات
- عدد صحيح موقّع مكون من 16 بتًا
- عدد صحيح موقّع مكون من 24 بتًا
- عدد صحيح موقّع مكون من 32 بتًا
لتجربة وحدة wave الخاصة بـبايثون، يمكنك تنزيل المواد الداعمة، والتي تتضمن بعض ملفات WAV النموذجية المشفرة بهذه التنسيقات.
قراءة بيانات التعريف الوصفية لـ WAV وإطارات الصوت
إذا لم تكن قد حصلت بالفعل على المواد الإضافية، فيمكنك تنزيل تسجيل عينة لطبول بونغو مباشرة من ويكيميديا كومنز للبدء. إنه صوت أحادي تم أخذ عينة منه بتردد 44.1 كيلوهرتز وتم ترميزه بتنسيق PCM 16 بت. الملف في المجال العام، لذا يمكنك استخدامه بحرية للأغراض الشخصية أو التجارية دون أي قيود.
لتحميل هذا الملف إلى بايثون، قم باستيراد وحدة wave واستدعاء الدالة open()
الخاصة بها باستخدام سلسلة تشير إلى المسار إلى ملف WAV الخاص بك كحجة للدالة:
>>> import wave
>>> with wave.open("Bongo_sound.wav") as wav_file:
... print(wav_file)
...
<wave.Wave_read object at 0x7fc07b2ab950>
عندما لا تقوم بتمرير أي وسيطات إضافية، فإن دالة wave.open()
، وهي الدالة الوحيدة التي تشكل جزءًا من الواجهة العامة للوحدة النمطية، تفتح الملف المحدد للقراءة وتعيد مثيل Wave_read. يمكنك استخدام هذا الكائن لاسترداد المعلومات المخزنة في رأس ملف WAV وقراءة إطارات الصوت المشفرة:
>>> with wave.open("Bongo_sound.wav") as wav_file:
... metadata = wav_file.getparams()
... frames = wav_file.readframes(metadata.nframes)
...
>>> metadata
_wave_params(
nchannels=1,
sampwidth=2,
framerate=44100,
nframes=212419,
comptype='NONE',
compname='not compressed'
)
>>> frames
b'\x01\x00\xfe\xff\x02\x00\xfe\xff\x01\x00\x01\x00\xfe\xff\x02\x00...'
>>> len(frames)
424838
يمكنك بسهولة الحصول على جميع معلمات ملف WAV الخاص بك في مجموعة مسماة ذات سمات وصفية مثل .nchannels
أو .framerate
. بدلاً من ذلك، يمكنك استدعاء التوابع الفردية، مثل .getnchannels()
، على كائن Wave_read لاختيار القطع المحددة من البيانات الوصفية التي تهمك.
تظهر لك إطارات الصوت الأساسية كمثال للبايتات غير المعالجة، وهي عبارة عن تسلسل طويل جدًا من قيم البايتات غير الموقعة. لسوء الحظ، لا يمكنك فعل الكثير بخلاف ما رأيته هنا لأن وحدة wave تعيد البايتات الخام فقط دون تقديم أي مساعدة في تفسيرها.
إن تسجيلك للعينة الخاصة بطبلة البونغو، والتي تقل مدتها عن خمس ثوانٍ وتستخدم قناة واحدة فقط، يتألف من ما يقرب من نصف مليون بايت! لفهمها، يجب أن تعرف تنسيق الترميز وفك تشفير هذه البايتات يدويًا إلى أرقام صحيحة.
وفقًا للبيانات الوصفية التي حصلت عليها للتو، هناك قناة واحدة فقط في كل إطار، وكل عينة صوتية تشغل بايتين أو ستة عشر بتًا. لذلك، يمكنك استنتاج أن ملفك تم ترميزه باستخدام تنسيق PCM بأعداد صحيحة ذات إشارة مكونة من 16 بتًا. يتوافق هذا التمثيل مع نوع البيانات القصيرة ذات الإشارة في C، والذي لا يوجد في بايثون.
على الرغم من أن بايثون لا يدعم بشكل مباشر نوع البيانات المطلوب، يمكنك استخدام وحدة array لإعلان مجموعة من الأرقام القصيرة الموقعة وتمرير كائن البايتات الخاص بك كمدخل. في هذه الحالة، تريد استخدام الحرف الصغير “h” كرمز نوع المجموعة لإخبار بايثون بكيفية تفسير بايتات الإطارات:
>>> import array
>>> pcm_samples = array.array("h", frames)
>>> len(pcm_samples)
212419
لاحظ أن المصفوفة الناتجة تحتوي على نصف عدد العناصر الموجودة في تسلسل البايتات الأصلي. وذلك لأن كل رقم في array يمثل عينة صوتية مكونة من 16 بت أو بايتين.
خيار آخر في متناول يدك هو وحدة struct، والتي تتيح لك فك ضغط تسلسل من البايتات إلى مجموعة من الأرقام وفقًا لسلسلة التنسيق المحددة:
>>> import struct
>>> format_string = "<" + "h" * (len(frames) // 2)
>>> pcm_samples = struct.unpack(format_string, frames)
>>> len(pcm_samples)
212419
يشير الرمز “أقل من” (<) في سلسلة التنسيق صراحةً إلى أن little-endian هو ترتيب البايت لكل عينة صوتية مكونة من بايتين (h). وعلى النقيض من ذلك، تفترض المصفوفة ضمناً ترتيب البايتات في منصتك، مما يعني أنه قد يتعين عليك استدعاء التابع .byteswap()
الخاصة بها عند الضرورة.
أخيرًا، يمكنك استخدام NumPy كبديل فعال وقوي لوحدات مكتبة بايثون القياسية، خاصةً إذا كنت تعمل بالفعل مع بيانات رقمية:
>>> import numpy as np
>>> pcm_samples = np.frombuffer(frames, dtype="<h")
>>> normalized_amplitudes = pcm_samples / (2 ** 15)
باستخدام مصفوفة NumPy لتخزين عينات PCM، يمكنك الاستفادة من عملياتها المتجهة حسب العناصر لتطبيع سعة الإشارة الصوتية المشفرة. أعلى قيمة لسعة مخزنة على عدد صحيح قصير موقّع هي سالب 32768 أو -215. يؤدي تقسيم كل عينة على 215 إلى تقليصها إلى فاصل مفتوح بين -1.0 و1.0، وهو ما يناسب مهام معالجة الصوت.
يؤدي هذا إلى نقطة يمكنك عندها أخيرًا البدء في تنفيذ مهام مثيرة للاهتمام على بيانات الصوت الخاصة بك في بايثون، مثل رسم شكل الموجة أو تطبيق المؤثرات الخاصة. ومع ذلك، قبل أن تفعل ذلك، يجب أن تتعلم كيفية حفظ عملك في ملف WAV.
اكتب ملف WAV الأول الخاص بك في بايثون
إن معرفة كيفية استخدام وحدة wave في بايثون تفتح لك إمكانيات مثيرة، مثل توليف الصوت. ألن يكون من الرائع تأليف الموسيقى أو المؤثرات الصوتية الخاصة بك من الصفر والاستماع إليها؟ الآن، يمكنك القيام بذلك!
من الناحية الرياضية، يمكنك تمثيل أي صوت معقد كمجموع عدد كافٍ من الموجات الجيبية ذات الترددات والسعات والمراحل المختلفة. ومن خلال مزجها بالنسب الصحيحة، يمكنك إعادة إنشاء الجرس الفريد للآلات الموسيقية المختلفة التي تعزف نفس النغمة. وفي وقت لاحق، يمكنك دمج بعض النغمات الموسيقية في أوتار واستخدامها لتكوين ألحان مثيرة للاهتمام.
هذه هي الصيغة العامة لحساب سعة A(t) في اللحظة الزمنية t لموجة جيبية بتردد f وتحول طور φ، وأقصى سعة لها هي A:

عندما تقوم بقياس السعات الخاصة بك إلى نطاق من القيم بين -1.0 و1.0، فيمكنك تجاهل عامل A في المعادلة. يمكنك أيضًا حذف حد φ لأن تحول الطور لا يكون عادةً ذا صلة بتطبيقاتك.
تابع وأنشئ نصًا برمجيًا لـبايثون باسم Synth_mono.py باستخدام الدالة المساعدة التالية، والتي تنفذ الصيغة أعلاه:
import math
FRAMES_PER_SECOND = 44100
def sound_wave(frequency, num_seconds):
for frame in range(round(num_seconds * FRAMES_PER_SECOND)):
time = frame / FRAMES_PER_SECOND
amplitude = math.sin(2 * math.pi * frequency * time)
yield round((amplitude + 1) / 2 * 255)
أولاً، تقوم باستيراد وحدة الرياضيات في بايثون للوصول إلى دالة sin()
ثم تحديد ثابت بمعدل الإطارات للصوت، مع تعيينه افتراضيًا على 44.1 كيلوهرتز. تأخذ دالة المساعدة sound_wave()
التردد بالهرتز ومدة الموجة المتوقعة بالثواني كمعلمات. بناءً عليها، تحسب عينات PCM غير الموقعة المكونة من 8 بت للموجة الجيبية المقابلة وتسلمها للمتصل.
لتحديد اللحظة الزمنية قبل إدخالها في الصيغة، قم بقسمة رقم الإطار الحالي على معدل الإطارات، مما يعطيك الوقت الحالي بالثواني. ثم، قم بحساب السعة عند هذه النقطة باستخدام صيغة الرياضيات المبسطة التي رأيتها سابقًا. أخيرًا، قم بتحويل السعة وتقليصها وقصها لتناسب النطاق من 0 إلى 255، وهو ما يتوافق مع عينة صوتية PCM غير موقّعة مكونة من 8 بتات.
يمكنك الآن توليف صوت نقي للنغمة الموسيقية A، على سبيل المثال، عن طريق توليد موجة جيبية بتردد 440 هرتز تستمر لمدة ثانيتين ونصف. بعد ذلك، يمكنك استخدام وحدة wave لحفظ عينات الصوت PCM الناتجة في ملف WAV:
import math
import wave
FRAMES_PER_SECOND = 44100
def sound_wave(frequency, num_seconds):
for frame in range(round(num_seconds * FRAMES_PER_SECOND)):
time = frame / FRAMES_PER_SECOND
amplitude = math.sin(2 * math.pi * frequency * time)
yield round((amplitude + 1) / 2 * 255)
with wave.open("output.wav", mode="wb") as wav_file:
wav_file.setnchannels(1)
wav_file.setsampwidth(1)
wav_file.setframerate(FRAMES_PER_SECOND)
wav_file.writeframes(bytes(sound_wave(440, 2.5)))
ابدأ بإضافة عبارة الاستيراد الضرورية واستدعِ دالة wave.open()
مع معامل الوضع الذي يساوي الحرف “wb”، والذي يرمز إلى الكتابة في الوضع الثنائي. في هذه الحالة، تُرجع الدالة كائن Wave_write
. لاحظ أن بايثون يفتح دائمًا ملفات WAV في الوضع الثنائي حتى إذا لم تستخدم حرف b صراحةً في قيمة معامل الوضع.
بعد ذلك، يمكنك ضبط عدد القنوات على واحد، وهو ما يمثل الصوت الأحادي، وعرض العينة على بايت واحد أو ثمانية بتات، وهو ما يتوافق مع ترميز PCM مع عينات عدد صحيح غير موقّعة مكونة من 8 بتات. كما يمكنك تمرير معدل الإطارات المخزن في الثابت. وأخيرًا، يمكنك تحويل عينات الصوت PCM المحسوبة إلى تسلسل من البايتات قبل كتابتها في الملف.
الآن بعد أن فهمت ما يفعله الكود، يمكنك المضي قدمًا وتشغيل البرنامج النصي:
$ python synth_mono.py
تهانينا! لقد نجحت في تجميع أول صوت لك في بايثون. حاول تشغيله مرة أخرى في مشغل الوسائط لسماع النتيجة. بعد ذلك، قم بإضفاء بعض الإثارة عليه.
مزج وحفظ الصوت الاستريو
إن إنتاج الصوت الأحادي هو نقطة بداية رائعة. ومع ذلك، فإن حفظ إشارة ستيريو بتنسيق ملف WAV يصبح أكثر صعوبة لأنه يتعين عليك تداخل عينات الصوت من القناتين اليسرى واليمنى في كل إطار. للقيام بذلك، يمكنك تعديل الكود الحالي كما هو موضح أدناه أو وضعه في ملف بايثون جديد تمامًا، على سبيل المثال، ملف يسمى synth_stereo.py:
import itertools
import math
import wave
FRAMES_PER_SECOND = 44100
def sound_wave(frequency, num_seconds):
for frame in range(round(num_seconds * FRAMES_PER_SECOND)):
time = frame / FRAMES_PER_SECOND
amplitude = math.sin(2 * math.pi * frequency * time)
yield round((amplitude + 1) / 2 * 255)
left_channel = sound_wave(440, 2.5)
right_channel = sound_wave(480, 2.5)
stereo_frames = itertools.chain(*zip(left_channel, right_channel))
with wave.open("output.wav", mode="wb") as wav_file:
wav_file.setnchannels(2)
wav_file.setsampwidth(1)
wav_file.setframerate(FRAMES_PER_SECOND)
wav_file.writeframes(bytes(stereo_frames))
تمثل الخطوط المميزة التغييرات الضرورية. أولاً، قم باستيراد وحدة itertools حتى تتمكن من chain()
أزواج العينات الصوتية المضغوطة وغير المضغوطة من كلتا القناتين على السطر 15. لاحظ أن هذه إحدى الطرق العديدة لتنفيذ التداخل بين القناتين. بينما تظل القناة اليسرى كما كانت من قبل، تصبح القناة اليمنى موجة صوتية أخرى بتردد مختلف قليلاً. وكلاهما لهما أطوال متساوية تبلغ ثانيتين ونصف.
كما يمكنك تحديث عدد القنوات في بيانات التعريف الخاصة بالملف وفقًا لذلك وتحويل إطارات الاستريو إلى بايتات خام. وعند تشغيلها، يجب أن يشبه ملف الصوت الناتج صوت نغمة الرنين التي تستخدمها معظم الهواتف في أمريكا الشمالية. ويمكنك محاكاة نغمات أخرى من بلدان مختلفة عن طريق ضبط ترددي الصوت، 440 هرتز و480 هرتز. لاستخدام ترددات إضافية، قد تحتاج إلى أكثر من قناتين.
بدلاً من ذلك، بدلاً من تخصيص قنوات منفصلة لموجات الصوت، يمكنك مزجها معًا لإنشاء تأثيرات مثيرة للاهتمام. على سبيل المثال، تنتج موجتان صوتيتان بترددات متقاربة للغاية نمط تداخل نابض. ربما تكون قد اختبرت هذه الظاهرة بنفسك عند السفر على متن طائرة لأن المحركات النفاثة على كلا الجانبين لا تدور أبدًا بنفس السرعات تمامًا. وهذا يخلق صوتًا نابضًا مميزًا في المقصورة.
يتلخص خلط موجتين صوتيتين في جمع سعاتهما. فقط تأكد من تحديد مجموعهما بعد ذلك بحيث لا يتجاوز نطاق السعة المتاح لتجنب التشويه:
import math
import wave
FRAMES_PER_SECOND = 44100
def beat(frequency1, frequency2, num_seconds):
for frame in range(round(num_seconds * FRAMES_PER_SECOND)):
time = frame / FRAMES_PER_SECOND
amplitude1 = math.sin(2 * math.pi * frequency1 * time)
amplitude2 = math.sin(2 * math.pi * frequency2 * time)
amplitude = max(-1, min(amplitude1 + amplitude2, 1))
yield round((amplitude + 1) / 2 * 255)
with wave.open("output.wav", mode="wb") as wav_file:
wav_file.setnchannels(1)
wav_file.setsampwidth(1)
wav_file.setframerate(FRAMES_PER_SECOND)
wav_file.writeframes(bytes(beat(440, 441, 2.5)))
هنا، تعود إلى الصوت الأحادي للحظة. بعد إنشاء موجتين صوتيتين على السطرين 9 و10، أضف سعاتهما المقابلة في السطر التالي. في بعض الأحيان، تلغي الموجتان بعضهما البعض، وفي أحيان أخرى، تعملان على تضخيم الصوت الإجمالي. تساعدك الدالتان min()
وmax()
المضمنتان في الحفاظ على السعة الناتجة بين -1.0 و1.0 في جميع الأوقات.
قم باللعب بالنص أعلاه عن طريق زيادة أو تقليل المسافة بين الترددين لمراقبة كيفية تأثير ذلك على إيقاع الضرب الناتج.
لقد قمت بتغطية الكثير من المجالات في هذا البرنامج التعليمي. بعد التعرف على بنية ملف WAV، تمكنت من التعامل مع وحدة الموجة في بايثون لقراءة وكتابة البيانات الثنائية الخام. بعد ذلك، قمت ببناء تجريداتك الخاصة أعلى وحدة الموجة حتى تتمكن من التفكير في بيانات الصوت بمصطلحات أعلى مستوى. وهذا بدوره سمح لك بتنفيذ العديد من أدوات التحليل والمعالجة الصوتية العملية والممتعة.
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.