Beberapa tahun yang lalu, saya membuat jam alarm pada mikrokontroler ATmega8, di mana saya menerapkan synthesizer melodi sederhana satu-nada (single-voice). Ada banyak artikel di Internet untuk pemula tentang topik ini. Sebagai aturan, timer 16-bit digunakan untuk menghasilkan frekuensi (catatan), yang dikonfigurasi dengan cara tertentu, memaksa pada tingkat perangkat keras untuk mengeluarkan sinyal dalam bentuk berliku-liku pada pin tertentu dari MC. Timer kedua (8-bit) digunakan untuk mengimplementasikan durasi catatan atau jeda. Catatan menurut rumus terkenal dibandingkan dengan frekuensi, dan mereka, pada gilirannya, dibandingkan dengan angka 16-bit tertentu, berbanding terbalik dengan frekuensi yang menentukan periode penghitung waktu.
Dalam desain saya, saya memberikan tiga melodi yang ditulis dengan kunci dan skala yang sama. Jadi, saya harus menggunakan sejumlah catatan yang terbatas dan tertentu, yang membuat pemodelan lebih mudah. Selain itu, ketiga nada dimainkan dengan kecepatan yang sama. Kode catatan dan kode durasinya mudah masuk ke dalam satu byte. Satu-satunya kelemahan dari model ini adalah kurangnya fleksibilitas, kemampuan untuk dengan cepat mengedit, mengganti atau menambah melodi. Untuk merekam melodi, pertama-tama saya membuat sketsa di editor musik di komputer, kemudian menyalin catatan dan durasinya, dengan penomoran yang saya putuskan sebelumnya, dan kemudian membentuk byte yang dihasilkan. Saya melakukan operasi terakhir menggunakan program Excel.
Di masa depan, saya ingin menghilangkan kelemahan tersebut, mengkhianati desain universalitas tertentu dan mengurangi waktu untuk menerapkan melodi. Ada gagasan bahwa program MK membaca byte dari salah satu format musik terkenal. Yang paling populer dan umum adalah format MIDI. Secara lebih harfiah, ini bukan format sebagai keseluruhan "sains" yang dapat dibaca di Internet. Spesifikasi MIDI mendefinisikan protokol untuk mengirimkan pesan real-time melalui antarmuka fisik yang sesuai dan menjelaskan bagaimana file midi diatur di mana pesan-pesan ini dapat disimpan. Format midi berorientasi musik, sehingga menemukan aplikasi di bidang yang relevan. Ini adalah kontrol sinkron dari peralatan suara, musik berwarna, synthesizer musik dan robot, dll. Di ranah domestik, format midi ditemui di era awal pengembangan ponsel. Dalam hal ini, pesan tentang penyertaan atau penonaktifan not tertentu, informasi tentang alat musik, volume not yang terdengar dan sebagainya dicatat dalam file midi. Ponsel yang memutar file seperti itu mengandung synthesizer yang menginterpretasikan pesan midi dalam file ini secara real time dan memutar melodi. Pada tahap paling awal, ponsel hanya mampu memainkan melodi nada tunggal. Seiring waktu, apa yang disebut polifoni muncul.
Di Internet, saya bertemu artikel tentang implementasi synthesizer polifonik di MK, yang membaca file midi. Dalam hal ini, setidaknya, "tabel gelombang" yang telah dibentuk sebelumnya (daftar bentuk gelombang suara) digunakan untuk setiap alat musik yang disimpan dalam memori MK. Dan dalam kasus khusus saya, kami akan fokus pada implementasi model yang lebih sederhana: synthesizer single-tone (single-voice).
Untuk mulai dengan, saya hati-hati mempelajari struktur file midi, dan sampai pada kesimpulan bahwa, di samping informasi yang diperlukan tentang catatan, itu berisi informasi tambahan yang berlebihan. Oleh karena itu, diputuskan untuk menulis program sederhana untuk mengonversi file midi ke formatnya sendiri. Program ini, bekerja dengan banyak file MIDI, tidak hanya mengkonversi format, tetapi juga mengaturnya dengan cara tertentu. Di muka, saya memutuskan untuk mengatur penyimpanan banyak lagu dalam memori ROM (EEPROM 24XX512). Untuk kenyamanan visualisasi dalam editor HEX, saya memastikan bahwa setiap melodi dimulai dari awal sektor ini. Tidak seperti kartu SD (misalnya), konsep sektor tidak berlaku untuk ROM yang digunakan, jadi saya mengekspresikan diri saya dengan persyaratan. Ukuran sektor adalah 512 byte. Dan sektor pertama ROM dicadangkan untuk alamat-alamat sektor awal dari setiap melodi. Diasumsikan bahwa melodi dapat mengambil beberapa sektor.
Deskripsi lengkap tentang format file midi, tentu saja, tidak layak dilakukan di sini. Saya akan menyentuh hanya pada poin yang paling penting dan perlu. File midi berisi 16 saluran, yang, biasanya, berhubungan dengan satu atau lebih alat musik lainnya. Dalam kasus kami, tidak masalah instrumen apa itu, dan hanya satu saluran yang dibutuhkan. Konten dari masing-masing saluran, bersama-sama dengan header, disusun dalam file midi sesuai dengan prinsip yang sangat mirip dengan mengatur penyimpanan aliran video dan audio dalam wadah AVI. Saya menulis tentang yang terakhir di salah satu artikel saya. Header file midi adalah seperangkat beberapa parameter. Salah satu parameter tersebut adalah resolusi waktu. Ini dinyatakan dalam jumlah "ticks" (semacam pixel) per quarter (PPQN). Seperempat adalah rentang waktu di mana not seperempat dimainkan. Tergantung pada tempo melodi, durasi kuartal mungkin berbeda. Oleh karena itu, durasi satu "pixel" (periode pengambilan sampel) tergantung pada tempo dan PPQN. Semua informasi tentang waktu acara ditentukan dengan keakuratan selama durasi ini.
Selain itu, tajuk berisi jenis file MIDI (tipe 0 atau tipe 1) dan jumlah saluran. Tanpa merinci, kami akan bekerja dengan tipe 1, jumlah saluran 2. File midi dengan melodi satu nada, secara logis, berisi satu saluran. Tetapi dalam file midi "tipe 1" ada, selain yang utama, saluran "non-musikal" lainnya di mana informasi tambahan dicatat yang tidak mengandung catatan. Inilah yang disebut metadata. Tidak perlu masuk ke detail. Satu-satunya informasi yang kami butuhkan di sana adalah ada informasi tentang langkah, dan dalam format yang tidak biasa: mikrodetik per kuartal. Di masa depan, akan ditunjukkan bagaimana menggunakan informasi ini, bersama dengan PPQN, untuk mengkonfigurasi timer MK, yang bertanggung jawab atas tempo.
Di blok saluran utama dengan catatan, kami hanya tertarik pada informasi tentang peristiwa menghidupkan dan mematikan catatan. Catatan yang memungkinkan acara memiliki dua parameter: nomor catatan dan volume. Secara total, 128 catatan dan 128 level volume disediakan. Kami hanya tertarik pada parameter pertama, karena tidak peduli berapa volume nada itu: semua nada saat memainkan melodi MK akan berbunyi pada volume yang sama. Dan, tentu saja, melodi tidak boleh berisi nota “overdubbed”, yaitu, kapan saja, lebih dari satu not seharusnya tidak berbunyi pada saat bersamaan. Kode acara mengambil (menghidupkan) catatan adalah 0x90. Kode acara off adalah 0x80. Namun, setidaknya editor Cakewalk Pro Audio 9 tidak menggunakan acara dengan kode 0x80 saat mengekspor komposisi ke format midi. Sebaliknya, acara 0x90 terjadi di seluruh bagian musik, dan catatan bahwa not dimatikan adalah volume nolnya. Yaitu, acara "matikan not" setara dengan acara "matikan not dengan volume nol". Mungkin ini dilakukan karena alasan ekonomi. Menurut spesifikasinya, kode acara tidak dapat ditulis ulang jika acara ini diulang. Antara peristiwa, informasi tentang interval waktu direkam dalam format panjang variabel. Ini adalah nilai integer dari jumlah "ticks" yang disebutkan di atas. Paling sering, satu byte sudah cukup untuk merekam interval waktu. Jika dua peristiwa mengikuti satu demi satu, maka di antara mereka interval waktu jelas sama dengan nol. Ini, misalnya, menonaktifkan yang pertama dan masuknya catatan kedua yang mengikutinya, jika tidak ada jeda (spasi) di antara mereka.
Mari kita coba menulis urutan catatan menggunakan program "Cakewalk Pro Audio 9". Ada banyak editor, tetapi saya memilih yang pertama.

