zoom-zt2 icon indicating copy to clipboard operation
zoom-zt2 copied to clipboard

G1on/G1xon

Open namp opened this issue 4 years ago • 26 comments

Does this work with G1on/G1xon and, in general, with pedals that use the ZDL format?

Thanks

namp avatar May 10 '20 09:05 namp

In theory the parse might work, but as you noted the pedal uses the (older) ZDL and ZDT formats and these appear quite different.

In theory the parsing could be changed to work with the older format, but I don't have one of these pedals to test against.... and I'm not sure the same uploading MIDI commands could be used.

The G1Four's ZT2 file starts

G1_FOUR_v1.10_Win_E/zt2$ hexdump -C FLST_SEQ.ZT2 | head 
00000000  3e 3e 3e 00 00 00 00 00  00 00 00 00 00 00 00 00  |>>>.............|
00000010  00 00 00 00 00 00 00 00  00 00 42 59 50 41 53 53  |..........BYPASS|
00000020  2e 5a 44 32 00 00 00 00  00 00 00 00 01 00 00 00  |.ZD2............|
00000030  00 00 00 00 3c 3c 3c 00  00 00 00 00 00 00 00 00  |....<<<.........|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 3e 3e  |..............>>|
00000050  3e 00 01 00 00 00 00 00  00 00 00 00 00 00 00 00  |>...............|
00000060  00 00 00 00 00 00 00 00  43 4f 4d 50 2e 5a 44 32  |........COMP.ZD2|
00000070  00 00 00 00 00 31 2e 32  30 00 01 10 00 00 01 00  |.....1.20.......|
00000080  00 00 52 41 43 4b 43 4f  4d 50 2e 5a 44 32 00 31  |..RACKCOMP.ZD2.1|
00000090  2e 32 30 00 01 20 00 00  01 00 00 00 53 4c 57 41  |.20.. ......SLWA|

Whereas the G1on's starts```

G1on_v1.21_Win_E$ hexdump -C FLST_SEQ.ZDT | head
00000000  3e 3e 3e 00 00 00 00 00  00 00 00 00 00 00 00 00  |>>>.............|
00000010  00 00 00 00 00 00 00 00  00 00 3c 3c 3c 00 00 00  |..........<<<...|
00000020  00 00 00 00 00 00 00 3e  3e 3e 00 01 00 00 00 00  |.......>>>......|
00000030  00 00 00 00 43 4f 4d 50  2e 5a 44 4c 00 00 00 00  |....COMP.ZDL....|
00000040  00 4f 50 54 43 4f 4d 50  2e 5a 44 4c 00 00 31 36  |.OPTCOMP.ZDL..16|
00000050  30 5f 43 4f 4d 50 2e 5a  44 4c 00 53 4c 57 41 54  |0_COMP.ZDL.SLWAT|
00000060  4b 2e 5a 44 4c 00 00 00  5a 4e 52 2e 5a 44 4c 00  |K.ZDL...ZNR.ZDL.|
00000070  00 00 00 00 00 4e 4f 49  53 45 47 54 45 2e 5a 44  |.....NOISEGTE.ZD|
00000080  4c 00 3c 3c 3c 00 01 00  00 00 00 00 00 00 00 3e  |L.<<<..........>|
00000090  3e 3e 00 02 00 00 00 00  00 00 00 00 47 45 51 2e  |>>..........GEQ.|

mungewell avatar May 11 '20 03:05 mungewell

Why not also check out https://github.com/SysExTones/g1on

I am seriously thinking about getting a g1on - the particles reverb sounds amazing. I have an eBAY bid right now ... if I look I will lose it ... wish me luck.

Also the g1on seems to be able to hold 100 patches cf 50 on g1x four.

shooking avatar Dec 10 '20 18:12 shooking

Hi, Regarding the G1Xon, has anyone figured out how the effects are arranged in the response you get with the command F0 52 00 64 29 F7? I started rearranging effects, or replace them with bypass, but it's still too complicated.

My first goal is to be able to tell how many effects there are in a patch and if they are enabled or not.

This is the original A0 patch: F0 52 00 64 28 40 21 00 00 02 03 08 00 00 64 00 00 00 00 00 00 04 00 00 00 00 21 00 00 01 16 00 50 00 00 00 00 00 0C 00 00 00 00 00 00 25 00 41 00 00 06 03 20 00 01 64 00 00 00 00 00 00 00 00 00 00 00 11 00 28 00 10 65 30 00 10 40 20 01 00 0C 00 00 00 00 02 00 00 61 02 00 48 04 4D 70 00 57 20 66 65 66 09 06 26 00 00 00 00 20 00 64 22 00 00 00 20 03 00 00 00 00 4D 55 53 45 00 55 4D 20 20 20 20 00 F7

I would appreciate any help. Thanks.

dkts2000 avatar Jan 18 '21 11:01 dkts2000

@dkts2000 My memory is a little fuzzy, but I do see the '0x29 - download current patch' command in my midi_notes which i posted here: https://github.com/Barsik-Barbosik/Zoom-Firmware-Editor/files/5662857/midi_notes.txt

I also did some work with uploading/downloading specific patches in the form of a 'PTCF' file. I have a script on my local machine which interpreted these, but it doesn't look like it got published yet.... I'll see if I can get those uploaded in the next couple of days. https://github.com/mungewell/zoom-zt2/blob/master/zoomzt2.py#L368

        packet = bytearray(b"\x52\x00\x6e\x09\x00")
        packet.append(int(location/10)-1)
        packet.append(location % 10)

Looking at your hex dump there does not seem to be ASCII name(s) at the start, even so this is a similar format... but without the text descriptors.

mungewell avatar Jan 18 '21 21:01 mungewell

@dkts2000 You may also benefit looking into the alternate way of reading the Patch set-up. https://github.com/mungewell/zoom-zt2/issues/13

This was more like reading the contents of the display, rather than reading the patch setup (CMD 0x29 and CMD 0x09).

mungewell avatar Jan 18 '21 21:01 mungewell

@dkts2000 Also note that the string "4D 55 53 45 00 55 4D 20 20 20 20" has a '00' in the middle, this suggests that this is packed (8bit -> 7bit, for transmission over Midi). You may need to unpack it before the block of data makes sense.

