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