پست

درک موسیقی با نگاه برنامه‌نویسی

درک موسیقی با نگاه برنامه‌نویسی

تئوری موسیقی برای برنامه‌نویس‌ها (بخش اول): مهندسی صدا از صفر مطلق

من ساز زدن بلد نیستم. بیش از یک بار تلاش کرده‌ام، و هر بار نهایتاً به جایی رسیدم که می‌توانستم تقریباً صداهای درستی تولید کنم، بدون اینکه هرگز بفهمم چرا این صداها «درست» هستند. مشکل هیچ‌وقت تمرین کردن نبود. مشکل این بود که به نظر می‌رسید هر توضیحی درباره تئوری موسیقی، دلایل بنیادی برای اینکه چرا چیزها این‌گونه هستند را جا می‌اندازد. به من می‌گفتند: «این پنج‌خط حامل است، این‌ها نت‌ها هستند، این گام ماژور است، الگو را حفظ کن». اما چرا این نت‌ها؟ چرا این الگو؟ جواب همیشه یک چیز بود: «چون قرارداد این است».

این روش برای آموزش سیستمی که اساساً از فیزیک و حساب بیرون آمده، بسیار عجیب و غیرمنطقی است. دوازده نت به یک دلیل مشخص وجود دارند. گام ماژور شکل خاص خودش را به یک دلیل مشخص دارد. آکوردهایی که خوب صدا می‌دهند، به دلایل علمی خوب صدا می‌دهند و شما می‌توانید تمام این دلایل را با منطق و کد محاسبه کنید.

بنابراین، تصمیم گرفتم از صفر مطلق شروع کنم و موسیقی را از اصول اولیه بیاموزم، و این سفر را با نوشتن کد آغاز کردم. این سری مقالات نتیجه‌ی همان مسیر است. ما با یک عدد ساده که در طول زمان تغییر می‌کند شروع می‌کنیم و اگر با من همراه باشید، دوازده نت را استخراج می‌کنیم، گام‌ها و آکوردها را با استفاده از آرایه‌ها می‌سازیم و یک توالی آکورد می‌نویسیم که شبیه یک موسیقی واقعی صدا می‌دهد. نیازی به هیچ سازی نیست و هیچ‌چیز را نباید صرفاً با «ایمان داشتن» بپذیرید. نت‌نویسی سنتی هم در نهایت خودش را نشان می‌دهد، اما نه تا زمانی که چیزی برای نت‌نویسی داشته باشیم.


صدا، عددی است که در طول زمان تغییر می‌کند

بیایید این مفهوم را به صورت ساختاریافته (Simple, Intermediate, Deep) کالبدشکافی کنیم:

سطح ساده (Simple): صدا در فیزیک چیزی جز لرزش فشار هوا نیست. یک اسپیکر با حرکت دادن مخروط خود به سمت داخل و بیرون، صدا تولید می‌کند.

سطح متوسط (Intermediate): هر کاری که کامپیوتر شما با صدا انجام می‌دهد، در نهایت به تولید لیستی از اعداد ختم می‌شود که به اسپیکر می‌گویند این مخروط در هر لحظه کجای مسیر باید باشد؛ و این کار دقیقاً چهل و چهار هزار بار در ثانیه انجام می‌شود. یک فایل صوتی، در واقع همین لیست است که روی دیسک نوشته شده است.

سطح عمیق (Deep): یک سینت‌سایزر (Synthesizer)، این لیست از اعداد را در لحظه (Real-time) تولید می‌کند. مرورگر وب می‌تواند این کار را برای شما انجام دهد، به شرطی که شکل موج مورد نظرتان را به آن بگویید. ساده‌ترین شکل، یک موج سینوسی (Sine Wave) است.

نقطه ضعف کدهای رویه‌ای (Procedural) در Web Audio API

کد اصلی و خامی که مستندات ارائه می‌دهند، معمولاً پر از متغیرهای Global و بدون ساختار است (مثلاً نمونه‌هایی که ctx و out را در Scope سراسری رها می‌کنند). این یک ریسک جدی برای مدیریت حافظه و تداخل در برنامه‌های وب (SPA) است.

به جای کدهای کثیف، بیایید این مفهوم را با رعایت اصول شی‌ءگرایی (OOP) و Clean Code پیاده‌سازی کنیم. ما یک کلاس Singleton برای مدیریت Audio Context می‌سازیم.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * AudioEngine Class
 * مدیریت متمرکز Web Audio API با رویکرد Singleton
 */
class AudioEngine {
    constructor() {
        if (AudioEngine.instance) {
            return AudioEngine.instance;
        }
        // کانتکست صدا در مرورگر
        this.ctx = new (window.AudioContext || window.webkitAudioContext)();
        this.out = this.ctx.destination; // خروجی نهایی به اسپیکرها
        AudioEngine.instance = this;
    }

    /**
     * پخش یک موج سینوسی خام با فرکانس مشخص
     * @param {number} frequency - فرکانس به هرتز (مثلاً 440)
     * @param {number} duration - مدت زمان به ثانیه
     */
    playRawSine(frequency = 440, duration = 1) {
        const osc = this.ctx.createOscillator();
        osc.frequency.value = frequency;
        osc.connect(this.out);
        
        osc.start();
        osc.stop(this.ctx.currentTime + duration);
    }
}

const engine = new AudioEngine();
// پخش فرکانس 440 هرتز به مدت 1 ثانیه
engine.playRawSine(440, 1);

تحلیل معماری: عدد 440 تنها چیزی است که در این کد معنای موسیقیایی دارد، و حتی آن هم کاملاً قراردادی و دلخواه است. این فرکانسی است که روزی کسی تصمیم گرفت آن را نت “A” (لا) بنامد و به عنوان دیاپازون (Tuning Fork) برای کوک کردن بقیه سیستم موسیقی انتخاب شد. اگر عدد را به 300 تغییر دهید، کد همچنان کار می‌کند، فقط زیر و بمی (Pitch) متفاوتی را می‌شنوید. نوسان‌ساز (Oscillator) ما هیچ درکی از این که “نت” چیست ندارد؛ او فقط می‌داند که باید چند بار در ثانیه نوسان کند.

نتیجه‌گیری: فرکانس همان زیر و بمی (Pitch) است؛ عدد بزرگتر یعنی نتِ زیرتر (بالاتر). هیچ‌چیز دیگری در موسیقی قرار نیست به این سادگی باشد.


چرا در انتهای نت صدای «تِق» (Click) شنیده شد؟

اگر کد بالا را اجرا کنید، در انتهای پخش صدا یک صدای «تق» کوچک می‌شنوید. مرورگر دقیقاً کاری را کرد که به او گفتیم، این کلیک یک باگ نرم‌افزاری نیست، بلکه فیزیک خالص است.

آسیب‌شناسی مشکل: وقتی اجرای نوسان‌ساز در ثانیه آخر متوقف شد، موج دقیقاً در اواسط حرکت خود بود. بنابراین، مخروط اسپیکر در نقطه‌ای از لبه‌ی دامنه حرکتی‌اش قرار داشت و با قطع ناگهانی جریان، فوراً به نقطه صفر (مرکز) بازگشت (Snapped back). این پرشِ آنی در فشار هوا، دقیقاً همان چیزی است که ما به عنوان صدای کلیک می‌شنویم.

راه‌حل (تکامل معماری با Envelope): راه‌حل، معرفی یک متغیر دوم است که در طول زمان تغییر کند؛ متغیری که به جای Pitch، حجم صدا (Volume) را کنترل کند. موسیقیدان‌ها به شکل و منحنی تغییرات این حجم صدا در طول زمان، انولوپ (Envelope) می‌گویند.

انولوپ، منحنیِ حجم صدای یک نت منفرد است؛ مسیری که از سکوت شروع می‌شود و دوباره به سکوت بازمی‌گردد. بالا رفتن حجم در ابتدای صدا، Attack نام دارد و افت آن در انتها Decay نامیده می‌شود. ده میلی‌ثانیه محو شدن در ابتدا (Fade in) و سپس یک افت کند تا رسیدن به نزدیک صفر؛ این دقیقاً همان مرز و تفاوتی است که یک «بوق آزمایشگاهی» را از صدایی که مایل هستید برای بار دوم بشنوید، متمایز می‌کند.