https://github.com/mungewell/zoom-zt2/blob/master/zoomzt2.py#L163

mungewell avatar Jan 18 '21 22:01 mungewell

@dkts2000 unpack.py.txt

$ python3 unpack.py 
00000000: A1 00 00 02 03 08 00 64  00 00 00 00 00 00 00 00  .......d........
00000010: 00 00 A1 00 00 16 00 50  00 00 00 80 0C 00 00 00  .......P........
00000020: 00 00 00 00 C1 00 00 86  03 A0 01 64 00 00 00 00  ...........d....
00000030: 00 00 00 00 00 00 11 00  00 90 65 B0 00 10 40 01  ..........e...@.
00000040: 80 0C 00 00 00 00 00 00  61 02 00 C8 04 F0 00 57  ........a......W
00000050: A0 E6 65 E6 06 26 00 80  00 00 A0 64 22 00 00 00  ..e..&.....d"...
00000060: 20 03 00 00 00 4D 55 53  45 55 4D 20 20 20 20 00   ....MUSEUM    .

mungewell avatar Jan 19 '21 05:01 mungewell

Thanks for info! It seems i can't fully understand this 8bit to 7bit convertion. I tried to "interpret" your unpack function to c++ but i don't get the same results.

#include <iostream>
#include <string>
#include <cmath>

using namespace std;

void unpack (uint8_t * sysex, uint8_t * data, uint8_t len)
{
  // Unpack data 7bit to 8bit, MSBs in first byte
  //data = bytearray(b"")
  int loop = -1;
  uint8_t hibits = 0;

  for (int i = 0; i < len; i++) { //byte in packet:
      uint8_t byt = sysex[i];
      if (loop != -1)
	{
	  uint8_t p = pow (2, loop);
	  if (hibits & p) {
	      byt = 0x80 + byt; //data.append(128 + byte)
	  //} else {
	      //data.append(byte)
	  }
	  data[i] = byt;
	  loop = loop - 1;
	} else {
	  hibits = byt;
	  // do we need to acount for short sets (at end of block block)?
	  loop = 6;
	}
  }
}

int main()
{
  uint8_t dataBuffer[] =
    { 0x40, 0x21, 0x00, 0x00, 0x02, 0x03, 0x08, 0x00, 0x00, 0x64, 0x00, 0x00,
0x00, 0x00,
    0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x01, 0x16,
      0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x41, 0x00, 0x00,
      0x06, 0x03, 0x20, 0x00, 0x01, 0x64, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x28,
      0x00, 0x10, 0x65, 0x30, 0x00, 0x10, 0x40, 0x20,
    0x01, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x61, 0x02,
      0x00, 0x48, 0x04, 0x4D, 0x70, 0x00, 0x57, 0x20,
    0x66, 0x65, 0x66, 0x09, 0x06, 0x26, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
      0x64, 0x22, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00,
    0x00, 0x00, 0x00,
    0x4D, 0x55, 0x53, 0x45, 0x00, 0x55, 0x4D, 0x20, 0x20, 0x20, 0x20, 0x00
  };

  uint8_t outputBuffer[200] = { 0 };
  
  int bufsize = sizeof (dataBuffer) / sizeof (dataBuffer[0]);
  
  unpack (dataBuffer, outputBuffer, bufsize);
  
}

this code gives me: 00 A1 00 00 02 03 08 00 00 64 00 00 00 00 00 00 00 00 00 00 00 A1 00 00 00 16 00 50 00 00 00 80 00 0C 00 00 00 00 00 00 00 00 C1 00 00 86 03 A0 00 01 64 00 00 00 00 00 00 00 00 00 00 00 11 00 00 00 90 65 B0 00 10 40 00 01 80 0C 00 00 00 00 00 00 00 61 02 00 C8 04 00 F0 00 57 A0 E6 65 E6 00 06 26 00 80 00 00 A0 00 64 22 00 00 00 20 03 00 00 00 00 4D 55 53 45 00 55 4D 20 20 20 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

but obviously something is wrong.

dkts2000 avatar Jan 22 '21 09:01 dkts2000

are you in a EU timezone @dkts2000 ? if so maybe we can also look at this together? My plan (see my YouTube) is to get a Ctrlr panel working for these pedals with a 21.5" touch screen for the gigs I play.

I recently got a B1on for the Particle reverb - other than that I have the G1XFour and B1XFour [[sure before I realised the great work done here that makes them near identical!]].

Anyhow have you considered adding some debug to Mungewell's python to allow you to cross check against your C++? I also ported to Lua and am using on Ctrlr running on a Raspberry Pi 4. The Pi 4 is also capable of running Mungewell's code, once you hack it a little because of import changes.

I will be making more YT and posting to these forums as I find more about the B1On which is pretty darn similar to the G1on. I strongly suspect we can use the same "get file content from midi" on these devices.

WHAT DID I HACK TO CHECK ALGORITHMS?

In the zoomzt2.py I added couple of routines locally:

def printhex(direct, msg):
    print(direct)
    l = []
    numchar=0
    for n in msg:
        l.append(n)
        numchar=numchar+1
        if numchar % 8 == 0:
            print(" ".join( "{0:#0{1}x}".format(int( m ), 4) for m in l))
            l = []
    if l is not None:
        print(" ".join( "{0:#0{1}x}".format(int( m ), 4) for m in l))

def printExtrahex(direct, msg):
    print(direct + " 0xf0 ")
    printhex(direct, msg)
    print(direct + " 0xf7 ")

def sniffMidiOut(mtype, data, printme = False):
    if printme == True:
        print("sniffMidiOut")
        if mtype == "sysex":
            printExtrahex("===>\t", data)
        else:
            printhex("===>\t", data)
    return mido.Message(mtype, data = data)


def sniffMidiIn(self, printme = False):
    msg = self.inport.receive()
    if printme == True:
        print("sniffMidiIn")
        if msg.type == "sysex":
            printExtrahex("<====\t", msg.data)
        else:
            printhex("<====\t", msg.data)
    return msg

