Ableton tidak diperlukan: sambungkan Ableton Push 2 ke VCV Rack


Penciptaan musik, akhir-akhir ini, berjalan dengan cara yang sama seperti foto 10 tahun yang lalu: masing-masing memiliki akun DSLR dan instagram sendiri. Industri musik sangat senang akan hal ini, karena minat seperti itu menghasilkan banyak uang. Setiap hari, plugin VST baru dan perangkat analog muncul, jumlah kursus tematik berkembang pesat, blog yang didedikasikan untuk produksi musik masuk ke youtube teratas. Dengan latar belakang ini, proyek-proyek sumber terbuka terlihat sangat menarik, memungkinkan siapa saja yang ingin mencoba diri mereka sendiri sebagai produsen tanpa mengeluarkan banyak uang untuk ini. Salah satu proyek tersebut adalah VCV Rack, yang bertujuan untuk membuat kelas alat musik yang paling mahal - synthesizer modular analog - dapat diakses oleh semua orang. Baru-baru ini, ketika saya mendapat ide untuk sebuah lagu,dan di tangan hanya ada laptop linux, saya ingin membuat seluruh lagu di VCV. Dan sepanjang jalan, ada keinginan untuk mengontrol synthesizer menggunakan pengontrol midi dengan cara yang menggunakan modul yang tersedia tidak bekerja. Pada akhirnya, saya memutuskan untuk menulis sebuah plugin untuk menghubungkan Ableton Push 2 ke VCV Rack. Kita akan berbicara tentang apa yang terjadi, dan bagaimana menulis modul kita sendiri untuk VCV di artikel ini.

Referensi cepat
VCV Rack — open source , . VCV , .


Ableton Push 2 — midi- Ableton, DAW, API .



API Rak VCV


Setiap modul dalam VCV terdiri dari dua bagian - audio dan grafik. Bagian audio mewarisi dari kelas Modul metode proses , yang disebut untuk setiap sampel, yaitu, dengan frekuensi sampling. Omong-omong, frekuensi pengambilan sampel dalam VCV dapat bervariasi dari standar 44,1 kHz hingga 768 kHz, yang memungkinkan emulasi synthesizer modular yang lebih akurat dengan daya komputasi yang memadai.

Objek dari tipe ModuleWidget bertanggung jawab atas grafik , yang mewarisi metode menggambar dari struktur dasar . VCV menggunakan perpustakaan grafik vektor nanovg. Menggambar di dalam metode menggambaritu bisa terjadi baik di dalam batas-batas modul (kelebihan terputus oleh mesin), dan misalnya di framebuffer, yang masih kita gunakan.

Jadi apa yang diperlukan untuk menulis modul Anda untuk VCV?

Menyiapkan lingkungan dan menginstal Rack SDK


Langkah pertama dijelaskan dengan baik dalam dokumentasi dan tidak menyebabkan kesulitan (setidaknya di bawah linux), jadi kami tidak akan memikirkannya.

Buat templat plugin


Ada skrip helper.py untuk ini di SDK Rack. Dia perlu mengatakan createplugin, dan kemudian menentukan nama plugin dan, opsional, informasi tentang itu.

<Rack SDK folder>/helper.py createplugin MyPlugin

Ketika templat plugin dibuat, ia dapat dikompilasi dan diinstal dengan perintah

RACK_DIR=<Rack SDK folder> make install

Gambar frontend modul


Setiap plugin dapat berisi beberapa modul, dan untuk setiap modul Anda perlu menggambar panel utama. Untuk ini, dokumentasi VCV menawarkan kita untuk menggunakan Inkscape atau editor vektor lainnya. Karena modul-modul dalam VCV dipasang pada virtual Eurorack-stand, tingginya selalu 128,5 mm dan lebarnya harus kelipatan 5,08 mm.
Elemen antarmuka dasar, seperti soket CV / Gerbang, tombol, dan bola lampu dapat ditandai dalam bentuk vektor. Untuk melakukan ini, gambar lingkaran tempat mereka dari warna yang sesuai ( lebih detail di sini ), sehingga helper.py kemudian menghasilkan kode untuk markup ini. Secara pribadi, menurut saya ini bukan fitur yang sangat nyaman dan lebih mudah untuk menempatkan elemen langsung dari kode. Ketika gambar dan tata letak siap, Anda perlu menjalankan helper.py lagi untuk membuat templat modul dan mengaitkannya dengan panel depan.

