diff options
author | Roland Reichwein <mail@reichwein.it> | 2025-01-04 11:28:20 +0100 |
---|---|---|
committer | Roland Reichwein <mail@reichwein.it> | 2025-01-04 11:28:20 +0100 |
commit | b2c35cdf69a9084806ac7930cf4475980d596cf6 (patch) | |
tree | b74b8f2ee2c66c59f7385407cfc34c2a0a16961f /PCM.cpp | |
parent | aaafcea7e26791acbf5b9612e3fb396edcdfcc8f (diff) |
Separate out implementation from headers
Diffstat (limited to 'PCM.cpp')
-rw-r--r-- | PCM.cpp | 149 |
1 files changed, 149 insertions, 0 deletions
@@ -1 +1,150 @@ #include "PCM.h" + +PCM::PCM(): m_phase(1000000) +{ + // prepare the sample + std::string data_s = Reichwein::File::getFile("media/click.s16le"); + 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)) + { + throw std::runtime_error("PCM could not be started"); + } + + 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); +} + +void PCM::click() +{ + snd_pcm_sframes_t delay; + if (0 > snd_pcm_delay(handle, &delay)) { + } + + m_phase = - click_latency_frames + delay; +} + +// generate 1 buffer size +void PCM::generate() +{ + int i; + + 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; +} + |