/*************************************************
*           BK Emulator v3.8 for Windows         *
*************************************************/

/*
File: Tape.cpp
Version: 1.0.0
Written by: Yuriy Kalmykov <kalmykov@stoik.com>
    Copyright (c) 2002-2004 Yuriy Kalmykov

    BK Emulator is a program emulated hardware environment for running
code for BK 0010(01) in different configurations.

    This code may be used in compiled form in any way you desire.
This file or it's parts can't be redistributed without the authors
written consent, but can be modified for your private needs.
    Providing that this notice and the authors name and all copyright
notices remains intact.

    Please, an email me to know how you are using it and where. You can
ask me for any information about this below code or any attendant
knowledge.

    This file is provided "as is" with no expressed or implied warranty.
The author accepts no liability for any damage or loss of business that
this product may cause.
*/


// Tape.cpp: implementation of the CTape class.
//


#include "stdafx.h"
#include "Globals.h"
#include "MSFManager.h"
#include "Tape.h"


#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction


CTape::CTape() :
	m_pWave(NULL),
	m_pPackBits(NULL),
	m_pPackLZW(NULL),
	m_nWaveLength(0),
	m_pRecord(NULL),
	m_bAutoBeginRecord(TRUE),
	m_bAutoEndRecord(TRUE),
	m_pBin(NULL)
{
	// Initialize table pointers
	for (int y = 0; y < MAX_TABLE_SIZE; ++y)
	{
		m_pScanTable[y] = NULL;
	}
}

CTape::~CTape()
{
	SAFE_DELETE_ARRAY(m_pWave);
	SAFE_DELETE_ARRAY(m_pPackBits);
	SAFE_DELETE_ARRAY(m_pPackLZW);
	SAFE_DELETE_ARRAY(m_pBin);
	SAFE_DELETE_ARRAY(m_pRecord);
	//очищаем таблицы
	ClearTables();
}


BOOL CTape::LoadWaveFile(const CString &strPath)
{
	CFile waveFile;

	if (!waveFile.Open(strPath, CFile::modeRead))
	{
		return FALSE;
	}

	WaveHeader waveHeader;
	WAVEFORMATEX wfx;
	DataHeader dataHeader;

	if (waveFile.Read(&waveHeader, sizeof(WaveHeader)) != sizeof(WaveHeader))
	{
		return FALSE;
	}

	if (waveHeader.riffTag != 0x46464952 || waveHeader.fmtTag != 0x20746d66)
	{
		return FALSE;
	}

	if (waveFile.Read(&wfx, waveHeader.fmtSize) != waveHeader.fmtSize)
	{
		return FALSE;
	}

	wfx.cbSize = 0;

	if (wfx.wFormatTag != WAVE_FORMAT_PCM)
	{
		return FALSE;
	}

	while (1)
	{
		if (waveFile.Read(&dataHeader, sizeof(DataHeader)) != sizeof(DataHeader))
		{
			return FALSE;
		}

		if (dataHeader.dataTag == 0x61746164)
		{
			break;
		}

		waveFile.Seek(dataHeader.dataSize, SEEK_CUR);
	}

	/*
	    тут нужна конвертация из любого формата wav WAVE_FORMAT_PCM, в 44100, 16 бит, 2 канала

	для WAVE_FORMAT_PCM данные могут быть только 8 или 16 бит,
	каналов 1 или 2 по стандарту, но никто не мешает сделать и больше
	частота дискретизации 8000, 11025, 22050, 44100, но никто не мешает сделать и 48000 и 96000
	*/
	//если звук изначально в нужном нам формате, то просто читаем его как обычно
	if (
	    (wfx.nSamplesPerSec == g_Config.m_nSoundSampleRate) &&
	    (wfx.nChannels == BUFFER_CHANNELS) &&
	    (wfx.wBitsPerSample == BUFFER_BPS)
	)
	{
		AllocWaveBuffer(dataHeader.dataSize);
		waveFile.Read(m_pWave, dataHeader.dataSize);
	}
	else //иначе будем конвертировать
	{
		//определим количество сэмплов в исходном сигнале
		int nSrcSamples = dataHeader.dataSize / wfx.nBlockAlign;
		//определим количество сэмплов в результирующем сигнале
		int nDstSamples = nSrcSamples * (g_Config.m_nSoundSampleRate / wfx.nSamplesPerSec);
		//выделяем буфер под звук
		AllocWaveBuffer(nDstSamples * SAMPLE_LENGTH);
		BYTE *inBuf8 = new BYTE[wfx.nAvgBytesPerSec];
		short *inBuf16 = reinterpret_cast<short *>(inBuf8);
		int readed;
		int channel;
		short data16;
		int ss, ds = 0, pds, cpds, dst_pos;

		switch (wfx.wBitsPerSample)
		{
			case 8:
				dst_pos = 0;

				//пока что-то читается, читаем
				while ((readed = waveFile.Read(inBuf8, wfx.nAvgBytesPerSec)) > 0)
				{
					pds = -1;

					//для каждого прочитавшегося сэмпла
					for (ss = 0; ss < readed / wfx.nBlockAlign; ss++)
					{
						//при необходимости делаем ресэмплинг
						ds = ss * (g_Config.m_nSoundSampleRate / wfx.nSamplesPerSec);
						//для каждого канала в сэмпле
						channel = 0;

						if (wfx.nChannels < BUFFER_CHANNELS)
						{
							//если каналов меньше BUFFER_CHANNELS
							data16 = (short)inBuf8[ss] << 7;

							//то один канал продублируем в BUFFER_CHANNELS
							while (channel < BUFFER_CHANNELS)
							{
								cpds = pds;

								if (cpds < 0)
								{
									m_pWave[(dst_pos + ds) * BUFFER_CHANNELS + channel] = data16;
								}
								else
								{
									while (cpds <= ds)
									{
										m_pWave[(dst_pos + cpds) * BUFFER_CHANNELS + channel] = data16;
										cpds++;
									}
								}

								channel++;
							}
						}
						else
						{
							//если каналов больше или равно BUFFER_CHANNELS
							//пройдёмся по всем каналам
							while (channel < wfx.nChannels)
							{
								cpds = pds;

								//но возьмём оттуда только первые два
								if (channel < BUFFER_CHANNELS)
								{
									data16 = (short) inBuf8[ss * wfx.nChannels + channel] << 7;

									if (cpds < 0)
									{
										m_pWave[(dst_pos + ds) * BUFFER_CHANNELS + channel] = data16;
									}
									else
									{
										while (cpds <= ds)
										{
											m_pWave[(dst_pos + cpds) * BUFFER_CHANNELS + channel] = data16;
											cpds++;
										}
									}
								}

								channel++;
							}
						}

						pds = ds + 1;
					}

					dst_pos += ds;
				}

				break;

			case 16:
				dst_pos = 0;

				//пока что-то читается, читаем
				while ((readed = waveFile.Read(inBuf16, wfx.nAvgBytesPerSec)) > 0)
				{
					pds = -1;

					//для каждого прочитавшегося сэмпла
					for (ss = 0; ss < readed / wfx.nBlockAlign; ss++)
					{
						//при необходимости делаем ресэмплинг
						ds = ss * (g_Config.m_nSoundSampleRate / wfx.nSamplesPerSec);
						//для каждого канала в сэмпле
						channel = 0;

						if (wfx.nChannels < BUFFER_CHANNELS)
						{
							//если каналов меньше BUFFER_CHANNELS
							data16 = inBuf16[ss];

							//то один канал продублируем в BUFFER_CHANNELS
							while (channel < BUFFER_CHANNELS)
							{
								cpds = pds;

								if (cpds < 0)
								{
									m_pWave[(dst_pos + ds) * BUFFER_CHANNELS + channel] = data16;
								}
								else
								{
									while (cpds <= ds)
									{
										m_pWave[(dst_pos + cpds) * BUFFER_CHANNELS + channel] = data16;
										cpds++;
									}
								}

								channel++;
							}
						}
						else
						{
							//если каналов больше или равно BUFFER_CHANNELS
							//пройдёмся по всем каналам
							while (channel < wfx.nChannels)
							{
								cpds = pds;

								//но возьмём оттуда только первые два
								if (channel < BUFFER_CHANNELS)
								{
									data16 = inBuf16[ss * wfx.nChannels + channel];

									if (cpds < 0)
									{
										m_pWave[(dst_pos + ds) * BUFFER_CHANNELS + channel] = data16;
									}
									else
									{
										while (cpds <= ds)
										{
											m_pWave[(dst_pos + cpds) * BUFFER_CHANNELS + channel] = data16;
											cpds++;
										}
									}
								}

								channel++;
							}
						}

						pds = ds + 1;
					}

					dst_pos += ds;
				}

				break;

			default: //что-то не так
				delete [] inBuf8;
				return FALSE;
		}

		delete [] inBuf8;
	}

	waveFile.Close();
//  CString strName = g_Config.GetCurrentPath() + _T("TestWave.wav");
//  SaveWaveFile(strName);
	// Calculate average
	CalculateAverage(m_nWaveLength);
	return TRUE;
}

