
// BKTapeReaderDlg.cpp : файл реализации
//

#include "stdafx.h"
#include "BKTapeReader.h"
#include "BKTapeReaderDlg.h"
#include "afxdialogex.h"
#include "SettingsDlg.h"
#include "globals.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define _USE_MATH_DEFINES
#include <cmath>
#include <corecrt_math_defines.h>

#include <locale>
#pragma warning(disable:4996)

// Диалоговое окно CAboutDlg используется для описания сведений о приложении

class CAboutDlg : public CDialogEx
{
	public:
		CAboutDlg();

// Данные диалогового окна
#ifdef AFX_DESIGN_TIME
		enum { IDD = IDD_ABOUTBOX };
#endif

	protected:
		virtual void DoDataExchange(CDataExchange *pDX) override;    // поддержка DDX/DDV

// Реализация
	protected:
		DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{}

void CAboutDlg::DoDataExchange(CDataExchange *pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()

static CString g_strResulListName = _T("!list.log"); // список опознанных файлов
static CString g_strWorkLogName = _T("!processing.log"); // лог работы программы

// диалоговое окно CBKTapeReaderDlg

CBKTapeReaderDlg::CBKTapeReaderDlg(CWnd *pParent /*=NULL*/)
	: CDialogEx(IDD_BKTAPEREADER_DIALOG, pParent)
	, m_bKillWork(false)
	, m_bKillQueue(false)
	, m_strStage(_T(""))
	, m_nBeginProgress(0)
	, m_nEndProgress(0)
	, m_nCurrentProgress(0)
	, m_nFilesNum(0)
{
	_wsetlocale(LC_ALL, L"Russian");
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CBKTapeReaderDlg::DoDataExchange(CDataExchange *pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_PROGRESS_STAGE, m_Progress);
	DDX_Text(pDX, IDC_STATIC_STAGE, m_strStage);
	DDX_Text(pDX, IDC_STATIC_STAGE_BEGIN, m_nBeginProgress);
	DDX_Text(pDX, IDC_STATIC_STAGE_END, m_nEndProgress);
	DDX_Text(pDX, IDC_STATIC_STAGE_CURR, m_nCurrentProgress);
	DDX_Text(pDX, IDC_STATIC_FILESNUM, m_nFilesNum);
}

BEGIN_MESSAGE_MAP(CBKTapeReaderDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_DROPFILES()
	ON_WM_SIZE()
	ON_WM_GETMINMAXINFO()
	ON_WM_CLOSE()
	ON_WM_DESTROY()
	ON_WM_TIMER()
	ON_BN_CLICKED(IDC_BUTTON_SETTINGS, &CBKTapeReaderDlg::OnBnClickedButtonSettings)
END_MESSAGE_MAP()


// обработчики сообщений CBKTapeReaderDlg

BOOL CBKTapeReaderDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();
	// Добавление пункта "О программе..." в системное меню.
	// IDM_ABOUTBOX должен быть в пределах системной команды.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);
	CMenu *pSysMenu = GetSystemMenu(FALSE);

	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);

		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// Задаёт значок для этого диалогового окна.  Среда делает это автоматически,
	//  если главное окно приложения не является диалоговым
	SetIcon(m_hIcon, TRUE);         // Крупный значок
	SetIcon(m_hIcon, FALSE);        // Мелкий значок
	m_Progress.SetRange32(0, 100);
	m_Progress.SetPos(0);
	m_Progress.SetStep(1);
	m_Resizer.Init(this);
	m_Resizer.AddCtrl(IDC_BUTTON_SETTINGS,      DT_LEFT                    | DT_BOTTOM);
	m_Resizer.AddCtrl(IDCANCEL,                           DT_RIGHT         | DT_BOTTOM);
	m_Resizer.AddCtrl(IDC_STATIC_FILENAME,      DT_LEFT | DT_RIGHT | DT_TOP, false, true);
	m_Resizer.AddCtrl(IDC_STATIC_FILESNUM,                DT_RIGHT | DT_TOP, true, true);
	m_Resizer.AddCtrl(IDC_STATIC_STAGE,         DT_LEFT | DT_RIGHT | DT_TOP, false, true);
	m_Resizer.AddCtrl(IDC_PROGRESS_STAGE,       DT_LEFT | DT_RIGHT | DT_TOP, false, true);
	m_Resizer.AddCtrl(IDC_STATIC_STAGE_BEGIN,   DT_LEFT            | DT_TOP);
	m_Resizer.AddCtrl(IDC_STATIC_STAGE_END,               DT_RIGHT | DT_TOP);
	m_Resizer.AddCtrl(IDC_STATIC_STAGE_CURR,    DT_CENTER          | DT_TOP);
	m_Resizer.AddCtrl(IDC_EDIT_INFO,            DT_LEFT | DT_RIGHT | DT_TOP | DT_BOTTOM, false, false);
	m_ParserAmp.bindDialog(this);
	m_ParserFront.bindDialog(this);
	g_Settings.ConfigSettings();
	g_Settings.LoadSettings();
	StartQueueThread();
	SetTimer(TAPE_UI_UPDATE, g_Settings.nTimerValue, nullptr);  // запустить таймер
	return TRUE;  // возврат значения TRUE, если фокус не передан элементу управления
}

void CBKTapeReaderDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// При добавлении кнопки свёртывания в диалоговое окно нужно воспользоваться приведённым ниже кодом,
//  чтобы нарисовать значок.  Для приложений MFC, использующих модель документов или представлений,
//  это автоматически выполняется рабочей областью.

void CBKTapeReaderDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // контекст устройства для рисования
		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
		// Выравнивание значка по центру клиентского прямоугольника
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;
		// Нарисуйте значок
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

// Система вызывает эту функцию для получения отображения курсора при перемещении
//  свёрнутого окна.
HCURSOR CBKTapeReaderDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}



