Нове життя старого синтезатора. Частина 2

    Продовження історії про старий згорілий синтезатор, в який я намагаюся вдихнути нове життя шляхом повної заміни заліза, що відповідає за генерацію звуку, на програмний синтезатор, побудований на базі міні-комп'ютера EmbedSky E8 з Linux на борту. Як це часто буває, між публікацією першої та другої частини статті пройшло набагато більше часу, ніж планувалося, але, тим не менш, продовжимо.
 
 
 
У попередній частині був викладений процес вибору апаратної платформи для нового «мозку» синтезатора з описом технічних характеристик рішення, стисло висвітлено процес складання необхідних бібліотек і проблем, з якими довелося зіткнутися в процесі. Тепер же що стосується заліза, то ми подивимося як влаштована клавіатурна матриця синтезатора, а далі буде більше деталей присвячених софтовой частини.
 
 
 
Клавіатурна матриця
Клавіатурна матриця синтезатора дуже схожа на звичайну клавіатурну матрицю, які багато любителів мікроконтролерів вже напевно підключали до своїх Arduino. Для кожної клавіші синтезатора на ній передбачено від одного (в найбільш дешевих моделях) до двох (в основній масі моделей) перемикачів. За допомогою двох розташованих поруч перемикачів, один з яких при натисканні клавіші замикається трохи раніше іншого, мікроконтролер може визначити умовну силу, а точніше швидкість, з якою клавіша була натиснута, щоб згодом був відтворений звук відповідної гучності. Виглядає це так:
 
 
На звороті плати розміщені діоди, які запобігають «хибне» зчитування натиснутих клавіш при одночасному натисканні декількох клавіш. Ось фрагмент принципової схеми клавіатурною матриці, на якій видно ці два перемикача і приєднані до них діоди:
 
 
Щоб просканувати матрицю, мікроконтролер послідовно підтягує стовпці (висновки, помічені як N) до харчування, і перевіряє рівень на рядках (висновки, помічені як B). Якщо рівень якого-небудь рядка виявиться високим, значить відповідна активному в даний момент поєднанню «стовпець-рядок» клавіша натиснута. На схемі показана лише частина клавіатури — всього на ній 76 клавіш (13 рядків і 6 х 2 колонок, що дає в сумі 156 можливих варіантів при скануванні матриці і 25 використовуваних висновків мікроконтролера). Сканування всієї клавіатури здійснюється кілька десятків разів в секунду і непомітно для виконавця.
 
У моєму синтезаторі мікроконтролером, відповідальним за сканування клавіатури, спочатку був 8-бітний одноразово програмований мікроконтролер Hitachi HD63B05V0, що працює на частоті 8 МГц і має 4 КБ ROM і 192 байта RAM пам'яті. На жаль, даний контролер виявився неробочим після інциденту з харчуванням, описаного на початку першої статті. Зате, на щастя, він виявився майже сумісний за висновками з наявними у мене контролером ATmega162, на який я його і замінив, перерізавши і перепаяти всього лише 2 доріжки на платі, одна з яких — це висновок RESET, який опинився зовсім не в тому місці, як у HD63B05V0.
 
Оскільки таке включення контролера не дозволяла мені скористатися вбудованим UART (так як він теж був на інших висновках), то для виведення інформації про натиснутих клавішах я скористався цієї односторонньою (тільки запис) реалізацією послідовного порту. Також в мікроконтролер був залитий завантажувач TinySafeBoot , що також використовує програмну реалізацію послідовного порту, для можливості майбутнього оновлення прошивки. Оскільки в якості мови для швидкої розробки всього високорівневого ПО синтезатора я вибрав Python + Qt5, то для TinySafeBoot я також написав модуль на Python, який дозволяє зчитувати і записувати прошивку в мікроконтролер AVR. Сам мікроконтролер AVR підключений до послідовного порту UART1 на платі EmbedSky E8 і живиться від напруги 3.3V, щоб уникнути необхідності в перетворенні рівнів.
 
 Вихідний код прошивки для AVR
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <string.h>
#include "dbg_putchar.h"

#define MIDI_BASE			18
#define ZERO_BASE			28
#define KEYS_COUNT			76