void CTape::CalculateAverage(int dwLength)
{
	/*
	расчёт среднего уровня. средним будем считать усреднённое значение амплитуды
	*/
	double amp_max = 0.0;

	for (int s = 0; s < dwLength; ++s)
	{
		amp_max += (double)GetCurrentSampleMono(m_pWave, s);
	}

	m_nAverage = (SAMPLE_TYPE)(amp_max / dwLength) + 1;
}


BOOL CTape::LoadTmpFile(const CString &strPath)
{
	CFileStatus fs;

	if (!CFile::GetStatus(strPath, fs))
	{
		return FALSE;
	}

	CFile tmpFile;

	if (!tmpFile.Open(strPath, CFile::modeRead))
	{
		return FALSE;
	}

	AllocWaveBuffer((int)fs.m_size);
	tmpFile.Read(m_pWave, (UINT)fs.m_size);
	tmpFile.Close();
	// Calculate average
	CalculateAverage(m_nWaveLength);
	return TRUE;
}


BOOL CTape::SaveWaveFile(const CString &strPath)
{
	CFile waveFile;

	if (!waveFile.Open(strPath, CFile::modeCreate | CFile::modeReadWrite))
	{
		return FALSE;
	}

	WaveHeader waveHeader;
	DataHeader dataHeader;
	WAVEFORMATEX wfx;
	wfx.wFormatTag = WAVE_FORMAT_PCM;
	wfx.nSamplesPerSec = g_Config.m_nSoundSampleRate;
	wfx.nChannels = BUFFER_CHANNELS;
	wfx.wBitsPerSample = BUFFER_BPS;
	wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels; //количество байтов на сэмпл
	wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; //сколько байтов в секунду проиграется
	wfx.cbSize = 0;
	dataHeader.dataTag = 0x61746164;
	dataHeader.dataSize = m_nWaveLength * SAMPLE_LENGTH;
	waveHeader.riffTag = 0x46464952;
	waveHeader.size = sizeof(WaveHeader) + sizeof(WAVEFORMATEX) + sizeof(DataHeader) + dataHeader.dataSize;
	waveHeader.waveTag = 0x45564157;
	waveHeader.fmtTag = 0x20746d66;
	waveHeader.fmtSize = sizeof(WAVEFORMATEX); // 18;
	waveFile.Write(&waveHeader, sizeof(WaveHeader));
	waveFile.Write(&wfx, waveHeader.fmtSize);
	waveFile.Write(&dataHeader, sizeof(DataHeader));
	waveFile.Write(m_pWave, (UINT)dataHeader.dataSize);
	return TRUE;
}


