Правильная организация данных в коде

mable
Offline
Зарегистрирован: 09.10.2019

Доброго времени суток.

Разрабатываю цветомузыку на ESP32. И возник вопрос, как с точки зрения дальнейшего развития кода организовать хранение нескольких массивов с данными.

Есть несколько массивов с данными, для разных вариантов работы анализатора БПФ - 3 полосы, 8 полос, 16 полос. На данный момент по каждому варианту есть массивы: с перечнем частот, с множителями по каждой частоте. В планах добавить массивы по цветам частот, по средней амплитуде.

В итоге получается набор массивов разной размерности: cutOffs3Band[3], cutOffs8Band[8], cutOffs16Band[16], shift3Band[3], shift[8]Band и т.д.

Сейчас данные беру через еще один массив с указателями,  в котром прописал массивы с данными.  Так как будут еще добавляться разные наборы (на 6 полос, на 12 полос), то сразу сделал массив на 16 вариантов. Чтоб по имени массива и индексу, сразу понять, какие данные использую.

float shift3Band[3] =
{
  0.4, 1.0, 2.3
};

int cutOffs3Band[3] =
{
  250, 2200, 8000
};

float shift8Band[8] =
{
  0.4, 0.6, 0.7, 1.0, 1.4, 1.8, 2.1, 2.3
};

int cutOffs8Band[8] =
{
  //0.4 0.6 0.7  1     1.4   1.8   2.1     2.3
  125, 250, 500, 1000, 2000, 4000, 6000, 12000 // распределение по октавам
};

float shift16Band[16] =
{
  0.4, 0.5, 0.8, 1.0, 1.3, 1.0, 1.0, 1.0, 1.0, 1.1, 1.25, 1.4, 1.6, 1.8, 1.9, 2.0
};

int cutOffs16Band[16] =
{
  //0.3 0.6 0.8  1    1    1    1     1     1     1.1   1.25  1.4   1.6   1.8   1.9   2.0
  100, 250, 450, 565, 715, 900, 1125, 1400, 1750, 2250, 2800, 3150, 4000, 5000, 6400, 12500
};

int *cutOffsXBand[16] = 
{
  0, 0, cutOffs3Band, 0, 0, 0, 0,
  cutOffs8Band, 0, 0, 0, 0, 0, 0,
  0, cutOffs16Band
};

float *shiftXBand[16] = 
{
  0, 0, shift3Band, 0, 0, 0, 0,
  shift8Band, 0, 0, 0, 0, 0, 0,
  0, shift16Band
};

 

Может есть варианты удобней - правильней, в плане дальнейшего развития проекта. Хотелось бы простого обращения к некой структуре, по понятному имени, для получения необходимых данных. И чтоб другой человек быстро понял, как получить частоты среза или палитру по частотам и т.д.

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

В принципе, можно что-то типа такого:

float shift3Band[] =
{
  0.4, 1.0, 2.3
};

int cutOffs3Band[] =
{
  250, 2200, 8000
};



typedef enum
{
	atFloat,
	atInt
} ArrayType;

typedef struct
{
	ArrayType type;
	size_t size;
	void* ptr;

} ArrayInfo;

ArrayInfo infos[] = {
	{atFloat, sizeof(shift3Band)/sizeof(shift3Band[0]), &shift3Band},
	{atInt, sizeof(cutOffs3Band)/sizeof(cutOffs3Band[0]), &cutOffs3Band}
};

void setup()
{
	Serial.begin(9600);

	for(size_t i=0;i<sizeof(infos)/sizeof(infos[0]);i++)
	{
		switch(infos[i].type)
		{
			case atFloat:
			{
				float* f = (float*) infos[i].ptr;
				for(size_t k=0;k<infos[i].size;k++)
				{
					Serial.println(*f++);
				}
			}
			break;

			case atInt:
			{
				int* iVar = (int*) infos[i].ptr;
				for(size_t k=0;k<infos[i].size;k++)
				{
					Serial.println(*iVar++);
				}
			}
			break;
		}
	}
}

void loop()
{
}

Таким образом не надо заранее знать размер массива, помещённого в infos - всё делает sizeof. Единственное "но" - надо хранить тип массива, чтобы корректно выполнить преобразование типов. Надеюсь, пример понятен.

З.Ы. Не компилировал, написал навскидку.

nik182
Offline
Зарегистрирован: 04.05.2015

