\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}