@@ -237,6 +237,10 @@ namespace flexasio {
237
237
value = static_cast <Enum>(std::underlying_type_t <Enum>(value) + 1 );
238
238
}
239
239
240
+ PaTime GetDefaultSuggestedLatency (long bufferSizeInFrames, ASIOSampleRate sampleRate) {
241
+ return 3 * bufferSizeInFrames / sampleRate;
242
+ }
243
+
240
244
}
241
245
242
246
constexpr FlexASIO::SampleType FlexASIO::float32 = { ::dechamps_cpputil::endianness == ::dechamps_cpputil::Endianness::LITTLE ? ASIOSTFloat32LSB : ASIOSTFloat32MSB, paFloat32, 4 , KSDATAFORMAT_SUBTYPE_IEEE_FLOAT };
@@ -533,16 +537,17 @@ namespace flexasio {
533
537
Log () << " Returning: " << info->name << " , " << (info->isActive ? " active" : " inactive" ) << " , group " << info->channelGroup << " , type " << ::dechamps_ASIOUtil::GetASIOSampleTypeString (info->type );
534
538
}
535
539
536
- FlexASIO::OpenStreamResult FlexASIO::OpenStream (bool inputEnabled, bool outputEnabled, double sampleRate, unsigned long framesPerBuffer, PaStreamCallback callback, void * callbackUserData)
540
+ template <typename Functor>
541
+ decltype (auto ) FlexASIO::WithStreamParameters(bool inputEnabled, bool outputEnabled, double sampleRate, PaTime defaultSuggestedLatency, Functor functor) const
537
542
{
538
- Log () << " CFlexASIO::OpenStream (inputEnabled = " << inputEnabled << " , outputEnabled = " << outputEnabled << " , sampleRate = " << sampleRate << " , framesPerBuffer = " << framesPerBuffer << " , callback = " << callback << " , callbackUserData = " << callbackUserData << " )" ;
539
- OpenStreamResult result;
540
- result. exclusive = hostApi.info .type == paWDMKS;
543
+ Log () << " FlexASIO::WithStreamParameters (inputEnabled = " << inputEnabled << " , outputEnabled = " << outputEnabled << " , sampleRate = " << sampleRate << " )" ;
544
+
545
+ auto exclusivity = hostApi.info .type == paWDMKS ? StreamExclusivity::EXCLUSIVE : StreamExclusivity::SHARED ;
541
546
542
547
PaStreamParameters common_parameters = { 0 };
543
548
common_parameters.sampleFormat = paNonInterleaved;
544
549
common_parameters.hostApiSpecificStreamInfo = NULL ;
545
- common_parameters.suggestedLatency = 3 * framesPerBuffer / sampleRate ;
550
+ common_parameters.suggestedLatency = defaultSuggestedLatency ;
546
551
547
552
PaWasapiStreamInfo common_wasapi_stream_info = { 0 };
548
553
if (hostApi.info .type == paWASAPI) {
@@ -570,7 +575,7 @@ namespace flexasio {
570
575
Log () << " Using " << (config.input .wasapiExclusiveMode ? " exclusive" : " shared" ) << " mode for input WASAPI stream" ;
571
576
if (config.input .wasapiExclusiveMode ) {
572
577
input_wasapi_stream_info.flags |= paWinWasapiExclusive;
573
- result. exclusive = true ;
578
+ exclusivity = StreamExclusivity::EXCLUSIVE ;
574
579
}
575
580
Log () << (config.input .wasapiAutoConvert ? " Enabling" : " Disabling" ) << " auto-conversion for input WASAPI stream" ;
576
581
if (config.input .wasapiAutoConvert ) {
@@ -602,7 +607,7 @@ namespace flexasio {
602
607
Log () << " Using " << (config.output .wasapiExclusiveMode ? " exclusive" : " shared" ) << " mode for output WASAPI stream" ;
603
608
if (config.output .wasapiExclusiveMode ) {
604
609
output_wasapi_stream_info.flags |= paWinWasapiExclusive;
605
- result. exclusive = true ;
610
+ exclusivity = StreamExclusivity::EXCLUSIVE ;
606
611
}
607
612
Log () << (config.output .wasapiAutoConvert ? " Enabling" : " Disabling" ) << " auto-conversion for output WASAPI stream" ;
608
613
if (config.output .wasapiAutoConvert ) {
@@ -616,20 +621,26 @@ namespace flexasio {
616
621
}
617
622
}
618
623
619
- result.stream = flexasio::OpenStream (
620
- inputEnabled ? &input_parameters : NULL ,
621
- outputEnabled ? &output_parameters : NULL ,
622
- sampleRate, framesPerBuffer, paPrimeOutputBuffersUsingStreamCallback, callback, callbackUserData);
623
- if (result.stream != nullptr ) {
624
- const auto streamInfo = Pa_GetStreamInfo (result.stream .get ());
625
- if (streamInfo == nullptr ) {
626
- Log () << " Unable to get stream info" ;
627
- }
628
- else {
629
- Log () << " Stream info: " << DescribeStreamInfo (*streamInfo);
630
- }
624
+ return functor (StreamParameters{
625
+ .inputParameters = inputEnabled ? &input_parameters : NULL ,
626
+ .outputParameters = outputEnabled ? &output_parameters : NULL ,
627
+ .sampleRate = sampleRate,
628
+ }, exclusivity);
629
+ }
630
+
631
+ Stream FlexASIO::OpenStream (const StreamParameters& streamParameters, unsigned long framesPerBuffer, PaStreamCallback callback, void * callbackUserData) const
632
+ {
633
+ Log () << " FlexASIO::OpenStream(framesPerBuffer = " << framesPerBuffer << " , callback = " << callback << " , callbackUserData = " << callbackUserData << " )" ;
634
+ auto stream = flexasio::OpenStream (
635
+ streamParameters, framesPerBuffer, paPrimeOutputBuffersUsingStreamCallback, callback, callbackUserData);
636
+ const auto streamInfo = Pa_GetStreamInfo (stream.get ());
637
+ if (streamInfo == nullptr ) {
638
+ Log () << " Unable to get stream info" ;
631
639
}
632
- return result;
640
+ else {
641
+ Log () << " Stream info: " << DescribeStreamInfo (*streamInfo);
642
+ }
643
+ return stream;
633
644
}
634
645
635
646
bool FlexASIO::CanSampleRate (ASIOSampleRate sampleRate)
@@ -641,20 +652,25 @@ namespace flexasio {
641
652
return false ;
642
653
}
643
654
644
- if (preparedState.has_value () && preparedState->IsExclusive () ) {
655
+ if (preparedState.has_value () && preparedState->GetStreamExclusivity () == StreamExclusivity::EXCLUSIVE ) {
645
656
// Some applications will call canSampleRate() while the stream is running. If the stream is exclusive our probes will fail.
646
657
// In that case we always say "yes" - always saying "no" confuses applications. See https://github.com/dechamps/FlexASIO/issues/66
658
+ // TODO: now that we are using Pa_IsFormatSupported() instead of Pa_OpenStream() to probe sample rates, is this still necessary?
647
659
Log () << " Faking sample rate " << sampleRate << " as available because an exclusive stream is currently running" ;
648
660
return true ;
649
661
}
650
662
663
+ const auto checkParameters = [&](const StreamParameters& streamParameters, StreamExclusivity) {
664
+ CheckFormatSupported (streamParameters);
665
+ };
666
+
651
667
// We do not know whether the host application intends to use only input channels, only output channels, or both.
652
668
// This logic ensures the driver is usable for all three use cases.
653
669
bool available = false ;
654
670
if (inputDevice.has_value ())
655
671
try {
656
672
Log () << " Checking if input supports this sample rate" ;
657
- OpenStream ( true , false , sampleRate, paFramesPerBufferUnspecified, NoOpStreamCallback, nullptr );
673
+ WithStreamParameters ( /* inputEnabled= */ true , /* outputEnabled= */ false , sampleRate, /* suggestedLatency */ 0 , checkParameters );
658
674
Log () << " Input supports this sample rate" ;
659
675
available = true ;
660
676
}
@@ -664,7 +680,7 @@ namespace flexasio {
664
680
if (outputDevice.has_value ())
665
681
try {
666
682
Log () << " Checking if output supports this sample rate" ;
667
- OpenStream ( false , true , sampleRate, paFramesPerBufferUnspecified, NoOpStreamCallback, nullptr );
683
+ WithStreamParameters ( /* inputEnabled= */ false , /* outputEnabled= */ true , sampleRate, /* suggestedLatency */ 0 , checkParameters );
668
684
Log () << " Output supports this sample rate" ;
669
685
available = true ;
670
686
}
@@ -778,7 +794,14 @@ namespace flexasio {
778
794
bufferInfos.push_back (asioBufferInfo);
779
795
}
780
796
return bufferInfos;
781
- }()), openStreamResult(flexASIO.OpenStream(buffers.inputChannelCount > 0 , buffers.outputChannelCount > 0 , sampleRate, unsigned long (bufferSizeInFrames), &PreparedState::StreamCallback, this)),
797
+ }()), streamWithExclusivity(flexASIO.WithStreamParameters(
798
+ buffers.inputChannelCount > 0 , buffers.outputChannelCount > 0 , sampleRate, GetDefaultSuggestedLatency(bufferSizeInFrames, sampleRate),
799
+ [&](const StreamParameters& streamParameters, StreamExclusivity streamExclusivity) {
800
+ return StreamWithExclusivity{
801
+ .stream = flexASIO.OpenStream (streamParameters, static_cast <unsigned long >(bufferSizeInFrames), &PreparedState::StreamCallback, this ),
802
+ .exclusivity = streamExclusivity,
803
+ };
804
+ })),
782
805
configWatcher (flexASIO.configLoader, [this ] { OnConfigChange (); }) {
783
806
if (callbacks->asioMessage ) ProbeHostMessages (callbacks->asioMessage );
784
807
}
@@ -808,7 +831,7 @@ namespace flexasio {
808
831
809
832
long FlexASIO::ComputeLatencyFromStream (PaStream* stream, bool output, size_t bufferSizeInFrames) const
810
833
{
811
- const PaStreamInfo* stream_info = Pa_GetStreamInfo (stream);
834
+ const PaStreamInfo* stream_info = Pa_GetStreamInfo (& stream);
812
835
if (!stream_info) throw ASIOException (ASE_HWMalfunction, " unable to get stream info" );
813
836
814
837
// See https://github.com/dechamps/FlexASIO/issues/10.
@@ -828,11 +851,26 @@ namespace flexasio {
828
851
const auto bufferSize = ComputeBufferSizes ().preferred ;
829
852
Log () << " Assuming " << bufferSize << " as the buffer size" ;
830
853
854
+ // Since CreateBuffers() has not been called yet, we do not know if the application intends
855
+ // to use only input channels, only output channels, or both. We arbitrarily decide to compute
856
+ // the input latency assuming an input-only stream, and the output latency assuming an
857
+ // output-only stream, because that makes this code least likely to fail. The tradeoff is this
858
+ // will likely return wrong latencies for full duplex streams (which tend to have higher
859
+ // latency due to the need for buffer adaptation).
860
+
861
+ const auto getLatency = [&](bool output) {
862
+ return WithStreamParameters (
863
+ /* inputEnabled=*/ !output, /* outputEnabled=*/ output, sampleRate, GetDefaultSuggestedLatency (bufferSize, sampleRate),
864
+ [&](const StreamParameters& streamParameters, StreamExclusivity) {
865
+ return ComputeLatencyFromStream (OpenStream (streamParameters, bufferSize, NoOpStreamCallback, nullptr ).get (), output, bufferSize);
866
+ });
867
+ };
868
+
831
869
if (!inputDevice.has_value ())
832
870
*inputLatency = 0 ;
833
871
else
834
872
try {
835
- *inputLatency = ComputeLatencyFromStream ( OpenStream ( true , false , sampleRate, bufferSize, NoOpStreamCallback, nullptr ). stream . get (), /* output=*/ false , bufferSize );
873
+ *inputLatency = getLatency ( /* output=*/ false );
836
874
Log () << " Using input latency from successful stream probe" ;
837
875
}
838
876
catch (const std::exception & exception ) {
@@ -843,7 +881,7 @@ namespace flexasio {
843
881
*outputLatency = 0 ;
844
882
else
845
883
try {
846
- *outputLatency = ComputeLatencyFromStream ( OpenStream ( false , true , sampleRate, bufferSize, NoOpStreamCallback, nullptr ). stream . get (), /* output=*/ true , bufferSize );
884
+ *outputLatency = getLatency ( /* output=*/ true );
847
885
Log () << " Using output latency from successful stream probe" ;
848
886
}
849
887
catch (const std::exception & exception ) {
@@ -856,8 +894,8 @@ namespace flexasio {
856
894
857
895
void FlexASIO::PreparedState::GetLatencies (long * inputLatency, long * outputLatency)
858
896
{
859
- *inputLatency = flexASIO.ComputeLatencyFromStream (openStreamResult .stream .get (), /* output=*/ false , buffers.bufferSizeInFrames );
860
- *outputLatency = flexASIO.ComputeLatencyFromStream (openStreamResult .stream .get (), /* output=*/ true , buffers.bufferSizeInFrames );
897
+ *inputLatency = flexASIO.ComputeLatencyFromStream (streamWithExclusivity .stream .get (), /* output=*/ false , buffers.bufferSizeInFrames );
898
+ *outputLatency = flexASIO.ComputeLatencyFromStream (streamWithExclusivity .stream .get (), /* output=*/ true , buffers.bufferSizeInFrames );
861
899
}
862
900
863
901
void FlexASIO::Start () {
@@ -885,7 +923,7 @@ namespace flexasio {
885
923
hostSupportsOutputReady (preparedState.flexASIO.hostSupportsOutputReady) {}
886
924
887
925
void FlexASIO::PreparedState::RunningState::RunningState::Start () {
888
- activeStream = StartStream (preparedState.openStreamResult .stream .get ());
926
+ activeStream = StartStream (preparedState.streamWithExclusivity .stream .get ());
889
927
}
890
928
891
929
void FlexASIO::Stop () {
0 commit comments