
يسير إنشاء الموسيقى مؤخرًا بالطريقة نفسها التي تسير بها الصورة قبل 10 سنوات: لكل منها حسابها الخاص DSLR و Instagram. صناعة الموسيقى سعيدة للغاية لأن هذا الاهتمام يجلب الكثير من المال. في كل يوم ، تظهر مكونات VST الإضافية والأجهزة التناظرية الجديدة ، يزداد عدد الدورات المواضيعية بسرعة ، وتنتقل المدونات المخصصة لإنتاج الموسيقى إلى أعلى youtube. على هذه الخلفية ، تبدو المشاريع مفتوحة المصدر مثيرة للاهتمام للغاية ، مما يسمح لأي شخص يريد تجربة نفسه كمنتج دون إنفاق الكثير من المال على ذلك. أحد هذه المشاريع هو VCV Rack ، الذي يهدف إلى جعل أغلى فئة من الآلات الموسيقية - آلات المزج التناظرية - متاحة للجميع. في الآونة الأخيرة ، عندما حصلت على فكرة المسار ،وفي متناول اليد لم يكن هناك سوى جهاز كمبيوتر محمول لينكس ، أردت أن أجعل المسار بأكمله في VCV. وعلى طول الطريق ، كانت هناك رغبة في التحكم في المزج باستخدام وحدة تحكم midi بطريقة لا تعمل باستخدام الوحدات المتاحة. في النهاية ، قررت كتابة مكون إضافي لربط Ableton Push 2 بـ VCV Rack. سنتحدث عن ما جاء بها ، وكيفية كتابة وحداتنا الخاصة لـ VCV في هذه المقالة.مرجع سريعVCV Rack — open source , . VCV
,
.
Ableton Push 2 — midi- Ableton,
DAW,
API .
VCV Rack API
تتكون كل وحدة في VCV من جزأين - الصوت والرسومات. يرث الجزء الصوتي من فئة الوحدة طريقة العملية ، التي تسمى لكل عينة ، أي بتردد أخذ العينات. بالمناسبة ، يمكن أن يختلف تردد أخذ العينات في VCV من 44.1 كيلوهرتز القياسي إلى 768 كيلوهرتز ، مما يسمح بمحاكاة وحدات تركيب أكثر دقة في وجود قوة حوسبة كافية.تكون الكائنات من نوع ModuleWidget مسؤولة عن الرسوم التي ترث طريقة الرسم من البنية الأساسية . يستخدم VCV مكتبة رسومات المتجهات nanovg. الرسم داخل طريقة السحبيمكن أن يحدث داخل حدود الوحدة النمطية (يتم قطع الفائض بواسطة المحرك) ، وعلى سبيل المثال في الإطار Framebuffer ، الذي ما زلنا نستخدمه.فماذا يستغرق لكتابة الوحدة النمطية الخاصة بك ل VCV؟إعداد البيئة وتثبيت Rack SDK
الخطوة الأولى موصوفة جيدًا في الوثائق ولا تسبب أي صعوبات (على الأقل تحت لينكس) ، لذلك لن نتحدث عنها.إنشاء قالب البرنامج المساعد
يوجد نص مساعد helper.py في Rack SDK. يجب أن يقول createplugin ، ثم حدد اسم المكون الإضافي ، ومعلومات اختياريًا عنه.<Rack SDK folder>/helper.py createplugin MyPlugin
عندما يتم إنشاء قالب البرنامج المساعد ، يمكن تجميعه وتثبيته باستخدام الأمرRACK_DIR=<Rack SDK folder> make install
ارسم الواجهة الأمامية للوحدة النمطية
يمكن أن يحتوي كل مكون إضافي على عدة وحدات ، ولكل وحدة من الوحدات تحتاج إلى رسم لوحة رئيسية. لهذا ، تقدم لنا وثائق VCV استخدام Inkscape أو أي محرر متجه آخر. نظرًا لأن الوحدات في VCV مثبتة على حامل Eurorack الظاهري ، فإن ارتفاعها دائمًا هو 128.5 مم ويجب أن يكون العرض مضاعفًا 5.08 مم.يمكن تمييز عناصر الواجهة الأساسية ، مثل مقابس CV / Gate والأزرار والمصابيح في شكل متجه. للقيام بذلك ، ارسم دوائر مكانها للألوان المقابلة ( مزيد من التفاصيل هنا ) ، بحيث يقوم helper.py بعد ذلك بإنشاء رمز لهذا الترميز. شخصيا ، بدا لي أن هذه ليست ميزة مريحة للغاية ومن الأسهل وضع العناصر مباشرة من التعليمات البرمجية. عندما تكون الصورة والتخطيط جاهزين ، تحتاج إلى تشغيل helper.py مرة أخرى لإنشاء قالب وحدة وربطه باللوحة الأمامية.helper.py createmodule MyModule res/MyModule.svg src/MyModule.cpp
نحن نربط الأزرار والتقلبات
بصرف النظر عن الشاشة ، يعتبر الكمبيوتر Ableton Push 2 جهاز USB-MIDI عادي ، مما يجعل من السهل إنشاء اتصال بينه وبين حامل VCV. للقيام بذلك ، قم بإنشاء قائمة انتظار ميدي الإدخال ومنفذ إخراج ميدي داخل فئة الوحدة النمطية.كود التهيئةstruct AbletonPush2 : Module {
midi::Output midiOutput;
midi::InputQueue midiInput;
bool inputConnected;
bool outputConnected;
}
دعونا نحاول العثور على Ableton Push بين أجهزة midi المتصلة والاتصال به. نظرًا لأن الوحدة النمطية مصممة للعمل حصريًا مع Ableton Push ، فإننا لا نحتاج إلى تحميل المستخدم عبئًا باختيار الجهاز ويمكنك ببساطة العثور عليه بالاسم.رمز اتصال وحدة التحكم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;
}
}
}
الآن في طريقة العملية ، يمكنك التحقق بشكل دوري مما إذا كانت وحدة التحكم متصلة واسأل قائمة انتظار ميدي إذا وصلت أي رسائل. هنا تجدر الإشارة إلى ما وكيف يتم ترميزه بشكل عام في معيار ميدي. في الواقع ، هناك نوعان رئيسيان من الرسائل ، وهما: مذكرة ON / OFF ، يحيلان رقم الملاحظة وقوة الضغط ، و CC - Command Control ، ينقل القيمة العددية لبعض المعلمات المتغيرة. يمكن العثور على مزيد من المعلومات حول ميدي هنا .رمز استطلاع قائمة انتظار MIDIvoid process(const ProcessArgs &args) override {
if (sampleCounter > args.sampleRate / updateFrequency) {
if ((!inputConnected) && (!outputConnected)) {
connectPush();
}
midi::Message msg;
while (midiInput.shift(&msg)) {
processMidi(msg);
}
}
}
الآن أريد تعليم منصات التحكم للتوهج بألوان مختلفة ، بحيث يكون من الأسهل التنقل فيها. للقيام بذلك ، سنحتاج إلى إرسال أمر ميدي المناسب له. ضع في اعتبارك ، على سبيل المثال ، اللوحة رقم 36 (هذه هي الوسادة اليسرى الأدنى). إذا قمت بالنقر فوقها ، سترسل وحدة التحكم الأمر 0x90 (ملاحظة على) ، متبوعًا برقم الملاحظة (36) ورقم من 0 إلى 127 ، مما يعني قوة الصحافة. وعندما ، على العكس من ذلك ، يرسل الكمبيوتر نفس الأمر 0x90 ونفس الملاحظة رقم 36 إلى وحدة التحكم ، ثم الرقم الثالث سيشير إلى اللون الذي يجب أن تتوهج فيه اللوحة اليسرى السفلية. يظهر عدد الأزرار والوسادات في الشكل أعلاه. يحتوي Push على إمكانيات قليلة للعمل مع الألوان واللوحات والرسوم المتحركة ومعلمات الإضاءة الخلفية الأخرى. لم أخوض في التفاصيل وأحضرت ببساطة كل الألوان الممكنة للوحة الافتراضية إلى الوسادات واخترت الألوان التي أعجبتني.ولكن في الحالة العامة ، يبدو تحويل أوامر midi إلى قيم PWM على مصابيح LED ، وفقًا للوثائق ، كما يلي:
كود التحكم في الإضاءة الخلفية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);
}
نقوم بتوصيل الشاشة
لتوصيل الشاشة ، عليك العمل مع USB. وحدة التحكم نفسها لا تعرف كيفية رسم أي شيء ولا تعرف أي شيء عن الرسومات. كل ما يريده هو إرسال إطار بحجم 160x960 بكسل على الأقل كل ثانيتين. تتم جميع عمليات العرض على جانب الكمبيوتر. لبدء توصيل جهاز USB وفتحه ، كما هو موضح في الوثائق :عرض رمز الاتصال#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#ifdef _WIN32
#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];
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;
}
}
}
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);
}
لإرسال إطار ، يجب عليك أولاً إرسال رأس 16 بايت ، ثم 160 سطرًا من 960 رقم 16 بت لكل منها. في الوقت نفسه ، وفقًا للوثائق ، لا يجب إرسال السلاسل في 1920 بايت ، ولكن في حزم من 2048 بايت ، مع استكمال الأصفار.كود نقل الاطار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);
}
}
الآن يبقى فقط رسم وكتابة إطار إلى المخزن المؤقت. للقيام بذلك، استخدم FramebufferWidget الدرجة التي تطبق drawFramebuffer طريقة . يستخدم VCV Rack خارج الصندوق مكتبة nanovg ، لذلك من السهل جدًا كتابة الرسومات هنا. نتعلم السياق البياني الحالي من APP المتغير العالمي وحفظ حالته. بعد ذلك ، قم بإنشاء إطار 160x960 فارغ وارسمه بطريقة السحب . بعد ذلك ، انسخ Framebuffer إلى الصفيف الذي سيتم إرساله عبر USB ، وأعد حالة السياق. في النهاية ، قم بتعيين العلامة القذرة بحيث لا ينسى محرك VCV تحديث الأداة المصغرة عند التكرار التالي للعرض.كود تقديم الإطار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);
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;
}
منطق التفاعل ورسم الخرائط للمعلمات
بالنسبة لمهمتي ، أردت أن أكون قادرًا على تبديل الأنماط المسجلة في التسلسلات ، وفي الوقت نفسه أتحكم في مجموعة معينة من المعلمات ، الخاصة بي لكل مجموعة من الأنماط. من خلال رسم الخرائط ، أفهم ربط معلمة وحدة نمطية واحدة مع معلمة وحدة نمطية أخرى أو وحدة تحكم ميدي. لقد وجدت القليل جدًا من المعلومات حول كيفية عمل رسم الخرائط بشكل جميل ، لذلك أخذت معظم الشفرة التي تنفذها من وحدة خريطة MIDI المدمجة في VCV . باختصار ، لكل مجموعة من المعلمات يتم إنشاء كائن ParamHandle خاص هناك ، والذي يخبر المحرك من خلال العكازات بما يرتبط بما.استنتاج
هنا وحدة حصلت عليها في النهاية. بالإضافة إلى التحويل القياسي للميدي إلى السيرة الذاتية ، فإنه يسمح لك بتجميع الوسائد ، وتعيين الألوان لها ، وربط معلمات الوحدة العشوائية مع مشفرات Push ، وعرض أسمائها وقيمها على شاشة التحكم. في الفيديو أدناه يمكنك أن ترى نظرة عامة ، وفي هذا الفيديو يمكنك أن ترى في العمل.الكود الكامل متاح هنا .تمكنا من اختباره فقط تحت Linux (Ubuntu 18.04) ، على نظام التشغيل MacOS لم يتم توصيل الشاشة بسبب تفاصيل libusb وكانت هناك تأخيرات غريبة في واجهة midi. على الرغم من ذلك ، ترك VCV Rack انطباعًا جيدًا جدًا كإطار وكقاعدة DAW معيارية ، آمل أن يستمر VCV في التطور ، وستساعد هذه المقالة شخصًا آخر على كتابة وحداته الخاصة به.