/*	wave2disk.cpp			15.05.2012 06:08			

	Roland Froehlich
	
changes:
-------------------------------------------------------------------------------
24.05.2008	Tascam FireOne: Input is from right channel
27.04.2008	automaticly open the next write file if file length exhausted
			per file: MAX_WAV_SHORT_ANZ for 60 min MONO at 44.1kHz, ca. 316 MB
-------------------------------------------------------------------------------
*/


#include	<windows.h>
#include	<stdio.h>
#include	<math.h>

#ifndef	DWORD
#define	DWORD unsigned long
#endif

#include	"saq_config.h"
#include	"wave2disk.h"
#include	"open_new_file.c"


static inline 	unsigned long swap16(unsigned long a) 
{  
	unsigned long	x= a & 0xff ; 
	unsigned long	y= (a>>8) & 0xff ; 
	return (x<<8)|y; 
}

static inline 	unsigned long swap32(unsigned long a) 
{ 	unsigned long x = a & 0xffff;
	unsigned long y = (a>>16) & 0xffff;
	return (swap16(x)<<16) | swap16(y);
}

// the callees are located in main.c:
/*extern "C" */ double	Get_SampleRate_IN(void);	// for Audio Input  File
/*extern "C" */ double	Get_SampleRate_OUT(void);	// for Audio Output File
/*extern "C" */ int		Get_framelength(void);

extern int 	left_channel;		// set input channel left=1 or right=0
extern int	time_format;		// 0= UTC
extern volatile int no_retry;	// if 1: no write to disk is possible

#define SAMPLE_SIZE	sizeof(short)	// 16 bit output per sample to disk, we use the standard Microsoft WAVE-file here.


#if 0	// in dev-cpp these routines are defined !
static
int trunc( double x)	/* pascal-style: sign/magnitude conversion */ 
{	if ( x < 0.0)	return -((int)floor(-x)); 
	else			return  ((int)floor( x)); 
}

static
int round( double x)	/* pascal-style: sign/magnitude conversion */ 
{	if ( x < 0.0)	return -((int)floor(-x+0.5)); 
	else			return  ((int)floor( x+0.5)); 
}
#endif


typedef struct wave_header_struct 
{
	DWORD	riffname;
	DWORD	dateilen;
	DWORD	wavname;
	DWORD	formname;
	DWORD	subchlen;
	short	format;
	short	nChannels;
	int		sample_freq;
	DWORD	bytes_per_sec;
	short	bytes_per_sample;
	short	bits_per_sample;
	DWORD	dataname;
	DWORD	datalen_bytes;
} wave_header_typ;


class RECORD_WAVE_class
{
public:
	volatile int	file_open_request;
	volatile int	file_close_request; 
	volatile int	terminate_request;
	volatile int	terminate_acknowledge;
	volatile int	file_is_open;
		
	int				pause;
	
	int				FRAMES;	// if 0: the correct length must be first inserted and modulo buffers must be build!
	double			wav_Fs;
	int				nAudio_channels;	// 1=MONO, 2=STEREO
	
#if ASIO_ENVIRONMENT			
	float 			* Lsource;
	float 			* Rsource;	
#endif
	char 			* Pprefix_filename;	
	char			prefix_name[80];
	char 			wavefile_name[260];
	
	volatile unsigned long	readIdx;		// linear index for short, 0 ... 2G for maximal 4GB *.wav-file length.
	
	int		update_hms;						// flag for GUI
	int		hours,minutes,seconds;			// the stored run time for display by GUI
	int		frames_per_sec, written_frames;

	
	char	timestring[40];
	
	char	* Get_Tapetime(void)
	{	update_hms = 0;
		sprintf( timestring,"%3d:%02d:%02d", hours,minutes,seconds);
		return &timestring[0];	
	}
		
	
	void	INC_h()	{ hours++; }
	
	void	INC_m()
	{	minutes++;
		if (minutes >= 60) 
		{	minutes = 0;
			INC_h();
		}	
	}
		
	void	INC_s()
	{	seconds++;
		if (seconds >= 60) 
		{	seconds = 0;
			INC_m();	
		}
		// after every second: trigger message to GUI:
		
		update_hms = 1;	
	}
	
private:
	volatile unsigned long	writIdx;		// linear index for short, 0 ... 2G

	FILE			*WAV_FILE;

	// data area, modulo LINWAVBUF_SIZE accessed, reserve if framelen is not 2^m !!!		
	short wave_lin_buf [ LINWAVBUF_SIZE ];	// cyclic ring-buffer, actual length can be reduced because of content of FRAMES. 	
	