int CTape::PackBits()
{
	int pos = 0; // Текущая позиция упакованного байта
	BYTE mask = 1; // Циклическая битовая маска
	ZeroMemory(m_pPackBits, m_nPackBitsLength);  //обнуляем массив, чтобы не выставлять вместе с 1 ещё и 0

	for (int i = 0; i < m_nWaveLength; ++i)
	{
		// Берём текущий сэмпл
		if (GetCurrentSampleMono(m_pWave, i) >= m_nAverage) // Если больше нуля, то выставляем 1, иначе пропускаем
		{
			m_pPackBits[pos] |= mask;
		}

		mask <<= 1; //сдвигаем маску на 1 влево

		if (!mask) //если маска опустела,
		{
			// то байт заполнился, надо дальше заново установить маску и переходитьк следующему байту
			mask = 1;
			pos++;
		}
	}

	return m_nPackBitsLength; // возвращаем сжатый размер.
	//непонятна логика. Зачем возвращать этот размер, если он заранее рассчитывается и известен
}


void CTape::UnpackBits()
{
	// Выделяем память для распакованных данных, тут уже должно быть 2 канала
	AllocWaveBuffer((m_nPackBitsLength << 3) * SAMPLE_LENGTH);
	m_nWaveLength = 0;

	for (int i = 0; i < m_nPackBitsLength; ++i)
	{
		BYTE mask = 1;

		// Разжиммаем каждый байт в 8 сэмплов
		for (int b = 0; b < 8; ++b)
		{
			//из бита 1 делаем байт MAX_SAMPLE, из бита 0 делаем байт MIN_SAMPLE
			SAMPLE_TYPE sample = (m_pPackBits[i] & mask) ? MAX_SAMPLE : MIN_SAMPLE;
			m_pWave[m_nWaveLength * BUFFER_CHANNELS] = sample;
			m_pWave[m_nWaveLength * BUFFER_CHANNELS + 1] = sample;
			mask <<= 1; //сдвигаем маску
			m_nWaveLength++;
		}
	}

	CalculateAverage(m_nWaveLength);
}


int CTape::PackLZW_Fast()
{
	/*
	m_pPackBits[0] = 'A';
	m_pPackBits[1] = 'B';
	m_pPackBits[2] = 'C';
	m_pPackBits[3] = 'A';
	m_pPackBits[4] = 'B';
	m_pPackBits[5] = 'C';
	m_pPackBits[6] = 'A';
	m_pPackBits[7] = 'B';
	m_pPackBits[8] = 'C';
	m_pPackBits[9] = 'A';
	m_pPackBits[10] = 'B';
	m_pPackBits[11] = 'C';
	m_pPackBits[12] = 'A';
	m_pPackBits[13] = 'B';
	m_pPackBits[14] = 'C';
	m_pPackBits[15] = 'A';
	m_pPackBits[16] = 'B';
	m_pPackBits[17] = 'C';
	m_pPackBits[18] = 'A';
	m_pPackBits[19] = 'B';
	m_pPackBits[20] = 'C';
	m_pPackBits[21] = 'A';
	m_pPackBits[22] = 'B';
	m_pPackBits[23] = 'C';
	m_pPackBits[24] = 'A';
	m_pPackBits[25] = 'B';
	m_pPackBits[26] = 'C';
	m_pPackBits[27] = 'A';
	m_pPackBits[28] = 'B';
	m_pPackBits[29] = 'C';
	*/
	// Инициализируем таблицы
	InitTables();
	m_nPackLZWLength = 0;
	WORD last_code = BAD_CODE;
	int read_pos;

	for (read_pos = 0; read_pos < m_nPackBitsLength; ++read_pos)
	{
		int code = TableLookup_Fast(last_code, read_pos);  // Берём текущую последовательность

		if (code == BAD_CODE || read_pos == (m_nPackBitsLength - 1))
		{
			// Если нет в таблице, или уже конец
			m_pPackLZW[m_nPackLZWLength++] = last_code;  // Упаковка: последовательность - 1 байт
			AddWord_Fast(last_code, read_pos);  // Добавление: последовательность в таблицу
			last_code = m_pPackBits[read_pos];
		}
		else
		{
			last_code = code;
		}
	}

	m_pPackLZW[m_nPackLZWLength++] = m_pPackBits[read_pos - 1]; // Пакуем последний байт как есть
	return m_nPackLZWLength; // Возвращаем длину сжатого массива
}


void CTape::UnpackLZW_Fast()
{
	// Инициализируем таблицы
	InitTables();
	//Очищаем будущий распакованный массив. я так понимаю, что m_nPackBitsLength - это размер будущих распакованных данных
	ZeroMemory(m_pPackBits, m_nPackBitsLength);
	m_nPackBitsLength = 0;
	int unpack_pos = 0;
	/*
	m_pPackLZW[0] = 'A';
	m_pPackLZW[1] = 'B';
	m_pPackLZW[2] = 'C';
	m_pPackLZW[3] = 257;
	m_pPackLZW[4] = 259;
	m_pPackLZW[5] = 258;
	m_pPackLZW[6] = 260;
	m_pPackLZW[7] = 263;
	m_pPackLZW[8] = 262;
	m_pPackLZW[9] = 265;
	m_pPackLZW[10] = 261;
	m_pPackLZW[11] = 267;
	*/
	WORD last_code = BAD_CODE;

	for (int read_pos = 0; read_pos < m_nPackLZWLength; ++read_pos)
	{
		if (m_pPackLZW[read_pos] < 256) // Если текущий код < 256, распакуем его как есть
		{
			m_pPackBits[m_nPackBitsLength++] = (BYTE)m_pPackLZW[read_pos];
		}
		else
		{
			// Иначе создаём таблику от текущей позиции до позиции этого кода
			while (unpack_pos < m_nPackBitsLength)
			{
				int code = TableLookup_Fast(last_code, unpack_pos);

				if (code == BAD_CODE)
				{
					// Если нет текущей последовательности в таблице, добавим её
					AddWord_Fast(last_code, unpack_pos);
					last_code = m_pPackBits[unpack_pos];
				}
				else
				{
					last_code = code;
				}

				unpack_pos++;
			}

			if (!UnpackWord(m_pPackLZW[read_pos]))  // Пробуем распаковать текущий код
			{
				// Если нет в таблице (случай XиX)
				int first_pos = m_nPackBitsLength;
				UnpackWord(last_code);  // Распаковываем последний известный код
				m_pPackBits[m_nPackBitsLength++] = TableLookup_Fast(BAD_CODE, first_pos);  // Распаковываем последний известный код первого байта
				AddWord_Fast(last_code, unpack_pos);  // Добавление: Xпоследовательность + X в таблицу
				last_code = BAD_CODE;
			}
		}
	}
}