اگر زمان Attack را به سمت نیم‌ثانیه بکشید، نت دیگر حالت «ضربه خوردن» ندارد، بلکه شروع به «متورم شدن» می‌کند. در این حالت صدا دیگر شبیه به یک ساز کوبه‌ای نیست، بلکه شبیه به کشیدن آرشه روی سیم (Bowed) است، در حالی که فرکانس حتی یک هرتز هم جابه‌جا نشده است. بخش عمده‌ای از آنچه شما به عنوان کاراکتر یا شخصیت یک نت می‌شنوید، مستقیماً از انولوپ آن نشأت می‌گیرد.

البته این یک نسخه ساده‌شده از انولوپ است. نسخه کامل‌تر آن ADSR نام دارد: Attack (حمله)، Decay (افت)، Sustain (نگهداشت) و Release (رهاسازی). Sustain سطحی است که وقتی کلید را نگه داشته‌اید صدا روی آن ثابت می‌ماند، و Release نحوه محو شدن صدا پس از رها کردن کلید است.

بیایید این سیستم پیشرفته را با یک متد (Helper) تمیز به کلاس خود اضافه کنیم تا در ادامه مقاله از آن بهره ببریم:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * بسط دادن AudioEngine برای پشتیبانی از Envelope (ADSR ساده شده)
 */
class AdvancedAudioEngine extends AudioEngine {
    
    /**
     * پخش یک نت با کنترل کامل Envelope برای جلوگیری از شوک فیزیکی اسپیکر
     * @param {number} freq - فرکانس نت
     * @param {number} start - تاخیر در شروع
     * @param {number} length - طول مدت پخش
     * @param {string} type - نوع موج (مثلا sine)
     */
    playNoteWithEnvelope(freq, start = 0, length = 0.5, type = "sine") {
        const t = this.ctx.currentTime + start;
        
        const osc = this.ctx.createOscillator();
        const env = this.ctx.createGain(); // کنترل‌کننده Volume
        
        osc.type = type;
        osc.frequency.value = freq;
        
        // پیاده‌سازی Envelope
        env.gain.setValueAtTime(0, t); // شروع از سکوت مطلق
        // Attack: رسیدن به ولوم 0.3 در 10 میلی‌ثانیه
        env.gain.linearRampToValueAtTime(0.3, t + 0.01);
        // Decay/Release: افت نمایی تا مرز خاموشی در انتهای طول نت
        env.gain.exponentialRampToValueAtTime(0.001, t + length);
        
        // الگوی زنجیره‌ای اتصال گره‌ها: Oscillator -> Gain -> Master Out
        osc.connect(env).connect(this.out);
        
        osc.start(t);
        osc.stop(t + length);
    }
}

با این متد قدرتمند، ما باگ فیزیکی اسپیکر را با مهندسی نرم‌افزار برطرف کردیم.


تئوری موسیقی برای برنامه‌نویس‌ها (بخش دوم): مهندسی هارمونیک‌ها و معماری نسبت‌ها

در بخش قبلی زیربنای تولید صدا را پایه‌ریزی کردیم و باگ کلیک فیزیکی اسپیکر را با الگوی Envelope حل کردیم. اما صدای تولید شده هنوز خام و مصنوعی است. بیایید عمیق‌تر شویم.

رنگ صدا (Timbre): فرکانس‌هایی که شما درخواست نکردید

موج سینوسی (Sine wave) یک فرکانس منفرد است و هیچ چیز دیگری در آن وجود ندارد؛ به همین دلیل است که صدای آن شبیه به تست‌های شنوایی‌سنجی است و هیچ شباهتی به سازهای واقعی ندارد. وقتی یک سیم گیتار را که روی فرکانس ۴۴۰ هرتز کوک شده به صدا درمی‌آورید، یک موج دریافت می‌کنید که ۴۴۰ بار در ثانیه تکرار می‌شود، اما آن سیم به طور همزمان در نیمه‌ها، یک‌سوم‌ها و یک‌چهارم‌های طول خود نیز در حال ارتعاش است. این‌ها فرکانس‌های اضافه‌ای در ۸۸۰، ۱۳۲۰، ۱۷۶۰ و به همین ترتیب رو به بالا هستند که همگی سوار بر فرکانس اصلی که شما درخواست کرده بودید، حرکت می‌کنند.

به این پشته از فرکانس‌ها، سری هارمونیک (Harmonic Series) گفته می‌شود. نتی که شما نواختید «فرکانس پایه» (Fundamental) نام دارد و سری هارمونیک، همان فرکانس است که ضرب در اعداد صحیح ۱، ۲، ۳، ۴، ۵ و الی آخر شده است:

  • ۱ برابر = ۲۲۰ هرتز

  • ۲ برابر = ۴۴۰ هرتز

  • ۳ برابر = ۶۶۰ هرتز

  • ۴ برابر = ۸۸۰ هرتز

این فرکانس‌ها به تنهایی نسبتاً خسته‌کننده‌اند. چیزی که اهمیت دارد این است که آن‌ها به عنوان یک «بسته» (Package) با هم می‌رسند، و دستورالعملی که تعیین می‌کند هر کدام از این فرکانس‌ها نسبت به بقیه چقدر بلندتر یا ضعیف‌تر باشد، دقیقاً همان چیزی است که باعث می‌شود صدای یک ویولن شبیه ویولن باشد و نه ترومپت. موسیقیدان‌ها به این ویژگی رنگ صدا (Timbre) می‌گویند، در حالی که نت پایه در هر دو ساز کاملاً یکسان است.

از منظر برنامه‌نویسی و وب، مرورگر به صورت پیش‌فرض چهار دستورالعمل موجی آماده را در اختیار ما قرار می‌دهد:

1
2
3
4
5
6
7
8
9
10
11
// بسط دادن منطق بخش قبل برای تست شکل موج‌ها
playWaveforms() {
    const types = ["sine", "triangle", "square", "sawtooth"];
    const baseFreq = 220;
    
    types.forEach((type, i) => {
        // پخش نت با تاخیر زمانی منظم برای شنیدن تفاوت‌ها
        this.playNoteWithEnvelope(baseFreq, i * 0.7, 0.6, type);
    });
}

در این منطق، فرکانس پایه در هر چهار حالت دقیقاً ۲۲۰ هرتز است، اما چهار شخصیت کاملاً متفاوت را می‌شنویم. موج مربعی (square) تنها شامل هارمونیک‌های فرد است، به همین دلیل صدایی توخالی و کمی الکترونیکی تولید می‌کند. در مقابل، موج دندان‌اره‌ای (sawtooth) شامل تمامی هارمونیک‌ها است و صدایی خشن و وزوزمانند دارد. شکل هر موج در واقع همان «دستورالعمل هارمونیک» آن است.

سری هارمونیک را به خاطر بسپارید، زیرا بخش عمده‌ای از مفاهیم بعدی را توضیح می‌دهد. هر نتی که می‌نوازید، پشته‌ای از نت‌های اضافه‌ی ساکت‌تر را همراه با خود می‌کشد. اینکه این نت‌ها چه هستند در کنترل ما نیست؛ این یک منطق حسابی (Arithmetic) است که توسط فیزیکِ سیم‌های مرتعش و ستون‌های هوا ثابت شده است و در هر سازی که بر اساس آن‌ها ساخته شده، یکسان عمل می‌کند.

دو برابر کردن فرکانس، همان نت را به شما می‌دهد

به این آرایه از فرکانس‌ها دقت کنید: [110, 220, 440, 880, 1760]. هر کدام از این نت‌ها، دقیقاً دو برابر فرکانس قبلی خود است. این‌ها زیر و بمی (Pitch) متفاوتی دارند، اما گوش ما آن‌ها را به عنوان یک نتِ کاملاً یکسان می‌شنود. نه فقط مشابه، بلکه دقیقاً همان نت. فرهنگ‌های مختلف در سراسر جهان بدون هیچ‌گونه ارتباطی با یکدیگر، مستقلاً به این نتیجه رسیده‌اند: فرکانس را دو برابر کنید و به صدایی چنان شبیه به صدای قبلی می‌رسید که شایسته داشتن نامی یکسان است. در نت‌نویسی غربی، تمام فرکانس‌های مثال بالا «A» (لا) نامیده می‌شوند و به فاصله بین آن‌ها اکتاو (Octave) می‌گویند.

