Добавляем SPDIF в легендарный Roland MT-32

Этот проект можно считать частью №2 по цифровизации старых синтезаторов. В первой части я рассказал, как при помощи AK4103A можно добавить цифровой выход в практически любой синтезатор, где схема ЦАП подразумевает стереопоток, комбинированный в одну I2S DATA линию.

Roland MT-32, очень редкий и знакомый в 90е старичкам-геймерам разве что по слухам — еще более старое устройство, в котором применена достаточно распространенная в D-серии Roland схема использования параллельного DAC, который занимается не просто поточным преобразованием финального стерео, но и попутно выполняет услуги ЦАП для чипа реверберации.

Разбираем демуксер

Получается, что у нас нет в прямой доступности цифрового потока, содержащего в себе финальные аудио данные. В PCM54HP приходит поток, последовательно содержащий в себе не только чистый левый и правый каналы, но в том числе и отдельно реверберированные данные для левого и правого каналов.

Выглядит это примерно так (все фреймы 16bit, порядок предположительный): [RSYN1][LSYN1][REV R][REV L][RSYN2][LSYN2][RSYN1][LSYN1][REV R][REV L][RSYN2][LSYN2] и т.д. Преимущество параллельного DAC заключается в том, что он работает мнгновенно, т.е. нет совсем никакой задержки в том, чтобы снять текущие значения с резисторной (по сути) сборки и в следующий миг выполнять задачу по преобразованию совершенно другой картины. Демультиплексированием всей этой каши аудиоданных занимается широко известный CD4051. Переключение каналов в CD4051 осуществляется через смикшированные от LA и Reverb чипов управляющие линии SH1 SH2 SH2 (SH — Sample/Hold), а также линию INH которая отключает все каналы. На выходе демуксера образуются три пары аналоговых каналов, которые в дальнейшем уже микшируются и проходят финальную обработку в низкочастотном фильтре. LPF-фильтр должен иметь плоскую амплитудную характеристику в диапазоне 0–20kHz и высокую степень затухания выше 20kHz.

Разрядность и частота дискретизации MT-32 согласно заявленным характеристикам — 15bit 32kHz. В первой версии MT-32 (т.н. «old») последний 16-ый LSB бит на входе PCM54HP даже закорочен на землю, а в самой шине данных выпадает 14-ый бит (отсчитывая с нуля). Тем не менее, для нас ширина кадра всегда будет 16 бит (MT-32 второй версии имеет уже полноценную 16-битную шину). Теоретически, частота переключения каналов 0–1–2–3–4–5–6–7 каждый раз задействует изменение состояния 0/1 в управляющем сигнале A, поэтому можно ожидать на этой линии 128kHz, и на линиях B и C — 64 и 32kHz соответственно. Но нам не известен порядок следования фреймов. Даже если последовательно записывать все состояния параллельной шины — это будет бесполезно, не знай мы порядка переключения ABC. На практике, не имея в наличии трехканального осциллографа, можно попробовать поймать состояния хотя бы двух линий из трех (A и C), а затем записать секвенции AB и BC, A и INH, чтобы свести в дальнейшем картину в одно целое.

Итак, теперь мы знаем порядок фреймов: [L REV][RSYN2][LSYN2][R REV][RSYN1][LSYN1] [L REV][RSYN2][LSYN2][R REV][RSYN1][LSYN1] … и т.д. Если послушать эти пины в аналоге, то станет ясно, что SYN1 — чистый сигнал, REV — реверберированный возврат. SYN2 судя по всему тоже аналоговый, но слишком тихий для того, чтобы его можно было разборчиво записать;, но раз уж SYN2 тоже подмешивается в финальный микс, мы тоже будем так делать. Кстати, если посмотреть неиспользованные выходы CD4051 CH4 и CH5, там будут [почти] четкие 32kHz.

Управляющий сигнал INH работает на частоте 256kHz, значит нам надо будет считывать все порты на этой частоте. Отключение всех каналов необходимо, чтобы не было фальшивого срабатывания на состояния rising edge ABC, когда INH=1 говорит нам, что выдавать в serial нам ничего не надо. При INH=0 мы должны считать шину, и в зависимости от состояний ABC, раскидать ее в соответствующий выход. В идеале, нам нужно определить начало фрейма (возьмем для опорного кадра самый высокий пик INH), и все L/R кадры смикшировать в два финальных. Но для теста можно начать с отсылки двух кадров с чистой нереверберированной информацией (RSYN1, LSYN1). Сначала я думал заморочиться над железным определением начала секвенса кадров, но затем опустил эту часть, т.к. даже если логика на старте начнет работать с потоком в середине секвенса, определив LSYN1 как конец секвенса, мы сбросим счетчики и начнем работать уже в правильном порядке. Логика в данном случае будет выглядеть примерно так (я воспользуюсь тут псевдоязыком с понятным всем синтаксисом):