then added some more annotations to print the blocks:

    def file_download(self, name):
        # download file from pedal to PC
        print("In file_download {}".format(name))
        packet = bytearray(b"\x52\x00\x6e\x60\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00")
        print("packet")
        head, tail = os.path.split(name)
        self.filename(packet, tail)

        msg = sniffMidiOut("sysex", data = packet)
        # msg = mido.Message("sysex", data = packet)
        self.outport.send(msg); sleep(0); msg = sniffMidiIn(self)
        
        # Read parts 1 through 17 - refers to FLST_SEQ, possibly larger
        data = bytearray(b"")
        while True:
            sData = [0x52, 0x00, 0x6e, 0x60, 0x05, 0x00]
            msg = sniffMidiOut("sysex", data=sData)
            #msg = mido.Message("sysex", data = [0x52, 0x00, 0x6e, 0x60, 0x05, 0x00])
            self.outport.send(msg); sleep(0); msg = sniffMidiIn(self)

            #sData = [0x52, 0x00, 0x6e, 0x60, 0x22, 0x14, 0x2f, 0x60, 0x00, 0x0c, 0x00, 0x04, 0x00, 0x00, 0x00]
            sData = [0x52, 0x00, 0x6e, 0x60, 0x22, 0x14, 0x2f, 0x60, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x00, 0x00]
            msg = sniffMidiOut("sysex", data=sData)
            #msg = mido.Message("sysex", data = [0x52, 0x00, 0x6e, 0x60, 0x22, 0x14, 0x2f, 0x60, 0x00, 0x0c, 0x00, 0x04, 0x00, 0x00, 0x00])
            self.outport.send(msg); sleep(0); msg = sniffMidiIn(self)

            sData = [0x52, 0x00, 0x6e, 0x60, 0x05, 0x00]
            msg = sniffMidiOut("sysex", data=sData)
            #msg = mido.Message("sysex", data = [0x52, 0x00, 0x6e, 0x60, 0x05, 0x00])
            self.outport.send(msg); sleep(0); msg = sniffMidiIn(self)

            #decode received data
            packet = msg.data
            length = int(packet[9]) * 128 + int(packet[8])
            if length == 0:
                print("WE GOT ZERO LEN BACK")
                break
            print("WE GOT {} LEN BACK".format(length))
            block = self.unpack(packet[10:10 + length + int(length/7) + 1])

            print("HERE IS THE BLOCK!! {} from {}".format(len(block), len(packet)+2))
            printhex("BLOCK ", block)
            # confirm checksum (last 5 bytes of packet)
            # note: mido packet does not have SysEx prefix/postfix
            checksum = packet[-5] + (packet[-4] << 7) + (packet[-3] << 14) \
                    + (packet[-2] << 21) + ((packet[-1] & 0x0F) << 28) 
            if (checksum ^ 0xFFFFFFFF) == binascii.crc32(block):
                data = data + block
            else:
                print("Checksum error", hex(checksum))
                break
        return(data)

This was especially handy when comparing Ctrlr - turns out some Linux low level Midi has a 512 byte max buffer. So I found some of the SYSEX has a buffer size 0x04 ... so I dropped it to 0x02 and went round twice ... got the data.

shooking avatar Jan 22 '21 10:01 shooking

Well, my c++ and python knowledge is minimal, so i won't be much of a help. I try to make an arduino based controller for the G1Xon. I can receive the SysEx responses from the g1xon but i can't decode them. My first goal is to be able to tell how many effects there are in a patch and if they are enabled or not.

dkts2000 avatar Jan 22 '21 10:01 dkts2000

OK so now I see why you want C/C++ for Arduino. I also do a lot of Arduino and more recently ESP32 but then I realized the Pi 4 has much more power, has GPIO if one needs it BUT with a touchscreen well ... much easier than playing with a load of IC4067.

Let me see if I can check your C/C++. Will try at the weekend.

shooking avatar Jan 22 '21 11:01 shooking

I found another piece of code for decoding :

uint8_t sysex_to_data(uint8_t *sysex, uint8_t *data, uint8_t len) {
  uint8_t cnt;
  uint8_t cnt2 = 0;
  uint8_t bits = 0;
  for (cnt = 0; cnt < len; cnt++) {
    if ((cnt % 8) == 0) {
      bits = sysex[cnt];
    } else {
      data[cnt2++] = sysex[cnt] | ((bits & 1) << 7);
      bits >>= 1;
    }
  }
  return cnt2;
}

this gives: 21 00 00 02 03 08 80 64 00 00 00 00 00 00 00 00 80 00 21 00 00 96 00 50 00 00 00 00 0C 00 00 00 00 00 00 80 41 80 00 06 83 20 01 64 00 00 00 00 00 00 00 00 00 00 11 00 00 10 65 B0 00 90 40 01 00 0C 00 00 80 00 00 80 61 02 00 48 04 F0 00 D7 A0 66 65 E6 86 26 00 80 00 00 20 64 22 00 00 00 20 03 00 00 00 4D 55 53 45 55 4D 20 20 20 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

if that helps

dkts2000 avatar Jan 22 '21 11:01 dkts2000

I was going to say I hacked your original code to pront out the into.

#include <iostream>
#include <iomanip>

#include <string>
#include <cmath>

using namespace std;

void unpack (uint8_t * sysex, uint8_t * data, uint8_t len)
{
  // Unpack data 7bit to 8bit, MSBs in first byte
  //data = bytearray(b"")
  int loop = -1;
  uint8_t hibits = 0;

  for (int i = 0; i < len; i++) { //byte in packet:
      uint8_t byt = sysex[i];
      if (loop != -1)
	{
	  uint8_t p = pow (2, loop);
	  if (hibits & p) {
	      byt = 0x80 + byt; //data.append(128 + byte)
	  //} else {
	      //data.append(byte)
	  }
	  data[i] = byt;
	  loop = loop - 1;
	} else {
	  hibits = byt;
	  // do we need to acount for short sets (at end of block block)?
	  loop = 6;
	}
      	std::cout << std::setfill('0') << std::setw(2) << std::hex << (0xff & (u
nsigned int)data[i]) << " ";
      if (i % 8 == 0) std::cout << std::endl;
  }
}

