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.

395 lines
11KB

  1. #include <app/AudioDisplay.hpp>
  2. #include <ui/MenuSeparator.hpp>
  3. #include <helpers.hpp>
  4. #include <set>
  5. namespace rack {
  6. namespace app {
  7. static std::string getDetailTemplate(std::string name, int numInputs, int inputOffset, int numOutputs, int outputOffset) {
  8. std::string text = name;
  9. text += " (";
  10. if (numInputs > 0) {
  11. text += string::f("%d-%d in", inputOffset + 1, inputOffset + numInputs);
  12. }
  13. if (numInputs > 0 && numOutputs > 0) {
  14. text += ", ";
  15. }
  16. if (numOutputs > 0) {
  17. text += string::f("%d-%d out", outputOffset + 1, outputOffset + numOutputs);
  18. }
  19. text += ")";
  20. return text;
  21. }
  22. struct AudioDriverValueItem : ui::MenuItem {
  23. audio::Port* port;
  24. int driverId;
  25. void onAction(const ActionEvent& e) override {
  26. port->setDriverId(driverId);
  27. }
  28. };
  29. static void appendAudioDriverMenu(ui::Menu* menu, audio::Port* port) {
  30. if (!port)
  31. return;
  32. for (int driverId : audio::getDriverIds()) {
  33. AudioDriverValueItem* item = new AudioDriverValueItem;
  34. item->port = port;
  35. item->driverId = driverId;
  36. item->text = audio::getDriver(driverId)->getName();
  37. item->rightText = CHECKMARK(item->driverId == port->getDriverId());
  38. menu->addChild(item);
  39. }
  40. }
  41. void AudioDriverChoice::onAction(const ActionEvent& e) {
  42. ui::Menu* menu = createMenu();
  43. menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDriver")));
  44. appendAudioDriverMenu(menu, port);
  45. }
  46. void AudioDriverChoice::step() {
  47. text = "";
  48. if (box.size.x >= 200.0)
  49. text += string::translate("AudioDisplay.driver");
  50. audio::Driver* driver = port ? port->getDriver() : NULL;
  51. std::string driverName = driver ? driver->getName() : "";
  52. if (driverName != "") {
  53. text += driverName;
  54. color.a = 1.0;
  55. }
  56. else {
  57. text += "(" + string::translate("AudioDisplay.noDriver") + ")";
  58. color.a = 0.5;
  59. }
  60. }
  61. struct AudioDriverItem : ui::MenuItem {
  62. audio::Port* port;
  63. ui::Menu* createChildMenu() override {
  64. ui::Menu* menu = new ui::Menu;
  65. appendAudioDriverMenu(menu, port);
  66. return menu;
  67. }
  68. };
  69. struct AudioDeviceValueItem : ui::MenuItem {
  70. audio::Port* port;
  71. int deviceId;
  72. int inputOffset;
  73. int outputOffset;
  74. void onAction(const ActionEvent& e) override {
  75. port->setDeviceId(deviceId);
  76. port->inputOffset = inputOffset;
  77. port->outputOffset = outputOffset;
  78. }
  79. };
  80. static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) {
  81. if (!port)
  82. return;
  83. {
  84. AudioDeviceValueItem* item = new AudioDeviceValueItem;
  85. item->port = port;
  86. item->deviceId = -1;
  87. item->text = "(" + string::translate("AudioDisplay.noDevice") + ")";
  88. item->rightText = CHECKMARK(item->deviceId == port->getDeviceId());
  89. menu->addChild(item);
  90. }
  91. for (int deviceId : port->getDeviceIds()) {
  92. int numDeviceInputs = port->getDeviceNumInputs(deviceId);
  93. int numDeviceOutputs = port->getDeviceNumOutputs(deviceId);
  94. std::string name = port->getDeviceName(deviceId);
  95. // Display only 32 channel offsets per device, because some virtual devices (e.g. ALSA) can have thousands of useless channels.
  96. for (int i = 0; i < 32; i++) {
  97. int inputOffset = i * port->maxInputs;
  98. int outputOffset = i * port->maxOutputs;
  99. if (inputOffset >= numDeviceInputs && outputOffset >= numDeviceOutputs)
  100. break;
  101. int numInputs = math::clamp(numDeviceInputs - inputOffset, 0, port->maxInputs);
  102. int numOutputs = math::clamp(numDeviceOutputs - outputOffset, 0, port->maxOutputs);
  103. AudioDeviceValueItem* item = new AudioDeviceValueItem;
  104. item->port = port;
  105. item->deviceId = deviceId;
  106. item->inputOffset = inputOffset;
  107. item->outputOffset = outputOffset;
  108. item->text = getDetailTemplate(name, numInputs, inputOffset, numOutputs, outputOffset);
  109. item->rightText = CHECKMARK(deviceId == port->getDeviceId() && inputOffset == port->inputOffset && outputOffset == port->outputOffset);
  110. menu->addChild(item);
  111. }
  112. }
  113. }
  114. void AudioDeviceChoice::onAction(const ActionEvent& e) {
  115. ui::Menu* menu = createMenu();
  116. menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDevice")));
  117. appendAudioDeviceMenu(menu, port);
  118. }
  119. void AudioDeviceChoice::step() {
  120. text = "";
  121. if (box.size.x >= 200.0)
  122. text += string::translate("AudioDisplay.device");
  123. std::string detail = "";
  124. if (port && port->getDevice())
  125. detail = getDetailTemplate(port->getDevice()->getName(), port->getNumInputs(), port->inputOffset, port->getNumOutputs(), port->outputOffset);
  126. if (detail != "") {
  127. text += detail;
  128. color.a = 1.0;
  129. }
  130. else {
  131. text += string::translate("AudioDisplay.noDevice");
  132. color.a = 0.5;
  133. }
  134. }
  135. struct AudioDeviceItem : ui::MenuItem {
  136. audio::Port* port;
  137. ui::Menu* createChildMenu() override {
  138. ui::Menu* menu = new ui::Menu;
  139. appendAudioDeviceMenu(menu, port);
  140. return menu;
  141. }
  142. };
  143. struct AudioSampleRateValueItem : ui::MenuItem {
  144. audio::Port* port;
  145. float sampleRate;
  146. void onAction(const ActionEvent& e) override {
  147. port->setSampleRate(sampleRate);
  148. }
  149. };
  150. static void appendAudioSampleRateMenu(ui::Menu* menu, audio::Port* port) {
  151. if (!port)
  152. return;
  153. std::set<float> sampleRates = port->getSampleRates();
  154. // Add current sample rate in case it's not in the list
  155. sampleRates.insert(port->getSampleRate());
  156. if (sampleRates.empty()) {
  157. menu->addChild(createMenuLabel("(" + string::translate("AudioDisplay.lockedByDevice") + ")"));
  158. }
  159. for (float sampleRate : sampleRates) {
  160. if (sampleRate <= 0)
  161. continue;
  162. AudioSampleRateValueItem* item = new AudioSampleRateValueItem;
  163. item->port = port;
  164. item->sampleRate = sampleRate;
  165. item->text = string::f("%g kHz", sampleRate / 1000.0);
  166. item->rightText = CHECKMARK(item->sampleRate == port->getSampleRate());
  167. menu->addChild(item);
  168. }
  169. }
  170. void AudioSampleRateChoice::onAction(const ActionEvent& e) {
  171. ui::Menu* menu = createMenu();
  172. menu->addChild(createMenuLabel(string::translate("AudioDisplay.sampleRate")));
  173. appendAudioSampleRateMenu(menu, port);
  174. }
  175. void AudioSampleRateChoice::step() {
  176. text = "";
  177. if (box.size.x >= 100.0)
  178. text += string::translate("AudioDisplay.sampleRateColon");
  179. float sampleRate = port ? port->getSampleRate() : 0;
  180. if (sampleRate > 0) {
  181. text += string::f("%g", sampleRate / 1000.f);
  182. color.a = 1.0;
  183. }
  184. else {
  185. text += "---";
  186. color.a = 0.5;
  187. }
  188. text += " kHz";
  189. }
  190. struct AudioSampleRateItem : ui::MenuItem {
  191. audio::Port* port;
  192. ui::Menu* createChildMenu() override {
  193. ui::Menu* menu = new ui::Menu;
  194. appendAudioSampleRateMenu(menu, port);
  195. return menu;
  196. }
  197. };
  198. struct AudioBlockSizeValueItem : ui::MenuItem {
  199. audio::Port* port;
  200. int blockSize;
  201. void onAction(const ActionEvent& e) override {
  202. port->setBlockSize(blockSize);
  203. }
  204. };
  205. static void appendAudioBlockSizeMenu(ui::Menu* menu, audio::Port* port) {
  206. if (!port)
  207. return;
  208. std::set<int> blockSizes = port->getBlockSizes();
  209. // Add current block size in case it's not in the list
  210. blockSizes.insert(port->getBlockSize());
  211. if (blockSizes.empty()) {
  212. menu->addChild(createMenuLabel("(" + string::translate("AudioDisplay.lockedByDevice") + ")"));
  213. }
  214. for (int blockSize : blockSizes) {
  215. if (blockSize <= 0)
  216. continue;
  217. AudioBlockSizeValueItem* item = new AudioBlockSizeValueItem;
  218. item->port = port;
  219. item->blockSize = blockSize;
  220. float latency = (float) blockSize / port->getSampleRate() * 1000.0;
  221. item->text = string::f("%d (%.1f ms)", blockSize, latency);
  222. item->rightText = CHECKMARK(item->blockSize == port->getBlockSize());
  223. menu->addChild(item);
  224. }
  225. }
  226. void AudioBlockSizeChoice::onAction(const ActionEvent& e) {
  227. ui::Menu* menu = createMenu();
  228. menu->addChild(createMenuLabel(string::translate("AudioDisplay.blockSize")));
  229. appendAudioBlockSizeMenu(menu, port);
  230. }
  231. void AudioBlockSizeChoice::step() {
  232. text = "";
  233. if (box.size.x >= 100.0)
  234. text += string::translate("AudioDisplay.blockSizeColon");
  235. int blockSize = port ? port->getBlockSize() : 0;
  236. if (blockSize > 0) {
  237. text += string::f("%d", blockSize);
  238. color.a = 1.0;
  239. }
  240. else {
  241. text += "---";
  242. color.a = 0.5;
  243. }
  244. }
  245. struct AudioBlockSizeItem : ui::MenuItem {
  246. audio::Port* port;
  247. ui::Menu* createChildMenu() override {
  248. ui::Menu* menu = new ui::Menu;
  249. appendAudioBlockSizeMenu(menu, port);
  250. return menu;
  251. }
  252. };
  253. void AudioDisplay::setAudioPort(audio::Port* port) {
  254. clearChildren();
  255. math::Vec pos;
  256. AudioDriverChoice* driverChoice = createWidget<AudioDriverChoice>(pos);
  257. driverChoice->box.size.x = box.size.x;
  258. driverChoice->port = port;
  259. addChild(driverChoice);
  260. pos = driverChoice->box.getBottomLeft();
  261. this->driverChoice = driverChoice;
  262. this->driverSeparator = createWidget<LedDisplaySeparator>(pos);
  263. this->driverSeparator->box.size.x = box.size.x;
  264. addChild(this->driverSeparator);
  265. AudioDeviceChoice* deviceChoice = createWidget<AudioDeviceChoice>(pos);
  266. deviceChoice->box.size.x = box.size.x;
  267. deviceChoice->port = port;
  268. addChild(deviceChoice);
  269. pos = deviceChoice->box.getBottomLeft();
  270. this->deviceChoice = deviceChoice;
  271. this->deviceSeparator = createWidget<LedDisplaySeparator>(pos);
  272. this->deviceSeparator->box.size.x = box.size.x;
  273. addChild(this->deviceSeparator);
  274. AudioSampleRateChoice* sampleRateChoice = createWidget<AudioSampleRateChoice>(pos);
  275. sampleRateChoice->box.size.x = box.size.x / 2;
  276. sampleRateChoice->port = port;
  277. addChild(sampleRateChoice);
  278. this->sampleRateChoice = sampleRateChoice;
  279. this->sampleRateSeparator = createWidget<LedDisplaySeparator>(pos);
  280. this->sampleRateSeparator->box.pos.x = box.size.x / 2;
  281. this->sampleRateSeparator->box.size.y = this->sampleRateChoice->box.size.y;
  282. addChild(this->sampleRateSeparator);
  283. AudioBlockSizeChoice* bufferSizeChoice = createWidget<AudioBlockSizeChoice>(pos);
  284. bufferSizeChoice->box.pos.x = box.size.x / 2;
  285. bufferSizeChoice->box.size.x = box.size.x / 2;
  286. bufferSizeChoice->port = port;
  287. addChild(bufferSizeChoice);
  288. this->bufferSizeChoice = bufferSizeChoice;
  289. }
  290. void AudioDeviceMenuChoice::onAction(const ActionEvent& e) {
  291. ui::Menu* menu = createMenu();
  292. appendAudioMenu(menu, port);
  293. }
  294. void AudioButton::setAudioPort(audio::Port* port) {
  295. this->port = port;
  296. }
  297. void AudioButton::onAction(const ActionEvent& e) {
  298. ui::Menu* menu = createMenu();
  299. appendAudioMenu(menu, port);
  300. }
  301. void appendAudioMenu(ui::Menu* menu, audio::Port* port) {
  302. menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDriver")));
  303. appendAudioDriverMenu(menu, port);
  304. menu->addChild(new ui::MenuSeparator);
  305. menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDevice")));
  306. appendAudioDeviceMenu(menu, port);
  307. menu->addChild(new ui::MenuSeparator);
  308. menu->addChild(createMenuLabel(string::translate("AudioDisplay.sampleRate")));
  309. appendAudioSampleRateMenu(menu, port);
  310. menu->addChild(new ui::MenuSeparator);
  311. menu->addChild(createMenuLabel(string::translate("AudioDisplay.blockSize")));
  312. appendAudioBlockSizeMenu(menu, port);
  313. // Uncomment this to use sub-menus instead of one big menu.
  314. // AudioDriverItem* driverItem = createMenuItem<AudioDriverItem>(string::translate("AudioDisplay.audioDriver"), RIGHT_ARROW);
  315. // driverItem->port = port;
  316. // menu->addChild(driverItem);
  317. // AudioDeviceItem* deviceItem = createMenuItem<AudioDeviceItem>(string::translate("AudioDisplay.audioDevice"), RIGHT_ARROW);
  318. // deviceItem->port = port;
  319. // menu->addChild(deviceItem);
  320. // AudioSampleRateItem* sampleRateItem = createMenuItem<AudioSampleRateItem>(string::translate("AudioDisplay.sampleRate"), RIGHT_ARROW);
  321. // sampleRateItem->port = port;
  322. // menu->addChild(sampleRateItem);
  323. // AudioBlockSizeItem* blockSizeItem = createMenuItem<AudioBlockSizeItem>(string::translate("AudioDisplay.blockSize"), RIGHT_ARROW);
  324. // blockSizeItem->port = port;
  325. // menu->addChild(blockSizeItem);
  326. }
  327. } // namespace app
  328. } // namespace rack