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 >= 1.5.7 & (lib)fftw3

Description:

A plugin that can receive PCM-data from other plugins (e.g. mp3ng or already
included in music resp.)
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.

Visualization delay (in ms) 0-2000
Corrects the delay between audio output and its visualization (due to buffering).
Softdevice has quiete a large buffer resulting in a visible gap between the
visualization and its corresponding audio data. This gap is about 1000ms while
playing back stereo at 44.1 kHz. The visualization is way too early ;-).

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.3 with a FF-DVB-s 2MB (CPU = PIII 1,2GHz) with mp3ng/music-plugin
  as self-made data provider as well as visualization-client.
  Additionally the lcdproc- and graphlcd-plugin were used as visualization-client.
  Thus, there were three clients active simultaneously.
- Additionally tested on VDR-1.4.5 with Softdevice (xv) (CPU = Athlon64 3000+)
  with mp3ng/music-plugin as self-made data provider as well as visualization-client.
- 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/muggle?) used the data provided by this plugin to visualize
  a spectrum analyzer.
- The bar heights for visualization clients are normalized to 100%, thus it is
  very easy to scale them for the needs of the actual visualization.
- To clarify the meaning of "idle" when playing some kind of music and displaying
  the SpAn-plugin's menu:
  Visualization-clients are defined as "idle" in the plugin's menu if they really
  are idle. If this status-message is displayed on the OSD there is very nothing
  being visualized. Thus, no data are requested -> "idle".
  If (let's suppose) e.g. atmolight was running, it was displayed as "idle"-client and
  mp3(ng) or music or cdda resp. (whichever plugin was playing) was displayed as
  "idle"-provider.


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):
...
  if(outlen)
+  {
+// Spectrum Analyzer: Hand over the PCM16-data to SpAn
+       int offset = FHS;
+       Span_SetPcmData_1_0 SetPcmData;
+       cPlugin *Plugin = cPluginManager::CallFirstService(SPAN_SET_PCM_DATA_ID, NULL);
+       if (Plugin)
+       {
+            SetPcmData.length = (unsigned int)(outlen-offset);
+            // the timestamp (ms) of the frame(s) to be visualized:
+            SetPcmData.index = index;
+            // tell SpAn the ringbuffer's size for it's internal bookkeeping of the data to be visualized:
+            SetPcmData.bufferSize = MP3BUFSIZE;
+            SetPcmData.data = buff + offset + 1; // get rid of the header
+            SetPcmData.bigEndian = false;
+            cPluginManager::CallFirstService(SPAN_SET_PCM_DATA_ID, &SetPcmData);
+       }
+
     f=new cFrame(buff,outlen+FHS,ftUnknown,index);
+  }
   return f;
 }

...

  if(w>0) {
+// Spectrum Analyzer: Tell SpAn which timestamp is currently playing
+          if ( frameIncomplete )
+          {
+              Span_SetPlayindex_1_0 SetPlayindexData;
+              cPlugin *Plugin = cPluginManager::CallFirstService(SPAN_SET_PLAYINDEX_ID, NULL);
+              if (Plugin)
+              {
+                  SetPlayindexData.index = playindex;
+                  cPluginManager::CallFirstService(SPAN_SET_PLAYINDEX_ID, &SetPlayindexData);
+              }
+              frameIncomplete = false;
+          }
+
         p+=w; pc-=w;
         if(pc<=0) {
           ringBuffer->Drop(pframe);
           pframe=0;
+	  frameIncomplete = true;
           goto next;
           }
         }
...

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;
}