void CTape::InitTables()
{
	for (int y = 0; y < MAX_TABLE_SIZE; ++y)
	{
		//т.к. эту функцию вызываем много раз, то выделять память надо только в первый раз
		if (!m_pScanTable[y])
		{
			m_pScanTable[y] = new WORD[258];
		}

		//все остальные разы - только заполнять таблицы начальными данными
		for (int x = 0; x < 258; x++)
		{
			m_pScanTable[y][x] = BAD_CODE;
		}
	}

	for (WORD y = 0; y < 256; ++y)
	{
		m_pScanTable[y][CODE] = y;
	}

	m_nScanTableSize = 257;
}


void CTape::ClearTables()
{
	for (int y = 0; y < MAX_TABLE_SIZE; ++y)
	{
		SAFE_DELETE_ARRAY(m_pScanTable[y]);
	}
}


int CTape::TableLookup_Fast(WORD prefix, int end_pos)
{
	if (prefix == BAD_CODE)
	{
		return m_pPackBits[end_pos];
	}

	return m_pScanTable[prefix][m_pPackBits[end_pos]];
}


void CTape::AddWord_Fast(WORD prefix, int end_pos)
{
	//если размер таблицы превысил все допустимые рамки
	if (m_nScanTableSize >= MAX_TABLE_SIZE)
	{
		//обнулим его, и начнем заполнять сначала
		InitTables();
		return;
	}

	m_pScanTable[prefix][m_pPackBits[end_pos]] = m_nScanTableSize;
	m_pScanTable[m_nScanTableSize][PREFIX] = prefix;
	m_pScanTable[m_nScanTableSize][CODE] = m_pPackBits[end_pos];
	m_nScanTableSize++;
}


BOOL CTape::UnpackWord(int code)
{
	if (code == BAD_CODE)
	{
		return FALSE;
	}

	if (code < 256)
	{
		m_pPackBits[m_nPackBitsLength++] = code;
		return TRUE;
	}

	if (!UnpackWord(m_pScanTable[code][PREFIX]))
	{
		return FALSE;
	}

	m_pPackBits[m_nPackBitsLength++] = (BYTE)m_pScanTable[code][CODE];
	return TRUE;
}


BOOL CTape::GetWaveFile(TAPE_FILE_INFO *pTfi, BOOL bHeaderOnly)
{
	if (!m_pWave)
	{
		return FALSE;
	}

	m_nPos = 0;
	m_nLastLen = 0;
	memset(pTfi, 255, sizeof(TAPE_FILE_INFO));
	int nLength = 0;

	if (FindTuning(1024, pTfi->start_tuning, nLength) == BAD_LENGTH)
	{
		return FALSE;
	}

	pTfi->synchro_start = m_nPos;

	if (!FindSyncro())
	{
		return FALSE;
	}

	if (FindTuning(7, pTfi->marker1, nLength) == BAD_LENGTH)
	{
		return FALSE;
	}

	pTfi->synchro_header = m_nPos;

	if (!FindSyncro())
	{
		return FALSE;
	}

	pTfi->header = m_nPos;

	for (int i = 0; i < 20; i++)
	{
		if (!ReadByte(((BYTE *)&pTfi->address)[i]))
		{
			return FALSE;
		}
	}

	if (FindTuning(7, pTfi->marker2, nLength) == BAD_LENGTH)
	{
		return FALSE;
	}

	pTfi->synchro_data = m_nPos;

	if (!FindSyncro())
	{
		return FALSE;
	}

	if (bHeaderOnly)
	{
		return TRUE;
	}

	SAFE_DELETE_ARRAY(m_pBin);
	m_pBin = new BYTE[pTfi->length];
	pTfi->data = m_nPos;

	for (int i = 0; i < pTfi->length; ++i)
	{
		if (!ReadByte(m_pBin[i]))
		{
			return FALSE;
		}
	}

	if (!ReadByte(((BYTE *)&pTfi->crc)[0]))
	{
		return FALSE;
	}

	if (!ReadByte(((BYTE *)&pTfi->crc)[1]))
	{
		return FALSE;
	}

	pTfi->synchro6 = m_nPos;

	if (!FindSyncro6())
	{
		return FALSE;
	}

	FindTuning(128, pTfi->end_tuning, nLength);
	pTfi->end = pTfi->end_tuning + nLength;
	return TRUE;
}


BOOL CTape::FindTuning(int nLength, DWORD &wave_pos, int &wave_length)
{
	int syncro_pos = BAD_LENGTH;
	int last_pos = m_nPos;
	int imp_num = 0;
	m_nAvgLength = 0;
	int begin_pos = m_nPos;
	BOOL bFound = FALSE;

	while (1)
	{
		last_pos = m_nPos;
		int len = CalcImpLength(m_pWave, m_nPos, m_nWaveLength);

		if (len == BAD_LENGTH)
		{
			m_nPos = last_pos;
			return FALSE;
		}

		if ((m_nLastLen - len) * (m_nLastLen - len) <= 3 * 3)
		{
			m_nAvgLength += len;
			imp_num++;

			if (imp_num >= nLength && !bFound)
			{
				syncro_pos = begin_pos;
				bFound = TRUE;
			}
		}
		else if (!bFound)
		{
			m_nAvgLength = 0;
			imp_num = 0;
			begin_pos = m_nPos;
		}
		else
		{
			break;
		}

		m_nLastLen = len;
		//TRACE ("\nWave pos %06i, Start = %i, Len = %i, Imp_Num = %i", m_nPos, begin_pos, len, imp_num);
		//Sleep (1);
	}

	m_nPos = last_pos;
	wave_pos = syncro_pos;
	wave_length = m_nPos - syncro_pos;
	m_nAvgLength = (m_nAvgLength + imp_num / 2) / imp_num;
	/*
	for (int i = 0; i < 100; ++i)
	{
	    int len = CalcImpLength (m_pWave, m_nPos, m_nWaveLength);
	    TRACE ("\nAfter Wave pos %06i, Start = %i, Len = %i, Imp_Num = %i", m_nPos, begin_pos, len, imp_num);
	    Sleep (1);
	}
	/*/
	return TRUE;
}