نام‌گذاری آن‌ها به هیچ‌وجه تصادفی و قراردادی نیست و سری هارمونیک دلیل آن را به وضوح توضیح می‌دهد. هر هارمونیکی که در فرکانس ۴۴۰ هرتز وجود دارد، از قبل در سری هارمونیک ۲۲۰ هرتز نشسته است. (زیرا سری ۲۲۰ شامل ۲۲۰، ۴۴۰، ۶۶۰، ۸۸۰، ۱۱۰۰ است و سری ۴۴۰ شامل ۴۴۰، ۸۸۰، ۱۳۲۰، ۱۷۶۰). نت بالاتر هیچ فرکانسی را اضافه نمی‌کند که نت پایین‌تر از قبل در حال تولید آن نبوده باشد. بنابراین، گوش شما همان “رنگ” را صرفاً با “روشنایی” متفاوتی می‌شنود.

این مشاهده دقیق فیزیکی، دو قانون پایه‌ای برای معماری بقیه سیستم به ما می‌دهد:

  1. Pitch ماهیت ضرب‌شونده دارد، نه جمع‌شونده: بالا رفتن به اندازه یک اکتاو به معنای «ضرب‌در دو» است، نه «به‌علاوه هر چیز دیگری». فاصله ۱۱۰ تا ۲۲۰ هرتز دقیقاً ۱۱۰ هرتز است و فاصله ۸۸۰ تا ۱۷۶۰ معادل ۸۸۰ هرتز است، اما گوش انسان هر دو را دقیقاً یک فاصله صوتی کاملاً یکسان می‌شنود. فضای فرکانس یک فضای لگاریتمی است و هر فاصله‌ای در موسیقی در واقع یک «نسبت» (Ratio) است.

  2. ما فقط نیاز داریم یک اکتاو را حل کنیم: از آنجا که دو برابر کردن، شما را به همان نت برمی‌گرداند، مسئله‌ی «کدام نت‌ها باید وجود داشته باشند» به این مسئله تقلیل می‌یابد: «چگونه باید فضای بین یک فرکانس و دو برابرِ آن فرکانس را تقسیم کنیم». اگر این مسئله را یک بار حل کنید، پاسخ به دست آمده در تمام محدوده فرکانسی قابل شنیدن تکرار می‌شود.

موضوع بحث‌برانگیز اینجاست: دقیقاً چگونه باید یک اکتاو را تقسیم کنیم؟

نسبت‌های ساده صدای خوبی دارند (و دلیل آن)

ساده‌لوحانه‌ترین پاسخ (Naive answer) در کدنویسی این است که فضای اکتاو را به قسمت‌های مساوی تقسیم کنیم و کار را تمام کنیم. اما هیچ‌کس در عمل این کار را نمی‌کند، زیرا مشخص شده است که ما تمام جفت‌فرکانس‌ها را به یک شکل تجربه نمی‌کنیم. ترکیب برخی از فرکانس‌ها به نظر باثبات (Settled) می‌آید و برخی دیگر شبیه به یک اشتباه به نظر می‌رسند، و شما می‌توانید این تفاوت را فوراً با گوش خود تشخیص دهید.

بیایید شش نسبت ریاضی مختلف را در برابر یک نت پایه تست کنیم:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
testRatios() {
    const ratios = [
        ["2/1 octave", 2],               // اکتاو کامل
        ["3/2 fifth", 3 / 2],            // فاصله پنجم
        ["4/3 fourth", 4 / 3],           // فاصله چهارم
        ["5/4 major third", 5 / 4],      // فاصله سوم ماژور
        ["16/15 semitone", 16 / 15],     // نیم‌پرده (ناهنجار)
        ["√2 the awkward one", Math.SQRT2] // رادیکال 2 (غیرقابل استفاده)
    ];

    ratios.forEach(([label, ratio], i) => {
        const time = i * 1.4;
        
        // نواختن همزمان نت پایه و نتی که بر اساس نسبت ریاضی ساخته شده
        this.playNoteWithEnvelope(220, time, 1.2);
        this.playNoteWithEnvelope(220 * ratio, time, 1.2);
        
        console.log(`${label} -> ${(220 * ratio).toFixed(2)}Hz`);
    });
}

وقتی این فواصل پخش می‌شوند، الگو اصلاً پنهان نیست. چهار نسبت اول کاملاً شبیه یک آکورد پایدار به نظر می‌رسند. نسبت 16/15 شبیه دو نت است که با هم در حال بحث و دعوا هستند. نسبت آخر (رادیکال ۲) صدایی آزاردهنده شبیه آژیر دزدگیر ماشین دارد. قانون کلی: هرچه کسر ساده‌تر باشد، صدای بهتری تولید می‌شود.. 2/1 اکتاو، سپس 3/2 فاصله پنجم، 4/3 چهارم، 5/4 سوم ماژور، و تا زمانی که به 16/15 می‌رسید ساختار کاملاً از هم می‌پاشد.

برای چیزی که به اندازه «خوب صدا دادن» سابجکتیو و حسی به نظر می‌رسد، این یک نتیجه‌ی به شدت ریاضی (Arithmetic) است. در معماری فیزیک صوت، دو دلیل علمی برای این رفتار وجود دارد:

  1. اشتراک در پشته‌ی هارمونیک: وقتی ۲۲۰ و ۳۳۰ هرتز (نسبت ۳:۲) را با هم می‌نوازید، نت اول هارمونیک‌های ۲۲۰، ۴۴۰، ۶۶۰، ۸۸۰، ۱۱۰۰ و ۱۳۲۰ را تولید می‌کند. نت دوم هارمونیک‌های ۳۳۰، ۶۶۰، ۹۹۰ و ۱۳۲۰ را تولید می‌کند. بنابراین آن‌ها در ۶۶۰ و ۱۳۲۰ هرتز دقیقاً با هم نقطه اشتراک دارند. دو نتی که یک فاصله پنجم از هم دور هستند، واقعاً دو صدای کاملاً مجزا محسوب نمی‌شوند؛ آن‌ها دو پشته از هارمونیک‌ها هستند که به شدت روی هم همپوشانی دارند و یکدیگر را تقویت می‌کنند. در مقابل، حالا ۲۲۰ را با ۳۱۱ (فرکانسی نزدیک به رادیکال ۲) ترکیب کنید. هیچ‌چیز در هیچ هارمونیکی روی هم نمی‌افتد. شما دو پشته کامل از فرکانس‌ها را دریافت می‌کنید که به لحاظ ساختاری هیچ ربطی به یکدیگر ندارند.

  2. خشونت صوتی (Roughness) و پدیده ضربان: وقتی دو فرکانس نزدیک به هم اما غیرمشابه باشند، مدام با یکدیگر هم‌فاز و غیرهم‌فاز می‌شوند (Drift in and out of phase) و شما می‌توانید پالس زدنِ ولوم صدا را احساس کنید. این پدیده Beating (ضربان) نام دارد و دقیقاً همان چیزی است که باعث می‌شود یک نت خارج از کوک، آزاردهنده و فالش به نظر برسد. هرچه این دو فرکانس به هم نزدیک‌تر شوند لرزش کُندتر می‌شود و زمانی که کاملاً یکسان شوند ناپدید می‌گردد، و سرعت این لرزش دقیقاً برابر با تفاضل فرکانس دو موج است (مثلاً شش هرتز اختلاف = شش پالس در ثانیه). وقتی با کسرهای پیچیده کار می‌کنید، دو پشته‌ی هارمونیکی پر از جفت‌فرکانس‌هایی هستند که تنها چند هرتز با هم فاصله دارند، و تک‌تک این جفت‌ها در حال پالس زدن و ضربان کوبیدن علیه یکدیگر هستند. این دقیقاً تعریف سیستماتیک ناهنجاری (Dissonance) است.

مطبوع بودن (Consonance) صرفاً این است که گوش شما بتواند یک الگوی تکرارشونده را به سرعت در این امواج پیدا کند. تا جایی که علم می‌گوید، داستان به همین سادگی است.


روی هم چیدن پنجم‌ها (Stacking Fifths) و باگی که نمی‌توانید فیکس کنید

اکنون واقعاً می‌توانیم چیزی بسازیم. ما می‌دانیم که نسبت‌های ساده، نسبت‌های خوبی هستند و بعد از خودِ اکتاو، تمیزترین نسبت در فیزیک صوت $3/2$ یا همان فاصله پنجم است.