int main()
{
  uint8_t dataBuffer[] =
    { 0x40, 0x21, 0x00, 0x00, 0x02, 0x03, 0x08, 0x00, 0x00, 0x64, 0x00, 0x00,
0x00, 0x00,
    0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x01, 0x16,
      0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x41, 0x00, 0x00,
      0x06, 0x03, 0x20, 0x00, 0x01, 0x64, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x28,
      0x00, 0x10, 0x65, 0x30, 0x00, 0x10, 0x40, 0x20,
    0x01, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x61, 0x02,
      0x00, 0x48, 0x04, 0x4D, 0x70, 0x00, 0x57, 0x20,
    0x66, 0x65, 0x66, 0x09, 0x06, 0x26, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
      0x64, 0x22, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00,
    0x00, 0x00, 0x00,
    0x4D, 0x55, 0x53, 0x45, 0x00, 0x55, 0x4D, 0x20, 0x20, 0x20, 0x20, 0x00
  };

  uint8_t outputBuffer[200] = { 0 };
  
  int bufsize = sizeof (dataBuffer) / sizeof (dataBuffer[0]);
  
  unpack (dataBuffer, outputBuffer, bufsize);
  
}

What I will do now (sorry in the middle of day job) is compare with an existing decode. I dont know why you use i for input AND output? Shouldnt the output be 7/8's as large - sorry I not looked at the code too deeply. Just added on some output diagnostics.

shooking avatar Jan 22 '21 11:01 shooking

i'm sure my interpretation of the python code is wrong. Try the other piece of code i posted earlier. i found it here: https://blogs.bl0rg.net/netzstaub/2008/08/14/encoding-8-bit-data-in-midi-sysex/

dkts2000 avatar Jan 22 '21 12:01 dkts2000

So I think you are wrong iterator. Something like this in your original code - notice we track input with "i" and output with "j"

pi@raspberrypi:~/Software/zoom-zt2 $ more test1.cpp
#include <iostream>
#include <iomanip>

#include <string>
#include <cmath>

using namespace std;

void
unpack (uint8_t * sysex, uint8_t * data, uint8_t len)
{
  // Unpack data 7bit to 8bit, MSBs in first byte
  //data = bytearray(b"")
  int loop = -1;
  uint8_t hibits = 0;

  int j = 0;
  for (int i = 0; i < len; i++)
    {				//byte in packet:
      uint8_t byt = sysex[i];
      if (loop != -1)
	{
	  uint8_t p = pow (2, loop);
	  if (hibits & p)
	    {
	      byt = 0x80 + byt;	//data.append(128 + byte)
	      //} else {
	      //data.append(byte)
	    }
	  data[j++] = byt;
	  loop = loop - 1;
	}
      else
	{
	  hibits = byt;
	  // do we need to acount for short sets (at end of block block)?
	  loop = 6;
	}
      std::cout << std::setfill ('0') << std::
	setw (2) << std::hex << (0xff & (unsigned int) data[j-1]) << " ";
      if (i % 8 == 0)
	std::cout << std::endl;
    }
}

int
main ()
{
  uint8_t dataBuffer[] =
    { 0x40, 0x21, 0x00, 0x00, 0x02, 0x03, 0x08, 0x00, 0x00, 0x64, 0x00, 0x00,
    0x00, 0x00,
    0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x01, 0x16,
    0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x41, 0x00, 0x00,
    0x06, 0x03, 0x20, 0x00, 0x01, 0x64, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x28,
    0x00, 0x10, 0x65, 0x30, 0x00, 0x10, 0x40, 0x20,
    0x01, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x61, 0x02,
    0x00, 0x48, 0x04, 0x4D, 0x70, 0x00, 0x57, 0x20,
    0x66, 0x65, 0x66, 0x09, 0x06, 0x26, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
    0x64, 0x22, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00,
    0x00, 0x00, 0x00,
    0x4D, 0x55, 0x53, 0x45, 0x00, 0x55, 0x4D, 0x20, 0x20, 0x20, 0x20, 0x00
  };

  uint8_t outputBuffer[200] = { 0 };

  int bufsize = sizeof (dataBuffer) / sizeof (dataBuffer[0]);

  unpack (dataBuffer, outputBuffer, bufsize);

}
pi@raspberrypi:~/Software/zoom-zt2 $ ./test1
b6 
a1 00 00 02 03 08 00 00 
64 00 00 00 00 00 00 00 
00 00 00 00 a1 00 00 00 
16 00 50 00 00 00 80 80 
0c 00 00 00 00 00 00 00 
00 c1 00 00 86 03 a0 a0 
01 64 00 00 00 00 00 00 
00 00 00 00 00 11 00 00 
00 90 65 b0 00 10 40 40 
01 80 0c 00 00 00 00 00 
00 00 61 02 00 c8 04 04 
f0 00 57 a0 e6 65 e6 e6 
06 26 00 80 00 00 a0 a0 
64 22 00 00 00 20 03 03 
00 00 00 4d 55 53 45 45 
55 4d 20 20 20 20 00 

shooking avatar Jan 22 '21 13:01 shooking

you are right! if you ommit the cout part:

      std::cout << std::setfill ('0') << std::
	setw (2) << std::hex << (0xff & (unsigned int) data[j-1]) << " ";
      if (i % 8 == 0)
	std::cout << std::endl;

which i think adds some 00s between the parts, and outpout the outputBuffer you get :

A1 00 00 02 03 08 00 64 00 00 00 00 00 00 00 00 00 00 A1 00 00 16 00 50 00 00 00 80 0C 00 00 00 00 00 00 00 C1 00 00 86 03 A0 01 64 00 00 00 00 00 00 00 00 00 00 11 00 00 90 65 B0 00 10 40 01 80 0C 00 00 00 00 00 00 61 02 00 C8 04 F0 00 57 A0 E6 65 E6 06 26 00 80 00 00 A0 64 22 00 00 00 20 03 00 00 00 4D 55 53 45 55 4D 20 20 20 20 00

which is the same as the one mungewell gave