#define hiz(port, dir) do { \
		(dir) = 0; \
		(port) = 0; \
	} while(0)

#define alow(port, dir) do { \
		(dir) = 0xff; \
		(port) = 0; \
	} while(0)

uint8_t keys[KEYS_COUNT];

/* Get state of a row by its index
 * starting from 1 to 13 */
uint8_t getRow(uint8_t idx)
{
	if (idx <= 8) {
		return (PINC & (1 << (8 - idx)));
	} else if (idx >= 9 && idx <= 11) {
		return (PINE & (1 << (11 - idx)));
	} else if (idx == 12) {
		return (PINA & (1 << PIN6));
	} else if (idx == 13) {
		return (PINA & (1 << PIN4));
	}

	return 0;
}

inline void activateColumn1(uint8_t idx)
{
	PORTD = 0x00 | (1 << (8 - idx));
	PORTB = 0x00;
}

void activateColumn2(uint8_t idx)
{
	if (idx <= 3) {
		PORTB = 0x00 | (1 << (idx + 4));
		PORTD = 0x00;
	} else if (idx == 4) {
		PORTB = 0x00 | (1 << PIN4);
		PORTD = 0x00;
	} else if (idx == 5 || idx == 6) {
		PORTD = 0x00 | (1 << (idx - 5));
		PORTB = 0x00;
	}
}

inline void deactivateColumns(void)
{
	PORTD = 0x00;
	PORTB = 0x00;
}

inline void initPorts(void)
{
	hiz(PORTA, DDRA);
	hiz(PORTC, DDRC);
	hiz(PORTE, DDRE);
	PORTB = 0x00;
	DDRB = 0xfe;
	DDRD = 0xff;
}

void resetRows(void)
{
	/* output low */
	alow(PORTC, DDRC);
	alow(PORTE, DDRE);

	/* don't touch PA7 & PA5 */
	DDRA |= 0x5f;
	PORTA &= ~0x5f;

	_delay_us(10);

	/* back to floating input */
	hiz(PORTC, DDRC);
	hiz(PORTE, DDRE);
	DDRA &= ~0x5f;
}

/* base MIDI note number is 25: C#0 */

int main(void)
{
	uint8_t row, col, layer;
	uint8_t note, offset;

	initPorts();
	memset(keys, 0, sizeof(keys));
	dbg_tx_init();
	dbg_putchar('O');
	dbg_putchar('K');

	while(1)
	{
		for (layer = 0; layer < 2; layer++)
		{
			for (col = 1; col <= 6; col++)
			{
				if (!layer)
					activateColumn1(col);
				else
					activateColumn2(col);

				for (row = 1; row <= 13; row++)
				{
					note = 6 * row + col + MIDI_BASE;
					offset = note - ZERO_BASE;

					if (getRow(row))
					{
						if (!layer)
						{
							/* increase velocity counter */
							if (keys[offset] < 254 && !(keys[offset] & 0x80))
								keys[offset]++;
						}
						else
						{
							if (!(keys[offset] & 0x80))
							{
								/* generate note-on event */
								dbg_putchar(0x90);
								dbg_putchar(note);
								/*dbg_putchar(keys[offset]);*/
								dbg_putchar(0x7f);
								/* stop counting */
								keys[offset] |= 0x80;
							}
						}
					}
					else
					{
						if (layer)
							continue;

						if (keys[offset] & 0x80)
						{
							/* generate note off event */
							dbg_putchar(0x90);
							dbg_putchar(note);
							dbg_putchar(0x00);
							/* reset key state */
							keys[offset] = 0x00;
						}
					}
				}

				deactivateColumns();
				resetRows();
			}
		}
	}

	return 0;
}

 
 Модуль на Python для TinySafeBoot
import serial
import binascii
import struct
import intelhex
import sys