	wave_header_typ	outheader;	// storage for the header, it is filled with actual values in the close routine and reinserted before file closing.
	
	int		DISKLEN,LINLEN;		// LINLEN can be unequal 2^m, especially in an ASIO environment! it is computed in Set_Frames()
		
public:
	       	
	~RECORD_WAVE_class()		// Destructor:
	{
		if (file_is_open) 
		{
			close_wave_trace ();
		}
		
	}	// Destructor
	
		
	RECORD_WAVE_class(char * prefix_filename )		// Constructor
	{	
		unsigned long P,Q;		// used for modulo arithmetic
	
		Pprefix_filename = prefix_filename;	// static pointer
		sprintf( &prefix_name[0],"%s", prefix_filename );	// store prefix of filename here
		
	
		readIdx = writIdx = 0;
		file_is_open = 0;
		WAV_FILE = NULL;		// at init: no file connected
		
		file_open_request = file_close_request = terminate_request = terminate_acknowledge = 0;
		
				
		nAudio_channels = 2;	// use stereo wave trace output as default, can be corrected at later time...
		wav_Fs = 44100.0 ;		// get this from ASIO driver or other caller !	
	
		hours=minutes=seconds = 0;
		update_hms = 0;
		frames_per_sec = 44100;	// can be overwritten
		written_frames = 0;
		
		pause = 0;				// this bool variable can be used as pause taste, but here not used in the GUI.
		
	//	buffer management:
	
		FRAMES = 256;			// preliminary, is overwritten by asio driver or subsequent initializations.
		
		P = WAVEOUT_BUFMAX /( nAudio_channels * FRAMES);
	
		DISKLEN = nAudio_channels*FRAMES*P;
	
		Q= LINWAVBUF_SIZE / DISKLEN;
		LINLEN = Q* DISKLEN;	// used for modulo access of linear buffer
	
		// if FRAMES==0  : this value is at construction (start of .exe) invalid before any access to the ring-buffer ! 
		// The first Audio-Callback must insert the correct length for designing the correct modulo buffer numbers!
		FRAMES = 0;		
			
	}	// Constructor
	
	void Set_Frames ( int frames)
	{	unsigned long P,Q;
	
	//	buffer management:
		readIdx = writIdx = 0;
		
		FRAMES = frames;			// init value of FRAMES is here overwritten by actual driver response
		
		P = WAVEOUT_BUFMAX /( nAudio_channels * FRAMES);
	
		DISKLEN = nAudio_channels*FRAMES*P;
	
		Q= LINWAVBUF_SIZE / DISKLEN;
		LINLEN = Q* DISKLEN;	// used for modulo access of linear buffer
	
		readIdx = writIdx = 0;	
		
	}	//Set_Frames()
	
	
	void MakeHeader_MULTI_CH (  int samples_words , int added_bytes = 0 ) // no additional chunk assumed (No Marker etc.)
	{
	    int bytelen; 
	
		bytelen= SAMPLE_SIZE * samples_words;		// here is the 2 GB upper bound for *.wav files. 
		
		outheader.riffname = swap32('RIFF');		// "RIFF"; Microsoft (+Dev-cpp)  is re-(per)verse ! Borland is OK.
		outheader.dateilen = sizeof (wave_header_typ)+ added_bytes + bytelen -8;
		outheader.wavname  = swap32('WAVE');		// "WAVE";
		outheader.formname = swap32('fmt ');		// "fmt ";
		outheader.subchlen = 16;					// 16 bit per sample
		outheader.format   = 1;						// PCM format
		outheader.nChannels = nAudio_channels;		// 1(MONO) or 2(STEREO)
		outheader.sample_freq = (int) round ( wav_Fs );
		outheader.bytes_per_sec = nAudio_channels * SAMPLE_SIZE * outheader.sample_freq;
		outheader.bytes_per_sample = (short) (SAMPLE_SIZE * nAudio_channels);
		outheader.bits_per_sample  = (short) (SAMPLE_SIZE*8);	// 16 bit per sample
		outheader.dataname = swap32('data');		// "data";
		outheader.datalen_bytes = bytelen;
	        
	}	// MakeHeader_MULTI_CH()
	
	
	
