Close

History of 1-4

A project log for Project 72 - Korg DW-6000 wave memory expansion

An attempt to reverse engineer and modify Korg DW-6000s firmware in order to expand its wave memory.

mateuszkolanskimateusz.kolanski 07/30/2017 at 14:270 Comments

So, it's been a long time (almost a year), but I finally got back to my little project and done some progress. If you know DW6000, or if your perception level is at least somewhere round 7, you will see that on the picture I posted yesterday I dialed in the new, non existant parameter 14. So, here's the story:

Before I even started further reverse engineering, I got in touch with Alfred Arnold (creator of the assembler I'm using) and asked him a few questions, because I wanted to get rid of any hardcoded addresses in jump tables. At the beginning I wanted to write a macro which would convert any label into either high or low byte and replace it with a nice DB. Long story short - the answer was pretty simple - all I needed to do was to use DWs instead. I didn't think about it first, because it seemed too easy:)

before:

.TBL03:	TABLE
	JB	
	DB    29H
        DB    14H

after: 

.TBL03:	TABLE
	JB	
	DW    .1429H    ; that's an autogenerated label pointing 
                        ; (originally) to 0x1429h

Then I fired up my emulator again (I didn't change anything in it, it just works) and started with setting a watchpoint which would break the execution if anything tries to read the data from any of both of the tables (more on them later). That brought me somewhat closer to what I wanted to do - I ended up knowing more or less where's the code which reads the data from the tables and does the stuff with it.

Next I just started to change parameter numbers and/or parameter values (incrementing or decrementing them) and just looking into memory window searching for some patterns. And I finally found something: two offsets which are always taking the parameter's value. Another watchpoint set and... bingo! With some backtracking I have finally found the code I was looking for.

Now, a word about the data tables. There are two tables (related to synthesizer's parameters) and in order to modify the code I had to understand precisely what kind of data is stored in them. The first one is 144 bytes (48x3) long. Each entry represents a parameter number (11..16, 21..26 and so on). 

The 1st byte holds the information about parameter offset (bits 3-7) relative to the beginning of each patch and its beginning bit (bits 0-2). That makes more sense if you just take a look at the DW6k's service manual (page 6, DW-6000 bit map).

The high nibble (4-7) of the second byte is a numeric value (0-9) which multiplied by 4 (size of an entry in the second table) gives an offset to the value from the second table. Just a relative pointer to say so. The low nibble holds a value between 0 and 3 which selects an appropriate display subroutine:
- 0: "normal" 2 digit (max) value (e.g. 0-31)
- 1: "normal" 2 digit (max) value, incremented (e.g. 0-31 displayed as 1-32)
- 2: value from 0 to 2 with translation -> 0=16, 1=8, 2=4 (octave selection)
- 3: value from 0 to 4 with translation -> 0=1, 1=-3, 2=3, 3=4, 4=5 (interval selection)

The last byte is kind of an index of each parameter with a small twist - "local" (per pach) parameters go from 0x00 to 0x21, "global" parameters (81-83) from 0xF0 to 0xF2 and invalid parameters (like our 14)  are marked with 0xFF.

The 2nd table isn't so exciting - the 1st byte is the maximum parameter's value, the second one is the bit mask to be applied on the value (after bit shifting) to get the desired value (hope you know what I mean:)). The 3rd one - I have no idea whatsoever, but it hasn't been used in any code which looks interesting to me, so let's just skip it. The last byte is another bitmask used to get or set the value.

Knowing all of that I took a look at the bit map again to find out where I could store my new parameter. Unfortunately there's no continuous 4 bit space, so I had to use only 3 bits and extend the memory by 8 banks (not 16 as I planned before). The second byte which holds the value of portamento time consumes only 5 bits, so I can use the remaining 3 for my purposes: offset = 1, start bit = 5 so my first byte is 0x0D. Then I started looking for any other 3-bit parameter that sits on the top of some other one to see which entry from the second table should I take for my purposes. The 5th one looks good. Now the display subroutine. I think that I will use the incremented one, so the resulting byte will be 0x51. Now we need to assign an index value and the next empty one is 0x22. Ok, so far so good. One more thing I needed to do was to find any instructions looking for the top patch's number and increment it to be able to select my new one.

Quick assembly, start the emulator and it looks really nice! I can dial-in 1-4 and set the value from 1 to 8, sweet!

Now it's time to handle the new parameter. That was actually quite easy - after finding a place in code which handles the other functions all I had to do was to modify it:

.11C9H:	LTI	E, 23H		; local parameter? (changed from 0x22 to 0x23)
	JRE	.11F1H		; nope
	MVIW	0C3H, 0E0H
	MOV	A, E	
	STAW	0C4H		; store A (params' ID) into $26C4
	NEI	E, 0EH		; ID == $0E? (param 3-6, chorus)
	JR	.11E5H		; go to chorus routine
        ; new code below
	NEI	E, 22H		; ID == 22? (1-4 ,select bank)
	JMP	.BANKSWITCH	; go to bank switch routine
        ; common instructions for all local parameters

 Easy, huh? Let's write some code then:

.BANKSWITCH:	MOV	A, D	; bank number (0-7) stored in D, copy to A
		SLL	A	; store in high nibble
		SLL	A
		SLL	A
		SLL	A
		MOV	B, A	; store in B
		DI		; enter critical section
		MOV	A, PB	; get current PB
		ANI	A, 0FH	; wipe high nibble
		ORA	A, B	; join together
		MOV	PB, A	; speak to hardware
		XRI	PC, 04H	; enable latch (PC2)
		NOP		; wait
		XRI	PC, 04H	; disable latch
		EI		; enable interrupts
		JMP	.11E1H	; go back (label is my guess:)

No rocket science either. 

The idea is to connect the memory through a latching buffer (74374) to PB[4..7] with its latch connected to PC[2]. This way I don't have to take care about any writes to PB (and that happens quite often, since it's a part of keyboard scanner), because it will be separated from the memory as long as PC[2] doesn't change from low to high (74374 is edge triggered).

Using the fact, that the existing code already provides me with the value to be set (register D), I'm just creating a copy of it (for safety's sake), shifting it to high nibble, merging it with PB's value (with interrupts disabled just to make sure that nothing will modify PB after it has been copied), writing it back and cycling PC to enable latch.

That may actually work if a) no other piece of code touches PC[2] (there's only one piece in code which may do that - if anything strange will ever happen I can easily patch it) and b) the jump address at the end is correct (I'm skipping the communication sequence between this board and KLM-654, because there's nothing to be communicated).

That's all for now. The new EEPROM is already programmed and sits in the keyboard. Next step is to connect an oscilloscope or a logic analzer and see if we're getting some data out of upd7810. We're getting really close.

Discussions