StéphaneS
Deno3y ago
29 replies
Stéphane

API architecture for generic callback

Hi there,
I have a general question about code architecture for an API I'm doing.
Basically, it listens to MIDI messages from a native library and I use an FFI binding to forward the messages to the user. A message is represented by an array of bytes, and from that you can describe its data and how to interpret it. I made a layer that converts the raw data to a typed object, but what I want to do is offer a choice to the user so he can retrieve either the raw data or the converted typed object.
What I have so far is like that :
interface InputCallbackParams<T extends Message<MessageData> | number[]> {
  message: T;
  deltaTime?: number;
}

export interface InputCallbackOptions {
  callback?: (params: InputCallbackParams<Message<MessageData>>) => void;
  rawCallback?: (params: InputCallbackParams<number[]>) => void;
}

And the method that the user can use to listen to messages :
onMessage(
    options: InputCallbackOptions,
  ): void {
    this.callback = Deno.UnsafeCallback.threadSafe(
      RtMidiCCallbackCallbackDefinition,
      (
        deltaTime: number,
        message: Deno.PointerValue<unknown>,
        messageSize: number | bigint,
      ) => {
        const msg_data = new Uint8Array(
          new Deno.UnsafePointerView(message!).getArrayBuffer(
            messageSize as number,
          ),
        );

        if (options.callback !== undefined) {
          options.callback({
            message: decodeMessage(msg_data),
            deltaTime,
          });
        }

        if (options.rawCallback !== undefined) {
          options.rawCallback({
            message: Array.from(msg_data),
            deltaTime,
          });
        }
      },
    );
    rtmidi.rtmidi_in_set_callback(this.device, this.callback!.pointer, null);
  }

And I wonder if there's a better way to do that and if it looks understandable as an API user. Any thoughts welcome 🙂
Was this page helpful?