(R,L)=(0,0);
(FLAG_RSYN1,FLAG_LSYN1,FLAG_RSYN2,FLAG_LSYN2,FLAG_RREV,FLAG_LREV)=(0,0,0,0,0,0);
(RSYN1,LSYN1,RSYN2,LSYN2,RREV,LREV)=(0,0,0,0,0,0);
while (256kHz_cycle) {
	input=read(PCM54_parallel);
	A=read(CD4051_A); B=read(CD4051_B); C=read(CD4051_C); INH=read(CD4051_INH);

	#INH==0 enables output
	if (INH==0) {
		if (A==1 && B==1 && C==0) {
			RSYN2=input; FLAG_RSYN2=1; #RSYN2
		}
		elsif (A==0 && B==1 && C==0) {
			LSYN2=input; FLAG_LSYN2=1; #LSYN2
		}
		elsif (A==0 && B==0 && C==1) {
			RREV=input; FLAG_RREV=1;   #RREV
		}
		elsif (A==0 && B==0 && C==0) {
			LREV=input; FLAG_LREV=1;   #LREV
		}
		elsif (A==1 && B==1 && C==1) {
			RSYN1=input; FLAG_RSYN1=1; #RSYN1
		}
		elsif (A==0 && B==1 && C==1) {
			LSYN1=input; 		   #LSYN1 this is the last channel in frame
			(FLAG_RSYN1,FLAG_LSYN1,FLAG_RSYN2,FLAG_LSYN2,FLAG_RREV,FLAG_LREV)=(1,1,1,1,1,1);
		}

		if (FLAG_RSYN1==1 && FLAG_LSYN1==1 && FLAG_RSYN2==1 && FLAG_LSYN2==1 && FLAG_RREV==1 && FLAG_LREV==1) {

			#in merge there will be magic
			L=merge(LSYN1,LSYN2,LREV);
			R=merge(RSYN1,RSYN2,RREV);
			write_serial(R,L); 

			#reset channel flags
			(FLAG_RSYN1,FLAG_LSYN1,FLAG_RSYN2,FLAG_LSYN2,FLAG_RREV,FLAG_LREV)=(0,0,0,0,0,0);
		}
	}
}

Железная часть

Схематично план всего проекта нарисовался вот такой:

7f93f1b817edac5b7ee66d05af81bafe.png

В этом плане участвует всего лишь только одна магическая фигура, и тут я честно скажу, на ум приходили гигантские конструкции из кучи логики, которые должны выполнять задачу микширования цифровых потоков. Мне подсказали, что стоит бросить заниматься фигней и освоить FPGA. Все сигналы DAC и A/B/C/INH имеют уровни CMOS, так что нам надо конвертировать их в уровень TTL при помощи CD4050B. Благодаря этому, мы также получаем выровненые уровни (желтый — TTL в другой шкале):

Синий - CMOS, желтый - TTL (увеличен)

Синий — CMOS, желтый — TTL (увеличен)

DIT

Поскольку мы имеем дело с 16 битами, можно использовать обширное количество DIT, т.к. все они как минимум работают с 16-bit RJ. Для этих целей я выбрал DIT4192, т.к. у меня он уже был в наличии после экспериментов в процессе работы над 18-bit DIT. Здесь вся обвязка стандартна, конфигурация:

DIT4192 Hardware mode

Mode operation

Slave (SYNC and SCLK are inputs)

Format

16-Bit Right-Justified

Sampling frequency

32kHz

Master clock

16.384MHz (512*fs)

Bit clock

1.048MHz (16×2*32KHz)

Обвязка по даташиту DIT4192

Обвязка по даташиту DIT4192

Магическая часть

Для магической части была выбрана демо-плата FPGA TangNano 9K, и план приобрел конкретность:

А затем была набросана и остальная схема. Обращаю внимание на коннектор I2S — можно полностью опустить монтаж DIT4192, если его нет в наличии, и использовать любой другой внешний SPDIF трансивер, или даже просто транспорт I2S в ресивер с соответствующим входом. Я проверял схему с внешней платой на основе WM8804 — все прекрасно работает.

Два PCM54HP, т.к. один футпринт для сокета, второй для коннектора на плату MT-32

