This is a 'Sp'ectrum 'An'alyzer Plugin for the Video Disk Recorder (VDR).

Written by:                    Christian Leuschen (christian.leuschen@gmx.de)

Project's homepage:            http://lcr.vdr-developer.org/

Latest version available at:   http://lcr.vdr-developer.org/downloads/

See the file COPYING for license information.
See the file CONTRIBUTORS for a list of persons that have contributed to this
plugin and who I want to thank for that.


Requirements:

VDR & (lib)fftw3

Description:

A plugin that can receive PCM-data from other plugins (e.g. mp3ng)
to compute frequency-data and to provide them to other plugins
to display a 'Sp'ectrum 'An'alyzer.
It is meant as a kind of middleware between
these providers and clients to be as generic as needed to be able to implement
the visualisation on all kinds of output-devices (OSD, lcd, graphlcd, framebuffer, etc.).
Sketch of the architecture:
| Data-provider sends PCM-data ---> | SpAn-plugin | >--- Visualization-clients
				request the spectrum-data "making s.th. handsome out of it" |


Controls:
---

Installation:

cd /put/your/path/here/VDR/PLUGINS/src
tar -xzf /put/your/path/here/vdr-span-X.Y.Z.tar.bz2
ln -s span-X.Y.Z span
cd /put/your/path/here/VDR
make
make plugins
./vdr -P span


Parameter:
---


Setup:

Activate spectrum analyzer?	yes/no
De-(Activates) the plugin.
yes: The plugin receives data (only if clients also request data) and
on request hands the results over to clients.
no: In this case all data provider- and visualization-plugins are
rejected when trying to hand over data or to request results.

Use logarithmic diagram		yes/no
A (yet naive) try to equalize the heights of the bars a bit.
Lower frequencies are prevailing which results in an "high dynamic
range of the heights of the bars" (= mostly few extremely high bars on
the left, further right more decreasing or even no bars at all).
yes: Suspends a "no" at "Use pure (unequalized) data". Scaling of the heights
of the bars is done logarithmically.
no: No logarithmic scaling is done.
Tips to improve this way of equalizing are warmly welcome!

Hide mainmenu entry		yes/no
Should be self-explanatory.

Use pure (unequalized) data	yes/no
yes: Really compute the pure data without equalizing anyhow.
no: Values of higher frequencies are emphasized resulting in a "bit nicer way
of displaying the frequencies" than with pure data.


Notes:
- Tested on VDR-1.4.0 with a FF-DVB-s 2MB (CPU = PIII 1,2GHz) with mp3ng-plugin
  as self-made data provider as well as visualization-client.
  Additionally the lcdproc-plugin was used as visualization-client,
  but just as a proof-of-concept. Thus, there were two clients
  active simultaneously.
- I would be very glad if the authors of plugins for rendering vdr-information
  on (external) displays (graphlcd/graphtft/lcdproc/etc.) or the OSD (mp3(ng)/
  cdda/radio?/muggle?) used the data provided by this plugin to visualize
  a spectrum analyzer.


Generic example how to use it:
The description of the service-protocol is defined in "defines.h".

