mungewell / twinlooper

Python code to interact with Rowin Twin Looper effects pedal
GNU General Public License v3.0
2 stars 0 forks source link

Parse the 'Info' downloaded, presumably points to audio data #3

Open mungewell opened 1 year ago

mungewell commented 1 year ago

After 'searching' the first block of data downloaded appears to be a 'index' type object.... I assume that it points to series of blocks to download. info.txt

00000000  6c 6f 6f 70 65 72 00 27  01 ff 8f 02 88 02 01 04  |looper.'........|
00000010  03 00 04 00 05 00 06 00  07 00 08 00 09 00 0a 00  |................|
00000020  0b 00 0c 00 0d 00 0e 00  0f 00 10 00 11 00 12 00  |................|
00000030  13 00 14 00 15 00 16 00  17 00 18 00 19 00 1a 00  |................|
00000040  1b 00 1c 00 1d 00 1e 00  1f 00 20 00 21 00 22 00  |.......... .!.".|
00000050  23 00 24 00 25 00 26 00  27 00 28 00 29 00 2a 00  |#.$.%.&.'.(.).*.|
00000060  2b 00 2c 00 2d 00 2e 00  2f 00 30 00 31 00 32 00  |+.,.-.../.0.1.2.|
00000070  33 00 34 00 35 00 36 00  37 00 38 00 39 00 3a 00  |3.4.5.6.7.8.9.:.|
00000080  3b 00 3c 00 3d 00 3e 00  3f 00 40 00 41 00 42 00  |;.<.=.>.?.@.A.B.|
00000090  43 00 44 00 45 00 46 00  47 00 48 00 49 00 4a 00  |C.D.E.F.G.H.I.J.|
...

The first block seen in my WireShark captures is

host    1.2.2   f0:00:32:0d:41:00:00:40:00:00:60:3f:00:10:7e:00:00:0b:00:f7

00000000: 00 F0 0F 00 F1 03 00 0B  00                       .........
None

and indeed this gives me audio. first_audio

mungewell commented 1 year ago

Here are some other 'info' blocks - with a short 1min length. info_1min.txt info_1min_overdub.txt

Firstly we can see that the shorter length results in less data in the first section:

00000000  6c 6f 6f 70 65 72 00 00  00 00 4c 00 20 00 28 00  |looper....L. .(.|
00000010  22 00 23 00 24 00 25 00  26 00 27 00 28 00 29 00  |".#.$.%.&.'.(.).|
00000020  2a 00 2b 00 2c 00 2d 00  2e 00 2f 00 30 00 31 00  |*.+.,.-.../.0.1.|
00000030  32 00 33 00 34 00 35 00  36 00 37 00 38 00 39 00  |2.3.4.5.6.7.8.9.|
00000040  3a 00 3b 00 3c 00 3d 00  3e 00 3f 00 40 00 41 00  |:.;.<.=.>.?.@.A.|
00000050  42 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |B...............|
00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
...

And when the overdub is used on the pedal, there is a 2nd part to the first section (rather than being all zeros).

...
00000510  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000520  00 00 00 00 49 00 4a 00  8d 00 8f 00 90 00 91 00  |....I.J.........|
00000530  92 00 93 00 94 00 95 00  96 00 97 00 98 00 99 00  |................|
00000540  9a 00 9b 00 9c 00 9d 00  9e 00 9f 00 a0 00 a1 00  |................|
00000550  a2 00 a3 00 a4 00 a5 00  a6 00 a7 00 a8 00 a9 00  |................|
00000560  aa 00 ab 00 69 00 6a 00  6b 00 6c 00 6d 00 6e 00  |....i.j.k.l.m.n.|
00000570  6f 00 70 00 71 00 72 00  73 00 74 00 75 00 76 00  |o.p.q.r.s.t.u.v.|
00000580  77 00 78 00 79 00 7a 00  7b 00 7c 00 7d 00 7e 00  |w.x.y.z.{.|.}.~.|
00000590  7f 00 80 00 81 00 82 00  83 00 84 00 85 00 86 00  |................|
000005a0  87 00 88 00 89 00 00 00  00 00 00 00 00 00 00 00  |................|
000005b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
...

When downloading (TShark Log4) with the official SW, the first data blocks are:

Request data block - pt1
00000000: 00 F0 27 01 F1 03 00 F2  00                       ..'......
--
Request data block - pt2
00000000: F1 F3 27 01 F1 03 00 FE  00                       ..'......
--
Request data block - pt3
00000000: E2 F7 27 01 F1 03 00 09  00                       ..'......
--
Request data block - pt4
00000000: D3 FB 27 01 F1 03 00 14  00                       ..'......
mungewell commented 1 year ago

OMG... just seeing those Requests lined up like that caused a 'light bulb' moment.

The address is 32 bits, that first byte is not a 'command' it is the 'low address byte'!

mungewell commented 1 year ago

'10min' loop - no overdub.

00000000  6c 6f 6f 70 65 72 00 27  01 ff 8f 02 88 02 01 04  |looper.'........|

from 10min data stops, end of base section.
00000520  8b 02 ff ff 00 00 00 00  00 00 00 00 00 00 00 00  |................|

00000a30  00 00 00 00 00 00 00 00  6c 6f 6f 70 65 72 00 30  |........looper.0|
00000a40  1a d5 19 1a 55 29 22 78  36 22 c4 86 29 fd ad 29  |....U)"x6"..)..)|