helper.py createmodule MyModule res/MyModule.svg src/MyModule.cpp

Kami menghubungkan tombol dan tikungan




Terlepas dari tampilan, Ableton Push 2 dilihat oleh komputer sebagai perangkat USB-MIDI biasa, yang membuatnya mudah untuk membuat koneksi antara itu dan VCV Rack. Untuk melakukan ini, buat antrian midi input dan port midi keluaran di dalam kelas modul.

Kode inisialisasi
struct AbletonPush2 : Module {
    midi::Output midiOutput;
    midi::InputQueue midiInput;

    bool inputConnected;
    bool outputConnected;
}


Mari kita coba temukan Ableton Push di antara perangkat midi yang tersambung dan sambungkan. Karena modul ini dirancang untuk bekerja secara eksklusif dengan Ableton Push, kita tidak perlu membebani pengguna dengan pilihan perangkat dan Anda dapat menemukannya dengan nama saja.

Kode Koneksi Pengontrol
void connectPush() {
    auto in_devs = midiInput.getDeviceIds();
    for (int i = 0; i < in_devs.size(); i++){
        if (midiInput.getDeviceName(in_devs[i]).find("Ableton") != std::string::npos) {
            midiInput.setDeviceId(in_devs[i]);
            inputConnected = true;
            break;
        }
    }
		
    auto out_devs = midiOutput.getDeviceIds();
    for (int i = 0; i < out_devs.size(); i++){
        if (midiOutput.getDeviceName(out_devs[i]).find("Ableton") != std::string::npos) {
            midiOutput.setDeviceId(out_devs[i]);
            outputConnected = true;
            break;
        }
    }
}


Sekarang dalam metode proses Anda dapat secara berkala memeriksa apakah controller terhubung dan bertanya pada antrian midi apakah ada pesan yang masuk. Di sini perlu disebutkan apa dan bagaimana umumnya dikodekan dalam standar midi. Sebenarnya, ada dua jenis pesan utama, yaitu Note ON / OFF, mentransmisikan nomor note dan gaya tekan, dan CC - Command Control, mentransmisikan nilai numerik dari beberapa parameter variabel. Informasi lebih lanjut tentang midi dapat ditemukan di sini .

Kode Polling Antrian MIDI
void process(const ProcessArgs &args) override {
    if (sampleCounter > args.sampleRate / updateFrequency) {

        if ((!inputConnected) && (!outputConnected)) {
            connectPush();
        }

	midi::Message msg;
	while (midiInput.shift(&msg)) {
	    processMidi(msg);
	}
    }
}


Sekarang saya ingin mengajarkan pad controller untuk bersinar dalam warna yang berbeda, sehingga lebih mudah untuk menavigasi mereka. Untuk melakukan ini, kita perlu mengirimnya perintah midi yang sesuai. Pertimbangkan, misalnya, pad nomor 36 (ini adalah pad kiri terendah). Jika Anda mengkliknya, controller akan mengirimkan perintah 0x90 (note on), diikuti oleh nomor note (36) dan angka dari 0 hingga 127, yang berarti kekuatan pers. Dan ketika, sebaliknya, komputer mengirimkan controller 0x90 perintah yang sama dan nomor note yang sama 36, ​​maka angka ketiga akan menunjukkan warna yang pad kiri bawah harus menyala. Jumlah tombol dan bantalan ditunjukkan pada gambar di atas. Push memiliki beberapa kemungkinan untuk bekerja dengan warna, palet, animasi, dan parameter backlighting lainnya. Saya tidak masuk ke rincian dan hanya membawa semua warna yang mungkin dari palet default ke bantalan dan memilih yang saya suka.Tetapi dalam kasus umum, konversi perintah midi ke nilai PWM pada LED, menurut dokumentasi, terlihat seperti ini:



