1 /* === S Y N F I G ========================================================= */
2 /*!	\file audiocontainer.cpp
3 **	\brief Audio Container implementation File
4 **
5 **	$Id$
6 **
7 **	\legal
8 **	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **
10 **	This package is free software; you can redistribute it and/or
11 **	modify it under the terms of the GNU General Public License as
12 **	published by the Free Software Foundation; either version 2 of
13 **	the License, or (at your option) any later version.
14 **
15 **	This package is distributed in the hope that it will be useful,
16 **	but WITHOUT ANY WARRANTY; without even the implied warranty of
17 **	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 **	General Public License for more details.
19 **	\endlegal
20 */
21 /* ========================================================================= */
22 
23 /* === H E A D E R S ======================================================= */
24 
25 #ifdef USING_PCH
26 #	include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 #	include <config.h>
30 #endif
31 
32 #include <algorithm>
33 #include <sigc++/sigc++.h>
34 
35 #include <ETL/stringf>
36 #include <ETL/clock>
37 //#include <ETL/thread>
38 #include <glibmm/thread.h>
39 
40 #include <synfig/general.h>
41 
42 #include <glibmm/main.h>
43 
44 #include "audiocontainer.h"
45 
46 #include <cstdio>
47 #include <sys/stat.h>
48 #include <errno.h>
49 
50 #include <set>
51 #include <vector>
52 
53 #ifdef WITH_FMOD
54 #include <fmod.h>
55 #endif
56 
57 #include <gui/localization.h>
58 
59 #endif
60 
61 /* === U S I N G =========================================================== */
62 
63 using namespace std;
64 using namespace etl;
65 using namespace synfig;
66 
67 /* === M A C R O S ========================================================= */
68 #ifdef __WIN32
69 #else //linux...
70 #define AUDIO_OUTPUT	FSOUND_OUTPUT_OSS
71 #endif
72 
73 /* === G L O B A L S ======================================================= */
74 //const double delay_factor = 3;
75 //Warning: Unused variable delay_factor
76 /* === P R O C E D U R E S ================================================= */
77 
78 /* === M E T H O D S ======================================================= */
79 
80 /* === E N T R Y P O I N T ================================================= */
81 
82 //Help constructing stuff
83 struct FSOUND_SAMPLE;
84 using studio::AudioContainer;
85 
86 #ifdef WITH_FMOD
build_profile(FSOUND_SAMPLE * sample,double & samplerate,std::vector<char> & samples)87 bool build_profile(FSOUND_SAMPLE *sample, double &samplerate, std::vector<char> &samples)
88 #else
89 bool build_profile(FSOUND_SAMPLE */*sample*/, double &/*samplerate*/, std::vector<char> &/*samples*/)
90 #endif
91 {
92 #ifdef WITH_FMOD
93 
94 	float sps = samplerate;
95 
96 	//trivial rejection...
97 	if(!sample || sps < 1)
98 	{
99 		synfig::warning("build_profile: Sample rate was too low or sample was invalid");
100 		return false;
101 	}
102 
103 	//lock for all samples and process them into a subset
104 	unsigned int mode = FSOUND_Sample_GetMode(sample);
105 
106 	//make sure that it's 8 bit... I hope this works...
107 
108 	//sample rate of the actual song...
109 	int allsamplerate = 0;
110 	FSOUND_Sample_GetDefaults(sample,&allsamplerate,0,0,0);
111 
112 	//get the size of the sample defaults from the mode
113 	int channels = 1;
114 	int channelsize = 1; //number of bytes
115 
116 	if(mode & FSOUND_16BITS) channelsize = 2; //this shouldn't happen
117 	if(mode & FSOUND_STEREO) channels = 2;
118 
119 	//Get the sample information
120 	int samplesize = channels*channelsize; //the only two things that increase samplesize
121 	int numsamples = FSOUND_Sample_GetLength(sample); //number of samples in the sound
122 	int sizeall = samplesize*numsamples; //should be the size of the entire song...
123 
124 	if(sizeall <= 0)
125 	{
126 		synfig::warning("ProfileAudio: Sample buffer cannot be size smaller than 1 (%X)",FSOUND_GetError());
127 		return false;
128 	}
129 
130 	//be sure that the new sample rate is less than or equal to the original
131 	if(sps > allsamplerate) sps = allsamplerate;
132 
133 	float stride = allsamplerate/(float)sps;
134 
135 	//down sampling to 8 bit min/max values
136 	synfig::warning("About to downsample from %d Hz to %.1f Hz, sample stride: %f", allsamplerate, sps, stride);
137 
138 	char *sampledata=0,*useless = 0;
139 	unsigned int len1,len2;
140 	// vector<char>	samples;
141 	{
142 		if(!FSOUND_Sample_Lock(sample,0,sizeall,(void**)&sampledata,(void**)&useless,&len1,&len2))
143 		{
144 			synfig::warning("ProfileAudio: Unable to lock the sound buffer... (%X)",FSOUND_GetError());
145 			return false;
146 		}
147 		synfig::warning("Locked: %X: %d bytes, %X: %d bytes",sampledata,len1,useless,len2);
148 
149 		if(channelsize == 1)
150 		{
151 			//process the data
152 			char *iter = sampledata;
153 			char *end = iter + sizeall;
154 
155 			float curaccum = 0;
156 			float numinc = sps/(float)allsamplerate;
157 
158 			/* Loop per sample DDA alg.
159 			*/
160 
161 			int i = 0;
162 
163 			//HACK - to prevent if statement inside inner loop
164 			//synfig::warning("wo baby wo baby, inc: %d, stride: %f, size: %d", inc, stride, sizeall);
165 			while(iter < end)
166 			{
167 				int maxs = 0, mins = 0;
168 
169 				for(;curaccum < 1; curaccum += numinc)
170 				{
171 					for(i = 0; iter < end && i < channels; ++i, iter += channelsize)
172 					{
173 						maxs = std::max(maxs,(int)*iter);
174 						mins = std::min(mins,(int)*iter);
175 					}
176 				}
177 				//insert onto new list
178 				samples.push_back(maxs);
179 				samples.push_back(mins);
180 
181 				//and flush all the used samples for curaccum
182 				curaccum -= 1;
183 			}
184 		}else if(channelsize == 2)
185 		{
186 			//process the data
187 			char *iter = sampledata;
188 			char *end = iter + sizeall;
189 
190 			float curaccum = 0;
191 			float numinc = sps/(float)allsamplerate;
192 
193 			/* Loop per sample DDA alg.
194 			*/
195 
196 			int i = 0;
197 
198 			//HACK - to prevent if statement inside inner loop
199 			//synfig::warning("wo baby wo baby, inc: %d, stride: %f, size: %d", inc, stride, sizeall);
200 			while(iter < end)
201 			{
202 				int maxs = 0, mins = 0;
203 
204 				for(;curaccum < 1; curaccum += numinc)
205 				{
206 					for(i = 0; iter < end && i < channels; ++i, iter += channelsize)
207 					{
208 						maxs = std::max(maxs,(int)*(short*)iter);
209 						mins = std::min(mins,(int)*(short*)iter);
210 					}
211 				}
212 				//insert onto new list
213 				samples.push_back(maxs / 256);
214 				samples.push_back(mins / 256);
215 
216 				//and flush all the used samples for curaccum
217 				curaccum -= 1;
218 			}
219 		}
220 	}
221 
222 	synfig::warning("Stats: %f seconds with %d bytes now %d bytes", (samples.size()/2)/sps, sizeall, samples.size());
223 	synfig::warning("		%f seconds before", numsamples/(float)allsamplerate);
224 
225 	//we're done yay!, unlock
226 	FSOUND_Sample_Unlock(sample,sampledata,useless,len1,len2);
227 	synfig::info("Unlocked");
228 
229 	//FSOUND_PlaySound(FSOUND_FREE,sound); //test
230 
231 	//we're done
232 	samplerate = sps*2; //it must be x2 because we are sampling max and min
233 
234 	return true;
235 
236 	#else
237 
238 	return false;
239 
240 	#endif
241 }
242 
243 
244 //FMOD Systemwide Specific data mostly here...
245 
246 struct scrubinfo;
247 
248 #ifdef WITH_FMOD
249 static double	buffer_length_sec = 0;
250 
251 //------- Scrubbing --------------
252 /* Scrubbing works as follows:
253 
254 	The sound is played using PlaySoundEx
255 		we specify a user created DSP for scrubbing
256 		set it initially to inactive
257 
258 	When the program initiates it
259 		we set the initial data in the shared structure and activate the dsp unit
260 		then for each cursor update we get we set the value in the shared structure
261 */
262 
263 /* Things to check:
264 	If IsPlaying just governs the channel play/stop value or if it also concerns the pause state
265 
266 */
267 
268 //so we can know where to create all this stuff
269 struct scrubinfo
270 {
271 	/*	Linearly fit the frequency to hit the desired zero point...
272 	*/
273 	/*struct scrubelement
274 	{
275 		double	pos;
276 		double	dt;
277 		//the amount of time left til the cursor hits this one
278 		//	it's incremental so that the cursor must pass previous
279 		//	ones before decrementing this value
280 	};
281 	*/
282 
283 	//the time it should take to get to the next position...
284 
285 	//to prevent from writing to the same location at once... (pos, deltatime, delaystart)
286 	//Glib::Mutex	lock;
287 
288 	//the queue system would provide a more accurate representation...
289 	volatile double pos;
290 	volatile double deltatime;
291 
292 	volatile double delaystart; //the amount of time we need to go before we start interpolating...
293 
294 	volatile int	channel;
295 
296 	/*std::list<scrubelement>	queue;
297 
298 	volatile int	channel;
299 
300 	//current position is FSOUND_GetCurrentPosition and current time is always 0...
301 
302 	void add(const scrubelement &elem)
303 	{
304 		lock.LockWrite();
305 
306 		queue.push_back(elem);
307 
308 		lock.UnlockWrite();
309 	}
310 
311 	//Function to safely get rid of all the old samples (dt < 0)
312 	void flush()
313 	{
314 		lock.LockWrite();
315 
316 		while(queue.size() && queue.front().dt < 0)
317 		{
318 			queue.pop_front();
319 		}
320 
321 		lock.UnlockWrite();
322 	}*/
323 
Lockscrubinfo324 	void Lock()
325 	{
326 		//lock.lock();
327 	}
328 
Unlockscrubinfo329 	void Unlock()
330 	{
331 		//lock.unlock();
332 	}
333 
334 	//All parameters and state should be set by the time we get here...
scrub_dsp_processscrubinfo335 	void scrub_dsp_process()
336 	{
337 		const double epsilon = 1e-5;
338 
339 		//Trivial reject... we go nowhere if we aren't playing (hit boundary...)
340 		if(!FSOUND_IsPlaying(channel)) return;
341 
342 		//Get rid of all the old samples
343 		//flush();
344 
345 		//Trivial reject #2 - We also go nowhere with no future samples (pause)
346 		/*if(queue.size() <= 0)
347 		{
348 			FSOUND_SetPaused(channel,true);
349 			return;
350 		}*/
351 
352 		double dt = buffer_length_sec;
353 
354 		//Lock ourselves so we don't die
355 		Lock();
356 
357 		//printf("DSP data: delay = %.3f s, pos = %d, dt = %.3f\n", delaystart, (int)pos, deltatime);
358 
359 		//Check delay
360 		if(delaystart > 0)
361 		{
362 			delaystart -= dt;
363 
364 			if(delaystart < 0)
365 			{
366 				dt = -delaystart; //add time back...
367 				delaystart = 0;
368 			}
369 		}
370 
371 		//Trivial reject for if we're past current sample...
372 		if(delaystart > 0 || deltatime <= 0)
373 		{
374 			FSOUND_SetPaused(channel,true);
375 			Unlock();
376 			return;
377 		}
378 
379 		//Calculate stretched frequency based on delayed future sample...
380 
381 		//NOTE: BY NOT TRACKING POSITION AS A FLOAT AND JUST USING THE SOUNDS VALUE
382 		//		WE ARE LOSING A TINY AMOUNT OF PRECISION ACCURACY EVERY UPDATE
383 		//		(THIS SHOULDN'T BE A PROBLEM)
384 		const double p0 = FSOUND_GetCurrentPosition(channel);
385 		double curdp = 0;
386 
387 		if(!FSOUND_GetPaused(channel))
388 		{
389 			curdp = FSOUND_GetFrequency(channel) * deltatime;
390 		}
391 
392 		//need to rescale derivative...
393 
394 		//Extrapolate from difference in position and deltatime vs dt...
395 		const double pa = p0 + curdp/2;
396 
397 		const double p1 = pos;
398 
399 		//const double pb = p0/3 + p1*2/3;
400 
401 		//will extrapolate if needed... (could be funky on a curve)
402 		double t = 0;
403 		if(deltatime > epsilon)
404 		{
405 			t = dt / deltatime;
406 		}
407 
408 		//Decrement deltatime (we may have gone past but that's what happens when we don't get input...)
409 		deltatime -= dt;
410 
411 		//we don't need to look at the current variables anymore...
412 		Unlock();
413 
414 		const double invt = 1-t;
415 		//double deltapos = (p1-p0)*t; //linear version
416 		double deltapos = invt*invt*p0 + 2*t*invt*pa + t*t*p1 - p0; //quadratic smoothing version
417 
418 		//Attempted cubic smoothing
419 		//const double invt2 = invt*invt;
420 		//const double t2 = t*t;
421 		//double deltapos = invt2*invt*p0 + 3*t*invt2*pa + 3*t2*invt*pb + t2*t*p1;
422 		//double deltapos = p0 + t*(3*(pa-p0) + t*(3*(p0+2*pa+pb) + t*((p1-3*pb+3*ba-p0)))); //unwound cubic
423 
424 		//printf("\ttime = %.2f; p(%d,%d,%d) dp:%d - delta = %d\n",t,(int)p0,(int)p1,(int)p2,(int)curdp,(int)deltapos);
425 
426 		//Based on the delta info calculate the stretched frequency
427 		const int dest_samplesize = FSOUND_DSP_GetBufferLength();
428 
429 		//rounded to nearest frequency... (hopefully...)
430 		int freq = (int)(deltapos * FSOUND_GetOutputRate() / (double)dest_samplesize);
431 
432 		//NOTE: WE MIGHT WANT TO DO THIS TO BE MORE ACCURATE BUT YEAH... ISSUES WITH SMALL NUMBERS
433 		//double newdp = deltapos / t;
434 
435 		//printf("\tfreq = %d Hz\n", freq);
436 
437 		// !If I failed... um assume we have to pause it... ?
438 		if(abs(freq) < 100)
439 		{
440 			FSOUND_SetPaused(channel,true);
441 		}else
442 		{
443 			//synfig::info("DSP f = %d Hz", freq);
444 			FSOUND_SetPaused(channel,false);
445 			if(!FSOUND_SetFrequency(channel,freq))
446 			{
447 				//ERROR WILL ROBINSON!!!...
448 				printf("Error in Freq... what do I do?\n");
449 			}
450 		}
451 	}
452 };
453 
454 struct scrubuserdata
455 {
456 	/* //for use with multiple
457 	//each one is a 'handle' to a pointer that will be effected by something else
458 	typedef scrubinfo**	value_type;
459 	typedef std::set< value_type > scrubslist;
460 	scrubslist		scrubs;
461 
462 	//so we can lock access to the list...
463 	ReadWriteLock	lock;
464 
465 	void AddScrub(scrubinfo **i)
466 	{
467 		lock.LockWrite();
468 		scrubs.insert(i);
469 		lock.UnLockWrite();
470 	}
471 
472 	void RemoveScrub(scrubinfo **i)
473 	{
474 		lock.LockWrite();
475 		scrubs.erase(i);
476 		lock.UnLockWrite();
477 	}*/
478 
479 	scrubinfo * volatile *	scrub;
480 };
481 
482 //Scrubbing data structures
483 static const int 		default_scrub_priority = 5; //between clear and sfx/music mix
484 static scrubuserdata	g_scrubdata = {0};
485 static FSOUND_DSPUNIT	*scrubdspunit = 0;
486 
scrubdspwrap(void * originalbuffer,void * newbuffer,int length,void * userdata)487 void * scrubdspwrap(void *originalbuffer, void *newbuffer, int length, void *userdata)
488 {
489 	//std::string	dsp = "DSP";
490 	if(userdata)
491 	{
492 		scrubuserdata &sd = *(scrubuserdata*)userdata;
493 
494 		/* //For use with multiple scrubs...
495 		//Lock so no one can write to it while we're reading from it...
496 		sd.lock.LockRead();
497 
498 		//make a copy of it...
499 		std::vector<scrubinfo**>	v(sd.scrubs.begin(),sd.scrubs.end());
500 
501 		//other things can do stuff with it again...
502 		sd.lock.UnLockRead();
503 
504 		//loop through the list and process all the active scrub units
505 		std::vector<scrubinfo**>::iterator	i = v.begin(),
506 											end = v.end();
507 		for(;i != end; ++i)
508 		{
509 			//check to make sure this object is active...
510 			if(*i && **i)
511 			{
512 				(**i)->scrub_dsp_process();
513 			}
514 		}
515 		*/
516 
517 		if(sd.scrub && *sd.scrub)
518 		{
519 			//dsp += " processing...";
520 			scrubinfo * info = (*sd.scrub);
521 			info->scrub_dsp_process();
522 		}
523 	}
524 
525 	//synfig::info(dsp);
526 
527 	return newbuffer;
528 }
529 
530 //------- Class for loading fmod on demand -------
531 
532 class FMODInitializer
533 {
534 	bool loaded;
535 	int	refcount;
536 
537 public:
FMODInitializer()538 	FMODInitializer():loaded(false),refcount(0) {}
~FMODInitializer()539 	~FMODInitializer()
540 	{
541 		clear();
542 	}
543 
addref()544 	void addref()
545 	{
546 		if(!loaded)
547 		{
548 			#ifdef WITH_FMOD
549 			synfig::info("Initializing FMOD on demand...");
550 
551 			{
552 				FSOUND_SetOutput(AUDIO_OUTPUT);
553 
554 				/*int numdrivers = FSOUND_GetNumDrivers();
555 				synfig::info("Num FMOD drivers = %d",numdrivers);
556 				synfig::info("Current Driver is #%d", FSOUND_GetDriver());
557 
558 				for(int i = 0; i < numdrivers; ++i)
559 				{
560 					unsigned int caps = 0;
561 					FSOUND_GetDriverCaps(i,&caps);
562 
563 					synfig::info("   Caps for driver %d (%s) = %x",i,FSOUND_GetDriverName(i),caps);
564 				}
565 
566 				FSOUND_SetDriver(0);*/
567 
568 				//Modify buffer size...
569 				//FSOUND_SetBufferSize(100);
570 
571 				if(!FSOUND_Init(44100, 32, 0))
572 				{
573 					synfig::warning("Unable to load FMOD");
574 				}else
575 				{
576 					loaded = true;
577 
578 					//Create the DSP for processing scrubbing...
579 					scrubdspunit = FSOUND_DSP_Create(&scrubdspwrap,default_scrub_priority,&g_scrubdata);
580 
581 					//Load the number of sec per buffer into the global variable...
582 					buffer_length_sec = FSOUND_DSP_GetBufferLength() / (double)FSOUND_GetOutputRate();
583 				}
584 			}
585 			#endif
586 		}
587 
588 		//add to the refcount
589 		++refcount;
590 		//synfig::info("Audio: increment fmod refcount %d", refcount);
591 	}
592 
decref()593 	void decref()
594 	{
595 		if(refcount <= 0)
596 		{
597 			synfig::warning("FMOD refcount is already 0...");
598 		}else
599 		{
600 			--refcount;
601 			//synfig::info("Audio: decrement fmod refcount %d", refcount);
602 
603 			//NOTE: UNCOMMENT THIS IF YOU WANT FMOD TO UNLOAD ITSELF WHEN IT ISN'T NEEDED ANYMORE...
604 			flush();
605 		}
606 	}
607 
is_loaded() const608 	bool is_loaded() const { return loaded; }
609 
clear()610 	void clear()
611 	{
612 		refcount = 0;
613 		flush();
614 	}
615 
flush()616 	void flush()
617 	{
618 		if(loaded && refcount <= 0)
619 		{
620 			#ifdef WITH_FMOD
621 			synfig::info("Unloading FMOD");
622 			if(scrubdspunit) FSOUND_DSP_Free(scrubdspunit);
623 			FSOUND_Close();
624 			#endif
625 			loaded = false;
626 		}
627 	}
628 };
629 
630 //The global counter for FMOD....
631 FMODInitializer		fmodinit;
632 
633 #endif
634 
635 //----- AudioProfile Implementation -----------
clear()636 void studio::AudioProfile::clear()
637 {
638 	samplerate = 0;
639 	samples.clear();
640 }
641 
get_parent() const642 handle<AudioContainer>	studio::AudioProfile::get_parent() const
643 {
644 	return parent;
645 }
646 
set_parent(etl::handle<AudioContainer> i)647 void studio::AudioProfile::set_parent(etl::handle<AudioContainer> i)
648 {
649 	parent = i;
650 }
651 
get_offset() const652 double studio::AudioProfile::get_offset() const
653 {
654 	if(parent)
655 		return parent->get_offset();
656 	return 0;
657 }
658 
659 //---------- AudioContainer definitions ---------------------
660 
661 struct studio::AudioContainer::AudioImp
662 {
663 	//Sample load time information
664 	FSOUND_SAMPLE *		sample;
665 	int					channel;
666 	int					sfreq;
667 	int					length;
668 
669 	//Time information
670 	double				offset; //time offset for playing...
671 
672 	//We don't need it now that we've adopted the play(t) time schedule...
673 	//current time... and playing info....
674 	//float				seekpost;
675 	//bool				useseekval;
676 
677 	//Make sure to sever our delayed start if we are stopped prematurely
678 	sigc::connection	delaycon;
679 
680 	//Action information
681 	bool				playing;
682 	double				curscrubpos;
683 	etl::clock			timer;	//for getting the time diff between scrub input points
684 
685 	//Scrubbing information...
686 	//the current position of the sound will be sufficient for normal stuff...
687 	#ifdef WITH_FMOD
688 	scrubinfo			scrinfo;
689 	#endif
690 
691 	scrubinfo			*scrptr;
692 
is_scrubbingstudio::AudioContainer::AudioImp693 	bool is_scrubbing() const {return scrptr != 0;}
694 #ifdef WITH_FMOD
set_scrubbingstudio::AudioContainer::AudioImp695 	void set_scrubbing(bool s)
696 #else
697 	void set_scrubbing(bool /*s*/)
698 #endif
699 	{
700 		#ifdef WITH_FMOD
701 		if(s)
702 			scrptr = &scrinfo;
703 		else
704 		#endif
705 		scrptr = 0;
706 	}
707 
708 	//helper to make sure we are actually playing (and to get a new channel...)
init_playstudio::AudioContainer::AudioImp709 	bool init_play()
710 	{
711 		#ifdef WITH_FMOD
712 		if(!FSOUND_IsPlaying(channel))
713 		{
714 			if(sample)
715 			{
716 				//play sound paused etc.
717 				channel = FSOUND_PlaySoundEx(FSOUND_FREE,sample,0,true);
718 				if(channel < 0 || FSOUND_GetError() != FMOD_ERR_NONE)
719 				{
720 					synfig::warning("Could not play the sample...");
721 					return false;
722 				}
723 			}
724 		}else
725 		{
726 			FSOUND_SetPaused(channel,true);
727 			FSOUND_SetFrequency(channel,sfreq);
728 		}
729 		return true;
730 
731 		#else
732 
733 		return false;
734 
735 		#endif
736 	}
737 
738 public: //structors
AudioImpstudio::AudioContainer::AudioImp739 	AudioImp():
740 		sample(0),
741 		channel(0),
742 		sfreq(0),
743 		length(0),
744 		offset(0),
745 		playing(false),
746 		curscrubpos(),
747 		scrptr(0)
748 	{
749 		//reuse the channel...
750 		#ifdef WITH_FMOD
751 		channel = FSOUND_FREE;
752 		#endif
753 	}
754 
~AudioImpstudio::AudioContainer::AudioImp755 	~AudioImp()
756 	{
757 		clear();
758 	}
759 
760 public: //helper/accessor funcs
start_playing_nowstudio::AudioContainer::AudioImp761 	bool start_playing_now() //callback for timer...
762 	{
763 		#ifdef WITH_FMOD
764 		if(playing)
765 		{
766 			//Make sure the sound is playing and if it is un pause it...
767 			if(init_play())
768 				FSOUND_SetPaused(channel,false);
769 		}
770 		#endif
771 
772 		return false; //so the timer doesn't repeat itself
773 	}
774 
isRunningstudio::AudioContainer::AudioImp775 	bool isRunning()
776 	{
777 		#ifdef WITH_FMOD
778 		return FSOUND_IsPlaying(channel);
779 		#else
780 		return false;
781 		#endif
782 	}
783 
isPausedstudio::AudioContainer::AudioImp784 	bool isPaused()
785 	{
786 #ifdef WITH_FMOD
787 		return FSOUND_GetPaused(channel);
788 #else
789 		return false;
790 #endif
791 	}
792 
793 
794 public: //forward interface
795 
796 	//Accessors for the offset - in seconds
get_offsetstudio::AudioContainer::AudioImp797 	const double &get_offset() const {return offset;}
set_offsetstudio::AudioContainer::AudioImp798 	void set_offset(const double &d)
799 	{
800 		offset = d;
801 	}
802 
803 	//Will override the parameter timevalue if the sound is running, and not if it's not...
804 #ifdef WITH_FMOD
get_current_timestudio::AudioContainer::AudioImp805 	bool get_current_time(double &out)
806 #else
807 	bool get_current_time(double &/*out*/)
808 #endif
809 	{
810 		if(isRunning())
811 		{
812 			#ifdef WITH_FMOD
813 			unsigned int pos = FSOUND_GetCurrentPosition(channel);
814 
815 			//adjust back by 1 frame... HACK....
816 			//pos -= FSOUND_DSP_GetBufferLength();
817 
818 			//set the position
819 			out = pos/(double)sfreq + offset;
820 			#endif
821 
822 			return true;
823 		}
824 		return false;
825 	}
826 
827 	//Big implementation functions...
828 	bool load(const std::string &filename, const std::string &filedirectory);
829 	void clear();
830 
831 	//playing functions
832 	void play(double t);
833 	void stop();
834 
835 	//scrubbing functions
836 	void start_scrubbing(double t);
837 	void scrub(double t);
838 	void stop_scrubbing();
839 
scrub_timestudio::AudioContainer::AudioImp840 	double scrub_time()
841 	{
842 		return curscrubpos;
843 	}
844 };
845 
846 //--------------- Audio Container definitions --------------------------
AudioContainer()847 studio::AudioContainer::AudioContainer():
848 	imp(NULL),
849 	profilevalid()
850 { }
851 
~AudioContainer()852 studio::AudioContainer::~AudioContainer()
853 {
854 	if(imp) delete (imp);
855 }
856 
load(const std::string & filename,const std::string & filedirectory)857 bool studio::AudioContainer::load(const std::string &filename,const std::string &filedirectory)
858 {
859 	if(!imp)
860 	{
861 		imp = new AudioImp;
862 	}
863 
864 	profilevalid = false;
865 	return imp->load(filename,filedirectory);
866 }
867 
868 #ifdef WITH_FMOD
get_profile(float samplerate)869 handle<studio::AudioProfile> studio::AudioContainer::get_profile(float samplerate)
870 #else
871 handle<studio::AudioProfile> studio::AudioContainer::get_profile(float /*samplerate*/)
872 #endif
873 {
874 	#ifdef WITH_FMOD
875 
876 	//if we already have done our work, then we're good
877 	if(profilevalid && prof)
878 	{
879 		//synfig::info("Using already built profile");
880 		return prof;
881 	}
882 
883 	//synfig::info("Before profile");
884 	//make a new profile at current sample rate
885 
886 	//NOTE: We might want to reuse the structure already there...
887 	prof = new AudioProfile;
888 	prof->set_parent(this); //Our parent is THIS!!!
889 
890 	if(!prof)
891 	{
892 		synfig::warning("Couldn't allocate audioprofile...");
893 		return handle<studio::AudioProfile>();
894 	}
895 
896 	//setting the info for the sample rate
897 	//synfig::info("Setting info...");
898 
899 	synfig::info("Building Profile...");
900 	prof->samplerate = samplerate;
901 	if(build_profile(imp->sample,prof->samplerate,prof->samples))
902 	{
903 		synfig::info("	Success!");
904 		profilevalid = true;
905 		return prof;
906 	}else
907 	{
908 		return handle<studio::AudioProfile>();
909 	}
910 
911 	#else
912 
913 	return handle<studio::AudioProfile>();
914 
915 	#endif
916 }
917 
clear()918 void studio::AudioContainer::clear()
919 {
920 	if(imp)
921 	{
922 		delete imp;
923 		imp = 0;
924 	}
925 
926 	profilevalid = false;
927 }
928 
play(double t)929 void studio::AudioContainer::play(double t)
930 {
931 	if(imp) imp->play(t);
932 }
933 
stop()934 void studio::AudioContainer::stop()
935 {
936 	if(imp) imp->stop();
937 }
938 
get_current_time(double & out)939 bool studio::AudioContainer::get_current_time(double &out)
940 {
941 	if(imp) return imp->get_current_time(out);
942 	else return false;
943 }
944 
set_offset(const double & s)945 void AudioContainer::set_offset(const double &s)
946 {
947 	if(imp) imp->set_offset(s);
948 }
949 
get_offset() const950 double AudioContainer::get_offset() const
951 {
952 	static double zero = 0;
953 	if(imp)
954 		return imp->get_offset();
955 	return zero;
956 }
957 
is_playing() const958 bool AudioContainer::is_playing() const
959 {
960 	if(imp)
961 		return imp->playing;
962 	return false;
963 }
964 
is_scrubbing() const965 bool AudioContainer::is_scrubbing() const
966 {
967 	if(imp)
968 		return imp->is_scrubbing();
969 	return false;
970 }
971 
start_scrubbing(double t)972 void AudioContainer::start_scrubbing(double t)
973 {
974 	if(imp) imp->start_scrubbing(t);
975 }
976 
stop_scrubbing()977 void AudioContainer::stop_scrubbing()
978 {
979 	if(imp) imp->stop_scrubbing();
980 }
981 
scrub(double t)982 void AudioContainer::scrub(double t)
983 {
984 	if(imp) imp->scrub(t);
985 }
986 
scrub_time() const987 double AudioContainer::scrub_time() const
988 {
989 	if(imp) return imp->scrub_time();
990 	else return 0;
991 }
992 
isRunning() const993 bool AudioContainer::isRunning() const
994 {
995 	if(imp) return imp->isRunning();
996 	else return false;
997 }
998 
isPaused() const999 bool AudioContainer::isPaused() const
1000 {
1001 	if(imp) return imp->isPaused();
1002 	else return false;
1003 }
1004 
1005 //----------- Audio imp information -------------------
1006 
1007 #ifdef WITH_FMOD
load(const std::string & filename,const std::string & filedirectory)1008 bool studio::AudioContainer::AudioImp::load(const std::string &filename,
1009 											const std::string &filedirectory)
1010 #else
1011 bool studio::AudioContainer::AudioImp::load(const std::string &/*filename*/,
1012 											const std::string &/*filedirectory*/)
1013 #endif
1014 {
1015 	clear();
1016 
1017 	#ifdef WITH_FMOD
1018 
1019 	//And continue with the sound loading...
1020 	string 	file = filename;
1021 
1022 	//Trivial reject... (fixes stat call problem... where it just looks at directory and not file...)
1023 	if(file.length() == 0) return false;
1024 
1025 	//we don't need the file directory?
1026 	if(!is_absolute_path(file))
1027 	{
1028 		file=filedirectory+filename;
1029 		synfig::warning("Not absolute hoooray");
1030 	}
1031 	synfig::info("Loading Audio file: %s", file.c_str());
1032 
1033 	//check to see if file exists
1034 	{
1035 		struct stat	s;
1036 		if(stat(file.c_str(),&s) == -1 && errno == ENOENT)
1037 		{
1038 			synfig::info("There was no audio file...");
1039 			return false;
1040 		}
1041 	}
1042 
1043 	//load fmod if we can...
1044 	//synfig::warning("I'm compiled with FMOD!");
1045 	fmodinit.addref();
1046 
1047 	//load the stream
1048 	int ch = FSOUND_FREE;
1049 	FSOUND_SAMPLE *sm = FSOUND_Sample_Load(FSOUND_FREE,file.c_str(),FSOUND_LOOP_OFF|FSOUND_MPEGACCURATE,0,0);
1050 
1051 	if(!sm)
1052 	{
1053 		synfig::warning("Could not open the audio file as a sample: %s",file.c_str());
1054 		goto error;
1055 	}
1056 
1057 	//synfig::warning("Opened a file as a sample! :)");
1058 
1059 	/*{
1060 		int bufferlen = FSOUND_DSP_GetBufferLength();
1061 		synfig::info("Buffer length = %d samples, %.3lf s",bufferlen, bufferlen / (double)FSOUND_GetOutputRate());
1062 	}*/
1063 
1064 	//set all the variables since everything has worked out...
1065 	//get the length of the stream
1066 	{
1067 		length = FSOUND_Sample_GetLength(sm);
1068 
1069 		int volume = 0;
1070 		FSOUND_Sample_GetDefaults(sm,&sfreq,&volume,0,0);
1071 
1072 		//double len = length / (double)sfreq;
1073 		//synfig::info("Sound info: %.2lf s long, %d Hz, %d Vol",(double)length,sfreq,volume);
1074 	}
1075 
1076 	//synfig::warning("Got all info, and setting up everything, %.2f sec.", length);
1077 	//synfig::warning("	BigSample: composed of %d samples", FSOUND_Sample_GetLength(sm));
1078 	synfig::info("Successfully opened %s as a sample and initialized it.",file.c_str());
1079 
1080 	//set up the playable info
1081 	sample = sm;
1082 	channel = ch;
1083 
1084 	//the length and sfreq params have already been initialized
1085 
1086 	return true;
1087 
1088 error:
1089 	if(sm) FSOUND_Sample_Free(sm);
1090 	file = "";
1091 
1092 	fmodinit.decref();
1093 
1094 	return false;
1095 
1096 	#else
1097 	return false;
1098 	#endif
1099 }
1100 
1101 #ifdef WITH_FMOD
play(double t)1102 void studio::AudioContainer::AudioImp::play(double t)
1103 #else
1104 void studio::AudioContainer::AudioImp::play(double /*t*/)
1105 #endif
1106 {
1107 	#ifdef WITH_FMOD
1108 	if(!sample) return;
1109 
1110 	//stop scrubbing if we are...
1111 	if(is_scrubbing()) stop_scrubbing();
1112 
1113 	//t -= offset;
1114 	t -= get_offset();
1115 	playing = true;
1116 
1117 	if(t < 0)
1118 	{
1119 		unsigned int timeout = (int)floor(-t * 1000 + 0.5);
1120 		//synfig::info("Playing audio delayed by %d ms",timeout);
1121 		//delay for t seconds...
1122 		delaycon = Glib::signal_timeout().connect(
1123 						sigc::mem_fun(*this,&studio::AudioContainer::AudioImp::start_playing_now),timeout);
1124 
1125 		init_play();
1126 		FSOUND_SetFrequency(channel,sfreq);
1127 		FSOUND_SetCurrentPosition(channel,0);
1128 		return;
1129 	}
1130 
1131 	unsigned int position = (int)floor(t*sfreq + 0.5);
1132 
1133 	if(position >= FSOUND_Sample_GetLength(sample))
1134 	{
1135 		synfig::warning("Can't play audio when past length...");
1136 		return;
1137 	}
1138 
1139 	init_play();
1140 	FSOUND_SetFrequency(channel,sfreq);
1141 	FSOUND_SetCurrentPosition(channel,position);
1142 	FSOUND_SetPaused(channel,false);
1143 
1144 	//synfig::info("Playing audio with position %d samples",position);
1145 
1146 	#endif
1147 }
1148 
stop()1149 void studio::AudioContainer::AudioImp::stop()
1150 {
1151 	delaycon.disconnect();
1152 
1153 	#ifdef WITH_FMOD
1154 	if(fmodinit.is_loaded() && playing && isRunning())
1155 	{
1156 		FSOUND_SetPaused(channel,true);
1157 	}
1158 	#endif
1159 
1160 	playing = false;
1161 }
1162 
clear()1163 void studio::AudioContainer::AudioImp::clear()
1164 {
1165 	#ifdef WITH_FMOD
1166 	delaycon.disconnect();
1167 
1168 	stop();
1169 	stop_scrubbing();
1170 
1171 	if(sample)
1172 	{
1173 		if(FSOUND_IsPlaying(channel))
1174 		{
1175 			FSOUND_StopSound(channel);
1176 		}
1177 		channel = FSOUND_FREE;
1178 		FSOUND_Sample_Free(sample);
1179 		fmodinit.decref();
1180 	}
1181 
1182 	playing = false;
1183 
1184 	#else
1185 	channel = 0;
1186 	#endif
1187 
1188 	sample = 0;
1189 	playing = false;
1190 }
1191 
1192 #ifdef WITH_FMOD
start_scrubbing(double t)1193 void studio::AudioContainer::AudioImp::start_scrubbing(double t)
1194 #else
1195 void studio::AudioContainer::AudioImp::start_scrubbing(double /*t*/)
1196 #endif
1197 {
1198 	//synfig::info("Start scrubbing: %lf", t);
1199 	if(playing) stop();
1200 
1201 	set_scrubbing(true);
1202 
1203 	#ifdef WITH_FMOD
1204 	//make sure the other one is not scrubbing...
1205 	if(g_scrubdata.scrub)
1206 	{
1207 		*g_scrubdata.scrub = 0; //nullify the pointer...
1208 	}
1209 
1210 	//Set up the initial state for the delayed audio position
1211 	scrinfo.delaystart = 0;
1212 	scrinfo.pos = 0;
1213 	scrinfo.deltatime = 0;
1214 
1215 	//set it to point to our pointer (dizzy...)
1216 	g_scrubdata.scrub = &scrptr;
1217 
1218 	//setup position info so we can know what to do on boundary conditions...
1219 	curscrubpos = (t - get_offset()) * sfreq;
1220 
1221 	//So we can get an accurate difference...
1222 	timer.reset();
1223 
1224 	//reposition the sound if it won't be when scrubbed (if it's already in the range...)
1225 	int curi = (int)curscrubpos;
1226 	if(curi >= 0 && curi < length)
1227 	{
1228 		init_play();
1229 		FSOUND_SetCurrentPosition(channel,curi);
1230 
1231 		//Set the values...
1232 		scrinfo.pos = curscrubpos;
1233 		scrinfo.delaystart = delay_factor*buffer_length_sec;
1234 
1235 		//synfig::info("\tStarting at %d samps, with %d p %.3f delay",
1236 		//				FSOUND_GetCurrentPosition(channel), (int)scrinfo.pos, scrinfo.delaystart);
1237 	}
1238 
1239 
1240 
1241 	//enable the dsp...
1242 	//synfig::info("\tActivating DSP");
1243 	FSOUND_DSP_SetActive(scrubdspunit,true);
1244 	#endif
1245 }
1246 
stop_scrubbing()1247 void studio::AudioContainer::AudioImp::stop_scrubbing()
1248 {
1249 	//synfig::info("Stop scrubbing");
1250 
1251 	if(is_scrubbing())
1252 	{
1253 		set_scrubbing(false);
1254 
1255 		#ifdef WITH_FMOD
1256 		g_scrubdata.scrub = 0;
1257 
1258 		//stop the dsp...
1259 		//synfig::info("\tDeactivating DSP");
1260 		FSOUND_DSP_SetActive(scrubdspunit,false);
1261 		if(FSOUND_IsPlaying(channel)) FSOUND_SetPaused(channel,true);
1262 		#endif
1263 	}
1264 
1265 	curscrubpos = 0;
1266 }
1267 
1268 #ifdef WITH_FMOD
scrub(double t)1269 void studio::AudioContainer::AudioImp::scrub(double t)
1270 #else
1271 void studio::AudioContainer::AudioImp::scrub(double /*t*/)
1272 #endif
1273 {
1274 	#ifdef WITH_FMOD
1275 	//synfig::info("Scrub to %lf",t);
1276 	if(is_scrubbing())
1277 	{
1278 		//What should we do?
1279 
1280 		/* Different special cases
1281 			All outside, all inside,
1282 			coming in (left or right),
1283 			going out (left or right)
1284 		*/
1285 		double oldpos = curscrubpos;
1286 		double newpos = (t - get_offset()) * sfreq;
1287 
1288 		curscrubpos = newpos;
1289 
1290 		//Ok the sound is running, now we need to tweak it
1291 		if(newpos > oldpos)
1292 		{
1293 			//Outside so completely stopped...
1294 			if(newpos < 0 || oldpos >= length)
1295 			{
1296 				//synfig::info("\tOut +");
1297 				if(FSOUND_IsPlaying(channel))
1298 				{
1299 					FSOUND_SetPaused(channel,true);
1300 				}
1301 
1302 				//Zero out the data!
1303 				scrinfo.Lock();
1304 				scrinfo.delaystart = 0;
1305 				scrinfo.deltatime = 0;
1306 				scrinfo.Unlock();
1307 
1308 				return;
1309 			}
1310 
1311 			//going in? - start the sound at the beginning...
1312 			/*else if(oldpos < 0)
1313 			{
1314 				//Set up the sound to be playing paused at the start...
1315 				init_play();
1316 				FSOUND_SetCurrentPosition(channel,0);
1317 
1318 				synfig::info("\tIn + %d", FSOUND_GetCurrentPosition(channel));
1319 
1320 				scrinfo.Lock();
1321 				scrinfo.pos = 0;
1322 				scrinfo.delaystart = delay_factor*buffer_length_sec;
1323 				scrinfo.deltatime = 0;
1324 				scrinfo.Unlock();
1325 			}*/
1326 			//don't need to deal with leaving... automatically dealt with...
1327 
1328 			else //We're all inside...
1329 			{
1330 				//Set new position and decide what to do with time...
1331 				scrinfo.Lock();
1332 				scrinfo.pos = newpos;
1333 
1334 				//should we restart the delay cycle... (is it done?)
1335 				if(!isRunning() || (scrinfo.delaystart <= 0 && scrinfo.deltatime <= 0 && isPaused()))
1336 				{
1337 					//synfig::info("Starting + at %d",(int)newpos);
1338 					scrinfo.deltatime = 0;
1339 					scrinfo.delaystart = delay_factor*buffer_length_sec;
1340 					scrinfo.Unlock();
1341 
1342 					//Set up the sound paused at the current position
1343 					init_play();
1344 					int setpos = min(max((int)newpos,0),length);
1345 					FSOUND_SetCurrentPosition(channel,setpos);
1346 					timer.reset();
1347 					return;
1348 				}
1349 
1350 				//No! just increment the time delta...
1351 				scrinfo.deltatime += timer.pop_time();
1352 
1353 				//Nope... continue and just increment the deltatime and reset position...
1354 				scrinfo.Unlock();
1355 
1356 				//set channel and unpause
1357 				FSOUND_SetPaused(channel,false);
1358 				scrinfo.channel = channel;
1359 
1360 			}
1361 		}else if(newpos < oldpos)
1362 		{
1363 			//completely stopped...
1364 			if(newpos >= length || oldpos < 0)
1365 			{
1366 				//synfig::info("Out -");
1367 				if(FSOUND_IsPlaying(channel))
1368 				{
1369 					FSOUND_SetPaused(channel,true);
1370 				}
1371 
1372 				//Zero out the data!
1373 				scrinfo.Lock();
1374 				scrinfo.delaystart = 0;
1375 				scrinfo.deltatime = 0;
1376 				scrinfo.Unlock();
1377 			}
1378 
1379 			//going in? - start going backwards at the end...
1380 			/*else if(oldpos >= length)
1381 			{
1382 				synfig::info("In -");
1383 				//Set up the sound to be playing paused at the start...
1384 				init_play();
1385 				FSOUND_SetCurrentPosition(channel,length-1);
1386 
1387 				scrinfo.Lock();
1388 				scrinfo.pos = length-1;
1389 				scrinfo.delaystart = delay_factor*buffer_length_sec;
1390 				scrinfo.deltatime = 0;
1391 				scrinfo.Unlock();
1392 			}*/
1393 			//we don't have to worry about the leaving case...
1394 
1395 			else //We're all inside...
1396 			{
1397 				//Set new position and decide what to do with time...
1398 				scrinfo.Lock();
1399 				scrinfo.pos = newpos;
1400 
1401 				//should we restart the delay cycle... (is it done?)
1402 				if(!isRunning() ||(scrinfo.delaystart <= 0 && scrinfo.deltatime <= 0 && isPaused()))
1403 				{
1404 					//synfig::info("Starting - at %d",(int)newpos);
1405 					scrinfo.deltatime = 0;
1406 					scrinfo.delaystart = delay_factor*buffer_length_sec;
1407 					scrinfo.Unlock();
1408 
1409 					//reset timing so next update will be a valid diff...
1410 					init_play();
1411 					int setpos = min(max((int)newpos,0),length);
1412 					FSOUND_SetCurrentPosition(channel,setpos);
1413 					timer.reset();
1414 					return;
1415 				}
1416 
1417 				//No! just increment the time delta...
1418 				scrinfo.deltatime += timer.pop_time();
1419 
1420 				//Nope... continue and just increment the deltatime and reset position...
1421 				scrinfo.Unlock();
1422 
1423 				//set channel and unpause
1424 				FSOUND_SetPaused(channel,false);
1425 				scrinfo.channel = channel;
1426 			}
1427 		}
1428 	}
1429 	#endif
1430 }
1431