BOOL CTape::FindSyncro()
{
	int size = 0;
	int nLength = 0;

	while (1)
	{
		nLength = CalcImpLength(m_pWave, m_nPos, m_nWaveLength);

		if (nLength == BAD_LENGTH)
		{
			return FALSE;
		}

		size = DefineLength(nLength);

		if (size >= 3)
		{
			break;
		}
	}

	int nSyncLen = 2;

	while (nSyncLen > 0)
	{
		nLength = CalcImpLength(m_pWave, m_nPos, m_nWaveLength);

		if (nLength == BAD_LENGTH)
		{
			return FALSE;
		}

		size = DefineLength(nLength);

		if (size == nSyncLen)
		{
			nSyncLen--;
		}
	}

	return TRUE;
}


BOOL CTape::FindSyncro6()
{
	int nLength = CalcImpLength(m_pWave, m_nPos, m_nWaveLength);

	if (nLength == BAD_LENGTH)
	{
		return FALSE;
	}

	int size = DefineLength(nLength);

	if (size < 6)
	{
		return FALSE;
	}

	return TRUE;
}


BOOL CTape::ReadBit(BOOL &bBit)
{
	int length0 = CalcImpLength(m_pWave, m_nPos, m_nWaveLength);

	if (length0 == BAD_LENGTH)
	{
		return FALSE;
	}

	int size0 = DefineLength(length0);
	int length1 = CalcImpLength(m_pWave, m_nPos, m_nWaveLength);

	if (length1 == BAD_LENGTH)
	{
		return FALSE;
	}

	int size1 = DefineLength(length1);

	//if (size1 != 1)
	//  return FALSE;
	if (size0 == 1)
	{
		bBit = FALSE;
	}
	else if (size0 == 2)
	{
		bBit = TRUE;
	}
	else
	{
		return FALSE;
	}

	return TRUE;
}


BOOL CTape::ReadByte(BYTE &byte)
{
	BYTE mask = 1;
	byte = 0;
	BOOL bBit;

	for (int i = 0; i < 8; ++i)
	{
		if (!ReadBit(bBit))
		{
			return FALSE;
		}

		if (bBit)
		{
			byte |= mask;
		}

		mask <<= 1;
	}

	return TRUE;
}

//на входе pos - позиция в сэмплах
SAMPLE_TYPE CTape::GetCurrentSampleMono(SAMPLE_TYPE *pWave, int pos)
{
	return max(pWave[pos * BUFFER_CHANNELS], pWave[pos * BUFFER_CHANNELS + 1]);
}

//на входе pos - позиция в сэмплах
void SetCurrentSampleMono(SAMPLE_TYPE *pWave, int pos, SAMPLE_TYPE sample)
{
	pWave[pos * BUFFER_CHANNELS] = sample;
	pWave[pos * BUFFER_CHANNELS + 1] = sample;
}

//////////////////////////////////////////////////////////////////////
//pos - позиция в сэмплах
//nLength - длина в сэмплах
int CTape::CalcImpLength(SAMPLE_TYPE *pWave, int &pos, int nLength)
{
	if (pos >= nLength)
	{
		return BAD_LENGTH;
	}

	// Пропускаем инверсные импульсы
	while (GetCurrentSampleMono(pWave, pos) < m_nAverage)
	{
		if (pos >= nLength)
		{
			return BAD_LENGTH;
		}

		pos++;
	}

	int len_1 = 0;

	// Считаем количество единиц
	while (GetCurrentSampleMono(pWave, pos) >= m_nAverage)
	{
		if (pos >= nLength)
		{
			return BAD_LENGTH;
		}

		len_1++;
		pos++;
	}

	int len_0 = 0;

	// Считаем количество нулей
	while (GetCurrentSampleMono(pWave, pos) < m_nAverage)
	{
		if (pos >= nLength)
		{
			return BAD_LENGTH;
		}

		len_0++;
		pos++;
	}

	return len_1 + len_0;
}


int CTape::DefineLength(int nLength)
{
	return (nLength + m_nAvgLength / 2) / m_nAvgLength;
}


BOOL CTape::CalcCRC(TAPE_FILE_INFO *pTfi, BOOL bCheck)
{
	if (!m_pBin)
	{
		return FALSE;
	}

	int crc = 0;

	for (int i = 0; i < pTfi->length; ++i)
	{
		crc += m_pBin[i];

		if (crc & 0xFFFF0000)
		{
			//если случился перенос в 17 разряд (т.е. бит С для word)
			crc &= 0x0000FFFF; //его обнулим
			crc++; //но прибавим к сумме
		}
	}

	if (bCheck)
	{
		if (crc != pTfi->crc)
		{
			return FALSE;
		}
	}
	else
	{
		pTfi->crc = (WORD)crc;
	}

	return TRUE;
}