Kode Kontrol Backlight
void lightOn(int note, int color){
    midi::Message msg;
    msg.setNote(note);
    msg.setValue(color);
    msg.setChannel(1);
    msg.setStatus(0x9);
    midiOutput.sendMessage(msg);
}

void lightOff(int note){
    midi::Message msg;
    msg.setNote(note);
    msg.setValue(0);
    msg.setChannel(1);
    msg.setStatus(0x8);
    midiOutput.sendMessage(msg);
}


Kami menghubungkan layar




Untuk menghubungkan layar Anda harus bekerja dengan USB. Kontroler itu sendiri tidak tahu cara menggambar apa pun dan tidak tahu apa-apa tentang grafis. Yang dia inginkan adalah mengirim bingkai berukuran 160x960 piksel setidaknya setiap 2 detik. Semua rendering dilakukan di sisi komputer. Untuk memulai, sambungkan dan buka perangkat USB, seperti yang dijelaskan dalam dokumentasi :

Tampilkan Kode Koneksi
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>

#ifdef _WIN32

// see following link for a discussion of the
// warning suppression:
// http://sourceforge.net/mailarchive/forum.php?
// thread_name=50F6011C.2020000%40akeo.ie&forum_name=libusbx-devel

// Disable: warning C4200: nonstandard extension used:
// zero-sized array in struct/union
#pragma warning(disable:4200)

#include <windows.h>
#endif

#ifdef __linux__
#include <libusb-1.0/libusb.h>
#else
#include "libusb.h"
#endif

#define ABLETON_VENDOR_ID 0x2982
#define PUSH2_PRODUCT_ID  0x1967

static libusb_device_handle* open_push2_device()
{
  int result;

  if ((result = libusb_init(NULL)) < 0)
  {
    printf("error: [%d] could not initilialize usblib\n", result);
    return NULL;
  }

  libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_ERROR);

  libusb_device** devices;
  ssize_t count;
  count = libusb_get_device_list(NULL, &devices);
  if (count < 0)
  {
    printf("error: [%ld] could not get usb device list\n", count);
    return NULL;
  }

  libusb_device* device;
  libusb_device_handle* device_handle = NULL;

  char ErrorMsg[128];

  // set message in case we get to the end of the list w/o finding a device
  sprintf(ErrorMsg, "error: Ableton Push 2 device not found\n");

  for (int i = 0; (device = devices[i]) != NULL; i++)
  {
    struct libusb_device_descriptor descriptor;
    if ((result = libusb_get_device_descriptor(device, &descriptor)) < 0)
    {
      sprintf(ErrorMsg,
        "error: [%d] could not get usb device descriptor\n", result);
      continue;
    }

    if (descriptor.bDeviceClass == LIBUSB_CLASS_PER_INTERFACE
      && descriptor.idVendor == ABLETON_VENDOR_ID
      && descriptor.idProduct == PUSH2_PRODUCT_ID)
    {
      if ((result = libusb_open(device, &device_handle)) < 0)
      {
        sprintf(ErrorMsg,
          "error: [%d] could not open Ableton Push 2 device\n", result);
      }
      else if ((result = libusb_claim_interface(device_handle, 0)) < 0)
      {
        sprintf(ErrorMsg,
          "error: [%d] could not claim interface 0 of Push 2 device\n", result);
        libusb_close(device_handle);
        device_handle = NULL;
      }
      else
      {
        break; // successfully opened
      }
    }
  }

  if (device_handle == NULL)
  {
    printf(ErrorMsg);
  }

  libusb_free_device_list(devices, 1);
  return device_handle;
}

static void close_push2_device(libusb_device_handle* device_handle)
{
  libusb_release_interface(device_handle, 0);
  libusb_close(device_handle);
}


Untuk mengirimkan frame, Anda harus terlebih dahulu mengirim header 16-byte, dan kemudian 160 baris masing-masing 960 angka 16-bit. Pada saat yang sama, menurut dokumentasi, string tidak boleh dikirim dalam 1920 byte, tetapi dalam paket 2048 byte, ditambah dengan nol.

Frame Transfer Code
struct Push2Display : FramebufferWidget {

