ricardomatias / ableton-live

A library for communicating with Live via WebSockets, works both in NodeJS and in the Browser.
https://ricardomatias.net/ableton-live/
79 stars 10 forks source link

Issues observing more than one property #16

Closed allforabit closed 1 year ago

allforabit commented 1 year ago

Hi there, great work on this library! I'm just having trouble observing more than one properties. E.g. given an Ableton song with 3 tracks.

const live = new AbletonLive();

const main = async () => {
  try {
    await live.connect();

    const tracks = await live.song.children("tracks");

    await tracks[0].observe("name", (v) => {
      console.log(v);
    });

    await tracks[1].observe("name", (v) => {
      console.log(v);
    });

    await tracks[2].observe("name", (v) => {
      console.log(v);
    });

  } catch (error) {
    console.error(error);
  }
};

main();

The websockets entry looks like this:

{
  "event": "callback",
  "result": {
    "data": "\"track name\"",
    "listeners": [
      "BOapnHqy09vhRLS-fLAbN",
      "Vx-E_k3Z7nVJ00_oCCcc0",
      "bYGH2h2OFGq7SFMw0OUuu"
    ]
  }
}

which suggests it's targeting all the callback for a single track name update.

I would expect each different track to log when their name updates but it only seems to factor in the last item that's observed and this overrides all the other callbacks and so the same name is printed three times in this case.

It could be a case of not using the api correctly though.

allforabit commented 1 year ago

It's to do with the event callback name not being entirely unique. I've got a workaround by changing the callback signature in live api:

callback(objectPath, objectId, property)

and I generate a unique name with this function and use in place of just the objectPath:

function callbackName(path, id) {
    if(id) {
        return path + '-' + id;
    }
    return path;
}
allforabit commented 1 year ago

I have a PR there if you want to try it out

ricardomatias commented 1 year ago

Thanks for the thorough description of the problem and testable scenario!

allforabit commented 1 year ago

Thanks very much for doing the update! I still seem to be having the same issue though after updating and using the latest device though I'm wondering if there was an issue updating it? Thanks again :-)

ricardomatias commented 1 year ago

My bad, I put the updated one inside the LiveAPI folder by mistake. Well spotted @allforabit!

allforabit commented 1 year ago

That's great, thanks for that. I did notice one other issue, there's always one :-) The removeObserver function isn't using the proper callback name in all places. Here's the updated function that seems to do the trick:

function removeObserver(args) {
    const res = JSON.parse(args);

    // * Request UUID
    var uuid = res.uuid;
    var objectId = res.objectId;
    var eventId = res.args.eventId;
    var objectPath = res.args.objectPath;

    var cbName = callbackName(objectPath, objectId)

    if (callbackEvents[cbName] !== undefined) {
        var listeners = callbackEvents[cbName].listeners;
        var idx = listeners.indexOf(eventId);

        listeners.splice(idx, 1);

        if (!listeners.length) {
            const api = callbackEvents[cbName].api;
            api.id = 0;
            delete callbackEvents[cbName];
        }
    }

    onSuccess(uuid);
}