BOOL CTape::LoadBinFile(const CString &strPath, TAPE_FILE_INFO *pTfi)
{
	CFile binFile;

	if (!binFile.Open(strPath, CFile::modeRead))
	{
		return FALSE;
	}

	//читаем первое слово - адрес загрузки
	if (binFile.Read(&pTfi->address, sizeof(WORD)) != sizeof(WORD))
	{
		return FALSE;
	}

	//читаем второе слово, длину.
	if (binFile.Read(&pTfi->length, sizeof(WORD)) != sizeof(WORD))
	{
		return FALSE;
	}

	//проверим настоящий бин файл или просто расширение совпало.
	if (pTfi->length != binFile.GetLength() - 4) //если не простой бин
	{
		if (pTfi->length != binFile.GetLength() - 22)   //и не сложный
		{
			return FALSE;
		}
		else
		{
			//а если сложный, надо пропустить немного ненужных данных
			binFile.Seek(20, SEEK_SET);
		}
	}

	SAFE_DELETE_ARRAY(m_pBin);
	m_pBin = new BYTE[pTfi->length];

	if (binFile.Read(m_pBin, pTfi->length) != pTfi->length)
	{
		return FALSE;
	}

	SAFE_DELETE_ARRAY(m_pWave);
	m_nPos = 0;
	CString strName = g_Config.GetFileName(strPath);
	CString strExt = g_Config.GetFileExt(strPath);
	CString strBinExt;
	strBinExt.LoadString(IDS_FILEEXT_BINARY);

	if (!strExt.CompareNoCase(strBinExt))
	{
		strName = g_Config.GetFileTitle(strPath);
	}

	//кроме всего этого, тут надо отлавливать бейсиковские файлы. у них свой формат имени.
	//6 символов - имя (пустое место забивается пробелами), потом .COD, .BIN, .ASC
	//а потом конец забивается нулями, а не пробелами.
	// .ASC обрабатывать не будем. .ASC нормальные люди не используют
	strExt = g_Config.GetFileExt(strName).MakeUpper();

	if (strExt == _T(".COD")) //если это бейсиковский .COD
	{
		memset(pTfi->name, 0, 16);
		strName = g_Config.GetFileTitle(strName);
		//преобразуем юникодную строку в бкашную
		UNICODEtoBK(strName, pTfi->name, 6, TRUE);
		pTfi->name[6] = '.';
		pTfi->name[7] = 'C';
		pTfi->name[8] = 'O';
		pTfi->name[9] = 'D';
	}
	else if (strExt == _T(".BIN")) //если это бейсиковский .BIN
	{
		memset(pTfi->name, 0, 16);
		strName = g_Config.GetFileTitle(strName);
		//преобразуем юникодную строку в бкашную
		UNICODEtoBK(strName, pTfi->name, 6, TRUE);
		pTfi->name[6] = '.';
		pTfi->name[7] = 'B';
		pTfi->name[8] = 'I';
		pTfi->name[9] = 'N';
	}
	else
	{
		//преобразуем юникодную строку в бкашную
		UNICODEtoBK(strName, pTfi->name, 16, TRUE);
	}

	if (!SetWaveFile(pTfi))
	{
		return FALSE;
	}

	m_nWaveLength = m_nPos;
	AllocWaveBuffer(m_nWaveLength * SAMPLE_LENGTH);
	m_nPos = 0;

	if (!SetWaveFile(pTfi))
	{
		return FALSE;
	}

	//CalculateAverage(m_nWaveLength);
	m_nAverage = MAX_SAMPLE / 2; //посчитаем средний уровень проще. массив-то полностью цифровой
	return TRUE;
}


BOOL CTape::SaveBinFile(const CString &strPath, TAPE_FILE_INFO *pTfi)
{
	if (!m_pBin)
	{
		return FALSE;
	}

	CFile binFile;

	if (!binFile.Open(strPath, CFile::modeCreate | CFile::modeWrite))
	{
		return FALSE;
	}

	binFile.Write(&pTfi->address, sizeof(WORD));
	binFile.Write(&pTfi->length, sizeof(WORD));
	binFile.Write(m_pBin, pTfi->length);
	return TRUE;
}


BOOL CTape::LoadMSFFile(const CString &strPath)
{
	CMSFManager msf;

	if (!msf.OpenFile(strPath, TRUE))
	{
		return FALSE;
	}

	DWORD nPackBits, nPackLZW;

	if (!msf.GetBlockTape(NULL, &nPackBits, &nPackLZW))
	{
		return FALSE;
	}

	SAFE_DELETE_ARRAY(m_pPackBits);
	m_pPackBits = new BYTE[nPackBits];
	m_nPackBitsLength = nPackBits;
	SAFE_DELETE_ARRAY(m_pPackLZW);
	m_pPackLZW = new WORD[nPackLZW];

	if (!msf.GetBlockTape(reinterpret_cast<BYTE *>(m_pPackLZW), &nPackBits, &nPackLZW))
	{
		return FALSE;
	}

	m_nPackLZWLength = nPackLZW / 2;
	UnpackLZW_Fast();
	UnpackBits();
	// Calculate average
	CalculateAverage(m_nWaveLength);
	return TRUE;
}


BOOL CTape::SaveMSFFile(const CString &strPath)
{
	//получим длину в битах из длины в сэмплах.
	m_nPackBitsLength = (m_nWaveLength + 7) >> 3;
	SAFE_DELETE_ARRAY(m_pPackBits);
	m_pPackBits = new BYTE[m_nPackBitsLength];
	SAFE_DELETE_ARRAY(m_pPackLZW);
	m_pPackLZW = new WORD[m_nPackBitsLength];
	CMSFManager msf;

	if (!msf.OpenFile(strPath, FALSE))
	{
		return FALSE;
	}

	int nPackBits = PackBits();
	int nPackLZW = PackLZW_Fast() * 2;

	if (!msf.SetBlockTape(reinterpret_cast<BYTE *>(m_pPackLZW), nPackBits, nPackLZW))
	{
		return FALSE;
	}

	return TRUE;
}


