Sub classing an Napi::Object<>

WebAudio has nested classes. There are 18 specific sub-classes of AudioNode. I am wondering if or how I might avoid coding things a large number of times by sub-classing.

Here is what I have for AudioNode.h. Notice no Init(), just protected stuff:

#ifndef AUDIO_NODE_H
#define AUDIO_NODE_H

#include <napi.h>
#include <LabSound.h>

namespace webaudio {

	// being forced to set AudioNode for the wrap type is worrying
	class AudioNode : public Napi::ObjectWrap<AudioNode> {
	protected:
		// accessors
		Napi::Value ContextGetter(const Napi::CallbackInfo& info);
		Napi::Value NumberOfInputsGetter(const Napi::CallbackInfo& info);
		Napi::Value NumberOfOutputsGetter(const Napi::CallbackInfo& info);
		Napi::Value ChannelCountGetter(const Napi::CallbackInfo& info);
		Napi::Value ChannelCountModeGetter(const Napi::CallbackInfo& info);
		Napi::Value ChannelInterpretationGetter(const Napi::CallbackInfo& info);

		// methods
		Napi::Object Connect(const Napi::CallbackInfo& info);
		Napi::Object Disconnect(const Napi::CallbackInfo& info);

        // internal lab references
		std::shared_ptr<lab::AudioContext> m_labContext;
		std::shared_ptr<lab::AnalyserNode> m_labAudioNode;
	}
}
#endif

Using the AnalyserNode as an example of a specific Node type. It would have an h file of:

#ifndef ANALYSER_NODE_H
#define ANALYSER_NODE_H

#include <AudioNode.h>

namespace webaudio {

	class AnalyserNode : public AudioNode {
	public:
		static Napi::Object Init(Napi::Env env, Napi::Object exports);
		AnalyserNode(const Napi::CallbackInfo& info);

	private:
		Napi::Value FftSizeGetter(const Napi::CallbackInfo& info);
		void FftSizeSetter(const Napi::CallbackInfo& info);

		Napi::Value FrequencyBinCountGetter(const Napi::CallbackInfo& info);

		Napi::Value MinDecibelsGetter(const Napi::CallbackInfo& info);
		void MinDecibelsSetter(const Napi::CallbackInfo& info);

		Napi::Value MaxDecibelsGetter(const Napi::CallbackInfo& info);
		void MaxDecibelsSetter(const Napi::CallbackInfo& info);

		Napi::Value SmoothingTimeConstantGetter(const Napi::CallbackInfo& info);
		void SmoothingTimeConstantSetter(const Napi::CallbackInfo& info);

		void GetFloatFrequencyData(const Napi::CallbackInfo& info);
		void GetByteFrequencyData(const Napi::CallbackInfo& info);
		void GetFloatTimeDomainData(const Napi::CallbackInfo& info);
		void GetByteTimeDomainData(const Napi::CallbackInfo& info);
	}
}
#endif

The code for the Init() in AnalyserNode would include the accessors & methods of super class too:

Napi::Object AnalyserNode::Init(Napi::Env env, Napi::Object exports) {
	// This method is used to hook the accessor and method callbacks
	Napi::Function func = DefineClass(env, "AnalyserNode", {
		// accessors & methods for super class
		InstanceAccessor("context", &AnalyserNode::ContextGetter, nullptr),
		InstanceAccessor("numberOfInputs", &AnalyserNode::NumberOfInputsGetter, nullptr),
		InstanceAccessor("numberOfOutputs", &AnalyserNode::NumberOfOutputsGetter, nullptr),
		InstanceAccessor("channelCount", &AnalyserNode::ChannelCountGetter, nullptr),
		InstanceAccessor("channelCountMode", &AnalyserNode::ChannelCountModeGetter, nullptr),
		InstanceAccessor("channelInterpretation", &AnalyserNode::ChannelInterpretationGetter, nullptr),
		InstanceMethod("connect", &AnalyserNode::Connect),
		InstanceMethod("disconnect", &AnalyserNode::Disconnect),

		// accessors & methods for this class
		InstanceAccessor("fftSize", &AnalyserNode::FftSizeGetter, &AnalyserNode::FftSizeSetter),
		InstanceAccessor("frequencyBinCount", &AnalyserNode::FrequencyBinCountGetter, nullptr),
		InstanceAccessor("minDecibels", &AnalyserNode::MinDecibelsGetter, &AnalyserNode::MinDecibelsSetter),
		InstanceAccessor("maxDecibels", &AnalyserNode::MaxDecibelsGetter, &AnalyserNode::MaxDecibelsSetter),
		InstanceAccessor("smoothingTimeConstant", &AnalyserNode::smoothingTimeConstantGetter, &AnalyserNode::smoothingTimeConstantSetter),
		InstanceMethod("getFloatFrequencyData", &AnalyserNode::getFloatFrequencyData),
		InstanceMethod("getByteFrequencyData", &AnalyserNode::getByteFrequencyData),
		InstanceMethod("getFloatTimeDomainData", &AnalyserNode::GetWebXRRenderTarget),
		InstanceMethod("getByteTimeDomainData", &AnalyserNode::GetNativeRenderTargetProvider),
	});

	Napi::FunctionReference* constructor = new Napi::FunctionReference();
	*constructor = Napi::Persistent(func);
	exports.Set("AnalyserNode", func);
	env.SetInstanceData<Napi::FunctionReference>(constructor);

	return exports;
}

