Matrix 6 and 1000 firmware - more than you wanted to know R. Grieb 9/2015 Voice sound generation hardware is controlled by timers to generate the correct DCO pitch, and control voltages (cv's) which control things like filter cutoff, resonance, env gen attack, etc. For each voice there are ~eleven cv's and several timer values that need to be "refreshed" on the order of 80 times each second. Other analog synths have this same requirement, although cv's might be used to control VCO frequency instead of the timers used with DCO's. The specific values to be written each time depend on the note being played, and the current voice parameter settings, envelope state, LFO state, lever states, etc. One approach would be for the update code (cv refresh code) to check each parameter during updating and branch to the correct code. This would have been too slow to allow the 6809 to handle all of the features of the Matrix 6/1000. Instead, an "update stack" is maintained for each voice based on the current parameter settings. This stack contains pointers to code, ptrs to variables, and some pre-computed values. Only pointers to the code needed to handle the enabled features are placed on the stack. If env gen 1 modulating VCF cutoff is not currently enabled, the pointer to the code to implement it is not placed on the stack. To update a voice, the code jumps to the start of the stack, jumps to the first address on it, executes that routine, jumps to the next address, etc. No decisions need to be made about what code to execute during updates. All of those decisions were made as the stack was built. This is a very fast and efficient way to update the voice cv's. A downside of this approach is that when parameters change, the stacks need to be updated for all six voices. Some parameter changes just affect one number on the stack, so that number can simply be changed very quickly. But some parameters can change the size of the stack. This is a problem, as the update values for that parameter may be in the middle of the stack. If the number of stack values needed changes, somehow the part of the stack after that section must be moved. If more values need to be inserted in the middle of the stack, then the section after them needs to shift down to make room. If some values need to be removed from the middle, then the section after them needs to shift up to fill the space. (There is no provision for gaps in the stack) One example of this would be changing a modulation amount from 0 to a non-zero value. When it's 0, the code for that modulation is not on the stack. But when it's non-zero, a pointer to that code now must be placed on the stack, in the correct spot for its destination (VCF, VCA, etc). The approach taken in the Matrix 6/1000 code was to rebuild the entire stack when any parameter change occurs that could either add to or delete values from it. This can take 10-15 mSec for each voice, or 60-90 mSec for all six. If you send a string of updates quickly, like a parameter sweep from a slider, the code will perform a number of 60-90 mSec updates at once, which can take a second or two. During this time, all voice cv refreshing simply stops. Some parameters do not change the update stack at all. For instance, changing most of the envelope generator parameters does not change the stack, and can be executed very quickly (and is, in the original code). The routine that completely rebuilds the update stack saves the current pointer into the stack at a number of places. So for instance, there is a pointer to the start of DCO1 PW update section, and one to the start of the VCF cutoff update section, etc. There are 15-20 pointers saved while the stack is being rebuilt. These pointers allow accessing just the part of the stack that needs updating, such as the VCF cutoff initial value, which is placed on the stack. But if the size of the VCF update part of the stack changes, the part of the stack that is after the VCF part must be rebuilt. So one approach to speeding things up would be to start at the place in the stack that needs changing, and rebuild from there on. This would be faster than rebuilding the entire stack. At first, this is how my code worked. But I came up with a better idea. Say I need to update the VCF cutoff part, but I don't know if the update will change the size or not. I could try to figure out if the size will change ahead of time, and either just update that one section, or rebuild from there down if I think it will change. But say I guess wrong. If the size changes and I guess that it won't, then the CPU will crash the next time it updates the voices. If the size doesn't change, and I guess that it will, then I have wasted the time spent rebuilding the rest of the update stack. Assume that after the VCF cutoff part of the stack is the VCF FM section. I have pointers to both of these sections saved. So what I do is I start at the VCF cutoff section, using the saved ptr to get to it. I update this section. When I am finished, I compare my stack ptr (which keeps track of where I am writing when I update the stack) against the saved ptr to the start of the next (VCF FM) section. If the two are equal, then the size of the VCF cutoff section has not changed and I am finished. This will be the case most of the time. If the two are not equal, then the VCF cutoff section has changed size, so I must rebuild the list from there to the end. Using this scheme allows fast updates most of the time, and also covers the case where the section being updated changes size and the update stack must be rebuilt (but only the part after the section that changed). I used a program called DASMX to disassemble the original EPROMs into 6809 assembly code. Then I spent lots of time fixing up data tables, jump tables, and other things in the file that confused the disassembler. My goal was to create source files that could pass through Hermann Seib's great 6809 assembler and create binary images that matched the original EPROMs exactly. Once I got to that point, I started working on figuring out what the different routines and variables did and adding comments. I created Verilog simulations of both the Matrix 6 and the 1000 that allow me to send it MIDI commands, press switches, etc, and watch the code execute, variables change, control voltages change. This is a very powerful tool and really helped me to figure out some parts of the code. When I was ready to start making changes, I used conditional assembly, so that I can turn on and off each optimization separately. If I turn off all optimizations, the generated image matches the original EPROMs exactly. If something is not working right, I can turn on my optimizations one at a time, and see which one is causing the problem. So I am not "patching" an EPROM binary file to make my changes. It would be hard to make as many changes as I have made using that technique. I found lots of places in the code where "long branches and long bsr's" were being used when the shorter (faster) ones could be. I have fixed all of these. Unfortunately, the speedup from those is probably well under 1%, but every little bit helps. For now, I think it's better not to share my source code files, as edits have to be made carefully, or a bug can be created, which might not show up until after many copies of the code have been made.