Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

375 lines
10KB

  1. /*
  2. ZynAddSubFX - a software synthesizer
  3. AlsaEngine.cpp - ALSA Driver
  4. Copyright 2009, Alan Calvert
  5. 2014, Mark McCurry
  6. This program is free software; you can redistribute it and/or modify
  7. it under the terms of version 2 of the GNU General Public License
  8. as published by the Free Software Foundation.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License (version 2 or later) for more details.
  13. You should have received a copy of the GNU General Public License (version 2)
  14. along with this program; if not, write to the Free Software Foundation,
  15. Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  16. */
  17. #include <iostream>
  18. #include <cmath>
  19. using namespace std;
  20. #include "../Misc/Util.h"
  21. #include "../Misc/Config.h"
  22. #include "InMgr.h"
  23. #include "AlsaEngine.h"
  24. #include "Nio.h"
  25. AlsaEngine::AlsaEngine(const SYNTH_T &synth)
  26. :AudioOut(synth)
  27. {
  28. audio.buffer = new short[synth.buffersize * 2];
  29. name = "ALSA";
  30. audio.handle = NULL;
  31. midi.handle = NULL;
  32. midi.alsaId = -1;
  33. midi.pThread = 0;
  34. }
  35. AlsaEngine::~AlsaEngine()
  36. {
  37. Stop();
  38. delete[] audio.buffer;
  39. }
  40. void *AlsaEngine::_AudioThread(void *arg)
  41. {
  42. return (static_cast<AlsaEngine *>(arg))->AudioThread();
  43. }
  44. void *AlsaEngine::AudioThread()
  45. {
  46. set_realtime();
  47. return processAudio();
  48. }
  49. bool AlsaEngine::Start()
  50. {
  51. return openAudio() && openMidi();
  52. }
  53. void AlsaEngine::Stop()
  54. {
  55. if(getMidiEn())
  56. setMidiEn(false);
  57. if(getAudioEn())
  58. setAudioEn(false);
  59. snd_config_update_free_global();
  60. }
  61. void AlsaEngine::setMidiEn(bool nval)
  62. {
  63. if(nval)
  64. openMidi();
  65. else
  66. stopMidi();
  67. }
  68. bool AlsaEngine::getMidiEn() const
  69. {
  70. return midi.handle;
  71. }
  72. void AlsaEngine::setAudioEn(bool nval)
  73. {
  74. if(nval)
  75. openAudio();
  76. else
  77. stopAudio();
  78. }
  79. bool AlsaEngine::getAudioEn() const
  80. {
  81. return audio.handle;
  82. }
  83. void *AlsaEngine::_MidiThread(void *arg)
  84. {
  85. return static_cast<AlsaEngine *>(arg)->MidiThread();
  86. }
  87. void *AlsaEngine::MidiThread(void)
  88. {
  89. snd_seq_event_t *event;
  90. MidiEvent ev;
  91. set_realtime();
  92. while(snd_seq_event_input(midi.handle, &event) > 0) {
  93. //ensure ev is empty
  94. ev.channel = 0;
  95. ev.num = 0;
  96. ev.value = 0;
  97. ev.type = 0;
  98. if(!event)
  99. continue;
  100. switch(event->type) {
  101. case SND_SEQ_EVENT_NOTEON:
  102. if(event->data.note.note) {
  103. ev.type = M_NOTE;
  104. ev.channel = event->data.note.channel;
  105. ev.num = event->data.note.note;
  106. ev.value = event->data.note.velocity;
  107. InMgr::getInstance().putEvent(ev);
  108. }
  109. break;
  110. case SND_SEQ_EVENT_NOTEOFF:
  111. ev.type = M_NOTE;
  112. ev.channel = event->data.note.channel;
  113. ev.num = event->data.note.note;
  114. ev.value = 0;
  115. InMgr::getInstance().putEvent(ev);
  116. break;
  117. case SND_SEQ_EVENT_KEYPRESS:
  118. ev.type = M_PRESSURE;
  119. ev.channel = event->data.note.channel;
  120. ev.num = event->data.note.note;
  121. ev.value = event->data.note.velocity;
  122. InMgr::getInstance().putEvent(ev);
  123. break;
  124. case SND_SEQ_EVENT_PITCHBEND:
  125. ev.type = M_CONTROLLER;
  126. ev.channel = event->data.control.channel;
  127. ev.num = C_pitchwheel;
  128. ev.value = event->data.control.value;
  129. InMgr::getInstance().putEvent(ev);
  130. break;
  131. case SND_SEQ_EVENT_CONTROLLER:
  132. ev.type = M_CONTROLLER;
  133. ev.channel = event->data.control.channel;
  134. ev.num = event->data.control.param;
  135. ev.value = event->data.control.value;
  136. InMgr::getInstance().putEvent(ev);
  137. break;
  138. case SND_SEQ_EVENT_PGMCHANGE:
  139. ev.type = M_PGMCHANGE;
  140. ev.channel = event->data.control.channel;
  141. ev.num = event->data.control.value;
  142. InMgr::getInstance().putEvent(ev);
  143. break;
  144. case SND_SEQ_EVENT_RESET: // reset to power-on state
  145. ev.type = M_CONTROLLER;
  146. ev.channel = event->data.control.channel;
  147. ev.num = C_resetallcontrollers;
  148. ev.value = 0;
  149. InMgr::getInstance().putEvent(ev);
  150. break;
  151. case SND_SEQ_EVENT_PORT_SUBSCRIBED: // ports connected
  152. if(true)
  153. cout << "Info, alsa midi port connected" << endl;
  154. break;
  155. case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: // ports disconnected
  156. if(true)
  157. cout << "Info, alsa midi port disconnected" << endl;
  158. break;
  159. case SND_SEQ_EVENT_SYSEX: // system exclusive
  160. case SND_SEQ_EVENT_SENSING: // midi device still there
  161. break;
  162. default:
  163. if(true)
  164. cout << "Info, other non-handled midi event, type: "
  165. << (int)event->type << endl;
  166. break;
  167. }
  168. snd_seq_free_event(event);
  169. }
  170. return NULL;
  171. }
  172. bool AlsaEngine::openMidi()
  173. {
  174. if(getMidiEn())
  175. return true;
  176. int alsaport;
  177. midi.handle = NULL;
  178. if(snd_seq_open(&midi.handle, "default", SND_SEQ_OPEN_INPUT, 0) != 0)
  179. return false;
  180. string clientname = "ZynAddSubFX";
  181. string postfix = Nio::getPostfix();
  182. if (!postfix.empty())
  183. clientname += "_" + postfix;
  184. if(Nio::pidInClientName)
  185. clientname += "_" + os_pid_as_padded_string();
  186. snd_seq_set_client_name(midi.handle, clientname.c_str());
  187. alsaport = snd_seq_create_simple_port(
  188. midi.handle,
  189. "ZynAddSubFX",
  190. SND_SEQ_PORT_CAP_WRITE
  191. | SND_SEQ_PORT_CAP_SUBS_WRITE,
  192. SND_SEQ_PORT_TYPE_SYNTH);
  193. if(alsaport < 0)
  194. return false;
  195. pthread_attr_t attr;
  196. pthread_attr_init(&attr);
  197. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  198. pthread_create(&midi.pThread, &attr, _MidiThread, this);
  199. return true;
  200. }
  201. void AlsaEngine::stopMidi()
  202. {
  203. if(!getMidiEn())
  204. return;
  205. snd_seq_t *handle = midi.handle;
  206. if((NULL != midi.handle) && midi.pThread)
  207. pthread_cancel(midi.pThread);
  208. midi.handle = NULL;
  209. if(handle)
  210. snd_seq_close(handle);
  211. }
  212. short *AlsaEngine::interleave(const Stereo<float *> &smps)
  213. {
  214. /**\todo TODO fix repeated allocation*/
  215. short *shortInterleaved = audio.buffer;
  216. memset(shortInterleaved, 0, bufferSize * 2 * sizeof(short));
  217. int idx = 0; //possible off by one error here
  218. double scaled;
  219. for(int frame = 0; frame < bufferSize; ++frame) { // with a nod to libsamplerate ...
  220. scaled = smps.l[frame] * (8.0f * 0x10000000);
  221. shortInterleaved[idx++] = (short int)(lrint(scaled) >> 16);
  222. scaled = smps.r[frame] * (8.0f * 0x10000000);
  223. shortInterleaved[idx++] = (short int)(lrint(scaled) >> 16);
  224. }
  225. return shortInterleaved;
  226. }
  227. bool AlsaEngine::openAudio()
  228. {
  229. if(getAudioEn())
  230. return true;
  231. int rc = 0;
  232. /* Open PCM device for playback. */
  233. audio.handle = NULL;
  234. rc = snd_pcm_open(&audio.handle, "hw:0",
  235. SND_PCM_STREAM_PLAYBACK, 0);
  236. if(rc < 0) {
  237. fprintf(stderr,
  238. "unable to open pcm device: %s\n",
  239. snd_strerror(rc));
  240. return false;
  241. }
  242. /* Allocate a hardware parameters object. */
  243. snd_pcm_hw_params_alloca(&audio.params);
  244. /* Fill it in with default values. */
  245. snd_pcm_hw_params_any(audio.handle, audio.params);
  246. /* Set the desired hardware parameters. */
  247. /* Interleaved mode */
  248. snd_pcm_hw_params_set_access(audio.handle, audio.params,
  249. SND_PCM_ACCESS_RW_INTERLEAVED);
  250. /* Signed 16-bit little-endian format */
  251. snd_pcm_hw_params_set_format(audio.handle, audio.params,
  252. SND_PCM_FORMAT_S16_LE);
  253. /* Two channels (stereo) */
  254. snd_pcm_hw_params_set_channels(audio.handle, audio.params, 2);
  255. audio.sampleRate = synth.samplerate;
  256. snd_pcm_hw_params_set_rate_near(audio.handle, audio.params,
  257. &audio.sampleRate, NULL);
  258. audio.frames = 512;
  259. snd_pcm_hw_params_set_period_size_near(audio.handle,
  260. audio.params, &audio.frames, NULL);
  261. audio.periods = 4;
  262. snd_pcm_hw_params_set_periods_near(audio.handle,
  263. audio.params, &audio.periods, NULL);
  264. /* Write the parameters to the driver */
  265. rc = snd_pcm_hw_params(audio.handle, audio.params);
  266. if(rc < 0) {
  267. fprintf(stderr,
  268. "unable to set hw parameters: %s\n",
  269. snd_strerror(rc));
  270. return false;
  271. }
  272. /* Set buffer size (in frames). The resulting latency is given by */
  273. /* latency = periodsize * periods / (rate * bytes_per_frame) */
  274. snd_pcm_hw_params_set_buffer_size(audio.handle,
  275. audio.params,
  276. synth.buffersize);
  277. //snd_pcm_hw_params_get_period_size(audio.params, &audio.frames, NULL);
  278. //snd_pcm_hw_params_get_period_time(audio.params, &val, NULL);
  279. pthread_attr_t attr;
  280. pthread_attr_init(&attr);
  281. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
  282. pthread_create(&audio.pThread, &attr, _AudioThread, this);
  283. return true;
  284. }
  285. void AlsaEngine::stopAudio()
  286. {
  287. if(!getAudioEn())
  288. return;
  289. snd_pcm_t *handle = audio.handle;
  290. audio.handle = NULL;
  291. pthread_join(audio.pThread, NULL);
  292. snd_pcm_drain(handle);
  293. if(snd_pcm_close(handle))
  294. cout << "Error: in snd_pcm_close " << __LINE__ << ' ' << __FILE__
  295. << endl;
  296. }
  297. void *AlsaEngine::processAudio()
  298. {
  299. while(audio.handle) {
  300. audio.buffer = interleave(getNext());
  301. snd_pcm_t *handle = audio.handle;
  302. int rc = snd_pcm_writei(handle, audio.buffer, synth.buffersize);
  303. if(rc == -EPIPE) {
  304. /* EPIPE means underrun */
  305. cerr << "underrun occurred" << endl;
  306. snd_pcm_prepare(handle);
  307. }
  308. else
  309. if(rc < 0)
  310. cerr << "error from writei: " << snd_strerror(rc) << endl;
  311. }
  312. return NULL;
  313. }