پس چه اتفاقی می‌افتد اگر سعی کنیم یک الفبای موسیقیایی را تنها با استفاده از اکتاوها و پنجم‌ها بسازیم؟ منطقی‌ترین کار ممکن را انجام می‌دهیم: مدام با نسبت پنجم بالا می‌رویم، و هر زمان که از مرز اکتاو خارج شدیم، فرکانس را نصف می‌کنیم تا به عقب برگردیم. این تقریباً همان کاری است که فیثاغورث انجام داد، و برای مدتی هم به زیبایی کار می‌کند:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * شبیه‌سازی سیستم کوک فیثاغورثی
 * ساخت گام با استفاده از ضرب متوالی در نسبت 3/2
 */
class PythagoreanTuning {
    static generateScale(baseFreq = 220, iterations = 12) {
        let currentFreq = baseFreq;
        const notes = [baseFreq];
        
        for (let i = 0; i < iterations; i++) {
            // حرکت به فاصله پنجم بعدی
            currentFreq = (currentFreq * 3) / 2;
            
            // اگر از اکتاو (440) خارج شدیم، آن را نصف می‌کنیم تا در همان اکتاو بماند
            while (currentFreq >= baseFreq * 2) {
                currentFreq = currentFreq / 2;
            }
            
            notes.push(currentFreq);
            console.log(`Fifth ${i + 1}: ${currentFreq.toFixed(3)}Hz`);
        }
        
        return notes.sort((a, b) => a - b);
    }
}

const pythagoreanNotes = PythagoreanTuning.generateScale(220);
// پخش نت‌ها با AudioEngine که در بخش‌های قبل ساختیم
// pythagoreanNotes.forEach((f, i) => engine.playNoteWithEnvelope(f, i * 0.22, 0.3));

به نت اول و نت آخر در خروجی نگاه کنید. ما از ۲۲۰ شروع کردیم، دوازده فاصله پنجم را اعمال کردیم، و روی عدد ۲۲۲٫۹۹ فرود آمدیم. نه ۲۲۰! این عدد آنقدر نزدیک است که گوش متوجه می‌شود در حال تلاش برای رسیدن به همان نت است، و آنقدر دور است که کاملاً غیرقابل استفاده و فالش باشد.

این خطا ناشی از گرد کردن اعداد در برنامه‌نویسی نیست، بلکه یک خطای ساختاری و ریاضی است. فهم این موضوع بدون تا کردن و نصف کردن اکتاوها ساده‌تر است:

1
2
3
4
5
6
7
8
9
10
11
// محاسبه شکافی که هرگز بسته نمی‌شود
const twelveFifths = Math.pow(3/2, 12);
const sevenOctaves = Math.pow(2, 7);

console.log("twelve fifths:", twelveFifths.toFixed(6));
console.log("seven octaves:", sevenOctaves.toFixed(6));
console.log("ratio:", (twelveFifths / sevenOctaves).toFixed(6));

// تبدیل اختلاف به واحد "سنت" (Cents)
console.log("in cents:", (1200 * Math.log2(twelveFifths / sevenOctaves)).toFixed(2));

دوازده فاصله پنجمِ بی‌نقص، از هفت اکتاوِ بی‌نقص با ضریب ۱٫۰۱۳۶ پیشی می‌گیرند (Overshoot می‌کنند). به این شکاف، کمای فیثاغورثی (Pythagorean comma) می‌گویند. «سنت» (Cent) واحدی است که فاصله‌های Pitch با آن اندازه‌گیری می‌شوند و در یک اکتاو ۱۲۰۰ سنت وجود دارد. بنابراین ۲۳ سنت، حدوداً معادل یک‌چهارم شکاف بین دو کلید مجاور در پیانو است، و شما می‌توانید این اختلاف را به شکل یک ضربان زشت و کند (Beating) بشنوید.

و این مشکل هرگز قابل حل نیست، آن هم به دلیلی که هر برنامه‌نویسی فوراً آن را تشخیص می‌دهد. روی هم چیدن پنجم‌ها به معنای ضرب کردن در $3/2$ است، بنابراین بعد از $n$ بار شما در نقطه $3^n / 2^n$ هستید. روی هم چیدن اکتاوها به معنای $2^m$ است. برای اینکه این دو خط هرگز به هم برسند، شما به معادله‌ی $3^n = 2^{n+m}$ نیاز دارید، که نیازمند این است که توانی از ۳ با توانی از ۲ برابر شود. سه و دو هر دو اعداد اول (Prime) هستند. این تساوی هرگز اتفاق نمی‌افتد، برای هیچ $n$ای، در هیچ زمانی.

سیستمی که ما می‌خواهیم، که در آن اکتاو خالص باشد و پنجم‌ها هم خالص باشند و همه‌چیز در یک حلقه تمیز بسته شود، اصلاً وجود ندارد و هرگز وجود نداشته است. تمام سیستم‌های کوک (Tuning systems) در طول تاریخ، صرفاً تصمیمات متفاوتی برای این بوده‌اند که این «خطا» را کجا تخلیه و پنهان کنند.

اعتدال مساوی (Equal Temperament): یک مصالحه مهندسی‌شده

پاسخ مدرن به این باگ این است که کلاً قید نسبت‌های خالص را بزنیم. اکتاو را بردارید، آن را به دوازده گام ضرب‌شونده‌ی کاملاً مساوی تقسیم کنید، و بپذیرید که از این به بعد هیچ‌چیز به جز خودِ اکتاو، هرگز دوباره کاملاً دقیق و بی‌نقص نخواهد بود.

یک گامِ تکی (یک نیم‌پرده)، برابر است با ریشه دوازدهم عدد ۲:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class EqualTemperament {
    // ریشه دوازدهم عدد دو
    static SEMITONE = Math.pow(2, 1/12);

    static calculateSteps(baseFreq = 220, steps = 12) {
        let freq = baseFreq;
        for (let i = 0; i <= steps; i++) {
            // engine.playNoteWithEnvelope(freq, i * 0.2, 0.28);
            freq = freq * this.SEMITONE;
        }
        console.log(`twelve steps later: ${(220 * Math.pow(this.SEMITONE, 12)).toFixed(10)}`);
    }
}

دوازده قدم جلو رفتن ما را دقیقاً روی عدد ۴۴۰ فرود می‌آورد، زیرا ریشه‌های ریاضی اینگونه کار می‌کنند؛ بنابراین اکتاو در این معماری از پایه و اساس بی‌نقص است و بقیه‌ی نت‌ها همگی تقریب‌زده‌شده هستند.

سوال واضح این است: چرا دقیقاً دوازده؟ این ربطی به سنت ندارد، و شما می‌توانید پاسخ آن را با یک الگوریتم Brute-force در چند خط کد پیدا کنید. اکتاو را برای هر عدد منطقیِ $n$ به قسمت‌های مساوی تقسیم کنید، و سپس بررسی کنید که بهترین گام موجود در آن تقسیم‌بندی، چقدر به یک فاصله پنجم واقعی ($3/2$ یا همان $1.5$) نزدیک است:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function findBestFifth(totalDivisions) {
    let minError = Infinity;
    let bestStep = 0;
    
    for (let candidate = 1; candidate < totalDivisions; candidate++) {
        // محاسبه میزان انحراف از پنجمِ بی‌نقص (1.5)
        const offset = Math.abs(Math.pow(2, candidate / totalDivisions) - 1.5);
        
        if (offset < minError) {
            minError = offset;
            bestStep = candidate;
        }
    }
    return { step: bestStep, error: minError };
}

// بررسی تمام تقسیم‌بندی‌های ممکن از 5 تا 25
for (let n = 5; n <= 25; n++) {
    const { step, error } = findBestFifth(n);
    const errorPercentage = (error / 1.5) * 100;
    console.log(`${n} steps: best fifth is step ${step}, off by ${errorPercentage.toFixed(3)}%`);
}

دوازده، اولین تقسیم‌بندی است که فاصله پنجم را با خطایی در حدود یک‌دهمِ درصد به درستی تولید می‌کند، و بیش از سه برابر بهتر از هر عدد کوچکتر از خودش عمل می‌کند. عدد بیست و چهار هم با آن مساوی می‌شود، اما فقط به این دلیل که بیست و چهار در واقع همان سیستم دوازده‌تایی است که بین هر جفت نت، یک نت اضافه گنجانده شده است؛ بنابراین یک رقیب واقعی نیست. شما باید تا اعداد ۲۹ و ۴۱ بالا بروید تا فاصله پنجمِ بهتری پیدا کنید، و هیچ‌کس قرار نیست کیبوردی بسازد که در هر اکتاوش ۴۱ کلید داشته باشد! دوازده، «ارزان‌ترین» تعداد نتی است که می‌تواند یک فاصله پنجم قانع‌کننده را برای ما بخرد، و وقتی فاصله پنجم نزدیک به واقعیت باشد، فواصل چهارم و سوم هم به همراه آن درست از آب درمی‌آیند.