    unsigned char frame_header[16] = {
          0xFF, 0xCC, 0xAA, 0x88,
          0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00 
    };

    libusb_device_handle* device_handle;

    unsigned char* image;

    void sendDisplay(unsigned char * image) {
        int actual_length;
     
        libusb_bulk_transfer(device_handle, PUSH2_BULK_EP_OUT, frame_header, sizeof(frame_header), &actual_length, TRANSFER_TIMEOUT);
        for (int i = 0; i < 160; i++)
            libusb_bulk_transfer(device_handle, PUSH2_BULK_EP_OUT, &image[(159 - i)*1920], 2048, &actual_length, TRANSFER_TIMEOUT);
    }
}


Sekarang tinggal menggambar dan menulis bingkai ke buffer. Untuk melakukan ini, gunakan kelas FramebufferWidget yang mengimplementasikan metode drawFramebuffer . VCV Rack out of the box menggunakan perpustakaan nanovg , jadi cukup mudah untuk menulis grafik di sini. Kami mempelajari konteks grafik saat ini dari variabel APP global dan menyimpan kondisinya. Selanjutnya, buat bingkai 160x960 kosong dan gambarlah dengan metode menggambar . Setelah itu, salin framebuffer ke array yang akan dikirim melalui USB, dan kembalikan keadaan konteksnya. Pada akhirnya, atur bendera kotor sehingga pada iterasi rendering berikutnya, mesin VCV tidak lupa memperbarui widget kami.

Bingkai Rendering Code
void drawFramebuffer() override {

    NVGcontext* vg = APP->window->vg;

    if (display_connected) {
        nvgSave(vg);
	glViewport(0, 0, 960, 160);
        glClearColor(0, 0, 0, 0);
        glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	nvgBeginFrame(vg, 960,  160, 1);
        draw(vg);
        nvgEndFrame(vg);

        glReadPixels(0, 0, 960, 160, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, image); 

        //  XOR   ,  
        for (int i = 0; i < 80*960; i ++){
            image[i * 4] ^= 0xE7;
	    image[i * 4 + 1] ^= 0xF3;
            image[i * 4 + 2] ^= 0xE7;
	    image[i * 4 + 3] ^= 0xFF;
        }
	    	
	sendDisplay(image);

	nvgRestore(vg);
    }

    dirty = true;
}


Logika interaksi dan pemetaan parameter


Untuk tugas saya, saya ingin dapat mengubah pola yang direkam dalam sequencer, dan pada saat yang sama mengontrol serangkaian parameter tertentu, saya sendiri untuk setiap kelompok pola. Dengan pemetaan, saya memahami pengikatan parameter dari satu modul dengan parameter dari modul lain atau pengontrol midi. Saya menemukan sangat sedikit informasi tentang cara membuat pemetaan dengan indah, jadi saya mengambil sebagian besar kode yang mengimplementasikannya dari modul Peta MIDI yang dibangun ke dalam VCV . Singkatnya, untuk setiap kumpulan parameter, sebuah objek ParamHandle khusus dibuat di sana , yang memberi tahu mesin melalui kruk apa yang terhubung dengan apa.

Kesimpulan


Berikut ini adalah modul yang saya dapatkan pada akhirnya. Selain konversi standar midi ke CV, ini memungkinkan Anda untuk mengelompokkan bantalan, menetapkan warna padanya dan mengaitkan parameter modul yang sewenang-wenang dengan Push encoders, menampilkan nama dan nilainya pada tampilan pengontrol. Dalam video di bawah ini Anda dapat melihat ikhtisarnya, dan dalam video ini Anda dapat melihat beraksi.


Kode lengkap tersedia di sini .

Kami berhasil mengujinya hanya di Linux (Ubuntu 18.04), pada MacOS tampilan tidak terhubung karena spesifikasi libusb dan ada penundaan aneh pada antarmuka midi. Meskipun demikian, VCV Rack meninggalkan kesan yang sangat baik sebagai kerangka kerja dan sebagai DAW modular, saya berharap VCV akan terus berkembang, dan artikel ini akan membantu orang lain menulis modul mereka sendiri untuk itu.

All Articles