summaryrefslogtreecommitdiffhomepage
path: root/PCM.cpp
blob: 8c0e01fee130eec589357d00b17bf41829018485 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#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);
}

// offset > 0: play earlier
// offset < 0: play later
void PCM::click(std::chrono::duration<double> offset)
{
  snd_pcm_sframes_t delay;
  if (0 > snd_pcm_delay(handle, &delay)) {
  }

  int64_t offset_frames{static_cast<int64_t>(offset.count() * f_sample)};
  m_phase = - click_latency_frames + delay + offset_frames;

  if (m_phase > 0) {
    log_cout << fmt::format("phase = {}", m_phase) << std::endl;
  }
}

// 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;
}