void CBKTapeReaderDlg::OnSize(UINT nType, int cx, int cy)
{
	CDialogEx::OnSize(nType, cx, cy);
	m_Resizer.OnSize(cx, cy);
}


void CBKTapeReaderDlg::OnGetMinMaxInfo(MINMAXINFO *lpMMI)
{
	m_Resizer.GetMinMaxInfo(lpMMI);
}


void CBKTapeReaderDlg::OnClose()
{
	StopQueueThread();
	CDialogEx::OnClose();
}


void CBKTapeReaderDlg::OnDestroy()
{
	g_Settings.SaveSettings();
	StopQueueThread();
	CDialogEx::OnDestroy();
}

void CBKTapeReaderDlg::StartQueueThread()
{
	m_thrQueue = std::thread(&CBKTapeReaderDlg::QueueWork, this);

	if (m_thrQueue.joinable())
	{
		m_thrQueue.detach();
	}
}

void CBKTapeReaderDlg::StopQueueThread()
{
	m_bKillWork = true;
	m_bKillQueue = true;
	m_queList.push(_T("Exit"));
	m_queueCheck.notify_one();
	MSG msg;

	// из-за взаимодействия с UI при попытке отмениться, всё задедлочивается.
	// поэтому возьмём на себя обработку сообщений
	// вот нигде в интернете не видел способа нормальным образом победить эту херню.
	// ждём, пока поток не завершится, и пока ждём - обрабатываем очередь сообщений
	while (!m_lockQueueWork.try_lock())
	{
		if (::GetMessage(&msg, GetSafeHwnd(), 0, 0))
		{
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
	}

	KillTimer(TAPE_UI_UPDATE);
	m_lockQueueWork.unlock();
	//std::lock_guard<std::mutex> lk(m_lockQueueWork);
}

void CBKTapeReaderDlg::QueueWork()
{
	m_lockQueueWork.lock();

	// до тех пор, пока не будет получен сигнал
	while (true)
	{
		std::unique_lock<std::mutex> locker(m_lockQueue);
		m_queueCheck.wait(locker, [&]()
		{
			return !m_queList.empty();
		});

		if (m_bKillQueue)
		{
			break;
		}

		while (!m_queList.empty())
		{
			CString s = m_queList.get();

			if (OpenFile(s))
			{
				if (!PreWork(s))
				{
					break;
				}
			}
		}

		m_strStage = _T("Все файлы обработаны, очередь пуста.");
		SetProgressRange(0, 100);
	}

	m_lockQueueWork.unlock();
}

void CBKTapeReaderDlg::OnDropFiles(HDROP hDropInfo)
{
	UINT nFileCount = DragQueryFile(hDropInfo, 0xFFFFFFFF, nullptr, 0);

	if (nFileCount >= 1) //дропнули какие-то файлы
	{
		for (UINT num = 0; num < nFileCount; ++num)
		{
			CString strFile;
			DragQueryFile(hDropInfo, num, strFile.GetBufferSetLength(MAX_FILE_PATH), MAX_FILE_PATH); //получим очередной дропнутый файл.
			strFile.ReleaseBuffer();
			m_queList.push(strFile); // помещаем файл в очередь
		}

		m_queueCheck.notify_one();
	}

	DragFinish(hDropInfo);
}


void CBKTapeReaderDlg::OnTimer(UINT_PTR nIDEvent)
{
	if (nIDEvent == TAPE_UI_UPDATE)
	{
		UpdateData(FALSE);
	}
	else
	{
		CDialogEx::OnTimer(nIDEvent);
	}
}


void CBKTapeReaderDlg::OnBnClickedButtonSettings()
{
	g_Settings.DoModal();
}


bool CBKTapeReaderDlg::OpenFile(CString &strName)
{
	SetDlgItemText(IDC_STATIC_FILENAME, strName);
	m_strStage = _T("Загрузка файла");
	SetProgressRange(0, 100);
	std::string s = ConvertUnicodetoStr(CP_ACP, strName);
	bool loadedOK = m_audioSrc.load(s);
	return loadedOK;
}

bool CBKTapeReaderDlg::SaveFile(CString &strName)
{
	m_strStage = _T("Сохранение файла");
	SetProgressRange(0, 100);
	std::string s = ConvertUnicodetoStr(CP_ACP, strName);
	bool savedOk = m_audioFile.save(s);
	return savedOk;
}

std::string CBKTapeReaderDlg::ConvertUnicodetoStr(UINT type, const CString &ustr)
{
	//сперва узнаем, какого размера будет новая строка
	int t_len = WideCharToMultiByte(type, 0, ustr, -1, nullptr, 0, nullptr, nullptr);
	int sizeOfString = t_len + 1; //и +1 для завершающего нуля
	auto lpszAscii = new CHAR[sizeOfString];
	std::string ret;

	if (lpszAscii)
	{
		t_len = WideCharToMultiByte(type, 0, ustr, -1, lpszAscii, sizeOfString, nullptr, nullptr);
		ret = std::string(lpszAscii);
		delete[] lpszAscii;
	}

	return ret;
}

void CBKTapeReaderDlg::SetProgressRange(int start, int end)
{
	int e1, s1;

	if (end >= start)
	{
		e1 = end;
		s1 = start;
	}
	else
	{
		s1 = end;
		e1 = start;
	}

	m_Progress.SetRange32(s1, e1);
	m_nBeginProgress = s1;
	m_nEndProgress = e1;
	SetProgressPos(s1);
}


void CBKTapeReaderDlg::SetProgressPos(int pos)
{
	m_nCurrentProgress = pos;
	m_Progress.SetPos(pos);
}

// попробуем обработать большой файл, чтобы из него выделять куски и обрабатывать их
bool CBKTapeReaderDlg::PreWork(CString &strInFileName)
{
	m_strCurrentFileName = strInFileName;
	m_strCurrentPath = GetFilePath(strInFileName);
	int nChannnels = m_audioSrc.getNumChannels(); //получим количество каналов
	int nWaveLength = m_audioSrc.getNumSamplesPerChannel(); //размер ваве в сэмплах.
	int nWorkingChan = CHANS::LEFT_SRC;
	int nWorkChannels = 1;

	if (nChannnels > 1)
	{
		nWorkingChan = CHANS::MIX_SRC;
		nWorkChannels = 3;
		m_audioSrc.setNumChannels(3);
		//создаём микшированный канал
		MixChn(m_audioSrc.samples[CHANS::LEFT_SRC], m_audioSrc.samples[CHANS::RIGHT_SRC], m_audioSrc.samples[CHANS::MIX_SRC]);

		if (m_bKillWork)
		{
			return false;
		}
	}

	if (g_Settings.bSolidFile)
	{
		m_audioFile.clearAudioBuffer();
		m_audioFile.setAudioBufferSize(nWorkChannels, nWaveLength);

		// копируем данные. не знаю как проще и быстрее
		for (int c = 0; c < nWorkChannels; ++c)
		{
			for (int p = 0; p < nWaveLength; ++p)
			{
				m_audioFile.samples[c][p] = m_audioSrc.samples[c][p];
			}
		}

		m_nFilesNum = 1;

		if (!Work(1, true, 0, nWaveLength))
		{
			return false;
		}
	}
	else
	{
		bool bSingleFile = true;
		int nFilesNum = 1;  // номер файла в мультифайле, отсчёт по человечески, с единицы
		int nPrevTunePos = 0;   // предыдущие значения
		int nTuneLength = 0;    // длина настроечной последовательности
		int nTunePos = 0;       // начало настроечной последовательности
		int nMarkerPos = 0;     // здесь этот параметр не нужен
		// ищем первую настроечную последовательность с начала
		int nResult = m_ParserAmp.FindNextTuning(m_audioSrc.samples[nWorkingChan], 0, nTunePos, nTuneLength, nMarkerPos);

		//если замикшировано хреново, то проверим немиксованные каналы.
		if (nResult != 0 && nChannnels > 1)
		{
			// проверим левый
			nResult = m_ParserAmp.FindNextTuning(m_audioSrc.samples[CHANS::LEFT_SRC], 0, nTunePos, nTuneLength, nMarkerPos);

			if (nResult != 0) // если в левом хреново
			{
				// проверим правый
				nResult = m_ParserAmp.FindNextTuning(m_audioSrc.samples[CHANS::RIGHT_SRC], 0, nTunePos, nTuneLength, nMarkerPos);
			}
		}

		if (nResult == 0) // если нашли
		{
			bool bWork = true;

			do // в цикле
			{
				nPrevTunePos = nTunePos; //запомним где была настроечная последовательность
				// ищем следующую, от конца найденной
				int nFindStartPos = nTunePos + nTuneLength;
				nResult = m_ParserAmp.FindNextTuning(m_audioSrc.samples[nWorkingChan], nFindStartPos, nTunePos, nTuneLength, nMarkerPos);

				//если замикшировано хреново, то проверим немиксованные каналы.
				if (nResult != 0 && nChannnels > 1)
				{
					// проверим левый
					nResult = m_ParserAmp.FindNextTuning(m_audioSrc.samples[CHANS::LEFT_SRC], nFindStartPos, nTunePos, nTuneLength, nMarkerPos);

					if (nResult != 0) // если в левом хреново
					{
						// проверим правый
						nResult = m_ParserAmp.FindNextTuning(m_audioSrc.samples[CHANS::RIGHT_SRC], nFindStartPos, nTunePos, nTuneLength, nMarkerPos);
					}
				}

				if (nResult < 0) // если не нашли
				{
					bWork = false; // всё, больше нечего делать.
					nTunePos = nWaveLength;
				}
				else
				{
					bSingleFile = false;
				}

				m_audioFile.clearAudioBuffer();
				m_audioFile.setAudioBufferSize(nWorkChannels, nTunePos - nPrevTunePos);

				// копируем данные. не знаю как проще и быстрее
				for (int c = 0; c < nWorkChannels; ++c)
				{
					for (int p = 0; p < nTunePos - nPrevTunePos; ++p)
					{
						m_audioFile.samples[c][p] = m_audioSrc.samples[c][p + nPrevTunePos];
					}
				}

				if (!bSingleFile)
				{
					m_nFilesNum = nFilesNum;
				}

				if (!Work(nFilesNum, bSingleFile, nPrevTunePos, nTunePos))
				{
					return false;
				}

				nFilesNum++;
			}
			while (bWork);

			if (!bSingleFile)
			{
				winprintf(_T("Конец кассеты.\r\n"));
			}
		}
		else
		{
			m_strStage = _T("Настроечная последовательность не найдена.");
			CString strGlbLogName = m_strCurrentPath + g_strResulListName;
			FILE *fl = _wfopen(strGlbLogName.GetString(), L"at");
			fwprintf(fl, L"%s:\t" + m_strStage + "\n", m_strCurrentFileName.GetString());
			fclose(fl);
			return false;
		}
	}

	return true;
}

// тут надо два вида лога вести. для одиночного файла и для мультифайла
bool CBKTapeReaderDlg::Work(int nFileNum, bool bSingle, int nTune1, int nTune2)
{
	std::lock_guard<std::mutex> lock(m_lockWork);
	CString strFileTitle = GetFileTitle(m_strCurrentFileName);
	CString strLogName = m_strCurrentPath + strFileTitle + _T(".log");
	int nChannnels = m_audioFile.getNumChannels(); //получим количество каналов
	int nWaveLength = m_audioFile.getNumSamplesPerChannel(); //размер ваве в сэмплах.
	//дальше надо делать свёртку вейвлет функции с каналом 0 и сохранять данные в канал 1
	//затем сохранить файл и посмотреть, что получилось.
	//пока никаких вейвлетов
	int nTemplateLen = 4; //это минимальный размер шаблона. Но он используется только при анализе фронтов.

	if (g_Settings.bFrontAnalyse)
	{
		m_audioFile.setNumChannels(CHANS::CHANNNELS);
		nTemplateLen = m_ParserAmp.GetImpulseLen(m_audioFile.samples[CHANS::LEFT_SRC]);

		if (nChannnels > 1)
		{
			if (nTemplateLen <= 0)
			{
				nTemplateLen = m_ParserAmp.GetImpulseLen(m_audioFile.samples[CHANS::RIGHT_SRC]);
			}

			if (nTemplateLen <= 0)
			{
				nTemplateLen = m_ParserAmp.GetImpulseLen(m_audioFile.samples[CHANS::MIX_SRC]);
			}
		}
	}

	if (nTemplateLen > 0)
	{
		CString str;
		str.Format(_T("%d"), nWaveLength);
		SetDlgItemText(IDC_STATIC_ARRLEN, str);
		str.Format(_T("%d"), nTemplateLen);
		SetDlgItemText(IDC_STATIC_FFTLEN, str);

		if (g_Settings.bFrontAnalyse)
		{
			PrepareTemplate(nTemplateLen, nTemplateLen == 4 ? PROC_TYPE::CONVOL_SIMSINE : PROC_TYPE::CONVOL_SINE); // возьмём синусоиду

			if (m_bKillWork)
			{
				return false;
			}

			Convol(m_temp, m_audioFile.samples[CHANS::LEFT_SRC], m_audioFile.samples[CHANS::LEFT_ST1], CONV_MODE::CONVOLUTION);

			if (m_bKillWork)
			{
				return false;
			}

			if (nChannnels > 1)
			{
				Convol(m_temp, m_audioFile.samples[CHANS::RIGHT_SRC], m_audioFile.samples[CHANS::RIGHT_ST1], CONV_MODE::CONVOLUTION);

				if (m_bKillWork)
				{
					return false;
				}

				Convol(m_temp, m_audioFile.samples[CHANS::MIX_SRC], m_audioFile.samples[CHANS::MIX_ST1], CONV_MODE::CONVOLUTION);

				if (m_bKillWork)
				{
					return false;
				}
			}

			//Amplifier(m_audioFile.samples[RIGHT_SRC]);
			//Attenuator(m_audioFile.samples[RIGHT_SRC], 4);

			if (g_Settings.bFrontAliasing)
			{
				PrepareTemplate(nTemplateLen, nTemplateLen == 4 ? PROC_TYPE::CONVOL_DERIVATIVE : PROC_TYPE::CORRELATION);

				if (m_bKillWork)
				{
					return false;
				}

				//реверсируем массив
				size_t slen = m_temp.size();

				for (size_t i = 0; i < slen / 2; ++i)
				{
					std::swap(m_temp[i], m_temp[slen - 1 - i]);
				}

				Convol(m_temp, m_audioFile.samples[CHANS::LEFT_ST1], m_audioFile.samples[CHANS::LEFT_ST2], CONV_MODE::CORRELATION);

				if (m_bKillWork)
				{
					return false;
				}

				if (nChannnels > 1)
				{
					Convol(m_temp, m_audioFile.samples[CHANS::RIGHT_ST1], m_audioFile.samples[CHANS::RIGHT_ST2], CONV_MODE::CORRELATION);

					if (m_bKillWork)
					{
						return false;
					}

					Convol(m_temp, m_audioFile.samples[CHANS::MIX_ST1], m_audioFile.samples[CHANS::MIX_ST2], CONV_MODE::CORRELATION);

					if (m_bKillWork)
					{
						return false;
					}
				}
			}

			//Amplifier(m_audioFile.samples[MIX_SRC]);
			//Attenuator(m_audioFile.samples[MIX_SRC], 4);
#if (0) //(_DEBUG)
			CString name = SetNewName(m_strCurrentFileName);

			if (!name.IsEmpty())
			{
				SaveFile(name);
				m_strStage = _T("Файл для отладки сохранён.");
			}
			else
			{
				m_strStage = _T("Файл для отладки не сохранён. Некорректное имя.");
			}

#endif
			ParseFront(CHANS::LEFT_ST1, _T("Левый"));

			if (m_bKillWork)
			{
				return false;
			}

			if (g_Settings.bFrontAliasing)
			{
				ParseFront(CHANS::LEFT_ST2, _T("Левый Г"));

				if (m_bKillWork)
				{
					return false;
				}
			}

			if (nChannnels > 1)
			{
				ParseFront(CHANS::RIGHT_ST1, _T("Правый"));

				if (m_bKillWork)
				{
					return false;
				}

				if (g_Settings.bFrontAliasing)
				{
					ParseFront(CHANS::RIGHT_ST2, _T("Правый Г"));

					if (m_bKillWork)
					{
						return false;
					}
				}

				ParseFront(CHANS::MIX_ST1, _T("Микс"));

				if (m_bKillWork)
				{
					return false;
				}

				if (g_Settings.bFrontAliasing)
				{
					ParseFront(CHANS::MIX_ST2, _T("Микс Г"));

					if (m_bKillWork)
					{
						return false;
					}
				}
			}
		}

		ParseAmp(CHANS::LEFT_SRC, _T("Левый"));

		if (m_bKillWork)
		{
			return false;
		}

		if (g_Settings.bFrontAnalyse && g_Settings.bFrontLevels)
		{
			ParseAmp(CHANS::LEFT_ST1, _T("Левый Ф"));

			if (m_bKillWork)
			{
				return false;
			}

			if (g_Settings.bFrontAliasing)
			{
				ParseAmp(CHANS::LEFT_ST2, _T("Левый ФГ"));

				if (m_bKillWork)
				{
					return false;
				}
			}
		}

		if (nChannnels > 1)
		{
			ParseAmp(CHANS::RIGHT_SRC, _T("Правый"));

			if (m_bKillWork)
			{
				return false;
			}

			ParseAmp(CHANS::MIX_SRC, _T("Микс"));

			if (m_bKillWork)
			{
				return false;
			}

			if (g_Settings.bFrontAnalyse && g_Settings.bFrontLevels)
			{
				ParseAmp(CHANS::RIGHT_ST1, _T("Правый Ф"));

				if (m_bKillWork)
				{
					return false;
				}

				if (g_Settings.bFrontAliasing)
				{
					ParseAmp(CHANS::RIGHT_ST2, _T("Правый ФГ"));

					if (m_bKillWork)
					{
						return false;
					}
				}

				ParseAmp(CHANS::MIX_ST1, _T("Микс Ф"));

				if (m_bKillWork)
				{
					return false;
				}

				if (g_Settings.bFrontAliasing)
				{
					ParseAmp(CHANS::MIX_ST2, _T("Микс ФГ"));

					if (m_bKillWork)
					{
						return false;
					}
				}
			}
		}

		CString strGlbLogName = m_strCurrentPath + g_strResulListName;
		FILE *fl = _wfopen(strGlbLogName.GetString(), L"at");

		if (bSingle)
		{
			fwprintf(fl, L"%s:\t", m_strCurrentFileName.GetString());
		}
		else
		{
			if (nFileNum == 1)
			{
				fwprintf(fl, L"%s\n", m_strCurrentFileName.GetString());
			}

			fwprintf(fl, L"\t(%02d, %9d, %9d):\t", nFileNum, nTune1, nTune2);
		}

		if (m_vecSavedBins.empty())
		{
			FILE *f = _wfopen(strLogName.GetString(), L"at");

			if (!bSingle)
			{
				fwprintf(f, L"(%02d): ", nFileNum);
			}

			fwprintf(f, L"Не удалось.\n");
			fwprintf(fl, L"Не удалось.\n");

			if (!m_vecUnSavedBins.empty())
			{
				for (auto &n : m_vecUnSavedBins)
				{
					fwprintf(f, L"%s -- %d раз.\n", n.strName.GetString(), n.nCount);
					fwprintf(fl, L"\t\t%s\t\t%o\t%o\n", n.strName.GetString(), n.addr, n.len);
				}
			}

			fclose(f);
		}
		else if (m_vecSavedBins.size() > 1)
		{
			FILE *f = _wfopen(strLogName, L"at");

			if (!bSingle)
			{
				fwprintf(f, L"(%02d): ", nFileNum);
			}

			bool bComma = false;

			for (auto &n : m_vecSavedBins)
			{
				fwprintf(f, L"%s -- %d раз.\n", n.strName.GetString(), n.nCount);

				if (bComma)
				{
					fwprintf(fl, L"\n\t\t");
				}
				else
				{
					bComma = true;
				}

				fwprintf(fl, L"%s\t\t%o\t%o", n.strName.GetString(), n.addr, n.len);
			}

			fwprintf(fl, L"\n");
			fclose(f);
		}
		else
		{
			BinFile &n = m_vecSavedBins.at(0);
			fwprintf(fl, L"%s\t\t%o\t%o\n", n.strName.GetString(), n.addr, n.len);
		}

		fclose(fl);
		m_vecSavedBins.clear();
		m_vecUnSavedBins.clear();
	}
	else
	{
		m_strStage = _T("Настроечная последовательность не найдена.");
		CString strGlbLogName = m_strCurrentPath + g_strResulListName;
		FILE *fl = _wfopen(strGlbLogName.GetString(), L"at");
		fwprintf(fl, L"%s:\t" + m_strStage + "\n", m_strCurrentFileName.GetString());
		fclose(fl);
	}

	return true;
}

void CBKTapeReaderDlg::ParseFront(CHANS chn, CString strID)
{
	ParseFunc(&m_ParserFront, chn, _T("Фронты:") + strID);
}

void CBKTapeReaderDlg::ParseAmp(CHANS chn, CString strID)
{
	ParseFunc(&m_ParserAmp, chn, _T("Уровни:") + strID);
}


void CBKTapeReaderDlg::ParseFunc(CWaveParserProto *wp, CHANS chn, CString strID)
{
	TAPE_FILE_INFO tfi;

	if (wp->ParseWaveFile(&tfi, m_audioFile.samples[chn], m_strCurrentFileName, strID))
	{
		uint16_t crc = wp->CalcCRC(&tfi);

		if ((crc == tfi.crc) || g_Settings.bIgnoreCRC)
		{
			if (crc == tfi.crc)
			{
				m_strStage = _T("Успешное распознавание.");
			}
			else
			{
				m_strStage = _T("Ошибка КС. ИГНОРИРУЕТСЯ!");
			}

			winprintf(m_strStage + _T("\r\n"));

			//сохраним бин файл.
			if (SaveBinFile(m_strCurrentFileName, &tfi, wp->getBin()))
			{
				winprintf(_T("Файл сохранён.\r\n"));
			}
			else
			{
				winprintf(_T("Ошибка сохранения файла.\r\n"));
			}

			return;
		}

		m_strStage = _T("Ошибка КС.");
		winprintf(m_strStage + _T("\r\n"));
	}
	else
	{
		m_strStage = _T("Ошибка распознавания.");
		winprintf(m_strStage + _T("\r\n"));
	}

	CheckUnSavedFile(&tfi);
}

void CBKTapeReaderDlg::CheckUnSavedFile(TAPE_FILE_INFO *pTfi)
{
	CString strName;

	if (pTfi->name[0] != 255)
	{
		strName = BKToUNICODE(pTfi->name, 16);
		strName.Trim();
	}
	else
	{
		strName = _T("Нераспознано");
	}

	if (!m_vecUnSavedBins.empty()) // если вектор не пуст, проверим, что там
	{
		// поищем в векторе, было ли уже такое имя
		for (auto &n : m_vecUnSavedBins)
		{
			if (n.strName == strName)
			{
				// это уже было
				n.nCount++; // увеличим счётчик попаданий
				return; // и выходим
			}
		}
	}

	// если не было, то добавим
	BinFile bf;
	bf.addr = pTfi->address; bf.len = pTfi->length; bf.crc = pTfi->crc;
	bf.nCount = 1;
	bf.strName = strName;
	m_vecUnSavedBins.push_back(bf);
}


void CBKTapeReaderDlg::PrepareTemplate(int slen, PROC_TYPE pt)
{
	m_temp.clear();
	SetProgressRange(0, slen);

	switch (pt)
	{
		case PROC_TYPE::CONVOL_SIMSINE:
			// эта штука просто восстанавливает сигнал, по крайней мере похоже
//          m_temp.emplace_back(-1.0);
//          m_temp.emplace_back(1.0);
//          m_temp.emplace_back(1.0);
			// это аналог синусоиды
			m_temp.emplace_back(1.0);
			m_temp.emplace_back(0.0);
			m_temp.emplace_back(-1.0);
			break;

		case PROC_TYPE::CONVOL_DERIVATIVE:
			// это производная, тоже хорошая функция
			m_temp.emplace_back(-1.0);
			m_temp.emplace_back(1.0);
			break;

		case PROC_TYPE::CONVOL_SINE:

			// это синусоида
			for (int i = 0; i < slen; ++i)
			{
				SetProgressPos(i);
				double p = double(i) * 2.0 * M_PI / double(slen);
				m_temp.emplace_back(sin(p));
			}

			break;

		case PROC_TYPE::CORRELATION:

			// сглаживание результата
			for (int i = 0; i < slen; ++i)  // это более-менее рабочий вариант
			{
				SetProgressPos(i);
				double p = double(i) * M_PI / double(slen);
				m_temp.emplace_back(sin(p));
			}

//          for (int i = 0; i < slen; ++i) // ещё более сильно сглаживает
//          {
//              SetProgressPos(i);
//              double p = double(i) * (M_PI/8.0) / double(slen);
//              m_temp.emplace_back(sin(p));
//          }
			break;
	}
}

void CBKTapeReaderDlg::Convol(WaveData &m, WaveData &s, WaveData &d, CONV_MODE mode)
{
	size_t size_s = s.size();
	size_t size_m = m.size();
	SetProgressRange(0, int(size_s));

	switch (mode)
	{
		case CONV_MODE::CONVOLUTION:
			m_strStage = _T("Свёртка");
			break;

		case CONV_MODE::CORRELATION:
			m_strStage = _T("Сглаживание");
			break;
	}

	size_t step = size_s / g_Settings.nProgressScale;

	if (step == 0)
	{
		step++;
	}

	size_t dl = step;

	for (size_t i = 0; i < size_s; ++i)
	{
		if (--dl <= 0)
		{
			dl = step;
			SetProgressPos(int(i));
		}

		double sum = 0.0;
		size_t end_m = (i < size_m) ? i + 1 : size_m;

		for (size_t j = 0; j < end_m; ++j)
		{
			sum += s[i - j] * m[j];
		}

		d[i] = sum;
	}

	SetProgressPos(int(size_s));
}

void CBKTapeReaderDlg::Amplifier(WaveData &s)
{
	m_strStage = _T("Усиление");
	size_t size_s = s.size();
	SetProgressRange(0, int(size_s));
	int n = 0;
	size_t step = size_s / g_Settings.nProgressScale;

	if (step == 0)
	{
		step++;
	}

	size_t dl = step;

	for (auto &i : s)
	{
		if (--dl <= 0)
		{
			dl = step;
			SetProgressPos(n);
		}

		if (abs(i) > 1e-6)
		{
			i /= sqrt(abs(i));
		}

		n++;
	}

	SetProgressPos(int(size_s));
}

void CBKTapeReaderDlg::Attenuator(WaveData &s, int val)
{
	m_strStage = _T("Ослабление");
	size_t size_s = s.size();
	SetProgressRange(0, int(size_s));
	int n = 0;
	size_t step = size_s / g_Settings.nProgressScale;

	if (step == 0)
	{
		step++;
	}

	size_t dl = step;

	for (auto &i : s)
	{
		if (--dl <= 0)
		{
			dl = step;
			SetProgressPos(n);
		}

		i /= val;
		n++;
	}

	SetProgressPos(int(size_s));
}

void CBKTapeReaderDlg::MixChn(WaveData &l, WaveData &r, WaveData &d)
{
	m_strStage = _T("Микширование");
	winprintf(m_strStage + _T("\r\n"));
	size_t size_l = l.size();
	SetProgressRange(0, int(size_l));
	size_t step = size_l / g_Settings.nProgressScale;

	if (step == 0)
	{
		step++;
	}

	size_t dl = step;
	// сперва определим смещение каналов относительно друг друга
	int nTuneLength = 0;    // здесь этот параметр не нужен
	int nTunePos = 0;       // здесь этот параметр не нужен
	int leftMarker = 0;     // позиция маркера в левом канале
	int rightMarker = 0;    // позиция маркера в правом канале
	int nResLt = m_ParserAmp.FindNextTuning(l, 0, nTunePos, nTuneLength, leftMarker);
	int nResRt = m_ParserAmp.FindNextTuning(r, 0, nTunePos, nTuneLength, rightMarker);

	// если маркеры найдены, и они друг от друга отстоят не сильно далеко, то миксуем
	// иначе можно всё попортить
	if ((nResLt == 0) && (nResRt == 0) && abs(leftMarker - rightMarker) < m_ParserAmp.GetAvgImpLen() * 10)
	{
		if (leftMarker >= rightMarker)
		{
			size_t delta = leftMarker - rightMarker;
			size_t n = 0;

			for (auto &i : d)
			{
				if (--dl <= 0)
				{
					dl = step;
					SetProgressPos(int(n));
				}

				//i = l[delta] + r[n] - l[delta] * r[n];
				i = (l[delta] + r[n]) / 2.0;
				n++; delta++;

				if (delta >= size_l)
				{
					delta = n;
				}
			}
		}
		else
		{
			size_t delta = rightMarker - leftMarker;
			size_t n = 0;

			for (auto &i : d)
			{
				if (--dl <= 0)
				{
					dl = step;
					SetProgressPos(int(n));
				}

				//i = l[n] + r[delta] - l[n] * r[delta];
				i = (l[n] + r[delta]) / 2.0;
				n++; delta++;

				if (delta >= size_l)
				{
					delta = n;
				}
			}
		}
	}
	else
	{
		if (nResLt == CWaveParserProto::BAD_MARKER)
		{
			winprintf(_T("Маркер данных в левом канале не найден.\r\n"));
		}
		else if (nResLt == CWaveParserProto::BAD_TUNING)
		{
			winprintf(_T("Настроечная последовательность в левом канале не найдена.\r\n"));
		}

		if (nResRt == CWaveParserProto::BAD_MARKER)
		{
			winprintf(_T("Маркер данных в правом канале не найден.\r\n"));
		}
		else if (nResRt == CWaveParserProto::BAD_TUNING)
		{
			winprintf(_T("Настроечная последовательность в правом канале не найдена.\r\n"));
		}

		winprintf(_T("Хороший результат автоматического микширования не гарантируется.\r\n"));
		// просто тупо миксуем
		int n = 0;

		for (auto &i : d)
		{
			if (--dl <= 0)
			{
				dl = step;
				SetProgressPos(n);
			}

			//i = l[n] + r[n] - l[n] * r[n];
			i = (l[n] + r[n]) / 2.0;
			n++;
		}
	}

	SetProgressPos(int(size_l));
}

bool CBKTapeReaderDlg::SaveBinFile(CString &strFileName, TAPE_FILE_INFO *pTfi, uint8_t *pBin)
{
	if (!pBin)
	{
		return false;
	}

	if (!m_vecSavedBins.empty()) // если вектор не пуст, проверим, что там
	{
		// поищем в векторе, сохраняли ли уже файл с такой КС
		for (auto &n : m_vecSavedBins)
		{
			if (n.crc == pTfi->crc)
			{
				// да, сохраняли
				n.nCount++;  // увеличим счётчик попаданий
				return true; // и выходим
			}
		}
	}

	// сюда попадаем, если надо сохранить новый экземпляр файла
	CString strFileTitle = BKToUNICODE(pTfi->name, 16);
	strFileTitle.Trim();
	SetSafeName(strFileTitle);
	CString strNewName = m_strCurrentPath + strFileTitle + _T(".bin");
	CString strName = SetNewName(strNewName);
	CFile binFile;

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

	BinFile bf;
	bf.addr = pTfi->address; bf.len = pTfi->length; bf.crc = pTfi->crc;
	bf.nCount = 1;
	bf.strName = GetFileName(strName);
	m_vecSavedBins.push_back(bf);
	binFile.Write(&pTfi->address, sizeof(uint16_t));
	binFile.Write(&pTfi->length, sizeof(uint16_t));

	if (g_Settings.bUseLongBinFormat)
	{
		binFile.Write(&pTfi->name, 16);
	}

	binFile.Write(pBin, pTfi->length);

	if (g_Settings.bUseLongBinFormat)
	{
		binFile.Write(&pTfi->crc, sizeof(uint16_t));
	}

	return true;
}


CString CBKTapeReaderDlg::SetNewName(CString &strOldName)
{
	CFileStatus fs;

	if (CFile::GetStatus(strOldName, fs)) //если даже такого имени нет, то его и оставим
	{
		CString strDrive;
		CString strPath;
		CString strName;
		CString strExt;
		_tsplitpath_s(strOldName.GetString(), strDrive.GetBufferSetLength(_MAX_DIR), _MAX_DIR, strPath.GetBufferSetLength(MAX_FILE_PATH), MAX_FILE_PATH, strName.GetBufferSetLength(MAX_FILE_PATH), MAX_FILE_PATH, strExt.GetBufferSetLength(_MAX_EXT), _MAX_EXT);
		strDrive.ReleaseBuffer();
		strPath.ReleaseBuffer();
		strName.ReleaseBuffer();
		strExt.ReleaseBuffer();
		CString strNewName;
		// Проверим, а не существует ли уже такой файл?
		int nNum = 0;

		do
		{
			//сделаем новое имя
			nNum++;
			CString str;
			_itot_s(nNum, str.GetBufferSetLength(32), 32, 10);
			str.ReleaseBuffer();
			strNewName = strDrive + strPath + strName + _T("(") + str + _T(")") + strExt;
		}
		while (CFile::GetStatus(strNewName, fs) && nNum < 10000);     //будем проверять имена с цифрой до тех пор, пока не найдём

		if (nNum >= 10000) //если вышли по такому условию, то явно что-то не так
		{
			return _T("");
		}

		return strNewName;
	}

	return strOldName;
}


/*
процедура, которая заменяет printf
вход:   CONTROL_ID - id контрола (едитбокса), в который делать вывод
format - формат выводимого на экран, всё как в printf
*/
static TCHAR strOut[_MAX_ENV]; //буфер, в котором будет формироваться форматированная строка
static TCHAR *emptyStr = _T("");
void CBKTapeReaderDlg::winprintf(const TCHAR *format, ...)
{
	va_list argptr;
	va_start(argptr, format);
	_vsntprintf_s(strOut, _MAX_ENV, _MAX_ENV, format, argptr); // форматируем строку
	va_end(argptr);

	if (g_Settings.bWorkLog)
	{
		CString strName = m_strCurrentPath + g_strWorkLogName;
		FILE *fl = _wfopen(strName.GetString(), L"at");
		fwprintf(fl, L"%s", strOut);
		fclose(fl);
	}

	UINT nTextLimit = SendDlgItemMessage(IDC_EDIT_INFO, EM_GETLIMITTEXT, 0, 0); // получим длину текста едит контрола
	UINT nStrLen = lstrlen(strOut); // длина новой добавляемой строки
	LRESULT hndl = SendDlgItemMessage(IDC_EDIT_INFO, EM_GETHANDLE, 0, 0); // получим хендл буфера текста едит контрола

	if (hndl)
	{
		// получим указатель на буфер текста едит контрола правильным образом
		auto buf = reinterpret_cast<TCHAR *>(::LocalLock(HLOCAL(hndl)));

		if (buf)
		{
			UINT nTxtLen = lstrlen(buf); // размер текста в буфере

			// если новая добавляемая строка уже не влазит
			if (nTxtLen + nStrLen >= nTextLimit)
			{
				UINT nOversize = nTxtLen + nStrLen - nTextLimit;
				// надо удалять строки из начала, до тех пор, пока не начнёт влазить
				UINT nPos = 0;
				TCHAR *p = buf;

				// найдём, сколько строк надо удалить
				while (nPos <= nOversize)
				{
					TCHAR *pf = _tcschr(p, _T('\n'));

					if (pf) // если нашлось
					{
						pf++; // передвинем за найденный символ, чтобы и его включать
						nPos += (pf - p);
						p = pf;
					}
					else
					{
						// если не нашлось, то удалить надо всё
						nPos = nTxtLen;
						break;
					}
				}

				SendDlgItemMessage(IDC_EDIT_INFO, EM_SETSEL, 0, nPos);
				SendDlgItemMessage(IDC_EDIT_INFO, EM_REPLACESEL, FALSE, reinterpret_cast<LPARAM>(emptyStr)); // или заменить на пустую строку
				nTxtLen -= nPos; // после этого у нас строка в буфере должна уменьшиться на столько
			}

			SendDlgItemMessage(IDC_EDIT_INFO, EM_SETSEL, nTxtLen, nTxtLen);
			SendDlgItemMessage(IDC_EDIT_INFO, EM_REPLACESEL, FALSE, reinterpret_cast<LPARAM>(strOut));
			SendDlgItemMessage(IDC_EDIT_INFO, EM_SCROLLCARET, 0, 0);
			//GetDlgItem(IDC_EDIT_INFO)->Invalidate(FALSE);
		}

		::LocalUnlock(HLOCAL(hndl));
	}
}