Зачем столько массивов ? Вам нужно 2. Для БПФ входные они же выходные. Дальше просто сумма гармоник в нужных пределах по частоте. Я б взял вообще 128 точек на БПФ преобразование. Благо памяти хватает.

rkit
Offline
Зарегистрирован: 23.11.2016

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

mable
Offline
Зарегистрирован: 09.10.2019

Для БПФ есть свои массивы. А вот для последующей обработки и нужны массивы с распределением по частотам - для 3х, 8, 16 полосных вариантов. Думаю еще  32 полосный добавить. К ним же нужны данные по корректировке уровня, чтоб по всем полосам на один уровень вывести амплитуду. Плюс соответствие цветов сделать. И чтоб просто передавать в функцию количество полос, а она уж брала из массивов нужные данные и делала, что нужно - разносила БПФ по частотам, корректировала уровень в каждой полосе, выдавала на ленту в нужных цветах и т.д.

b707
Offline
Зарегистрирован: 26.05.2017

Насколько я помню, в БПФ размеры всех массивов должны быть степенью двойки. Иначе это не Быстрое ПФ

mable
Offline
Зарегистрирован: 09.10.2019

Планируется множество режимов, потому и хочу сделать универсальное решение по анализу и отображению данных после БПФ. Включили режим спектроанализатора на 8 полос, передали в функцию, что сейчас нужно разбивать БПФ на 8 полос, он взял данные из нужного массива, на выходе получили пики по этим 8 полосам и передали дальше на отрисовку. А если завтра понадобится 6 полос, то чтоб просто добавили два массива с перечнем частот и поправками и снова все заработало. 

mable
Offline
Зарегистрирован: 09.10.2019

Это массивы с частотами и поправками по амплитуде, для самого БПФ есть пара своих массивов. 

Грубо говоря, есть функция, в которую я передаю, что хочу результат БПФ раскидать на 3 полосы, она лезет в массив cutOffs3Band, берет оттуда частоты а на выходе дает массив с 3 элементами. После этого уже из таблицы shift3Band берет данные для корректировки уровня. Ну и для отображения так же хочу добавить таблицы с вариантами по цветам.

Меня больше интересует, как правильно организовать хранение вот этих массивов. Чтоб при добавлении новых вариантов анализа или отображения было как можно меньше проблем. 

nik182
Offline
Зарегистрирован: 04.05.2015

Зарезирвировать один(!) массив с максимально возможной емкостью. Потом только брать из него столько, сколько надо.

Но судя по Вашим хотелкам, Вы совершенно не понимаете, что такое Фурье преобразование и как амплитуды полученных гармоник соотносятся с амплитудой исходного сигнала. Особенно порадовало "берет данные для корректировки уровня". Правильно выполненное преобразование и правильно набранные суммы гармоник по выбранным полосам частот автоматически дают амплитуды выходного сигнала, которые в крайнем случае требуют корректировки на выходную характеристику конечных элементов - для ламп накаливания и светодиодов будут разные выходные характеристики по светоотдаче.     

mable
Offline
Зарегистрирован: 09.10.2019

Попробую еще раз донести свою мысль.

Я не трогаю в данной теме БПФ. Вопрос лишь в том, как организовать правильно и красиво массивы с данными разной размерности. Тот вариант, который я использую сейчас в принципе решает все вопросы, если нужен будет анализ на другое число полос - просто добавляю массив по частотам + массив корректирующий, прописываю ссылки на них. Возможно, есть другие варианты, более правильные. Один из них уже предложили выше.

А теперь о "Правильно выполненное преобразование и правильно набранные суммы гармоник" - так поделитесь этим знанием, подскажите, как добиться того, чтоб розовый шум выдавал мне по всем полосам одинаковые значения. Вот такие у меня странные хотелки. )

nik182
Offline
Зарегистрирован: 04.05.2015

Вы уже почти ответили на вопрос. Делаем Фурье много полосным. Полосы делим на заданное количество групп так, что бы в каждую группу входило количество полос обратно пропорциональное частоте. Суммируем все гармоники в каждой группе. Для розового шума получаем одинаковые значения сумм, пропорциональные амплитуде входного сигнала. Тут большой вопрос с какой частотой оцифровывать входной сигнал и сколько времени, т.е. сколько точек иметь на выходе Фурье преобразования. Если цифровать сигнал АЦП то выходной сигнал - свет - всегда будет иметь задержку, чем больше точек Фурье преобразования, тем больше задержка, но точнее рассчитываются амплитуды в каждой группе.  Интереснее брать исходный сигнал в цифровом виде и делать предварительный анализ. По крайней мере в таком виде красные лампочки моргают точно при ударе по барабану, а не через заметную глазу задержку. 

       

