Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions SarAsio/wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down
68 changes: 65 additions & 3 deletions SarTest/SarTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>\n"
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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();
}
Expand Down
2 changes: 2 additions & 0 deletions tools/test/Invoke-SarTest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
3 changes: 3 additions & 0 deletions tools/test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand All @@ -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
Expand Down
Loading