gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

mqSettingsGui.cpp (6168B)


      1 #include "mqSettingsGui.h"
      2 
      3 #include <iostream>
      4 #include <cpp-terminal/input.hpp>
      5 
      6 #include "audioOutputPA.h"
      7 #include "midiDevice.h"
      8 #include "portmidi/pm_common/portmidi.h"
      9 #include "portaudio/include/portaudio.h"
     10 
     11 #include "dsp56kEmu/logging.h"
     12 
     13 using Key = Term::Key;
     14 
     15 constexpr auto g_itemActive = Term::fg::bright_yellow;
     16 constexpr auto g_itemDefault = Term::fg::white;
     17 constexpr auto g_itemHovered = Term::bg::gray;
     18 constexpr auto g_itemNotHovered = Term::bg::black;
     19 
     20 constexpr int32_t g_maxEntries = 17;
     21 
     22 namespace mqConsoleLib
     23 {
     24 SettingsGui::SettingsGui()
     25 	: m_win(140, 21)
     26 {
     27 }
     28 
     29 void SettingsGui::render(int _midiInput, int _midiOutput, int _audioOutput)
     30 {
     31 	if(!handleTerminalSize())
     32 		m_win.clear();
     33 
     34 	m_midiInput.clear();
     35 	m_midiOutput.clear();
     36 	m_audioOutput.clear();
     37 
     38 	int x = 1;
     39 	int y = 1;
     40 
     41 	m_win.fill_bg(1,1, static_cast<int>(m_win.get_w()), 1, Term::bg::gray);
     42 	m_win.fill_fg(1,1, static_cast<int>(m_win.get_w()), 1, Term::fg::black);
     43 
     44 	x = renderSettings(x, y, _midiInput, 0, "MIDI Input");	x += 3;
     45 	x = renderSettings(x, y, _midiOutput, 1, "MIDI Output");	x += 3;
     46 	renderSettings(x, y, _audioOutput, 2, "Audio Output");
     47 
     48 	m_win.fill_fg(1,21, 90, 21, Term::fg::gray);
     49 	m_win.print_str(1, 21, "Use Cursor Keys to navigate, Return to select a device. Escape to go back.");
     50 
     51 	std::cout << m_win.render(1,1,true) << std::flush;
     52 }
     53 
     54 void SettingsGui::onOpen()
     55 {
     56 	GuiBase::onOpen();
     57 	findSettings();
     58 }
     59 
     60 void SettingsGui::onEnter()
     61 {
     62 	m_enter = true;
     63 }
     64 
     65 void SettingsGui::onDown()
     66 {
     67 	moveCursor(0,1);
     68 }
     69 
     70 void SettingsGui::onLeft()
     71 {
     72 	moveCursor(-1,0);
     73 }
     74 
     75 void SettingsGui::onRight()
     76 {
     77 	moveCursor(1,0);
     78 }
     79 
     80 void SettingsGui::onUp()
     81 {
     82 	moveCursor(0,-1);
     83 }
     84 
     85 bool SettingsGui::processKey(int _ch)
     86 {
     87 	switch (_ch)
     88 	{
     89 	case Key::ESC:
     90 	case Key::ENTER:
     91 		onEnter();
     92 		return true;
     93 	case Key::ARROW_DOWN:
     94 		onDown();
     95 		return true;
     96 	case Key::ARROW_LEFT:
     97 		onLeft();
     98 		return true;
     99 	case Key::ARROW_RIGHT:
    100 		onRight();
    101 		return true;
    102 	case Key::ARROW_UP:
    103 		onUp();
    104 		return true;
    105 	default:
    106 		return false;
    107 	}
    108 }
    109 
    110 void SettingsGui::findSettings()
    111 {
    112 	for (auto& setting : m_settings)
    113 		setting.clear();
    114 
    115 	// MIDI
    116 	const auto devCount = Pm_CountDevices();
    117 
    118 	for(int i=0; i<devCount; ++i)
    119 	{
    120 		const auto* di = Pm_GetDeviceInfo(i);
    121 		if(!di)
    122 			continue;
    123 
    124 		const auto name = MidiDevice::getDeviceNameFromId(i);
    125 		if(di->input)
    126 			m_settings[0].push_back({i, name});
    127 		if(di->output)
    128 			m_settings[1].push_back({i, name});
    129 	}
    130 
    131 	const auto count = Pa_GetDeviceCount();
    132 
    133 	for(int i=0; i<count; ++i)
    134 	{
    135 		const auto* di = Pa_GetDeviceInfo(i);
    136 		if(!di)
    137 			continue;
    138 
    139 		if(di->maxOutputChannels < 2)
    140 			continue;
    141 
    142 		PaStreamParameters p{};
    143 
    144 		p.channelCount = 2;
    145 		p.sampleFormat = paFloat32;
    146 		p.device = i;
    147 		p.suggestedLatency = di->defaultLowOutputLatency;
    148 
    149 		const auto supportResult = Pa_IsFormatSupported(nullptr, &p, 44100.0);
    150 		if(supportResult != paFormatIsSupported)
    151 		{
    152 			std::stringstream ss;
    153 
    154 			ss << "Audio Device not supported: " << AudioOutputPA::getDeviceNameFromId(i) << ", result " << Pa_GetErrorText(supportResult);
    155 
    156 			const auto* hostError = Pa_GetLastHostErrorInfo();
    157 			if(hostError)
    158 				ss << ", host error info: " << hostError->errorCode << ", msg: " << (hostError->errorText ? hostError->errorText : "null");
    159 
    160 			const auto msg(ss.str());
    161 			LOG( msg);
    162 			continue;
    163 		}
    164 
    165 		const std::string name = AudioOutputPA::getDeviceNameFromId(i);
    166 
    167 		m_settings[2].push_back({i, name});
    168 	}
    169 }
    170 
    171 void SettingsGui::changeDevice(const Setting& _value, int column)
    172 {
    173 	switch (column)
    174 	{
    175 	case 0:
    176 		m_midiInput = _value.name;
    177 		break;
    178 	case 1:
    179 		m_midiOutput = _value.name;
    180 		break;
    181 	case 2:
    182 		m_audioOutput = _value.name;
    183 		break;
    184 	default:
    185 		break;
    186 	}
    187 }
    188 
    189 int SettingsGui::renderSettings(int x, int y, int _selectedId, const int _column, const std::string& _headline)
    190 {
    191 	m_win.print_str(x, y, _headline);
    192 	y += 2;
    193 
    194 	int maxX = x + static_cast<int>(_headline.size());
    195 
    196 	const auto& settings = m_settings[_column];
    197 
    198 	const bool scroll = settings.size() > g_maxEntries;
    199 
    200 	auto& scrollPos = m_scrollPos[_column];
    201 
    202 	if(scroll)
    203 	{
    204 		int focusPos = 0;
    205 
    206 		if(m_cursorX == _column)
    207 		{
    208 			focusPos = m_cursorY;
    209 		}
    210 		else
    211 		{
    212 			for(size_t i=0; i<settings.size(); ++i)
    213 			{
    214 				if(_selectedId == settings[i].devId)
    215 				{
    216 					focusPos = static_cast<int>(i);
    217 					break;
    218 				}
    219 			}
    220 		}
    221 
    222 		while((focusPos - scrollPos) < 3 && scrollPos > 0)
    223 			--scrollPos;
    224 		while((focusPos - scrollPos) > g_maxEntries - 3 && (scrollPos + g_maxEntries) < static_cast<int>(settings.size()))
    225 			++scrollPos;
    226 
    227 		if(scrollPos > 0)
    228 		{
    229 			constexpr char32_t arrowUp = 0x2191;
    230 			m_win.set_char(x, y-1, arrowUp);
    231 			m_win.set_char(x + 3, y-1, arrowUp);
    232 			m_win.set_char(x + 6, y-1, arrowUp);
    233 		}
    234 
    235 		if((scrollPos + g_maxEntries) < static_cast<int>(settings.size()))
    236 		{
    237 			constexpr char32_t arrowDown = 0x2193;
    238 			m_win.set_char(x, y + g_maxEntries, arrowDown);
    239 			m_win.set_char(x + 3, y + g_maxEntries, arrowDown);
    240 			m_win.set_char(x + 6, y + g_maxEntries, arrowDown);
    241 		}
    242 	}
    243 	else
    244 	{
    245 		scrollPos = 0;
    246 	}
    247 	for(size_t i=0; i<std::min(static_cast<int>(settings.size()), g_maxEntries); ++i)
    248 	{
    249 		const int idx = static_cast<int>(i) + m_scrollPos[_column];
    250 
    251 		if(idx < 0)
    252 			continue;
    253 
    254 		if(idx >= settings.size())
    255 			break;
    256 
    257 		const auto& s = settings[idx];
    258 
    259 		const auto len = static_cast<int>(s.name.size());
    260 
    261 		if(x + len > maxX)
    262 			maxX = x + len;
    263 
    264 		if(m_cursorY == idx && m_cursorX == _column)
    265 		{
    266 			m_win.fill_bg(x,y, x + len - 1, y, g_itemHovered);
    267 			if(m_enter)
    268 			{
    269 				m_enter = false;
    270 				changeDevice(s, _column);
    271 			}
    272 		}
    273 		else
    274 		{
    275 			m_win.fill_bg(x,y, x + len - 1, y, g_itemNotHovered);
    276 		}
    277 		m_win.fill_fg(x,y, x + len, y, s.devId == _selectedId ? g_itemActive : g_itemDefault);
    278 		m_win.print_str(x, y, s.name);
    279 		++y;
    280 	}
    281 	return maxX;
    282 }
    283 
    284 void SettingsGui::moveCursor(int x, int y)
    285 {
    286 	m_cursorX += x;
    287 	m_cursorY += y;
    288 
    289 	if(m_cursorX >= static_cast<int>(m_settings.size()))
    290 		m_cursorX = 0;
    291 	if(m_cursorX < 0)		
    292 		m_cursorX = static_cast<int>(m_settings.size() - 1);
    293 	if(m_cursorY < 0)
    294 		m_cursorY = m_settings[m_cursorX].empty() ? 0 : static_cast<int>(m_settings[m_cursorX].size()) - 1;
    295 	if(m_cursorY >= static_cast<int>(m_settings[m_cursorX].size()))
    296 		m_cursorY = 0;
    297 }
    298 }