Visualizer Spektrum Audio FHT – Membuat Tampilan LED Audio Menakjubkan
Komponen dan Perlengkapan
 | | × | 1 | |
| | MATRIKS LED RGB WS2812B 8X32 |
| × | 1 | |
 | | × | 3 | |
 | | × | 2 | |
 | | × | 2 | |
 | | Resistor Lubang Melalui, 390 ohm |
| × | 1 | |
 | | 1N4007 – Diod Tegangan Tinggi, Arus Tinggi |
| × | 1 | |
 | | × | 2 | |
 | | × | 1 | |
 | | Tombol Tekan SparkFun 12mm |
| × | 2 | |
| | Papan prototipe generik 4x6 cm |
| × | 1 | |
| | Stop Kontak Stereo RCA Papan PCB |
| × | 1 | |
Alat dan Mesin yang Diperlukan
 | | |
| | Kabel Jack Audio 3,5 mm ke Konektor Stereo RCA |
| |
Tentang Proyek Ini
Pustaka Arduino FHT
FHT melakukan pekerjaan yang sama seperti FFT yang lebih dikenal, tetapi tidak seperti itu, FHT hanya menggunakan data nyata, sementara FFT bekerja pada data kompleks, oleh karena itu FHT menggunakan setengah daya pemrosesan dan setengah memori sistem.
Pustaka ini menggunakan satu array untuk pemantauan data alih-alih dua, satu untuk nilai nyata dan satu untuk nilai imajiner, yang digunakan oleh pustaka FFT, ini juga menyederhanakan perhitungan matematika kompleks dengan menghindari loop dengan perhitungan logaritma titik melayang kompleks dan alih-alih menggunakan beberapa tabel numerik.
Keburukannya adalah selalu mengembalikan setengah nilai dari ukuran array yang digunakan untuk melakukan pemantauan, ini menyebabkan kehilangan resolusi dan presisi tertentu di kedua ujung rentang audio. Namun, dalam kasus spesifik ini, kami tidak perlu mengembangkan instrumen pengukur tetapi hanya sebuah gadget sederhana, kami tidak membutuhkan presisi ekstrim, kami hanya perlu menyalakan sejumlah LED dalam irama musik.
Tentu saja ini bukan tempat yang tepat untuk membahas modalitas operasi pustaka FHT, siapa pun yang ingin mempelajari lebih lanjut dapat merujuk ke situs web Open Music Lab di mana mereka akan menemukan semua dokumentasi yang diperlukan untuk memahami trik yang digunakan, penjelasan rinci tentang berbagai fungsi dan beberapa contoh generik yang telah ditulis untuk Arduino.
Pustaka FFT sangat cepat, begitu cepat sehingga diperlukan untuk menyisipkan penundaan dalam kode untuk mengurangi berkedip. Anda akan segera melihat hasil akhir, tampilan jauh lebih cepat dan responsif daripada versi yang menggunakan pustaka FFT yang sering terlihat tertinggal di belakang musik. Penggunaan sumber daya sistem yang lebih sedikit memungkinkan kami untuk menambahkan beberapa baris kode untuk mengelola fungsi tambahan.
Versi Baru
Proyek ini identik dengan versi sebelumnya, jadi mereka yang telah membuat versi sebelumnya hanya perlu menambahkan tombol dan resistor 10K serta memperbarui kode.
Selain penerapan pustaka FHT, versi ini memiliki fungsi tambahan berikut:
- memiliki tombol tambahan untuk penyesuaian kecerahan;
- memungkinkan menyimpan pengaturan warna dan kecerahan di EEPROM agar dapat dimuat ulang saat dimatikan;
- menampilkan level pengaturan warna dan kecerahan saat tombol ditekan dan saat dinyalakan (dapat dinonaktifkan).
Kode
Versi Kode FHTArduino
/* Hak Cipta (c) 2020 Janux Izin diberikan secara cuma-cuma, tanpa biaya, kepada setiap orang yang mendapatkan salinan perangkat lunak ini dan berkas dokumentasi terkait ("Perangkat Lunak"), untuk berurusan dalam Perangkat Lunak tanpa keterbatasan, termasuk tanpa keterbatasan hak untuk menggunakan, menyalin, memodifikasi, menggabungkan, menerbitkan, mendistribusikan, mensublisensikan, dan/atau menjual salinan Perangkat Lunak, dan untuk mengizinkan orang yang diberikan Perangkat Lunak untuk melakukan hal tersebut, tunduk pada kondisi berikut: Pemberitahuan hak cipta di atas dan pemberitahuan izin ini harus disertakan dalam semua salinan atau bagian besar dari Perangkat Lunak. PERANGKAT LUNAK DISEDIAKAN "SEBAGAIMANA ADANYA", TANPA JAMINAN APAPUN, BAIK TERSURAT MAUPUN TERSURAT, TERMASUK TETAPI TIDAK TERBATAS PADA JAMINAN KOMERSIBILITAS, KECUKOPAN UNTUK TUJUAN TERTENTU DAN NONPELANGGARAN. DALAM HAL APAPUN PENULIS ATAU PEMEGANG HAK CIPTA TIDAK BERTANGGUNG JAWAB TERHADAP KLAIM, KERUGIAN ATAU TANGGUNG JAWAB LAIN, BAHWA DALAM TINDAKAN KESEPAKATAN, TORT ATAU SELAINNYA, YANG TIMBUL DARI, DI LUAR ATAU DALAM KONEKSI DENGAN PERANGKAT LUNAK ATAU PENGGUNAAN ATAU URUSAN LAIN DALAM PERANGKAT LUNAK. Berdasarkan proyek asli untuk matriks LED MAX72xx dan pustaka FFT yang dibuat oleh Shajeeb. Bagian pengaturan konfigurasi berdasarkan karya Ragnar Ranøyen Homb dari Kreasi Norwegia. */ #define LIN_OUT 1 //Magnitud output linear FHT #define FHT_N 128 //set SAMPLES untuk FHT, Harus merupakan pangkat 2 #include <FHT.h> #define xres 32 //Jumlah total kolom dalam tampilan, harus <= SAMPLES/2 #define yres 8 //Jumlah total baris dalam tampilan #define ledPIN 6 //pin keluaran untuk mengontrol LED #define NUM_LEDS (xres * yres) //total LED dalam Matriks #include <Adafruit_NeoPixel.h> #define colorPIN 5 //pin untuk mengubah warna LED #define brightnessPIN 10 //pin untuk mengubah kecerahan byte displaycolor = 0; //nilai warna default byte brightness = 1; //level kecerahan default #include <EEPROM.h> #define CONFIG_START 32 //Lokasi awal memori #define CONFIG_VERSION "VER01" //Versi konfigurasi typedef struct { char version[6]; byte displaycolor; byte brightness; } configuration_type; configuration_type CONFIGURATION = { CONFIG_VERSION, displaycolor, brightness }; byte yvalue; int peaks[xres]; byte state = HIGH; // pembacaan saat ini dari pin masukan byte previousState = LOW; // pembacaan sebelumnya dari pin masukan unsigned long lastDebounceTime = 0; // waktu terakhir pin keluaran diubah unsigned long debounceDelay = 100; // waktu debounce; tingkatkan jika output berkedip byte data_avgs[xres]; //Array untuk pemantauan // Parameter 1 = jumlah LED dalam matriks // Parameter 2 = nomor pin // Parameter 3 = flag tipe piksel, gabungkan sesuai kebutuhan: // NEO_KHZ800 800 KHz bitstream (produk NeoPixel sebagian besar dengan LED WS2812) // NEO_KHZ400 400 KHz (piksel FLORA 'v1' (bukan v2) klasik, driver WS2811) // NEO_GRB Piksel dirakit untuk bitstream GRB (produk NeoPixel sebagian besar) // NEO_RGB Piksel dirakit untuk bitstream RGB (piksel FLORA v1, bukan v2) Adafruit_NeoPixel pixel = Adafruit_NeoPixel(NUM_LEDS, ledPIN, NEO_GRB + NEO_KHZ800); // Filter EQ byte eq[32] = { 60, 65, 70, 75, 80, 85, 90, 95, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 115, 125, 140, 160, 185, 200, 200, 200 }; bool EQ_ON = true; // set ke false untuk menonaktifkan eq //Tentukan 5 set warna untuk LED, 0 untuk warna custom tunggal byte colors[][8] = { {170, 160, 150, 140, 130, 120, 1, 1}, {1, 5, 10, 15, 20, 25, 90, 90}, {90, 85, 80, 75, 70, 65, 1, 1}, {90, 90, 90, 30, 30, 30, 1, 1}, {170, 160, 150, 140, 130, 120, 110, 0} }; //Tentukan karakter untuk tampilan pengaturan byte charBitmap[] = { 0x1C, 0x10, 0x10, 0x10, 0x10, 0x1C, 0x08, 0x18, 0x08, 0x08, 0x08, 0x1C, 0x0C, 0x12, 0x04, 0x08, 0x10, 0x1E, 0x0C, 0x12, 0x02, 0x06, 0x12, 0x0C, 0x10, 0x10, 0x10, 0x14, 0x1E, 0x04, 0x1E, 0x10, 0x1E, 0x02, 0x12, 0x0C, 0x1E, 0x10, 0x10, 0x1E, 0x12, 0x1E, 0x1E, 0x02, 0x04, 0x08, 0x08, 0x08, 0x0C, 0x12, 0x0C, 0x12, 0x12, 0x0C, 0x1C, 0x12, 0x1C, 0x12, 0x12, 0x1C }; void setup() { pixel.begin(); //inisialisasi Matriks LED //Mulai operasi FFT ADCSRA = 0b11100101; // set ADC ke mode free running dan set pre-scaler ke 32 (0xe5) ADMUX = 0b00000000; // gunakan pin A0 dan referensi tegangan eksternal // Baca data konfigurasi dari EEPROM if (loadConfig()) { displaycolor = CONFIGURATION.displaycolor; brightness = CONFIGURATION.brightness; } //Set kecerahan yang dimuat dari EEPROM pixel.setBrightness(brightness * 24 + 8); //Tampilkan konfigurasi saat ini saat mulai //ubah true ke false jika Anda tidak ingin ini showSettings(3, true); } void loop() { while (1) { // mengurangi getaran Sampling(); // Pustaka FHT hanya menggunakan satu array data RearrangeFHT(); // susun ulang hasil FHT untuk cocok dengan jumlah kolom tampilan SendToDisplay(); // kirim ke tampilan sesuai nilai yang diukur colorChange(); // periksa apakah tombol ditekan untuk mengubah warna brightnessChange(); // periksa apakah tombol ditekan untuk mengubah kecerahan delay(10); // penundaan untuk mengurangi berkedip (FHT terlalu cepat :D) } } void Sampling() { for (int i = 0; i < FHT_N; i++) { while (!(ADCSRA & 0x10)); // tunggu hingga ADC menyelesaikan konversi saat ini yaitu bit ADIF diatur ADCSRA = 0b11110101 ; // hapus bit ADIF sehingga ADC dapat melakukan operasi berikutnya (0xf5) //bit ADLAR adalah 0, jadi 10 bit dari Register Data ADC rata kanan byte m = ADCL; // ambil data ADC byte j = ADCH; int value = (j << 8) | m; // bentuk menjadi nilai int value -= 0x0200; // bentuk menjadi int bertanda value <<= 6; // bentuk menjadi int bertanda 16b fht_input[i] = value / 8; // salin ke array input FHT setelah dikompresi } // ++ mulai proses data FHT -+-+--+-+--+-+--+-+--+-+--+-+--+-+- fht_window(); // window data untuk respons frekuensi yang lebih baik fht_reorder(); // susun ulang data sebelum melakukan fht fht_run(); // proses data dalam fht fht_mag_lin(); // ambil output dari fht } void RearrangeFHT() { // FHT mengembalikan nilai nyata hanya menggunakan satu array // setelah pemanggilan fht_mag_lin() nilai sampel berada di // posisi pertama FHT_N/2 dari array fht_lin_out[] int step = (FHT_N / 2) / xres; int c = 0; for (int i = 0; i < (FHT_N / 2); i += step) { data_avgs[c] = 0; for (int k = 0 ; k < step ; k++) { data_avgs[c] = data_avgs[c] + fht_lin_out[i + k]; // magnitudo output linear } data_avgs[c] = data_avgs[c] / step ; // simpan nilai rata-rata c++; } } void SendToDisplay() { for (int i = 0; i < xres; i++) { if (EQ_ON) data_avgs[i] = data_avgs[i] * (float)(eq[i]) / 100; // terapkan filter EQ data_avgs[i] = constrain(data_avgs[i], 0, 80); // set nilai maks & min untuk bucket ke 0-80 data_avgs[i] = map(data_avgs[i], 0, 80, 0, yres); // ubah ulang nilai rata-rata ke yres 0-8 yvalue = data_avgs[i]; peaks[i] = peaks[i] - 1; // perlahan-lahan menyala jika (yvalue > peaks[i]) peaks[i] = yvalue; // simpan puncak jika > puncak sebelumnya yvalue = peaks[i]; // ambil puncak untuk ditampilkan setColumn(i, yvalue); // gambar kolom } pixel.show(); // tampilkan kolom } // Hidupkan LED pada kolom x sesuai nilai y void setColumn(byte x, byte y) { int led, i; for (i = 0; i < yres; i++) { led = GetLedFromMatrix(x, i); // ambil LED saat ini berdasarkan koordinat x,y if (peaks[x] > i) { switch (displaycolor) { case 4: if (colors[displaycolor][i] == 0) { // tampilkan warna custom dengan nilai nol dalam array pixel.setPixelColor(led, 255, 255, 255); //putih } else { // warna standar yang ditentukan dalam array colors pixel.setPixelColor(led, Wheel(colors[displaycolor][i])); } break; case 5: //ubah warna berdasarkan kolom pixel.setPixelColor(led, Wheel(x * 16)); break; case 6: //ubah warna berdasarkan baris pixel.setPixelColor(led, Wheel(i * y * 3)); break; case 7: //ubah warna berdasarkan... negara :D //bendera Italia //if (x < 11) pixel.setPixelColor(led, 0, 255, 0); //if (x > 10 && x < 21) pixel.setPixelColor(led, 255, 255, 255); //if (x > 20) pixel.setPixelColor(led, 255, 0, 0); //bintang dan garis jika (i < yres - 2) { if (x & 0x01) { pixel.setPixelColor(led, 0, 0, 255); } else { pixel.setPixelColor(led, 255, 0, 0); } } else { pixel.setPixelColor(led, 255, 255, 255); } break; default: //tampilkan warna yang ditentukan dalam array warna pixel.setPixelColor(led, Wheel(colors[displaycolor][i])); } //AKHIR SWITCH } else { //Matikan LED pixel.setPixelColor(led, pixel.Color(0, 0, 0)); } } } //================================================================ // Hitung nomor LED berdasarkan koordinat x,y // berlaku untuk WS2812B dengan tata letak serpentis yang ditempatkan secara horizontal // dan nol LED di pojok kanan bawah (konektor DIN di sisi kanan) // nilai masukan: x= 0 hingga xres-1 , y= 0 hingga yres-1 // kembalikan nomor LED dari 0 hingga NUM_LED //================================================================ int GetLedFromMatrix(byte x, byte y) { int led; x = xres - x - 1; if (x & 0x01) { //kolom ganjil bertambah terbalik led = ((x + 1) * yres - y - 1); } else { //kolom genap bertambah normal led = ((x + 1) * yres - yres + y); } return constrain(led, 0, NUM_LEDS); } //================================================================ void colorChange() { int reading = digitalRead(colorPIN); if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) { displaycolor++; if (displaycolor > 7) displaycolor = 0; showSettings(1, true); //set ke false jika Anda tidak ingin ini saveConfig(); lastDebounceTime = millis(); } previousState = reading; } void brightnessChange() { int reading = digitalRead(brightnessPIN); if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) { brightness++; if (brightness > 7) brightness = 0; pixel.setBrightness(brightness * 24 + 8); showSettings(2, true); //set ke false jika Anda tidak ingin ini saveConfig(); lastDebounceTime = millis(); } previousState = reading; } // Utilitas dari sketsa demo Adafruit Neopixel // Masukkan nilai 0 hingga 255 untuk mendapatkan nilai warna. // Warna adalah transisi R - G - B - kembali ke R. unsigned long Wheel(byte WheelPos) { WheelPos = 255 - WheelPos; if (WheelPos < 85) { return pixel.Color(255 - WheelPos * 3, 0, WheelPos * 3); } if (WheelPos < 170) { WheelPos -= 85; return pixel.Color(0, WheelPos * 3, 255 - WheelPos * 3); } WheelPos -= 170; return pixel.Color(WheelPos * 3, 255 - WheelPos * 3, 0); } // muat apa yang ada di EEPROM ke dalam konfigurasi lokal jika pengaturan valid int loadConfig() { if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] && EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] && EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2] && EEPROM.read(CONFIG_START + 3) == CONFIG_VERSION[3] && EEPROM.read(CONFIG_START + 4) == CONFIG_VERSION[4]) { // muat (timpa) konfigurasi lokal untuk (unsigned int i = 0; i < sizeof(CONFIGURATION); i++) { *((char*)&CONFIGURATION + i) = EEPROM.read(CONFIG_START + i); } return 1; // kembalikan 1 jika konfigurasi dimuat } return 0; // kembalikan 0 jika konfigurasi TIDAK dimuat } // simpan konfigurasi ke dalam EEPROM void saveConfig() { CONFIGURATION.displaycolor = displaycolor; CONFIGURATION.brightness = brightness; for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++) EEPROM.write(CONFIG_START + i, *((char*)&CONFIGURATION + i)); } // 1 tampilkan level warna, 2 tampilkan level kecerahan, 3 keduanya void showSettings(byte num, bool show) { if (show) { pixel.clear(); if (num == 1 || num == 3) { drawChar(0, 0); drawChar(displaycolor + 1, 5); } if (num == 2 || num == 3) { drawChar(9, xres - 9); drawChar(brightness + 1, xres - 4); } delay(1000); pixel.clear(); } } // Gambar karakter khusus void drawChar(byte val, byte pos) { for (int x = 4; x >= 0; x--) { for (int y = 5; y >= 0; y--) { if ((charBitmap[val * 6 + 5 - y] >> x) & 0x01) { pixel.setPixelColor(GetLedFromMatrix(4 - x + pos, y + 1), Wheel((pos > 10) * 170)); pixel.show(); } } } } //by Janux®, Versi terakhir pada 28/06/2020. Skema Rangkaian