	int	open_wave_trace ()
	{	
		char sz20[20];
				
		if (file_is_open) return -1;

		if ( nAudio_channels == 2)
			sprintf( sz20,"_S%dk", (int) ( wav_Fs/1000.0 ) );
		else
			sprintf( sz20,"_M%dk", (int) ( wav_Fs/1000.0 ) );	

		sprintf( &prefix_name[0],"%s%s", Pprefix_filename , sz20);	// store prefix of filename here
		
		if (no_retry == 0)						
			no_retry = open_new_binfile( & WAV_FILE , &prefix_name[0], ".wav" ,wavefile_name, NULL,time_format );	// wave binary trace		
		if (no_retry != 0) return -2;
					
		if ( WAV_FILE )
		{
	
//			wav_Fs = Get_SampleRate();
				
			MakeHeader_MULTI_CH ( 0 );		// reserve space on disk, preliminary header, sample length = 0
	
			readIdx = writIdx = 0;
			file_open_request = file_close_request = 0;
				
			fwrite( & outheader , sizeof(char) , sizeof(outheader) , WAV_FILE);
			
			frames_per_sec = (int) round ( wav_Fs );
			written_frames = 0;
			hours=minutes=seconds = 0;
				
			file_is_open = 1;
			return 0;	
		}	
		else return -2;
		
	}	// open_wave_trace ()


		
			
	int	close_wave_trace ()
	{
		if (!file_is_open) 
		{	file_close_request = 0;			//	ack for close	
			return -1;
		}
	
		file_is_open = 0;
			
		fseek( WAV_FILE , 0, SEEK_SET);		// back to start of file
	
//		wav_Fs = Get_SampleRate();
			
		MakeHeader_MULTI_CH ( readIdx );	// samples_words
	
		fwrite( & outheader , sizeof(char) , sizeof(outheader) , WAV_FILE);
		fclose( WAV_FILE);				
		
		file_close_request = 2;			//	ack for close	
		return 0;
				
	}	// close_wave_trace ()
	
	
	
	void Wave_to_Disk ()		// must be periodically called, slow process,esp. in GUI (in Timer event or other thread).
	{	int written_len;
		int	short_index;
		short * data_pointer;
		
//		if  (no_retry) goto ende;
				
		if ( (file_open_request == 1 )&& (!terminate_request) )
		{	
			if (no_retry == 0) open_wave_trace();
			file_open_request = 0;
		}
		
		if (!file_is_open) goto ende;
			
	//	data transfer phase: 
		
		while (( (readIdx + DISKLEN) <= writIdx )&&(file_close_request == 0 ))
		{
			short_index = readIdx % LINLEN;	// modulo LINWAVBUF_SIZE
			
			data_pointer = & wave_lin_buf [ short_index ];					
		
			written_len = fwrite( data_pointer , sizeof(short) , DISKLEN , WAV_FILE);
		
			if (written_len == DISKLEN)
			{	
				readIdx += DISKLEN ;
		
				if ( readIdx >= MAX_WAV_SHORT_ANZ ) 	// maximal file length reached?	
				{	
					// file_close_request= 1;	
					// handle automaticly to write the next file:
					close_wave_trace();
					file_close_request = 0;		// set ack to zero
					if (no_retry == 0) 	open_wave_trace();			// 17.05.2012 08:40
					else 				terminate_request= 1;
				}
				
			}
			else 
			{	file_close_request= 1;			// error (disk full?) ==> abort, no retry 
				no_retry = 1;
			}
						
		}	// while...
	
		
		if ( file_close_request == 1 )
		{
			close_wave_trace();
		}
	
	ende:
				  			
		if (terminate_request) terminate_acknowledge = 1;
					  				
	}	// Wave_to_Disk (), slow process


	//////////////////////////////	FOR SPEED UP ===>>	11.12.2004
	
	// avoid the call __ftol from VC++ here:
	
#if 0	// not used in the moment
	long __inline Ftolong (double x)		// same function as: return (long) x;
	{	long retval;
	   __asm fld x
	   __asm fistp retval					// cast double to long, avoid the library call here
	   return retval;
	}
	
	long __inline FtolongS (double x)		// same function as: return (long) ( 32767.0 * x); 
	{	long retval;
		const float max16bit = 32767.0f;
	   __asm fld x
	   __asm fmul max16bit
	   __asm fistp retval					// cast double to long, avoid the library call here
	   return retval;
	}
	
	
	long __inline longSAT32 (double x)
	{
	   if (x > 0x7FFFFFFF)
	      return 0x7FFFFFFF;
	   else if (x < -0x7FFFFFFF)
	      return -0x7FFFFFFF;     // cast rounds towards 0, so there is a gap if we return 0x80000000
	   return (long) x;
	}
#endif
	
	
	