class TSB(object):
	CONFIRM = '!'
	REQUEST = '?'

	def __init__(self, port):
		self.port = serial.Serial(port, baudrate=9600, timeout=1)
		self.flashsz = 0

	def check(self):
		if not self.flashsz:
			raise Exception("Not activated")

	def activate(self):
		self.port.write("@@@")
		(self.tsb, self.version, self.status, self.sign, self.pagesz, self.flashsz, self.eepsz) = \
			struct.unpack("<3sHB3sBHH", self.port.read(14))
		self.port.read(2)
		self.pagesz *= 2
		self.flashsz *= 2
		self.eepsz += 1
		assert(self.port.read() == self.CONFIRM)

	def rflash(self, progress=None, size=0):
		self.check()
		self.port.write("f")
		self.addr = 0
		self.flash = ""
		size = self.flashsz if not size else size
		while self.addr < size:
			if progress is not None:
				progress("read", self.addr, size)
			self.port.write(self.CONFIRM)
			page = self.port.read(self.pagesz)
			if len(page) != self.pagesz:
				raise Exception("Received page too short: %d" % len(page))
			self.addr += len(page)
			self.flash += page
		return self.flash.rstrip('\xff')

	def wflash(self, data, progress=None):
		if len(data) % self.pagesz != 0:
			data = data + "\xff" * (self.pagesz - (len(data) % self.pagesz))
		assert(len(data) % self.pagesz == 0)
		self.check()
		self.port.write("F")
		self.addr = 0
		assert(self.port.read() == self.REQUEST)
		while self.addr < len(data):
			if progress is not None:
				progress("write", self.addr, len(data))
			self.port.write(self.CONFIRM)
			self.port.write(data[self.addr:self.addr + self.pagesz])
			self.addr += self.pagesz
			assert(self.port.read() == self.REQUEST)
		self.port.write(self.REQUEST)
		return self.port.read() == self.CONFIRM

	def vflash(self, data, progress=None):
		fw = self.rflash(progress, len(data))
		return fw == data

	def info(self):
		print "Tiny Safe Bootloader: %s" % self.tsb
		print "Page size:   %d" % self.pagesz
		print "Flash size:  %d" % self.flashsz
		print "EEPROM size: %d" % self.eepsz

if __name__ == "__main__":
	import argparse

	def progress(op, addr, total):
		sys.stdout.write("\r%s address: $%0.4x/$%0.4x" % (op, addr, total))
		sys.stdout.flush()

	parser = argparse.ArgumentParser()
	parser.add_argument("filename", help="firmware file in Intel HEX format")
	parser.add_argument("--device", help="Serial port to use for programming", default="/dev/ttyUSB0")
	args = parser.parse_args()

	tsb = TSB(args.device)
	tsb.activate()
	tsb.info()
	fw = intelhex.IntelHex(args.filename)
	assert(tsb.wflash(fw.tobinstr(), progress))
	assert(tsb.vflash(fw.tobinstr(), progress))
	print "\nOK\n"

 
В якості програматора для AVR я спочатку використовував програматор на базі Launchpad MSP430 , яких у мене є в наявності кілька штук, а потім це саморобний чудо (непогано працює, до речі), поступилося місцем прибулому з Китаю программатору TL866CS MiniPro. Відчуття від нового програматора вкрай позитивні.
 
Дуже докладно про пристрій клавіатури синтезатора і способи її сканування, включаючи один дуже оригінальний спосіб сканування через інтерфейс мікроконтролера AVR для підключення зовнішньої мікросхеми ОЗУ розповідається на сайті OpenMusicLabs
 
 
Приготування ядра з підтримкою Realtime Preemption
Почасти для отримання більшого контролю над планувальником і зниження затримки (latency) при програванні звуку, а почасти зі спортивного інтересу, я вирішив використовувати ядро ​​з патчем PREEPMT RT , однією з основних особливостей якого є те, що переривання також стають «процесами», які можуть бути витіснені планувальником з урахуванням пріоритету. Оригінальна ядро, що поставляється Samsung для процесора S5PV210, на базі якого побудована система, базується на ядрі версії 3.0.8, судячи з усього від Android. Жоден з патчів RT_PREEMPT, наявних на сайті проекту, призначених для даної версії ядра (3.0.8), не хотів накладатися на исходники без конфліктів, але врешті-решт, дозволивши всі конфлікти вручну, вдалося накласти патч версії 3.0.8-rt23.
 
