43 : juce::Thread(
"HeadlessCLIMenu"), m_processor(processor)
61 if (threadShouldExit())
71juce::String HeadlessCLIMenu::readLine()
74 if (!std::getline(std::cin, line))
81 return juce::String(line).trim();
84void HeadlessCLIMenu::callOnMessageThread(std::function<
void()> fn)
87 jassert(!juce::MessageManager::getInstance()->isThisTheMessageThread());
89 juce::WaitableEvent done;
90 juce::MessageManager::callAsync([fn = std::move(fn), &done]()
105void HeadlessCLIMenu::printSeparator()
107 std::cout << juce::String::repeatedString(
"-", sc_separatorWidth) <<
"\n";
110void HeadlessCLIMenu::printHeader(
const juce::String& title)
114 std::cout <<
" Mema - " << title <<
"\n";
119void HeadlessCLIMenu::printPrompt()
121 std::cout <<
"\n> " << std::flush;
128int HeadlessCLIMenu::getActiveInputCount()
131 auto* dev = dm ? dm->getCurrentAudioDevice() :
nullptr;
132 return dev ? dev->getActiveInputChannels().countNumberOfSetBits() : 0;
135int HeadlessCLIMenu::getActiveOutputCount()
138 auto* dev = dm ? dm->getCurrentAudioDevice() :
nullptr;
139 return dev ? dev->getActiveOutputChannels().countNumberOfSetBits() : 0;
146void HeadlessCLIMenu::runMainMenu()
148 while (!threadShouldExit() && !m_quit)
152 auto* dev = dm ? dm->getCurrentAudioDevice() :
nullptr;
154 int numIn = getActiveInputCount();
155 int numOut = getActiveOutputCount();
158 juce::String deviceLine =
"none";
161 auto setup = dm->getAudioDeviceSetup();
162 deviceLine = dev->getName()
163 +
" " + juce::String(
static_cast<int>(setup.sampleRate)) +
" Hz"
164 +
" buf " + juce::String(setup.bufferSize);
169 for (
int ch = 1; ch <= numIn; ++ch)
174 for (
int ch = 1; ch <= numOut; ++ch)
180 juce::String pluginLine;
181 if (pluginDesc.name.isNotEmpty())
182 pluginLine = pluginDesc.name
184 +
" " + (m_processor.
isPluginPost() ?
"post" :
"pre") +
"-matrix";
188 printHeader(
"Configuration");
189 std::cout <<
" 1 Input mutes [" << numIn <<
" ch, " << mutedIn <<
" muted]\n";
190 std::cout <<
" 2 Output mutes [" << numOut <<
" ch, " << mutedOut <<
" muted]\n";
191 std::cout <<
" 3 Matrix gains [" << numIn <<
" x " << numOut <<
"]\n";
192 std::cout <<
" 4 Audio device [" << deviceLine <<
"]\n";
193 std::cout <<
" 5 Plugin [" << pluginLine <<
"]\n";
194 std::cout <<
" 6 Load config\n";
195 std::cout <<
" 7 Save config\n";
196 std::cout <<
" q Quit\n";
199 auto input = readLine().toLowerCase();
202 if (input ==
"1") runInputMutesMenu();
203 else if (input ==
"2") runOutputMutesMenu();
204 else if (input ==
"3") runMatrixMenu();
205 else if (input ==
"4") runAudioDeviceMenu();
206 else if (input ==
"5") runPluginMenu();
207 else if (input ==
"6") doLoadConfig();
208 else if (input ==
"7") doSaveConfig();
209 else if (input ==
"q")
212 std::cout <<
"\nQuitting Mema...\n";
214 juce::MessageManager::callAsync([]()
216 juce::JUCEApplication::getInstance()->systemRequestedQuit();
220 std::cout <<
" Unknown option '" << input <<
"'\n";
228void HeadlessCLIMenu::runInputMutesMenu()
230 while (!threadShouldExit() && !m_quit)
232 int numIn = getActiveInputCount();
234 printHeader(
"Input Mutes");
238 std::cout <<
" No active input channels.\n";
239 std::cout <<
"\n b Back\n";
241 auto in = readLine().toLowerCase();
242 if (m_quit || in ==
"b" || in ==
"q")
break;
246 for (
int ch = 1; ch <= numIn; ++ch)
249 std::cout <<
" " << std::setw(2) << ch
250 <<
" Input " << std::setw(2) << ch
251 <<
" [" << (muted ?
"MUTED " :
"active") <<
"]\n";
253 std::cout <<
"\n b Back\n";
254 std::cout <<
"\n Enter channel number to toggle, or 'b' to go back.\n";
257 auto input = readLine().toLowerCase();
259 if (input ==
"b" || input ==
"q")
break;
261 int ch = input.getIntValue();
262 if (ch >= 1 && ch <= numIn)
264 bool currentlyMuted = m_processor.
getInputMuteState(
static_cast<std::uint16_t
>(ch));
265 callOnMessageThread([
this, ch, currentlyMuted]()
269 std::cout <<
" Input " << ch << (currentlyMuted ?
" unmuted.\n" :
" muted.\n");
272 std::cout <<
" Invalid channel number.\n";
280void HeadlessCLIMenu::runOutputMutesMenu()
282 while (!threadShouldExit() && !m_quit)
284 int numOut = getActiveOutputCount();
286 printHeader(
"Output Mutes");
290 std::cout <<
" No active output channels.\n";
291 std::cout <<
"\n b Back\n";
293 auto in = readLine().toLowerCase();
294 if (m_quit || in ==
"b" || in ==
"q")
break;
298 for (
int ch = 1; ch <= numOut; ++ch)
301 std::cout <<
" " << std::setw(2) << ch
302 <<
" Output " << std::setw(2) << ch
303 <<
" [" << (muted ?
"MUTED " :
"active") <<
"]\n";
305 std::cout <<
"\n b Back\n";
306 std::cout <<
"\n Enter channel number to toggle, or 'b' to go back.\n";
309 auto input = readLine().toLowerCase();
311 if (input ==
"b" || input ==
"q")
break;
313 int ch = input.getIntValue();
314 if (ch >= 1 && ch <= numOut)
317 callOnMessageThread([
this, ch, currentlyMuted]()
321 std::cout <<
" Output " << ch << (currentlyMuted ?
" unmuted.\n" :
" muted.\n");
324 std::cout <<
" Invalid channel number.\n";
332void HeadlessCLIMenu::runMatrixMenu()
334 while (!threadShouldExit() && !m_quit)
336 int numIn = getActiveInputCount();
337 int numOut = getActiveOutputCount();
339 int dispIn = juce::jmin(numIn, sc_matrixDisplayMax);
340 int dispOut = juce::jmin(numOut, sc_matrixDisplayMax);
342 printHeader(
"Matrix Gains");
344 if (numIn == 0 || numOut == 0)
346 std::cout <<
" No active channels.\n";
347 std::cout <<
"\n b Back\n";
349 auto in = readLine().toLowerCase();
350 if (m_quit || in ==
"b" || in ==
"q")
break;
355 std::cout <<
" Outs:";
356 for (
int o = 1; o <= dispOut; ++o)
357 std::cout << std::setw(4) << o;
358 if (numOut > sc_matrixDisplayMax)
359 std::cout <<
" (+" << (numOut - sc_matrixDisplayMax) <<
" more)";
363 for (
int i = 1; i <= dispIn; ++i)
365 std::cout <<
" In" << std::setw(2) << i <<
":";
366 for (
int o = 1; o <= dispOut; ++o)
369 static_cast<std::uint16_t
>(i),
static_cast<std::uint16_t
>(o));
370 std::cout << std::setw(4) << (en ?
"+" :
".");
374 if (numIn > sc_matrixDisplayMax)
375 std::cout <<
" (first " << sc_matrixDisplayMax <<
" inputs shown)\n";
377 std::cout <<
"\n + = crosspoint enabled . = disabled\n";
378 std::cout <<
"\n Enter 'in out' (e.g. '2 3') to edit a crosspoint, or 'b' to go back.\n";
381 auto input = readLine();
383 if (input.toLowerCase() ==
"b" || input.toLowerCase() ==
"q")
break;
386 juce::StringArray tokens;
387 tokens.addTokens(input,
" \t",
"");
388 tokens.removeEmptyStrings();
390 if (tokens.size() != 2)
392 std::cout <<
" Please enter two numbers separated by a space.\n";
396 int selIn = tokens[0].getIntValue();
397 int selOut = tokens[1].getIntValue();
399 if (selIn < 1 || selIn > numIn || selOut < 1 || selOut > numOut)
401 std::cout <<
" Out of range. In: 1-" << numIn <<
" Out: 1-" << numOut <<
"\n";
406 while (!threadShouldExit() && !m_quit)
409 static_cast<std::uint16_t
>(selIn),
410 static_cast<std::uint16_t
>(selOut));
412 static_cast<std::uint16_t
>(selIn),
413 static_cast<std::uint16_t
>(selOut));
414 float dB = (factor > 0.0f)
415 ? juce::Decibels::gainToDecibels(factor)
418 printHeader(
"Crosspoint In " + juce::String(selIn)
419 +
" -> Out " + juce::String(selOut));
421 std::cout <<
" 1 Toggle enable [" << (en ?
"enabled " :
"disabled") <<
"]\n";
422 std::cout <<
" 2 Set gain [" << juce::String(dB, 1) <<
" dB]\n";
423 std::cout <<
"\n b Back\n";
426 auto cpInput = readLine().toLowerCase();
428 if (cpInput ==
"b" || cpInput ==
"q")
break;
432 callOnMessageThread([
this, selIn, selOut, en]()
435 static_cast<std::uint16_t
>(selIn),
436 static_cast<std::uint16_t
>(selOut),
439 std::cout <<
" Crosspoint " << (en ?
"disabled.\n" :
"enabled.\n");
441 else if (cpInput ==
"2")
443 std::cout <<
" Enter gain in dB (e.g. -6.0, 0.0): " << std::flush;
444 auto dbInput = readLine();
447 float newDb = dbInput.getFloatValue();
448 float newFactor = juce::Decibels::decibelsToGain(newDb, -100.0f);
450 callOnMessageThread([
this, selIn, selOut, newFactor]()
453 static_cast<std::uint16_t
>(selIn),
454 static_cast<std::uint16_t
>(selOut),
457 std::cout <<
" Gain set to " << juce::String(newDb, 1) <<
" dB.\n";
460 std::cout <<
" Unknown option.\n";
477static std::vector<DeviceEntry>
collectDevices(juce::AudioDeviceManager* dm,
bool wantInputs)
479 std::vector<DeviceEntry> result;
480 for (
auto* type : dm->getAvailableDeviceTypes())
482 type->scanForDevices();
483 for (
auto& name : type->getDeviceNames(wantInputs))
484 result.push_back({ type->getTypeName(), name });
489void HeadlessCLIMenu::runAudioDeviceMenu()
491 while (!threadShouldExit() && !m_quit)
493 auto* dm = m_processor.getDeviceManager();
496 std::cout <<
" Audio device manager not available.\n";
500 auto setup = dm->getAudioDeviceSetup();
501 auto* dev = dm->getCurrentAudioDevice();
502 juce::String inputDeviceName = setup.inputDeviceName.isNotEmpty()
503 ? setup.inputDeviceName :
"(none)";
504 juce::String outputDeviceName = setup.outputDeviceName.isNotEmpty()
505 ? setup.outputDeviceName :
"(none)";
506 juce::String typeName = dev ? dev->getTypeName() :
"(none)";
507 int sampleRate = dev ?
static_cast<int>(setup.sampleRate) : 0;
508 int bufferSize = dev ? setup.bufferSize : 0;
509 int numIn = getActiveInputCount();
510 int numOut = getActiveOutputCount();
512 printHeader(
"Audio Device");
513 std::cout <<
" Input device: " << inputDeviceName <<
"\n";
514 std::cout <<
" Output device: " << outputDeviceName <<
"\n";
515 std::cout <<
" Device type: " << typeName <<
"\n";
516 std::cout <<
" Sample rate: " << sampleRate <<
" Hz\n";
517 std::cout <<
" Buffer size: " << bufferSize <<
" samples\n";
518 std::cout <<
" Active inputs: " << numIn <<
"\n";
519 std::cout <<
" Active outputs: " << numOut <<
"\n";
521 std::cout <<
" 1 Change input device\n";
522 std::cout <<
" 2 Change output device\n";
523 std::cout <<
" 3 Change sample rate\n";
524 std::cout <<
" 4 Change buffer size\n";
525 std::cout <<
"\n b Back\n";
528 auto input = readLine().toLowerCase();
530 if (input ==
"b" || input ==
"q")
break;
536 if (devices.empty()) { std::cout <<
" No input devices found.\n";
continue; }
538 printHeader(
"Select Input Device");
539 for (
int i = 0; i < static_cast<int>(devices.size()); ++i)
540 std::cout <<
" " << std::setw(2) << (i + 1)
541 <<
" [" << devices[i].typeName <<
"] "
542 << devices[i].deviceName
543 << (devices[i].deviceName == inputDeviceName ?
" <-- current" :
"") <<
"\n";
544 std::cout <<
"\n b Back\n";
547 auto sel = readLine().toLowerCase();
549 if (sel ==
"b")
continue;
551 int idx = sel.getIntValue() - 1;
552 if (idx < 0 || idx >=
static_cast<int>(devices.size()))
554 std::cout <<
" Invalid selection.\n";
558 juce::String selectedType = devices[idx].typeName;
559 juce::String selectedDevice = devices[idx].deviceName;
561 callOnMessageThread([dm, selectedType, selectedDevice]()
563 dm->setCurrentAudioDeviceType(selectedType,
true);
564 auto s = dm->getAudioDeviceSetup();
565 s.inputDeviceName = selectedDevice;
566 s.useDefaultInputChannels =
true;
567 dm->setAudioDeviceSetup(s,
true);
569 std::cout <<
" Input device changed to: " << selectedDevice <<
"\n";
573 else if (input ==
"2")
576 if (devices.empty()) { std::cout <<
" No output devices found.\n";
continue; }
578 printHeader(
"Select Output Device");
579 for (
int i = 0; i < static_cast<int>(devices.size()); ++i)
580 std::cout <<
" " << std::setw(2) << (i + 1)
581 <<
" [" << devices[i].typeName <<
"] "
582 << devices[i].deviceName
583 << (devices[i].deviceName == outputDeviceName ?
" <-- current" :
"") <<
"\n";
584 std::cout <<
"\n b Back\n";
587 auto sel = readLine().toLowerCase();
589 if (sel ==
"b")
continue;
591 int idx = sel.getIntValue() - 1;
592 if (idx < 0 || idx >=
static_cast<int>(devices.size()))
594 std::cout <<
" Invalid selection.\n";
598 juce::String selectedType = devices[idx].typeName;
599 juce::String selectedDevice = devices[idx].deviceName;
601 callOnMessageThread([dm, selectedType, selectedDevice]()
603 dm->setCurrentAudioDeviceType(selectedType,
true);
604 auto s = dm->getAudioDeviceSetup();
605 s.outputDeviceName = selectedDevice;
606 s.useDefaultOutputChannels =
true;
607 dm->setAudioDeviceSetup(s,
true);
609 std::cout <<
" Output device changed to: " << selectedDevice <<
"\n";
613 else if (input ==
"3")
615 if (dev ==
nullptr) { std::cout <<
" No active device.\n";
continue; }
617 auto rates = dev->getAvailableSampleRates();
618 if (rates.isEmpty()) { std::cout <<
" No sample rates reported by device.\n";
continue; }
620 printHeader(
"Select Sample Rate");
621 for (
int i = 0; i < rates.size(); ++i)
622 std::cout <<
" " << std::setw(2) << (i + 1)
623 <<
" " <<
static_cast<int>(rates[i]) <<
" Hz"
624 << (
static_cast<int>(rates[i]) == sampleRate ?
" <-- current" :
"") <<
"\n";
625 std::cout <<
"\n b Back\n";
628 auto sel = readLine().toLowerCase();
630 if (sel ==
"b")
continue;
632 int idx = sel.getIntValue() - 1;
633 if (idx < 0 || idx >= rates.size())
635 std::cout <<
" Invalid selection.\n";
639 double newRate = rates[idx];
640 callOnMessageThread([dm, newRate]()
642 auto s = dm->getAudioDeviceSetup();
643 s.sampleRate = newRate;
644 dm->setAudioDeviceSetup(s,
true);
646 std::cout <<
" Sample rate changed to " <<
static_cast<int>(newRate) <<
" Hz.\n";
650 else if (input ==
"4")
652 if (dev ==
nullptr) { std::cout <<
" No active device.\n";
continue; }
654 auto sizes = dev->getAvailableBufferSizes();
655 if (sizes.isEmpty()) { std::cout <<
" No buffer sizes reported by device.\n";
continue; }
657 printHeader(
"Select Buffer Size");
658 for (
int i = 0; i < sizes.size(); ++i)
659 std::cout <<
" " << std::setw(2) << (i + 1)
660 <<
" " << sizes[i] <<
" samples"
661 << (sizes[i] == bufferSize ?
" <-- current" :
"") <<
"\n";
662 std::cout <<
"\n b Back\n";
665 auto sel = readLine().toLowerCase();
667 if (sel ==
"b")
continue;
669 int idx = sel.getIntValue() - 1;
670 if (idx < 0 || idx >= sizes.size())
672 std::cout <<
" Invalid selection.\n";
676 int newSize = sizes[idx];
677 callOnMessageThread([dm, newSize]()
679 auto s = dm->getAudioDeviceSetup();
680 s.bufferSize = newSize;
681 dm->setAudioDeviceSetup(s,
true);
683 std::cout <<
" Buffer size changed to " << newSize <<
" samples.\n";
686 std::cout <<
" Unknown option.\n";
694void HeadlessCLIMenu::runPluginMenu()
696 while (!threadShouldExit() && !m_quit)
698 auto desc = m_processor.getPluginDescription();
699 bool loaded = desc.name.isNotEmpty();
701 printHeader(
"Plugin");
702 std::cout <<
" Plugin: " << (loaded ? desc.name : juce::String(
"none")) <<
"\n";
706 bool enabled = m_processor.isPluginEnabled();
707 bool post = m_processor.isPluginPost();
710 std::cout <<
" 1 Toggle processing [" << (enabled ?
"enabled " :
"disabled") <<
"]\n";
711 std::cout <<
" 2 Toggle pre/post [" << (post ?
"post" :
"pre ") <<
"-matrix]\n";
712 std::cout <<
" 3 Parameter remote control\n";
716 std::cout <<
"\n No plugin loaded. Load a configuration file that includes a plugin.\n";
719 std::cout <<
"\n b Back\n";
722 auto input = readLine().toLowerCase();
724 if (input ==
"b" || input ==
"q")
break;
729 bool enabled = m_processor.isPluginEnabled();
730 bool post = m_processor.isPluginPost();
734 callOnMessageThread([
this, enabled]()
736 m_processor.setPluginEnabledState(!enabled);
738 std::cout <<
" Plugin processing " << (enabled ?
"disabled.\n" :
"enabled.\n");
740 else if (input ==
"2")
742 callOnMessageThread([
this, post]()
744 m_processor.setPluginPrePostState(!post);
746 std::cout <<
" Plugin insertion set to " << (post ?
"pre-matrix.\n" :
"post-matrix.\n");
748 else if (input ==
"3")
750 runPluginParametersMenu();
753 std::cout <<
" Unknown option.\n";
757void HeadlessCLIMenu::runPluginParametersMenu()
759 while (!threadShouldExit() && !m_quit)
761 auto& params = m_processor.getPluginParameterInfos();
763 printHeader(
"Plugin Parameters - Remote Control");
767 std::cout <<
" No parameters available.\n";
768 std::cout <<
"\n b Back\n";
770 auto in = readLine().toLowerCase();
771 if (m_quit || in ==
"b" || in ==
"q")
break;
775 for (
int i = 0; i < static_cast<int>(params.size()); ++i)
777 const auto& p = params[i];
778 juce::String typeStr;
781 case ParameterControlType::Toggle: typeStr =
"Toggle ";
break;
782 case ParameterControlType::Discrete: typeStr =
"Discrete ";
break;
783 case ParameterControlType::Continuous: typeStr =
"Continuous";
break;
784 default: typeStr =
"Continuous";
break;
786 std::cout <<
" " << std::setw(3) << (i + 1)
787 <<
" " << p.name.paddedRight(
' ', 24)
788 <<
" [" << (p.isRemoteControllable ?
"remote: yes" :
"remote: no ") <<
"]"
789 <<
" " << typeStr <<
"\n";
792 std::cout <<
"\n b Back\n";
793 std::cout <<
"\n Enter parameter number to configure, or 'b' to go back.\n";
796 auto input = readLine().toLowerCase();
798 if (input ==
"b" || input ==
"q")
break;
800 int idx = input.getIntValue() - 1;
801 if (idx < 0 || idx >=
static_cast<int>(params.size()))
803 std::cout <<
" Invalid parameter number.\n";
808 while (!threadShouldExit() && !m_quit)
810 auto& params2 = m_processor.getPluginParameterInfos();
811 if (idx >=
static_cast<int>(params2.size()))
break;
812 const auto& p = params2[idx];
814 juce::String typeStr;
817 case ParameterControlType::Toggle: typeStr =
"Toggle";
break;
818 case ParameterControlType::Discrete: typeStr =
"Discrete";
break;
819 case ParameterControlType::Continuous: typeStr =
"Continuous";
break;
820 default: typeStr =
"Continuous";
break;
823 printHeader(
"Parameter: " + p.name);
824 std::cout <<
" Name: " << p.name <<
"\n";
825 std::cout <<
" Index: " << p.index <<
"\n";
826 std::cout <<
" Value: " << juce::String(p.currentValue, 3)
827 << (p.label.isNotEmpty() ?
" " + p.label : juce::String()) <<
"\n";
829 std::cout <<
" 1 Toggle remote-controllable [" << (p.isRemoteControllable ?
"yes" :
"no ") <<
"]\n";
830 if (p.isRemoteControllable)
831 std::cout <<
" 2 Set control type [" << typeStr <<
"]\n";
832 std::cout <<
"\n b Back\n";
835 auto cpInput = readLine().toLowerCase();
837 if (cpInput ==
"b" || cpInput ==
"q")
break;
841 bool nowRemote = p.isRemoteControllable;
842 ParameterControlType curType = p.type;
843 int curSteps = p.stepCount;
844 int capturedIdx = idx;
845 callOnMessageThread([
this, capturedIdx, nowRemote, curType, curSteps]()
847 m_processor.setPluginParameterRemoteControlInfos(capturedIdx, !nowRemote, curType, curSteps);
849 std::cout <<
" Remote-controllable " << (nowRemote ?
"disabled.\n" :
"enabled.\n");
851 else if (cpInput ==
"2" && p.isRemoteControllable)
853 printHeader(
"Control Type: " + p.name);
854 std::cout <<
" 1 Continuous (slider / fader)\n";
855 std::cout <<
" 2 Discrete (combo box, requires step count >= 2)\n";
856 std::cout <<
" 3 Toggle (on/off button, 2 steps)\n";
857 std::cout <<
"\n b Back\n";
860 auto typeInput = readLine().toLowerCase();
862 if (typeInput ==
"b")
continue;
864 ParameterControlType newType = p.type;
865 int newSteps = p.stepCount;
866 bool validChoice =
true;
868 if (typeInput ==
"1")
870 newType = ParameterControlType::Continuous;
873 else if (typeInput ==
"2")
875 std::cout <<
" Enter number of steps (>= 2, current: "
876 << (p.stepCount >= 2 ? p.stepCount : 2) <<
"): " << std::flush;
877 auto stepsInput = readLine();
879 int enteredSteps = stepsInput.getIntValue();
880 newType = ParameterControlType::Discrete;
881 newSteps = juce::jmax(2, enteredSteps);
883 else if (typeInput ==
"3")
885 newType = ParameterControlType::Toggle;
890 std::cout <<
" Invalid selection.\n";
896 int capturedIdx2 = idx;
897 callOnMessageThread([
this, capturedIdx2, newType, newSteps]()
899 m_processor.setPluginParameterRemoteControlInfos(capturedIdx2,
true, newType, newSteps);
901 std::cout <<
" Control type updated.\n";
905 std::cout <<
" Unknown option.\n";
914void HeadlessCLIMenu::doLoadConfig()
916 std::cout <<
"\n Enter path to .config file to load\n"
917 " (leave empty to cancel): " << std::flush;
918 auto path = readLine();
919 if (m_quit || path.isEmpty())
return;
921 juce::File file(path);
922 if (!file.existsAsFile())
924 std::cout <<
" File not found: " << path <<
"\n";
927 if (!file.hasReadAccess())
929 std::cout <<
" Cannot read file: " << path <<
"\n";
933 auto xmlConfig = juce::parseXML(file);
936 std::cout <<
" File does not contain valid XML.\n";
939 if (!MemaAppConfiguration::isValid(xmlConfig))
941 std::cout <<
" File does not contain a valid Mema configuration.\n";
947 auto sharedXml = std::make_shared<std::unique_ptr<juce::XmlElement>>(std::move(xmlConfig));
948 callOnMessageThread([sharedXml]()
950 auto* config = MemaAppConfiguration::getInstance();
951 if (config ==
nullptr)
return;
952 config->SetFlushAndUpdateDisabled();
953 config->resetConfigState(std::move(*sharedXml));
954 config->ResetFlushAndUpdateDisabled();
957 std::cout <<
" Configuration loaded from: " << path <<
"\n";
960void HeadlessCLIMenu::doSaveConfig()
962 juce::String defaultName = juce::Time::getCurrentTime().toISO8601(
true).substring(0, 10)
965 std::cout <<
"\n Enter target path for the configuration file\n"
966 " (suggestion: " << defaultName <<
", leave empty to cancel): " << std::flush;
967 auto path = readLine();
968 if (m_quit || path.isEmpty())
return;
970 juce::File targetFile(path);
971 if (targetFile.getFileExtension() !=
".config")
972 targetFile = targetFile.withFileExtension(
".config");
974 if (!targetFile.hasWriteAccess())
976 std::cout <<
" Cannot write to: " << targetFile.getFullPathName() <<
"\n";
980 auto* config = MemaAppConfiguration::getInstance();
981 if (config ==
nullptr)
983 std::cout <<
" Configuration not available.\n";
987 auto xmlConfig = config->getConfigState();
990 std::cout <<
" Configuration state could not be read.\n";
994 if (!xmlConfig->writeTo(targetFile))
995 std::cout <<
" Failed to write: " << targetFile.getFullPathName() <<
"\n";
997 std::cout <<
" Configuration saved to: " << targetFile.getFullPathName() <<
"\n";