цифровая электроника
вычислительная техника
встраиваемые системы

 

Синтезатор звуковых сигналов на Arduino своими руками

Автор: Mike(admin) от 21-02-2020, 06:35

Большинство из вас уже знают функцию tone() из стандартной библиотеки Arduino. Но она просто генерирует прямоугольный сигнал и звучит довольно скучно.


Синтезатор звуковых сигналов на Arduino своими руками

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


В этом проекте мы используем таймер/счетчик 2 микроконтроллера ATmega328 для генерации сигнала ШИМ. После фильтрации с помощью фильтра нижних частот, состоящего из резисторов и конденсаторов, мы получаем синусоидальный, пилообразный или прямоугольный сигнал волну с программируемой частотой и скважностью. Модулируя с помощью ADSR-огибающей, можно даже имитировать любой музыкальный инструмент и играть какую-либо мелодию. Схема простого синтезатора аудиосигналов представлена на следующем изображении.


Синтезатор звуковых сигналов на Arduino своими руками

Далее представлен код для синтезатора на Arduino, состоящий из трех файлов: скетча chimes.ino с примером мелодии и движка синтезатора, содержащегося в файлах chimes.cpp и chimes.h.


chimes.ino



#include "chimes.h"
using namespace Chimes;
// Сумма значений ADSR не должна превышать 100%
uint8_t envelope[] = {
	0,  // атака[%]
	20, //спад[%]
	0,  //сустейн[%]
	80, //высвобождение[%]
	16  //уровень сустейна 1..32
};

void setup()
{
	init(
		TRI, //TRI: Треугольник, RECT: Прямоугольник
		50,  // коэффициент заполнения 0..100%, имеет значение только для треугольника и прямоугольника
		envelope);
}

uint16_t melody[][2] = {{330, 1000}, {415, 1000}, {370, 1000}, {247, 1000}, {0, 1000}, {330, 1000}, {370, 1000}, {415, 1000}, {330, 1000}, {0, 1000}, {415, 1000}, {330, 1000}, {370, 1000}, {247, 1000}, {0, 1000}, {247, 1000}, {370, 1000}, {415, 1000}, {330, 1000}};

void loop()
{
	static int i = 0;
	if (i < 19 && !isPlaying())
	{
		play(melody[i][0], melody[i][1]);
		i++;
	}
}

chimes.h



#ifndef CHIMES_H
#define CHIMES_H
#include "Arduino.h"

enum waveform
{
	SINE, // Синус
	RECT, // Прямоугольник 
	TRI,  // Треугольник
	PAUSE // Внутренняя переменная, не используется
};
#define MAX_VOLUME 32

namespace Chimes
{
void init(uint8_t waveform = SINE, uint8_t duty_cycle = 50, uint8_t *envelope = NULL);
void play(uint16_t freq, uint16_t duration);

// Возвращает true во время воспроизведения ноты
boolean isPlaying();
}

#endif

chimes.cpp



#include <Math.h>
#include "chimes.h"

#define ISR_CYCLE 16 //16s

char strbuf[255];
uint16_t ADSR_default[] = {0, 0, 100, 0, MAX_VOLUME};
uint16_t ADSR_env[5];
uint16_t nSamples;
uint8_t adsrPhase;
uint32_t tPeriod;
uint8_t *samples;
uint8_t *_envelope, _waveform, _duty_cycle;
uint16_t &_sustain_lvl = ADSR_env[4];

enum ADSR_phase
{
	ATTACK,
	DECAY,
	SUSTAIN,
	RELEASE
};