Через те, що в модифікованому таким чином ядрі модифікованими також опинилися такі базові структури, як spinlock і mutex, з ним перестали лінкуватися поставляються у вигляді скомпільованих об'єктних файлів пропрієтарні драйвери деяких периферійних пристроїв: відеокамер, контролера ємнісного тачскрина, і, що найжахливіше, аудиокодека. Повернемося до них пізніше, а зараз відключимо їх і спробуємо перший раз запустити плату зі свіжозібраним ядром реального часу і… отримаємо моментальний kernel panic. Походив він ще до запуску відладчика kgdb (який, як з'ясувалося пізніше, все одно не працював би, навіть якби запустився), так що для налагодження довелося вставляти printf-и в файл
init/main.c
, функцію
start_kernel
, щоб визначити місце, в якому все руйнується. Таким чином з'ясувалося, що останнє, що встигало зробити ядро, це викликати функцію
hrtimers_init()
, Ініціалізується таймери високої роздільної здатності та їх переривання. Цей код залежить від конкретної платформи, і в нашому випадку знаходиться в
arch/arm/plat-s5p/hr-time-rtc.c
. Як я вже говорив, однією з основних особливостей ядра з патчем PREEMPT RT є те, що переривання стають потоками. Це можливо і в звичайному ядрі, але ядро ​​з PREEMPT RT по-замовчуванням намагається зробити такими майже всі переривання. Подальший аналіз коду показав, що для роботи цих потоків використовується задача kthreadd_task, яка ініціалізується в самому кінці функції
start_kernel
— набагато пізніше, ніж відбувається ініціалізація таймерів. Падіння ж відбувалося через те, що переривання таймера ядро ​​намагалося зробити потоковим, в той час як kthreadd_task ще NULL. Вирішується це установкою для окремих переривань, які не варто робити потоковими ні за яких обставин, прапора IRQF_NO_THREAD який і був доданий до прапорів переривання таймера в
hr-time-rtc.c
. Ура! Ядро завантажилося, але це ще тільки початок…
 
Як я вже згадував вище, одним з побічних ефектів стало те, що модуль, що відповідає за аудіо введення / висновок, перестав лінкуватися з новим ядром. Почасти це було тим, що ядро ​​з PREEMPT RT підтримує (у версії 3.0.8) тільки механізм управління пам'яттю SLAB, а спочатку модуль був скомпільований з включеним механізмом SLUB, яка не підтримується новим ядром. Однак, мені пощастило працювати в Лабораторії Касперського, і я умовив колегу декомпілювати для мене файли драйвера і кодека за допомогою декомпілятор Hex-Rays для ARM, після чого вдалося практично повністю відтворити їх вихідний код. Практично — тому що в результаті з «новим» драйвером аудиоинтерфейс став визначатися, однак через якісь відмінностей у низкоуровневой процедурі ініціалізації регістрів мікросхеми WM8960 звук програвався з артефактами. Якийсь час я намагався підправити свій драйвер, але потім вибрав більш легкий шлях — я відправив в техпідтримку китайської компанії EmbedSky Tech, де купував міні-комп'ютер, свій патч з PREEMPT_RT, і попросив їх скомпілювати для мене і вислати файли аудіодрайвер. Хлопці швидко відгукнулися і прислали мені файли, з яким звук, нарешті, запрацював як належить.
 
До речі, поки я возився зі своїм декомпільовану драйвером, я виявив, що відладчик kgdb не працює ні з моїм, ні з оригінальним ядром. Як з'ясувалося, для його роботи потрібна підтримка синхронного (polling) опитування послідовного порту, яка була відсутня в драйвері послідовного порту Samsung (
drivers/tty/serial/samsung.c
). Я додав у драйвер необхідну підтримку, засновану на цьому патчі, після чого відладчик запрацював.
 
