diff --git a/ZZ9000_proto.sdk/ZZ9000OS/src/ax.c b/ZZ9000_proto.sdk/ZZ9000OS/src/ax.c
index 6adef6ee12bb6ff7407747af5413f0db96ea342d..eb61e8e4c58d4c9d3df45626ae55e9df6d2bbb54 100644
--- a/ZZ9000_proto.sdk/ZZ9000OS/src/ax.c
+++ b/ZZ9000_proto.sdk/ZZ9000OS/src/ax.c
@@ -10,6 +10,9 @@
 #include "mntzorro.h"
 #include "sleep.h"
 #include "stdlib.h"
+#include "ax.h"
+#include "memorymap.h"
+#include "xtime_l.h"
 
 #define IIC2_DEVICE_ID	XPAR_XIICPS_1_DEVICE_ID
 #define IIC2_SCLK_RATE	100000
@@ -21,6 +24,9 @@ XI2s_Rx i2srx;
 XAudioFormatter audio_formatter;
 XAudioFormatter audio_formatter_rx;
 
+static uint8_t* audio_tx_buffer = (uint8_t*)AUDIO_TX_BUFFER_ADDRESS;
+static uint8_t* audio_rx_buffer = (uint8_t*)AUDIO_RX_BUFFER_ADDRESS;
+
 int adau_write16(u8 i2c_addr, u16 addr, u16 value) {
 	XIicPs* iic = &Iic2;
 	int status;
@@ -183,100 +189,18 @@ int adau_read24(u8 i2c_addr, u16 addr, u8* buffer) {
 void program_adau(u8* program, u32 program_len, u8* params, u32 param_len) {
 	for (u32 i = 0; i < program_len; i+=5) {
 		int res = adau_write40(0x34, 1024+i/5, &program[i]);
-		printf("[adau_write40] %lx: %d\n", i, res);
+		if (res != 0) printf("[adau_write40] %lx: %d\n", i, res);
 	}
 
 	for (u32 i = 0; i < param_len; i+=4) {
 		int res = adau_write32(0x34, 0+i/4, &params[i]);
-		printf("[adau_write32] %lx: %d\n", i, res);
+		if (res != 0) printf("[adau_write32] %lx: %d\n", i, res);
 	}
 }
 
-// returns 1 if adau1701 found, otherwise 0
-int audio_adau_init(uint32_t* audio_buffer) {
-	XIicPs_Config* i2c_config;
-	i2c_config = XIicPs_LookupConfig(IIC2_DEVICE_ID);
-	int status = XIicPs_CfgInitialize(&Iic2, i2c_config, i2c_config->BaseAddress);
-	printf("[adau] XIicPs_CfgInitialize 2: %d\n", status);
-	usleep(10000);
-	printf("[adau] XIicPs 2 is ready: %lx\n", Iic2.IsReady);
-	status = XIicPs_SelfTest(&Iic2);
-	printf("[adau] XIicPs_SelfTest: %x\n", status);
-
-	if (status != 0) {
-		printf("[adau] I2C instance 2 self test failed.");
-		return 0;
-	}
-
-	status = XIicPs_SetSClk(&Iic2, IIC2_SCLK_RATE);
-	printf("[adau] XIicPs_SetSClk: %x\n", status);
-
-	u8 rbuf[5];
-	u8 i = 0x34;
-
-	//usleep(10000);
-	// DSP core control: set ADM, DAM, CR
-	status = adau_write16(i, 2076, (1<<4)|(1<<3)|(1<<2));
-	if (status == 0) {
-		printf("[adau] write DSP core control: %d\n", i);
-		printf("[adau] ZZ9000AX detected.");
-	} else {
-		printf("[adau] ZZ9000AX not detected.");
-		return 0;
-	}
-
-	status = adau_read16(i, 2076, rbuf);
-	if (status == 0) {
-		printf("[adau] read: %d %x %x\n", i, rbuf[0], rbuf[1]);
-	}
-
-	// DAC setup: DS = 01
-	status = adau_write16(i, 2087, 1);
-	printf("[adau] write DAC setup: %d\n", status);
-
-	rbuf[0] = 0;
-	rbuf[1] = 0;
-
-	status = adau_read16(i, 2087, rbuf);
-	printf("[adau] read from 2087: %02x%02x (status: %d)\n", rbuf[0], rbuf[1], status);
-
-	program_adau(Program_Data_IC_1, sizeof(Program_Data_IC_1), Param_Data_IC_1, sizeof(Param_Data_IC_1));
-
-	// TODO: OBP/OLRP
-	u16 MS  = 1<<11; // clock master output
-	//u16 OBF = (0<<10)|(0<<9);    // bclock = 49.152/16 = mclk/4 = 3.072mhz
-	u16 OBF = (1<<10)|(0<<9);    // bclock = 49.152/4 = mclk = 12.288mhz
-	u16 OLF = (0<<8)|(0<<7);    // lrclock = 49.152/1024 = word clock = 48khz?!
-	u16 MSB = 0;    // msb 1
-	u16 OWL = 1<<1; // 16 bit
-	status = adau_write16(i, 0x081e, MS|OBF|OLF|MSB|OWL);
-	printf("[adau] write serial output control: %d\n", status);
-
-	u32 MP0 = 1<<2; // MP02 digital input 0
-	u32 MP1 = 1<<6; //
-	u32 MP2 = 1<<10; //
-	u32 MP3 = 1<<14; //
-	u32 MP4 = 1<<18; // MP42 serial clock in
-	u32 MP5 = 1<<22; // MP52 serial clock in
-
-	u32 MP6 = 1<<2; //
-	u32 MP7 = 1<<6; //
-	u32 MP8 = 1<<10; //
-	u32 MP9 = 1<<14; //
-	u32 MP10 = 1<<18; // MP102 set (serial clock out)
-	u32 MP11 = 1<<22; // MP112 set (serial clock out)
-	status = adau_write24(i, 0x0820, MP0|MP1|MP2|MP3|MP4|MP5);
-	printf("[adau] write MP control 0x820: %d\n", status);
-	status = adau_write24(i, 0x0821, MP6|MP7|MP8|MP9|MP10|MP11);
-	printf("[adau] write MP control 0x821: %d\n", status);
-
-	status = adau_read24(i, 0x0820, rbuf);
-	printf("[adau] read from 0x820: %02x%02x%02x (status: %d)\n", rbuf[0], rbuf[1], rbuf[2], status);
-	status = adau_read24(i, 0x0821, rbuf);
-	printf("[adau] read from 0x821: %02x%02x%02x (status: %d)\n", rbuf[0], rbuf[1], rbuf[2], status);
-
+void audio_init_i2s() {
 	XI2stx_Config* i2s_config = XI2s_Tx_LookupConfig(XPAR_XI2STX_0_DEVICE_ID);
-	status = XI2s_Tx_CfgInitialize(&i2s, i2s_config, i2s_config->BaseAddress);
+	int status = XI2s_Tx_CfgInitialize(&i2s, i2s_config, i2s_config->BaseAddress);
 
 	printf("[adau] I2S_TX cfg status: %d\n", status);
 
@@ -297,7 +221,7 @@ int audio_adau_init(uint32_t* audio_buffer) {
 			XAUD_FORMATTER_CTRL + XAUD_FORMATTER_MM2S_OFFSET, 0);
 
 	XAudioFormatterHwParams af_params;
-	af_params.buf_addr = (u32)audio_buffer;
+	af_params.buf_addr = (u32)audio_tx_buffer;
 	af_params.bits_per_sample = BIT_DEPTH_16;
 	af_params.periods = AUDIO_NUM_PERIODS; // 1 second = 192000 bytes
 	af_params.active_ch = 2;
@@ -328,7 +252,7 @@ int audio_adau_init(uint32_t* audio_buffer) {
 			XAUD_FORMATTER_CTRL + XAUD_FORMATTER_S2MM_OFFSET, 0);
 
 	XAudioFormatterHwParams afrx_params;
-	afrx_params.buf_addr = (u32)AUDIO_RX_BUFFER_ADDRESS;
+	afrx_params.buf_addr = (u32)audio_rx_buffer;
 	afrx_params.bits_per_sample = BIT_DEPTH_16;
 	afrx_params.periods = AUDIO_NUM_PERIODS; // 1 second = 192000 bytes
 	afrx_params.active_ch = 2;
@@ -365,6 +289,95 @@ int audio_adau_init(uint32_t* audio_buffer) {
 	printf("[adau] XI2s_Tx_Enable\n");
 	XAudioFormatterDMAStart(&audio_formatter);
 	printf("[adau] XAudioFormatterDMAStart done.\n");
+}
+
+// returns 1 if adau1701 found, otherwise 0
+// set audio_tx_buffer and audio_rx_buffer before!
+int audio_adau_init(int program_dsp) {
+	XIicPs_Config* i2c_config;
+	i2c_config = XIicPs_LookupConfig(IIC2_DEVICE_ID);
+	int status = XIicPs_CfgInitialize(&Iic2, i2c_config, i2c_config->BaseAddress);
+	printf("[adau] XIicPs_CfgInitialize 2: %d\n", status);
+	usleep(10000);
+	printf("[adau] XIicPs 2 is ready: %lx\n", Iic2.IsReady);
+	status = XIicPs_SelfTest(&Iic2);
+	printf("[adau] XIicPs_SelfTest: %x\n", status);
+
+	if (status != 0) {
+		printf("[adau] I2C instance 2 self test failed.");
+		return 0;
+	}
+
+	status = XIicPs_SetSClk(&Iic2, IIC2_SCLK_RATE);
+	printf("[adau] XIicPs_SetSClk: %x\n", status);
+
+	u8 rbuf[5];
+	u8 i = 0x34;
+
+	//usleep(10000);
+	// DSP core control: set ADM, DAM, CR
+	status = adau_write16(i, 2076, (1<<4)|(1<<3)|(1<<2));
+	if (status == 0) {
+		printf("[adau] write DSP core control: %d\n", i);
+		printf("\n[adau] ~~~~ ZZ9000AX detected. ~~~~\n\n");
+	} else {
+		printf("[adau] ZZ9000AX not detected.\n");
+		return 0;
+	}
+
+	status = adau_read16(i, 2076, rbuf);
+	if (status == 0) {
+		printf("[adau] read: %d %x %x\n", i, rbuf[0], rbuf[1]);
+	}
+
+	// DAC setup: DS = 01
+	status = adau_write16(i, 2087, 1);
+	printf("[adau] write DAC setup: %d\n", status);
+
+	rbuf[0] = 0;
+	rbuf[1] = 0;
+
+	status = adau_read16(i, 2087, rbuf);
+	printf("[adau] read from 2087: %02x%02x (status: %d)\n", rbuf[0], rbuf[1], status);
+
+	if (program_dsp) {
+		program_adau(Program_Data_IC_1, sizeof(Program_Data_IC_1), Param_Data_IC_1, sizeof(Param_Data_IC_1));
+	}
+
+	// TODO: OBP/OLRP
+	u16 MS  = 1<<11; // clock master output
+	//u16 OBF = (0<<10)|(0<<9);    // bclock = 49.152/16 = mclk/4 = 3.072mhz
+	u16 OBF = (1<<10)|(0<<9);    // bclock = 49.152/4 = mclk = 12.288mhz
+	u16 OLF = (0<<8)|(0<<7);    // lrclock = 49.152/1024 = word clock = 48khz?!
+	u16 MSB = 0;    // msb 1
+	u16 OWL = 1<<1; // 16 bit
+	status = adau_write16(i, 0x081e, MS|OBF|OLF|MSB|OWL);
+	printf("[adau] write serial output control: %d\n", status);
+
+	u32 MP0 = 1<<2; // MP02 digital input 0
+	u32 MP1 = 1<<6; //
+	u32 MP2 = 1<<10; //
+	u32 MP3 = 1<<14; //
+	u32 MP4 = 1<<18; // MP42 serial clock in
+	u32 MP5 = 1<<22; // MP52 serial clock in
+
+	u32 MP6 = 1<<2; //
+	u32 MP7 = 1<<6; //
+	u32 MP8 = 1<<10; //
+	u32 MP9 = 1<<14; //
+	u32 MP10 = 1<<18; // MP102 set (serial clock out)
+	u32 MP11 = 1<<22; // MP112 set (serial clock out)
+	status = adau_write24(i, 0x0820, MP0|MP1|MP2|MP3|MP4|MP5);
+	printf("[adau] write MP control 0x820: %d\n", status);
+	status = adau_write24(i, 0x0821, MP6|MP7|MP8|MP9|MP10|MP11);
+	printf("[adau] write MP control 0x821: %d\n", status);
+
+	status = adau_read24(i, 0x0820, rbuf);
+	printf("[adau] read from 0x820: %02x%02x%02x (status: %d)\n", rbuf[0], rbuf[1], rbuf[2], status);
+	status = adau_read24(i, 0x0821, rbuf);
+	printf("[adau] read from 0x821: %02x%02x%02x (status: %d)\n", rbuf[0], rbuf[1], rbuf[2], status);
+
+	audio_init_i2s();
 
 	return 1;
 }
@@ -372,6 +385,20 @@ int audio_adau_init(uint32_t* audio_buffer) {
 static int interrupt_enabled_audio = 0;
 static uint32_t interrupt_waiting_audio = 0;
 
+XTime debug_time_start = 0;
+
+void audio_debug_timer(int zdata) {
+	if (zdata == 0) {
+		XTime_GetTime(&debug_time_start);
+	} else {
+		XTime debug_time_stop;
+		XTime_GetTime(&debug_time_stop);
+		printf("%x;%09.2f us\n", (uint8_t)zdata,
+				1.0 * (debug_time_stop-debug_time_start) / (COUNTS_PER_SECOND/1000000));
+		XTime_GetTime(&debug_time_start);
+	}
+}
+
 int isra_count = 0;
 
 // audio formatter interrupt, triggered whenever a period is completed
@@ -381,16 +408,17 @@ void isr_audio(void *dummy) {
 	XAudioFormatter_WriteReg(XPAR_XAUDIOFORMATTER_0_BASEADDR,
 		XAUD_FORMATTER_STS + XAUD_FORMATTER_MM2S_OFFSET, val);
 
-	if (isra_count++>100) {
+	if (isra_count++>1000) {
 		printf("[isra]\n");
 		isra_count = 0;
 	}
 
 	if (interrupt_enabled_audio) {
+		audio_debug_timer(0);
+
 		interrupt_waiting_audio = 1;
 
 		mntzorro_write(MNTZ_BASE_ADDR, MNTZORRO_REG2, (1 << 30) | 1);
-		usleep(1);
 		mntzorro_write(MNTZ_BASE_ADDR, MNTZORRO_REG2, (1 << 30) | 0);
 	} else {
 		interrupt_waiting_audio = 0;
@@ -402,6 +430,7 @@ uint32_t audio_get_interrupt() {
 }
 
 void audio_clear_interrupt() {
+	//printf("[clear] audio\n");
 	interrupt_waiting_audio = 0;
 }
 
@@ -414,7 +443,7 @@ void isr_audio_rx(void *dummy) {
 	XAudioFormatter_WriteReg(XPAR_XAUDIOFORMATTER_1_BASEADDR,
 		XAUD_FORMATTER_STS + XAUD_FORMATTER_S2MM_OFFSET, val);
 
-	if (israrx_count++>100) {
+	if (israrx_count++>1000) {
 		printf("[isra_rx]\n");
 		israrx_count = 0;
 	}
@@ -427,45 +456,96 @@ uint32_t audio_get_dma_transfer_count() {
 void audio_set_interrupt_enabled(int en) {
 	printf("[audio] enable irq: %d\n", en);
 	interrupt_enabled_audio = en;
+
+	audio_silence();
 }
 
 // offset = offset from audio tx buffer
 // returns audio_buffer_collision (1 or 0)
-int audio_swab(int audio_scale, uint32_t offset) {
+int audio_swab(int audio_buf_samples, uint32_t offset, int byteswap) {
 	int audio_buffer_collision = 0;
-	uint16_t* data = (uint16_t*)(((void*)AUDIO_TX_BUFFER_ADDRESS)+offset);
-
-	if (audio_scale > 1) {
-		// for lower freqs that are divisions of 48000Hz
-		int k = (3840/2)/audio_scale-1;
-		for (int i=3840/2-audio_scale; i>=0; i-=audio_scale) {
-			uint16_t s = __builtin_bswap16(data[k]);
-			data[i] = s;
-			for (int j=1; j<audio_scale; j++) {
-				data[i+j] = s;
-			}
-			k--;
-		}
-	} else {
-		// 48000Hz
-		for (int i=0; i<3840/2; i++) {
+	uint16_t* data = (uint16_t*)(audio_tx_buffer + offset);
+	int audio_freq = audio_buf_samples * 50;
+
+	//printf("[audio:%d] play: %d +%lu\n", byteswap, audio_freq, offset);
+
+	// byteswap
+	if (byteswap) {
+		for (int i=0; i < audio_buf_samples * 2; i++) {
 			data[i] = __builtin_bswap16(data[i]);
 		}
 	}
 
+	// FIXME missing filter, wonky address calculation
+	// resample if other freq
+	if (audio_freq != 48000) {
+		resample_s16((int16_t*)(audio_tx_buffer + offset),
+				(int16_t*)(audio_tx_buffer+0x20000), audio_freq, 48000, audio_buf_samples);
+		memcpy(audio_tx_buffer + offset, audio_tx_buffer+0x20000, AUDIO_BYTES_PER_PERIOD);
+	}
+
 	u32 txcount = audio_get_dma_transfer_count();
 
 	// is the distance of reader (audio dma) and writer (amiga) in the ring buffer too small?
 	// then signal this condition so amiga can adjust
-	if (abs(txcount-offset) < 3840) {
+	if (abs(txcount-offset) < AUDIO_BYTES_PER_PERIOD) {
 		audio_buffer_collision = 1;
-		printf("[aswap] ring collision %d\n", abs(txcount-offset));
+		//printf("[aswap] ring collision %d\n", abs(txcount-offset));
 	} else {
 		audio_buffer_collision = 0;
 	}
 
-	printf("[aswap] d-a: %ld scl: %d\n",txcount-offset,audio_scale);
+	if (audio_buffer_collision) {
+		printf("[aswap] d-a: %ld\n",txcount-offset);
+	}
 
 	return audio_buffer_collision;
 }
 
+uint32_t resample_s16(int16_t *input, int16_t *output,
+		int inSampleRate, int outSampleRate, uint32_t inputSize) {
+    const uint32_t channels = 2;
+
+    //printf("[resample] %p -> %p (%d -> %d) %lu bytes\n",
+    //		input, output, inSampleRate, outSampleRate, inputSize);
+
+    uint32_t outputSize = 3840/4; // (uint32_t) (inputSize * (double) outSampleRate / (double) inSampleRate);
+    //outputSize -= outputSize % channels;
+
+    double stepDist = ((double) inSampleRate / (double) outSampleRate);
+    const uint64_t fixedFraction = (1LL << 32);
+    const double normFixed = (1.0 / (1LL << 32));
+    uint64_t step = ((uint64_t) (stepDist * fixedFraction + 0.5));
+    uint64_t curOffset = 0;
+
+    // HACK: glue at the end
+    input[inputSize*2]   = input[(inputSize-1)*2];
+    input[inputSize*2+1] = input[(inputSize-1)*2+1];
+
+    for (uint32_t i = 0; i < outputSize; i++) {
+        for (uint32_t c = 0; c < channels; c++) {
+            *output++ = (int16_t) (input[c] + (input[c + channels] - input[c]) * (
+                    (double) (curOffset >> 32) + ((curOffset & (fixedFraction - 1)) * normFixed)));
+        }
+        curOffset += step;
+        input += (curOffset >> 32) * channels;
+        curOffset &= (fixedFraction - 1);
+    }
+    return outputSize;
+}
+
+void audio_set_tx_buffer(uint8_t* addr) {
+	printf("[audio] set tx buffer: %p\n", addr);
+	audio_tx_buffer = addr;
+	audio_init_i2s();
+}
+
+void audio_set_rx_buffer(uint8_t* addr) {
+	printf("[audio] set rx buffer: %p\n", addr);
+	audio_rx_buffer = addr;
+	audio_init_i2s();
+}
+
+void audio_silence() {
+	memset(audio_tx_buffer, 0, AUDIO_TX_BUFFER_SIZE);
+}
diff --git a/ZZ9000_proto.sdk/ZZ9000OS/src/ax.h b/ZZ9000_proto.sdk/ZZ9000OS/src/ax.h
index 9bb074831a484f2c7f530ffe0f0bd3211ae4a3cf..1956fb1efff8458cc8bd0c9972a348290c0848ce 100644
--- a/ZZ9000_proto.sdk/ZZ9000OS/src/ax.h
+++ b/ZZ9000_proto.sdk/ZZ9000OS/src/ax.h
@@ -1,10 +1,14 @@
 #include <stdint.h>
 
-int audio_adau_init(uint32_t* audio_buffer);
+int audio_adau_init(int program_dsp);
 void isr_audio(void *dummy);
 void isr_audio_rx(void *dummy);
 void audio_set_interrupt_enabled(int en);
 void audio_clear_interrupt();
 uint32_t audio_get_interrupt();
 uint32_t audio_get_dma_transfer_count();
-int audio_swab(int audio_scale, uint32_t offset);
+int audio_swab(int audio_buf_samples, uint32_t offset, int byteswap);
+void audio_set_tx_buffer(uint8_t* addr);
+void audio_set_rx_buffer(uint8_t* addr);
+uint32_t resample_s16(int16_t *input, int16_t *output, int inSampleRate, int outSampleRate, uint32_t inputSize);
+void audio_silence();
diff --git a/ZZ9000_proto.sdk/ZZ9000OS/src/main.c b/ZZ9000_proto.sdk/ZZ9000OS/src/main.c
index 84d2da7c99fcadeac9e8d4c45b09266f1738a710..195cc3bd3e63f6321f51c53d839e54aadb9c4117 100644
--- a/ZZ9000_proto.sdk/ZZ9000OS/src/main.c
+++ b/ZZ9000_proto.sdk/ZZ9000OS/src/main.c
@@ -71,7 +71,7 @@ void disable_reset_out() {
 	XGpioPs_WritePin(&Gpio, output_pin, 0);
 	usleep(10000);
 	XGpioPs_WritePin(&Gpio, output_pin, 1);
-	print("[gpio] ethernet reset done.\n");
+	print("[gpio] ethernet reset done.\r\n");
 
 	// FIXME
 	int adau_reset = 11;
@@ -81,7 +81,7 @@ void disable_reset_out() {
 	usleep(10000);
 	XGpioPs_WritePin(&Gpio, adau_reset, 1);
 
-	print("[gpio] ADAU reset done.\n");
+	print("[gpio] ADAU reset done.\r\n");
 }
 
 u32 blitter_colormode = MNTVA_COLOR_32BIT;
@@ -102,9 +102,8 @@ static uint32_t usb_storage_write_block = 0;
 
 // ethernet state
 uint16_t ethernet_send_result = 0;
-static int backlog_nag_counter = 0;
-static int interrupt_enabled_ethernet = 0;
-static int interrupt_waiting_ethernet = 0;
+int eth_backlog_nag_counter = 0;
+int interrupt_waiting_ethernet = 0;
 
 // usb state
 uint16_t usb_status = 0;
@@ -114,7 +113,8 @@ uint32_t debug_lowlevel = 0;
 
 // audio state (ZZ9000AX)
 static int audio_buffer_collision = 0;
-static uint32_t audio_scale = 1;
+static uint32_t audio_scale = 48000/50;
+static uint32_t audio_offset = 0;
 static int adau_enabled = 0;
 
 void handle_amiga_reset() {
@@ -127,6 +127,11 @@ void handle_amiga_reset() {
 
 	video_reset();
 
+	// stop audio
+	audio_set_tx_buffer((uint8_t*)AUDIO_TX_BUFFER_ADDRESS);
+	audio_silence();
+	audio_set_rx_buffer((uint8_t*)AUDIO_RX_BUFFER_ADDRESS);
+
 	// usb
 	usb_storage_available = zz_usb_init();
 	usb_status = 0;
@@ -134,8 +139,8 @@ void handle_amiga_reset() {
 
 	// ethernet
 	ethernet_send_result = 0;
-	backlog_nag_counter = 0;
-	interrupt_enabled_ethernet = 0;
+	eth_backlog_nag_counter = 0;
+	video_state->interrupt_enabled_ethernet = 0;
 
 	// FIXME document
 	cur_mem_offset = 0x3500000;
@@ -154,6 +159,8 @@ void handle_amiga_reset() {
 		f-=0.0001;
 	}*/
 
+	adau_enabled = audio_adau_init(1);
+
 	// clear interrupt holding amiga
 	mntzorro_write(MNTZ_BASE_ADDR, MNTZORRO_REG2, (1 << 30) | 0);
 
@@ -168,7 +175,7 @@ int main() {
 
 	disable_reset_out();
 
-	video_init();
+	video_state = video_init();
 
 	xadc_init();
 
@@ -178,8 +185,6 @@ int main() {
 
 	handle_amiga_reset();
 
-	adau_enabled = audio_adau_init((uint32_t*)AUDIO_TX_BUFFER_ADDRESS);
-
 	fpga_interrupt_connect(isr_video, isr_audio, isr_audio_rx);
 
 	// ARM app run environment
@@ -210,6 +215,11 @@ int main() {
 	u32 zstate_raw;
 	int need_req_ack = 0;
 
+	// audio parameters (buffer locations)
+	const int ZZ_NUM_AUDIO_PARAMS = 4;
+	uint16_t audio_params[ZZ_NUM_AUDIO_PARAMS];
+	int audio_param = 0; // selected parameter
+
 	// decoder parameters (mp3 etc)
 	const int ZZ_NUM_DECODER_PARAMS = 8;
 	uint16_t decoder_params[ZZ_NUM_DECODER_PARAMS];
@@ -329,7 +339,7 @@ int main() {
 					if (zdata & 8) {
 						// clear/ack
 						if (zdata & 16) {
-							//printf("[clear] eth\n");
+							printf("[clear] eth\n");
 							interrupt_waiting_ethernet = 0;
 						}
 						if (zdata & 32) {
@@ -337,7 +347,7 @@ int main() {
 						}
 					} else {
 						printf("[enable] eth: %d\n", (int)zdata);
-						interrupt_enabled_ethernet = zdata & 1;
+						video_state->interrupt_enabled_ethernet = zdata & 1;
 					}
 					break;
 				case REG_ZZ_MODE: {
@@ -754,7 +764,10 @@ int main() {
 				}
 				case REG_ZZ_DEBUG: {
 					debug_lowlevel = zdata;
-
+					break;
+				}
+				case REG_ZZ_DEBUG_TIMER: {
+					audio_debug_timer(zdata);
 					break;
 				}
 				case REG_ZZ_PRINT_CHR: {
@@ -813,14 +826,41 @@ int main() {
 					break;
 				case REG_ZZ_AUDIO_SWAB:
 					{
-						// byteswap audio buffer
-						uint32_t offset = zdata<<8; // *256
-						audio_buffer_collision = audio_swab(audio_scale, offset);
+						int byteswap = 1;
+						if (zdata&(1<<15)) byteswap = 0;
+						audio_offset = (zdata&0x7fff)<<8; // *256
+						audio_buffer_collision = audio_swab(audio_scale, audio_offset, byteswap);
+
 						break;
 					}
 				case REG_ZZ_AUDIO_SCALE:
 					audio_scale = zdata;
 					break;
+				case REG_ZZ_AUDIO_PARAM:
+					// DECODER PARAMS:
+					// 0: tx buffer offset hi
+					// 1: tx buffer offset lo
+					// 2: rx buffer offset hi
+					// 3: rx buffer offset lo
+
+					if (zdata<ZZ_NUM_AUDIO_PARAMS) {
+						audio_param = zdata;
+					} else {
+						audio_param = 0;
+					}
+					break;
+				case REG_ZZ_AUDIO_VAL:
+					audio_params[audio_param] = zdata;
+					if (audio_param == 1) {
+						uint8_t* addr = (uint8_t*)video_state->framebuffer +
+								((audio_params[0]<<16)|audio_params[1]);
+						audio_set_tx_buffer(addr);
+					} else if (audio_param == 3) {
+						uint8_t* addr = (uint8_t*)video_state->framebuffer +
+								((audio_params[2]<<16)|audio_params[3]);
+						audio_set_rx_buffer(addr);
+					}
+					break;
 				case REG_ZZ_DECODER_PARAM:
 					if (zdata<ZZ_NUM_DECODER_PARAMS) {
 						decoder_param = zdata;
@@ -851,15 +891,30 @@ int main() {
 								+ ((decoder_params[4]<<16)|decoder_params[5]);
 						size_t output_buffer_size = (decoder_params[6]<<16)|decoder_params[7];
 
-						printf("[decode:mp3] %p (%x) -> %p (%x)\n", input_buffer, input_buffer_size,
-								output_buffer, output_buffer_size);
+						if (zdata == 0) {
+							printf("[decode:mp3:%d] %p (%x) -> %p (%x)\n", (int)zdata, input_buffer, input_buffer_size,
+									output_buffer, output_buffer_size);
+
+							decode_mp3_init(input_buffer, input_buffer_size);
+						} else {
+							int max_samples = output_buffer_size;
+							int mp3_freq = mp3_get_hz();
+							if (mp3_freq < 48000) {
+								uint8_t* temp_buffer = output_buffer + 8*3840; // FIXME hack
+								max_samples = mp3_get_hz()/50*2;
+								//printf("[mp3] f: %d max: %d\n", mp3_get_hz(), max_samples);
+
+								decode_mp3_samples(temp_buffer, max_samples);
 
-						decode_mp3(input_buffer, input_buffer_size, output_buffer, output_buffer_size);
+								// resample
+								resample_s16((int16_t*)temp_buffer, (int16_t*)output_buffer,
+										mp3_get_hz(), 48000, max_samples/2);
 
-						uint16_t* data = (uint16_t*)output_buffer;
-						for (int i=0; i<output_buffer_size/2; i++) {
-							data[i] = __builtin_bswap16(data[i]);
+							} else {
+								decode_mp3_samples(output_buffer, max_samples);
+							}
 						}
+
 						break;
 					}
 				}
@@ -1004,10 +1059,9 @@ int main() {
 			need_req_ack = 2;
 		} else {
 			// there are no read/write requests, we can do other housekeeping
-
 			ethernet_task();
 
-			if (zstate == 0) {
+			if ((zstate & 0xff) == 0) {
 				// RESET
 				handle_amiga_reset();
 			}
@@ -1036,14 +1090,14 @@ int main() {
 
 		// check for queued up ethernet frames
 		int ethernet_backlog = ethernet_get_backlog();
-		if (ethernet_backlog > 0 && backlog_nag_counter > 5000) {
+		if (ethernet_backlog > 0 && eth_backlog_nag_counter > 5000) {
 			video_state->interrupt_signal_ethernet = 1;
 			interrupt_waiting_ethernet = 1;
-			backlog_nag_counter = 0;
+			eth_backlog_nag_counter = 0;
 		}
 
-		if (interrupt_enabled_ethernet && ethernet_backlog > 0) {
-			backlog_nag_counter++;
+		if (video_state->interrupt_enabled_ethernet && ethernet_backlog > 0) {
+			eth_backlog_nag_counter++;
 		}
 	}
 
diff --git a/ZZ9000_proto.sdk/ZZ9000OS/src/memorymap.h b/ZZ9000_proto.sdk/ZZ9000OS/src/memorymap.h
index 98d5b760d204443ef09eda6da2163a306bb3e84d..5d48dbc85559adc26c5a3739a95f426d4ca3e223 100644
--- a/ZZ9000_proto.sdk/ZZ9000OS/src/memorymap.h
+++ b/ZZ9000_proto.sdk/ZZ9000OS/src/memorymap.h
@@ -5,13 +5,13 @@
 #define AUDIO_BYTES_PER_PERIOD      3840
 
 #define FRAMEBUFFER_ADDRESS         0x00200000
-#define AUDIO_TX_BUFFER_ADDRESS     0x00200000
 #define AUDIO_TX_BUFFER_SIZE        (AUDIO_BYTES_PER_PERIOD * AUDIO_NUM_PERIODS)
-#define AUDIO_RX_BUFFER_ADDRESS     0x00220000  // FIXME
 
-#define Z3_SCRATCH_ADDR             0x033F0000  // FIXME
-#define ADDR_ADJ                    0x001F0000
+#define Z3_SCRATCH_ADDR             0x033F0000 // FIXME @ _Bnu
+#define ADDR_ADJ                    0x001F0000 // FIXME @ _Bnu
 
+#define AUDIO_TX_BUFFER_ADDRESS     0x3FC00000 // default, changed by driver
+#define AUDIO_RX_BUFFER_ADDRESS     0x3FC20000 // default, changed by driver
 #define TX_BD_LIST_START_ADDRESS    0x3FD00000
 #define RX_BD_LIST_START_ADDRESS    0x3FD08000
 #define TX_FRAME_ADDRESS            0x3FD10000
diff --git a/ZZ9000_proto.sdk/ZZ9000OS/src/mp3/decode_mp3.c b/ZZ9000_proto.sdk/ZZ9000OS/src/mp3/decode_mp3.c
index d4e9e98445e84aed198400994089c590266026f4..f703500bca914ce9d5298e4aea6be847f2700b43 100644
--- a/ZZ9000_proto.sdk/ZZ9000OS/src/mp3/decode_mp3.c
+++ b/ZZ9000_proto.sdk/ZZ9000OS/src/mp3/decode_mp3.c
@@ -3,38 +3,60 @@
 #define MINIMP3_IMPLEMENTATION 1
 #include "minimp3_ex.h"
 
-// TODO: package all minimp3 dependencies in this file
-// TODO: enforce output format 16 bit signed integer samples, stereo interleaved, 48000Hz (first step: check if minimp3 can do all the necessary conversions by itself; for re-sampling consider something like libsamplerate?)
-// TODO: adapt decode_mp3 interface to more specific requirements ("call a decode_mp3() function via a ZZ9000 register giving source and dest (uint32_t) pointers")
-// TODO: status register where Amiga can pull result (OK/Error/busy)
-
-int decode_mp3(unsigned char * input_buffer, size_t input_buffer_size, unsigned char * output_buffer, size_t output_buffer_size) {
-	mp3dec_ex_t mp3d;
-	mp3dec_frame_info_t frame_info;
-	memset(&frame_info, 0, sizeof(frame_info));
-	// // sets up input_buffer as mp3d->file.buffer
-	int ret = mp3dec_ex_open_buf(&mp3d, input_buffer, input_buffer_size, MP3D_DO_NOT_SCAN);
-	if (ret) {
-		printf("mp3dec_ex_open_buf failed: %d\n", ret);
-		exit(ret);
-	}
-	size_t offset_new_samples = 0;
-	int max_samples = UINT_MAX;
-	memset(output_buffer, 0, output_buffer_size);
+static mp3dec_ex_t mp3d;
+static mp3dec_frame_info_t frame_info;
+
+int decode_mp3_samples(void* output_buffer, int max_samples) {
+	int max_bytes = max_samples * 2;
+	int out_offset = 0;
+	int total_bytes_decoded = 0;
+
+	//printf("[mp3] out_offset: %d max_bytes: %d\n", out_offset, max_bytes);
+
+	// this will point into mp3d->buffer, which is defined on the stack
+	// as mp3d_sample_t buffer[MINIMP3_MAX_SAMPLES_PER_FRAME]
+	mp3d_sample_t * pcm_buffer = NULL;
+
 	while (1) {
-		// this will point into mp3d->buffer, which is defined on the stack
-		// as mp3d_sample_t buffer[MINIMP3_MAX_SAMPLES_PER_FRAME]
-		mp3d_sample_t * pcm_buffer = NULL;
 		size_t read_samples = mp3dec_ex_read_frame(&mp3d, &pcm_buffer, &frame_info, max_samples);
-		if (!read_samples) {
-			break;
-		}
-		if (offset_new_samples + (read_samples * sizeof(mp3d_sample_t)) >= output_buffer_size) {
+		max_samples -= read_samples;
+
+		int bytes_decoded = read_samples * sizeof(mp3d_sample_t);
+		total_bytes_decoded += bytes_decoded;
+
+		//printf("[mp3] decoded: %d bytes\n", bytes_decoded);
+
+		if (bytes_decoded > 0) {
+			int bytes_to_copy = bytes_decoded;
+			memcpy(output_buffer + out_offset, pcm_buffer, bytes_to_copy);
+			out_offset += bytes_decoded;
+
+			if (out_offset >= max_bytes) {
+				break;
+			}
+		} else {
 			break;
 		}
-		size_t new_frame_size = read_samples * sizeof(mp3d_sample_t);
-		memcpy(output_buffer + offset_new_samples, pcm_buffer, new_frame_size);
-		offset_new_samples += read_samples * sizeof(mp3d_sample_t);
 	}
-	return offset_new_samples;
+
+	return total_bytes_decoded;
+}
+
+int decode_mp3_init(uint8_t* input_buffer, size_t input_buffer_size) {
+	memset(&frame_info, 0, sizeof(frame_info));
+
+	// sets up input_buffer as mp3d->file.buffer
+	int ret = mp3dec_ex_open_buf(&mp3d, input_buffer, input_buffer_size, MP3D_DO_NOT_SCAN);
+	if (ret) {
+		printf("mp3dec_ex_open_buf failed: %d\n", ret);
+	}
+	return ret;
+}
+
+int mp3_get_hz() {
+	return mp3d.info.hz;
+}
+
+int mp3_get_channels() {
+	return mp3d.info.channels;
 }
diff --git a/ZZ9000_proto.sdk/ZZ9000OS/src/mp3/mp3.h b/ZZ9000_proto.sdk/ZZ9000OS/src/mp3/mp3.h
index bdb98242912b3ec9a11f77c13a4e75232cf80700..792578a9fdf0357a93f1261f681af9b76f6babae 100644
--- a/ZZ9000_proto.sdk/ZZ9000OS/src/mp3/mp3.h
+++ b/ZZ9000_proto.sdk/ZZ9000OS/src/mp3/mp3.h
@@ -1 +1,4 @@
-int decode_mp3(unsigned char * input_buffer, size_t input_buffer_size, unsigned char * output_buffer, size_t output_buffer_size);
+int decode_mp3_init(uint8_t* input_buffer, size_t input_buffer_size);
+int decode_mp3_samples(void* output_buffer, int max_samples);
+int mp3_get_hz();
+int mp3_get_channels();
diff --git a/ZZ9000_proto.sdk/ZZ9000OS/src/video.c b/ZZ9000_proto.sdk/ZZ9000OS/src/video.c
index b805b45e0612b02bef123b51879b9cd644913b77..d47d4b18a9855834ea2c0f5ab6f273cee4f03f77 100644
--- a/ZZ9000_proto.sdk/ZZ9000OS/src/video.c
+++ b/ZZ9000_proto.sdk/ZZ9000OS/src/video.c
@@ -61,7 +61,7 @@ struct ZZ_VIDEO_STATE* video_get_state() {
 	return &vs;
 }
 
-void video_init() {
+struct ZZ_VIDEO_STATE* video_init() {
 	vs.framebuffer = (u32*) FRAMEBUFFER_ADDRESS;
 
 	// default to more compatible 60hz mode
@@ -70,6 +70,8 @@ void video_init() {
 	vs.colormode = 0;
 
 	video_reset();
+
+	return video_get_state();
 }
 
 void video_reset() {
@@ -326,7 +328,6 @@ void isr_video(void *dummy) {
 		if ((vs.interrupt_signal_ethernet && vs.interrupt_enabled_ethernet)) {
 			// interrupt amiga (trigger int6/2)
 			mntzorro_write(MNTZ_BASE_ADDR, MNTZORRO_REG2, (1 << 30) | 1);
-			usleep(1);
 			mntzorro_write(MNTZ_BASE_ADDR, MNTZORRO_REG2, (1 << 30) | 0);
 
 			// FIXME legacy behavior
diff --git a/ZZ9000_proto.sdk/ZZ9000OS/src/video.h b/ZZ9000_proto.sdk/ZZ9000OS/src/video.h
index 87cca1abd118519eec31a8a621c3a00d0d6bbd90..0a93e9b4c4dbd3ec9207e4a50e9f8a5226f3a207 100644
--- a/ZZ9000_proto.sdk/ZZ9000OS/src/video.h
+++ b/ZZ9000_proto.sdk/ZZ9000OS/src/video.h
@@ -64,7 +64,7 @@ struct ZZ_VIDEO_STATE {
 	int interrupt_signal_ethernet;
 };
 
-void video_init();
+struct ZZ_VIDEO_STATE* video_init();
 void video_reset();
 void isr_video(void *dummy);
 void video_mode_init(int mode, int scalemode, int colormode);
diff --git a/ZZ9000_proto.sdk/ZZ9000OS/src/zz_regs.h b/ZZ9000_proto.sdk/ZZ9000OS/src/zz_regs.h
index dc3d7574c3d6b6820f8adba4f8e3c84fa637b989..36bfc0dd6b53152d91b3688a5359a0fbd8885275 100644
--- a/ZZ9000_proto.sdk/ZZ9000OS/src/zz_regs.h
+++ b/ZZ9000_proto.sdk/ZZ9000OS/src/zz_regs.h
@@ -85,11 +85,11 @@ enum zz_reg_offsets {
   REG_ZZ_AUDIO_SWAB     = 0x70,
   REG_ZZ_UNUSED_REG72   = 0x72,
   REG_ZZ_AUDIO_SCALE    = 0x74,
-  REG_ZZ_DECODE         = 0x76,
-  REG_ZZ_DECODER_PARAM  = 0x78,
-  REG_ZZ_DECODER_VAL    = 0x7A,
-  REG_ZZ_UNUSED_REG7C   = 0x7C,
-  REG_ZZ_UNUSED_REG7E   = 0x7E,
+  REG_ZZ_AUDIO_PARAM    = 0x76,
+  REG_ZZ_AUDIO_VAL      = 0x78,
+  REG_ZZ_DECODER_PARAM  = 0x7A,
+  REG_ZZ_DECODER_VAL    = 0x7C,
+  REG_ZZ_DECODE         = 0x7E,
 
   REG_ZZ_ETH_TX         = 0x80,
   REG_ZZ_ETH_RX         = 0x82,
@@ -161,7 +161,7 @@ enum zz_reg_offsets {
   REG_ZZ_UNUSED_REGF8   = 0xF8,
   REG_ZZ_UNUSED_REGFA   = 0xFA,
   REG_ZZ_DEBUG          = 0xFC,
-  REG_ZZ_UNUSED_REGFE   = 0xFE,
+  REG_ZZ_DEBUG_TIMER    = 0xFE,
 };
 
 enum zz9k_card_features {