namespace Chimes
{
void init(uint8_t waveform, uint8_t duty_cycle, uint8_t *envelope)
{
	Serial.begin(115200);
	//PWM Signal generation
	DDRB |= (1 << PB3) + (1 << PB0);				  //OC2A, вывод 11
	TCCR2A = (1 << WGM21) + (1 << WGM20);			  // быстрый ШИМ
	TCCR2A |= (0 << COM2A0) + (1 << COM2A1);		  // Установка OC2A для сравнения, очистить OC2A в нижней части (режим инвертирования)
	TCCR2B = (0 << CS22) + (0 << CS21) + (1 << CS20);
	samples = (uint8_t *)malloc(0);
	_waveform = waveform;
	_duty_cycle = duty_cycle;
	_envelope = envelope;
}

void play(uint16_t freq, uint16_t duration)
{
	uint8_t waveform = _waveform;
	// инициализация adsr в соответствии с длиной ноты
	for (int i = 0; i < 4; i++)
	{
		if (_envelope)
		{
			ADSR_env[i] = (uint32_t)_envelope[i] * duration / 100;
		}
		else
		{
			ADSR_env[i] = (uint32_t)ADSR_default[i] * duration / 100;
		}
		//Serial.println(ADSR_env[i]);
	}
	ADSR_env[4] = _envelope ? _envelope[4] : MAX_VOLUME;
	//Serial.println(ADSR_env[4]);

	if (freq == 0)
	{ // пауза
		tPeriod = ISR_CYCLE * 100;
		waveform = PAUSE;
	}
	else
		tPeriod = 1E6 / freq;

	nSamples = tPeriod / ISR_CYCLE;
	realloc(samples, nSamples);
	uint16_t nDuty = (_duty_cycle * nSamples) / 100;

	switch (waveform)
	{
	case SINE: // Синус
		for (int i = 0; i < nSamples; i++)
		{
			samples[i] = 128 + 127 * sin(2 * PI * i / nSamples);
		}
		break;

	case TRI: // Треугольник
		for (int16_t i = 0; i < nSamples; i++)
		{
			if (i < nDuty)
			{
				samples[i] = 255 * (double)i / nDuty; //Rise
			}
			else
			{
				samples[i] = 255 * (1 - (double)(i - nDuty) / (nSamples - nDuty)); //Fall
			}
		}
		break;
	case RECT: // Прямоугольник
		for (int16_t i = 0; i < nSamples; i++)
		{
			i < nDuty ? samples[i] = 255 : samples[i] = 0;
		}
		break;
	case PAUSE:
		memset(samples, 0, nSamples);
	}
	TIMSK2 = (1 << TOIE2);
	/*for(uint16_t i = 0; i < nSamples; i++) {
		sprintf(strbuf, "%d: %d", i, samples[i]);
		Serial.println(strbuf);
	}*/
}

// Возвращает true во время воспроизведения ноты
boolean isPlaying()
{
	return (1 << TOIE2) & TIMSK2;
}
} // namespace Chimes

// Вызывается каждые 16 секунд, когда переполнен TIMER1
ISR(TIMER2_OVF_vect)
{
	static uint32_t adsr_timer, adsr_time;
	static uint16_t cnt; // Счетчик индекса
	static uint8_t sustain_lvl, vol;

	// Установка OCR2A на следующее значение в массиве выборок, это соответственно изменит рабочий цикл
	OCR2A = vol * samples[cnt] / MAX_VOLUME;
	if (cnt < nSamples - 1)
	{
		cnt++;
	}
	else
	{
		cnt = 0;
		adsr_timer += tPeriod;
		if (adsr_timer >= 10000)
		{ //every 10 millisecond
			adsr_timer = 0;

			switch (adsrPhase)
			{
			case ATTACK:
				if (ADSR_env[ATTACK])
				{
					vol = MAX_VOLUME * (float)adsr_time / ADSR_env[ATTACK];
					if (vol == MAX_VOLUME)
					{
						adsrPhase = DECAY;
						adsr_time = 0;
					}
				}
				else
				{
					adsrPhase = DECAY;
					vol = MAX_VOLUME;
					adsr_time = 0;
				}
				break;

			case DECAY:
				if (ADSR_env[DECAY])
				{
					sustain_lvl = _sustain_lvl;
					vol = MAX_VOLUME - (MAX_VOLUME - _sustain_lvl) * (float)adsr_time / ADSR_env[DECAY];
					if (vol <= sustain_lvl)
					{
						adsr_time = 0;
						adsrPhase = SUSTAIN;
					}
				}
				else
				{
					adsrPhase = SUSTAIN;
					sustain_lvl = MAX_VOLUME;
					adsr_time = 0;
				}
				break;

			case SUSTAIN:
				if (adsr_time > ADSR_env[SUSTAIN])
				{
					adsrPhase = RELEASE;
					adsr_time = 0;
				}

				break;
			case RELEASE:
				if (ADSR_env[RELEASE])
				{
					vol = sustain_lvl * (1 - (float)adsr_time / ADSR_env[RELEASE]);
					if (vol == 0)
					{
						adsr_time = 0;
						TIMSK2 = (0 << TOIE2);
						adsrPhase = ATTACK;
					}
				}
				else
				{
					adsrPhase = ATTACK;
					vol = 0;
					adsr_time = 0;
					TIMSK2 = (0 << TOIE2);
				}
				break;
			}
			adsr_time += 10;
		}
	}
}



© digitrode.ru


Теги: Arduino, аудио




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

Комментарии:

Оставить комментарий