Два PCM54HP, т.к. один футпринт для сокета, второй для коннектора на плату MT-32

Прототип

Конечно же, схема не сразу приобрела финальный вид. Я начал с «бутерброда», разделяющего конвертер уровней от FPGA, пробуя различные варианты от резисторных делителей до CD4050 …

далеко не первая макетка

далеко не первая макетка

… и получив ровные уровни, я остановился на CD4050 и сел проектировать плату. Платы были достаточно быстро сделаны на известном PCBway (впервые воспользовался), ну и всем известно, что в РФ за такие смешные деньги прототип вообще не заказать.

Финальный дизайн предполагает, что ЦАП PCM54HP будет выпаян из MT-32 и сокетирован на плате DIT. После этого, плату DIT можно либо впаять, либо также сокетировать на PBS коннекторах. Но для начала, можно просто надеть плату DIT вторым этажом на микросхему ЦАП, как я и делал это с «бутербродом».

Учим verilog

Я не имел никакого опыта в дизайне FPGA-проектов. Но оказалось, что мой код на псевдо-языке очень даже похож на verilog! Через пару недель (я занимался проектом всего несколько часов в неделю) был написан первый рабочий код:

module top (
        input mclk,             //master clock //pin 51
        input clk_inh,          //256kHz INH clk input //pin 53
        input [2:0] ch_id,      //cd4051 sample/hold controls a/b/c 128/64/32kHz //pin a 77 b 76 c 48
        input [15:0] dac,       //parallel input from dac
	    input sys_rst_n,        //reset input
	    output wire dtr,        //data ready flag //pin54
        output sdata,           //16bit i2s sdata output  //pin 49
        output wire wclk,       //i2s word select lrck output 32kHz //pin 31
        output wire bck         //i2s bit clock output 1024MHz //pin32
);

wire [31:0] data;

dac_decoder dac1(
	.clk_inh(clk_inh),.ch_id(ch_id),.dac(dac),.data(data),.rst_n(sys_rst_n),.dtr(dtr)
);

i2s_serializer ser1 (
	.mclk(mclk),.sdata(sdata),.wclk(wclk),.bck(bck),.data(data),.rst_n(sys_rst_n)
);

endmodule


module i2s_serializer (
        input mclk,             	//master clock 16.384MHz
	    input [31:0] data,		    //input channels register 
	    input wire rst_n,		    //reset button	
	    output reg sdata,   	    //i2s sdata output
        output reg wclk,        	//i2s word select lrck output mclk/512 = 32kHz
        output wire bck         	//[3] bit'mclk. i2s bit clock output 
					//16bit * 2 * 32000 = 1.024 MHz (16.384/16)
);
reg [31:0] mclk_counter;       		//32bit counter
assign bck=mclk_counter[3];     	//1.024MHz divide
reg [31:0] data_buf;			    //i2s output buffer 
reg [4:0] cbit;				        //0-15 current bit counter
			
initial begin
	mclk_counter<=0; cbit<=0; wclk<=0; data_buf<=0;
end

always  @(posedge mclk,negedge rst_n) begin
	if(!rst_n) begin mclk_counter<=0; end 
	else begin mclk_counter<=mclk_counter+1; end
end

//i2s WCLK=0 left, =1 right
always  @(negedge bck) begin					            //send sdata from buffer
	if (wclk==0) begin sdata<=data_buf[31-cbit]; end 	    //LSYN send
	else if (wclk==1) begin sdata<=data_buf[15-cbit]; end	//RSYN send
	cbit<=cbit+4'b01;
	if (cbit==15 && wclk==0) 
		begin cbit<=0; wclk<=1; end			            //LSYN1 end
	else if (cbit==15 && wclk==1) 
		begin cbit<=0; wclk<=0; data_buf<=data; end 	//RSYN1 end, new buffer read
end
endmodule



module dac_decoder (
	    input wire rst_n,
        input clk_inh,          	//256kHz INH clk input
        input [2:0] ch_id,    		//cd4051 sample/hold controls a/b/c
        input [15:0] dac,       	//parallel input from dac
	    output reg [31:0] data,		//32 bit
	    output reg dtr			    //data ready flag
);
reg [15:0] ch0;				//LREV
reg [15:0] ch6;				//RSYN2
reg [15:0] ch2;				//LSYN2
reg [15:0] ch1;				//RREV
reg [15:0] ch7;				//RSYN1
reg [15:0] ch3;				//LSYN1
initial begin
	ch0<=0; ch1<=0; ch2<=0; ch3<=0; ch6<=0; ch7<=0; dtr<=0; data<=0;
