// AudioFilterNode.h
// * PURPOSE
//   A framework class designed to make it easy to develop simple
//   audio filters in the BeOS Media Kit.  The actual DSP work
//   is done externally by an IAudioOp object.
//
// * NOT SUPPORTED YET... 
//   - multiple inputs or outputs
//   - chaining multiple operations (yum)
//
// * HISTORY
//   e.moon		7sep99		Begun: abstracting from FlangerNode and
//                      implementing IAudioOp.

#ifndef __AudioFilterNode_H__
#define __AudioFilterNode_H__

#include <BufferProducer.h>
#include <BufferConsumer.h>
#include <Controllable.h>
#include <Debug.h>
#include <MediaEventLooper.h>

#include "IAudioOpHost.h"

// forwards
class BBufferGroup;
class BMediaAddOn;

class AudioBuffer;
class IAudioOpFactory;
class IAudioOp;
class IParameterSet;

class AudioFilterNode :
	public		BBufferConsumer,
	public		BBufferProducer,
	public		BControllable,
	public		BMediaEventLooper,
	public		IAudioOpHost {

public:													// *** HOOKS

	// *** FORMAT NEGOTIATION
	
	// +++++ 8sep99: there's currently no differentiation made between input
	//               and output formats.  there probably should be, though for
	//               now you can do extra format restriction in your factory's
	//               createOp() implementation... which is too late.
	// +++++
	// +++++ okay, time to split out into getRequiredInputFormat(), ... etc
	//       subclasses allowing format conversion will need to restrict buffer
	//       sizes based on current connection (input or output.)

	// requests the required format for the given type (ioFormat.type must be
	// filled in!)
	// upon returning, any fields left as wildcards are negotiable.
	// Default implementation hands off to getPreferredFormat(),
	// yielding a rather strict node.
	
	virtual status_t getRequiredInputFormat(
		media_format&								ioFormat) { return getPreferredInputFormat(ioFormat); }
		
	virtual status_t getRequiredOutputFormat(
		media_format&								ioFormat) { return getPreferredOutputFormat(ioFormat); }
		
	// requests the required format for the given type (ioFormat.type must be
	// filled in!)
	// upon returning, all fields must be filled in.
	//
	// Default raw audio format:
	//   44100hz
	//   host-endian
	//   1 channel
	//   float
	//   1k buffers
	//
	virtual status_t getPreferredInputFormat(
		media_format&								ioFormat);
	virtual status_t getPreferredOutputFormat(
		media_format&								ioFormat);

	// test the given template format against a proposed format.
	// specialize wildcards for fields where the template contains
	// non-wildcard data; write required fields into proposed format
	// if they mismatch.
	// Returns B_OK if the proposed format doesn't conflict with the
	// template, or B_MEDIA_BAD_FORMAT otherwise.
	
	virtual status_t validateProposedInputFormat(
		const media_format&					preferredFormat,
		media_format&								ioProposedFormat);

	virtual status_t validateProposedOutputFormat(
		const media_format&					preferredFormat,
		media_format&								ioProposedFormat);
		
	// fill in wildcards in the given format.
	// (assumes the format passes validateProposedFormat().)
	// Default implementation: specializes all wildcards to format returned by
	//                         getPreferredXXXFormat()
	
	virtual void specializeInputFormat(
		media_format&								ioFormat) {

		media_format preferred;
		preferred.type = B_MEDIA_RAW_AUDIO;
		status_t err = getPreferredInputFormat(preferred);
		ASSERT(err == B_OK);
		_specialize_raw_audio_format(preferred, ioFormat);
	}

	virtual void specializeOutputFormat(
		media_format&								ioFormat) {

		char fmt_buffer[256];
		string_for_format(ioFormat, fmt_buffer, 255);
		PRINT((
			"### specializeOutputFormat:\n"
			"    given '%s'\n", fmt_buffer));

		media_format preferred;
		preferred.type = B_MEDIA_RAW_AUDIO;
		status_t err = getPreferredOutputFormat(preferred);
		ASSERT(err == B_OK);

		string_for_format(preferred, fmt_buffer, 255);
		PRINT((
			"    pref '%s'\n", fmt_buffer));

//		ioFormat.SpecializeTo(&preferred);
		_specialize_raw_audio_format(preferred, ioFormat);

		string_for_format(ioFormat, fmt_buffer, 255);
		PRINT((
			"    writing '%s'\n", fmt_buffer));
	}

public:													// *** ctor/dtor

	virtual ~AudioFilterNode();
	
	// the node acquires ownership of opFactory
	AudioFilterNode(
		const char*									name,
		IAudioOpFactory*						opFactory,
		BMediaAddOn*								addOn=0);
		
public:													// *** accessors
	const media_input& input() const { return m_input; }
	const media_output& output() const { return m_output; }

public:													// *** BMediaNode

	virtual status_t HandleMessage(
		int32												code,
		const void*									data,
		size_t											size);

	virtual BMediaAddOn* AddOn(
		int32*											outID) const;

	virtual void SetRunMode(
		run_mode										mode);
	
protected:											// *** BMediaEventLooper

	virtual void HandleEvent(
		const media_timed_event*		event,
		bigtime_t										howLate,
		bool												realTimeEvent=false);

	// "The Media Server calls this hook function after the node has
	//  been registered.  This is derived from BMediaNode; BMediaEventLooper
	//  implements it to call Run() automatically when the node is registered;
	//  if you implement NodeRegistered() you should call through to
	//  BMediaEventLooper::NodeRegistered() after you've done your custom 
	//  operations."

	virtual void NodeRegistered();
	
	// "Augment OfflineTime() to compute the node's current time; it's called
	//  by the Media Kit when it's in offline mode. Update any appropriate
	//  internal information as well, then call through to the BMediaEventLooper
	//  implementation."

	virtual bigtime_t OfflineTime(); //nyi

public:													// *** BBufferConsumer

	virtual status_t AcceptFormat(
		const media_destination&		destination,
		media_format*								ioFormat);
	
	// "If you're writing a node, and receive a buffer with the B_SMALL_BUFFER
	//  flag set, you must recycle the buffer before returning."	

	virtual void BufferReceived(
		BBuffer*										buffer);
	
	// * make sure to fill in poInput->format with the contents of
	//   pFormat; as of R4.5 the Media Kit passes poInput->format to
	//   the producer in BBufferProducer::Connect().

	virtual status_t Connected(
		const media_source&					source,
		const media_destination&		destination,
		const media_format&					format,
		media_input*								outInput);

	virtual void Disconnected(
		const media_source&					source,
		const media_destination&		destination);
		
	virtual void DisposeInputCookie(
		int32												cookie);
	
	// "You should implement this function so your node will know that the data
	//  format is going to change. Note that this may be called in response to
	//  your AcceptFormat() call, if your AcceptFormat() call alters any wildcard
	//  fields in the specified format. 
	//
	//  Because FormatChanged() is called by the producer, you don't need to (and
	//  shouldn't) ask it if the new format is acceptable. 
	//
	//  If the format change isn't possible, return an appropriate error from
	//  FormatChanged(); this error will be passed back to the producer that
	//  initiated the new format negotiation in the first place."

	virtual status_t FormatChanged(
		const media_source&					source,
		const media_destination&		destination,
		int32												changeTag,
		const media_format&					newFormat);
		
	virtual status_t GetLatencyFor(
		const media_destination&		destination,
		bigtime_t*									outLatency,
		media_node_id*							outTimeSource);
		
	virtual status_t GetNextInput(
		int32*											ioCookie,
		media_input*								outInput);

	virtual void ProducerDataStatus(
		const media_destination&		destination,
		int32												status,
		bigtime_t										tpWhen);
	
	// "This function is provided to aid in supporting media formats in which the
	//  outer encapsulation layer doesn't supply timing information. Producers will
	//  tag the buffers they generate with seek tags; these tags can be used to
	//  locate key frames in the media data."

	virtual status_t SeekTagRequested(
		const media_destination&		destination,
		bigtime_t										targetTime,
		uint32											flags,
		media_seek_tag*							outSeekTag,
		bigtime_t*									outTaggedTime,
		uint32*											outFlags);
	
public:													// *** BBufferProducer

	// "When a consumer calls BBufferConsumer::RequestAdditionalBuffer(), this
	//  function is called as a result. Its job is to call SendBuffer() to
	//  immediately send the next buffer to the consumer. The previousBufferID,
	//  previousTime, and previousTag arguments identify the last buffer the
	//  consumer received. Your node should respond by sending the next buffer
	//  after the one described. 
	//
	//  The previousTag may be NULL.
	//  Return B_OK if all is well; otherwise return an appropriate error code."
	virtual void AdditionalBufferRequested(
		const media_source&					source,
		media_buffer_id							previousBufferID,
		bigtime_t										previousTime,
		const media_seek_tag*				previousTag); //nyi
		
	virtual void Connect(
		status_t										status,
		const media_source&					source,
		const media_destination&		destination,
		const media_format&					format,
		char*												ioName); //nyi
		
	virtual void Disconnect(
		const media_source&					source,
		const media_destination&		destination); //nyi
		
	virtual status_t DisposeOutputCookie(
		int32												cookie); //nyi
		
	virtual void EnableOutput(
		const media_source&					source,
		bool												enabled,
		int32* _deprecated_); //nyi
		
	virtual status_t FormatChangeRequested(
		const media_source&					source,
		const media_destination&		destination,
		media_format*								ioFormat,
		int32* _deprecated_); //nyi
		
	virtual status_t FormatProposal(
		const media_source&					source,
		media_format*								ioFormat); //nyi
		
	virtual status_t FormatSuggestionRequested(
		media_type									type,
		int32												quality,
		media_format*								outFormat); //nyi
		
	virtual status_t GetLatency(
		bigtime_t*									outLatency); //nyi
		
	virtual status_t GetNextOutput(
		int32*											ioCookie,
		media_output*								outOutput); //nyi
	
	// "This hook function is called when a BBufferConsumer that's receiving data
	//  from you determines that its latency has changed. It will call its
	//  BBufferConsumer::SendLatencyChange() function, and in response, the Media
	//  Server will call your LatencyChanged() function.  The source argument
	//  indicates your output that's involved in the connection, and destination
	//  specifies the input on the consumer to which the connection is linked.
	//  newLatency is the consumer's new latency. The flags are currently unused."
	virtual void LatencyChanged(
		const media_source&					source,
		const media_destination&		destination,
		bigtime_t										newLatency,
		uint32											flags); //nyi

	virtual void LateNoticeReceived(
		const media_source&					source,
		bigtime_t										howLate,
		bigtime_t										tpWhen); //nyi
	
	// PrepareToConnect() is the second stage of format negotiations that happens
	// inside BMediaRoster::Connect().  At this point, the consumer's AcceptFormat()
	// method has been called, and that node has potentially changed the proposed
	// format.  It may also have left wildcards in the format.  PrepareToConnect()
	// *must* fully specialize the format before returning!

	virtual status_t PrepareToConnect(
		const media_source&					source,
		const media_destination&		destination,
		media_format*								ioFormat,
		media_source*								outSource,
		char*												outName); //nyi
		
	virtual status_t SetBufferGroup(
		const media_source&					source,
		BBufferGroup*								group); //nyi
	
	virtual status_t SetPlayRate(
		int32												numerator,
		int32												denominator); //nyi
	
	virtual status_t VideoClippingChanged(
		const media_source&					source,
		int16												numShorts,
		int16*											clipData,
		const media_video_display_info& display,
		int32*											outFromChangeTag); //nyi

public:													// *** BControllable

	virtual status_t GetParameterValue(
		int32												id,
		bigtime_t*									outLastChangeTime,
		void*												outValue,
		size_t*											ioSize); //nyi
		
	virtual void SetParameterValue(
		int32												id,
		bigtime_t										changeTime,
		const void*									value,
		size_t											size); //nyi

public:													// *** IAudioOpHost
	virtual IParameterSet* parameterSet() const; //nyi

protected:											// HandleEvent() impl.
	void handleParameterEvent(
		const media_timed_event*		event);
		
	void handleStartEvent(
		const media_timed_event*		event);
		
	void handleStopEvent(
		const media_timed_event*		event);
		
	void ignoreEvent(
		const media_timed_event*		event);

protected:											// *** internal operations

	// create and register a parameter web
	// +++++ 7sep99: hand off to IParameterSet::makeGroup()
	virtual void initParameterWeb();
	
	// [re-]initialize operation if necessary
	virtual void updateOperation(); //nyi
	
	// create or discard buffer group if necessary
	virtual void updateBufferGroup(); //nyi
	
//	// construct delay line if necessary, reset filter state
//	virtual void initFilter();
//	
//	virtual void startFilter();
//	virtual void stopFilter();

	// figure processing latency by doing 'dry runs' of processBuffer()
	virtual bigtime_t calcProcessingLatency(); //nyi
	
	// filter buffer data; inputBuffer and outputBuffer may be the same
	virtual void processBuffer(
		BBuffer*										inputBuffer,
		BBuffer*										outputBuffer);
	
	status_t _validate_raw_audio_format(
		const media_format&					preferredFormat,
		media_format&								ioProposedFormat);
	
	void _specialize_raw_audio_format(
		const media_format&					templateFormat,
		media_format&								ioFormat); 
				
private:												// *** connection/format members

	// +++++ expose input & output to subclasses [8sep99]

	// Connections & associated state variables	
	media_input				m_input;

	media_output			m_output;
	bool							m_outputEnabled;

	// Time required by downstream consumer(s) to properly deliver a buffer
	bigtime_t					m_downstreamLatency;

	// Worst-case time needed to fill a buffer
	bigtime_t					m_processingLatency;

	// buffer group for on-the-fly conversion [8sep99]
	// (only created when necessary, ie. more data going out than coming in)
	BBufferGroup*			m_bufferGroup;
	
	
	// +++++ test 9sep99
	bigtime_t					m_tpLastReceived;
	
private:												// *** parameters
	IParameterSet*								m_parameterSet;

private:												// *** operations
	IAudioOpFactory*	m_opFactory;
	
	// the current operation
	IAudioOp*					m_op;

private:												// *** add-on stuff

	// host add-on
	BMediaAddOn*	m_addOn;
};

#endif /*__AudioFilterNode_H__*/