اما این تقریب چقدر نزدیک است؟

1
2
3
4
5
6
7
const pureFifth = (220 * 3) / 2;
const temperedFifth = 220 * Math.pow(2, 7/12);

console.log("pure fifth: ", pureFifth.toFixed(4) + "Hz");
console.log("tempered fifth: ", temperedFifth.toFixed(4) + "Hz");
console.log("difference: ", (1200 * Math.log2(temperedFifth / pureFifth)).toFixed(2), "cents");

دقیقاً دو سنت پایین‌تر (Flat). اگر با دقت گوش کنید، در جفت دوم یک لرزش کُند وجود دارد، و این لرزش و ضربان در تک‌تک فواصل پنجمی که روی هر پیانویی در کره زمین نواخته می‌شود، حضور دارد. ما همگی تصمیم گرفتیم که «کمی اشتباه بودن در همه‌جا» بسیار بهتر از این است که در یک گام بی‌نقص باشیم و در سایر گام‌ها غیرقابل‌استفاده!

پاداشِ این مصالحه این است که نت‌ها حالا تبدیل به اعداد صحیح (Integers) شده‌اند. هر نتی را به عنوان عدد 0 انتخاب کنید، هر نت دیگر صرفاً به اندازه یک عدد صحیحِ نیم‌پرده‌ای از آن فاصله دارد. قرارداد استاندارد ما شماره‌گذاری MIDI است، که در آن عدد ۶۹ نماینده نت A با فرکانس ۴۴۰ هرتز است و تبدیل آن تنها یک خط کد است:

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * تبدیل شماره نت MIDI به فرکانس واقعی
 */
const midiToFreq = (noteNumber) => 440 * Math.pow(2, (noteNumber - 69) / 12);

console.log("60 (middle C):", midiToFreq(60).toFixed(2));
console.log("69 (the A above it):", midiToFreq(69).toFixed(2));
console.log("81 (an octave up):", midiToFreq(81).toFixed(2));

// نواختن یک گام ماژور ساده فقط با آرایه‌ای از اعداد صحیح
// [60, 62, 64, 65, 67, 69, 71, 72].forEach((n, i) => engine.playNoteWithEnvelope(midiToFreq(n), i * 0.25, 0.4));

این خط آخر یک گام ماژور را می‌نوازد، در حالی که ما هنوز حتی تعریف نکرده‌ایم که گام چیست. از اینجا به بعد همه‌چیز در این مقاله با استفاده از آرایه‌ها انجام می‌شود، و من دیگر نیازی ندارم که اصلاً به فرکانس‌ها فکر کنم.


تئوری موسیقی برای برنامه‌نویس‌ها (بخش سوم): گام‌ها، چرخش آرایه‌ها و مهندسی احساسات

در بخش قبلی دیدیم که چگونه با استفاده از سیستم «اعتدال مساوی» (Equal Temperament)، باگِ فیزیکیِ کوکِ فیثاغورثی را حل کردیم و به ۱۲ نتِ استاندارد با فواصل لگاریتمی مساوی رسیدیم. نتیجه این شد که نت‌ها حالا صرفاً اعداد صحیح (Integers) هستند (مثل فرمت MIDI) و ما می‌توانیم با یک تابع ساده، هر عدد را به فرکانس تبدیل کنیم.

اما اگر به کیبورد پیانو نگاه کنید، یک تصادف تاریخی (Historical accident) را می‌بینید. با وجود اینکه ما دوازده نت با فواصل کاملاً مساوی در هر اکتاو داریم، تنها هفت نت دارای حروف الفبا و کلیدهای بزرگِ سفید هستند و پنج نتِ دیگر با علامت دیز (Sharp) روی کلیدهای کوچکِ سیاه قرار گرفته‌اند. فیزیکِ زیرینِ این سیستم کاملاً یکنواخت است، اما رابط کاربریِ کیبورد اصلاً یکنواخت نیست. دلیل این طراحی چیست؟

چرا دوازده نت، اما نه همه آن‌ها با هم؟

داشتن دوازده نت به این معنا نیست که باید از همه آن‌ها همزمان استفاده کنیم. اگر تمام ۱۲ نت را پشت سر هم بنوازید (گام کروماتیک)، نتیجه به طرز عجیبی غیرموسیقایی است و بیشتر شبیه به یک افکت صوتی (Sound effect) به نظر می‌رسد. چون در این حالت تمام فواصل کاملاً یکسان هستند، هیچ نتی برجسته نمی‌شود، هیچ حسی از «خانه» یا مبدأ وجود ندارد و گوش شما هیچ راهی برای تشخیص اینکه کجای این سیستم قرار دارید، نخواهد داشت.

موسیقی در واقع انتخابِ یک زیرمجموعه (Subset) از این ۱۲ نت است. تقریباً همیشه این زیرمجموعه شامل هفت نت است که به گونه‌ای انتخاب شده‌اند تا فواصل بین آن‌ها نامساوی باشد. این فواصل نامساوی دقیقاً همان چیزی است که به گوش شما اجازه می‌دهد یک نت را از نت دیگر تشخیص دهد. به این زیرمجموعه گام (Scale) می‌گویند.

یک برنامه‌نویس، گام را به شکل مجموعه‌ای از نت‌ها نمی‌نویسد، بلکه آن را به صورت لیستی از فواصل (Gaps/Steps) بین نت‌ها تعریف می‌کند.

یک عدد، مرز بین شادی و غم

بیایید یک کلاس MusicTheory برای ساختن این گام‌ها بنویسیم:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MusicTheory {
    /**
     * تبدیل شماره نت MIDI به فرکانس
     */
    static midiToFreq(n) {
        return 440 * Math.pow(2, (n - 69) / 12);
    }

    /**
     * ساخت آرایه‌ای از نت‌ها بر اساس نت ریشه (Root) و الگوی فواصل
     */
    static buildScale(root, pattern) {
        return pattern.reduce(
            (notes, step) => [...notes, notes.at(-1) + step],
            [root]
        );
    }
}

// الگوی گام ماژور: فواصلی که مجموعشان دقیقاً 12 می‌شود تا اکتاو بسته شود
const majorPattern = [2, 2, 1, 2, 2, 2, 1];

// ساخت گام دو ماژور (C Major) از نت MIDI شماره 60
const cMajor = MusicTheory.buildScale(60, majorPattern);
console.log("C major:", cMajor); // خروجی: [60, 62, 64, 65, 67, 69, 71, 72]

الگوی فواصل [2, 2, 1, 2, 2, 2, 1] در مجموع برابر با ۱۲ است، بنابراین الگو دقیقاً در انتهای اکتاو بسته می‌شود. این گام ماژور است؛ آشناترین صدایی که در موسیقی غربی وجود دارد، و در کد ما صرفاً یک آرایه هفت‌عنصری است.

حالا اگر فقط یک عدد در این آرایه را تغییر دهید، حال و هوای موسیقی کاملاً عوض می‌شود. گام ماژور و مینور طبیعی (Natural Minor)، در واقع یک ایده هفت‌نتی یکسان هستند که فقط فواصل آن‌ها بُر خورده (Shuffled) است. گام‌های پنتاتونیک (Pentatonic) دو نت را از این الگو حذف می‌کنند. به همین دلیل است که نواختن نت اشتباه (فالش) در آن‌ها بسیار سخت است و تمام کلاس‌های آموزش گیتار برای مبتدیان از همین گام شروع می‌شوند. گام بلوز (Blues) دوباره یک نت عمداً ناهنجار را به الگو اضافه می‌کند.

مُدها (Modes): چرخش درایه‌های یک آرایه

در تئوری موسیقی کلاسیک، همیشه مُدها را به عنوان هفت نام عجیب و غریب یونانی (مثل دورین، فریژین و…) معرفی می‌کنند که باید آن‌ها را حفظ کنید. اما از دیدگاه یک مهندس نرم‌افزار، مُدها چیزی نیستند جز چرخش یک آرایه (Array Rotations).