end

always  @(negedge clk_inh,negedge rst_n) begin
	if(!rst_n) begin
		dtr<=0; data<=0;
	end 
	else begin
	case (ch_id)
		4 : begin dtr<=0; end 			    // empty
		0 : begin dtr<=0; ch0<=dac; end 	// LREV
		6 : begin dtr<=0; ch6<=dac; end 	// RSYN2 
		2 : begin dtr<=0; ch2<=dac; end 	// LSYN2
		5 : begin dtr<=0; end 			    // empty
		1 : begin dtr<=0; ch1<=dac; end 	// RREV
		7 : begin dtr<=0; ch7<=dac; end 	// RSYN
		3 : begin dtr<=1; ch3<=dac; data = {dac,ch7}; end // LSYN
	endcase
	end
end
endmodule

Первое видео захвата цифры, пока что без реверберации (было ночью, уровень записи низкий):

Микширование 6 каналов в стерео

Самая простая логика аудиомикширования — суммирование сигналов. Эта логика работает и в цифре. Помните, реальная битность Roland MT-32 первой версии — 15. Так что можно использовать 17-битный буфер для суммирования всех трех каналов, а затем простейшим сдвигом поделить результат на 2 и остаться в пределах 16 бит. Заодно, я прикрутил кнопку отключения реверберации на одну из кнопок TangNano9K.

reg signed [16:0] l; reg signed [16:0] r;
reg signed [15:0] left; reg signed [15:0] right;
	case (ch_id)
		4 : begin dtr<=0; end // empty
		0 : begin dtr<=0; lrev<=dac; end // LREV
		6 : begin dtr<=0; rsyn2<=dac; end // RSYN2
		2 : begin dtr<=0; lsyn2<=dac; end // LSYN2
		5 : begin dtr<=0; end // empty
		1 : begin dtr<=0; rrev<=dac; end // RREV
		7 : begin dtr<=0; rsyn1<=dac; end // RSYN1
		3 : begin dtr<=1; lsyn1<=dac;     // LSYN1
            if (!rev_sw) begin l<=dac; r<=rsyn1; end 
            else begin l<=dac+lrev+lsyn2; r<=rsyn1+rrev+rsyn2; end
            left[15:0]<=l[15:0]; right[15:0]<=r[15:0]; data<={left,right};
            end 
	endcase

Возможные проблемы

Щелчки и треск

На последнем этапе, я обнаружил, что частота DTR (флаг, сигнализирующий о готовности кадра) слегка быстрее, чем WCLK (что-то типа 32.0010kHz@DTR против ровных 32kHz@WCLK). После нескольких «научных тыков» было выявлено, что частота A/B/C/INH зависит от кварца на 32.768kHz, и судя по всему, из нее нельзя получить чистые 32kHz. Кстати, в «новом» MT-32 использован кварц на 16.384MHz, и вот из него как раз можно получить чистые 32kHz делением на 512, но это может подтвердить только владелец «нового» MT-32. Сначала я думал, что плавающая частота является причиной этих щелков;, но оказался неправ.

Первая причина в том, что вы не можете оперировать с регистрами много раз в пределах одного такта. Нужен конвейер, и логика

            if (!rev_sw) begin l<=dac; r<=rsyn1; end 
            else begin l<=dac+lrev+lsyn2; r<=rsyn1+rrev+rsyn2; end
            left[15:0]<=l[15:0]; right[15:0]<=r[15:0]; data<={left,right};

должна быть разделена:

always  @(negedge clk_inh,negedge rst_n) begin
	if (!rst_n) begin
		dtr<=0; data<=0;
	end 
	else begin
	case (ch_id)
		4 : begin dtr<=0; left[15:0]<=l[15:0]; right[15:0]<=r[15:0]; end // empty
		0 : begin dtr<=0; lrev<=dac; end // LREV
		6 : begin dtr<=0; rsyn2<=dac; end // RSYN2
		2 : begin dtr<=0; lsyn2<=dac; end // LSYN2
		5 : begin dtr<=1; data<={left,right}; end // empty
		1 : begin dtr<=0; rrev<=dac; end // RREV
		7 : begin dtr<=0; rsyn1<=dac; end // RSYN1
		3 : begin dtr<=0; lsyn1<=dac;     // LSYN1
              		if (!rev_sw) begin l<=dac; r<=rsyn1; end 
              		else begin l<=dac+lrev+lsyn2; r<=rsyn1+rrev+rsyn2; end
            	    end 
	endcase
	end