Data provider (taken from adapted mp3ng-plugin - player-mp3.c):
// Spectrum Analyzer: Push data to analyze
// Since I needed some time to figure it out:
// As reminder: "pc" is the counter for the bytes that have been written to p (unsigned char)
//				However, PCM data seems to consist of ([signed|unsigned]int).
//				That's why the length that's communicated to the span-plugin has to be smaller,
//				i.e pc/sizeof/(signed int) = pc/4.
// The PCM-data is stored interleaved: e.g. 16-bit stereo means, that there are:
//
// |--;--|--;--|--;--|--;--|	(|=border of int, - = 1 byte, ; = border of channels)
//  LL RR LL RR LL RR LL RR		LL/RR = two byte for the left/right channel
 

	Span_SetPcmData_1_0 SetPcmData;
	cPlugin *Plugin = cPluginManager::CallFirstService(SPAN_SET_PCM_DATA_ID, NULL);
	int z;
	int offset = FHS+1; // empirically found out
	SetPcmData.length = (unsigned int)pc-offset;
	if (SetPcmData.length > PCM_FRAME_SIZE)
		SetPcmData.length = (unsigned int)PCM_FRAME_SIZE;
	else if (SetPcmData.length < 0)
		SetPcmData.length = 0;
	if (SetPcmData.length > 10)
	{
		int *data = new int[SetPcmData.length];
		SetPcmData.data = data;
		
		// nasty trick to get the 16-bit data (interleaved in 32-bit (int))
		int *p_castedL = new int[SetPcmData.length];
		int *p_castedR = new int[SetPcmData.length];
		
		memcpy(p_castedL,p+offset,(SetPcmData.length-offset)*sizeof(unsigned char));
		memcpy(p_castedR,p+offset+2,(SetPcmData.length-offset-2)*sizeof(unsigned char));
		
		SetPcmData.length = (unsigned int)SetPcmData.length/sizeof(int);
		
		if (Plugin)
		{
			for (z = 0; z < SetPcmData.length/2; z++) {
  				SetPcmData.data[z] = (int)(p_castedL[z]>>16);
			}
			for (z = SetPcmData.length/2; z < SetPcmData.length; z++) {
				SetPcmData.data[z] = (int)(p_castedR[z]>>16);
			}
			cPluginManager::CallFirstService(SPAN_SET_PCM_DATA_ID, &SetPcmData);

		}
		delete [] data;
		delete [] p_castedL;
		delete [] p_castedR;
	}


Visualization-client (from adapted graphlcd-plugin - display.c):