BOOL CTape::SetWaveFile(TAPE_FILE_INFO *pTfi)
{
	m_nPos = 0;
	m_nAvgLength = 17;
	ASSERT(m_nAvgLength & 1);

	if (!SaveTuning(4096))
	{
		return FALSE;
	}

	if (!SaveSyncro())
	{
		return FALSE;
	}

	if (!SaveTuning(8))
	{
		return FALSE;
	}

	if (!SaveSyncro())
	{
		return FALSE;
	}

	for (int i = 0; i < 20; ++i)
	{
		if (!SaveByte(((BYTE *)&pTfi->address)[i]))
		{
			return FALSE;
		}
	}

	if (!SaveTuning(8))
	{
		return FALSE;
	}

	if (!SaveSyncro())
	{
		return FALSE;
	}

	for (int i = 0; i < pTfi->length; ++i)
	{
		if (!SaveByte(m_pBin[i]))
		{
			return FALSE;
		}
	}

	CalcCRC(pTfi, FALSE);

	if (!SaveByte(((BYTE *)&pTfi->crc)[0]))
	{
		return FALSE;
	}

	if (!SaveByte(((BYTE *)&pTfi->crc)[1]))
	{
		return FALSE;
	}

	if (!SaveSyncro6())
	{
		return FALSE;
	}

	if (!SaveTuning(256))
	{
		return FALSE;
	}

	return TRUE;
}


BOOL CTape::SaveTuning(int nLength)
{
	for (int i = 0; i < nLength; ++i)
	{
		if (!SaveImp(1))
		{
			return FALSE;
		}
	}

	return TRUE;
}


BOOL CTape::SaveSyncro()
{
	if (!SaveImp(3))
	{
		return FALSE;
	}

	if (!SaveImp(2))
	{
		return FALSE;
	}

	if (!SaveImp(1))
	{
		return FALSE;
	}

	return TRUE;
}


BOOL CTape::SaveSyncro6()
{
	if (!SaveImp(12))
	{
		return FALSE;
	}

	return TRUE;
}


BOOL CTape::SaveImp(int size)
{
	if (m_pWave)
	{
		for (int i = 0; i < size * (m_nAvgLength / 2 + 1); ++i)
		{
			if (m_nPos >= m_nWaveLength)
			{
				return FALSE;
			}

			SetCurrentSampleMono(m_pWave, m_nPos, MAX_SAMPLE);
			m_nPos++;
		}

		for (int i = 0; i < size * (m_nAvgLength / 2); ++i)
		{
			if (m_nPos >= m_nWaveLength)
			{
				return FALSE;
			}

			SetCurrentSampleMono(m_pWave, m_nPos, MIN_SAMPLE);
			m_nPos++;
		}
	}
	else
	{
		m_nPos += size * m_nAvgLength;
	}

	return TRUE;
}


BOOL CTape::SaveBit(BOOL bBit)
{
	if (bBit)
	{
		if (!SaveImp(2))
		{
			return FALSE;
		}
	}
	else
	{
		if (!SaveImp(1))
		{
			return FALSE;
		}
	}

	if (!SaveImp(1))
	{
		return FALSE;
	}

	return TRUE;
}


BOOL CTape::SaveByte(BYTE byte)
{
	BYTE mask = 1;

	for (int i = 0; i < 8; ++i)
	{
		if (!SaveBit(byte & mask))
		{
			return FALSE;
		}

		mask <<= 1;
	}

	return TRUE;
}


//////////////////////////////////////////////////////////////////////
//на входе, nSize - размер в байтах
BOOL CTape::AllocWaveBuffer(int nSize)
{
	if (m_pWave == NULL || m_nWaveLength * SAMPLE_LENGTH < nSize)
	{
		SAFE_DELETE_ARRAY(m_pWave);
		m_pWave = new SAMPLE_TYPE[nSize / sizeof(SAMPLE_TYPE)];
		m_nWaveLength = 0;

		if (!m_pWave)
		{
			return FALSE;
		}

		m_nWaveLength = nSize / SAMPLE_LENGTH;
	}

	m_nAverage = MIN_SAMPLE;
	memset(m_pWave, m_nAverage, nSize);
	return TRUE;
}


BOOL CTape::LoadBuffer(BYTE *pBuff, int nSize)
{
	if (!AllocWaveBuffer(nSize))
	{
		return FALSE;
	}

	memcpy(m_pWave, pBuff, nSize);
	CalculateAverage(m_nWaveLength);
	return TRUE;
}


void CTape::SetPlayWavePos(int play_pos)
{
	m_nPlayPos = play_pos;
}


/*
вход: pBuff - буфер, куда передаётся tape, формат данных в буфере - 16бит 2 канала
buffLength - размер буфера в байтах.
*/
void CTape::PlayWaveGetBuffer(BYTE *pBuff, int buffLength)
{
	//SAMPLE_TYPE *inBuf = reinterpret_cast<SAMPLE_TYPE *>(pBuff); //выходной буфер в словах
	int nSmplBuffLen = buffLength / SAMPLE_LENGTH; //количество сэмплов, которое поместится в выходном буфере.

	//m_nWaveLength и m_nPlayPos - измеряются в сэмплах
	if (m_lockPlay.IsLocked())
	{
		int nLength = m_nWaveLength - m_nPlayPos;

		if (nLength > nSmplBuffLen) //если длина больше чем буфер,
		{
			nLength = nSmplBuffLen;    // уменьшим до длины буфера
		}

		/*
		        for (int i = 0; i < nLength; i++, m_nPlayPos++)
		        {
		            *inBuf++ = m_pWave[m_nPlayPos * BUFFER_CHANNELS];
		            *inBuf++ = m_pWave[m_nPlayPos * BUFFER_CHANNELS + 1];
		        }

		        // но если nLength < nSmplBuffLen, остаток буфера надо добить нулями
		        while (nLength < nSmplBuffLen)
		        {
		            *inBuf++ = MIN_SAMPLE;
		            *inBuf++ = MIN_SAMPLE;
		            nLength++;
		        }
		оптимизация
		*/
		INT_PTR nByteLength = nLength * SAMPLE_LENGTH;
		memcpy(pBuff, m_pWave + m_nPlayPos, nByteLength);
		m_nPlayPos += nLength;

		if (nByteLength < buffLength)
		{
			memset(pBuff + nByteLength, 0, (buffLength - nByteLength));
		}

		if (m_nPlayPos >= m_nWaveLength)
		{
			StopPlay();
		}
	}
}