	/*****************************************************************************/
	/* Saturate : keeps value in range without branching                         */
	/*****************************************************************************/
	
	inline float SaturateMAX(float input, float fMax)
	{
		static const float fGrdDiv = 0.5f;
		float x1 = fabsf(input + fMax);
		float x2 = fabsf(input - fMax);
		return fGrdDiv * (x1 - x2);
	}
	
	inline short FromFloat_to16_SAT (float source )	// not extremely fast, but exact and portable
	{
		float   finter = SaturateMAX(source, 1.f);
		return (short)((finter * 32767.505f) - .5f);
	}
	
	
	
	// a jumpless saturation for 16 bit output range:
	
	//short __inline WshortSAT16 (float y)
	
	short WshortSAT16 (float y)
	{	
	//	long r1,r2;
	//	long x = FtolongS (  (double)y );	// we hope that the input is |y| < 2^30 here ! raisonable are ca. 2^15
	//  possible racing condition with ASIO Drivers !!!
	
		long x = (long) (  32767.0f *y );	// we hope that the input is |y| < 2^30 here ! raisonable are ca. 2^15
		
	#if 0	
		r1 = labs(x + 0x7FFFL);		// this is an inline operator in MS VC++, but in other compilers ?
		r2 = labs(x - 0x7FFFL);
		return (short) ((r1-r2)>>1);
	#else
		if ( x >  0x7FFFL) x=  0x7FFFL;
		else
		if ( x < -0x7FFFL) x= -0x7FFFL;	
		return (short) x;
	#endif
	
	}
	

	// MONO track or Stereo interleaved track, write LEFT channel only:
	void FromFloat16_Mono_Skip ( float * source, short * target, int frames )	
	{

		while (--frames >= 0) 
		{	*target++ = FromFloat_to16_SAT ( *source );
			source += 2;
		}
		
	}
	
	void FromFloat16_Mono ( float * source, short * target, int frames )	// MONO track or Stereo interleaved track
	{
		while (--frames >= 0) *target++ = FromFloat_to16_SAT ( *source++ );
	
	}

	
	void Trace_Stereo_Wave_Interleaved_Out ( float * source , int samplecount)	// source is stereo interleaved pair (L,R) !!!
	{	
		int	short_index;
		short * data_pointer;
				
		if (!file_is_open) return;
		if (pause) return;			// forget this buffer
		
		short_index = writIdx % LINLEN;	// modulo LINWAVBUF_SIZE
			
		data_pointer = & wave_lin_buf [ short_index ];	

		if ( nAudio_channels == 2)
		{	
			FromFloat16_Mono ( source , data_pointer , 2 * samplecount );	// Left,Right interleaved buffer
			writIdx += (2* samplecount ) ;	// stereo track	
		}
		else
		{
			FromFloat16_Mono_Skip ( source , data_pointer , samplecount );	// Left,Right interleaved buffer: write LEFT only				
			writIdx +=  samplecount  ;		// mono track	
		}

	
		written_frames += samplecount;
		if ( written_frames >= frames_per_sec )
		{
			written_frames -= frames_per_sec;
			INC_s();
		}
									
	}	// Trace_Stereo_Wave_Interleaved_Out()	
		
	void Trace_Stereo_Wave_Interleaved_In ( float * source , int samplecount)	// source is stereo interleaved pair (L,R) !!!
	{	
		int	short_index;
		short * data_pointer;
				
		if (!file_is_open) return;
		if (pause) return;			// forget this buffer
		
		short_index = writIdx % LINLEN;	// modulo LINWAVBUF_SIZE
			
		data_pointer = & wave_lin_buf [ short_index ];	

		if ( nAudio_channels == 2)
		{	
			FromFloat16_Mono ( source , data_pointer , 2 * samplecount );	// Left,Right interleaved buffer
			writIdx += (2* samplecount ) ;	// stereo track	
		}
		else
		{	
			if (left_channel==0) source++;		// write RIGHT channel only, skipped LEFT channel

			FromFloat16_Mono_Skip ( source , data_pointer , samplecount );	// Left,Right interleaved buffer: write LEFT only				
			writIdx +=  samplecount  ;		// mono track	
		}

	
		written_frames += samplecount;
		if ( written_frames >= frames_per_sec )
		{
			written_frames -= frames_per_sec;
			INC_s();
		}
									
	}	// Trace_Stereo_Wave_Interleaved_In()	
	
	
#if ASIO_ENVIRONMENT 
	
