Приветствую всех. Впервые столкнулся с проблемой , которую не смог решить гуглом и чтением существующих тем на форумах. Итак делаю проект барабанного триггерного модуля , который воспроизводит звук бас-бочки ( звук в формате wav сохранен в памяти программ ) по сигналу с триггера ( как это выглядит на практике можно посмотреть тут : . На данный момент проект существует на платформе Teensy 3.2 + Teensy Audio Board и работает прекрасно . Решил перенести на ESP32 в связке с ЦАП pcm5102 . Поскольку для ESP32 нет аудио-библиотеки , которая бы работала подобно Teensy Audio Library пришлось нужную функцию написать самому. Вернее адаптировать под себя найденный пример на просторах интернета. Cуть проста - инициализируется интерфейс I2S , которому в loop() передается порциями данные аудио из wav файла , и когда все данные переданы воспроизведение останавливается сбросом флага. Так вот каждый раз уже после вывода всех аудио данных звучит довольно ощутимый щелчек ( аудио файл и снимок из cubase ниже ) , как при отключении усилителя обычно происходит. Так же и в начале есть подобный эффект , но гораздо менее ощутимый и слышеш только в наушниках ( семпл бас-бочки имеет плавное нарастание и спад громкости в начале и конце соотв. так что этот артефакт результат не самого аудио файла , а именно аппаратной или программной части ESP32 ) . Так вот у меня вопрос - чем это вызванно и как с этим бороться ? В документации по I2S на сайте Espressif я не нашел функции I2S.Mute() или что нибудь подобное . Кроме того попутный вопрос - как правильно "наложить " воспроизведение двух таких семплов друг на друга со смещением по времени . Т.е. допустим у меня уже запустилось воспроизведение семпла один раз и пока оно длится приходит второй сигнал от триггера и нужно запустить еще раз вопроизведение этого же семпла . Изначально я думал в этой ситуации при повторном срабатывании триггера просто возвращать указатель на аудио-данные wav файла в начало и делать так кадый раз пока не перестанут идти сигналы с триггера и файл просто не доиграет до конца. Но теперь мне кажется , что это не лучшая идея т.к. резкое прерывание воспроизведения будет вызывать подобные искажения с щелчками или еще чем похуже. Спасибо заранее за помощь. Звуковой пример , записан с выхода ЦАП в линейный вход звуковой карты : https://on.soundcloud.com/drGnZ Код (C++): [== C++ ==] //------------------------------------------------------------------------------------------------------------------------ #include "driver/i2s.h" #include "Kick.h" // файл wav //------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------ bool isPlaing = true ; // флаг состояния воспроизведения ( играет/не играет ) static const i2s_port_t i2s_num = I2S_NUM_0; // i2s номер порта unsigned const char* TheData; // указатель на данные wav файла uint32_t DataIdx=0; // значение смещения указателя "TheData" в процессе передачи порции данных интерфейсу I2S const uint8_t* nameWav ; struct WavHeader_Struct { // RIFF Section char RIFFSectionID[4]; // "RIFF" uint32_t Size; // Size of entire file less 8 char RiffFormat[4]; // "WAVE" // Format Section char FormatSectionID[4]; // letters "fmt" uint32_t FormatSize; // Size of format section less 8 uint16_t FormatID; // 1=uncompressed PCM uint16_t NumChannels; // 1=mono,2=stereo uint32_t SampleRate; // 44100, 16000, 8000 etc. uint32_t ByteRate; // =SampleRate * Channels * (BitsPerSample/8) uint16_t BlockAlign; // =Channels * (BitsPerSample/8) uint16_t BitsPerSample; // 8,16,24 or 32 // Data Section char DataSectionID[4]; // The letters "data" uint32_t DataSize; // Size of the data that follows }WavHeader; //------------------------------------------------------------------------------------------------------------------------ uint32_t currenttime ; uint32_t looptime ; //------------------------------------------------------------------------------------------------------------------------ // I2S configuration structures static const i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), .sample_rate = 44100, // Note, this will be changed later .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // high interrupt priority .dma_buf_count = 8, // 8 buffers .dma_buf_len = 1024, // 1K per buffer, so 8K of buffer space .use_apll=0, .tx_desc_auto_clear= true, .fixed_mclk=-1 }; // These are the physical wiring connections to our I2S decoder board/chip from the esp32, there are other connections // required for the chips mentioned at the top (but not to the ESP32), please visit the page mentioned at the top for // further information regarding these other connections. static const i2s_pin_config_t pin_config = { .bck_io_num = 26, // The bit clock connectiom, goes to pin 27 of ESP32 .ws_io_num = 25, // Word select, also known as word select or left right clock .data_out_num = 27, // Data out from the ESP32, connect to DIN on 38357A .data_in_num = I2S_PIN_NO_CHANGE // we are not interested in I2S data into the ESP32 }; //------------------------------------------------------------------------------------------------------------------------ void setup() { memcpy(&WavHeader,&rawData,44); // копируем "заголовочную" часть wav файла в структуру i2s_driver_install(i2s_num, &i2s_config, 0, NULL); // Инициализация I2S i2s_set_pin(i2s_num, &pin_config); // Назначаем пины I2S i2s_set_sample_rates(i2s_num, WavHeader.SampleRate); TheData=rawData; nameWav=TheData+44; // смешаем указатель на начало аудио-данных wav файла } void loop() { playWav(); } void playWav(void){ size_t BytesWritten; while(DataIdx!=WavHeader.DataSize && isPlaing ) { i2s_write(i2s_num,TheData+DataIdx,4,&BytesWritten,1); // выводим данные на I2S , если звуковые данные являются представлением двух байт на каждный канал , // мы отдаем интерфейсу четыре байта за один раз DataIdx+=4; // смещение указателя на четыре байта в массиве данных wav if (DataIdx>=WavHeader.DataSize){ // если вывели все данные // возвращаем указатель в началао массива данных DataIdx=0; isPlaing=false; // сбрасываем флаг для остановки воспроизведения . Если не сбрасывать , файл будет воспроизводиться зацикленно } } }