#include "PCM.h" PCM::PCM(Config& config): m_config(config), m_phase(1000000) { // prepare the sample std::string data_s = Reichwein::File::getFile(m_config.get_click_data_filename()); m_data.resize(data_s.size() / 2); // src is in bytes memcpy(m_data.data(), data_s.data(), data_s.size()); // non-blocking if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { throw std::runtime_error(fmt::format("Playback open error: {}", snd_strerror(err))); } if ((err = snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 1, f_sample, 1, pcm_latency_us)) < 0) { // latency in us throw std::runtime_error(fmt::format("Playback open error: {}", snd_strerror(err))); } generate(); npfd = snd_pcm_poll_descriptors_count(handle); if (npfd < 0) { throw std::runtime_error("snd_pcm_poll_descriptors_count() failed"); } pfd = (struct pollfd *)malloc(npfd * sizeof(struct pollfd)); if (pfd == nullptr) { throw std::runtime_error("alloca() error for PCM"); } if (0 > snd_pcm_poll_descriptors(handle, pfd, npfd)) { throw std::runtime_error("snd_pcm_poll_descriptors() failure"); } if (0 > snd_pcm_start(handle)) { // On raspberry, this results in error. However, seems to generally work without log_cout << "PCM could not be started" << std::endl; } if (npfd != 1) { std::cout << "Warning: " << std::to_string(npfd) << " poll fds for pcm" << std::endl; } else if (fd() <= 2) { std::cout << "Warning: Bad PCM fd: " << std::to_string(fd()) << std::endl; } } PCM::~PCM() { // pass the remaining samples, otherwise they're dropped in close err = snd_pcm_drain(handle); if (err < 0) std::cerr << fmt::format("snd_pcm_drain failed: {}", snd_strerror(err)) << std::endl; snd_pcm_close(handle); free(pfd); } // offset > 0: play earlier // offset < 0: play later void PCM::click(std::chrono::duration offset) { snd_pcm_sframes_t delay; if (0 > snd_pcm_delay(handle, &delay)) { } int64_t offset_frames{static_cast(offset.count() * f_sample)}; m_phase = - click_latency_frames + delay + offset_frames; if (m_phase > 0) { log_cout << fmt::format("Warning: Click missed: Phase = {}", m_phase) << std::endl; } } // generate 1 buffer size void PCM::generate() { int i; if (m_config.get_output() == 0) { for (i = 0; i < nframes; i++) { buffer[i] = 0; } m_phase += nframes; } else { for (i = 0; i < nframes; i++) { if (m_phase < 0 || m_phase >= m_data.size()) { buffer[i] = 0; } else { buffer[i] = m_data[m_phase]; } m_phase++; } } } int PCM::fd() { return pfd->fd; } // write from buffer to ALSA PCM void PCM::write() { snd_pcm_sframes_t written = snd_pcm_writei(handle, buffer, nframes); if (written < 0) { if (written == -EPIPE) { std::cout << "Warning: PCM underrun" << std::endl; } std::cout << "Recovering." << std::endl; written = snd_pcm_recover(handle, written, 0); } if (written < 0) { throw std::runtime_error("snd_pcm_writei failed: "s + snd_strerror(written)); } if (written != nframes) { std::cout << "Warning: written " << std::to_string(written) << " frames instead of "<< std::to_string(nframes) << std::endl; } snd_pcm_sframes_t avail; snd_pcm_sframes_t delay; if (0 > snd_pcm_avail_delay(handle, &avail, &delay)) { log_cout << "Error detecting avail and delay" << std::endl; } else { debug_cout << fmt::format("Delay: {}, avail. buffer; {} frames", delay, avail) << std::endl; } generate(); } bool PCM::wait_for_event() { int result = poll(pfd, npfd, 10000); if (result > 0) { // event return true; } else if (result == 0) { // timeout return false; } else { throw std::runtime_error("Poll unsuccessful"); } } bool PCM::write_available() { snd_pcm_sframes_t result = snd_pcm_avail(handle); if (0 > result) { std::cerr << "Error: snd_pcm_avail()" << std::endl; //throw std::runtime_error("snd_pcm_avail()"); } return result >= nframes; }