	void FromFloat16_Stereo ( float * source, short * target, int frames )	// STEREO track
	{
		while (--frames >= 0) 
		{	*target = FromFloat_to16_SAT ( *source++ );
			target  = target +2;											// interleaved write a mono track
		}
	
	}
	
	void ASIO_Trace_Stereo_Wave ( void )
	{	
		int	short_index;
		short * data_pointer;
				
		if (!file_is_open) return;
		if (pause) return;			// forget this buffer
		
		short_index = writIdx % LINLEN;	// modulo LINWAVBUF_SIZE
			
		data_pointer = & wave_lin_buf [ short_index ];	
	
		{	
			FromFloat16_Stereo ( Lsource , data_pointer    , FRAMES );		// Left buffer  , Lsource is a static adresse here
			FromFloat16_Stereo ( Rsource , data_pointer +1 , FRAMES );		// Right buffer , Rsource is a static adresse here
		}	
		
		writIdx += (2* FRAMES ) ;	// stereo track	
	
		written_frames += FRAMES;
		if ( written_frames >= frames_per_sec )
		{
			written_frames -= frames_per_sec;
			INC_s();
		}
									
	}	// ASIO_Trace_Stereo_Wave()
	
#endif	// #if ASIO_ENVIRONMENT 

	
};	// class RECORD_WAVE_class


//------  at start from .exe here is the static storage, is automaticly deleted after end of process.  -->

static RECORD_WAVE_class RF_RECWAV (RF_PREFIX);			// init with prefix filename string!
static RECORD_WAVE_class AF_RECWAV (AF_PREFIX);



//------ Interface functions for main.c ( make a wrapper to cpp-classes) : -->>
#ifdef __cplusplus
extern "C" {
#endif

//--------------------------------------------------------------------------------------
// Proper variables for main.c, but hidden for main.c for handling easy consistency here.
// At start from .exe we assume, that no wave file trace is activated.
// It is only activated by pressing on the keyboard: 
// 'I' or 'i' for Input-Wave, or/and  
// 'O' or 'o' for Output-Wave.
// These commands perform the storage of Input/Output data to be stored on the Harddisk.
// In case of error ( or medium full) the trace file is automatically closed.
//--------------------------------------------------------------------------------------

int	Record_Status_RF(void)
{
	return RF_RECWAV.file_is_open;	
}

int	Record_Status_AF(void)
{
	return AF_RECWAV.file_is_open;	
}

void Toggle_Record_Switch_RF (int audiochannels)	// in GUI called
{
	if (RF_RECWAV.file_is_open) 
	{	RF_RECWAV.file_close_request = 1;
		
	}
	else
	{
		RF_RECWAV.wav_Fs = Get_SampleRate_IN();
		RF_RECWAV.nAudio_channels = audiochannels;
		RF_RECWAV.file_open_request = 1;
		
	}
}	// Toggle_Record_Switch_RF()


void Toggle_Record_Switch_AF(int audiochannels)	// in GUI called
{
	if (AF_RECWAV.file_is_open) 
	{	AF_RECWAV.file_close_request = 1;
		
	}
	else
	{
		AF_RECWAV.wav_Fs = Get_SampleRate_OUT();
		AF_RECWAV.nAudio_channels = audiochannels;
		AF_RECWAV.file_open_request = 1;
			
	}	
	
}	// Toggle_Record_Switch_AF()



void Record_WaveStereo_RF ( float* src, int samplecount)	// fast process, in Audio Callback
{
	if (RF_RECWAV.FRAMES == 0) RF_RECWAV.Set_Frames ( Get_framelength() );
	 
	RF_RECWAV.Trace_Stereo_Wave_Interleaved_In ( src , samplecount);
			
}

void Record_WaveStereo_AF( float* src, int samplecount)	// fast process, in Audio Callback
{
	if (AF_RECWAV.FRAMES == 0) AF_RECWAV.Set_Frames ( Get_framelength() );
	
	AF_RECWAV.Trace_Stereo_Wave_Interleaved_Out ( src, samplecount );	
}


void Poll_Recorder_toDisk  (void)	// slow process, periodically in GUI called
{
	RF_RECWAV.Wave_to_Disk();
	AF_RECWAV.Wave_to_Disk();		
}

char* Get_Record_TapeTime_RF(void)
{
	return RF_RECWAV.Get_Tapetime();
}
	
char* Get_Record_TapeTime_AF(void)
{
	return AF_RECWAV.Get_Tapetime();
}



//---------------------------------------------------------------------------
#ifdef __cplusplus
}
#endif
//---------------------------------------------------------------------------

