For questions/coments/suggestions, I can be contacted via E-Mail: mp1@damicos.net
Current state of the project (what works and what doesn't): - Done: The way in which settings are passed to the analog control chips is reverse engineered and outlined in the technical data section.
- Done: Development board (Fez Panda II by GHI) IO is wired into the Z80 socket (see pictures below in the build logs).
- Done: C++ code is written that allows all of the analog control chips to be set up.
- Done: C++ code is written to receive incoming Midi messages
- Done: C++ code is written to display values on the LED numerical display and buttons.
- Done: Store patch info in on board flash.
- Done: Keyboard input handled
- Done: Add Play state (you know the mode of operation where you are playing and changing patches via front panel and Midi)
- Partially done: Port all code to STM32F4 and create new plugin for z80 socket
- Not done: Wire the UART pin from board to the correct pins of the 40 pin socket where the UART on the MP-1 goes.
- Done: Add patch editing
- Partially Done: C++ code can set patches in the hardware though chorus needs some timing work.
- Not Done: Hook the flash memory code into the patch manager to save the current patch when any changes occur.
- Not Done: Add configuration editing
- Not Done Add Midi Mapping capability.
- Not Done Add firmware flash update from USB drive
- Not Done: Storage of patch data to SD or USB flash drive
- Not done: add Midi Sysex
- Not Done: Special multi key commands
- Not Done: Design and fab a finalized circuit board to fit in the Z80 socket with headers that tie into the I/O board ribbon as well as the development board. Likewise, make a dip header with a couple jumper wires to replace the 40 pin UART. Currently, I'm still using protoboards.
Below is a log entry of day to day progress I have made on this project to date.
Log 12/5/14: - Refactored some of the patch container code such that it is easier to work with.
- Added a Patch wrapper/helper class that can manipulate the patch data in accordance with the patch definition class. This is the class that is now used by the Patch manager and User interface state machine to change, modify, and store patches. It greatly simplifies the code and allows for the scratch patch implementation I discussed previously.
- Finished most of the Store state... Still one bug to work out before I can call it done though.
- Spent quite a bit of time working with the STM32F4 Discovery board. Not too much progress on porting but I have been playing with the CMSIS and FreeRTOS libraries as I will need them for the port. I've also been trying out VisualGDB which is a plugin for Visual Studio that allows programming and debugging of the discovery board. I'm kind of in that bang your head against the wall mode that happens with a new board when you try to get all the pieces working correctly. Just takes time and it eventually comes together.
Log 11/12/14: - Finished up the "Editing" state.
- Created the "Store" state and got most of it finished. Since I already had the Bank button working in the play state, I could reuse most of the code for the store state from that as they are so similar (picking the number uses the up/down arrow to change bank and the number buttons choose the 1-9 digit. I just need to actually write the data to the correct patch location when the 1-9 digit is selected (including writing the patch to flash memory). I also need to implement the unsaved patch behavior. When a patch is edited and the store button is pressed, it goes into store mode and the destination patch number can be selected. If at that point store is hit again, the patch is not saved but the mode goes back to play mode. To let the user know that the current settings are not saved, the LED display blinks showing that there are unsaved settings. Likewise, if the unit is turned off and on, this behavior continues. This means that in order to replicate that functionality, I need to have a scratch patch to work with in edit mode that is saved to nonvolatile memory as parameters are changed. When the unit is booted, it will need to check if there is an unsaved scratch patch. If so, load it and start the LED's blinking. I currently have the scratch patch when in edit mode but I will need to persist it through all of the states. It shouldn't be too difficult.
- I've been thinking a little about one of the first mods I'd like to make for my new firmware that would extend the MP-1 past it's current capabilities. I was thinking that in many of the existing mods out there, a somewhat common theme is that the EQ frequencies are changed to match the characteristics of what the mod is supposed to do. I'm thinking it would be nice if the MP-1 had a fully Parametric EQ that could allow the eq to be changed on the fly. I was considering doing a parametric with op amps and more digital pots but I came across a single chip solution. I don't know how it would sound but it would be an easy way to get there. The chip is the Sanyo LC75281E. I'm not sure if it's still manufactured but it looks like they are still available for sale. Here's a link to the data sheet ( http://www.digchip.com/datasheets/parts/datasheet/413/LC75281E-pdf.php ). I think it would be relatively easy to make it work. It would be a daughter board that would plug into the sockets for U19 and U18 (Iknow U18 is not socketed, but for anyone who has replaced their Opamps it now should be) . The biggest difficulty would be that this chip can't run at as high a voltage so I would need to regulate the source voltage and message the input and output signals down and up respectively with an opamp (probably reusing U18). I see the chip goes for about $10 and a breakout board can be had for a couple dollars on ebay. I may just have to order some parts and give it a try. Also, since the chip supports two channels, it might be possible to use the second channel to increase the resolution of the eq gain controls such that there are 12 steps between each of the current +/-2db gain values. So each band could effectively have 144 possible steps. If anyone is reading this and has any thoughts/opinions about this, I'd love to hear from you.
Log 11/10/14: - Good News/Bad News. I started checking into why my Effects loop code wasn't working. It seemed like all was good with it, so I scoped out Pin 16 of U42. It was going high when the effects loop parameter was in but Logic High was only about 1 volt. I put back in the original Z80 processor to check it on the original firmware. It behaved the same way. So it looks like a hardware problem with either U42 or U33 (probably U33). I'll have to investigate it further to fix it. I guess the good news is that there wasn't a bug in my firmware so one less thing to fix.
- I checked the timing on my Chorus algorithm. It's a little messed up. When I originally reverse engineered the chorus, I missed something. I was using a rate of 0.1 when I was grabbing the data at the output pins of U25. What I didn't notice was that as the rate is changed, the data rate goes up to facilitate the shorter period. When at 10.0, the time between samples is about 500us. At a rate of 0.1, it is 20ms. This means that my code will need to service the chorus with a hardware timer. I was just using the display service call but that is too slow (at 2ms). It's not a huge change but I will need to implement a hardware timer interrupt and signal a semaphore when the ISR is raised to allow a task to service the chorus. I like this better than using the Display/Keyboard task as it will be a cleaner solution.
- Worked some more on getting the STM32F4 going. I'm having some issues getting Interrupts to work properly. When I enable an interrupt, I can see the default handler getting called but when I implement a function with the same name to replace the default handler, it never gets called. It's probably a problem somewhere in the Crossworks config files but I will need to do some digging to find the cause.
- Started breaking out all processor specific code into separate files such that porting will be easier and I can build for different targets by just changing a couple project settings.
Log 11/6/14: - Now that I have editing mostly complete, I spent some time testing the driver code that sets up the control chips on the board. I found a few bugs in how I was setting up the overdrive gains. Some were just simple porting errors from when I moved the code from .net to c++, but one of them was something I missed when I was figuring out the mappings between overdrive settings and the digital pots. As it turns out, Overdrive 2 has different mappings depending on what voicing is selected. when in Solid State mode, the mapping is opposite that of the tube modes. So in SS, as the parameter is changed from 0 to 10.0, the value sent to the digital pot goes from 0 to 78db. When a tube voice is chosen, as the parameter is changed from 0 to 10.0 the digital pot goes from 78 to 0db. I vaguely remember noticing this before but I must not have gotten around to fixing it. In any event, It's fixed now. I compared using a scope to check the signals as well as compared the sound to the original firmware and it is the same. I still have two more driver issues to look into. The effects loop isn't currently working. I'm probably not setting up the flip flop U42 correctly as it also drives the keyboard and display multiplex lines. I'm sure it will be fairly easy to find/fix. The other has to do with the chorus. The chorus is working but I think the rate timing is off so I will need to verify that. Probably a porting issue.
Log 11/4/14: - Got most of the Patch Editing State finished. I can now modify parameters for everything in a patch except the chorus rate and depth parameters (those parameters work a little different than the rest, so I created different classes derived off of my IParameter interface class to handle them - just need to finish the implementation). I should be able to get those done tomorrow. I may re-factor the code a little as I'd like it to be a little more straight forward when incrementing/decrementing values. Overall, I think it went together pretty quick and seems to be working well. Next step will be the "Store" state so I can save a patch after edits are complete.
- Did some more work on getting the STM32F4 Discovery board runnning. I fixed the GPIO and started getting the USART configured to work for midi. I had some trouble getting it to compile using the STM Peripheral Library due to conflicts with the way Crossworks ARM set up the project. Eventually, I was able to get it. However, it might take some more work to get the clocks set up correctly. STM offers a spreadsheet that generates a C file to be added to your project that will configure the clocks. I couldn't get it to work in my project due to how Crossworks sets up the startup code. I may need to investigate further. I'll find out when I set the baud rate on the USART and try to send/receive data to another device.
- Starting to work on getting a nicer wiring scheme in the MP-1. I'm getting close enough to having the firmware complete that I'd like to be able to close it up and give it a try in my Rack. This is one of the reasons I'm getting serious about the project again. The unit in my rack now has a dead battery. I know I could replace it easily, but that would be admitting defeat since my mod will allow operation without a battery. Since I've got 2 MP-1's (one on the bench and one in the rack), I'll probably put the mod in both so I can test it in my guitar rig and fix problems on the bench. I think I'll need to drill a hole in the side so I can put a USB cable through for updating the firmware and debugging. It would be nice if I could update the firmware wirelessly via http. I will have to keep that in mind for some time in the future.
Log 10/31/14: Pretty good progress over the last few days.
- Fixed a couple bugs in the Play state (ie wrap when patch goes over 128 or under 1, button validation for invalid button entries now will show "no" like the oem firmware does).
- Fixed up some issues in the state manager and added a class for the Edit state (and started work on patch editing)
- Started porting code to the STM32F4 Discovery board. Got all of the code running on it with the exception of the processor specific things like GPIO, UART, and Flash Memory which will need to be fixed up.
- Fixed the Key repeat for when buttons are held down.
- Added a new thread task that handles the timing for LED's that blink. Much nicer solution than how I was doing it before
- Soon I will start taking some videos of the MP-1 in action with the new processor/firmware. Stay tuned...
Log 10/28/14: It's been a long time but I'm finally back at it again... Currently working on the user interface for the oem controls/display. My goal is to get it built and tested to work exactly like the OEM V2 firmware before moving on to new features.
- I have all of the buttons working in play mode including the bank button. I still need to get the Clean/Dist/SS Leds to change with the patch but that should be pretty easy. Also need to look into my key repeat algorithm as it seems like it is a little off from what it should be.
- Since it has taken me so long to get working on it again, the dev board I was using is no longer in production. I did some looking around and found the STM32F4Discovery board for around $15. It seems like it will be a good fit as it is faster with more memory than my current board. I purchased a couple and will see if I can get the code ported over to it. It is an arm processor so it should be doable in a reasonable amount of time. Also got an Arduino Due. It's another possible option but I don't like the fact that the I/O is not 5 volt tolerant. So to use it I would need to use level shifters on all of the input pins.
- Fixed a bug in my threading model where some pointers were being deleted before they should have been. Took a while to find but now things are feeling pretty solid again.
Log 6/3/2013: Hard drive crash...
Everything was backed up but I had to rebuild the box which was still painful. Everything is back up and running now. Just slowed progress.
Log 5/15/2013:
Made good progress over the last week. I created the state machine code that manages the UI state of the MP1. The states I have identified so far are Play Mode, Edit Patch Mode, Edit configuration mode, and Copy Patch Mode. Right now I have the play mode state working. In this mode Midi messages, Button Input, and rotary input get handled by the PlayMode state object. This object is responsible for updating the display with the currently selected patch and calling the drivers to configure the chips when Midi change messages come in. I hooked up the Midi Code and can now change patches from a Midi foot controller. I have a bug in that the chips are not correctly getting setup (probably a minor problem due to the driver port to C++). Once I have the edit mode state code done this should be easier to track down as I will be able to individually change parameters via the front panel. I could do it by writing test code but I'm close to getting the editing working so I'll just hold off till then. Also worth noting, I got the button input driver working. I'm now working on the eventing for the buttons. It should work very similar to the midi eventing which is based on the observer pattern. Once that is done I can port/create the edit state class.
Log 5/7/2013:
Made good progress today. Finished the framework for which the patches will be store and retrieved. Was able to initialize 256 patches and store them into on board cpu flash memory. Right now my code size is around 60kb out of 512Kb flash and 40kb of ram is being used out of 90kb. To store 256 patches in flash it currently takes about 8kb with fixed size patch names of 25 characters. So as far as memory goes, I seem to be in good shape. The code to initialize the default 256 patches and copy them to flash memory runs in about a half of a second. This will only be necessary the first time the unit is powered up because after that it will just read the patch data from flash. I have the patch manager working such that it can read patches right out of flash (as well as RAM) so there is no penalty copying to RAM on start up. I will need to measure the read time such that patch changes can be executed quickly but I don't think that will be a problem. I'm liking the speed and code size improvements that I'm seeing in C++ vs C# so this port definitely feels worth while. I'm getting really close to hooking up the Patch management code to the MIDI and Display code such that I can begin doing patch changes and such.
I'll be posting some design info on my Patch Management code soon. It's starting to have a decent feel to it. It's more complicated than just a collection class because I had some requirements that I wanted it to be capable of:
- Patches needed to be stored in a very compact form to conserve flash memory.
- Patches should be readable from flash or RAM with little impact on code operation.
- Patch structure and Parameters should be configurable at runtime such that operation can be modified via configuration data in the future without doing custom builds. The idea being that different displays, rotaries, buttons, digital control chips, etc. should be defined through data such that the mods done are not a one size fits all endeavor requiring custom firmware builds. My hope is that only chip/IO driver code will be necessary to create the next wiz bang mod that's controlled via firmware.
- Patch access should be reasonably fast (< 1ms).
- Default Patch initialization should be reasonably fast at first power up ( < 1second). Also, patch initialization should be readable in the code vs cryptic binary data blobs.
Some of these requirements caused difficulty in C# due to code size limitations, micro framework boot times, interop, and code execution speed. I think I've got it all covered now in C++.
Log 5/1/2013:
I haven't had too much time to work on it in the last week or two but I did make some progress in a couple areas. I was able to implement code that writes and reads to/from onboard processor flash memory at runtime such that patches can be stored without the need for a battery or external memory. It was a little more complicated than it was in .Net, but eventually I got it working. Also, I did some preliminary work toward making a new front panel. I have a CNC machine that I can cut the new aluminum panel from but I wan't sure how I would be able to easily silk screen the text and such on the panel. A friend and I experimented and came up with a process that worked really well. We used an acrylic panel in our tests but I don't think the material will make any difference.
We started by applying masking tape over the panel. Next we used a CAD program to design some text that we would like applied to the panel. We then used a 60 watt laser cutter (owned by a hobbyist club I belong to) to engrave the text from the cad file. The laser cutter isn't powerful enough to cut aluminum, but it can completely remove the painters tape. Once it finished, we just sprayed painted the masked panel white. When the tape was removed we had very nice white lettering that looked like it was silk screened on the panel. This will also be nice because I can do multiple colors by just repeating the process with a different mask and paint color. When done, I think this will look nicer than the original panel.
Log 4/12/2013:
Currently working on the patch classes. I decided to rewrite instead of porting the code because I really wasn't satisfied with the quality of .Net code I had written. This was mostly due to memory constraints that limited the implementation. The design i'm currently working on is similar to a flyweight pattern such that patches can be stored as efficiently as possible. My thought is to have a single Patch definition class which holds onto all of the possible values for all of the parameters. So for instance, for Master Volume, the Patch definition would contain a parameter definition that contains a list of all possible volume settings that are allowed when editing. This would include the number shown on the display, the value sent to the driver to change the analog chip settings, etc. Since there is only one definition, a separate list of Patches with instance data will hold onto the settings for each patch. So if patch 2 volume is edited all that changes is which setting in the parameter definition is used. This makes the instance data very small because it's just a reference to the parameter definition.
I also fixed a couple bugs in my midi implementation and did some testing with a midi foot controller.
Log 3/29/2013:
Today I measured the Midi latency as discussed yesterday. I temporarily modified the program such that in the ISR, I pulse an IO line and then in the PatchManager onMidiProgramChange I pulse the IO line again. Then using a digital storage scope, I measured the time between low to high transitions. This came in at about 26.4 us (micro seconds). This only accounts for the latency of my midi notification code. For a real patch change there will also be latency for the lookup of the patch parameters as well as the time to set the parameters into all of the chips. I'm looking to stay under 5ms (milli-seconds) for the total. I think the midi latency is well within reason to achieve this goal so I think the Midi notification design is sound. Therefore I will keep moving forward with the patch manager coding.
Log 3/28/2013:
Today I finished writing the Midi receive implementation. It's pretty much as described in my last log, but also uses an Observer Pattern to notify objects when Midi messages come in. To summarize, when a byte is received on the UART, an Interrupt Service routine is called in the UART class. The ISR signals a mutex. Meanwhile, a high priority thread is running in the Midi class that blocks on the mutex. So, when the UART mutex is signaled, the thread unblocks and the Midi class then gets the new character (the advantage of this is that the processing doesn't need to be in a static method so the member variables can be accessed and multiple instances can be created). The unblocked Midi object thread next processes the incoming byte as part of a Midi Message (see Midi Spec for details). Once enough bytes have been received to complete the message, a method is called on all objects derived from IMidiReceivable that have subscribed to the Midi Events. In this way, for any object to receive incoming Midi message events, that object just needs to be derived from the interface class IMidiReceivable and call the subscribeMidi(this) method in the Midi class. From then on it will have it's methods such as ::onMidiProgramChange(int nProgram) called. This design allows loose coupling from the ISR all the way to the object that gets the notification. It works well, but I still need to measure the time it takes for the event to propagate through the system. My initial feeling is that the speed will be fine. Right now I have it updating the display with the patch number coming from a Midi Program Change message. The next step (after measuring the message latency) is to start porting/rewriting the patch manager class and underlying patch and Patch config classes. Stay tuned...
Log 3/22/2013:
Created code to configure the UARTS to send data and receive data on an interrupt. This will be used to service the midi messaging. I also got the tasking working such that when the serial data comes in, the Interrupt service routine will trigger a task synchronization with the MIDI processing task. The idea is that the processing task thread will block until the ISR receives enough bytes to complete a MIDI message. Once it does, it will unblock the MIDI processing task (which is at the highest priority) such that it can grab the incomming message data, decode it, and then cuase whatever action is necessary (ie. change patch). Next I need to finish the MIDI processing class and organize all of the new code...
Log 3/19/2013:
Just finished getting the display/LED multiplexing code ported over to C++. I used a tasking library such that the display refreshes about every 3ms (anything less starts to visibly flicker) at a very low thread priority. This low priority will ensure that midi control messages will be processed quickly as those will be processed at a much higher thread priority. When the UART interrupt code is fired it will check if the midi transmission is complete and if so will signal the midi change worker thread to process the incomming midi change/control message. That's the next bit of code I need to write/port. Good to be making progress again...
Log 3/4/2013:
Lately I've received some e-mail correspondence asking if this project is still ongoing. I apologize for going dark and not updating the site more regularly. To answer the question, yes the project is still underway but has been stagnant for a while do to time constraints. I have been evaluating C++ arm compilers and porting the .net code over to C++. The compilers I'm looking at are Yagarto, Keil uVision, and Rowley Crossworks. As much as I'd like to go with the free version of Yagarto, I've not been able to get c++ code to compile on it and samples are scarce. Keil uVision seems like a very capable environment but the free version is too limited (32K flash) and the paid version is too much money for this project (since it's really a hobby project). That leads me to Rowley Crossworks Arm. I downloaded the 30 day evaluation package and started working with that. It seems to work well and Rowley offers a $150 hobbyist license. So far this seems like the way to go. Will post more when I make the final decision.
As far as the actual coding goes, I ported most of the display code to c++. Just need to get the timer interrupts working such that it refreshes the LED's. Stay tuned, more to come...
Log 8/1/2012:
Made some progress on moving to native C/C++. As it turns out, the erase on the Panda II board was successful. At first I was having trouble connecting to it using the jtag port with a segger JLink programmer. As it turns out, the problem was just a configuration issue in the flash software. I had selected an ARM 7 core (which it is) but needed to select an LPC device. Once I did that, all was good and I was able to flash code onto the board. I'm currently using the evaluation version of Keil uVision4 as my development environment. I really like this package and it integrates well with the JLink. I was able to get flashing, debugging, and simulation working. The only downside of using this package is that because it's the evaluation version, it only supports projects up to 32K in size. 32K probably won't be enough by the time I'm done. I'd gladly purchase a more full version of the package but the price of admission is a too high for what I'm trying to do. In addition, I've also begun down the path of trying the Yagarto compiler with eclipse. If I can get that working, I can use the full 512K of flash memory on the device and have a free (albeit not as nice) solution if I ever get to the point of open sourcing this firmware.
In the mean time, I flashed my board with a minimal program that included my ported RLP code and all seems to be working well. The next step is to get the UART interrupt configured which I will need for the MIDI messages. One of those things that the .netmf completely took care of. The speed, will be worth the work though. Also, boot time feels instantaneous now.
Log 7/27/12:
Well, it's been a long time since I 've been able to work on this project, but I've started going on it again. So after some thought about what's next, I think one of the big problems I still have is the performance I'm getting with the .NETMF platform. I really enjoy using it but I have to take a step back and decide if it's right for this project. Using RLP got me quite a bit of the performance I needed, but there is limited space for RLP code on the Panda II. Also, I found that I had to move quite a bit of code down to that layer because I want patch changes to be as fast as possible. This required the actual IO bit twiddling be down there but I think the MIDI and patch lookup would also benefit greatly from being native code as it would avoid so much of the going back and forth between c and c#.
The way I currently have it, to do a patch change a native serial driver calls up to .Net where the Midi class processes the control message and notifies the patch manager that a change is to occur. The patchsettings are looked up and then passed down to RLP where the IO is manipulated to setup the hardware. If all of this code were native, a great deal of time could be saved going back and forth from native to managed. I could move the midi and patch manager stuff down but that doesn't leave much left in the managed space. Also, by going native I gain much usable RAM and flash memory because there is no need for the .NETMF. I also think booting will be faster because the framework won't need to be loaded but I could be wrong about that.
I started porting the code over to a native C program starting with the RLP code (as that is the easiest since it's already in C and it's also the lowest level). Most of the port just required the RLP defines to be replaced with the native ARM equivalents. I have all of the IO output functions done but still need to work on the display and input functions. I''ve been using the simulator in uVision by Keil to verify the port because I had some trouble erasing the Panda II. To do native arm code on the Panda II, the .NETMF has to be removed such that the board can be reflashed. Erasing also allows the JTAG port to become enabled. To erase the board special firmware from GHI needs to be flashed to the board. When that firware runs, it erases the board and enables the jtag port. Unfortunately, after loading the erase firmware, the board is showing no signs of life. I ordered another one but will need to get to the bottom of what happened on the first one before I brick another.
Also, comming soon, I will start working on modding an ADA Pitchtraq to see if I can add MIDI control to it. Much of the unit is similar to the MP-1 as far as display and buttons go but much will need to be reverse engineered also. So I will probably go down the same road as with the MP-1 where I try to pull the Z80 and redo the control using an ARM.
Log 9/20/11:
For the last few days, I've been working on general code cleanup and bug fixing. I've refactored much of the parameter mapping code as there were some problems with the way I was mapping hardware values to display values. I had originally written the patch collection class such that it held onto the data the way it would be sent down to the C (driver) code when a patch change happens. The idea was that no lookup would be necessary so it would be faster. The problem was that if I held onto that data, there were times when it couldn't be mapped correctly to a display value. The reason is that parameter values are not always a one to one mapping with the display values. For instance, when OD1 is set to 9.0 on the display, the associated hardware value is the same as when OD1 is set to 9.5. So if the collection only holds onto the hardware data, there is no way to know if 9.0 or 9.5 should be shown on the display when an edit is started. I modified the code such that instead the patch object holds onto the index of the display value or hardware value in their respective maps. This will be slightly slower when patch changes happen but it's just a matter of indexing an array (no searching) so it should be reasonable.
Once I made the patch editor write the value changes to the hardware, I found a couple other issues that I'm working on now. One is that I missed something when I reversed engineered the parameter mapping for OD1. If the voice is set to SS, the mapping table for OD1 is in reverse order than if it is on a tube voicing. Also, I was having trouble getting the FxLoop to work. After a little investigation with a scope, it appears that my code was working fine (also confirmed by putting the original processor back in). I just have a bad chip (U16). The switching signal looked good there but it always passes the direct signal. To be honest, I don't know if I've ever actually used the fx loop on my MP-1 so who knows how long it's been bad. I can't remember if I ever explcitly tested that parameter when I wrote the C code to enable the loop. I guess I'll have to make sure the chorus is still working correctly as that is enabled by the same chip.
Log 9/14/11:
I haven't had a whole lot of time to work on it the last couple weeks but have been making some progress here and there. Currently, I'm writing the code to allow editing patches. I refactored the code to implement a simple state machine to handle the various modes of operation based on user input. I implemented a base class which represents a generic state machine class for user input. For each state I derive a class off of the base that overides the methods with the necessary logic to handle user input for the state. When an event takes place that could cause a state change, a current state member gets set to the appropriate state object. If a button, rotary, or midi message comes in that doesn't cause a state change, the keyhandler just calls the current state object to handle the event. So, as an example, when the MP-1 starts, the current state member is set to the play state object. In this state, if a user presses the ^ button, the button press is passed to the curent state object (play state) and is handled there. This calls the patchselector object to increment the patch number, update the display, and set the analog hardware. If the user then hits the edit button, the keyhandler will change the state by updating the state member to hold a reference to the edit state object. Now if the user hits the ^ button again the button press event will be handled by the current state but since it is now set to an edit state object, the logic in that object will just increment the currently selected parameter within a temporary patch. It's nothing real spectacular... just an object oriented state machine implementation.
I have it mostly working. I can press the edit button and view the individual settings of the parameters by pressing the various parameter buttons. I still need to write the code to handle the store button to comit the change. Likewise, I need to write the code to update the hardware as parameters are changed so you can hear the change. For this, I need to add functionality to the c code such that I can write to a single hardware chip instead of writing all the parameters. Once all of the edit state code is done I will also need to create a configuration state for general configuration data (like midi). After that, I think most all of the base MP-1 functionality will be complete and I can move onto functionality that was never before in the MP-1 (although I already have a few such features).
Log 8/26/11:
Today, I started working on patch storage. My goal was to get some patch settings put in a file and have my dev board load those settings at startup. My first thought was to use an XML file to hold the data. This would give a nice way to edit the patches on a PC with a simple text editor and would lend itself well to firmware updates. I formatted up a 4GB micro SD card with a fat32 file system and went to work creating an XML file with a couple patches in it. Then I wrote some code to read in the patch info and set it into my patch container class using the XMLReader assembly. When starting up, I create a thread for the splash screen and then in the background, load in the patch info. With a couple patches, it worked great. The problems started when I tried to load 128 patches from the file. Since XML is a text file format with tags, it can get very verbose and bloated. As a result, loading the 128 patches was taking around 10 seconds. This is too long. Hey when the mood hits, you've got to hear that power chord as soon as possible. So, I think that I will consider loading from XML to be an Import/Export operation. Back to the drawing board...
Feeling somewhat defeated, I decided to write some code that would save and load the data out to a binary format. Once I got that working I did a save of the 128 default patches that I have hardcoded and then did a read of the file. My initial attempt took about 1 second for save and 1 second for load. This is probably good enough as it takes more than one second to get through the splash screen. Still, I think I can do better as I had to do some copying of array data to get it into an array with a type that the filestream writer can deal with. I couldn't find the best way to convert so I need to get that straightened out. I also got to thinking that it might be cool to reverse engineer the sysex file for the mp-1 and allow loading directly from those files as well. At least now I have a starting point for writing the patch edit code. And the best part: saving/loading with no battery!!! Not to mention that the sd card can be popped out, put into another MP-1 and, just like that, instant backup.
Log 8/24/11:
So... for the last few days, I've been working through some problems with my software design. I was trying to store all of the parameter mapping information into a couple c# hash tables. This makes it really simple to map the hw parameter info to the display info. I implemented it but started having problems due to the amount of available ram on the fez panda ii board I am using. Just loading the hash tables consumed 30KB of memory. Since that wasn't going to work, I redesigned it to use simple arrays to hold the data. I wanted to be able to store the info in RAM such that later I could load the mapping configuration info from an XML file located on an SD card. I found this approach improved the the RAM usage and cut it down to about half of what it was but still 15KB is a pretty big chunk on a device like this. Finally, I reworked it such that it simply uses a switch/case and returns constants. This uses more program memory but is very light on RAM usage. The downside is that I can't load the mapping from the config file (unless I find a more efficient way to store the data in RAM or move to a board with more memory such as the GHI EMX). Ultimately, the switch/case lookup got me into having a healthy amount of RAM being avalable for the rest of the program. I loaded about 30 patches into my patch container class and still had about 25KB to work with. After that, I hooked up my midi event notification to my PatchSelector class. When a Program change message comes in, it calls the PatchSelector with the Program number from the midi message. The selector then calls down to the C RLP layer and sets all of the parameters for the patch. Finally, it sets the LED display to the number of the patch. I was able to give it a test run today and could see and hear patch changes on the MP-1 that were being commanded by my X-15 midi control pedal. Overall, I think I'm making good progress.
Log 8/19/11:
Not quite as far as I wanted to be by the weekend but still made good progress. Yesterday and today, I wrote the high level LED Display class. This class has methods that allow the LED's to be manipulated by the developer without having to have integral knowledge of how the display works. It does all of the charachter mapping to the individual 7 Segment LED segments. To display something on the LED a simple call like this is made
- DisplayLedOem.OutputText("ADA")
- DisplayLedOem.OutputText(someNumber.ToString())
- The display class also has methods to turn on or off the Button Led's by index as well as methods to manipulate the Clean/Dist display dots.
It was a little more complicated than I had anticipated to get the string mapping code to work because there are so many permutations/boundary cases of what a string could look like that is passed in. Likewise, so many error cases for where the decimal point can be displayed, max string length, etc. I'm probably not explaining it great but, for instance, if a string like "1.2" is passed in it needs to be parsed and padded to look like " 12" and the right most decimal point must be set whereas something like "1.2 " needs to be parsed to " 12 " with the leftmost decimal point displayed. The padding and which decimal point to show (if any) is dependant on the length of the input string, where the decimal point is and where spaces occur, not to mention that there are only two places a decimal point can be placed so something like "12.3.4" can be displayed but not "1.23.4". In any event, the code is written and working well with all of my test cases.
Side note: It's really nice to be working up in the C# layer now as I can work in an object oriented way with a full debugger in Visual Studio 2010 express (free) and I have access to so many nice assemblies for manipulation of strings, etc.
Log 8/17/11:
Today I wrote the button/keypad driver. Nothing too remarkable about it though I did run into a few minor gotcha's.
- First, the scan columns are shared with the keyboard digit multiplexor lines. This is fine but forces the code that services the LED's to be tightly coupled with the code that scans the keyboard. So now the interrupt driven task that services the LED's also services the Keyboard as well as the chorus LFO.
- The driver chip that the keyboard rows are connected to has two active low enable pins. One is the chip select and the other is tied to the read pin of the Z80. As such, since I'm not using the Z80 or the read pin, I had to pull the read pin low on my socket adapter to allow the chip to enable.
- I'm seeing a strange behavior with the store button. For some reason, when it is pushed, it activates an entire row of buttons in the scan matrix. I'm not sure if this is normal behavior or if this is just something strange with my unit. A friend of mine has an MP-1 that I plan to check against to see.
So generally the way the keyboard driver works is as follows:
- Set the scan column (set by the LED multiplexor code)
- Configure the D1, D2, D3, and D4 data lines to be read lines
- enable the row scan driver chip
- read D1, D2, D3, and D4
- if any of the data lines are low, determine what the associated key scan code is in the column
- Configure the data lines to be write lines again
- repeat for each column
- when all columns are scanned, if a key was pressed, send an event up to the managed layer with the scan code.
- determine if the key was held down for a given length of time and if so send more events up to the manged layer for key reapeat. I still need to figure out what the stock MP-1 does for reapeat (ie how long etc).
Right now, I'm doing the key repeat stuff down in the driver but I might decide to instead make the driver just send button down and button up events. The managed code would then have the logic for repeat. This might be a better way to go as it would give more control for triggering actions on button up. Will have to give that some thought...
Log 8/16/11:
I added the C# class, modified the calling code to handle Program change and Control change messages, and hooked up my ART x15 Ultrafoot to the arduino MIDI shield. Within 10 minutes I was up and running and my board was correctly receiving MIDI commands from the pedal. For now, I think this will give me a great starting point to hook my patch container code to my mapping code to the C RLP hardware driver code. I should be able to get some MIDI patch changes going within the next day or two. At some point, I'll still need to take some measurements to see if I need to move the UART code down to C or if the .Net Micro Framework serial drivers are good enough (I'm hoping they are). I think the next logical step is to get some key/button input code working such that I have mostly complete interfaces for all of the stock MP-1 hardware. At that point I can start concentrating on the user interface in C# which should speed up the development. We'll see...
Log 8/15/11:
Today I tried out my chorus algorithm. At first it didn't work. After seeing a newer schematic (than the one I have) posted in the forums at adadepot.com, I was able to see the problem. The schematic I had was not a great scan and I missed how the flip flops were arranged that feed the lfo clk and dat lines. Once I saw the good schematic, it became apparent what was happening and also made me understand why there were 9 clock pulses to clock 8 data bits. As it turns out, the data gets clocked into a flip flop (U32) on the negative edge of the LFO clk signal (which would be positive before the nand gate). So, in effect. the first low to high transition of the LFO CLK just latches garbage on the output of the dat flip flop into the shift register (U25) but is necessary to set up the clk to latch the data on the low going edge. Therefore, a ninth low to high clock pulse is required to clock all eight data bits and make the first garbage bit fall off the end before the strobe line fires and puts the data on the outputs (resitor ladder network).
After fixing up my code, the LFO data looks good on the scope and after plugging in, I can verify that it is indeed chorusing. I'll need to do some more testing at some point to compare it to the original and make sure it's working correctly but all indications are that one more piece of my firmware is complete.
Next I'll start working on the MIDI...
Log 8/13/11:
Had to rewrite the LFO algorithm. I had thought that the ARM processor would be fast enough to calculate the LFO data points on the fly and it very well might be but I couldn't try it. Since my algorithm used a taylor series, it relied on floating point math. The problem with this is that as soon as I used floating point in my C RLP library, the size of the binary shot way up (by about 5KB) due to it pulling in more code. As a result, the RLP wouldn't fit so I needed to rewrite it using integer math. Pretty common for the embedded world I guess. I looked into using an algorithm to compute the trig function with only integer math but in the end just ended up using a lookup table. This is probably the fastest way to do it anyway and could lend itself well to different waveform configurations if I ever implement that feature. I just need to test it now...
Log 8/12/11:
Yesterday I wrote the code to generate the Chorus LFO data. Instead of writing code to generate a sinusoid from scratch, I did a little searching and found a little code snipit from someone who wrote a function to calculate a Cosine wave based on the Taylor series. In essence, the part of the cosine wave that I care about is the part from 90 degrees to 270 degrees where the wave goes negative. This section equates to one period of the LFO waveform. Since I need the wave to be positive, I just add an offset of one to the wave. As such, I modified the cosine code to phase shift the cosine by -90 degress and added an offset of one. Then, I scale the angle such that 0-360 degrees of the LFO mapps to 0-180 degrees of the cosine (the part I care about after the phase shift was applied). I also reworked the code such that given the LFO frequency (which is the rate setting), the function will calculate the data point based on elapsed time instead of angle. Likewise, the data point is also multiplied by the LFO amplitude (which comes from the depth). That should do it for calculating the LFO waveform. I will service the LFO (apply the next sample to the latch) in the same interrupt task that I'm using to service the LED's. I shlould be able to test my code on the MP-1 later today.
I'm starting to have an issue with using RLP for my C functiions. The way RLP works is that you write C code and compile it with a gcc compiler for ARM. This generates an elf binary library file. Then, in the .Net Micro project, you add this file to the resources. To execute the code, RLP provides C# methods that allow you to load the code from the resources into ram and invoke. The problem is that for the Fez Panda II, the maximum size of the C code plus static data must be under 10KB in size. Many optimizations can be done to reduce the compiled size. I have just started hitting the 10KB limit with the code I have written. I'm confident that I should be able to reduce my code's footprint and get everything in there that I need but it's something that I need to start considering. I got around the issue for now by simply setting the compiler switches to not include symbols (which I'm not using anyway) and to optimize for size which helped a great deal, but I expect that I will hit the limit again soon. The good news is that I'm nearing the end of needing to use C as I have most of the hardware driver code already written. All that is left is the button input code (which could probably be done in c# (since I don't really need us resolution) and possibly the UART code if I find it necessary. Still, I'd like to have extra space for future expansion...
Log 8/10/11:
Updated some of my findings from yesterday (see below). Also, finally received the header pins that I needed for my arduino midi shield. I now have all that I need to hook up a midi controller and start writing the midi implementation. I don't really plan to use this shield for the final solution but for now it will make it nice for development. I installed the pins and attached the board to my fez panda ii board so now it looks like this.
Log 8/09/11:
Mapped the Depth setting (0-100) to the amplitude of the LFO waveform (0-255). Also started figuring out the math to generate the LFO programatically. To do this I captured the data from 0-255 of the LFO (half of one period with the other half being the same but fipped horizontally) at max depth (255). I then put that in a spreadsheet and did an x-y plot of the data vs time. The waveform data looked parabolic peaking at the amplitude comming from the depth. I normalized the time to one second in the spreadsheet (just to simplify the math) and then generated a column of data based on the function data = Amplitude * time ^ 2. I then overlaid that column on the data plot such that I could compare the actual LFO data to my calculated data to see if my parabolic function was close to the actual. There was a little difference but the waveform was pretty close. It's possible the difference is due to rounding error and jitter. I think at least for now I will go with this and see how it works. Also, after doing some research, it sounds like this waveform is pretty common for a chorus LFO. It is often referred to as HyperTriangular. This is a nice simple site that discusses the sound differences of different LFO waveforms http://www.hobby-hour.com/guitar/chorus_effects.php. This is a good thread also: http://www.diystompboxes.com/smfforum/index.php?topic=81162.0;wap2
Update: I wasn't satisfied with the curve fit I was getting using a parabolic curve. I decided to go back to my original thought that this might be a model of an inverted rectified sinusoid. I performed the same action with a spreadsheet but used a sin function in the range of 270 - 360 degrees. The sine wave is a much nicer fit and looks right on the money when compared to the MP-1 measured data. I think I have all the info I need to code up the chorus and make it sound just like the original.
Below are plots showing the data from the MP1 v2.01 and the data from a Sin function (left) and a parabolic function (right) as discussed above:
Log 8/08/11:
Project got derailed a bit last week. A friend of mine had a catastrophic computer failure that was putting his business at risk. I had to spend several days helping him get back up and running.
Today I got back to the fun stuff... Last week, I got an updated eprom for the MP-1 V2.01 (Thanks a million MarshallJMP). Today, I got my scope set up to capture the chorus LFO data again so I can start comparing the V1.38 chorus algorithm to the V2.01. I started capturing data but didn't get a chance to get the data into spreadsheet/graphical form. Hopefully I can get that done later this evening and find out once and for all what if any differences there are. The chorus depth and rate are the last of the parameter mappings that I need to figure out.
UPDATE: I did some comparison of the LFO data from V1.38 to V2.01. Granted, the amount of data that I compared was small but at a Rate of .5 and Depth of 100, the two firmware revisions perform in exactly the same way. The waveform is identical in shape, period and amplitude. I will still need to do some more analysis to confirm they are the same at other values but I'm pretty confident that the LFO waveform (chorus envelope) didn't change between revs. I also was able to get the rate mapping to the LFO period. To do this I took a data snapshot at rates of .1, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, and 10.0. I then measured the time between 0xFF data values (waveform peak when depth set to 100). I put that data into a spreadsheet with the associated rate and generated a x-y plot. I noticed that it looked inversely proportional. Upon closer inspection, the rate exactly maps to the frequency of the LFO (ie. 1/period). So at a rate of 10.0 the frequency will be 10 Hertz and at .1, the frequency will be .1 Hz. Duh... So what I just described is the most difficult way to find something really obvious. Tomorrow I will get the mapping of the depth. After thinking about it a little, I think a nice addition to my firmware would be to allow data maps in the XML config file such that the chorus envelope could be customized by the user. Just thinking in text...
Log 7/27/11:
I'm really excited. I can set a patch on the MP-1 via my code/board. It took several hours of debugging but it works!!! The bugs were fairly simple (mostly just stupid mistakes). I'm going to be on vacation for a few days so this was a great place to wrap up until I get back. Next, I'll have to do a little code cleanup and then I can move on to hooking my patch container classes up to the patch setting code and display code. Still quite a bit to do but this was a nice hurdle to clear.
Log 7/25/11:
Soldered up an adapter to allow me to connect the I/O of my dev board to the necessary pins of the z80 cpu socket. I used a breadboard with some headers to fit in the chip socket and wired in A0, A1, A2, A3, D0, D1, D2, D3, D4, D5, D6, D7, and M1. I grounded pin 20 of the CPU adapter board as I had no need for that pin but needed it to be low for the chipselect demux chips to work. The headers connect to wires that have headers at the other end to fit the dev board connectors. I hooked up the board to my MP-1. After a little debugging (oops soldered the ground wire for pin 20 to the wrong pin - That whole top/bottom pinout thing), I was able to get the LEDS on the front panel to light up. The multiplexing wasn't exactly stellar as there was quite a bit of flickering, but I expected that since I was incrementing the display digit in .net and calling down to c to update the segments and mutiplex digit. I will fix this by scheduling a task in c that fires every millisecond to update the next digit/switch LEDs. The .net code will only call down when necessary to update the source data for the LEDs. Also, I may have successfully set a patch, but I ran out of time over lunch to verify that the patch was set correctly so I'll verify that tomorrow. Overall, it was pretty exciting to see some signs of life from the MP-1 without a z80 installed!!!
Update: I rewrote the display code as a task like I discussed above. The LED's look good now. The first picture above shows all of the LED's turned on by the development board. The second picture shows how the development board is connected. Kind of Frankenstein but it was made from stuff I had lying around...
Log 7/21/11:
Wrote code to control the TC9170 eq level control. Also realized a flaw in some of my timing. Not a big deal, but I need another IO line (now up to 13) to enable/disable the demultiplexors that do the chip selecting. Disabling the chip is what causes the active high clock signal that the flip flops use to latch their data. So my timing goes as follows:
- Start with Demultiplexors disabled (pin 4 high)
- Set A0, A1, A2, and A3 to select the chip that I want to write to. Note: there is a table in the schematic where the left number (address) is the number to set these lines to.
- Start bit banging the the data lines (the number on the right in the table corresponds to the data line that will be bit banged for a given function). Each time a data line is changed, enable and disable the demultiplexors such that the flip flops latch the data on the data lines (ie the chip select will go low and then high which is the flip flop data clock).
I'm getting really close to hooking it up to the MP-1 to start debugging but still need to set the voicing lines, effects loop line, and chorus enable line. Should be pretty simple to get those done. Right now it's looking like it will take about 1.5 ms to set all of the analog control parameters into the chips.
Log 7/20/11:
Investigated the order in which the strobe lines fire for the attenuation chips. The picture above shows the scope I've been using to figure out all of the timings. I posted some information on the strobe timing here: Chip Selects (IO Multiplexing).
Also, found an error on the schematic for the MP-1. U27 pin 10 is shown as connected to ATT CLK, however, it also shows pin 15 as being connected to ATT CLK. Pin 15 is shown correctly and pin 10 should read ATT DAT. I started adding the code to access the attenuation chips and write patch data to them. Made good progress but still need to write the EQ code.
Log 7/19/11:
Wrote managed code to abstract and send the data, clock, and strobe signals for the TC9176P. The code works but, as I expected, the .Net Micro Framework is slow at bit banging GPIO. I found that sending the 20 bits of data, while clocking and strobing took about 4ms. I would like that to be more like under 100us. I think I could tighten up the managed code a bit as I was running debug code but I think in the end I will need to move the GPIO code down to unmanaged using RLP. That should take care of the issue. Hopefully I don't run into similar issues with interrupt latencies when I get to the midi implementation.
Update: Couldn't call it a day just yet so I wrote the equivalent volume setting functions in unmanaged C and loaded them using RLP. The 20 bits of data with clock and strobe are now taking a little over 80us. I think that will be fine for this application. So, the moral of the story is that I will need to do any time critical I/O in unmanaged code. For now I will continue to use RLP, but I may in the future want to do a full .Net Micro Framework port and do the unmanaged code down there. The advantage to this would be that I could then have the flexability of someday creating my own board from scratch that runs this firmware. RLP is a GHI technology that is locked down and only useable on their boards which is ok for now... Next step is to get through the gating and multiplexing logic chips.
Log 7/18/11:
Finished mapping the front pannel user settings to the internal settings of the analog control chips for OD1, OD2, Bass, Mid, Treble, and Presence. I started posting the data in the technical section under parameter mapping. Will finish updating the data later. So, I think I have enough information now to start coding up my dev board to set the analog parameters (other than chorus - still need to figure some things out for that). Next logical step is to get my board to set a patch on the MP-1. Will start on that tomorrow.
Log 7/15/11:
I reverse engineered the mapping of the master volume level on the front panel to the serial data being set into the TC9176P chip (U13). After tediously recording all of the the data it all came out to a pretty simple mapping. When the master volume is set to 10.0 on the front panel, 0 db of attenuation will be applied to either the left or right channel (more on that later). Each time the master volume is decremented, 2db of attenuation is added all the way until it gets down to 0.0 on the front panel where the attenuation gets set to 78 db which is the maximum the chip can do (so from 0.1 to 0.0 it goes from -54 to -78).
When the voicing is set to solid state, this attenuation is applied to the R channel and the L channel is set to the maximum attenuation the chip can do which is 78db. When a tube voicing is selected, the attentuation described above is applied to the L channel and the right channel is set to the max attenuation of 78db. Note: it will always set the levels of both channels when the master volume is incremented or decremented. So there are two 20 bit data frames strobed in (see the data sheet for how the 20 bits are formatted). Also, the active channel will always be the first frame.
I also, performed the following tasks:
- Posted a spreadsheet of some LFO data in the chorus section
- Dug up a 40 pin Zif (zero insertion force) socket which I put the z80 into so I can remove it easily once I start hooking up my IO
- Added data sheets on the technical section that I am looking at often
Log 7/13/11:
Today I worked some more on reverse engineering the chorus algorithm. I used a Tektronix scope with data digital capture/decode ability to capture the eight bits of parallel data (triggered by the strobe line) on the output side of the LFO shift register. I then stored that data to a CSV file such that I could import it into a spreadsheet. After that I did an X-Y plot of the data. It appeared a little different than I expected. The bottom of the wave was very sinusoidal but the top was very sharp almost like the bottom of a rectified sine wave. I confirmed that I was capturing the data correctly by comparing the plot to what the analog signal looked like on the scope after the ladder network. It looked pretty much the same but a little less sharp on top due to the capacitor in that part of the circuit smoothing it. I'd really like to get my hands on a bin file of the 2.x revision of the MP-1 firmware such that I can see if this was changed in later revisions. I am using the 1.38 version. I'll post the data in the Chorus Technical data section: Chorus Description
I've also started rethinking the way I'm going to tackle this project. After posting on the ADADepot.com forums http://www.adadepot.com/forum/index.php/topic,13016.msg107075/topicseen.html#new, I read between the lines that some people might want the updated micro controller features but not necessarily a whole new UI. I'm thinking it might be a better starting point, albeit more work, to just go after making my dev board plug into the socket where the z80 was and duplicate the stock firmware using all of the stock decode chips etc. From what I can see on the schematic (though it is pretty blurry), I think it should take about 13 I/O lines to do it. I will still be targeting revamping the UI as I go but by going this direction, I think many people could benefit from an inexpensive upgrade to their MP-1 and if they later decide to, could upgrade the UI as a separate plugin/firmware upgrade.
Log 7/12/11:
Refactored the info on this site to be more organized. Also, I was working with the mini SD card and was having problems with my dev board. Turns out that there is a power problem on the Fez Panda II where certain sd cards (mine's a Sandisk 4GB) require a good deal of power when plugged in. I had to solder a few capacitors onto the board and that solved the issue. Now I can read the card reliably. The problem/solution was outlined here by someone else with the same problem:
Log 7/10/11:
I have written patch classes/collections for holding patch data. I started with some patch data obtained from http://adadepot.com that lists patch setting of some famous guitarists. I then hooked up the rotary code to the patch selection code such that I could turn the rotary and cycle through the selected patch from the patch list. After that, I connected up a 16x2 lcd panel and displayed the patch number and name on the display. I know, I know it's pretty simplistic but hey these little building blocks are what get the progress really rolling. I've also started working on reading the patch data from an xml file located on a micro sd card.
In the meantime, I started working on reverse engineering the chorus effect on the MP-1. I will need this to be accounted for in my design because the delay times of the chorus come directly from the Z80 albeit in an indirect way. For a description of how the Chorus works, go to: Chorus Description
Note: one thing that struck me as being odd (possibly a bug in the MP-1 1.38 firmware) is that when the data line is clocked into the shift register, for some reason 9 data bits are being clocked in. This would cause one bit to fall off before the strobe line goes high. I can't think of any reason it would do this so I'm guessing it was unintended I'll have to investigate further to confirm what I'm saying. I do know that some bugs were fixed for the chorus in later versions so maybe that's what the deal is.
|
|