00000000: A1 00 00 02 03 08 00 64  00 00 00 00 00 00 00 00  .......d........
00000010: 00 00 A1 00 00 16 00 50  00 00 00 80 0C 00 00 00  .......P........
00000020: 00 00 00 00 C1 00 00 86  03 A0 01 64 00 00 00 00  ...........d....
00000030: 00 00 00 00 00 00 11 00  00 90 65 B0 00 10 40 01  ..........e...@.
00000040: 80 0C 00 00 00 00 00 00  61 02 00 C8 04 F0 00 57  ........a......W
00000050: A0 E6 65 E6 06 26 00 80  00 00 A0 64 22 00 00 00  ..e..&.....d"...
00000060: 20 03 00 00 00 4D 55 53  45 55 4D 20 20 20 20 00   ....MUSEUM    .

Thanks! i'll try to implement the pack function as well and then experiment with different patches to try to figure out the patch parts. I'm curious though, why the other code i found gives different result.

dkts2000 avatar Jan 22 '21 14:01 dkts2000

Hey! I left the cout there to show you what it was doing :-)

I even wrote a better version that produces this:

Input: 
40 21 00 00 02 03 08 00 
Output: 
a1 00 00 02 03 08 00 
Input: 
00 64 00 00 00 00 00 00 
Output: 
64 00 00 00 00 00 00 
Input: 
04 00 00 00 00 21 00 00 
Output: 
00 00 00 00 a1 00 00 
Input: 
01 16 00 50 00 00 00 00 
...

so you can see the transformation. You might like to do the same on the other code you found to understand it... or just accept the unpack is taking 8 bytes and transforming to 7 ... so you cannot use "i" for both input and output stream.

Anyhow I am starting to play with the B1On as well as the other B1XFour/G1XFour. Would be cool to collaborate some how.

BTW you notice - of the special BYTE is 00 you can just drain next 8 chars and move to next chunk. You do have to be careful when there are insufficient input bytes to encode 8 -> 7.

shooking avatar Jan 22 '21 14:01 shooking

I can't convert the pack function to c++.

    def pack(self, data):
        # Pack 8bit data into 7bit, MSB's in first byte followed
        # by 7 bytes (bits 6..0).
        packet = bytearray(b"")
        encode = bytearray(b"\x00")

        for byte in data:
            encode[0] = encode[0] + ((byte & 0x80) >> len(encode))
            encode.append(byte & 0x7f)

            if len(encode) > 7:
                packet = packet + encode
                encode = bytearray(b"\x00")

        # don't forget to add last few bytes
        if len(encode) > 1:
            packet = packet + encode

        return(packet)

packet and encode are uint8_t arrays?

EDIT: finally i made a working function in c++. I leave it here in case someone wants it:

std::vector<uint8_t> encodePacket(std::vector<uint8_t> data)
{
  // # Pack 8bit data into 7bit, MSB's in first byte followed
  //# by 7 bytes (bits 6..0).
  auto len = data.size();//is this right??? or iterate using for (size_t i=0;i<data.size()....)?
  std::vector<uint8_t> packet;    //packet = bytearray(b"")
  std::vector<uint8_t> encode(1); //encode = bytearray(b"\x00")
  encode[0] = 0;

  for (int i = 0; i < len; i++)  { //for byte in data:
    uint8_t b = data[i];
    auto len_encode=encode.size();//???. code also works if i use >> encode.size() for the bit shifting
    encode[0] = encode[0] + ((b & 0x80) >> len_encode); //encode[0] = encode[0] + ((byte & 0x80) >> len(encode))
    encode.push_back(b & 0x7f);    //encode.append(byte & 0x7f)

    if (encode.size() > 7)    { //if len(encode) > 7:
      packet.insert(packet.end(), encode.begin(), encode.end()); //packet = packet + encode
      encode.clear(); //encode = bytearray(b"\x00")
      encode.push_back(0);
    }
  }
  //# don't forget to add last few bytes
  if (encode.size() > 1) { //if len(encode) > 1:
    packet.insert(packet.end(), encode.begin(), encode.end()); //packet = packet + encode
  }
  return packet;
}

dkts2000 avatar Jan 22 '21 17:01 dkts2000

can u be a little more specific? so all it is doing is the reverse of the unpack. Previous 8 sysex bytes get turned into 7 8-bit bytes for processing in your local computer (in your case Arduino but I more or less gave up on them for reasonably processing tasks because they hit nasty limits - and since I used to use embedded processors professionally I know when to quit with them).

Now your mission is to take 7 8-bit bytes and turn it into 8 7 bit bytes.

In C++ you have Vectors, Lists etc. Arduino has a Standard Library clone (I used it to create arpegiator unit for a Midi controller).

So create a blank vector of bytes (unsigned chars). read in 7 bytes from the packed data. create a new byte from top bits of next 7 bytes. that I think is the first. Then push_back the 7 bytes & 0x7F to mask off the top bits.

And you can see that is what Mungwell is doing - but in Python. I suspect the new phantom byte is (pseudo code)

pb = 0
for i = 0 .. 6
# check if the byte[0] top bit is top bit of first byte
  pb |= (byte[i]&128)>>(6 - i)
# but if byte[0] top bit is least significant bit it might be
#   pb |= (byte[i]&128)>>i
# actually look at Mungwell's code. The encode starts empty list.
# then he uses len(encode) as the shift - and he check BEFORE he expands.
# so I think we should use  pb |= (byte[i]&128)>>i
end

That would encode the top bit
then we would push the pb first.
Then iterator 0 ... 6 and push back byte[i] & 0x7F
But - check out how elegant Mungewell's code is ... the does it in one pass. Then when he gets to the next "pb" see how he seeds it. I look at a LOT of code and this is really neat stuff.

shooking avatar Jan 22 '21 18:01 shooking

BTW I added SYSEX for controlling parameters on a B1ON ... my guess is change 65 to come other string on the G1ON and it should work. Will be checking some more and publishing as I find them.

shooking avatar Jan 22 '21 19:01 shooking

Ok I think I sort of got it. What I notice on the B1On cf the B1X is often times it is prudent to resend the "EditorOn" - this was also pointed out in the other link I added above. It's only now I have on that I realise way. But in the meantime I also got into the Zoom-Firmware-Editor that Mungewell also worked on. And, other than it being WAY too cold to go into garage to test firmware upgrade on PC, seems to allow me to take a B1XFour's firmware, pull out Particles and other FX from a Zoom 70 and inject them. Let's see whether they are all in the Guard blocker or now.