00000bc0  1e 98 87 1e e9 55 16 c3  30 16 a4 af 0d 68 6f 0d  |.....U..0....ho.|
00000bd0  c4 c4 04                                          |...|
00000bd3

from '1min_dub'... data restarts in middle... this is the overdub

00000520  00 00 00 00 49 00 4a 00  8d 00 8f 00 90 00 91 00  |....I.J.........|

Theory time... we have three sections, first two contain data which changes length according to duration of 'base' sound. Normally the bytes count up. The third section seems to contain much more random data.

I think the first is the 'base' loop, where the numbers are indexes into the third section which actually points to the location in memory. The second section is the 'overdub' loop, if there is an overdub. Normally the indexes will be the same as the first section but when a part is overdubbed the index is replaced with a new one containing audio from 'base' and 'overdub' mixed.

This would allow recording over various bits of the 'base' loop, whilst also allow the 'over dub' to be turn on/off at will, and even cleared completely.

OK, now to code something to parse this...

mungewell commented 1 year ago

Something like: parse_info.py.txt

$ python3 parse_info.py| head -n 40
Base               : Overdub
0x0190 -> 0x000000
0x0191 -> 0x000000
0x0192 -> 0x000000
0x0193 -> 0x000000
0x0194 -> 0x000000
0x0195 -> 0x000000
0x0196 -> 0x000000
0x0197 -> 0x000000
0x0198 -> 0x000000
0x0199 -> 0x000000
0x019A -> 0x000000
0x019B -> 0x000000
0x019C -> 0x000000
0x019D -> 0x000000
0x019E -> 0x000000
0x019F -> 0x000000
0x01A0 -> 0x000000
0x01A1 -> 0x000000 : 0x041C -> 0x000000
0x01A2 -> 0x000000 : 0x041E -> 0x000000
0x01A3 -> 0x000000 : 0x041F -> 0x000000
0x01A4 -> 0x000000 : 0x0420 -> 0x000000
0x01A5 -> 0x000000 : 0x0421 -> 0x000000
0x01A6 -> 0x000000 : 0x0422 -> 0x000000
0x01A7 -> 0x000000 : 0x0423 -> 0x000000
0x01A8 -> 0x000000 : 0x0424 -> 0x000000
0x01A9 -> 0x000000 : 0x0425 -> 0x000000
0x01AA -> 0x000000 : 0x0426 -> 0x000000
0x01AB -> 0x000000 : 0x0427 -> 0x000000
0x01AC -> 0x000000 : 0x0428 -> 0x000000
0x01AD -> 0x000000 : 0x0429 -> 0x000000
0x01AE -> 0x000000
0x01AF -> 0x000000
0x01B0 -> 0x000000
0x01B1 -> 0x000000
0x01B2 -> 0x000000
0x01B3 -> 0x000000
0x01B4 -> 0x000000
0x01B5 -> 0x000000
0x01B6 -> 0x000000
...
mungewell commented 1 year ago