mable
Offline
Зарегистрирован: 09.10.2019

Что то сумма всех частот по полосам, при розовом шуме(РШ) на входе, совсем не совпадает. 

Частота оцифровки 20 000, 512 семплов. Шаг получается 39 Гц. 

Сейчас я ищу максимальное значение среди всех частот от нижней границы до верхней.  При РШ на выходе получаю, с учетом поправочных коэффициентов из массива выше, одинаковую амплитуду по всем полосам. А если просто суммирую все входящие в полосу частоты, то разница в разы. Или я что то не так понял, или все же считать надо как то по другому. 

А задержка в 25 мс на оцифровку + 20 мс на БПФ и вывод на ленту не такая уж большая на мой взгляд, глаз не успевает заметить. Тем более, что оцифровка идет паралельно с БПФ и к моменту окончания анализа новая порция данных уже готова, то вполне бодро все отрабатывает.

nik182
Offline
Зарегистрирован: 04.05.2015

Сгенерировал случайный розовый шум в полосе 10 - 10000 Гц. Оцифровка 20000 Гц. Сделал FFT. Рассчитал энергию гармоник. Посчитал суммы в двух частотных группах 200-400 и 2000-4000 Гц. Количество точек в окнах разное, потому что FFT даёт равномерную сетку частот, а по определению розового шума энергия флуктуаций равна в одинаковых логарифмических окнах. Разница меньше 15%.  

Но как говориться против опыта не пойдёшь. Если у Вас всё нормально работает, то хорошо. Единственное что я не понял, как FFT можно делать одновременно с оцифровкой. Обработка FFT требует полного массива данных. Ей нельзя скормить постепенно поступающие данные. Или я чего то не знаю? Где можно почитать об этом?  

mable
Offline
Зарегистрирован: 09.10.2019

Вроде так и должно быть, пару ошибок у себя нашел - не правильно частоты по октавам брал. Но все равно не так хорошо получается. С одной стороны я понимаю, что должно быть все ок и мой метод далек от идеала. С другой - мне ведь математическая точность и не нужна, важно, чтоб на реальной музыке все отлично смотрелось, а здесь гораздо важней динамика и точность ритма, чтоб на каждый инструмент была реакция адекватная по световой картинке.

А по БПФ с сделал так. Сделал два буфера для сбора данных, по таймеру заполняю первый, как только нужное число семплов собрал - передаю этот буфер на обработку БПФ и переключаю сбор данных во второй буфер и т.д.

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

mable, мне кажется, сама идея с БПФ в данном случае порочна:

- во-первых, БПФ дает линейный масштаб по частоте, а нам нужен логарифмический,

- во-вторых, задержка в десятки мс - это очень много.

nik182
Offline
Зарегистрирован: 04.05.2015

Посмотрел что в мире делают по данной теме и наткнулся на http://wiki.openmusiclabs.com/wiki/ArduinoFFT специально ускоренная версия FFT для avr.

 http://wiki.openmusiclabs.com/wiki/FFTFunctions страница с описанием функций. Особенно порадовали функции fft_mag_log() - исправляет страшилку №1 коллеги andriano, и fft_mag_octave() - выдает сразу сумму магнитуд по  октавам, про которую я писал ранее. Всё хорошо документировано и расписано по временам выполнения.

P.S. Заглянул внутрь. Всё на avr ассемблере. Для ESP32 не подойдёт. Но теория обработки после FFT преобразования описана.  

mable
Offline
Зарегистрирован: 09.10.2019

Для ESP32 вот этот вариант хорош

http://www.robinscheibler.org/2017/12/12/esp32-fft.html

2 мс на 2048 отсчетов.

Изучаю пример по вашей ссылке, думаю, применю у себя их подход. )

 

nik182
Offline
Зарегистрирован: 04.05.2015
mable
Offline
Зарегистрирован: 09.10.2019

Я сейчас эту библиотеку и использую, но она гораздо медленней чем по ссылке выше. 

А по самой обработке семплов мне гораздо больше вот у этого дядьки нравится, у него я и взял идею с 2мя буферами для сбора данных, разделением частот по полосам - чтоб при изменении данных или частот, не надо было в коде ничего править и еще несколько. У него там три ролика по этой теме.

https://www.youtube.com/watch?v=f_zt7zdGJCA