// Spectrum Analyzer visualization
	if ( GraphLCDSetup.enableSpectrumAnalyzer )
	{
		if (cPluginManager::CallFirstService(SPAN_GET_BAR_HEIGHTS_ID, NULL))
		{
			Span_GetBarHeights_v1_0 GetBarHeights;
			
			int bandsSA = 20;
			int falloffSA = 8;
			int channelsSA = 1;
			
			unsigned int bar;
			unsigned int *barHeights = new unsigned int[bandsSA];
			unsigned int *barHeightsLeftChannel = new unsigned int[bandsSA];
			unsigned int *barHeightsRightChannel = new unsigned int[bandsSA];
			unsigned int volumeLeftChannel;
			unsigned int volumeRightChannel;
			unsigned int volumeBothChannels;
			unsigned int *barPeaksBothChannels = new unsigned int[bandsSA];
			unsigned int *barPeaksLeftChannel = new unsigned int[bandsSA];
			unsigned int *barPeaksRightChannel = new unsigned int[bandsSA];
							
			GetBarHeights.bands 					= bandsSA;
			GetBarHeights.barHeights				= barHeights;
			GetBarHeights.barHeightsLeftChannel 	= barHeightsLeftChannel;
			GetBarHeights.barHeightsRightChannel	= barHeightsRightChannel;
			GetBarHeights.volumeLeftChannel			= &volumeLeftChannel;
			GetBarHeights.volumeRightChannel		= &volumeRightChannel;
			GetBarHeights.volumeBothChannels		= &volumeBothChannels;
			GetBarHeights.name						= "graphlcd";
			GetBarHeights.falloff					= falloffSA;
			GetBarHeights.barPeaksBothChannels		= barPeaksBothChannels;
			GetBarHeights.barPeaksLeftChannel		= barPeaksLeftChannel;
			GetBarHeights.barPeaksRightChannel		= barPeaksRightChannel;
			
			if ( cPluginManager::CallFirstService(SPAN_GET_BAR_HEIGHTS_ID, &GetBarHeights ))
			{
				int i;
				int barWidth = 2;
				int saStartX = FRAME_SPACE_X;
				int saEndX = saStartX + barWidth*bandsSA*2 + bandsSA/4 - 1;
				int saStartY = FRAME_SPACE_Y;
				int saEndY = FRAME_SPACE_Y + bitmap->Height()/2 - 3;
				
				LastTimeSA.Set(100);
				
				if ( GraphLCDSetup.SAShowVolume )
				{
					
					saStartX = FRAME_SPACE_X  + bitmap->Width()/2 - (barWidth*bandsSA*2 + bandsSA/4)/2 - 2;
					saEndX = saStartX + barWidth*bandsSA*2 + bandsSA/4 - 1;
					
					// left volume
					bitmap->DrawRectangle(FRAME_SPACE_X,
						saStartY,
						saStartX-1,
						saEndY + 1,
						GLCD::clrWhite, true);
					
					for ( i=0; (i<logo->Width()/2-2) && (i<3*(volumeLeftChannel*saStartX)/100); i++)
					{
						bitmap->DrawRectangle(saStartX - i - 2,
							saStartY + saEndY/2 - i,
							saStartX - i - 4,
							saStartY + saEndY/2 + i,
							GLCD::clrBlack, true);
					}
					
					// right volume
					bitmap->DrawRectangle(saEndX + 1,
						saStartY,
						bitmap->Width() - 1,
						saEndY + 1,
						GLCD::clrWhite, true);
					
					for ( i=0; (i<logo->Width()/2-2) && (i<3*(volumeRightChannel*saStartX)/100); i++)
					{
						bitmap->DrawRectangle(saEndX + 2 + i,
							saStartY + saEndY/2 - i,
							saEndX + i + 4,
							saStartY + saEndY/2 + i,
							GLCD::clrBlack, true);
					}
				}
				// black background
				bitmap->DrawRectangle(saStartX,
					saStartY,
					saEndX,
					saEndY + 1,
					GLCD::clrBlack, true);

				for ( i=0; i < bandsSA; i++ )
				{
/*					if ( channelsSA == 2 )
					{
						bar = barHeightsLeftChannel[i];
						bar = barHeightsRightChannel[i];
					}*/
					if ( channelsSA == 1)
					{
						// the bar
						bar = (barHeights[i]*(saEndY-saStartY))/100;
						bitmap->DrawRectangle(saStartX + barWidth*2*(i)+ barWidth + 1,
							saEndY,
							saStartX + barWidth*2*(i) + barWidth+ barWidth + 1,
							saEndY - bar,
							GLCD::clrWhite, true);
						
						// the peak
						bar = (barPeaksBothChannels[i]*(saEndY-saStartY))/100;
						if ( bar > 0 )
						{
							bitmap->DrawRectangle(saStartX + barWidth*2*(i)+ barWidth + 1,
								saEndY - bar,
								saStartX + barWidth*2*(i) + barWidth+ barWidth + 1,
								saEndY - bar+1,
								GLCD::clrWhite, true);
						}
					}
				}
			}
			
			delete [] barHeights;
			delete [] barHeightsLeftChannel;
			delete [] barHeightsRightChannel;
			delete [] barPeaksBothChannels;
			delete [] barPeaksLeftChannel;
			delete [] barPeaksRightChannel;
		}
	}

// Service protocol to identify pcm-data-provider- and visualization-client-plugins (taken from mp3ng):
bool cPluginMp3::Service(const char *Id, void *Data)
{
	if (strcmp(Id, SPAN_PROVIDER_CHECK_ID) == 0)
	{
		if (Data != NULL)
			*((Span_Provider_Check_1_0*)Data)->isActive = true;
		return true;
	}
	
	if (strcmp(Id, SPAN_CLIENT_CHECK_ID) == 0)
	{
		if ( MP3Setup.enableSpectrumAnalyzer && (Data != NULL) )
		{
			*((Span_Client_Check_1_0*)Data)->isActive = true;
		}
		return true;
	}
	
	return false;
}

TODO:
- Test it on my streaming-client (PIII 450MHz with DXR3)
