// Tape.h: interface for the CTape class.
//


#pragma once

#include "LockVarType.h"
#include "BKSound_Defines.h"
#include <mmsystem.h>

//"RIFF"
#define RIFF_TAG 0x46464952
// "fmt "
#define FMT_TAG 0x20746d66
// "data"
#define DATA_TAG 0x61746164
// "WAVE"
#define WAVE_TAG 0x45564157

#pragma pack(push)
#pragma pack(1) // envelope period (envT, R13-R14) has invalid word alignment
struct TAPE_FILE_INFO
{
	DWORD               start_tuning;
	DWORD               synchro_start;
	DWORD               marker1;
	DWORD               synchro_header;
	DWORD               header;
	WORD                address;
	WORD                length;
	BYTE                name[16];
	DWORD               marker2;
	DWORD               synchro_data;
	DWORD               data;
	WORD                crc;
	DWORD               synchro6;
	DWORD               end_tuning;
	DWORD               end;
};
#pragma pack(pop)

struct WaveHeader
{
	DWORD               riffTag;
	DWORD               size;
	DWORD               waveTag;
	DWORD               fmtTag;
	DWORD               fmtSize;
};

struct DataHeader
{
	DWORD               dataTag;
	DWORD               dataSize;
};

#define MAX_TABLE_SIZE 65000
#define RECORD_BUFFER (4 * 1024 * 1024)
#define RECORD_GROW (2 * 1024 * 1024)
#define BAD_CODE 65535
#define RESET_CODE 256
#define PREFIX 256
#define CODE 257
#define BAD_LENGTH -1

class CTape
{
		int             m_nWorkingSSR;      //рабочая частота дискретизации
		int             m_nWorkingChannels; //рабочее количество каналов, битность всегда 16
		int             m_nSampleSize;      //размер сэмпла. для текущих значений
		double          m_dWorkingResCoeff; //коэффициент для ресемплинга из 44100 в текущую частоту

		LockVarType     m_lockPlay;
		SAMPLE_TYPE    *m_pWave;            //собственно массив wave.
		int             m_nWaveLength;      //размер массива m_pWave в сэмплах
		int             m_nPlayPos;         //текущая позиция воспроизведения в сэмплах

		LockVarType     m_lockRecord;       //флаг записи
		SAMPLE_TYPE    *m_pRecord;          //массив, куда записывается звук
		int             m_nRecordPos;       //текущая позиция в массиве в сэмплах
		int             m_nRecordLength;    //длина массива в сэмплах

		bool            m_bAutoBeginRecord;
		bool            m_bAutoEndRecord;

		BYTE           *m_pBin;             //массив бинарных данных, в которые преобразуются wav и tap
		int             m_nPos;
		SAMPLE_TYPE     m_nAverage;
		int             m_nLastLen;
		int             m_nAvgLength;

		WORD           *m_pScanTable[MAX_TABLE_SIZE]; //массив для упаковки/распаковки
		int             m_nScanTableSize;
		//функции упаковки/распаковки wav в tap
		void            InitTables();
		void            ClearTables();
		int             TableLookup_Fast(BYTE *pPackBits, WORD prefix, int end_pos);
		void            AddWord_Fast(BYTE *pPackBits, WORD prefix, int end_pos);
		bool            UnpackWord(BYTE *pPackBits, int &nPackBitsLength, WORD code);
		//функции преобразования модулированного wav в bin
		bool            FindTuning(int nLength, DWORD &wave_pos, int &wave_length);
		bool            FindSyncro();
		bool            FindSyncro6();
		int             CalcImpLength(SAMPLE_TYPE *pWave, int &pos, int length);
		inline SAMPLE_TYPE GetCurrentSampleMono(register SAMPLE_TYPE *pWave, register int pos);
		void            SetCurrentSampleMono(SAMPLE_TYPE *pWave, int pos, SAMPLE_TYPE sample);
		int             DefineLength(int length);
		bool            ReadBit(bool &bBit);
		bool            ReadByte(BYTE &byte);
		//функции преобразования bin в модулированный wav
		bool            SaveTuning(int length);
		bool            SaveSyncro();
		bool            SaveSyncro6();
		bool            SaveImp(int size);
		bool            SaveBit(bool bBit);
		bool            SaveByte(BYTE byte);

		int             ConvertSamples(WAVEFORMATEX wfx_in, BYTE *inBuf, int nBufSize, int nDataLen);
		void            ResampleBuffer(int nSrcSSR, int nDstSSR);
		bool            FindRecordBegin(int buffLength);
		bool            FindRecordEnd();
		void            CalculateAverage();

	public:
		CTape();
		virtual ~CTape();
		void            SetWaveParam(int nWorkingSSR = DEFAULT_SOUND_SAMPLE_RATE, int nWorkingChn = BUFFER_CHANNELS);
		int             GetWorkingSSR()
		{
			return m_nWorkingSSR;
		}
		int             GetWorkingChannels()
		{
			return m_nWorkingChannels;
		}
		// Wave pack/unpack methods
		void            PackBits(BYTE *pPackBits, int nPackBitsLength);
		void            UnpackBits(BYTE *pPackBits, int nPackBitsLength);

		int             PackLZW_Fast(BYTE *pPackBits, int nPackBitsLength, WORD *pPackLZW);
		void            UnpackLZW_Fast(BYTE *pPackBits, int nPackBitsLength, WORD *pPackLZW, int nPackLZWLength);

		// Wave read/write methods
		bool            SetWaveFile(TAPE_FILE_INFO *pTfi);
		bool            GetWaveFile(TAPE_FILE_INFO *pTfi, bool bHeaderOnly = false);
		bool            CalcCRC(TAPE_FILE_INFO *pTfi, bool bCheck);

		bool            LoadWaveFile(const CString &strPath);
		bool            SaveWaveFile(const CString &strPath);

		bool            LoadBinFile(const CString &strPath, TAPE_FILE_INFO *pTfi);
		bool            SaveBinFile(const CString &strPath, TAPE_FILE_INFO *pTfi);

		bool            LoadMSFFile(const CString &strPath);
		bool            SaveMSFFile(const CString &strPath);

		bool            LoadTmpFile(const CString &strPath);

		// Wave buffer methods
		bool            AllocWaveBuffer(int nSize);
		bool            LoadBuffer(BYTE *pBuff, int nSize);
		inline int      GetWaveLength()
		{
			return m_nWaveLength;
		}
		inline BYTE    *GetWaveBuffer()
		{
			return reinterpret_cast<BYTE *>(m_pWave);
		}

		// Playing methods
		int             GetPlayWavePos();
		void            SetPlayWavePos(int play_pos);
		void            PlayWaveGetBuffer(BYTE *pBuff, int buffLength);

		void            StartPlay();
		void            StopPlay();
		bool            IsPlaying();
		void            StartRecord(bool bAutoBeginRecord, bool bAutoEndRecord);
		void            RecordWaveGetBuffer(BYTE *pBuff, int buffLength);
		void            StopRecord();
		bool            IsRecording();
};