Back to the B1ON.

I am using the following helper scripts on the Raspberry Pi.

pi@raspberrypi:~/Software/ZoomPedalB1ON $ more EditorOn.sh 
export MIDI_DEV=`amidi -l | grep ZOOM | awk '{print $2}'`
amidi -p ${MIDI_DEV} -S "f0 52 00 65 50 f7" -r temp.bin -t 1 ; hexdump -C temp.bin
pi@raspberrypi:~/Software/ZoomPedalB1ON $ more GetMoreData.sh 
#!/bin/bash
export MIDI_DEV=`amidi -l | grep ZOOM | awk '{print $2}'`

probeString="F0 52 00 65 60 05 00 F7"
echo ${probeString}
theFile=temp.$$
amidi -p ${MIDI_DEV} -S ${probeString} -r ${theFile} -t 1 ; hexdump -C ${theFile}; rm ${theFile}
pi@raspberrypi:~/Software/ZoomPedalB1ON $ more GetCurrentPatch.sh 
#!/bin/bash
export MIDI_DEV=`amidi -l | grep ZOOM | awk '{print $2}'`

#            F0 52 00 65 29 00 F7
probeString="F0 52 00 65 29 F7"
echo ${probeString}
theFile=temp.$$
amidi -p ${MIDI_DEV} -S ${probeString} -r ${theFile} -t 1 ; hexdump -C ${theFile}; rm ${theFile}

Why the Pi4? Eventually that is the brain I want to use to control it. It is basically Linux and I can run Ctrlr and normal amidi on it to check things out without having to run my Windows box in garage - but tomorrow I will use it to check the state of injected firmware since updating needs a Windows box.

What I did was to go to some patch, empty it all out. Grab the sysex. Then systematically add the first FX on a B1On into the next slot, capturing in between. And I used the above program to decode.

After a while I see what I think is the pattern. Let me give a "for example" with the repeated FX.

61 00 00 c2 01 b0 01 32 00 00 00 00 00 00 00 00 00 00
61 00 00 c2 01 b0 01 32 00 00 00 00 00 00 00 00 00 00
61 00 00 c2 01 b0 01 32 00 00 00 00 00 00 00 00 00 00
61 00 00 c2 01 b0 01 32 00 00 00 00 00 00 00 00 00 00
61 00 00 c2 01 b0 01 32 00 00 00 00 00 00 00 00 00 00
a0 64 04 00 00 00 20 03 00 00 00 54 43 20 43 4c 45 41 4e 20 20 00
                                  T  C    C  L  E  A  N

So see you were passing all the bytes EXCEPT the F7 to unpack AFTER the 28. 0x29 was used to run the command. On a Zoom often times one less is the command response, in this case 0x28

So the above is the decoded. And it seems 18 bytes are in the FX for slots 1, 2, 3, 4, 5 then some more cruft. Then the 10 chars at the end for a name. In this case TC CLEAN. So my guess is the first param is some ID. Then likely some group.

So now I do a similar trick on "SLAP".

41 00 00 c2 08 30 00 64 a0 00 00 00 00 00 00 00 00 00 
61 00 00 84 07 40 01 64 00 00 00 00 00 00 00 00 00 00 
31 00 00 c4 03 70 00 0d 80 a1 c1 e1 c1 08 00 00 00 00 
01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
                                 S  L  A  P
60 64 04 00 00 00 20 03 00 00 00 53 4c 41 50 20 20 20 20 20 20 00

Now SLAP only has slots 1 to 3 in use. So we notice "BYPASS" must be the last two rows. I see see if the others are some FXID, GroupID and maybe some number per parameter.

So I will see if I can hack some of Mungewell's construct in Python to parse these B1On's

shooking avatar Jan 24 '21 00:01 shooking

Lots of information here, and other bugs, on the G1on - so it looks like you are making progress, even if I don't have the capacity to follow all of it....

When I was looking at the patch files from the G1Four it took me a while to figure out that the values for each parameter are stored in n-bit values spread across byte boundaries. The first parameters had more bits than the later... it may be the same for the G1on. The Construct code is a little funky because I couldn't figure out how to describe these split values....

I wrote up a lot here: https://github.com/mungewell/zoom-zt2/issues/7#issuecomment-630428690

Good luck, happy to help where I can ;-)

mungewell avatar Jan 24 '21 16:01 mungewell

Thanks. I am taking a break to watch FA cuo football. I have written an automation to generate parameter changes then unpack.

So fx1 P1 spans 2 bytes and seems to be low high - I keep forgetting it is an 8 bit when unpacked.

0 - 02 1 - 42 so shift right 6 Sounds familiar? 2 - 82 3 - c2 4 - 02 01 = 102 5 - 42 01 = 142 6 - 82 01 = 182 7 - c2 01 = 1c2 8 - 02 02 = 202 9 - 42 02 = 242

242 = 1001000010 shift right 6 1001 = 9

Looks promising.

On Sun, 24 Jan 2021 16:53 mungewell, [email protected] wrote:

Lots of information here, and other bugs, on the G1on - so it looks like you are making progress, even if I don't have the capacity to follow all of it....

When I was looking at the patch files from the G1Four it took me a while to figure out that the values for each parameter are stored in n-bit values spread across byte boundaries. The first parameters had more bits than the later... it may be the same for the G1on. The Construct code is a little funky because I couldn't figure out how to describe these split values....

I wrote up a lot here: #7 (comment) https://github.com/mungewell/zoom-zt2/issues/7#issuecomment-630428690

Good luck, happy to help where I can ;-)

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/mungewell/zoom-zt2/issues/6#issuecomment-766393326, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFO7EVZHVUWSA6PPX75X6VTS3RGB5ANCNFSM4M5FBJ5A .

shooking avatar Jan 24 '21 18:01 shooking

Nice hints - I will look at that tomorrow. Dinner time.

In the meantime @dkts2000 maybe you already have a methodology but here's what I am doing (I NEED to change my summary to make it clear what I am seeing - sorry I just filter thru tons of info really quick - then assume because I can see it so can others - Asperger's can be a curse).

