1/*
2 * Copyright (c) 1999-2000, Eric Moon.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions, and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions, and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * 3. The name of the author may not be used to endorse or promote products
17 *    derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31
32// FlangerNode.cpp
33// e.moon 16jun99
34
35#include "FlangerNode.h"
36
37#include "AudioBuffer.h"
38#include "SoundUtils.h"
39
40#include <Buffer.h>
41#include <BufferGroup.h>
42#include <ByteOrder.h>
43#include <Catalog.h>
44#include <Debug.h>
45#include <ParameterWeb.h>
46#include <TimeSource.h>
47
48#include <cstdio>
49#include <cstdlib>
50#include <cstring>
51#include <cmath>
52
53#undef B_TRANSLATION_CONTEXT
54#define B_TRANSLATION_CONTEXT "CortexAddOnsFlanger"
55
56// -------------------------------------------------------- //
57// local helpers
58// -------------------------------------------------------- //
59
60float calc_sweep_delta(
61	const media_raw_audio_format& format,
62	float fRate);
63
64float calc_sweep_base(
65	const media_raw_audio_format& format,
66	float fDelay, float fDepth);
67
68float calc_sweep_factor(
69	const media_raw_audio_format& format,
70	float fDepth);
71
72// -------------------------------------------------------- //
73// constants
74// -------------------------------------------------------- //
75
76// input-ID symbols
77enum input_id_t {
78	ID_AUDIO_INPUT
79};
80
81// output-ID symbols
82enum output_id_t {
83	ID_AUDIO_MIX_OUTPUT
84	//ID_AUDIO_WET_OUTPUT ...
85};
86
87// parameter ID
88enum param_id_t {
89	P_MIX_RATIO_LABEL		= 100,
90	P_MIX_RATIO,
91
92	P_SWEEP_RATE_LABEL	= 200,
93	P_SWEEP_RATE,
94
95	P_DELAY_LABEL				= 300,
96	P_DELAY,
97
98	P_DEPTH_LABEL				= 400,
99	P_DEPTH,
100
101	P_FEEDBACK_LABEL		= 500,
102	P_FEEDBACK
103};
104
105const float FlangerNode::s_fMaxDelay = 100.0;
106const char* const FlangerNode::s_nodeName = "FlangerNode";
107
108
109// -------------------------------------------------------- //
110// ctor/dtor
111// -------------------------------------------------------- //
112
113FlangerNode::~FlangerNode() {
114	// shut down
115	Quit();
116
117	// free delay buffer
118	if(m_pDelayBuffer)
119		delete m_pDelayBuffer;
120}
121
122FlangerNode::FlangerNode(BMediaAddOn* pAddOn) :
123	// * init base classes
124	BMediaNode(s_nodeName), // (virtual base)
125	BBufferConsumer(B_MEDIA_RAW_AUDIO),
126	BBufferProducer(B_MEDIA_RAW_AUDIO),
127	BControllable(),
128	BMediaEventLooper(),
129
130	// * init connection state
131	m_outputEnabled(true),
132	m_downstreamLatency(0),
133	m_processingLatency(0),
134
135	// * init filter state
136	m_pDelayBuffer(0),
137
138	// * init add-on stuff
139	m_pAddOn(pAddOn) {
140
141//	PRINT((
142//		"\n"
143//		"--*-- FlangerNode() [%s] --*--\n\n",
144//		__BUILD_DATE));
145//
146	// the rest of the initialization happens in NodeRegistered().
147}
148
149
150// -------------------------------------------------------- //
151// *** BMediaNode
152// -------------------------------------------------------- //
153
154status_t FlangerNode::HandleMessage(
155	int32 code,
156	const void* pData,
157	size_t size) {
158
159	// pass off to each base class
160	if(
161		BBufferConsumer::HandleMessage(code, pData, size) &&
162		BBufferProducer::HandleMessage(code, pData, size) &&
163		BControllable::HandleMessage(code, pData, size) &&
164		BMediaNode::HandleMessage(code, pData, size))
165		BMediaNode::HandleBadMessage(code, pData, size);
166
167	// +++++ return error on bad message?
168	return B_OK;
169}
170
171BMediaAddOn* FlangerNode::AddOn(
172	int32* poID) const {
173
174	if(m_pAddOn)
175		*poID = 0;
176	return m_pAddOn;
177}
178
179void FlangerNode::SetRunMode(
180	run_mode mode) {
181
182	// disallow offline mode for now
183	// +++++
184	if(mode == B_OFFLINE)
185		ReportError(B_NODE_FAILED_SET_RUN_MODE);
186
187	// +++++ any other work to do?
188
189	// hand off
190	BMediaEventLooper::SetRunMode(mode);
191}
192
193// -------------------------------------------------------- //
194// *** BMediaEventLooper
195// -------------------------------------------------------- //
196
197void FlangerNode::HandleEvent(
198	const media_timed_event* pEvent,
199	bigtime_t howLate,
200	bool realTimeEvent) {
201
202	ASSERT(pEvent);
203
204	switch(pEvent->type) {
205		case BTimedEventQueue::B_PARAMETER:
206			handleParameterEvent(pEvent);
207			break;
208
209		case BTimedEventQueue::B_START:
210			handleStartEvent(pEvent);
211			break;
212
213		case BTimedEventQueue::B_STOP:
214			handleStopEvent(pEvent);
215			break;
216
217		default:
218			ignoreEvent(pEvent);
219			break;
220	}
221}
222
223// "The Media Server calls this hook function after the node has
224//  been registered.  This is derived from BMediaNode; BMediaEventLooper
225//  implements it to call Run() automatically when the node is registered;
226//  if you implement NodeRegistered() you should call through to
227//  BMediaEventLooper::NodeRegistered() after you've done your custom
228//  operations."
229
230void FlangerNode::NodeRegistered() {
231
232	PRINT(("FlangerNode::NodeRegistered()\n"));
233
234	// figure preferred ('template') format
235	m_preferredFormat.type = B_MEDIA_RAW_AUDIO;
236	getPreferredFormat(m_preferredFormat);
237
238	// initialize current format
239	m_format.type = B_MEDIA_RAW_AUDIO;
240	m_format.u.raw_audio = media_raw_audio_format::wildcard;
241
242	// init input
243	m_input.destination.port = ControlPort();
244	m_input.destination.id = ID_AUDIO_INPUT;
245	m_input.node = Node();
246	m_input.source = media_source::null;
247	m_input.format = m_format;
248	strlcpy(m_input.name, B_TRANSLATE("Audio input"), B_MEDIA_NAME_LENGTH);
249
250	// init output
251	m_output.source.port = ControlPort();
252	m_output.source.id = ID_AUDIO_MIX_OUTPUT;
253	m_output.node = Node();
254	m_output.destination = media_destination::null;
255	m_output.format = m_format;
256	strlcpy(m_output.name, B_TRANSLATE("Mix output"), B_MEDIA_NAME_LENGTH);
257
258	// init parameters
259	initParameterValues();
260	initParameterWeb();
261
262	// Start the BMediaEventLooper thread
263	SetPriority(B_REAL_TIME_PRIORITY);
264	Run();
265}
266
267// "Augment OfflineTime() to compute the node's current time; it's called
268//  by the Media Kit when it's in offline mode. Update any appropriate
269//  internal information as well, then call through to the BMediaEventLooper
270//  implementation."
271
272bigtime_t FlangerNode::OfflineTime() {
273	// +++++
274	return 0LL;
275}
276
277// -------------------------------------------------------- //
278// *** BBufferConsumer
279// -------------------------------------------------------- //
280
281status_t FlangerNode::AcceptFormat(
282	const media_destination& destination,
283	media_format* pioFormat) {
284
285	PRINT(("FlangerNode::AcceptFormat()\n"));
286
287	// sanity checks
288	if(destination != m_input.destination) {
289		PRINT(("\tbad destination\n"));
290		return B_MEDIA_BAD_DESTINATION;
291	}
292	if(pioFormat->type != B_MEDIA_RAW_AUDIO) {
293		PRINT(("\tnot B_MEDIA_RAW_AUDIO\n"));
294		return B_MEDIA_BAD_FORMAT;
295	}
296
297	validateProposedFormat(
298		(m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) ?
299			m_format : m_preferredFormat,
300		*pioFormat);
301	return B_OK;
302}
303
304// "If you're writing a node, and receive a buffer with the B_SMALL_BUFFER
305//  flag set, you must recycle the buffer before returning."
306
307void FlangerNode::BufferReceived(
308	BBuffer* pBuffer) {
309	ASSERT(pBuffer);
310
311	// check buffer destination
312	if(pBuffer->Header()->destination !=
313		m_input.destination.id) {
314		PRINT(("FlangerNode::BufferReceived():\n"
315			"\tBad destination.\n"));
316		pBuffer->Recycle();
317		return;
318	}
319
320	if(pBuffer->Header()->time_source != TimeSource()->ID()) {
321		PRINT(("* timesource mismatch\n"));
322	}
323
324	// check output
325	if(m_output.destination == media_destination::null ||
326		!m_outputEnabled) {
327		pBuffer->Recycle();
328		return;
329	}
330
331	// process and retransmit buffer
332	filterBuffer(pBuffer);
333
334	status_t err = SendBuffer(pBuffer, m_output.source, m_output.destination);
335	if (err < B_OK) {
336		PRINT(("FlangerNode::BufferReceived():\n"
337			"\tSendBuffer() failed: %s\n", strerror(err)));
338		pBuffer->Recycle();
339	}
340	// sent!
341}
342
343// * make sure to fill in poInput->format with the contents of
344//   pFormat; as of R4.5 the Media Kit passes poInput->format to
345//   the producer in BBufferProducer::Connect().
346
347status_t
348FlangerNode::Connected(const media_source& source,
349	const media_destination& destination, const media_format& format,
350	media_input* poInput)
351{
352	PRINT(("FlangerNode::Connected()\n"
353		"\tto source %" B_PRId32 "\n", source.id));
354
355	// sanity check
356	if(destination != m_input.destination) {
357		PRINT(("\tbad destination\n"));
358		return B_MEDIA_BAD_DESTINATION;
359	}
360	if(m_input.source != media_source::null) {
361		PRINT(("\talready connected\n"));
362		return B_MEDIA_ALREADY_CONNECTED;
363	}
364
365	// initialize input
366	m_input.source = source;
367	m_input.format = format;
368	*poInput = m_input;
369
370	// store format (this now constrains the output format)
371	m_format = format;
372
373	return B_OK;
374}
375
376void FlangerNode::Disconnected(
377	const media_source& source,
378	const media_destination& destination) {
379
380	PRINT(("FlangerNode::Disconnected()\n"));
381
382	// sanity checks
383	if(m_input.source != source) {
384		PRINT(("\tsource mismatch: expected ID %" B_PRId32 ", got %" B_PRId32
385				"\n", m_input.source.id, source.id));
386		return;
387	}
388	if(destination != m_input.destination) {
389		PRINT(("\tdestination mismatch: expected ID %" B_PRId32 ", got %"
390				B_PRId32 "\n", m_input.destination.id, destination.id));
391		return;
392	}
393
394	// mark disconnected
395	m_input.source = media_source::null;
396
397	// no output? clear format:
398	if(m_output.destination == media_destination::null) {
399		m_format.u.raw_audio = media_raw_audio_format::wildcard;
400	}
401
402	m_input.format = m_format;
403
404}
405
406void FlangerNode::DisposeInputCookie(
407	int32 cookie) {}
408
409// "You should implement this function so your node will know that the data
410//  format is going to change. Note that this may be called in response to
411//  your AcceptFormat() call, if your AcceptFormat() call alters any wildcard
412//  fields in the specified format.
413//
414//  Because FormatChanged() is called by the producer, you don't need to (and
415//  shouldn't) ask it if the new format is acceptable.
416//
417//  If the format change isn't possible, return an appropriate error from
418//  FormatChanged(); this error will be passed back to the producer that
419//  initiated the new format negotiation in the first place."
420
421status_t FlangerNode::FormatChanged(
422	const media_source& source,
423	const media_destination& destination,
424	int32 changeTag,
425	const media_format& newFormat) {
426
427	// flat-out deny format changes
428	return B_MEDIA_BAD_FORMAT;
429}
430
431status_t FlangerNode::GetLatencyFor(
432	const media_destination& destination,
433	bigtime_t* poLatency,
434	media_node_id* poTimeSource) {
435
436	PRINT(("FlangerNode::GetLatencyFor()\n"));
437
438	// sanity check
439	if(destination != m_input.destination) {
440		PRINT(("\tbad destination\n"));
441		return B_MEDIA_BAD_DESTINATION;
442	}
443
444	*poLatency = m_downstreamLatency + m_processingLatency;
445	PRINT(("\treturning %" B_PRIdBIGTIME "\n", *poLatency));
446	*poTimeSource = TimeSource()->ID();
447	return B_OK;
448}
449
450status_t FlangerNode::GetNextInput(
451	int32* pioCookie,
452	media_input* poInput) {
453
454	if(*pioCookie)
455		return B_BAD_INDEX;
456
457	++*pioCookie;
458	*poInput = m_input;
459	return B_OK;
460}
461
462void FlangerNode::ProducerDataStatus(
463	const media_destination& destination,
464	int32 status,
465	bigtime_t tpWhen) {
466
467	PRINT(("FlangerNode::ProducerDataStatus()\n"));
468
469	// sanity check
470	if(destination != m_input.destination) {
471		PRINT(("\tbad destination\n"));
472	}
473
474	if(m_output.destination != media_destination::null) {
475		// pass status downstream
476		status_t err = SendDataStatus(
477			status,
478			m_output.destination,
479			tpWhen);
480		if(err < B_OK) {
481			PRINT(("\tSendDataStatus(): %s\n", strerror(err)));
482		}
483	}
484}
485
486// "This function is provided to aid in supporting media formats in which the
487//  outer encapsulation layer doesn't supply timing information. Producers will
488//  tag the buffers they generate with seek tags; these tags can be used to
489//  locate key frames in the media data."
490
491status_t FlangerNode::SeekTagRequested(
492	const media_destination& destination,
493	bigtime_t targetTime,
494	uint32 flags,
495	media_seek_tag* poSeekTag,
496	bigtime_t* poTaggedTime,
497	uint32* poFlags) {
498
499	PRINT(("FlangerNode::SeekTagRequested()\n"
500		"\tNot implemented.\n"));
501	return B_ERROR;
502}
503
504// -------------------------------------------------------- //
505// *** BBufferProducer
506// -------------------------------------------------------- //
507
508// "When a consumer calls BBufferConsumer::RequestAdditionalBuffer(), this
509//  function is called as a result. Its job is to call SendBuffer() to
510//  immediately send the next buffer to the consumer. The previousBufferID,
511//  previousTime, and previousTag arguments identify the last buffer the
512//  consumer received. Your node should respond by sending the next buffer
513//  after the one described.
514//
515//  The previousTag may be NULL.
516//  Return B_OK if all is well; otherwise return an appropriate error code."
517
518void FlangerNode::AdditionalBufferRequested(
519	const media_source& source,
520	media_buffer_id previousBufferID,
521	bigtime_t previousTime,
522	const media_seek_tag* pPreviousTag) {
523
524	PRINT(("FlangerNode::AdditionalBufferRequested\n"
525		"\tOffline mode not implemented."));
526}
527
528void FlangerNode::Connect(
529	status_t status,
530	const media_source& source,
531	const media_destination& destination,
532	const media_format& format,
533	char* pioName) {
534
535	PRINT(("FlangerNode::Connect()\n"));
536	status_t err;
537
538	// connection failed?
539	if(status < B_OK) {
540		PRINT(("\tStatus: %s\n", strerror(status)));
541		// 'unreserve' the output
542		m_output.destination = media_destination::null;
543		return;
544	}
545
546	// connection established:
547	strncpy(pioName, m_output.name, B_MEDIA_NAME_LENGTH);
548	m_output.destination = destination;
549	m_format = format;
550
551	// figure downstream latency
552	media_node_id timeSource;
553	err = FindLatencyFor(m_output.destination, &m_downstreamLatency, &timeSource);
554	if(err < B_OK) {
555		PRINT(("\t!!! FindLatencyFor(): %s\n", strerror(err)));
556	}
557	PRINT(("\tdownstream latency = %" B_PRIdBIGTIME "\n", m_downstreamLatency));
558
559	// prepare the filter
560	initFilter();
561
562	// figure processing time
563	m_processingLatency = calcProcessingLatency();
564	PRINT(("\tprocessing latency = %" B_PRIdBIGTIME "\n", m_processingLatency));
565
566	// store summed latency
567	SetEventLatency(m_downstreamLatency + m_processingLatency);
568
569	if(m_input.source != media_source::null) {
570		// pass new latency upstream
571		err = SendLatencyChange(
572			m_input.source,
573			m_input.destination,
574			EventLatency() + SchedulingLatency());
575		if(err < B_OK)
576			PRINT(("\t!!! SendLatencyChange(): %s\n", strerror(err)));
577	}
578
579	// cache buffer duration
580	SetBufferDuration(
581		buffer_duration(
582			m_format.u.raw_audio));
583}
584
585void FlangerNode::Disconnect(
586	const media_source& source,
587	const media_destination& destination) {
588
589	PRINT(("FlangerNode::Disconnect()\n"));
590
591	// sanity checks
592	if(source != m_output.source) {
593		PRINT(("\tbad source\n"));
594		return;
595	}
596	if(destination != m_output.destination) {
597		PRINT(("\tbad destination\n"));
598		return;
599	}
600
601	// clean up
602	m_output.destination = media_destination::null;
603
604	// no input? clear format:
605	if(m_input.source == media_source::null) {
606		m_format.u.raw_audio = media_raw_audio_format::wildcard;
607	}
608
609	m_output.format = m_format;
610
611	// +++++ other cleanup goes here
612}
613
614status_t FlangerNode::DisposeOutputCookie(
615	int32 cookie) {
616	return B_OK;
617}
618
619void FlangerNode::EnableOutput(
620	const media_source& source,
621	bool enabled,
622	int32* _deprecated_) {
623	PRINT(("FlangerNode::EnableOutput()\n"));
624	if(source != m_output.source) {
625		PRINT(("\tbad source\n"));
626		return;
627	}
628
629	m_outputEnabled = enabled;
630}
631
632status_t FlangerNode::FormatChangeRequested(
633	const media_source& source,
634	const media_destination& destination,
635	media_format* pioFormat,
636	int32* _deprecated_) {
637
638	// deny
639	PRINT(("FlangerNode::FormatChangeRequested()\n"
640		"\tNot supported.\n"));
641
642	return B_MEDIA_BAD_FORMAT;
643}
644
645status_t FlangerNode::FormatProposal(
646	const media_source& source,
647	media_format* pioFormat) {
648
649	PRINT(("FlangerNode::FormatProposal()\n"));
650
651	if(source != m_output.source) {
652		PRINT(("\tbad source\n"));
653		return B_MEDIA_BAD_SOURCE;
654	}
655
656	if(pioFormat->type != B_MEDIA_RAW_AUDIO) {
657		PRINT(("\tbad type\n"));
658		return B_MEDIA_BAD_FORMAT;
659	}
660
661	validateProposedFormat(
662		(m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) ?
663			m_format :
664			m_preferredFormat,
665		*pioFormat);
666	return B_OK;
667}
668
669status_t FlangerNode::FormatSuggestionRequested(
670	media_type type,
671	int32 quality,
672	media_format* poFormat) {
673
674	PRINT(("FlangerNode::FormatSuggestionRequested()\n"));
675	if(type != B_MEDIA_RAW_AUDIO) {
676		PRINT(("\tbad type\n"));
677		return B_MEDIA_BAD_FORMAT;
678	}
679
680	if(m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format)
681		*poFormat = m_format;
682	else
683		*poFormat = m_preferredFormat;
684	return B_OK;
685}
686
687status_t FlangerNode::GetLatency(
688	bigtime_t* poLatency) {
689
690	PRINT(("FlangerNode::GetLatency()\n"));
691	*poLatency = EventLatency() + SchedulingLatency();
692	PRINT(("\treturning %" B_PRIdBIGTIME "\n", *poLatency));
693
694	return B_OK;
695}
696
697status_t FlangerNode::GetNextOutput(
698	int32* pioCookie,
699	media_output* poOutput) {
700
701	if(*pioCookie)
702		return B_BAD_INDEX;
703
704	++*pioCookie;
705	*poOutput = m_output;
706
707	return B_OK;
708}
709
710// "This hook function is called when a BBufferConsumer that's receiving data
711//  from you determines that its latency has changed. It will call its
712//  BBufferConsumer::SendLatencyChange() function, and in response, the Media
713//  Server will call your LatencyChanged() function.  The source argument
714//  indicates your output that's involved in the connection, and destination
715//  specifies the input on the consumer to which the connection is linked.
716//  newLatency is the consumer's new latency. The flags are currently unused."
717void FlangerNode::LatencyChanged(
718	const media_source& source,
719	const media_destination& destination,
720	bigtime_t newLatency,
721	uint32 flags) {
722
723	PRINT(("FlangerNode::LatencyChanged()\n"));
724
725	if(source != m_output.source) {
726		PRINT(("\tBad source.\n"));
727		return;
728	}
729	if(destination != m_output.destination) {
730		PRINT(("\tBad destination.\n"));
731		return;
732	}
733
734	m_downstreamLatency = newLatency;
735	SetEventLatency(m_downstreamLatency + m_processingLatency);
736
737	if(m_input.source != media_source::null) {
738		// pass new latency upstream
739		status_t err = SendLatencyChange(
740			m_input.source,
741			m_input.destination,
742			EventLatency() + SchedulingLatency());
743		if(err < B_OK)
744			PRINT(("\t!!! SendLatencyChange(): %s\n", strerror(err)));
745	}
746}
747
748void FlangerNode::LateNoticeReceived(
749	const media_source& source,
750	bigtime_t howLate,
751	bigtime_t tpWhen) {
752
753	PRINT(("FlangerNode::LateNoticeReceived()\n"
754		"\thowLate == %" B_PRIdBIGTIME "\n"
755		"\twhen    == %" B_PRIdBIGTIME "\n", howLate, tpWhen));
756
757	if(source != m_output.source) {
758		PRINT(("\tBad source.\n"));
759		return;
760	}
761
762	if(m_input.source == media_source::null) {
763		PRINT(("\t!!! No input to blame.\n"));
764		return;
765	}
766
767	// +++++ check run mode?
768
769	// pass the buck, since this node doesn't schedule buffer
770	// production
771	NotifyLateProducer(
772		m_input.source,
773		howLate,
774		tpWhen);
775}
776
777// PrepareToConnect() is the second stage of format negotiations that happens
778// inside BMediaRoster::Connect().  At this point, the consumer's AcceptFormat()
779// method has been called, and that node has potentially changed the proposed
780// format.  It may also have left wildcards in the format.  PrepareToConnect()
781// *must* fully specialize the format before returning!
782
783status_t FlangerNode::PrepareToConnect(
784	const media_source& source,
785	const media_destination& destination,
786	media_format* pioFormat,
787	media_source* poSource,
788	char* poName) {
789
790	char formatStr[256];
791	string_for_format(*pioFormat, formatStr, 255);
792	PRINT(("FlangerNode::PrepareToConnect()\n"
793		"\tproposed format: %s\n", formatStr));
794
795	if(source != m_output.source) {
796		PRINT(("\tBad source.\n"));
797		return B_MEDIA_BAD_SOURCE;
798	}
799	if(m_output.destination != media_destination::null) {
800		PRINT(("\tAlready connected.\n"));
801		return B_MEDIA_ALREADY_CONNECTED;
802	}
803
804	if(pioFormat->type != B_MEDIA_RAW_AUDIO) {
805		PRINT(("\tBad format type.\n"));
806		return B_MEDIA_BAD_FORMAT;
807	}
808
809	// do a final validity check:
810	status_t err = validateProposedFormat(
811		(m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) ?
812			m_format :
813			m_preferredFormat,
814		*pioFormat);
815
816	if(err < B_OK) {
817		// no go
818		return err;
819	}
820
821	// fill in wildcards
822	specializeOutputFormat(*pioFormat);
823
824	// reserve the output
825	m_output.destination = destination;
826	m_output.format = *pioFormat;
827
828	// pass back source & output name
829	*poSource = m_output.source;
830	strncpy(poName, m_output.name, B_MEDIA_NAME_LENGTH);
831
832	return B_OK;
833}
834
835status_t FlangerNode::SetBufferGroup(
836	const media_source& source,
837	BBufferGroup* pGroup) {
838
839	PRINT(("FlangerNode::SetBufferGroup()\n"));
840	if(source != m_output.source) {
841		PRINT(("\tBad source.\n"));
842		return B_MEDIA_BAD_SOURCE;
843	}
844
845	if(m_input.source == media_source::null) {
846		PRINT(("\tNo producer to send buffers to.\n"));
847		return B_ERROR;
848	}
849
850	// +++++ is this right?  buffer-group selection gets
851	//       all asynchronous and weird...
852	int32 changeTag;
853	return SetOutputBuffersFor(
854		m_input.source,
855		m_input.destination,
856		pGroup,
857		0, &changeTag);
858}
859
860status_t FlangerNode::SetPlayRate(
861	int32 numerator,
862	int32 denominator) {
863	// not supported
864	return B_ERROR;
865}
866
867status_t FlangerNode::VideoClippingChanged(
868	const media_source& source,
869	int16 numShorts,
870	int16* pClipData,
871	const media_video_display_info& display,
872	int32* poFromChangeTag) {
873	// not sane
874	return B_ERROR;
875}
876
877// -------------------------------------------------------- //
878// *** BControllable
879// -------------------------------------------------------- //
880
881status_t FlangerNode::GetParameterValue(
882	int32 id,
883	bigtime_t* poLastChangeTime,
884	void* poValue,
885	size_t* pioSize) {
886
887//	PRINT(("FlangerNode::GetParameterValue()\n"));
888
889	// all parameters are floats
890	if(*pioSize < sizeof(float)) {
891		return B_NO_MEMORY;
892	}
893
894	*pioSize = sizeof(float);
895	switch(id) {
896		case P_MIX_RATIO:
897			*(float*)poValue = m_fMixRatio;
898			*poLastChangeTime = m_tpMixRatioChanged;
899			break;
900
901		case P_SWEEP_RATE:
902			*(float*)poValue = m_fSweepRate;
903			*poLastChangeTime = m_tpSweepRateChanged;
904			break;
905
906		case P_DELAY:
907			*(float*)poValue = m_fDelay;
908			*poLastChangeTime = m_tpDelayChanged;
909			break;
910
911		case P_DEPTH:
912			*(float*)poValue = m_fDepth;
913			*poLastChangeTime = m_tpDepthChanged;
914			break;
915
916		case P_FEEDBACK:
917			*(float*)poValue = m_fFeedback;
918			*poLastChangeTime = m_tpFeedbackChanged;
919			break;
920
921		default:
922			return B_ERROR;
923	}
924
925	return B_OK;
926}
927
928void FlangerNode::SetParameterValue(
929	int32 id,
930	bigtime_t changeTime,
931	const void* pValue,
932	size_t size) {
933
934	switch(id) {
935		case P_MIX_RATIO:
936		case P_SWEEP_RATE:
937		case P_DELAY:
938		case P_DEPTH:
939		case P_FEEDBACK: {
940			if(size < sizeof(float))
941				break;
942
943//      this is from ToneProducer.  it's fishy.
944//			if(size > sizeof(float))
945//				size = sizeof(float);
946
947			media_timed_event ev(
948				changeTime,
949				BTimedEventQueue::B_PARAMETER,
950				0,
951				BTimedEventQueue::B_NO_CLEANUP,
952				size,
953				id,
954				(char*)pValue, size);
955			EventQueue()->AddEvent(ev);
956			break;
957		}
958	}
959}
960
961// -------------------------------------------------------- //
962// *** HandleEvent() impl
963// -------------------------------------------------------- //
964
965void FlangerNode::handleParameterEvent(
966	const media_timed_event* pEvent) {
967
968	float value = *(float*)pEvent->user_data;
969	int32 id = pEvent->bigdata;
970	size_t size = pEvent->data;
971	bigtime_t now = TimeSource()->Now();
972
973	switch(id) {
974		case P_MIX_RATIO:
975			if(value == m_fMixRatio)
976				break;
977
978			// set
979			m_fMixRatio = value;
980			m_tpMixRatioChanged = now;
981			// broadcast
982			BroadcastNewParameterValue(
983				now,
984				id,
985				&m_fMixRatio,
986				size);
987			break;
988
989		case P_SWEEP_RATE:
990			if(value == m_fSweepRate)
991				break;
992
993			// set
994			m_fSweepRate = value;
995			m_tpSweepRateChanged = now;
996
997			if(m_output.destination != media_destination::null) {
998				m_fThetaInc = calc_sweep_delta(
999					m_format.u.raw_audio,
1000					m_fSweepRate);
1001			}
1002
1003			// broadcast
1004			BroadcastNewParameterValue(
1005				now,
1006				id,
1007				&m_fSweepRate,
1008				size);
1009			break;
1010
1011		case P_DELAY:
1012			if(value == m_fDelay)
1013				break;
1014
1015			// set
1016			m_fDelay = value;
1017			m_tpDelayChanged = now;
1018
1019			if(m_output.destination != media_destination::null) {
1020				m_fSweepBase = calc_sweep_base(
1021					m_format.u.raw_audio,
1022					m_fDelay, m_fDepth);
1023			}
1024
1025			// broadcast
1026			BroadcastNewParameterValue(
1027				now,
1028				id,
1029				&m_fDelay,
1030				size);
1031			break;
1032
1033		case P_DEPTH:
1034			if(value == m_fDepth)
1035				break;
1036
1037			// set
1038			m_fDepth = value;
1039			m_tpDepthChanged = now;
1040
1041			if(m_output.destination != media_destination::null) {
1042				m_fSweepBase = calc_sweep_base(
1043					m_format.u.raw_audio,
1044					m_fDelay, m_fDepth);
1045				m_fSweepFactor = calc_sweep_factor(
1046					m_format.u.raw_audio,
1047					m_fDepth);
1048			}
1049
1050			// broadcast
1051			BroadcastNewParameterValue(
1052				now,
1053				id,
1054				&m_fDepth,
1055				size);
1056			break;
1057
1058		case P_FEEDBACK:
1059			if(value == m_fFeedback)
1060				break;
1061
1062			// set
1063			m_fFeedback = value;
1064			m_tpFeedbackChanged = now;
1065			// broadcast
1066			BroadcastNewParameterValue(
1067				now,
1068				id,
1069				&m_fFeedback,
1070				size);
1071			break;
1072	}
1073}
1074
1075void FlangerNode::handleStartEvent(
1076	const media_timed_event* pEvent) {
1077	PRINT(("FlangerNode::handleStartEvent\n"));
1078
1079	startFilter();
1080}
1081
1082void FlangerNode::handleStopEvent(
1083	const media_timed_event* pEvent) {
1084	PRINT(("FlangerNode::handleStopEvent\n"));
1085
1086	stopFilter();
1087}
1088
1089void FlangerNode::ignoreEvent(
1090	const media_timed_event* pEvent) {
1091	PRINT(("FlangerNode::ignoreEvent\n"));
1092
1093}
1094
1095
1096// -------------------------------------------------------- //
1097// *** internal operations
1098// -------------------------------------------------------- //
1099
1100
1101// figure the preferred format: any fields left as wildcards
1102// are negotiable
1103void FlangerNode::getPreferredFormat(
1104	media_format& ioFormat) {
1105	ASSERT(ioFormat.type == B_MEDIA_RAW_AUDIO);
1106
1107	ioFormat.u.raw_audio = media_raw_audio_format::wildcard;
1108	ioFormat.u.raw_audio.channel_count = 1;
1109	ioFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
1110
1111//	ioFormat.u.raw_audio.frame_rate = 44100.0;
1112//	ioFormat.u.raw_audio.buffer_size = 0x1000;
1113}
1114
1115// test the given template format against a proposed format.
1116// specialize wildcards for fields where the template contains
1117// non-wildcard data; write required fields into proposed format
1118// if they mismatch.
1119// Returns B_OK if the proposed format doesn't conflict with the
1120// template, or B_MEDIA_BAD_FORMAT otherwise.
1121
1122status_t FlangerNode::validateProposedFormat(
1123	const media_format& preferredFormat,
1124	media_format& ioProposedFormat) {
1125
1126	char formatStr[256];
1127	PRINT(("FlangerNode::validateProposedFormat()\n"));
1128
1129	ASSERT(preferredFormat.type == B_MEDIA_RAW_AUDIO);
1130
1131	string_for_format(preferredFormat, formatStr, 255);
1132	PRINT(("\ttemplate format: %s\n", formatStr));
1133
1134	string_for_format(ioProposedFormat, formatStr, 255);
1135	PRINT(("\tproposed format: %s\n", formatStr));
1136
1137	status_t err = B_OK;
1138
1139	if(ioProposedFormat.type != B_MEDIA_RAW_AUDIO) {
1140		// out of the ballpark
1141		ioProposedFormat = preferredFormat;
1142		return B_MEDIA_BAD_FORMAT;
1143	}
1144
1145	// wildcard format
1146	const media_raw_audio_format& wild = media_raw_audio_format::wildcard;
1147	// proposed format
1148	media_raw_audio_format& f = ioProposedFormat.u.raw_audio;
1149	// template format
1150	const media_raw_audio_format& pref = preferredFormat.u.raw_audio;
1151
1152	if(pref.frame_rate != wild.frame_rate) {
1153		if(f.frame_rate != pref.frame_rate) {
1154			if(f.frame_rate != wild.frame_rate)
1155				err = B_MEDIA_BAD_FORMAT;
1156			f.frame_rate = pref.frame_rate;
1157		}
1158	}
1159
1160	if(pref.channel_count != wild.channel_count) {
1161		if(f.channel_count != pref.channel_count) {
1162			if(f.channel_count != wild.channel_count)
1163				err = B_MEDIA_BAD_FORMAT;
1164			f.channel_count = pref.channel_count;
1165		}
1166	}
1167
1168	if(pref.format != wild.format) {
1169		if(f.format != pref.format) {
1170			if(f.format != wild.format)
1171				err = B_MEDIA_BAD_FORMAT;
1172			f.format = pref.format;
1173		}
1174	}
1175
1176	if(pref.byte_order != wild.byte_order) {
1177		if(f.byte_order != pref.byte_order) {
1178			if(f.byte_order != wild.byte_order)
1179				err = B_MEDIA_BAD_FORMAT;
1180			f.byte_order = pref.byte_order;
1181		}
1182	}
1183
1184	if(pref.buffer_size != wild.buffer_size) {
1185		if(f.buffer_size != pref.buffer_size) {
1186			if(f.buffer_size != wild.buffer_size)
1187				err = B_MEDIA_BAD_FORMAT;
1188			f.buffer_size = pref.buffer_size;
1189		}
1190	}
1191
1192	if(err != B_OK) {
1193		string_for_format(ioProposedFormat, formatStr, 255);
1194		PRINT((
1195			"\tformat conflict; suggesting:\n\tformat %s\n", formatStr));
1196	}
1197
1198	return err;
1199}
1200
1201// fill in wildcards in the given format.
1202// (assumes the format passes validateProposedFormat().)
1203void FlangerNode::specializeOutputFormat(
1204	media_format& ioFormat) {
1205
1206	char formatStr[256];
1207	string_for_format(ioFormat, formatStr, 255);
1208	PRINT(("FlangerNode::specializeOutputFormat()\n"
1209		"\tinput format: %s\n", formatStr));
1210
1211	ASSERT(ioFormat.type == B_MEDIA_RAW_AUDIO);
1212
1213	// carpal_tunnel_paranoia
1214	media_raw_audio_format& f = ioFormat.u.raw_audio;
1215	const media_raw_audio_format& w = media_raw_audio_format::wildcard;
1216
1217	if (f.frame_rate == w.frame_rate)
1218		f.frame_rate = 44100.0;
1219	if (f.channel_count == w.channel_count) {
1220		//+++++ tweaked 15sep99
1221		if (m_input.source != media_source::null)
1222			f.channel_count = m_input.format.u.raw_audio.channel_count;
1223		else
1224			f.channel_count = 1;
1225	}
1226	if (f.format == w.format)
1227		f.format = media_raw_audio_format::B_AUDIO_FLOAT;
1228	if (f.byte_order == w.byte_order)
1229		f.byte_order = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN;
1230	if (f.buffer_size == w.buffer_size)
1231		f.buffer_size = 2048;
1232
1233	string_for_format(ioFormat, formatStr, 255);
1234	PRINT(("\toutput format: %s\n", formatStr));
1235}
1236
1237// set parameters to their default settings
1238void FlangerNode::initParameterValues() {
1239	m_fMixRatio = 0.5;
1240	m_tpMixRatioChanged = 0LL;
1241
1242	m_fSweepRate = 0.1;
1243	m_tpSweepRateChanged = 0LL;
1244
1245	m_fDelay = 10.0;
1246	m_tpDelayChanged = 0LL;
1247
1248	m_fDepth = 25.0;
1249	m_tpDepthChanged = 0LL;
1250
1251	m_fFeedback = 0.1;
1252	m_tpFeedbackChanged = 0LL;
1253}
1254
1255// create and register a parameter web
1256void FlangerNode::initParameterWeb() {
1257	BParameterWeb* pWeb = new BParameterWeb();
1258	BParameterGroup* pTopGroup = pWeb->MakeGroup(
1259		B_TRANSLATE("FlangerNode parameters"));
1260
1261	BNullParameter* label;
1262	BContinuousParameter* value;
1263	BParameterGroup* g;
1264
1265	// mix ratio
1266	g = pTopGroup->MakeGroup(B_TRANSLATE("Mix ratio"));
1267	label = g->MakeNullParameter(
1268		P_MIX_RATIO_LABEL,
1269		B_MEDIA_NO_TYPE,
1270		B_TRANSLATE("Mix ratio"),
1271		B_GENERIC);
1272
1273	value = g->MakeContinuousParameter(
1274		P_MIX_RATIO,
1275		B_MEDIA_NO_TYPE,
1276		"",
1277		B_GAIN, "", 0.0, 1.0, 0.05);
1278	label->AddOutput(value);
1279	value->AddInput(label);
1280
1281	// sweep rate
1282	g = pTopGroup->MakeGroup(B_TRANSLATE("Sweep rate"));
1283	label = g->MakeNullParameter(
1284		P_SWEEP_RATE_LABEL,
1285		B_MEDIA_NO_TYPE,
1286		B_TRANSLATE("Sweep rate"),
1287		B_GENERIC);
1288
1289	value = g->MakeContinuousParameter(
1290		P_SWEEP_RATE,
1291		B_MEDIA_NO_TYPE,
1292		"",
1293		B_GAIN, "Hz", 0.01, 10.0, 0.01);
1294	label->AddOutput(value);
1295	value->AddInput(label);
1296
1297	// sweep range: minimum delay
1298	g = pTopGroup->MakeGroup(B_TRANSLATE("Delay"));
1299	label = g->MakeNullParameter(
1300		P_DELAY_LABEL,
1301		B_MEDIA_NO_TYPE,
1302		B_TRANSLATE("Delay"),
1303		B_GENERIC);
1304
1305	value = g->MakeContinuousParameter(
1306		P_DELAY,
1307		B_MEDIA_NO_TYPE,
1308		"",
1309		B_GAIN, "ms", 0.1, s_fMaxDelay/2.0, 0.1);
1310	label->AddOutput(value);
1311	value->AddInput(label);
1312
1313	// sweep range: maximum
1314	g = pTopGroup->MakeGroup(B_TRANSLATE("Depth"));
1315	label = g->MakeNullParameter(
1316		P_DEPTH_LABEL,
1317		B_MEDIA_NO_TYPE,
1318		B_TRANSLATE("Depth"),
1319		B_GENERIC);
1320
1321	value = g->MakeContinuousParameter(
1322		P_DEPTH,
1323		B_MEDIA_NO_TYPE,
1324		"",
1325		B_GAIN, "ms", 1.0, s_fMaxDelay/4.0, 0.1);
1326	label->AddOutput(value);
1327	value->AddInput(label);
1328
1329	// feedback
1330	g = pTopGroup->MakeGroup(B_TRANSLATE("Feedback"));
1331	label = g->MakeNullParameter(
1332		P_FEEDBACK_LABEL,
1333		B_MEDIA_NO_TYPE,
1334		B_TRANSLATE("Feedback"),
1335		B_GENERIC);
1336
1337	value = g->MakeContinuousParameter(
1338		P_FEEDBACK,
1339		B_MEDIA_NO_TYPE,
1340		"",
1341		B_GAIN, "", 0.0, 1.0, 0.01);
1342	label->AddOutput(value);
1343	value->AddInput(label);
1344
1345	// * Install parameter web
1346	SetParameterWeb(pWeb);
1347}
1348
1349// construct delay line if necessary, reset filter state
1350void FlangerNode::initFilter() {
1351	PRINT(("FlangerNode::initFilter()\n"));
1352	ASSERT(m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format);
1353
1354	if(!m_pDelayBuffer) {
1355		m_pDelayBuffer = new AudioBuffer(
1356			m_format.u.raw_audio,
1357			frames_for_duration(
1358				m_format.u.raw_audio,
1359				(bigtime_t)s_fMaxDelay*1000LL));
1360		m_pDelayBuffer->zero();
1361	}
1362
1363	m_framesSent = 0;
1364	m_delayWriteFrame = 0;
1365	m_fTheta = 0.0;
1366	m_fThetaInc = calc_sweep_delta(m_format.u.raw_audio, m_fSweepRate);
1367	m_fSweepBase = calc_sweep_base(m_format.u.raw_audio, m_fDelay, m_fDepth);
1368	m_fSweepFactor = calc_sweep_factor(m_format.u.raw_audio, m_fDepth);
1369
1370//
1371//	PRINT((
1372//		"\tFrames       %ld\n"
1373//		"\tDelay        %.2f\n"
1374//		"\tDepth        %.2f\n"
1375//		"\tSweepBase    %.2f\n"
1376//		"\tSweepFactor  %.2f\n",
1377//		m_pDelayBuffer->frames(),
1378//		m_fDelay, m_fDepth, m_fSweepBase, m_fSweepFactor));
1379}
1380
1381void FlangerNode::startFilter() {
1382	PRINT(("FlangerNode::startFilter()\n"));
1383}
1384void FlangerNode::stopFilter() {
1385	PRINT(("FlangerNode::stopFilter()\n"));
1386}
1387
1388// figure processing latency by doing 'dry runs' of filterBuffer()
1389bigtime_t FlangerNode::calcProcessingLatency() {
1390	PRINT(("FlangerNode::calcProcessingLatency()\n"));
1391
1392	if(m_output.destination == media_destination::null) {
1393		PRINT(("\tNot connected.\n"));
1394		return 0LL;
1395	}
1396
1397	// allocate a temporary buffer group
1398	BBufferGroup* pTestGroup = new BBufferGroup(
1399		m_output.format.u.raw_audio.buffer_size, 1);
1400
1401	// fetch a buffer
1402	BBuffer* pBuffer = pTestGroup->RequestBuffer(
1403		m_output.format.u.raw_audio.buffer_size);
1404	ASSERT(pBuffer);
1405
1406	pBuffer->Header()->type = B_MEDIA_RAW_AUDIO;
1407	pBuffer->Header()->size_used = m_output.format.u.raw_audio.buffer_size;
1408
1409	// run the test
1410	bigtime_t preTest = system_time();
1411	filterBuffer(pBuffer);
1412	bigtime_t elapsed = system_time()-preTest;
1413
1414	// clean up
1415	pBuffer->Recycle();
1416	delete pTestGroup;
1417
1418	// reset filter state
1419	initFilter();
1420
1421	return elapsed;
1422}
1423
1424// filter buffer data in place
1425//
1426// +++++ add 2-channel support 15sep991
1427//
1428
1429const size_t MAX_CHANNELS = 2;
1430
1431struct _frame {
1432	float channel[MAX_CHANNELS];
1433};
1434
1435void FlangerNode::filterBuffer(
1436	BBuffer* pBuffer) {
1437
1438	if(!m_pDelayBuffer)
1439		return;
1440
1441	// for each input frame:
1442	// - fetch
1443	// - write delay line(writeFrame)
1444	// - read delay line(writeFrame-readOffset)  [interpolate]
1445	// - mix (replace)
1446	// - advance writeFrame
1447	// - update readOffset
1448
1449	AudioBuffer input(m_format.u.raw_audio, pBuffer);
1450
1451	ASSERT(
1452		m_format.u.raw_audio.channel_count == 1 ||
1453		m_format.u.raw_audio.channel_count == 2);
1454	uint32 channels = m_format.u.raw_audio.channel_count;
1455	bool stereo = m_format.u.raw_audio.channel_count == 2;
1456
1457	uint32 samples = input.frames() * channels;
1458	for(uint32 inPos = 0; inPos < samples; ++inPos) {
1459
1460		// read from input buffer
1461		_frame inFrame = {};
1462		inFrame.channel[0] = ((float*)input.data())[inPos];
1463		if(stereo)
1464			inFrame.channel[1] = ((float*)input.data())[inPos + 1];
1465
1466		// interpolate from delay buffer
1467		float readOffset = m_fSweepBase + (m_fSweepFactor * sin(m_fTheta));
1468		float fReadFrame = (float)m_delayWriteFrame - readOffset;
1469		if(fReadFrame < 0.0)
1470			fReadFrame += m_pDelayBuffer->frames();
1471
1472//		float delayed;
1473
1474
1475		// read low-index (possibly only) frame
1476		_frame delayedFrame = {};
1477
1478		int32 readFrameLo = (int32)floor(fReadFrame);
1479		uint32 pos = readFrameLo * channels;
1480		delayedFrame.channel[0] = ((float*)m_pDelayBuffer->data())[pos];
1481		if(stereo)
1482			delayedFrame.channel[1] = ((float*)m_pDelayBuffer->data())[pos+1];
1483
1484		if(readFrameLo != (int32)fReadFrame) {
1485
1486			// interpolate (A)
1487			uint32 readFrameHi = (int32)ceil(fReadFrame);
1488			delayedFrame.channel[0] *= ((float)readFrameHi - fReadFrame);
1489			if(stereo)
1490				delayedFrame.channel[1] *= ((float)readFrameHi - fReadFrame);
1491
1492			// read high-index frame
1493			int32 hiWrap = (readFrameHi == m_pDelayBuffer->frames()) ? 0 : readFrameHi;
1494			ASSERT(hiWrap >= 0);
1495			pos = (uint32)hiWrap * channels;
1496			_frame hiFrame;
1497			hiFrame.channel[0] = ((float*)m_pDelayBuffer->data())[pos];
1498			if(stereo)
1499				hiFrame.channel[1] = ((float*)m_pDelayBuffer->data())[pos+1];
1500
1501			// interpolate (B)
1502			delayedFrame.channel[0] +=
1503				hiFrame.channel[0] * (fReadFrame - (float)readFrameLo);
1504			if(stereo)
1505				delayedFrame.channel[1] +=
1506					hiFrame.channel[1] * (fReadFrame - (float)readFrameLo);
1507		}
1508
1509		// mix back to output buffer
1510		((float*)input.data())[inPos] =
1511			(inFrame.channel[0] * (1.0-m_fMixRatio)) +
1512			(delayedFrame.channel[0] * m_fMixRatio);
1513		if(stereo)
1514			((float*)input.data())[inPos+1] =
1515				(inFrame.channel[1] * (1.0-m_fMixRatio)) +
1516				(delayedFrame.channel[1] * m_fMixRatio);
1517
1518		// write to delay buffer
1519		uint32 delayWritePos = m_delayWriteFrame * channels;
1520		((float*)m_pDelayBuffer->data())[delayWritePos] =
1521			inFrame.channel[0] +
1522			(delayedFrame.channel[0] * m_fFeedback);
1523		if(stereo)
1524			((float*)m_pDelayBuffer->data())[delayWritePos+1] =
1525				inFrame.channel[1] +
1526				(delayedFrame.channel[1] * m_fFeedback);
1527
1528		// advance write position
1529		if(++m_delayWriteFrame >= m_pDelayBuffer->frames())
1530			m_delayWriteFrame = 0;
1531
1532		// advance read offset ('LFO')
1533		m_fTheta += m_fThetaInc;
1534		if(m_fTheta > 2 * M_PI)
1535			m_fTheta -= 2 * M_PI;
1536
1537//		if(m_fDelayReadDelta < 0.0) {
1538//			if(m_fDelayReadOffset < m_fDelay)
1539//				m_fDelayReadDelta = -m_fDelayReadDelta;
1540//		} else {
1541//			if(m_fDelayReadOffset > m_fDepth)
1542//				m_fDelayReadDelta = -m_fDelayReadDelta;
1543//		}
1544//		m_fDelayReadOffset += m_fDelayReadDelta;
1545	}
1546}
1547
1548
1549/*!	Figure the rate at which the (radial) read offset changes,
1550	based on the given sweep rate (in Hz)
1551*/
1552float
1553calc_sweep_delta(const media_raw_audio_format& format, float fRate)
1554{
1555	return 2 * M_PI * fRate / format.frame_rate;
1556}
1557
1558/*!	Figure the base delay (in frames) based on the given
1559	sweep delay/depth (in msec)
1560*/
1561float
1562calc_sweep_base(const media_raw_audio_format& format, float delay, float depth)
1563{
1564	return (format.frame_rate * (delay + depth)) / 1000.0;
1565}
1566
1567
1568float
1569calc_sweep_factor(const media_raw_audio_format& format, float depth)
1570{
1571	return (format.frame_rate * depth) / 1000.0;
1572}
1573
1574
1575// END -- FlangerNode.cpp --
1576