slight improved version: parse_info.py.txt

I also realized a few things:

  1. Each item in the "Info.bin" must represent ~1s of audio. Which means that it really represents 64 blocks of audio data (48KHz samples, 2ch, 24bit).
  2. The first block is always at different address, as noted in picture above the level is faded - perhaps this is why
  3. Need to check whether official SW downloads 'base' audio, or 'overdub' audio.
mungewell commented 1 year ago

Have some confirmations.

Created a 1min loop, with multiple overdubs, of note is that the 'info' only contains the last overdub and the previous ones are 'bake' into the base loop. This make total sense.

  1. For this 1min loop, there are 124 indexes. Also of note is that it doesn't contain the 3rd section.
  2. Still to look at.
  3. The official software downloads the base audio. The output contained 3 overdubs, doesn't include the 4th.

The parse info file is here: https://github.com/mungewell/twinlooper/blob/main/tshark_log5/after/info_parse.txt

Although the file looks 'full', there is the end to the base section here:

000000f0  9c 07 9d 07 9e 07 9f 07  a0 07 a1 07 a2 07 a3 07  |................|
00000100  a4 07 a5 07 a6 07 a7 07  00 00 01 00 02 00 03 00  |................|
                                   ^^ ^^ 

I was able to correlate the 'info' changes, in sequence numbers, with the different addresses (from PCAP) downloaded from the pedal.

I thought there may be a mathematical relationship between index and address, however that doesn't appear so: Screenshot_2023-02-06_16-35-39

mungewell commented 1 year ago

I scripted something to look at discontinues in the download address - allowing for the 0x38 missing a few bytes (should really be 0x3c to stay on track).

This shows that they don't all occur after the 0x38, but can exist mid 'block' and that the new location can be mid block too. full_address.txt