I have not seen an example that covers anything close to this. Any thoughts?

If this is a problem, how about the exotic C++ multiple inheritance as another way out?

Looking at a similar issue, we may want to use the equivalent of Object.setPrototypeOf in order to make sure we’re calling the right c++ functions when the superclass methods are invoked on the derived objects.

1 Like

Also a quick question I have a c++ binary search method which is obviously faster than javascript, how do I implement this function in the solution and call it in experience.js. I am really new into the napi stuff

Please create new topics for different questions :slight_smile: that way more eyes will see what you’re asking.

In short @waverider404 I think that depends on what you’re doing, and how often you need to access data in the JS engine from your C++ implementation, that will influence whether or not you’ll see a performance improvement. Create a new topic and we can discuss further.

1 Like

done!

1 Like

@Drigax, thanks for looking into this. That discussion goes on for a while, and looks messy & not conclusive. What I am going to try first is the C++ way of multiple inheritance. I Think N-API might just be blissfully unaware.

By this I mean, AudioNode is not a subclass of anything, but has some protected N-API calls:

class AudioNode {
protected:
	// accessors
	Napi::Value ContextGetter(const Napi::CallbackInfo& info);
	Napi::Value NumberOfInputsGetter(const Napi::CallbackInfo& info);
	Napi::Value NumberOfOutputsGetter(const Napi::CallbackInfo& info);
	Napi::Value ChannelCountGetter(const Napi::CallbackInfo& info);
	Napi::Value ChannelCountModeGetter(const Napi::CallbackInfo& info);
	Napi::Value ChannelInterpretationGetter(const Napi::CallbackInfo& info);

	// methods
	Napi::Object Connect(const Napi::CallbackInfo& info);
	Napi::Object Disconnect(const Napi::CallbackInfo& info);

    // internal lab references
	std::shared_ptr<lab::AudioContext> m_labContext;
}

The specific node class subclasses BOTH AudioNode AND ObjectWrap:

class AnalyserNode : public AudioNode, public Napi::ObjectWrap<AnalyserNode> {
public:
	static Napi::Object Init(Napi::Env env, Napi::Object exports);
	AnalyserNode(const Napi::CallbackInfo& info);

private:
	Napi::Value FftSizeGetter(const Napi::CallbackInfo& info);
	void FftSizeSetter(const Napi::CallbackInfo& info);

	Napi::Value FrequencyBinCountGetter(const Napi::CallbackInfo& info);

	Napi::Value MinDecibelsGetter(const Napi::CallbackInfo& info);
	void MinDecibelsSetter(const Napi::CallbackInfo& info);

	Napi::Value MaxDecibelsGetter(const Napi::CallbackInfo& info);
	void MaxDecibelsSetter(const Napi::CallbackInfo& info);

	Napi::Value SmoothingTimeConstantGetter(const Napi::CallbackInfo& info);
	void SmoothingTimeConstantSetter(const Napi::CallbackInfo& info);

	void GetFloatFrequencyData(const Napi::CallbackInfo& info);
	void GetByteFrequencyData(const Napi::CallbackInfo& info);
	void GetFloatTimeDomainData(const Napi::CallbackInfo& info);
	void GetByteTimeDomainData(const Napi::CallbackInfo& info);

    // internal lab references
 	std::shared_ptr<lab::AnalyserNode> m_labAudioNode;

}

The Init() would be the same as before. How would ObjectWrap even know that some stuff got farmed out to another subclass? The final AudioNode was even specified as the type of ObjectWrap.