Копаємо далі. Другим побічним ефектом нового ядра виявилася вкрай низька, з великими «лагами», швидкість роботи всіх чотирьох багатостраждальних послідовних портів системи на кристалі S5PV210, в результаті чого була неможлива нормальна робота в терміналі через послідовний порт, а також не працювала як належить перепрошивка контролера AVR, опитувального клавіатуру синтезатора. Я довго намагався зрозуміти в чому причина, але зауважив лише те, що введення кожного символу в терміналі приводив до генерації декількох мільйонів переривань послідовного порту — ядро, схоже, не поспішало їх обробляти. У підсумку я вирішив цю проблему тим, що за допомогою вищезгаданого прапора IRQF_NO_THREAD зробив всі переривання послідовних портів непотокового. Це рішення вийшло не дуже красивою, тому що крім драйвера Samsung довелося внести зміни у файли
serial_core.c
і
serial_core.h
, що зачіпають взагалі всі послідовні порти. Тому що в ядрі з PREEMPT RT не можна використовувати spin_lock_t в драйверах, які NO_THREAD, а потрібно використовувати raw_spinlock_t.
 
В оригінальному ядрі, яке, як я говорив вище, підтримує різні периферійні пристрої, такі як відеокамери, апаратні кодеки, HDMI і т.д., з 512 МБ оперативної пам'яті було доступно лише близько 390 МБ, а інше було зарезервовано для роботи вищевказаних пристроїв, причому завжди (навіть якщо в процесі конфігурування ядра вони були відключені). Дуже марнотратно, особливо враховуючи, що зайві 120 МБ оперативної пам'яті синтезатору дуже навіть не завадять для зберігання семплів. Пам'ять резервувалася у файлі
arch/arm/mach-s5pv210/mach-tq210.c
, який є головною точкою збору всієї інформації про конфігурацію і пристроях конкретної машини (у нашому випадку — плати). Коментуємо виділення пам'яті — виклик функції
s5p_reserve_bootmem
, і отримуємо 120 МБ додаткової пам'яті для роботи синтезатора.
 
Остання зміна, яке було внесено в ядро, стосувалося мінімального розміру буфера для аудіо, який в оригіналі дорівнював одній сторінці пам'яті, що при частоті дискретизації 44100 Гц, 2 каналу по 16 біт давало приблизно 20 мс — забагато. Це значення було змінено у файлі
sound/soc/samsung/dma.c
на 128 байт, після чого мінімальний розмір буфера зменшився до декількох мілісекунд без шкоди стабільності і працездатності.
 
 Вихідний код ядра з PREEMPT RT і всіма модифікаціями на GitHub
 
 
Як відбувається спілкування мікроконтролера AVR з LinuxSampler
AVR підключений до послідовного порту плати міні-комп'ютера, і випльовує в свій софтверний UART готові MIDI-повідомлення. Щоб позбавити себе від необхідності писати драйвери, було прийнято рішення використовувати в якості транспорту для всіх аудіо і MIDI-даних сервер JACK. Невелике пріложеньіце на C підключається до послідовного порту, реєструє себе в JACK як MIDI-OUT і починає перенаправляти туди всі отримані MIDI-повідомлення, а JACK вже доставляє їх в LinuxSampler. Дешево і сердито.
 
 Вихідний код програми-мосту між послідовним портом і JACK
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <sysexits.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <termios.h>
#include <jack/jack.h>
#include <jack/midiport.h>

#define UART_SPEED	B9600

jack_port_t	*output_port;
jack_client_t *jack_client = NULL;
int input_fd;

void init_serial(int fd)
{
    struct termios termios;
    int res;

    res = tcgetattr (fd, &termios);
    if (res < 0) {
        fprintf (stderr, "Termios get error: %s\n", strerror(errno));
        exit (EXIT_FAILURE);
    }

    cfsetispeed (&termios, UART_SPEED);
    cfsetospeed (&termios, UART_SPEED);

    termios.c_iflag &= ~(IGNPAR | IXON | IXOFF);
    termios.c_iflag |= IGNPAR;

    termios.c_cflag &= ~(CSIZE | PARENB | CSTOPB | CREAD | CLOCAL);
    termios.c_cflag |= CS8;
    termios.c_cflag |= CREAD;
    termios.c_cflag |= CLOCAL;

    termios.c_lflag &= ~(ICANON | ECHO);
    termios.c_cc[VMIN] = 3;
    termios.c_cc[VTIME] = 0;

    res = tcsetattr (fd, TCSANOW, &termios);
    if (res < 0) {
        fprintf (stderr, "Termios set error: %s\n", strerror(errno));
        exit (EXIT_FAILURE);
    }
}

