mungewell / zoom-zt2

Python script to install/remove effects from the Zoom G1Four pedal
MIT License
50 stars 10 forks source link

FX: ID and Group from decode #28

Closed shooking closed 2 years ago

shooking commented 3 years ago

Hi Mungewell

I managed to find some time to revisit the Zoom pedals recently - your decode preset was super useful. C, inspite of its lovely low level bit operators, caused me no end of grief with bit arrays, packing and struct alignment.

Anyhow in your excellent work you find IDs like:

output from decode_preset.py
Container: 
    PTCF = Container: 
        effects = 5
        name = u'DRIVNCLEAN' (total 10)
        id1 = 58720305
        id2 = 25165856
        id3 = 75497521
        id4 = 92274736
        id5 = 16777280
...
I think the construct Python you use is much easier than the C

typedef union t_int {
    int x;
    BYTE v[4];
    struct t_2short {
        short fx;
        short id;
    } s;
} myInt;

myInt a;
            printf("FX: %d (%08x)\tFXID: %04d (%04x)\tFXGP: %d (%04x)\n",
                a.x, a.x,
                a.s.fx,
                a.s.fx,
                a.s.id / 32,
                a.s.id / 32);

It's taken me an age to realise this can be interpreted as two shorts that store hints on the FXID and GID. So your ID can also be viewed as GID x 32 (first short) and FXID (2nd short).

/EditorOn.sh && ./GetMoreData.sh && ./GetCurrentPatch.sh && ./B1XFourPatchUnpack000 currentPatch.bin
...
Patch name
DRIVNCLEAN
FX: 5
FX: 58720305 (03800031) FXID: 0049 (0031)   FXGP: 28 (001c)
FX: 25165856 (01800020) FXID: 0032 (0020)   FXGP: 12 (000c)
FX: 75497521 (04800031) FXID: 0049 (0031)   FXGP: 36 (0024)
FX: 92274736 (05800030) FXID: 0048 (0030)   FXGP: 44 (002c)
FX: 16777280 (01000040) FXID: 0064 (0040)   FXGP: 8 (0008)

I am sure you already know this - just incase others are not aware. Likely it is some misaligned bit pattern.

What is not so clear. On some FX the slot is 2 wide (we know this but I not yet managed to find from the preset alone). I think the decode_screens knows this right?

But I was able to then set "7" FX on a single preset :-). Now it is only 5, but well a bit odd. And this is somewhat non-deterministic. So I can get the 7th slot to add an FX sometimes, not others.

I used this program to drive it.

#!/bin/bash
export MIDI_DEV=`amidi -l | grep ZOOM | awk '{print $2}'`
#
# expect SLOT FX_ID FX_GROUP
#
# So we get string like
# LMT1176.ZD2
# 1.50
# 01 20
# 80 01
#
# or
# CRNTRI3S.ZD2
# 1.10
# 01 31
# 00 06
#
# in 01 20 the 20 is the FX ID
# in 01 31, sometimes we need 0x30 = 48
#
# So the last two are "reversed" is 80 1 == 1 80, 00 06 = 06 00
#
# ie 1 48 
#    2 3 20
#
# we read in theSlot and bias by -1

theSlot=$1
theSlotMod=$(($theSlot-1))
hexSlot=`printf "%02x" $theSlotMod`

# We read in the FX ID and convert to low/high

theFXID=$2
hexFXLow=$(($theFXID & 127))
hexFXHigh=$(( ($theFXID / 128) & 127 ))
hexFXValueLow=`printf "%02x" $hexFXLow`
hexFXValueHigh=`printf "%02x" $hexFXHigh`

# We read in the FX GROUP and convert to low/high
theValue=$3
#theValueMod=$(($theValue / 32 ))
theValueMod=$(($theValue))

# we switch FX on
OnOff=`printf "%02x" 1`

hexGroupVal=`printf "%02x" $theValueMod`

#                                 sl    ID       GID
#            F0 52 00 6E 64 03 00 00 01 30 00 00 08 00 F7
probeString="F0 52 00 6E 64 03 00 ${hexSlot} ${OnOff} ${hexFXValueLow} ${hexFXValueHigh} 00 ${hexGroupVal} 00 F7"
echo ${probeString}
theFile=temp.$$
amidi -p ${MIDI_DEV} -S ${probeString} -r ${theFile} -t 1 ; hexdump -C ${theFile}; rm ${theFile}

I generated the decode of the patch. Then deleted - notice I then had to adjust the slot IDs for my code.

./FXM_Delete.sh 5
./FXM_Delete.sh 4
./FXM_Delete.sh 3
./FXM_Delete.sh 2
./FXM_Delete.sh 1
./FXM_ID.sh 1 16 12
./FXM_ID.sh 2 112 20
./FXM_ID.sh 3 17 28
./FXM_ID.sh 4 49 28 ; # oops 3 is 2 wide! Need to start next at 5
./FXM_ID.sh 3 17 28
./FXM_ID.sh 5 49 28
./FXM_ID.sh 6 49 28 ; # didnt do anything
./FXM_ID.sh 7 49 28; # hmm .. this is 5th FX displayed ... sometimes ...