کافیست اولین فاصله‌ی آرایه را بردارید و به انتهای آن منتقل کنید؛ به همین سادگی شما مُد بعدی را ساخته‌اید:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * متد کمکی برای چرخش (Shift) درایه‌های یک آرایه
 */
const rotateArray = (arr, by) => 
    arr.map((_, i) => arr[(i + by) % arr.length]);

const modes = [
    "Ionian", "Dorian", "Phrygian", "Lydian", 
    "Mixolydian", "Aeolian", "Locrian"
];

modes.forEach((name, i) => {
    // چرخش آرایه فواصل گام ماژور به تعداد i
    const pattern = rotateArray(majorPattern, i);
    console.log(name.padEnd(11), pattern.join(" "));
});

هفت مُد، یک آرایه واحد، و هفت چرخش مختلف. مُد Ionian همان گام ماژور است و مُد Aeolian همان مینور طبیعی است. این یعنی «ماژور» و «مینور» دو سیستم جداگانه نیستند، بلکه آن‌ها چرخشِ ۰ و چرخشِ ۵ از یک آرایه کاملاً یکسان هستند! مُد Lydian صدایی رؤیایی دارد، Phrygian لحنی اسپانیایی به خود می‌گیرد و Locrian شبیه به یک سیستمِ خراب و شکسته صدا می‌دهد. تمام این احساسات صرفاً از جابجایی جایگاهِ فواصل نسبت به نتِ شروع به دست می‌آیند. اینجا دقیقاً همان نقطه‌ای بود که احساس کردم تئوری موسیقی دیگر یک چیز قراردادی و دلخواه (Arbitrary) نیست.

آکوردها: نت‌هایی که با فواصل سوم روی هم چیده می‌شوند

آکورد (Chord) چیزی بیش از پخش چند نت به صورت همزمان نیست. اما این تعریف ضعیفی است، زیرا اکثر ترکیبات تصادفی وحشتناک صدا می‌دهند. سوال مهندسی این است: کدام ترکیبات صدای بدی ندارند؟

ما قبلاً پاسخ این سوال را در مبحث نسبت‌ها دادیم: نت‌هایی که هارمونیک‌هایشان روی هم همپوشانی (Overlap) داشته باشند. در یک گام، نت‌هایی که دارای این ویژگی هستند، دقیقاً با فاصله دو درجه از یکدیگر قرار دارند؛ فاصله‌ای که موسیقیدان‌ها به آن سوم (Third) می‌گویند. بنابراین، یک گام را بردارید، یک نت را برای شروع انتخاب کنید و سپس هر نت را یک‌درمیان بگیرید.

پایه‌ای‌ترین نوع آکورد، تِریاد (Triad) است که از اندیس‌های [0, 2, 4] گام ما تشکیل می‌شود:

1
2
3
4
5
6
7
// استخراج آکورد تریاد از یک گام
const cMajorScale = MusicTheory.buildScale(60, [2, 2, 1, 2, 2, 2, 1]);
const triadIndices = [0, 2, 4];

const triad = triadIndices.map(i => cMajorScale[i]);
console.log("triad:", triad.join(" ")); // خروجی: 60 64 67

این یک آکورد دو ماژور (C Major) است. سه نت که در فاصله‌های 0، 4 و 7 نیم‌پرده بالاتر از نت پایه (Root) قرار دارند. نسبت فرکانسی آن‌ها 1 : 1.26 : 1.50 است، که بسیار نزدیک به نسبت خالص و ساده‌ی 4 : 5 : 6 در فیزیک است. سه نسبتِ ساده که در همه‌جا هارمونیک‌های مشترک دارند و به همین دلیل صدایی به شدت باثبات و نشسته (Settled) تولید می‌کنند.

یک فاصله «سوم» می‌تواند 4 نیم‌پرده (سوم ماژور) یا 3 نیم‌پرده (سوم مینور) باشد. روی هم چیدن این دو در هر صورت ما را به فاصله 7 نیم‌پرده‌ای می‌رساند: ماژور $4+3$ است، در حالی که مینور $3+4$ است.

حالا کافیست نتِ وسطِ آرایه را فقط یک نیم‌پرده پایین بیاورید: الگوی [0, 4, 7] تبدیل به [0, 3, 7] می‌شود و شما از آکورد ماژور به آکورد مینور می‌رسید. حرکتِ یک نت به اندازه یک نیم‌پرده، تمام چیزی است که دو قطبِ احساسیِ موسیقی غربی را از هم جدا می‌کند! آکورد کاسته (Diminished) با الگوی [0, 3, 6] هر دو فاصله را فشرده می‌کند و صدایی مضطرب و حل‌نشده دارد. آکورد افزوده (Augmented) با الگوی [0, 4, 8] هر دو را کِش می‌دهد و صدایی القا می‌کند که انگار قرار است در یک فیلم اتفاق بدی بیفتد.

کشف این موضوع برایم به طرز خوبی آزاردهنده بود؛ من همیشه فکر می‌کردم ماژور و مینور دسته‌بندی‌های عمیق و پیچیده‌ای هستند، اما فهمیدم آن‌ها صرفاً یک عنصر از آرایه هستند که فقط به اندازه یک واحد (یک نیم‌پرده) با هم تفاوت دارند!


تئوری موسیقی برای برنامه‌نویس‌ها (بخش سوم): گام‌ها، چرخش آرایه‌ها و مهندسی احساسات

در بخش قبلی دیدیم که چگونه با استفاده از سیستم «اعتدال مساوی» (Equal Temperament)، باگِ فیزیکیِ کوکِ فیثاغورثی را حل کردیم و به ۱۲ نتِ استاندارد با فواصل لگاریتمی مساوی رسیدیم. نتیجه این شد که نت‌ها حالا صرفاً اعداد صحیح (Integers) هستند (مثل فرمت MIDI) و ما می‌توانیم با یک تابع ساده، هر عدد را به فرکانس تبدیل کنیم.

اما اگر به کیبورد پیانو نگاه کنید، یک تصادف تاریخی (Historical accident) را می‌بینید. با وجود اینکه ما دوازده نت با فواصل کاملاً مساوی در هر اکتاو داریم، تنها هفت نت دارای حروف الفبا و کلیدهای بزرگِ سفید هستند و پنج نتِ دیگر با علامت دیز (Sharp) روی کلیدهای کوچکِ سیاه قرار گرفته‌اند. فیزیکِ زیرینِ این سیستم کاملاً یکنواخت است، اما رابط کاربریِ کیبورد اصلاً یکنواخت نیست. دلیل این طراحی چیست؟

چرا دوازده نت، اما نه همه آن‌ها با هم؟

داشتن دوازده نت به این معنا نیست که باید از همه آن‌ها همزمان استفاده کنیم. اگر تمام ۱۲ نت را پشت سر هم بنوازید (گام کروماتیک)، نتیجه به طرز عجیبی غیرموسیقایی است و بیشتر شبیه به یک افکت صوتی (Sound effect) به نظر می‌رسد. چون در این حالت تمام فواصل کاملاً یکسان هستند، هیچ نتی برجسته نمی‌شود، هیچ حسی از «خانه» یا مبدأ وجود ندارد و گوش شما هیچ راهی برای تشخیص اینکه کجای این سیستم قرار دارید، نخواهد داشت.

موسیقی در واقع انتخابِ یک زیرمجموعه (Subset) از این ۱۲ نت است. تقریباً همیشه این زیرمجموعه شامل هفت نت است که به گونه‌ای انتخاب شده‌اند تا فواصل بین آن‌ها نامساوی باشد. این فواصل نامساوی دقیقاً همان چیزی است که به گوش شما اجازه می‌دهد یک نت را از نت دیگر تشخیص دهد. به این زیرمجموعه گام (Scale) می‌گویند.

یک برنامه‌نویس، گام را به شکل مجموعه‌ای از نت‌ها نمی‌نویسد، بلکه آن را به صورت لیستی از فواصل (Gaps/Steps) بین نت‌ها تعریف می‌کند.

یک عدد، مرز بین شادی و غم

بیایید یک کلاس MusicTheory برای ساختن این گام‌ها بنویسیم:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MusicTheory {
    /**
     * تبدیل شماره نت MIDI به فرکانس
     */
    static midiToFreq(n) {
        return 440 * Math.pow(2, (n - 69) / 12);
    }

    /**
     * ساخت آرایه‌ای از نت‌ها بر اساس نت ریشه (Root) و الگوی فواصل
     */
    static buildScale(root, pattern) {
        return pattern.reduce(
            (notes, step) => [...notes, notes.at(-1) + step],
            [root]
        );
    }
}