double
get_time(void)
{
    double		seconds;
    int		ret;
    struct timeval	tv;

    ret = gettimeofday(&tv, NULL);

    if (ret) {
        perror("gettimeofday");
        exit(EX_OSERR);
    }

    seconds = tv.tv_sec + tv.tv_usec / 1000000.0;

    return seconds;
}

double
get_delta_time(void)
{
    static double	previously = -1.0;
    double		now;
    double		delta;

    now = get_time();

    if (previously == -1.0) {
        previously = now;

        return 0;
    }

    delta = now - previously;
    previously = now;

    assert(delta >= 0.0);

    return delta;
}

static double
nframes_to_ms(jack_nframes_t nframes)
{
    jack_nframes_t sr;

    sr = jack_get_sample_rate(jack_client);

    assert(sr > 0);

    return (nframes * 1000.0) / (double)sr;
}

static double
nframes_to_seconds(jack_nframes_t nframes)
{
    return nframes_to_ms(nframes) / 1000.0;
}

static jack_nframes_t
ms_to_nframes(double ms)
{
    jack_nframes_t sr;

    sr = jack_get_sample_rate(jack_client);

    assert(sr > 0);

    return ((double)sr * ms) / 1000.0;
}

static jack_nframes_t
seconds_to_nframes(double seconds)
{
    return ms_to_nframes(seconds * 1000.0);
}

static void
process_midi_output(jack_nframes_t nframes)
{
    int t, res;
    void *port_buffer;
    char midi_buffer[3];
    jack_nframes_t	last_frame_time;

    port_buffer = jack_port_get_buffer(output_port, nframes);
    if (port_buffer == NULL) {
        printf("jack_port_get_buffer failed, cannot send anything.\n");
        return;
    }

    jack_midi_clear_buffer(port_buffer);

    last_frame_time = jack_last_frame_time(jack_client);
    t = seconds_to_nframes(get_delta_time());

    res = read(input_fd, midi_buffer, sizeof(midi_buffer));
    if (res < 0 && errno == EAGAIN)
        return;
    res = jack_midi_event_write(port_buffer, t, midi_buffer, 3);

    if (res != 0) {
        printf("jack_midi_event_write failed, NOTE LOST.");
    }
}

static int
process_callback(jack_nframes_t nframes, void *notused)
{
    if (nframes <= 0) {
        printf("Process callback called with nframes = 0; bug in JACK?");
        return 0;
    }

    process_midi_output(nframes);
    return 0;
}

int
connect_to_input_port(const char *port)
{
    int ret;

    ret = jack_port_disconnect(jack_client, output_port);

    if (ret) {
        printf("Cannot disconnect MIDI port.");

        return -3;
    }

    ret = jack_connect(jack_client, jack_port_name(output_port), port);

    if (ret) {
        printf("Cannot connect to %s.", port);

        return -4;
    }

    printf("Connected to %s.", port);

    return 0;
}

static void
init_jack(void)
{
    int i, err;

    jack_client = jack_client_open("midibridge", JackNullOption, NULL);

    if (jack_client == NULL) {
        printf("Could not connect to the JACK server; run jackd first?");
        exit(EXIT_FAILURE);
    }

    err = jack_set_process_callback(jack_client, process_callback, 0);
    if (err) {
        printf("Could not register JACK process callback.");
        exit(EXIT_FAILURE);
    }

    char port_name[32];
    snprintf(port_name, sizeof(port_name), "midi_out");
    output_port = jack_port_register(jack_client, port_name, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);

    if (output_port == NULL) {
        printf("Could not register JACK output port '%s'.", port_name);
        exit(EXIT_FAILURE);
    }

    if (jack_activate(jack_client)) {
        printf("Cannot activate JACK client.");
        exit(EXIT_FAILURE);
    }
}

static void
usage(void)
{
    fprintf(stderr, "usage: midibridge -a <input port>\n");
    exit(EXIT_FAILURE);
}