Original state:

Patch name
Sans Clean
FX: 5
FX: 25165840 (01800010) FXID: 0016 (0010)   FXGP: 12 (000c)
FX: 41943152 (02800070) FXID: 0112 (0070)   FXGP: 20 (0014)
FX: 58720273 (03800011) FXID: 0017 (0011)   FXGP: 28 (001c)
FX: 58720305 (03800031) FXID: 0049 (0031)   FXGP: 28 (001c)
FX: 25165840 (01800010) FXID: 0016 (0010)   FXGP: 12 (000c)

So sometimes FXM_Delete.sh 7 actually deletes slot 1. Quite literally odd.

shooking commented 3 years ago

And now I realise, after writing my own ZD2 parser, you already did most of what I do with your construct and Python.

Just incase you are interested it is also possible to pull out the parameter names directly from the ZD2.

/*
** OK so we look for
** 54 58 45 31 == TXE1
** 4f 6e 4f 66 66 == OnOff (later we try to work out a vector to this offset)
** 50 52 4d 4a == PRMJ - like PRME but with binary data
** 50 52 4d 45 == PRME - more readable form
*/

I am pretty sure the PRMJ is Japanese and E is English. Why?

Infile: ZNR.ZD2
Filesize: 26307
INPUT
FX: ZNR

VERSION: 1.30
Mungewell's ID
01 00 00 40 
FXID: 64(0040) FXGID: 8(0008)

...
PRMJstart =     61f3(   25075)
"Parameters":[  
                {  
                   "name":"DETCT",
                   "explanation":"制御信号の検出位置を選択します。",
                   "blackback":false,
                   "pedal":false
                },
                {  
                   "name":"Depth",
                   "explanation":"ノイズリダクションの深さを設定します。",
                   "blackback":false,
                   "pedal":false
                },
                {  
                   "name":"THRSH",
                   "explanation":"効果が現れる閾値を調節します。",
                   "blackback":false,
                   "pedal":false
                },
                {  
                   "name":"Decay",
                   "explanation":"減衰時間を調節します。",
                   "blackback":false,
                   "pedal":false
                }
        ]
PRMEstart =     6470(   25712)
"Parameters":[  
                {  
                   "name":"DETCT",
                   "explanation":"Sets control signal detection level.",
                   "blackback":false,
                   "pedal":false
                },
                {  
                   "name":"Depth",
                   "explanation":"Sets the depth of noise reduction.",
                   "blackback":false,
                   "pedal":false
                },
                {  
                   "name":"THRSH",
                   "explanation":"Adjusts the effect sensitivity.",
                   "blackback":false,
                   "pedal":false
                },
                {  
                   "name":"Decay",
                   "explanation":"Adjust the envelope release.",
                   "blackback":false,
                   "pedal":false
                }
        ]

Not sure if you already done this but I also managed to pull out the bitmaps. Write from hex 88 offset to bitmap file size [[there is a BM at 0x88, the header is standard bitmap]]. The size is int from bytes 0x8a - 0x8d (fileSize in struct below)

typedef struct t_bmHeader {
    BYTE    signature[2];
    BYTE    fileSize[4];
    BYTE    reserved[4];
    BYTE    dataOffset[4];
} bmHeader;

typedef struct t_bmInfoHeader {
    BYTE    size[4];
    BYTE    width[4];
    BYTE    height[4];
    BYTE    planes[2];
    BYTE    bitsPerPixel[2];
    BYTE    compression[4];
    BYTE    imageSize[4];
    BYTE    xPixelsPerM[4];
    BYTE    yPixelsPerM[4];
    BYTE    coloursUsed[4];
    BYTE    importantColours[4];
} bmInfoHeader;

typedef struct t_bmColourTable{
    BYTE    red;
    BYTE    green;
    BYTE    blue;
    BYTE    reserved;
} bmColourTable;

need 2 of the above colour .. one for white, one for black.

I am almost there with the decode now. Thanks for all your tips. Now to write it in Lua for Ctrlr.

shooking commented 3 years ago

OK and now I see the Max midi value, that I was getting from the CycleFXData, is also available in the ZD2 files ... nice!

