\documentclass[a4paper,fontsize=14bp]{article}

\input{../common-preamble}
\input{../bmstu-preamble}
\input{../fancy-listings-preamble}
\numerationTop

\begin{document}
\thispagestyle{empty}
\makeBMSTUHeader

% ... работе, номер, тема, предмет, ?а, кто
\makeReportTitle{лабораторной}{3}{Звуковой эффект}{Микропроцессорные устройства обработки сигналов}{}{А.И. Германчук}
\newpage
\thispagestyle{empty}
\tableofcontents
\newpage
\pagestyle{fancy}
\section{Цель}
Целью работы является разработка и исследование программы обработки сигналов на языке программирования Си, реализующей звуковой эффект, определенный в индивидуальном задании (контроллер динамического диапазона, ограничитель).

\section{Описание звукового эффекта}
Контроллер динамического диапазона - это адаптивная регулировка динамического диапазона сигнала. Динамический диапазон - это соотношение между наибольшими и наименьшими значениями, которые может принять определенная величина. Существует несколько типов контроллеров. В данной лабораторной работе будет рассматриваться ограничитель. Назначение ограничителя состоит в том, чтобы обеспечить контроль над самыми высокими пиками сигнала, но при этом, как можно меньше изменять динамику сигнала. Это достигается за счёт использования характеристической кривой с бесконечным отношением \cite[с. 109]{dsp:dafx}. Ограничитель динамического диапазона подавляет громкость звуков, пересекающих заданный порог (рисунок \hrf{pic:limiter}).

\begin{figure}[H]
  \centering
  \def\svgwidth{100mm}
  \input{pics/02-dsp-03lab-limiter.pdf_tex}
  \caption{Иллюстрация работы ограничителя сигнала}
  \label{pic:limiter}
\end{figure}

Для достижения плавности кривой применяемого ослабления громкости используются такие параметры, как время атаки и высвобождения. Время атаки и время высвобождения соответствуют времени, за которое сигнал увеличивается или уменьшается до конечного значения (рисунок \hrf{pic:limiter-explain}).  На рисунке видно, что при отсутствии времени атаки и освобождения усиление применяется сразу, создавая неплавные «обрезанные» кривые (жёсткое урезание).

\begin{figure}[H]
  \centering
  \def\svgwidth{170mm}
  \input{pics/02-dsp-03lab-limiter-explain.pdf_tex}
  \caption{Иллюстрация влияния времени атаки и освобождения}
  \label{pic:limiter-explain}
\end{figure}

Ограничитель обычно используется для того, чтобы «поймать» самые громкие моменты источника, уменьшив их таким образом, чтобы защитить от нежелательных искажений и сохранить целостность общего баланса звука.

\section{Математическое описание ограничителя}
\label{sect:description}
Динамическая обработка выполняется усилительными устройствами, где коэффициент усиления автоматически регулируется уровнем входного сигнала. Динамическая обработка проходит в несколько этапов.
\begin{enumerate}
\item Сначала используется схема определения амплитуды/уровня по алгоритму PEAK \cite[с. 107]{dsp:dafx}.

В начале алгоритма значение переменной $X_{peak}$ равно нулю. Далее сравнивается модуль поступающего сигнала $|x(n)|$ с предыдущим значением $X_{peak}$. В случае если значение модуля поступающего сигнала больше, то значение $X_{peak}$ для текущего элемента вычисляется с использованием коэффициента $AT$, которое используется, как сокращение для времени атаки (attack time). В ином случае вычисление значения $X_{peak}$ для текущего элемента производится с помощью коэффициента $RT$, то есть времени освобождения (release time). Математическая формула описанного выше алгоритма

\begin{equation}
  X_{p}(n) = \begin{cases}
    (1 - AT) \times X_{p}(n - 1) + AT \times |X(n)|, |X(n)| > X_{p}(n - 1)\\
    (1 - RT) \times X_{p}(n - 1), |X(n)| \leq X_{p}(n - 1),
  \end{cases}
\end{equation}

  где
  \begin{itemize}
  \item [] $X_{p}$ - пиковое значение $X_{peak}$; 
  \item [] $AT$ – время атаки;
  \item [] $RT$ – время освобождения;
  \item [] $n$ – номер текущего элемента.
  \end{itemize}

\item После определения значения $X_{peak}$ выполняется функция для получения коэффициента усиления $f$ по формуле
  \begin{equation}
    f(n) = min \bigg( 1; \frac{lt}{X_{peak}n} \bigg),
  \end{equation}
  где
  \begin{itemize}
  \item [] $n$ – номер текущего элемента; 
  \item [] $lt$ – пороговое значение.
  \end{itemize}

