diff --git a/SarAsio/wrapper.cpp b/SarAsio/wrapper.cpp index 92d0853..76fd1f6 100644 --- a/SarAsio/wrapper.cpp +++ b/SarAsio/wrapper.cpp @@ -27,6 +27,9 @@ using namespace Sar; SarAsioWrapper *gActiveWrapper; static const char kNoInterfaceSelected[] = "No Interface Selected"; +// Channels per direction offered while no hardware interface is selected: +// a stereo pair, since some hosts (Live, Reason) refuse devices without one. +static const long kFakeChannelCount = 2; SarAsioWrapper::SarAsioWrapper() { @@ -109,7 +112,7 @@ AsioStatus SarAsioWrapper::getChannels(long *inputCount, long *outputCount) LOG(INFO) << "SarAsioWrapper::getChannels"; if (!_innerDriver) { - *inputCount = *outputCount = 1; + *inputCount = *outputCount = kFakeChannelCount; return AsioStatus::OK; } @@ -276,7 +279,7 @@ AsioStatus SarAsioWrapper::getSamplePosition(int64_t *pos, int64_t *timestamp) AsioStatus SarAsioWrapper::getChannelInfo(AsioChannelInfo *info) { if (!_innerDriver) { - if (info->index != 0) { + if (info->index < 0 || info->index >= kFakeChannelCount) { return AsioStatus::NotPresent; } @@ -335,18 +338,22 @@ AsioStatus SarAsioWrapper::createBuffers( LOG(INFO) << "SarAsioWrapper::createBuffers: no inner driver, trying " << "to create fake channels."; - // Allow allocating a single fake channel if hardware device is not yet - // selected. Supports one input and one output. - if (channelCount > 2) { + // Allow allocating the fake channels if a hardware device is not yet + // selected, so a host can load SAR to reach its control panel. + if (channelCount > 2 * kFakeChannelCount) { return AsioStatus::InvalidMode; } for (long i = 0; i < channelCount; ++i) { - if (infos[i].index != 0) { + if (infos[i].index < 0 || infos[i].index >= kFakeChannelCount) { return AsioStatus::InvalidMode; } } + for (auto buf : _fakeBuffers) { + free(buf); + } + _fakeBuffers.clear(); for (long i = 0; i < channelCount; ++i) { diff --git a/SarTest/SarTest.cpp b/SarTest/SarTest.cpp index c42c483..c8f784a 100644 --- a/SarTest/SarTest.cpp +++ b/SarTest/SarTest.cpp @@ -55,6 +55,10 @@ int usage() " Open SAR's control panel on a running host, press the hardware\n" " interface's Configure button and close it again; fail if a second\n" " clock instance was created (issue #133).\n" + " SarTest no-interface\n" + " Open SAR with no hardware interface configured, the way a DAW sees\n" + " it before first-time setup; fail unless it offers a stereo input\n" + " and output (issues #31, #54).\n" "\n" "Layout options: --endpoints N (playback/recording pairs, default 2)\n" " --channels C (per endpoint, default 2) --prefix \n" @@ -117,8 +121,10 @@ WasapiOptions wasapiOptions(const Args& args, const EndpointLayout& layout) } // Writes default.json for the layout unless --keep-config, backing up an -// existing configuration the first time. -bool prepareConfig(const Args& args, const EndpointLayout& layout) +// existing configuration the first time. An empty driverClsid leaves SAR +// without a hardware interface. +bool prepareConfig(const Args& args, const EndpointLayout& layout, + const std::wstring& driverClsid = SAR_TEST_CLOCK_CLSID_STR) { if (args.has(L"keep-config")) { return true; @@ -134,7 +140,7 @@ bool prepareConfig(const Args& args, const EndpointLayout& layout) } } - if (!writeDriverConfig(layout, SAR_TEST_CLOCK_CLSID_STR, + if (!writeDriverConfig(layout, driverClsid, args.getInt(L"wavert-min-frames", 0), path)) { return false; } @@ -651,6 +657,60 @@ int cmdControlPanel(const Args& args) return rc; } +// Issues #31 and #54: before a hardware interface is chosen, SAR offered one +// mono input and one mono output, and DAWs that need a stereo output (Live, +// Reason) refused to load it, so its control panel couldn't be reached. +int cmdNoInterface(const Args& args) +{ + EndpointLayout layout = EndpointLayout::fromArgs(args); + + layout.pairs = 0; + + if (!prepareConfig(args, layout, L"")) { + return 1; + } + + AsioHost host(hostOptions(args, layout)); + int rc = 0; + std::string failure; + + if (!host.load()) { + return 1; + } + + if (!host.open()) { + rc = 2; + failure = "open failed"; + } else if (!host.start() || !host.stop()) { + rc = 2; + failure = "start/stop failed"; + } else if (host.stats().physicalInputs < 2 || host.stats().physicalOutputs < 2) { + rc = 2; + failure = "no stereo pair without a hardware interface"; + } + + host.close(); + + if (rc != 0) { + logf("%s", failure.c_str()); + } + + JsonObject result; + + result.setString("command", "no-interface") + .setBool("passed", rc == 0) + .setInt("exitCode", rc) + .setRaw("host", host.toJson()); + + if (!failure.empty()) { + result.setString("failure", failure); + } + + writeResults(args, result.str()); + logf("%s", rc == 0 ? "PASSED" : "FAILED"); + return rc; +} + } // namespace int wmain(int argc, wchar_t **argv) @@ -686,6 +746,8 @@ int wmain(int argc, wchar_t **argv) rc = cmdRace(args); } else if (args.command == L"control-panel") { rc = cmdControlPanel(args); + } else if (args.command == L"no-interface") { + rc = cmdNoInterface(args); } else { rc = usage(); } diff --git a/tools/test/Invoke-SarTest.ps1 b/tools/test/Invoke-SarTest.ps1 index 559139e..7ba008d 100644 --- a/tools/test/Invoke-SarTest.ps1 +++ b/tools/test/Invoke-SarTest.ps1 @@ -149,6 +149,8 @@ if ($Scenarios -contains 'issues') { # Regression checks for specific GitHub issues, one short host each. $summary.scenarios += Invoke-Scenario 'issue-133-control-panel' ( @('control-panel', '--endpoints', 1) + $common) -Timeout 120 + $summary.scenarios += Invoke-Scenario 'issue-54-no-interface' ( + @('no-interface') + $common) -Timeout 120 } if ($Scenarios -contains 'matrix') { diff --git a/tools/test/README.md b/tools/test/README.md index fa74263..06374d1 100644 --- a/tools/test/README.md +++ b/tools/test/README.md @@ -86,6 +86,8 @@ The `issue-*` scenarios are regression checks for specific GitHub issues. `SarTest.exe control-panel` (#133) opens SAR's control panel on a running host, presses the hardware interface's Configure button and checks that the clock never had a second instance alive. It needs an interactive desktop. +`SarTest.exe no-interface` (#31, #54) opens SAR with no hardware interface +configured and checks that it still offers a stereo input and output. `Invoke-SarTest.ps1 -Scenarios` selects any of `issues`, `matrix`, `race` and `kill`. @@ -100,6 +102,7 @@ SarTest wasapi [--duration S] [--wait S] [--expect-invalidation] SarTest run [--iterations K] [--duration S] SarTest race [--cycles K] [--up S] [--down MS] [--openers T] [--hold MS] SarTest control-panel +SarTest no-interface ``` `install` creates the SAR software device node if needed, installs the