// الگوی گام ماژور: فواصلی که مجموعشان دقیقاً 12 می‌شود تا اکتاو بسته شود
const majorPattern = [2, 2, 1, 2, 2, 2, 1];

// ساخت گام دو ماژور (C Major) از نت MIDI شماره 60
const cMajor = MusicTheory.buildScale(60, majorPattern);
console.log("C major:", cMajor); // خروجی: [60, 62, 64, 65, 67, 69, 71, 72]

الگوی فواصل [2, 2, 1, 2, 2, 2, 1] در مجموع برابر با ۱۲ است، بنابراین الگو دقیقاً در انتهای اکتاو بسته می‌شود. این گام ماژور است؛ آشناترین صدایی که در موسیقی غربی وجود دارد، و در کد ما صرفاً یک آرایه هفت‌عنصری است.

حالا اگر فقط یک عدد در این آرایه را تغییر دهید، حال و هوای موسیقی کاملاً عوض می‌شود. گام ماژور و مینور طبیعی (Natural Minor)، در واقع یک ایده هفت‌نتی یکسان هستند که فقط فواصل آن‌ها بُر خورده (Shuffled) است. گام‌های پنتاتونیک (Pentatonic) دو نت را از این الگو حذف می‌کنند. به همین دلیل است که نواختن نت اشتباه (فالش) در آن‌ها بسیار سخت است و تمام کلاس‌های آموزش گیتار برای مبتدیان از همین گام شروع می‌شوند. گام بلوز (Blues) دوباره یک نت عمداً ناهنجار را به الگو اضافه می‌کند.

مُدها (Modes): چرخش درایه‌های یک آرایه

در تئوری موسیقی کلاسیک، همیشه مُدها را به عنوان هفت نام عجیب و غریب یونانی (مثل دورین، فریژین و…) معرفی می‌کنند که باید آن‌ها را حفظ کنید. اما از دیدگاه یک مهندس نرم‌افزار، مُدها چیزی نیستند جز چرخش یک آرایه (Array Rotations).

کافیست اولین فاصله‌ی آرایه را بردارید و به انتهای آن منتقل کنید؛ به همین سادگی شما مُد بعدی را ساخته‌اید:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * متد کمکی برای چرخش (Shift) درایه‌های یک آرایه
 */
const rotateArray = (arr, by) => 
    arr.map((_, i) => arr[(i + by) % arr.length]);

const modes = [
    "Ionian", "Dorian", "Phrygian", "Lydian", 
    "Mixolydian", "Aeolian", "Locrian"
];

modes.forEach((name, i) => {
    // چرخش آرایه فواصل گام ماژور به تعداد i
    const pattern = rotateArray(majorPattern, i);
    console.log(name.padEnd(11), pattern.join(" "));
});

هفت مُد، یک آرایه واحد، و هفت چرخش مختلف. مُد Ionian همان گام ماژور است و مُد Aeolian همان مینور طبیعی است. این یعنی «ماژور» و «مینور» دو سیستم جداگانه نیستند، بلکه آن‌ها چرخشِ ۰ و چرخشِ ۵ از یک آرایه کاملاً یکسان هستند! مُد Lydian صدایی رؤیایی دارد، Phrygian لحنی اسپانیایی به خود می‌گیرد و Locrian شبیه به یک سیستمِ خراب و شکسته صدا می‌دهد. تمام این احساسات صرفاً از جابجایی جایگاهِ فواصل نسبت به نتِ شروع به دست می‌آیند. اینجا دقیقاً همان نقطه‌ای بود که احساس کردم تئوری موسیقی دیگر یک چیز قراردادی و دلخواه (Arbitrary) نیست.

آکوردها: نت‌هایی که با فواصل سوم روی هم چیده می‌شوند

آکورد (Chord) چیزی بیش از پخش چند نت به صورت همزمان نیست. اما این تعریف ضعیفی است، زیرا اکثر ترکیبات تصادفی وحشتناک صدا می‌دهند. سوال مهندسی این است: کدام ترکیبات صدای بدی ندارند؟

ما قبلاً پاسخ این سوال را در مبحث نسبت‌ها دادیم: نت‌هایی که هارمونیک‌هایشان روی هم همپوشانی (Overlap) داشته باشند. در یک گام، نت‌هایی که دارای این ویژگی هستند، دقیقاً با فاصله دو درجه از یکدیگر قرار دارند؛ فاصله‌ای که موسیقیدان‌ها به آن سوم (Third) می‌گویند. بنابراین، یک گام را بردارید، یک نت را برای شروع انتخاب کنید و سپس هر نت را یک‌درمیان بگیرید.

پایه‌ای‌ترین نوع آکورد، تِریاد (Triad) است که از اندیس‌های [0, 2, 4] گام ما تشکیل می‌شود:

1
2
3
4
5
6
7
// استخراج آکورد تریاد از یک گام
const cMajorScale = MusicTheory.buildScale(60, [2, 2, 1, 2, 2, 2, 1]);
const triadIndices = [0, 2, 4];

const triad = triadIndices.map(i => cMajorScale[i]);
console.log("triad:", triad.join(" ")); // خروجی: 60 64 67

این یک آکورد دو ماژور (C Major) است. سه نت که در فاصله‌های 0، 4 و 7 نیم‌پرده بالاتر از نت پایه (Root) قرار دارند. نسبت فرکانسی آن‌ها 1 : 1.26 : 1.50 است، که بسیار نزدیک به نسبت خالص و ساده‌ی 4 : 5 : 6 در فیزیک است. سه نسبتِ ساده که در همه‌جا هارمونیک‌های مشترک دارند و به همین دلیل صدایی به شدت باثبات و نشسته (Settled) تولید می‌کنند.

یک فاصله «سوم» می‌تواند 4 نیم‌پرده (سوم ماژور) یا 3 نیم‌پرده (سوم مینور) باشد. روی هم چیدن این دو در هر صورت ما را به فاصله 7 نیم‌پرده‌ای می‌رساند: ماژور $4+3$ است، در حالی که مینور $3+4$ است.

حالا کافیست نتِ وسطِ آرایه را فقط یک نیم‌پرده پایین بیاورید: الگوی [0, 4, 7] تبدیل به [0, 3, 7] می‌شود و شما از آکورد ماژور به آکورد مینور می‌رسید. حرکتِ یک نت به اندازه یک نیم‌پرده، تمام چیزی است که دو قطبِ احساسیِ موسیقی غربی را از هم جدا می‌کند! آکورد کاسته (Diminished) با الگوی [0, 3, 6] هر دو فاصله را فشرده می‌کند و صدایی مضطرب و حل‌نشده دارد. آکورد افزوده (Augmented) با الگوی [0, 4, 8] هر دو را کِش می‌دهد و صدایی القا می‌کند که انگار قرار است در یک فیلم اتفاق بدی بیفتد.

کشف این موضوع برایم به طرز خوبی آزاردهنده بود؛ من همیشه فکر می‌کردم ماژور و مینور دسته‌بندی‌های عمیق و پیچیده‌ای هستند، اما فهمیدم آن‌ها صرفاً یک عنصر از آرایه هستند که فقط به اندازه یک واحد (یک نیم‌پرده) با هم تفاوت دارند!


آکوردهای هفتم (Sevenths): ورود به موسیقی واقعی

اگر یک نت چهارم اضافه کنیم، یعنی یک فاصله سومِ دیگر بالاتر برویم، به آکوردهای هفتم می‌رسیم. اینجا دقیقاً نقطه‌ای است که صداها از حالت یک سرود مذهبی ساده خارج شده و شبیه به موسیقی واقعی می‌شوند:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const midiToFreq = (n) => 440 * Math.pow(2, (n - 69) / 12);

// الگوهای آکورد هفتم
const seventhShapes = {
  "major 7th": [0, 4, 7, 11], // آکورد کافه-جاز
  "minor 7th": [0, 3, 7, 10], // نرم و کمی غم‌انگیز
  "dominant 7th": [0, 4, 7, 10] // آکورد نمایان، پر از تنش
};

Object.entries(seventhShapes).forEach(([name, shape], i) => {
  console.log(name.padEnd(13), shape.join(" "));
  // shape.forEach((s) => engine.playNoteWithEnvelope(midiToFreq(60 + s), i * 2.2, 1.8));
});