00000000: C4 3F 79 08 38 00 00 42  00                       .?y.8..B.
00000000: 00 40 79 08 F1 03 00 49  00                       .@y....I.
00000000: F1 43 79 08 F1 03 00 55  00                       .Cy....U.
00000000: E2 47 79 08 F1 03 00 60  00                       .Gy....`.

00000000: F1 D3 81 09 F1 03 00 BC  00                       .........
00000000: E2 D7 81 09 F1 03 00 C7  00                       .........
00000000: D3 DB 81 09 F1 03 00 D2  00                       .........
00000000: C4 DF 81 09 38 00 00 99  00                       ....8....
00000000: E2 F7 85 09 F1 03 00 A3  00                       .........
00000000: D3 FB 85 09 F1 03 00 AE  00                       .........
00000000: C4 FF 85 09 38 00 00 75  00                       ....8..u.

00000000: C4 CF 8F 09 38 00 00 9B  00                       ....8....
00000000: 00 D0 8F 09 F1 03 00 A2  00                       .........
00000000: F1 D3 8F 09 F1 03 00 AE  00                       .........
00000000: C4 EF 9B 08 38 00 00 70  00                       ....8..p.
00000000: 00 F0 9B 08 F1 03 00 77  00                       .......w.
00000000: F1 F3 9B 08 F1 03 00 83  00                       .........

00000000: D3 2B A6 08 F1 03 00 5E  00                       .+.....^.
00000000: C4 2F A6 08 38 00 00 25  00                       ./..8..%.
mungewell commented 1 year ago

Still massively confused about how the app knows which address to download.

Checked that there are no repeated address, except these two.

$ grep -A 2 -e '^1.2.1' log.decoded | grep -e '^00000000:' | cut -d ' ' -f 1-5| sort | uniq -c | grep -v "\s1\s"
      2 00000000: 00 00 F3 1E
      2 00000000: 62 63 64 65

0x1EF30000 - info block 0x65646362 - response to ID request

mungewell commented 1 year ago

I also further dismissed the idea of a linear relationship between 'index' and 'address'.

By sorting each numerically I saw that index 'positions' (between 0.0 and 1.0) were at the extremes, whereas addresses sorted the same way covered the whole range (ie no big gaps).

mungewell commented 1 year ago

So maybe I've figured a way to look at this, and map addresses (seen in PCAP) to those indexes in the 'info' block.

We know data must be downloaded in order (to render wav file), so if I number the info indexes in sequence as a fraction to number of them (ie start at 0 and up to 1). I can then also number the discontinuities in download address the same way.

Concat and sort gives me some form of map, which should tie the two domains together.....

0                       0       0x021B
0.000743494423792       6       141295616       86C0000
0.008130081300813       1       0x021C
0.016260162601626       2       0x021D
0.024390243902439       3       0x021E
0.032520325203252       4       0x021F
0.039776951672863       321     141557760       8700000
0.040650406504065       5       0x025D
0.048780487804878       6       0x025F
0.056910569105691       7       0x0260
0.065040650406504       8       0x0261
0.073170731707317       9       0x0262
0.08130081300813        10      0x0263

0.089430894308943       11      0x0226
0.097560975609756       12      0x0227
0.105691056910569       13      0x0228
0.113821138211382       14      0x0229
0.121951219512195       15      0x022A
0.130081300813008       16      0x0264
0.13184634448575        1064    159503345       981D3F1
0.138211382113821       17      0x0266
0.146341463414634       18      0x0267
0.154471544715447       19      0x0268
0.16260162601626        20      0x0269
0.170731707317073       21      0x026A
0.173234200743494       1398    160419780       98FCFC4
...
mungewell commented 1 year ago

As noted in #5, the pedal seems to build new 'songs' as it is cycled through connecting to PC or start/stopping the loop playback.

One interesting thing is that some of the header appears to be changing during this. For essential something is the same.

==> song2.txt <==
Header:
00000000: 6C 6F 6F 70 65 72 00 AA  02 00 EF 03 20 00 16 08  looper...... ...
                                               ^^ ^^ length in seconds?
                               ^^  ^^ ^^ ^^ different?
00000010: B6 03 B7 03 B8 03 B9 03  BA 03 BB 03 BC 03 DA 03  ................

==> song3.txt <==
Header:
00000000: 6C 6F 6F 70 65 72 00 26  01 00 DA 03 20 00 16 08  looper.&.... ...
00000010: B6 03 B7 03 B8 03 B9 03  BA 03 BB 03 BC 03 BD 03  ................
mungewell commented 1 year ago

Still can't figure a mapping, confirmed that the extra/3rd block does not exist. Must have been an error in my code.

What range of address do we actually have?

$ grep -e '6[0-9A-F] 08 F1 03 00' full_address.txt  | sort
00000000: 00 00 6C 08 F1 03 00 96  00                       ..l......
00000000: 00 00 6D 08 F1 03 00 95  00                       ..m......
00000000: 00 00 6E 08 F1 03 00 94  00                       ..n......
00000000: 00 00 6F 08 F1 03 00 93  00                       ..o......
00000000: 00 10 6C 08 F1 03 00 86  00                       ..l......

Thru

00000000: F1 F3 D2 09 F1 03 00 4B  00                       .......K.
00000000: F1 F3 D3 09 F1 03 00 4A  00                       .......J.
00000000: F1 F3 D8 09 F1 03 00 45  00                       .......E.
00000000: F1 F3 D9 09 F1 03 00 44  00                       .......D.
00000000: F1 F3 DA 09 F1 03 00 43  00                       .......C.
00000000: F1 F3 DB 09 F1 03 00 42  00                       .......B.

So: 0x086C0000 = 141295616 thru 0x09DBF3F1 = 165409777

gives a delta of 24114161 (0x16FF3F1)

mungewell commented 1 year ago

Each audio block is (0x03F1) * 4 + 0x3C = 0x1000 = 4096 bytes (with extra bytes to correct addressing). Since we don't see a lot of address discontinuities, adjacent indexes must be a whole multiple of that.

Now timing (rough calculations) Each audio block renders `(0x03F1) 4 + 0x38 - (8 5)' = 4502 byes, 24bit by 2ch = ~675 samples at 48Khz = 0.0140625s. Audio clip was ~30s long, so 2133 audio blocks need, and 124 indexes given. ~17.2 audio blocks per index.

mungewell commented 1 year ago

That's too close to 16 to be a co-incidence.... so each each index would be 0x10000 bytes, and to cover the address range above you'd need a range of at least 0x16F (367) in index value. Plausible.