/*
вход: pBuff - буфер, откуда берётся tape, формат данных в буфере - 16 бит 2 канала
buffLength - размер буфера в байтах.
*/
void CTape::RecordWaveGetBuffer(BYTE *pBuff, int buffLength)
{
	if (!m_lockRecord.IsLocked())
	{
		return;
	}

	//посчитаем размер буфера в сэмплах
	int SmplBufLen = buffLength / SAMPLE_LENGTH;

	//если место в буфере кончается, надо сделать новый буфер, побольше. и данные из старого скопировать в новый
	while (m_nRecordPos + SmplBufLen >= m_nRecordLength)
	{
		//!!!тут надо бы алгоритм поэффективнее.
		SAMPLE_TYPE *pNewRecord = new SAMPLE_TYPE[(m_nRecordLength + RECORD_GROW) * BUFFER_CHANNELS];
		m_nRecordLength += RECORD_GROW;
		ZeroMemory(pNewRecord, m_nRecordLength * SAMPLE_LENGTH);
		memcpy(pNewRecord, m_pRecord, m_nRecordPos * SAMPLE_LENGTH);
		delete [] m_pRecord;
		m_pRecord = pNewRecord;
	}
	//тут не просто надо скопировать, а скопировать и посчитать m_nAverage
	SAMPLE_TYPE *pRecPos = m_pRecord + m_nRecordPos * BUFFER_CHANNELS;
	SAMPLE_TYPE *pSrcPos = reinterpret_cast<SAMPLE_TYPE *>(pBuff);
	double t = 0.0;

	for (int i = 0; i < SmplBufLen; ++i)
	{
		t += (double) * pSrcPos; //считаем средее только по одному каналу, подразумеваем что они не сильно друг от друга отличаются по уровню
		*pRecPos++ = *pSrcPos++;
		*pRecPos++ = *pSrcPos++;
	}

	m_nAverage = (SAMPLE_TYPE)(t / SmplBufLen);

	if (FindRecordEnd())
	{
		StopRecord();
	}

	if (FindRecordBegin(SmplBufLen))  // Если нашли точку начала записи
	{
		m_nRecordPos += SmplBufLen;
	}
}

void CTape::StartPlay()
{
	m_nPos = 0;
	m_lockPlay.Lock();
}

void CTape::StopPlay()
{
	m_lockPlay.UnLock();
}

BOOL CTape::IsPlaying()
{
	return (BOOL) m_lockPlay.IsLocked();
}


void CTape::StartRecord(BOOL bAutoBeginRecord, BOOL bAutoEndRecord)
{
	m_bAutoBeginRecord = bAutoBeginRecord;
	m_bAutoEndRecord = bAutoEndRecord;
	m_nAverage = AVG_SAMPLE;
	m_nRecordPos = 0;
	m_nLastLen = BAD_LENGTH;
	SAFE_DELETE_ARRAY(m_pRecord);
	m_pRecord = new SAMPLE_TYPE[RECORD_BUFFER * BUFFER_CHANNELS]; //создаём новый массив длиной RECORD_BUFFER сэмплов, каждый сэмпл - BUFFER_CHANNELS * sizeof(SAMPLE_TYPE)
	m_nRecordLength = RECORD_BUFFER; //длина массива в сэмплах
	ZeroMemory(m_pRecord, m_nRecordLength * SAMPLE_LENGTH);
	m_lockRecord.Lock();
}


void CTape::StopRecord()
{
	if (m_lockRecord.IsLocked())
	{
		m_lockRecord.UnLock();
		SAFE_DELETE_ARRAY(m_pWave);
		m_pWave = m_pRecord;
		m_pRecord = NULL;
		m_nWaveLength = m_nRecordPos;
	}
}

BOOL CTape::IsRecording()
{
	return (BOOL) m_lockRecord.IsLocked();
}


//////////////////////////////////////////////////////////////////////
//buffLength - длина буфера в сэмплах
BOOL CTape::FindRecordBegin(int buffLength)
{
	if (m_bAutoBeginRecord)
	{
		m_nRecordPos = 0;
		int imp_num = 0;

		while (1)
		{
			int len = CalcImpLength(m_pRecord, m_nRecordPos, buffLength);

			//тут len - в сэмплах
			if (len == BAD_LENGTH)
			{
				m_nRecordPos = 0;
				return FALSE;
			}

			//без возведения в квадрат логически правильнее, т.к. отрицательное значение тоже надо учитывать
			if (m_nLastLen - len <= 3)   //if ( (m_nLastLen - len) * (m_nLastLen - len) <= 3*3 )
			{
				imp_num++;

				if (imp_num >= 50) //30
				{
					break;
				}
			}
			else
			{
				imp_num = 0;
			}

			m_nLastLen = len;
			//TRACE ("\nWave pos %06i, Start = %i, Len = %i, Imp_Num = %i", m_nPos, begin_pos, len, imp_num);
			//Sleep (1);
		}

		m_nRecordPos = 0;
		m_bAutoBeginRecord = FALSE;
	}

	return TRUE;
}


BOOL CTape::FindRecordEnd()
{
	if (m_bAutoBeginRecord)
	{
		return FALSE;
	}

	if (m_bAutoEndRecord)
	{
		SAMPLE_TYPE last_sample = -1;
		SAMPLE_TYPE sample;
		int last_length = 0;

		for (int i = 0; i < g_Config.m_nSoundSampleRate / CPU_FRAMES_PER_SECOND; ++i)
		{
			sample = GetCurrentSampleMono(m_pRecord, m_nRecordPos + i);

			if (sample == last_sample)
			{
				last_length++;
			}
			else
			{
				last_sample = sample;
				last_length = 0;
			}

			if (last_length == 500)
			{
				m_bAutoEndRecord = FALSE;
				return TRUE;
			}
		}
	}

	return FALSE;
}
