featherbear / presonus-studiolive-api

Exploring the PreSonus network control protocol from a StudioLive Series III
https://featherbear.cc/presonus-studiolive-api/
46 stars 13 forks source link

Other controls #16

Open fis-cz opened 1 year ago

fis-cz commented 1 year ago

Hi,

I love your project. I would like to ask, do you also plan to implement other controls (i.e. gain, phanotm, lpf, eq, compressor and so on?)?

I would like to help you as I want completely control the 24R mixer using the MIDI surface from the electron app. I'll try to help you with the protocol analysis, but I need to get in first.

featherbear commented 1 year ago

Hi @fis-cz, thanks for taking interest in the project.

Absolutely; I planned to implement absolutely everything that I can - and ideally recreate the functionality of Universal Control. (Also a heads up, the folks at PreSonus seem to be developing an API of sorts for Universal Control, so this project may not be needed; though still has its purposes as an independent solution)

The controls you mentioned above are simple to add in, but I first need to solve https://github.com/featherbear/presonus-studiolive-api/issues/14 roadblock.

Currently this project is on hold for me because I'm busy finishing up my university thesis, but hopefully soon™ I should be able to jump back on to this!

fis-cz commented 1 year ago

Great news, I am trying to work with the code now but unfortunately I never obtain the first subscription packet. It seems to me that there is some problem with permissions as all are returning false in previous packets. Hopefully I'll be able to figure it out. I also try to take a look to CK, it seems to me there is ZB inside so I'll try to figure it out.

featherbear commented 1 year ago

There shouldn't be any permission restrictions if I remember correctly. The access code is sent inside of the ZB packet (encapsulated within CK for detailed configurations) - but it is only a client-side check, so you should be able to completely avoid it, as per https://github.com/featherbear/presonus-studiolive-api/issues/5


You will likely need to use commit https://github.com/featherbear/presonus-studiolive-api/commit/ce7f39548c25ec3d19073c7eb33ff87b9760d815 which has the ZB wait removed. Note that functions will break because they rely on the contents of ZB

Call connect with <API>.connect(..., true)

featherbear commented 1 year ago

Added a packet capture and some notes at https://github.com/featherbear/presonus-studiolive-api/issues/14#issuecomment-1214187823

fis-cz commented 1 year ago

Ok, so got it complete.

The ZB message is the standard compressed BSJson (https://neurojson.org/)

The CK chunk message is just a container for other messages.

CK followed by 6 bytes (some kind of header you've already described somehow, but not important for other parsing. Then there is the data message identifier (such as ZB) which means compressed data followed by some zero argument then there is an offset of the data in the full data range, then there is total data size (all chunks) then there is the chunk size (current one):

CK xx xx xx xx xx xx ZB 00 00 00 00 aa aa aa aa bb bb bb bb cc cc cc cc XX XX XX XX

xx ... CK header (maybe it is some chunk ID as all related chunks seems to have same, but can't be sure) ZB ... Chunked message identifier 00 ... ? probably some reserved space - it seems it is some message argument (as in CK case) aa ... LE int32 data offset (in the final buffer) bb ... LE int32 total data size cc ... LE int32 current chunk data size XX ... Chunk data

Currently I am using these two files to decode (d.ts needed for bjd) (npm install bjd), https://www.npmjs.com/package/bjd.

Of course, this needs to be improved so much, but it gives nice and full state of the mixer.

bjd.d.ts

declare module "bjd" {

   export function decode(data: Buffer): any

}

CK.ts

import Client from "../Client"
import * as zlib from "zlib";
import * as bjd from "bjd";

let chunkBuffer: Buffer[] = [];

export default function handleCKPacket(this: Client, data: Buffer) {

   data = data.slice(4);

   const chunkOffset = data.readUInt32LE(0);
   const totalSize = data.readUInt32LE(4);
   const chunkSize = data.readUInt32LE(8);

   const chunkData = data.slice(12);
   chunkBuffer.push(chunkData);

   if (chunkOffset + chunkSize === totalSize) {
      const fullData = Buffer.concat(chunkBuffer);
      const unzip = zlib.inflateSync(fullData);
      const data = bjd.decode(unzip);
      console.log(data);
   }

}
featherbear commented 1 year ago

That's awesome work @fis-cz! I didn't try to combine the two CK payloads... silly me

The https://www.npmjs.com/package/bjd package you used is an implementation of UBJSON.
I had originally implemented the decoder without knowing this, but I eventually found out it was UBJSON after looking at the Universal Control program binaries

I've updated the decoder implementation to support int64 values - https://github.com/featherbear/presonus-studiolive-api/commit/6eed5238d0332d7561d7df15e9e4d6f5b1a460d5#diff-4d26eb190012397140dcf516873130b788d14e55420fde3feb6eb0b9b8a5cdf6R92-R98, and it seems to be working well! So we don't need to add in the extra bjd package

Issues #12 and #14 can now be closed ✨

fis-cz commented 1 year ago

Thanks, Great it works, I checked and it seems to be working even on my mixer (24R). Regarding the UBJson, It took a bit to me to figure out, what it is. It was clear it is some JSON extension, but was hard to find on the internet :) Just consider using of that standard lib for one of further major releases. I know it would have a big impact to the code, but as it is a standard and its source is available I would not be afraid to use it. For now, the parser seems to work correctly, but I need to test it more extensively.

featherbear commented 1 year ago

I'm happy to use a standard library, I only wrote it myself back when I didn't know that it was a UBJSON payload :)

According to the listed libraries on the specification website, there are two (though not officially checked)

I tried the second one a week ago but it didn't integrate right out of the box Also, it may be worthwhile to implement an FFI call to the C / C++ code for better performance