int
main(int argc, char *argv[])
{
    int		ch;
    char	*autoconnect_port_name = NULL;

    while ((ch = getopt(argc, argv, "a:")) != -1) {
        switch (ch) {
        case 'a':
            autoconnect_port_name = strdup(optarg);
            break;
        default:
            usage();
        }
    }

    input_fd = open("/dev/ttySAC1", O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
    if (input_fd < 0) {
        fprintf(stderr, "Cannot open serial port %s\n", strerror(errno));
        return EXIT_FAILURE;
    }

    init_serial (input_fd);

    init_jack();

    if (autoconnect_port_name) {
        if (connect_to_input_port(autoconnect_port_name)) {
            printf("Couldn't connect to '%s', exiting.", autoconnect_port_name);
            exit(EXIT_FAILURE);
        }
    }

    getc(stdin);

    return 0;
}

 
 
Таке рішення також дозволяє програвати MIDI-файли через JACK за допомогою
jack-smf-player
, який я скомпілював для ARM і WAV/MP3 через mplayer з підтримкою виводу звуку в JACK.
 
 
Бонус
Завдяки коментарю nefelim4ag до попереднього посту, я дізнався про існування libhybris — бібліотеки, яка дозволяє використовувати Android-драйвера у звичайній Linux-системі. Після деяких танців з бубнами, всіх подробиць яких я, на жаль, вже не пам'ятаю, мені вдалося завести libhybris у своїй системі і пересобрать Qt 5 і PyQt5 з підтримкою OpenGL ES 2.0, EGLFS і Qt Quick 2.0. Тепер мій користувальницький інтерфейс використовує Qt Quick і виглядає у відповідності з останніми модними тенденціями косить під Android 4.0.
 
 
 
 
Наостанок
Невелике демо — поки тільки аудіо, так як синтезатор зараз знаходиться в наполовину розібраному стані. Відео ж буде в наступному пості, яка народиться швидше за все в серпні, після того як приїде замовлена ​​в Китаї плата, що з'єднує воєдино всі частини синтезатора. Крім того, наступний пост буде, швидше за все, присвячений вже не таким низькорівневим маніпуляціям з ядром, а процесу доведення до розуму користувача частині софта на PyQt5 і QtQuick і, звичайно, демонстрації получившегося
 
Якщо комусь цікаво:
 Список всього ПЗ, яке було крос-скомпільовані для ARM
     
  • alsa-lib-1.0.27.2
  •  
  • alsa-utils-1.0.27.2

  •  
  • libaudiofile-0.3.6
  •  
  • dbus-1.8.0
  •  
  • dropbear-2014.63
  •  
  • fftw-3.3.3
  •  
  • fluidsynth-1.1.6
  •  
  • fontconfig-2.11.0
  •  
  • freetype-2.5.3
  •  
  • glib-2.34.3
  •  
  • libicu-52.1
  •  
  • jack-audio-connection-kit-0.121.3
  •  
  • jack-smf-utils-1.0
  •  
  • libffi-3.0.13
  •  
  • libgig-3.3.0
  •  
  • libgig-svn
  •  
  • libhybris
  •  
  • libsamplerate-0.1.8
  •  
  • libsndfile-1.0.25
  •  
  • linuxsampler-1.0.0
  •  
  • linuxsampler-svn
  •  
  • mplayer SVN-r36900-4.4.6
  •  
  • openssl-1.0.0l
  •  
  • psutil-1.2.1
  •  
  • pyjack-0.5.2
  •  
  • PyQt-gpl-5.2
  •  
  • pyserial-2.7
  •  
  • Python-2.7.6
  •  
  • strace-4.8
  •  
  • tslib-1.4.1
  •  
 
Якщо вам буде потрібно зібрати щось з цього списку і виникнуть проблеми, я із задоволенням поділюся досвідом. Крім того, багато зі сказаного тут справедливо для іншої популярної платформи під назвою FriendlyARM Tiny210, яка побудована на базі того ж самого процесора S5PV210 і, можливо, комусь знадобиться використовувати з нею ядро ​​реального часу.
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.