pi@raspberrypi:~/Software/ZoomPedal/ZD2 $ ../ParseZD2_000 HALL.ZD2
Infile: HALL.ZD2
Filesize: 17933
INPUT
"FX": {
"name" : "Hall",
"filename": "HALL.ZD2"
"bmpname": "HALL.ZD2.BMP"
"version" : "1.50",
"FXID" : 48,
"FXGID": 9,
"groupName": "REVERB",
"description" : "This reverb effect simulates the acoustics of a concert hall.",
OnOffStart =     1502(    5378)

 O (4f)  n (6e)  O (4f)  f (66)  f (66)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)   (01)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)  \u00b4 (b4)  
 (0a)   (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) Max midi: 1, default: 0

 H (48)  a (61)  l (6c)  l (6c)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)  \u00ff (ff)  \u00ff (ff)  \u00ff (ff)  \u00ff (ff) 
  (00)   (00)   (00)   (00)   (01)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)   (14)  
                                     (0c)   (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) Max midi: -1, default: 0

 P (50)  r (72)  e (65)  D (44)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)  c (63)   (00)   (00)   (00) 
 0 (30)   (00)   (00)   (00)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)  0 (30)      (09)   (00)   (00) 
 (0d)   (00)   (00)    (00)   (00)  
  (00)   (00)   (00)   (00)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)   (00)   (00)   (00)   (00) Max midi: 99, default: 48

 D (44)  e (65)  c (63)  a (61)  y (79)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)   (1d)   (00)   (00)   (00) 
     (09)   (00)   (00)   (00)   (1d)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)  \u00ec (ec)  
                                      (0c)   (00)   (00) 
 (0d)   (00)   (00)    (00)   (00)  
  (00)   (00)   (00)   (00)   (00)   (00)   (00)   (00) 
  (10)   (00)   (00)   (00)   (00)   (00)   (00)   (00) Max midi: 29, default: 9

 M (4d)  i (69)  x (78)   (00)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)  d (64)   (00)   (00)   (00) 
 . (2e)   (00)   (00)   (00)  d (64)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)  \u00c8 (c8)  
                                      (0b)   (00)   (00) 
  (00)   (00)   (00)   (00)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)   (00)   (00)   (00)   (00) 
  (10)   (00)   (00)   (00)   (00)   (00)   (00)   (00) Max midi: 100, default: 46

 T (54)  a (61)  i (69)  l (6c)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)   (01)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)  \u00b4 (b4)  
 (0a)   (00)   (00) 
 (0d)   (00)   (00)    (00)  (9a)  
  (00)   (00)   (00)   (00)   (00)   (00)   (00)   (00) 
  (06)   (00)   (00)   (00)   (00)   (00)   (00)   (00) Max midi: 1, default: 0

  (17)   (00)   (00)   (00)   (1e)   (00)   (00)   (00) 
 \u00d8 (d8)   (06)   (00)   (80)   (14)   (00)   (00)   (00) 

 (0a)   (00)   (00)   (00)  \u00c0 (c0)   (07)   (00)   (80) 
  (18)   (00)   (00)   (00)   (16)   (00)   (00)   (00) 
 8 (38)   (07)   (00)   (80)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)   (00)   (00)   (00)   (00) 
  (00)   (00)   (00)   (00)   (00)   (00)   (00)   (00) Max midi: 20, default: 10

  (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) Max midi: 0, default: 0

  (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) Max midi: 0, default: 0

  (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) Max midi: 0, default: 0

"Parameters":[  
        {  
           "name":"PreD",
           "explanation":"Adjusts the delay between input of the original sound and start of the reverb sound.",
           "blackback":false,
           "pedal":false
        },
        {  
           "name":"Decay",
           "explanation":"Sets the duration of the reverberations.",
           "blackback":false,
           "pedal":false
        },
        {  
           "name":"Mix",
           "explanation":"Adjusts the amount of effected sound that is mixed with the original sound.",
           "blackback":false,
           "pedal":false
        },
        {  
           "name":"Tail",
           "explanation":"When ON, effect sound continues even after effect is turned off. When OFF, effect sound stops right when effect is turned off.",
           "blackback":false,
           "pedal":false
        }
    ]

Basically I look for the OnOffstart, and values are relative to that

    // 5 is longest hex string we look for
    for (unsigned int i = 0; i < unpacked.size() - 5; i++)
    {
..
    else if 
        (
            unpacked[i]   == 0x4f && unpacked[i+1] == 0x6e &&
            unpacked[i+2] == 0x4f && unpacked[i+3] == 0x66 &&
        unpacked[i+4] == 0x66

        )
        {
            OnOffstart = i + 5;
        }
...
    if (OnOffstart !=-1)
    {
        // we need to work out offset
        printf("OnOffStart = %8x(%8d)\n", OnOffstart, OnOffstart);
        // OnOff, Name, 1 - 8 parameters?
        for (int j = 0; j < 10; j++)
        {
            for (size_t i = 0; i < 0x38; i++)
            {
                if (i % 8 == 0) printf("\n");
                int v = unpacked[OnOffstart - 5 + j * 0x38 + i];
                    printf("%2c (%02x) ", v, v);
            }
            // so [12, 13] and [16, 17] for each param is interesting
            short mmax = 
                unpacked[OnOffstart - 5 + j * 0x38 + 12] + 
                unpacked[OnOffstart - 5 + j * 0x38 + 13] * 256;
            short mcurr = 
                unpacked[OnOffstart - 5 + j * 0x38 + 16] + 
                unpacked[OnOffstart - 5 + j * 0x38 + 17] * 256;
            printf("Max midi: %d, default: %d\n", mmax, mcurr);

            printf("\n");
        }

    }

This is almost everything I need!

mungewell commented 2 years ago

Added some of this functionality to 'decode_effect.py' https://github.com/mungewell/zoom-zt2/commit/e38bf2dcf870b7127840fd7d611c9f505c68197f

shooking commented 2 years ago

forgot to close this - sorry. Great work as ever