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