\item После вычисления коэффициента усиления используется сглаживающий фильтр для предотвращения слишком резких изменений усиления. Для его реализации используется формула
\begin{equation}
  cfc = \begin{cases}
    AT, f(n) < f(n-1)\\
    RT, f(n) \geq f(n-1),
  \end{cases}
\end{equation}
где $n$ – номер текущего элемента.

\item Далее вычисляется множитель для регулирования входного сигнала по формуле
  \begin{equation}
g(n) = (1 - cfc) \times g(n - 1) + cfc \times f(n)    
  \end{equation}
\item После получения искомого множителя входного сигнала, его значение умножается на задержанный в буфере задержки входной сигнал. Сигнал задерживается для того, чтобы компенсировалась любая задержка при вычислении множителя \cite[с. 106]{dsp:dafx}.
\end{enumerate}

Результирующая комбинированная система изображена на рисунке \hrf{pic:limiter-scheme} \cite[с. 109]{dsp:dafx}.

\begin{figure}[H]
  \centering
  \def\svgwidth{165mm}
  \input{pics/02-dsp-03lab-limiter-scheme.pdf_tex}
  \caption{Принципиальная схема алгоритма работы ограничителя}
  \label{pic:limiter-scheme}
\end{figure}

\section{Алгоритм обработки сигналов}
Алгоритм реализован на основе раздела \hrf{sect:description}. В реализации алгоритма учитывается средняя частота дискретизации современных аудиофайлов 44,1 КГц. Алгоритм буферизует отсчёты на 6 миллисекунд, то есть если 44 отсчета - это одна милисекунда, то величина буфера должна быть $44 * 6 = 264$ отсчётов.

На вход алгоритма, вычисляющего значение коэффициента, принимается единичный отсчёт. Пороговое значение задаётся константой \code{LT} в теле функции.