آکورد ماژور هفتم (Major 7th) همان آکورد کلاسیکِ کافه‌های جاز است. مینور هفتم (Minor 7th) نرم و کمی ملانکولیک (غم‌انگیز) است. اما آکورد نمایان هفتم (Dominant 7th) جالب‌ترین آن‌هاست. «نمایان» (Dominant) صرفاً نام سنتیِ درجه پنجم یک گام است، و این آکورد قرار است در سیستم موسیقی کارهای بسیار مهمی انجام دهد.

یک گام، دقیقاً هفت آکورد به شما می‌دهد

این همان بخشی است که باعث شد چارت‌های آکورد (Chord Charts) بالاخره برای من منطقی به نظر برسند.

هیچ ویژگی خاص و مقدسی درباره شروع از درجه اول (ریشه) وجود ندارد. اگر پروسه‌ی «یک‌درمیان انتخاب کردن نت‌ها» را برای هر یک از هفت درجه گام به نوبت انجام دهید و اکتاو را دور بزنید، هفت آکورد به دست می‌آورید که همگی تنها و تنها از همان هفت نتِ گام ساخته شده‌اند:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class ChordGenerator {
    static generateDiatonicChords(rootMidi) {
        // گام ماژور را می‌سازیم
        const scalePattern = [2, 2, 1, 2, 2, 2, 1];
        const scale = MusicTheory.buildScale(rootMidi, scalePattern).slice(0, 7);
        const names = ["C", "D", "E", "F", "G", "A", "B"];

        const getChordOnDegree = (degree) => {
            return [0, 2, 4].map((step) => {
                const i = degree + step;
                // منطق دور زدن اکتاو با Modulo
                return scale[i % 7] + Math.floor(i / 7) * 12;
            });
        };

        names.forEach((name, degree) => {
            const notes = getChordOnDegree(degree);
            const shape = notes.map((n) => n - notes[0]);
            
            // تشخیص کیفیت آکورد بر اساس فواصل تولید شده
            const quality = shape[1] === 4 ? "major" 
                          : shape[2] === 6 ? "diminished" 
                          : "minor";
            
            console.log(`${name} ${quality.padEnd(11)} ${notes.join(" ")}`);
        });
    }
}

هیچ‌کس این کیفیت‌ها را به صورت دستی انتخاب نکرده است. سه تا از آن‌ها به صورت خودکار ماژور درمی‌آیند، سه تا مینور و آخری کاسته (Diminished) می‌شود؛ و این الگو صرفاً به خاطر فواصل نامساوی در گام ماژور، به ریاضیاتِ سیستم تحمیل می‌شود. اگر پروسه یک‌درمیان را از درجه‌ای شروع کنید که همسایه‌هایش فواصل ۴ و سپس ۳ نیم‌پرده‌ای دارند، یک آکورد ماژور می‌گیرید. اگر فواصل ۳ و سپس ۴ باشند، یک آکورد مینور دریافت می‌کنید.

اعداد رومی (Roman Numerals): آدرس‌دهی نسبی در موسیقی

موسیقیدان‌ها این هفت آکورد را با اعداد رومی می‌نویسند: حروف بزرگ برای ماژور، حروف کوچک برای مینور، و یک دایره کوچک برای آکورد کاسته. I ii iii IV V vi vii°

این نت‌نویسی در حال انجام یک کار به شدت مفید است که مدت‌ها طول کشید تا من متوجه آن شوم: این روش، آکوردها را بر اساس موقعیتشان در گام توصیف می‌کند، نه بر اساس نام فرکانسی آن‌ها. یک آکورد V (پنج)، صرفاً «آکوردی است که روی درجه پنجم ساخته شده»، در هر گامی که باشید. این دقیقاً مفهوم آدرس‌دهی نسبی (Relative Addressing) در برنامه‌نویسی است. یک چارت آکورد که با اعداد رومی نوشته شده، در واقع یک «سورس کدِ مستقل از گام» (Key-independent source) است و عمل Transpose (تغییر گام آهنگ) در آن، صرفاً جمع کردن یک عدد ثابت (Constant) با کل مقادیر است. وقتی یک توالی ثابت را در دو گام مختلف می‌نوازید، گوش شما دقیقاً همان موسیقی را می‌شنود، به همین دلیل است که این نت‌نویسی اختراع شده است.

کشش و رهایی (Tension and Resolution)

یک آکورد به تنهایی فقط یک صدا است. موسیقی زمانی اتفاق می‌افتد که شما آن‌ها را در یک ترتیب خاص قرار دهید، و این ترتیب به شدت اهمیت دارد. برخی توالی‌ها حس رسیدن به مقصد (فرود) را القا می‌کنند و برخی دیگر شبیه به یک سوالِ بی‌پاسخ هستند.

قوی‌ترین کشش در این سیستم، کشش و جاذبه‌ی آکورد V برای بازگشت به آکورد I است، و دو دلیل کاملاً فیزیکی و ملموس برای آن وجود دارد:

  1. نت پیشرو (Leading Tone): نت B در آکورد V (سل ماژور) قرار دارد و دقیقاً یک نیم‌پرده پایین‌تر از ریشه آکورد I (یعنی نت C) است. نتی که تا این حد به یک مقصد نزدیک است، انگار دارد به درِ آن مقصد تکیه می‌دهد. موسیقیدان‌ها به آن نت پیشرو می‌گویند و دقیقاً مانند یک تعلیق (Cliffhanger) در داستان‌نویسی عمل می‌کند.

  2. تریتونِ بی‌ثبات (Tritone): آکورد V7 (نمایان هفتم) شامل هر دو نت B و F است که دقیقاً ۶ نیم‌پرده با هم فاصله دارند. شش نیم‌پرده یعنی تریتون، دقیقاً نصف یک اکتاو: $2^{6/12}$ که برابر است با رادیکال دو (همان نسبت گنگ و آزاردهنده‌ای که قبلاً تست کردیم). بی‌ثبات‌ترین فاصله موجود، دقیقاً درون این آکورد نشسته است. وقتی به آکورد I باز می‌گردید، هر دو نتِ این تریتون با یک حرکت یک نیم‌پرده‌ای در جهت‌های مخالف، به نت‌های باثبات حل می‌شوند (نت F به E می‌رود و نت B به C می‌رود). تنشی که در موسیقی می‌شنوید، در واقع معاوضه این نسبت گنگ فیزیکی با نسبت‌های ساده و باثبات است.

چهار توالی آکورد که ده‌هزار بار شنیده‌اید

زبانِ «تنش و رهایی» کاملاً بر روی همین مکانیزم ریاضی استوار است. وقتی این قوانین را می‌دانید، می‌توانید توالی‌هایی بسازید که پایه و اساس موسیقی مدرن هستند:

1
2
3
4
5
6
7
const progressions = {
    "I V vi IV": [1, 5, 6, 4], // توالی کلاسیک موسیقی پاپ
    "ii V I":    [2, 5, 1],    // ستون فقرات موسیقی جاز
    "I vi IV V": [1, 6, 4, 5], // موسیقی دهه‌ی 50 میلادی
    "vi IV I V": [6, 4, 1, 5]  // همان چهار آکورد پاپ، اما شروع از یک نت غمگین
};

توالی I V vi IV از چهار آکوردی تشکیل شده که بخش ترسناکی از کل موسیقی پاپ روی آن ساخته شده است. ii V I ساختار اصلی موسیقی جاز است. vi IV I V دقیقاً همان نت‌های توالی اول پاپ است، که فقط آرایه آن چرخیده تا از جای غمگین‌تری (آکورد درجه ۶ یا همان مینور) شروع شود.

شما می‌توانید این آرایه‌ها را دستکاری کنید. تقریباً هر توالیِ از اعداد بین ۱ تا ۷ در کنار هم جواب می‌دهد، زیرا تمام این آکوردها از همان هفت نت اولیه ساخته شده‌اند. داشتن یک «گام»، یک فضای محدود و ایزوله به شما می‌دهد که رسیدن به جواب‌های غلط در آن بسیار سخت است. پایان یافتن روی آکورد ۱ حس تمام شدن می‌دهد، پایان روی ۵ حس این را القا می‌کند که خط دیگری در راه است، و پایان روی ۷ به نظر می‌رسد چیزی در سیستم خراب شده است.