Pertama, Anda perlu mengkonfigurasi pengaturan proyek. Di editor ini Anda dapat mengatur resolusi dalam waktu (PPQN). Saya memilih nilai minimum sama dengan 48. Nilai terlalu besar tidak ada artinya, karena Anda harus bekerja dengan jumlah besar melebihi ukuran 1 byte. Namun nilai minimum 48 cukup memuaskan. Di hampir setiap melodi, not yang lebih pendek dari 1/32 tidak ditemukan. Dan jika jumlah "ticks" per quarter adalah 48, maka note atau pause 1/32 akan memiliki durasi 48 / (32/4) = 6 "ticks". Yaitu, ada kemungkinan teoritis untuk benar-benar membagi 1/32 catatan dengan 2, dan bahkan oleh 3. Kami meninggalkan parameter yang tersisa di jendela properti proyek secara default.

Selanjutnya, buka properti trek pertama dan berikan nomor saluran yang sama dengan 1. Untuk selera Anda, pilih tambalan yang sesuai dengan alat musik saat memainkan melodi di editor. Nomor tambalan, tentu saja, tidak akan mempengaruhi hasil akhir.

Tempo melodi diatur dalam jumlah perempat per menit pada bilah alat editor. Nilai tempo default adalah 100 bpm.
Mikrokontroler memiliki timer 8-bit, yang, sebagaimana telah disebutkan, akan digunakan untuk mengontrol durasi nada dan jeda yang terdengar. Diputuskan bahwa interval waktu antara operasi yang berdekatan (gangguan) dari pengatur waktu seperti itu akan sesuai dengan interval satu "centang". Tergantung pada tempo melodi, nilai interval waktu ini akan berbeda. Saya memutuskan untuk menggunakan interupsi timer overflow. Dan tergantung pada parameter inisialisasi timer awal, dimungkinkan untuk menyesuaikan interval waktu yang sama ini, yang tergantung pada tempo melodi. Sekarang mari kita beralih ke perhitungan.
Sebagai aturan, dalam praktiknya, rata-rata, tempo lagu terletak pada kisaran urutan 50 hingga 200. Sudah dikatakan bahwa tempo dalam file midi diatur dalam seperempat mikrodetik. Untuk tempo 50, nilai ini adalah 60.000.000 / 50 = 1.200.000, dan untuk tempo 250 akan menjadi 240.000. Karena, menurut proyek, seperempat berisi 48 kutu, panjang centang untuk tempo minimum adalah 1.200.000 / 48 = 25.000 μs. Dan untuk kecepatan maksimum, jika Anda menghitung dengan cara yang sama, - 5000 μs. Untuk MK dengan frekuensi kuarsa 8 MHz dan pembagi pengatur waktu maksimum 1024, kami mendapatkan yang berikut ini. Untuk kecepatan minimum, timer harus dihitung 25000 / (1024/8) = 195 kali. Hasilnya dibulatkan ke nilai integer terdekat, kesalahan pembulatan praktis tidak mempengaruhi hasilnya. Untuk langkah maksimum - 5000 / (1024/8) = 39. Di sini, kesalahan pembulatan tidak mempengaruhi semua lagi, karena nilai bulat dari 39 juga diperoleh untuk nilai tempo tetangga dari 248 hingga 253. Dengan demikian, timer harus diinisialisasi dengan nilai terbalik: untuk tempo minimum - (256-195) = 61, dan untuk maksimum - (256) -39) = 217. Kecepatan minimum di mana timer akan disediakan dalam konfigurasi MK saat ini adalah 39 bpm. Dengan nilai ini, timer harus dihitung 250 kali. Dan dengan nilai 38 - sudah 257, yang melampaui batas timer. Saya memutuskan untuk mengambil nilai 40 bpm untuk kecepatan minimum, dan 240 untuk maksimum.
Untuk menghitung jumlah kutu, penghitung waktu virtual berdasarkan sebelumnya akan digunakan. Ini adalah jumlah kutu yang menetapkan durasi catatan atau jeda, sebagaimana telah disebutkan di atas.
Untuk menerapkan pemutaran catatan, timer 16-bit digunakan. Menurut spesifikasi MIDI, total 128 catatan disediakan. Tetapi dalam praktiknya mereka digunakan lebih sedikit. Terlebih lagi, not oktaf terendah (dengan frekuensi sekitar 50 Hz) dan tertinggi (dengan frekuensi sekitar 8 kHz) tidak akan direproduksi oleh mikrokontroler secara harmonis. Tetapi untuk semua ini, timer 16-bit dengan pembagi tetap mencakup hampir seluruh rentang catatan yang disediakan oleh midi, yaitu, tanpa 35 yang pertama. Tapi saya memilih sebagai catatan awal dengan nomor 37 (kodenya adalah 36, karena pengkodean berasal dari nol). Ini dilakukan untuk kenyamanan, karena nomor ini sesuai dengan nada "C", sebagai nada pertama dalam skala tradisional. Ini sesuai dengan itu dengan frekuensi 65,4 Hz, dan setengah siklus - 1 / 65,4 / 2 = 0,00764 detik. Periode waktu ini pada frekuensi MK 8 MHz dan pembagi 1 (yaitu, tanpa pembagi) akan menghitung timer kira-kira secara keseluruhan untuk 0,00764 / (1/8000000) = 61156 kali. Untuk nada ke-35, jika Anda menghitung, nilai ini akan menjadi 68645, yang berada di luar jangkauan timer 16-bit. Tetapi, bahkan jika ada kebutuhan untuk memainkan catatan di bawah tanggal 36, Anda dapat memasukkan pembagi waktu pertama yang tersedia, sama dengan 8. Tapi tidak ada kebutuhan praktis untuk ini, sama seperti tidak ada bahkan untuk memainkan catatan paling atas. Namun demikian, untuk nada 128 paling atas, nada "G" dengan frekuensi 12.543,85 Hz, nilai timer adalah, jika dihitung dengan cara yang sama, 319. Secara spesifik dari semua perhitungan di atas ditentukan oleh konfigurasi spesifik dari mode timer, yang akan ditampilkan kemudian.
Sekarang saya memiliki pertanyaan yang tidak kalah pentingnya: bagaimana cara mendapatkan hubungan antara nomor catatan dan kode untuk penghitung waktu? Ada rumus yang terkenal untuk menghitung frekuensi catatan berdasarkan nomornya. Dan kode timer untuk frekuensi yang diketahui dihitung dengan mudah, seperti yang ditunjukkan di atas dalam contoh. Tetapi akar derajat ke-12 muncul dalam rumus untuk ketergantungan frekuensi pada catatan, dan secara umum, saya tidak ingin memuat pengontrol dengan prosedur komputasi seperti itu. Di sisi lain, membuat array kode penghitung waktu untuk semua catatan juga tidak rasional. Dan saya memutuskan untuk melakukan yang berikut, memilih jalan tengah. Cukup membuat array kode timer untuk 12 not pertama, yaitu satu oktaf. Dan catatan oktaf berikut harus diperoleh dengan mengalikan frekuensi not oktaf pertama secara berurutan dengan 2. Atau, hal yang sama, dengan secara berurutan membagi nilai-nilai kode penghitung waktu dengan 2. Kemudahan lain adalah bahwa angka oktaf, secara kebetulan, adalah argumen dalam operasi perpindahan bitwise ke kanan ( »), Yang akan digunakan sebagai operasi membagi dengan kekuatan dua. Saya memilih operator ini bukan karena kebetulan, karena argumennya mencerminkan eksponen dari kekuatan pembagi (jumlah divisi dengan 2). Dan ini adalah angka oktaf. Untuk set catatan saya, total 8 oktaf terlibat (oktaf terakhir tidak lengkap). Catatan dalam file midi dikodekan dengan satu byte, lebih tepatnya, 7 bit. Untuk memainkan not dalam MK, sesuai dengan ide di atas, Anda harus terlebih dahulu menghitung angka oktaf dan nomor not dalam oktaf menggunakan kode catatan. Operasi ini dilakukan pada tahap konversi file midi ke format yang disederhanakan. Delapan oktaf dapat dikodekan dalam tiga bit, dan 12 not dalam satu oktaf dapat dikodekan dalam empat bit. Secara total, ternyata catatan dikodekan dalam tujuh bit yang sama seperti pada file midi, tetapi hanya dalam representasi yang berbeda yang cocok untuk MK. Karena fakta bahwa 16 bit dapat dikodekan dengan 4 bit, dan catatan dalam oktaf 12, ada byte yang tidak digunakan.
Bit kedelapan terakhir dapat digunakan sebagai penanda untuk mengaktifkan atau menonaktifkan catatan. Dalam kasus MK, karena kebulatan suara melodi, informasi tentang nada yang dibisukan akan berlebihan. Dengan pergantian not secara langsung dalam melodi, tidak ada "turn-off-turn on", tetapi "switch" not. Dan pada saat jeda, "diam diaktifkan", untuk itu Anda dapat memilih byte khusus dari set byte yang tidak digunakan, dan tidak menggunakan informasi tentang mematikan nada sama sekali. Gagasan seperti itu bagus karena menghemat ukuran melodi yang dihasilkan setelah konversi, tetapi umumnya menyulitkan model. Saya tidak mengikuti ide ini, karena sudah ada banyak memori.
Informasi tentang catatan melodi dalam file midi disimpan di blok saluran yang sesuai dalam tampilan "interval-event-interval-event ...". Dalam format yang dikonversi, prinsip yang persis sama berlaku. Untuk merekam suatu peristiwa (menghidupkan atau mematikan catatan), seperti disebutkan di atas, satu byte digunakan. Bit pertama (bit paling signifikan 7) mengkodekan jenis acara. Nilai "1" adalah nada aktif, dan nilai "0" adalah nada mati. Tiga bit berikutnya mengkodekan angka oktaf, dan empat bit terendah mengkodekan nomor catatan dalam oktaf. Satu byte juga digunakan untuk merekam interval waktu. Dalam format midi asli, format panjang variabel digunakan untuk ini. Kelemahan kecilnya adalah bahwa hanya 7 bit yang menyandikan interval waktu (jumlah "ticks"), dan bit kedelapan adalah tanda kelanjutan. Artinya, dengan satu byte, sebenarnya, Anda dapat menyandikan interval hingga 128 ticks. Tetapi karena interval waktu antara peristiwa dalam melodi yang nyata dan sederhana kadang-kadang melebihi 128, tetapi hampir tidak pernah melebihi 256, saya meninggalkan format panjang variabel dan dikelola dengan satu byte. Ini mengkodekan interval waktu hingga 256 kutu. Karena proyek ini menggunakan 48 ticks per quarter, atau 48 * 4 = 192 ticks per cycle, satu byte dapat digunakan untuk menyandikan interval 256/192 = 1 durasi. (3) (satu keseluruhan dan satu ketiga) siklus, yang cukup.
Dalam format asli di mana file midi dikonversi, saya juga menerapkan header kecil, berukuran 16 byte. 14 byte pertama berisi nama melodi. Secara alami, nama tidak boleh melebihi 14 karakter. Kemudian muncul ruang nol. Byte terakhir berikutnya mencerminkan tempo melodi dalam tampilan yang nyaman untuk MK. Nilai ini dihitung pada tahap konversi dan berfungsi untuk menginisialisasi timer MK, yang bertanggung jawab atas langkah tersebut. Cara menghitungnya dibahas dalam beberapa paragraf di atas.Mulai dari byte ke-17, isi melodi mengikuti. Setiap byte ganjil sesuai dengan interval waktu, dan setiap byte genap berhubungan dengan suatu peristiwa (catatan). Byte pertama akan menjadi nol jika melodi dimulai dengan catatan, dari awal file midi, tanpa jeda pendahuluan. Tanda akhir melodi adalah label dua byte 0xFF. Tugas ini melibatkan reproduksi siklus melodi oleh mikrokontroler. Agar melodi dalam loop terdengar harmonis dari sudut pandang irama, itu harus dilingkarkan dengan benar. Untuk melakukan ini, jika perlu, setelah not terakhir Anda perlu menjeda panjang tertentu, biasanya sampai ukuran terakhir diisi. Dan untuk ini, Anda perlu mengalihkan acara yang sesuai. Saya menggunakan byte 0x0F, yang tidak digunakan dalam catatan pengodean. Ini sesuai dengan mematikan nada ke-16 pada oktaf pertama, yang tidak masuk akal,karena hanya ada 12 catatan dalam satu oktaf. Saya sebutkan di atas tentang byte idle. Dengan demikian, byte ini mengkodekan "note diam", bit tinggi yang juga dapat berfungsi sebagai tanda on atau off, meskipun redundansi informasi dalam kasus ini. Untuk mengatur catatan ini di editor midi, saya mengambil catatan pertama atau kedua (salah satunya). Biarkan saya mengingatkan Anda bahwa 36 catatan pertama tidak digunakan dalam model. Dengan demikian, not pertama (atau kedua) digunakan seperlunya untuk penyelesaian melodi yang benar, agar ritme tidak rusak saat memainkannya dalam satu lingkaran.Untuk mengatur catatan ini di editor midi, saya mengambil catatan pertama atau kedua (salah satunya). Biarkan saya mengingatkan Anda bahwa 36 catatan pertama tidak digunakan dalam model. Dengan demikian, not pertama (atau kedua) digunakan seperlunya untuk penyelesaian melodi yang benar, agar ritme tidak rusak saat memainkannya dalam satu lingkaran.Untuk mengatur catatan ini di editor midi, saya mengambil catatan pertama atau kedua (salah satunya). Biarkan saya mengingatkan Anda bahwa 36 catatan pertama tidak digunakan dalam model. Dengan demikian, not pertama (atau kedua) digunakan seperlunya untuk penyelesaian melodi yang benar, agar ritme tidak rusak saat memainkannya dalam satu lingkaran.Melanjutkan bekerja di editor "Cakewalk Pro Audio 9", kami akan membuat melodi yang sewenang-wenang. Angka-angka di bawah ini menunjukkan catatan melodi yang saya tulis ulang dari salah satu gambar di Internet. Gambar catatan disajikan dalam dua gaya: gaya "Piano roll" dan gaya klasik. Yang pertama sangat nyaman untuk menulis dan mengedit melodi menggunakan mouse komputer. Itulah yang saya gunakan.
Seperti yang dapat Anda lihat dari gambar, pada akhirnya nada terendah (pertama) diterapkan untuk tanda diam pada interval waktu yang tepat untuk mengatur pola siklus yang benar. Dan di awal melodi, mengingat ada sentuhan, ada indentasi seperempat sebelum not pertama.Editor menyediakan mode untuk menampilkan acara dalam bentuk tabel.
Seperti yang dapat Anda lihat dari gambar, tidak ada yang berlebihan dalam daftar acara, kecuali untuk mencatat, seperti yang kadang-kadang terjadi dengan manipulasi yang tidak perlu pada proyek musik. Namun, jika peristiwa yang tidak perlu yang tidak terkait dengan catatan karena beberapa alasan termasuk dalam daftar, mereka dapat dihapus dengan menekan tombol Del. Meskipun, pada tahap konversi, semua peristiwa yang tidak perlu diabaikan, dan waktu delta "terakumulasi". Omong-omong, saya menambahkan fungsi ini ke program pada tahap debugging. Seperti yang Anda tebak, tabel tersebut mencerminkan waktu dan durasi setiap catatan beserta properti lain yang tidak kita butuhkan. Yaitu, dengan satu baris dalam tabel dua acara midi diekspresikan sekaligus: menyalakan dan mematikan catatan.Simpan melodi dalam format "midi 1", seperti yang ditunjukkan pada gambar.
Buka file yang disimpan di editor HEX. Harus segera dicatat bahwa, tidak seperti file avi yang sama (seperti yang saya tulis sebelumnya), byte nilai numerik dalam file midi disajikan tidak dalam urutan terbalik, tetapi menurut senioritas (big endian).
Dalam gambar, saya hanya menandai bytes yang diinginkan. Pertama, bingkai merah tebal menguraikan tiga kelompok dua byte di masing-masing. Ini, masing-masing, adalah jenis format MIDI (1), jumlah saluran (2) dan jumlah kutu per kuartal (48). Nilai-nilai inilah yang harus dimiliki oleh ketiga konstanta ini untuk pekerjaan lebih lanjut dari program transformasi. Busur ungu menandai awal dari masing-masing dari dua saluran. Di saluran pertama, 6 byte ditandai dengan bingkai abu-abu, di dalamnya tiga byte disorot dengan bingkai biru. 6 byte ini merujuk pada peristiwa meta (marker 0xFF) dengan kode 0x51 dan panjang konten 0x03 byte. Tiga byte lebih lanjut - isi acara. Acara ini mengatur tempo melodi dengan hanya tiga byte ini dalam bingkai biru. Byte rendah terakhir dapat dibuang dengan aman, karena akurasi super tidak penting. Saya tidak akan memberikan deskripsi terperinci dan menyeluruh dari semua byte dalam file.Di trek kedua - di trek dengan catatan - nilai interval waktu dilingkari dalam bingkai biru. By the way, dalam contoh khusus ini, tidak melebihi satu byte, kecuali untuk satu-satunya kasus dengan catatan kedua dari belakang. Ini adalah not kedua dari melodi (menghitung nada semu tambahan dari akhir) yang bertahan tiga perempat dari ukuran, yaitu 48 * 3 = 144 kutu dan melebihi 128. Dan untuk itu Anda harus menggunakan dua byte, sesuai dengan format panjang variabel. Dan untuk mewakili interval waktu dalam format yang dikonversi, nilai 144 mudah dikodekan dengan satu byte. Saya mengitari kasing khusus ini dalam bingkai biru ganda. Catatan dilingkari dalam bingkai hijau, atau lebih tepatnya, kodenya. Volume setiap nada dilingkari dalam bingkai abu-abu. Seperti yang telah disebutkan, volume nol adalah tanda bisu (pelepasan) not, dan di seluruh komposisi ada satu peristiwa:menyalakan catatan. Kode untuk acara ini, 0x90, ditandai dengan warna kuning. Saya tidak menguraikan semua nada sampai akhir melodi. Satu-satunya pengecualian adalah bingkai biru ganda untuk interval waktu tunggal yang melebihi ambang 128 kutu.Sekali lagi, seperti yang disebutkan di atas, program untuk mengonversi file midi ke dalam formatnya sendiri untuk MK benar-benar berfungsi dengan sekelompok beberapa file midi, dan pada outputnya membuat file gambar untuk EEPROM. Pertimbangkan fragmen dari file ini yang berkaitan dengan konten melodi yang dikonversi dari contoh di atas. Saya membukanya di editor HEX lain untuk menunjukkan gambar berdasarkan sektor dan memperhatikannya. Setiap melodi baru dimulai dengan sektor baru.
Byte terakhir dari baris pertama (16 byte pertama), dilingkari dalam bingkai merah, mengatur tempo melodi. Menurut perhitungan, nilai 0xC1 (193) jatuh pada tempo 154, 155 dan 156. Hanya dalam proyek saya mengatur tempo melodi ke 155 bpm, yang terlihat di salah satu screenshot sebelumnya. Byte pertama (sampai tanggal 14) dilingkari dalam bingkai biru menentukan nama komposisi. Dalam contoh ini, "Klasik." Untuk MK, informasi ini tidak perlu, hanya diperlukan untuk orientasi di editor HEX. Meskipun, jika Anda membuat proyek yang lebih kompleks pada MK menggunakan tampilan, Anda dapat menggunakan informasi ini dengan menampilkan nama melodi yang dimainkan.Baris kedua (dari byte ke-17) memulai isi melodi. Seperti halnya file midi asli, saya tidak mengecat semua catatan, tetapi hanya melukis sebagian. Bytes aneh yang disorot dengan warna biru adalah interval waktu. Bahkan byte yang ditandai dengan bingkai hijau adalah catatan bersama dengan tanda on / off mereka. Sebagai contoh, dua byte hijau pertama, 0xB4 dan 0x34, merujuk pada catatan yang sama dengan kode 0x34, dan byte berbeda hanya dalam satu bit orde tinggi. Dalam byte 0xB4 (0b10110100), bit tinggi adalah satu, yang merupakan tanda menyalakan catatan, dan dalam byte 0x34 (0b00110100), bit tinggi adalah nol, yang merupakan tanda mematikan catatan. Byte 0x34 mengodekan catatan dengan parameter berikut: kode oktaf 0b011, dan kode catatan dalam satu oktaf - 0b0100. Atau, dalam bentuk desimal, masing-masing 3 dan 4. Jika Anda menghitung bukan dari nol,ternyata not pertama dalam melodi itu milik oktaf keempat dan not kelima. Penomoran oktaf di sini dipilih secara sewenang-wenang tanpa memperhitungkan penomoran standar akun. Catatan yang disepakati, menurut tabel tambahan kalkulasi saya Excel, adalah catatan dengan kode 76 (0x4C) untuk format midi, yaitu catatan E6 (catatan "e" dari oktaf pertengahan ke-6). Begitulah: komposisi dimulai dengan catatan ini.Harus dicatat kasus khusus dalam urutan musik, ketika catatan yang sama diulang tanpa jeda. Dalam contoh kami, semua catatan yang berdekatan yang bebas jeda berbeda. Tetapi ada melodi di mana not itu berulang tanpa jeda. Yaitu, interval waktu antara mematikan satu dan menyalakan nada yang sama persis berikutnya adalah nol. Mengingat kekhasan sintesis musik yang kompleks, urutan seperti itu akan terdengar asing di synthesizer mana pun. Tetapi dalam kasus MK, akan terdengar sangat kohesif sehingga akan sulit untuk mendengar perbedaan antara dua not yang identik. Dalam praktiknya, tentu saja, tidak akan ada merger yang jelas karena perhitungan antara yang terjadi di MC, tetapi tetap saja, interval waktu ini kemungkinan besar akan jauh lebih sedikit daripada durasi bahkan satu centang. Untuk kasus khusus seperti itu, program sedang dalam fase konversi,menabrak kombinasi seperti itu, memperkenalkan jeda antara catatan dengan panjang 1 centang dan mengurangi durasi catatan di sebelah kiri catatan dengan interval waktu yang sama. “Gap” minimum 1 tick sudah cukup, seperti yang telah ditunjukkan oleh latihan.Dalam bingkai biru ganda, saya melingkari nilai interval waktu (0x90), yang melebihi 128, dan untuk itu saya harus menghabiskan dua byte dalam file midi, sesuai dengan format panjang variabel. Lingkaran hijau dilingkari byte dan mematikan catatan semu yang sama untuk menyelaraskan komposisi. Program MK, setelah melihat byte-byte ini, akan menafsirkannya sebagai menghidupkan keheningan. Akhirnya, dua byte 0xFF dikelilingi dalam bingkai biru tebal menandai akhir melodi. Nilai semua byte berikut dalam sektor memori saat ini dapat berupa apa saja, diabaikan.Pertimbangkan sektor pertama dari file gambar EEPROM keluaran. Seperti yang sudah saya tulis, ini berfungsi sebagai daftar alamat sektor-sektor awal melodi. Program ini berhasil memindai 8 lagu tanpa kesalahan (pada saat penulisan, saya telah merekam 8 lagu). Nilai jumlah melodi dicatat dalam byte 512 terakhir dari sektor ini. Dan sejak awal sektor ini, alamat ditulis. Untuk melodi pertama, alamatnya 0x01, yang sesuai dengan sektor kedua (yang pertama, jika Anda hitung dari awal). Melodi ketiga dan keempat (dua dari delapan) ternyata panjang dan tidak cocok dalam satu sektor. Oleh karena itu, celah diamati dalam urutan alamat. Jika Anda menghitung, 64kB memori, Anda dapat merekam tidak lebih dari 127 lagu, sehingga satu sektor untuk mengatasi cukup.
Semua perkiraan awal dan perhitungan tercermin dalam artikel, saya lakukan di Excel. Tangkapan layar di bawah ini menunjukkan tangkapan layar dari tabel yang dihasilkan (dalam mode dua jendela).
Siapa yang peduli, di bawah ini di bawah spoiler adalah teks dari program C yang mengkonversi file midi ke file untuk mikrokontroler. Dari teks, saya menghapus baris tambahan yang digunakan untuk debugging. Program, sejauh ini, berfungsi, tidak berpura-pura dapat dibaca dan melek huruf secara tertulis.File utama 1.cpp#include <stdio.h> #include <windows.h> #include <string.h> #define SPACE 1 HANDLE openInputFile(const char * filename) { return CreateFile ( filename, // Open Two.txt. GENERIC_READ, // Open for writing 0, // Do not share NULL, // No security OPEN_ALWAYS, // Open or create FILE_ATTRIBUTE_NORMAL, // Normal file NULL); // No template file } HANDLE openOutputFile(const char * filename) { return CreateFile ( filename, // Open Two.txt. GENERIC_WRITE, // Open for writing 0, // Do not share NULL, // No security OPEN_ALWAYS, // Open or create FILE_ATTRIBUTE_NORMAL, // Normal file NULL); // No template file } void filepos(HANDLE f, unsigned int p){ LONG LPos; LPos = p; SetFilePointer (f, LPos, NULL, FILE_BEGIN); //FILE_CURRENT //https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointer } DWORD wr; DWORD ww; unsigned long int read32(HANDLE f){ unsigned char b3,b2,b1,b0; ReadFile(f, &b3, 1, &wr, NULL); ReadFile(f, &b2, 1, &wr, NULL); ReadFile(f, &b1, 1, &wr, NULL); ReadFile(f, &b0, 1, &wr, NULL); return b3<<24|b2<<16|b1<<8|b0; } unsigned long int read24(HANDLE f){ unsigned char b2,b1,b0; ReadFile(f, &b2, 1, &wr, NULL); ReadFile(f, &b1, 1, &wr, NULL); ReadFile(f, &b0, 1, &wr, NULL); return b2<<16|b1<<8|b0; } unsigned int read16(HANDLE f){ unsigned char b1,b0; ReadFile(f, &b1, 1, &wr, NULL); ReadFile(f, &b0, 1, &wr, NULL); return b1<<8|b0; } unsigned char read8(HANDLE f){ unsigned char b0; ReadFile(f, &b0, 1, &wr, NULL); return b0; } void message(unsigned char e){ printf("Error %d: ",e); switch(e){ case 1: // - -; printf("In track0 event is not FF\n"); break; case 2: // - 127 printf("Len of FF >127\n"); break; case 3: // ; printf("Midi is incorrect\n"); break; case 4: // ; printf("Delta>255\n"); break; case 5: // RPN NRPN; printf("RPN or NRPN is detected\n"); break; case 6: // ; printf("Note in 1...35 range\n"); break; case 7: // ; printf("Long of name of midi file >18\n"); break; } system("PAUSE"); } int main(){ HANDLE in; HANDLE out; unsigned int i,j; unsigned int inpos; unsigned int outpos=0; unsigned char byte; // ; unsigned char byte1; // 1 ; unsigned char byte2; // 2 ; unsigned char status; //- ( ); unsigned char sz0; // -; unsigned long int bsz0; // -; unsigned short int format, ntrks, ppqn; // ; unsigned long int bsz1; // ; unsigned long int bpm; // ( . ); unsigned long int time=0; // ( ); unsigned char scale; // , ; unsigned char oct; // ; unsigned char nt; // ; unsigned char outnote; // ; unsigned char prnote=0; // ; unsigned char tdt; // () ; unsigned int dt; // ( ); unsigned int outdelta=0; // ( ); unsigned char prdelta=0; // ; char fullname[30]; // ; char name[16]; // ; WIN32_FIND_DATA fld; // mid; HANDLE hf; unsigned short int csz; // ; unsigned char nfile=0; // ; unsigned char adr[128]; // ; out=openOutputFile("IMAGE.out"); outpos=512; // ; filepos(out,outpos); hf=FindFirstFile(".\\midi\\*.mid",&fld); do{ printf("\n***** %s *****\n",fld.cFileName); if(strlen(fld.cFileName)>18){ // ; message(7); } sprintf(name,"%s",fld.cFileName); name[strlen(fld.cFileName)-4]=0; // ; sprintf(fullname,".\\midi\\%s",fld.cFileName); // ; WriteFile(out, name, strlen(name), &ww, NULL); // ; in=openInputFile(fullname); // ; #include "process.cpp" // ; outpos+=((csz/512)+1)*512; // ; adr[nfile]=(outpos/512)-((csz/512)+1); // () ; filepos(out,outpos); CloseHandle(in); nfile+=1; }while(FindNextFile(hf,&fld)); // , ; FindClose(hf); WriteFile(out, &outnote, 1, &ww, NULL); outpos=0; // ; filepos(out,outpos); WriteFile(out, adr, nfile, &ww, NULL); outpos=511; // ; filepos(out,outpos); WriteFile(out, &nfile, 1, &ww, NULL); CloseHandle(out); system("PAUSE"); return 0; }
Lampiran file process.cpp Bagian dasar dari program untuk MK, sebenarnya, sangat sederhana. Pertimbangkan salah satu opsi untuk penerapannya, lebih tepatnya, bagian utamanya.
Timer 1, digunakan untuk menghasilkan bunyi not, dikonfigurasikan sebagai berikut. Untuk mengaktifkan dan menonaktifkan catatan, masing-masing pengganti berikut digunakan.
#define ENT1 TCCR1B=0x09;TCCR1A=0x40 #define DIST1 TCCR1B=0x00;TCCR1A=0x00;PORTB.1=0
Sebelum memulai timer, Anda perlu menetapkan register OCR1A nilai 16-bit yang sesuai dengan frekuensi yang sedang diputar. Ini akan ditampilkan nanti. Ketika timer dihidupkan, register TCCR1B ditugaskan Mode Pembentukan Bentuk Gelombang dengan pembagi timer 1, dan register TCCR1A diatur ke Toggle OC1A pada Bandingkan Pertandingan. Dalam hal ini, sinyal dihapus dari output yang ditunjuk khusus dari MK "OC1A". Dalam ATmega8 dalam paket SMD, ini adalah pin 13, yang sama dengan PORTB.1. Ketika timer dimatikan, kedua register diatur ulang, dan output dari PORTB.1 dipaksa ke nol. Ini diperlukan untuk mencegah, selama diam, output dari tegangan konstan, yang tidak diinginkan untuk input VLF. Meskipun, Anda dapat menempatkan kapasitor di sirkuit, tetapi Anda juga dapat menonaktifkan output secara terprogram. Tegangan konstan dapat terjadi pada output ini jika not dimatikan pada saat fase yang sesuai dari sinyal, dan ini dalam 50% kasus.
Buat array nilai timer untuk 12 catatan oktaf pertama. Nilai-nilai ini dihitung sebelumnya.
freq[]={61156,57724,54484,51426,48540,45815,43244,40817,38526,36364,34323,32396};
Catatan oktaf lain, seperti yang saya katakan, akan diperoleh dengan membagi dengan derajat dua.
Konfigurasi timer 0 bahkan lebih sederhana. Ini bekerja terus-menerus, dengan interupsi melimpah, setiap kali diinisialisasi lagi dengan nilai yang sesuai dengan tempo melodi. Pembagi waktu adalah 5: TCCR0 = 0x05. Berdasarkan timer ini, timer virtual dibuat yang menghitung tics (kali) dalam melodi. Pemrosesan respon dari timer ini ditempatkan dalam siklus program utama.
Fungsi interupsi timer 0 adalah sebagai berikut.
interrupt [TIM0_OVF] void timer0_ovf_isr(void){ if(ent01){ vt01+=1; } TCNT0=top0; }
Di sini variabel ent01 bertanggung jawab untuk mengaktifkan timer virtual. Dengan variabel ini, dapat dinyalakan atau dimatikan jika perlu. Variabel vt01 adalah variabel primer yang dapat dihitung dari timer virtual. Baris TCNT0 = top0 menunjukkan inisialisasi timer 0 ke nilai yang diinginkan top0, yang dibaca dari judul melodi sebelum memutarnya.
Jumlah melodi yang akan dimainkan sesuai dengan variabel alm. Ini juga berfungsi sebagai bendera awal reproduksi. Dia perlu menetapkan nomor melodi di salah satu cara, tergantung pada tugasnya. Setelah itu, blok berikutnya dari siklus utama akan menjadi aktif.
if(alm){
Pengalihan lebih lanjut dari catatan ke catatan dilakukan di unit pemrosesan penghitung waktu virtual, yang juga ditempatkan di loop utama.
if(vt01>=top01){
Dari komentar dalam teks program, semuanya harus cukup jelas dan dapat dimengerti.
Untuk menghentikan melodi, gunakan penyisipan utama loop berikut.
if(stop){
Ada komentar kecil tentang implementasi pemutaran melodi. Sebelum setiap not baru mulai berbunyi, mikrokontroler menghabiskan sedikit waktu untuk mengubah byte read dari note menjadi nilai timer. Kali ini, sebagaimana ternyata dalam praktiknya, relatif kecil, dan tidak memengaruhi kualitas pemutaran. Tetapi saya ragu bahwa operasi ini akan tetap tidak terlihat. Dalam hal ini, jeda tambahan akan muncul sebelum setiap nada, dan irama melodi akan dipecahkan. Tapi masalah ini juga bisa dipecahkan. Cukup untuk menghitung nilai timer dari note berikutnya terlebih dahulu sementara note saat ini berbunyi. Prosedur ini harus dilakukan secara terpisah dari pemrosesan penghitung waktu virtual dalam loop program utama menggunakan bendera yang ditunjuk khusus. Karena kenyataan bahwa waktu perhitungan tidak mungkin melebihi waktu bermain bahkan not terpendek, solusi seperti itu tepat.
Sekarang mari kita beralih ke pengujian program.
Selain potongan kode di atas, saya menambahkan fungsi pemrosesan tombol ke program MK, yang dengannya saya mengontrol penyertaan atau penonaktifan melodi tertentu. EEPROM terhubung ke MK melalui bus I2C, bekerja dengan yang diimplementasikan pada tingkat perangkat lunak. Proyek ini dilakukan dengan bantuan "CodeVisionAVR" bersama dengan "CodeWizardAVR". Saya mengeluarkan MK dari pin 13 ke kartu suara PC melalui pembagi dan merekam suara melodi dalam editor suara. Saya mem-flash memori EEPROM dengan bantuan firmware, yang saya tulis di salah satu artikel sebelumnya. Karena kenyataan bahwa tidak semua byte dari file gambar berguna, firmware memori dapat diimplementasikan hanya dengan byte yang berguna (hingga penanda akhir melodi) untuk menghemat waktu perekaman dan sumber daya chip. Untuk melakukan ini, Anda dapat membuat program terpisah, atau menulis byte ke chip secara langsung selama konversi, menambah program utama.
Di antara delapan melodi, ada tiga yang menguji, dengan bantuan yang saya akan mengevaluasi rentang frekuensi dengan telinga, suara menggabungkan catatan identik, suara catatan terpendek, transisi cepat, dll. Biarkan saya mengingatkan Anda bahwa menggabungkan not yang sama benar-benar terdengar dengan jeda satu tick, dan not pertama dalam merger bertahan satu tick lebih sedikit.
Salah satu nada uji adalah urutan not dari pertama hingga terakhir dengan durasi satu not dalam seperempat dan tempo melodi 40 bpm.

Dalam skenario ini, satu nada terdengar sedikit lebih dari satu detik, dan karenanya Anda dapat mendengarkan secara detail bagaimana seluruh rentang nada terdengar. Pada spektrum frekuensi dalam editor audio "Adobe Audition" komponen frekuensi utama dan harmonik atasnya diamati karena bentuk gelombang gigi gergaji yang sesuai. Dan hubungan logaritmik antara nomor catatan dan frekuensi sangat mencolok.

Menganalisis interval waktu, terlihat jelas bahwa jeda nyata antara not berurutan rata-rata sekitar 145 sampel (pada frekuensi sampling dari rekaman audio 44100 Hz), yaitu sekitar 3 ms. Ini adalah waktu di mana MK melakukan perhitungan yang diperlukan. Sisipan ini hadir secara teratur sebelum setiap catatan. Saya secara khusus menulis artinya dalam sampel, karena informasi ini lebih asli dan lebih akurat, meskipun ini tidak terlalu penting.

Dan panjang satu centang pada kecepatan rata-rata melodi 120 bpm adalah sekitar 10 ms. Oleh karena itu, pada prinsipnya, adalah mungkin untuk tidak memperkenalkan koreksi yang sama dalam 1 tick, ketika dua not yang identik berjalan satu demi satu tanpa jeda. Saya pikir penyisipan reguler 3 ms di antara catatan akan cukup. Saat mendengarkan melodi, sisipan biasa ini tidak terlihat sama sekali, dan melodi terdengar merata. Oleh karena itu, tidak ada kebutuhan khusus untuk menghitung nilai timer untuk not berikutnya ketika not saat ini diputar.
Melodi tes lain dengan tempo 200 bpm berisi berturut-turut 1/32 not yang sama dari kisaran menengah tanpa jeda. Dalam hal ini, setelah pemrosesan, saat bermain di antara mereka, ada jeda 1 tick, yang pada tempo cepat ini sebanyak 310 sampel (sekitar 6 ms) dari sinyal yang direkam.

Omong-omong, panjang jeda ini sebanding dengan periode sinyal, yang menunjukkan tempo melodi yang tinggi. Dan suaranya mengingatkan saya pada sebuah getaran.
Pada prinsipnya, ini bisa selesai. Saya puas dengan hasil perangkat, itu melebihi semua harapan. Sebagian besar waktu saya curahkan untuk mempelajari format midi dan debugging program untuk konversi. Salah satu artikel berikut saya juga akan membahas topik yang berkaitan dengan MIDI, yang akan berbicara tentang penerapan format ini di aplikasi menarik lainnya.