\subsection{Основная программа \code{main}}
Функция \code{main} вызывается при запуске программы и реализует следующие шаги: 
\begin{enumerate}
\item инициализация кодека;
\item чтение из кодека текущего отсчета сигнала по прерыванию от интерфейса I2S \code{x}, \code{x = I2S2_W0_MSW_R};
\item вычисление и корректировка коэффициента g и сохранение текущего отсчёта в буфер;
\item получение выходного отсчета сигнала \code{y}, путём умножения буферизованного отсчёта на вычисленный на текущем шаге коэффициент \code{_smpy(g, leftBuffer[qRead++]};
\item смещение флагов чтения и записи данных в буфер, ожидание готовности контроллера прерываний;
\item запись значений обоих каналов обратно в контроллер I2S;
\item переход на шаг 2.
\end{enumerate}

\subsection{Функция ограничителя}
Фактически, функция ограничителя разделена на две части:
\begin{enumerate}
\item приём отсчёта, буферизация и вычисление коэффициента ограничителя;
\item чтение из очереди и умножение буферизованного отсчёта на коэффициент.
\end{enumerate}

Функция расчёта коэффициента реализует следующие шаги алгоритма:
\begin{enumerate}
\item получение входного значения \code{x};
\item получение модуля отсчёта;
\item определение коэффициента \code{cfc} для вычисления уровня;
\item вычисление уровня \code{xpeak}, \code{xpeak = (1 - cfc) * xpeak + coeff * x}; 
\item определение коэффициента усиления \code{f}, \code{f = min(1, LT / xpeak)};
\item определение коэффициента \code{cfc} для вычисления управляющего коэффициента \code{g = (1 - cfc) * g + cfc * f}; 
\item возврат коэффициента \code{g} из функции.
\end{enumerate}

Код, вызывающий функцию, кроме буферизации прочитанного значения (\code{leftBuffer[qWrite++] = left;}) осуществляет умножение ранее буферизованного отсчёта на текущее скорректированное значение коэффициента фильтра \code{out_left = _smpy(g, leftBuffer[qRead++])} и запись выходного отсчета на шину контроллера I2S \code{I2S2_W0_MSW_W = out_left;} 

\section{Выводы}
В результате выполнения работы была разработана программа обработки сигнала на языке программирования Си, реализующая звуковой эффект - контроллер динамического диапазона, а именно, ограничитель. В ходе работы были получены графики входных и выходных отсчетов и по ним исследован результат применения звукового эффекта на входной сигнал.
\nocite{gost:texts}
\newpage
\printbibliography[heading=bibintoc, title={Список литературы}, resetnumbers=1]

\newpage
\appendix
\section*{Приложения}
\addcontentsline{toc}{section}{Приложения}
\renewcommand{\thesubsection}{\Alph{subsection}}

\subsection{Полные листинги программ}
\label{appendix:fulls}
\begin{lstlisting}[language=C,style=CCodeStyle,caption={Функция вычисления коэффициента ограничителя}, label=code:prog-coeff]
Int16 oneCountCoefficient(Int16 x) {
	const Int16 LT = 28576;
	Int16 at_cfc = 9830;
	Int16 rt_cfc = 328;

    Int16 a = abs(x); // <@\lh{dkgreen}{модуль значения текущего отсчёта}@>
    Int16 cmp;
    xpeak = (a > xpeak) // <@\lh{dkgreen}{коэффициент x пиковое}@>
            ? (_smpy((32767 - at_cfc), xpeak) + _smpy(at_cfc, a))
            : _smpy((32767 - rt_cfc), xpeak);

    if (abs(LT) > abs(xpeak)) // <@\lh{dkgreen}{коэффициент cmp}@>
        cmp = (Int16)(((Int32) LT << 15) / ((Int32) xpeak));

    Int16 f = (cmp < 32767) ? cmp : 32767;
    Int16 cfc = (f < g) ? at_cfc : rt_cfc; // <@\lh{dkgreen}{коэффициент cfc}@>

    // <@\lh{dkgreen}{возврат вычисленного коэффициента g}@>
    return _smpy((32767 - cfc), g) + _smpy(cfc, f);
}
\end{lstlisting}

\begin{lstlisting}[language=C,style=CCodeStyle,caption={Полный листинг программы на языке С}, label=code:prog-c]
// DSP, board libraries
#include "C5515.h"
#include "gpio.h"
#include "i2c.h"
#include "i2s.h"
#include "stdio.h"
// <@\lh{dkgreen}{заголовок с функцией oneCountCoefficient}@>
#include "mylimiter.h"

#define AIC3204_I2C_ADDR 0x18
#define Rcv 0x08
#define Xmit 0x20
extern Int16 aic3204_stereo_in1();

volatile Int16 xpeak = 1;
volatile Int16 g = 1;

Int16 oneCountCoefficient(Int16 x);

void main(void) {
    // <@\lh{dkgreen}{инициализация}@>
    c5515_init();

    // <@\lh{dkgreen}{конфигурирование параллельного порта}@>
    SYS_EXBUSSEL &= ~0x7000;
    SYS_EXBUSSEL |= 0x1000;

    // <@\lh{dkgreen}{конфигурирование последовательного порта}@>
    SYS_EXBUSSEL &= ~0x0C00;
    SYS_EXBUSSEL |= 0x0400;
    
    c5515_GPIO_init();
    c5515_GPIO_setDirection(GPIO10, GPIO_OUT);
    c5515_GPIO_setOutput(GPIO10, 1);    // <@\lh{dkgreen}{вывод AIC3201 из reset}@>
    I2C_init();                         // <@\lh{dkgreen}{инициализация I2C}@>

    // <@\lh{dkgreen}{I2S настройки}@>
    I2S2_SRGR = 0x0015;
    I2S2_ICMR = 0x0028;
    I2S2_CR = 0x8012;

    // Режим стерео входа
    aic3204_stereo_in1();

    #define BUF_LENGTH 264
    Int16 left, right;   // <@\lh{dkgreen}{отсчеты до примененого эффекта}@>
    Int16 out_left = 0;  // <@\lh{dkgreen}{отсчеты после примененого эффекта}@>
    Int16 leftBuffer[BUF_LENGTH];       // <@\lh{dkgreen}{буфер}@>
    Int16 qWrite = 0;
    Int16 qRead = -255;

    while(1){
        // <@\lh{dkgreen}{Ожидание прерывания по получению отсчёта}@>
        while ((Rcv & I2S2_IR) == 0);   
        left = I2S2_W0_MSW_R;  // <@\lh{dkgreen}{16-битное значение левого канала}@>
        right = I2S2_W1_MSW_R; // <@\lh{dkgreen}{16-битное значение правого канала}@>

        g = oneCountCoefficient(left);  // <@\lh{dkgreen}{текущий коэффициент}@>
        leftBuffer[qWrite++] = left;    // <@\lh{dkgreen}{буферизация отсчёта}@>
        if  (qWrite == BUF_LENGTH)
			qWrite = 0;    // <@\lh{dkgreen}{не дать флагу записи переполниться}@>

        if (qRead >= 0) {
            // <@\lh{dkgreen}{прочитать из буфера и умножить на коэффициент}@>  
            out_left = _smpy(g, leftBuffer[qRead++]); 
            if  (qRead == BUF_LENGTH)
				qRead = 0; // <@\lh{dkgreen}{не дать флагу чтения переполниться}@>
        } else {
            ++qRead;
          }
          
        while ((Xmit & I2S2_IR) == 0);  // <@\lh{dkgreen}{Ждём контроллер прерывания}@>

        // <@\lh{dkgreen}{записать изменённое}@>
        I2S2_W0_MSW_W = out_left;

        // <@\lh{dkgreen}{записать не изменённое}@>
        I2S2_W1_MSW_W = right;
    }
}
\end{lstlisting}



\end{document}