ExerciseFXParam.sh

pi@raspberrypi:~/Software/ZoomPedalB1ON $ more ExerciseFXParam.sh 
#!/bin/bash
#
# Set your current patch to all Bypass.
# Then load an FX.
# modify the parameter by one each time
# store output in a file


exerFX=1
exerParam=2
./FXM_PN.sh ${exerFX} ${exerParam} 0

# first FX param let's try on to 10 as most are not on/off
theFile=FXInvestigation.txt
rm ${theFile}
for i in {1..10}
do
	./EditorOn.sh
	./GetMoreData.sh
	./GetCurrentPatch.sh >> ${theFile}
	./B1OnPatchUnpack000 currentPatch.bin >> ${theFile}
	./FXM_PN.sh ${exerFX} ${exerParam} ${i} >> ${theFile}
done

# what changes for JUST FX P1 moving from 0 to 10?

So the above copies crap systematically to a file. Then over a tea or coffee you see the info. zB (this is the summary I NEED to make!!!)

OUTPUT
p1 = ([4]*256+[3])>>6
 0  1  2  3  4  5  6
61 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
61 00 00 02 00 08 00 00 00 00 00 00 00 00 00 00 00 00 
61 00 00 02 00 10 00 00 00 00 00 00 00 00 00 00 00 00 
61 00 00 02 00 18 00 00 00 00 00 00 00 00 00 00 00 00 
61 00 00 02 00 20 00 00 00 00 00 00 00 00 00 00 00 00 
61 00 00 02 00 28 00 00 00 00 00 00 00 00 00 00 00 00 
61 00 00 02 00 30 00 00 00 00 00 00 00 00 00 00 00 00 
61 00 00 02 00 38 00 00 00 00 00 00 00 00 00 00 00 00 
61 00 00 02 00 40 00 00 00 00 00 00 00 00 00 00 00 00 
61 00 00 02 00 48 00 00 00 00 00 00 00 00 00 00 00 00 
       4  5
 0 -> 00 00        0
 1 -> 08 00    01000
 2 -> 10 00    10000
 3 -> 18 00    11000
 4 -> 20 00   100000
 5 -> 28 00   101000
 6 -> 30 00   110000
 7 -> 38 00   111000 so right shit 3
 8 -> 40 
 9 -> 48
10 ->

And hence I adjust my C++ to write out more summary. I modified your port of Mungewells code to use C++ vector. Then we can push back like our Python buddies.

B1OnPatchUnpack000.cpp

pi@raspberrypi:~/Software/ZoomPedalB1ON $ more B1OnPatchUnpack000.cpp 
#include <iostream>
#include <fstream>
#include <iomanip>
#include <iterator>

#include <string>
#include <cmath>
#include <vector>
using namespace std;
typedef unsigned char BYTE;

vector<BYTE> unpack (vector<BYTE> &vi);

vector<BYTE> unpack ( vector<BYTE> &sysex )
{
	// Unpack data 7bit to 8bit, MSBs in first byte
	//data = bytearray(b"")
	int loop = -1;
	uint8_t hibits = 0;

	int j = 0;
	vector<BYTE>	unpacked;

	// We expect this unpacked sysex to start from byte 5 (0 bias)
	for (int i = 5; i < sysex.size() - 5 - 1; i++)
	{	
		//byte in packet:
		uint8_t byt = sysex[i];
		if (loop != -1)
		{
			uint8_t p = pow (2, loop);
			if (hibits & p)
			{
				byt = 0x80 + byt; //data.append(128 + byte)
				unpacked.push_back(byt);
			}
			else
		       	{
				unpacked.push_back(byt);
			}
			loop = loop - 1;
		}
		else
		{
			hibits = byt;
			// do we need to acount for short sets (at end of block 
block)?
			loop = 6;
		}
	}

	// Start to summarize what we know
	cout << "FX1 P1 = " << ( ( (unpacked[4] << 8) + (unpacked[3]) ) >> 6) <<
 endl;
	cout << "FX1 P2 = " << ( ( (unpacked[6] << 8) + (unpacked[5]) ) >> 3) <<
 endl;
	cout << endl;
	return unpacked;
}

vector<BYTE> readFile(char* filename)
{
    // open the file:
    ifstream file(filename, ios::binary | ios::in);

    // Stop eating new lines in binary mode!!!
    file.unsetf(ios::skipws);

    // get its size:
    streampos fileSize;

    file.seekg(0, ios::end);
    fileSize = file.tellg();
    file.seekg(0, ios::beg);

    vector<BYTE> vec;
    // reserve capacity
    vec.reserve(fileSize);

    // read the data:
    vec.insert(vec.begin(),
               istream_iterator<BYTE>(file),
               istream_iterator<BYTE>());
    // cout << "vec was " << vec.size() << endl;
    file.close();
    return vec;
}

int
main (int argc, char **argv)
{
	/* read the file in, strip off header and F7 */

	cout << "Infile: " << argv[1] << endl;
	ifstream in_f(argv[1], ios::in | ios::binary);
	// ofstream out_f(argv[1], ios::out | ios::binary);

	vector<BYTE> vo;
	vector<BYTE> vi = readFile(argv[1]);

	cout << "INPUT\n";
	int ctr = 0;
	for(auto i: vi)
	{
		if (ctr == 5) cout << endl;
		if (ctr > 5 && ((ctr - 5) % 16 == 0)) cout << endl;
		ctr++;
		int value = i;
		cout << setfill ('0') << setw (2) << hex << (0xff & (BYTE) value
) << " ";
	}
	cout << endl;

	// cout << "file size: " << vi.size() << endl;
	vo = unpack (vi);

	ctr = 0;
	// we expect 5 x 18 bytes then rest.
	cout << "OUTPUT\n";
	for (auto i: vo)
	{
		if (ctr % 18 == 0) cout << endl;
		ctr++;
		int value = i;
		cout << setfill ('0') << setw (2) << hex << (0xff & (BYTE) value
) << " ";
	}
	cout << endl;
	return 0;
}

So this effectively closes the loop on generating candidate sysex strings. It's similar to how I do it in other synths (except ****** AKAI Miniak where you cannot get it to generate such strings!!).