end

Вторая причина — нужно избегать обновления регистра, если он находится в режиме чтения сериализатором. Частота сериализатора гораздо выше частоты декодера, и можно испортить кадр, т.к. сериализатор может прочитать уже какую-то часть регистра во время обновления. Используя эту логику, мы, конечно, получаем более низкий рейт обновления кадров для сериализатора, но этим можно пожертвовать, т.к. эксперименты с FIFO ни к чему не привели — если входящая частота выше, рано или поздно какие-то данные придется пропустить, и лучше сделать это сразу, нежели накапливать данные и при переполнении получать слышимый пропуск.

5 : begin dtr<=1; if (drq==0) begin data<={left,right}; dtw<=1; end end // empty

После этих исправлений, запись более 3х часов показала, что щелчки ушли.

3 часа записи - ни одного щелчка

3 часа записи — ни одного щелчка

Десинхронизация

Другая проблема может возникнуть, если кварцевый генератор, используемый для master clock, нестабилен, или имеет плохой контакт. Это заметно по срыву WCLK.

DC offset

тишина на уровне -6dB

тишина на уровне -6dB

Из-за известной проблемы «MT-32 digital overflow» и аппаратного битового сдвига, весь поток определяется звуковой картой на уровне -6dB. Цифровой DC offset легко решается вычитанием определенного числа из всех семплов потока. Но поскольку поток LA приходит на ЦАП в виде signed, а DIT и I2S по спецификации работают с unsigned, простым вычитанием фиксированного offset=16384 я добился только смещения до -12dB. Это пока уходит в список «доделать».

4 : begin dtr<=0; dtw<=0; left[15:0]<= $unsigned(l)-offset; right[15:0]<= $unsigned(r)-offset; end // empty

Монтаж

Проблема плохих контактов. Это был кошмар на протяжении многих недель — я искал проблему в verilog, в частотах, в битах, и совсем не подумал, что способ «надевания» макетки на чип совершенно ненадежен — несколько пинов в процессе частого «надевания» отогнулись чуть дальше своих соседей, вследствии чего я постоянно получал испорченные данные и испорченный звук.

Решение этой проблемы только одно — нужно выпаять ЦАП из платы MT-32 и сокетировать его на плате DIT. Процесс выпайки весьма сложен без соответствующего опыта. Сперва нужно заменить несколько ближайших керамических конденсаторов на низкопрофильные — чтоб не мешали установке DIT. Пленочные конденсаторы встают идеально. Затем нужно выпаять ЦАП.

на всякий случай приготовлен второй PCM54HP

на всякий случай приготовлен второй PCM54HP

Пока меняем конденсаторы, время подумать о том, как забирать сигналы A/B/C/INH от демуксера. Поначалу я просто подпаивал провода непосредственно к пинам микросхемы. Но провода часто гнулись, ломались, и я решил напаять вторым этажом кусочек макетки, чтобы иметь более легкий доступ к сигналам.

таким же

таким же «вторым этажом» стоял DIT над ЦАП и не контачил

Я использовал двурядные PBD коннекторы с шагом 2.54 для сокетирования DIT. Один ряд, конечно же, должен быть вынут. Почему PBD? Просто у меня их было в запасах гораздо больше, чем однорядных PBS:)

Можно установить плату в сокет.

Достанем плату MT-32 из корпуса. Время найти место для монтажа RCA.

Финальная картинка:

Попутно я решил заменить всю керамику и электролиты на свежие. Буржуйство, но использовать Wima и Nichicon — почему бы и нет? Выпайка старых конденсаторов показала, что старая керамика сильно гуляет от предписанных номиналов, а вот старые электролиты Sanyo показали себя на высоте — почти все не ниже номинала, и только в цепи питания парочка 1000uF действительно просела и нуждалась в замене.

77d492f9e980e15557f6b3a1bb6c9ee1.JPG

Вместо заключения.

Если познать дзен, можно обойтись вообще без каких-либо внешних трансиверов, и реализовать логику I2S/SPDIF на том же самом FPGA. Данное решение (предположительно) может быть использовано для вывода цифрового звука из любого синтезатора Roland D-серии, т.к. архитектура LA-синтеза использует практически одну и ту же схематику. Проект почти закончен (готовится вторая версия платы с небольшим модом для улучшения качества аналогового выхода ЦАП и вариантом выбора трансивера — WM8804 или DIT4192).

Ссылки

MT-32 DIT на Github

Roland MT-32 service notes

PCM54HP datasheet

DIT4192 datasheet

© Habrahabr.ru