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.

367 lines
10KB

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