FXM_PN.sh

pi@raspberrypi:~/Software/ZoomPedalB1ON $ more FXM_PN.sh 
#!/bin/bash
export MIDI_DEV=`amidi -l | grep ZOOM | awk '{print $2}'`
#
# seems to also dump, sometimes, state of the effect?
# but not consistently - likely based on EditorOn
#
# expect FX Param value
# ie 1 1 10
#    2 3 20
theFX=$1
theParam=$2
theValue=$3
theFXMod=$(($theFX-1))
theParamMod=$(($theParam+1))
hexFX=`printf "%02x" $theFXMod`
hexParam=`printf "%02x" $theParamMod`
theVlow=$(($theValue & 127))
theVhigh=$(( ($theValue / 128) & 127 ))
hexValueLow=`printf "%02x" $theVlow`
hexValueHigh=`printf "%02x" $theVhigh`
echo $theValue
echo $hexValueLow
echo $hexValueHigh

probeString="F0 52 00 65 31 ${hexFX} ${hexParam} ${hexValueLow} ${hexValueHigh} 
F7"
echo ${probeString}
theFile=temp.$$
amidi -p ${MIDI_DEV} -S ${probeString} -r ${theFile} -t 1 ; hexdump -C ${theFile
}; rm ${theFile}

EditorOn.sh

pi@raspberrypi:~/Software/ZoomPedalB1ON $ more EditorOn.sh 
export MIDI_DEV=`amidi -l | grep ZOOM | awk '{print $2}'`
amidi -p ${MIDI_DEV} -S "f0 52 00 65 50 f7" -r temp.bin -t 1 ; hexdump -C temp.b
in

GetMoreData.sh

This seems to clear the buffer - no idea if it is correct but I am engineering with it.

pi@raspberrypi:~/Software/ZoomPedalB1ON $ more GetMoreData.sh 
#!/bin/bash
export MIDI_DEV=`amidi -l | grep ZOOM | awk '{print $2}'`

probeString="F0 52 00 65 60 05 00 F7"
echo ${probeString}
theFile=temp.$$
amidi -p ${MIDI_DEV} -S ${probeString} -r ${theFile} -t 1 ; hexdump -C ${theFile
}; rm ${theFile}

GetCurrentPatch.sh

This gets current patch

pi@raspberrypi:~/Software/ZoomPedalB1ON $ more GetCurrentPatch.sh 
#!/bin/bash
export MIDI_DEV=`amidi -l | grep ZOOM | awk '{print $2}'`

#            F0 52 00 65 29 00 F7
probeString="F0 52 00 65 29 F7"
echo ${probeString}
theFile=currentPatch.bin
rm ${theFile}
amidi -p ${MIDI_DEV} -S ${probeString} -r ${theFile} -t 1 ; hexdump -C ${theFile
}

As you can see in the C++ I found the first 2 param for FX1. And it looks like it will be an offset to find the others?

I will deffo need Mungewell's insights to find the FX and checksums.

shooking avatar Jan 24 '21 19:01 shooking

My guess is 12 bit packed across byte boundaries? In the unpacked, starting a 0 offset it seems like for FX1.


	// Start to summarize what we know
	cout << "FX1 P1 = " << ( ( (unpacked[ 4] << 8) + (unpacked[ 3]) ) >> 6 ) << endl;
	cout << "FX1 P2 = " << ( ( (unpacked[ 6] << 8) + (unpacked[ 5]) ) >> 3 ) << endl;
	// might need some masking? So looks like we have a byte shared. Maybe 12 bit values?
	// so I probably should also mask when shifting right except 4 is < 5 so the value self masks?
	cout << "FX1 P3 = " << ( ( ((unpacked[ 8] & 0xF) << 8) + (unpacked[ 7]) )      ) << endl;
	cout << "FX1 P4 = " << ( ( ((unpacked[ 9] & 0xF) << 8) + (unpacked[ 8]) ) >> 5 ) << endl;
	cout << "FX1 P5 = " << ( ( ((unpacked[10] & 0xF) << 8) + (unpacked[ 9]) ) >> 5 ) << endl;
	cout << "FX1 P6 = " << ( ( ((unpacked[11] & 0xF) << 8) + (unpacked[10]) ) >> 5 ) << endl;
	cout << "FX1 P7 = " << ( ( ((unpacked[12] & 0xF) << 8) + (unpacked[11]) ) >> 5 ) << endl;
	cout << "FX1 P8 = " << ( ( ((unpacked[13] & 0xF) << 8) + (unpacked[12]) ) >> 5 ) << endl;

And it looks like we add 18 x (FX - 1) for the others to those offsets (to be detemined) I think I dont need to mask top/bottom nybble in all cases base on my comment above. If bottom 4 bits dont count but we right shift more ... it's academic.

What I am going to do is see if I can find a 9 parameter FX. Ba GEQ is 8. And of course 10 = 0xa ... makes me wonder if that is why volume is at 10?

Then I will write a random number generate and do some Monte Carlo over the parameter space. So I will look for FX1 P1 - 9 set to 9 randoms. Then check for each value do I get a bingo? If so great. If not .. have I hit a limiter due to the TMS enforcing a max/min? I did generate a JSON file on the B1XFour for the parameters - that was tedious. I was hoping to use something like this to trial sysex 0 ... increment up in 10s say. If stops increasing then go back to previous 10, go up in 5, etc etc .. find the max midi value for a given FX and param.

Oh and in the above ... my guess is 3, 4, 5, 6 might also be shared with FX/Group. More as I find it.

I hope they have the ASCII format output like on B1XFour. That would make it easier to match Sysex value to displayed value.

shooking avatar Jan 24 '21 22:01 shooking

I put dumps of 1.30 patches, their zeroed out versions (with my decode) so one can read off the FXID/GID in the patch - I need to do a bit more work to map to the specific FX. You can find it here - https://github.com/shooking/ZoomPedalFun/tree/main/B1ON/DerivedData/1.30

Equally, if you look up a few directories you get the current summary of what I know.

Taking a break to play out some of the new Sysex I found from a GCE-3 on a B1Xfour. Finding kinky ways to do similar things.

shooking avatar Aug 06 '21 15:08 shooking