File: SC_PaUtils.cpp

package info (click to toggle)
supercollider 1%3A3.13.0%2Brepack-3
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 80,296 kB
  • sloc: cpp: 476,363; lisp: 84,680; ansic: 77,685; sh: 25,509; python: 7,909; makefile: 3,440; perl: 1,964; javascript: 974; xml: 826; java: 677; yacc: 314; lex: 175; objc: 152; ruby: 136
file content (175 lines) | stat: -rw-r--r-- 8,021 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// helper functions for SuperCollider server's PortAudio backend

#include "SC_PaUtils.hpp"
#include <cstring>
#include <cstdio>

#ifdef __APPLE__
#    include <pa_mac_core.h>
#endif

using PaSupportCheckFunc = PaError (*)(PaStreamParameters&, double);
PaError CheckDeviceSampleRateOrGetDefault(int* device, double sampleRate, int maxChannels, int defaultDevice,
                                          const char* deviceType, PaSupportCheckFunc isSupportedFunc) {
    if (*device != paNoDevice && sampleRate) {
        // check if device can support requested SR
        PaStreamParameters parameters = MakePaStreamParameters(*device, maxChannels, 0);
        PaError err = isSupportedFunc(parameters, sampleRate);
        if (err != paNoError) {
            fprintf(stdout, "PortAudio error: %s\nRequested sample rate %f for device %s is not supported\n",
                    Pa_GetErrorText(err), sampleRate, Pa_GetDeviceInfo(*device)->name);
            return err;
        }
    }
    // in case we still don't have a proper device, use the default device
    if (*device == paNoDevice) {
        *device = defaultDevice;
        if (*device != paNoDevice)
            fprintf(stdout, "Selecting default system %s device\n", deviceType);
    }
    return paNoError;
}

void TryMatchDeviceSameAPI(int* matchingDevice, const int* knownDevice, bool isInput) {
    if (*matchingDevice != paNoDevice || *knownDevice == paNoDevice)
        return;

    const auto* devInfo = Pa_GetDeviceInfo(*knownDevice);
    const auto* apiInfo = Pa_GetHostApiInfo(devInfo->hostApi);
    const auto maxChannels = isInput ? devInfo->maxInputChannels : devInfo->maxOutputChannels;
    bool isAsioFullDuplex = apiInfo->type == paASIO && maxChannels > 0;
    if (isAsioFullDuplex)
        *matchingDevice = *knownDevice;
    else {
        *matchingDevice = isInput ? apiInfo->defaultInputDevice : apiInfo->defaultOutputDevice;
        if (*matchingDevice != paNoDevice)
            fprintf(stdout, "Selecting default %s %s device\n", apiInfo->name, (isInput ? "input" : "output"));
    }
}

std::string GetPaDeviceName(PaDeviceIndex index) {
    auto* pdi = Pa_GetDeviceInfo(index);
    return GetPaDeviceName(pdi);
}

std::string GetPaDeviceName(const PaDeviceInfo* pdi) {
    std::string name;
#ifndef __APPLE__
    name += Pa_GetHostApiInfo(pdi->hostApi)->name;
    name += " : ";
#endif
    name += pdi->name;
    return name;
}

PaDeviceIndex GetPaDeviceFromName(const char* device, bool isInput) {
    if (device == nullptr || device[0] == '\0')
        return paNoDevice;

    PaDeviceIndex numDevices = Pa_GetDeviceCount();
    for (PaDeviceIndex i = 0; i < numDevices; i++) {
        auto* pdi = Pa_GetDeviceInfo(i);
        std::string devString = GetPaDeviceName(i);
        if (strstr(devString.c_str(), device)) {
            if (isInput && pdi->maxInputChannels > 0) {
                return i;
            } else if (!isInput && pdi->maxOutputChannels > 0) {
                return i;
            }
        }
    }

    return paNoDevice;
}

PaError TryGetDefaultPaDevices(PaDeviceIndex* inDevice, PaDeviceIndex* outDevice, int numIns, int numOuts,
                               double sampleRate) {
    if (numIns && !numOuts) {
        *outDevice = paNoDevice;
        auto maxChannels = (*inDevice != paNoDevice) ? Pa_GetDeviceInfo(*inDevice)->maxInputChannels : 0;
        return CheckDeviceSampleRateOrGetDefault(
            inDevice, sampleRate, maxChannels, Pa_GetDefaultInputDevice(), "input",
            [](PaStreamParameters& params, double sr) { return Pa_IsFormatSupported(&params, nullptr, sr); });
    } else if (!numIns && numOuts) {
        *inDevice = paNoDevice;
        auto maxChannels = (*outDevice != paNoDevice) ? Pa_GetDeviceInfo(*outDevice)->maxOutputChannels : 0;
        return CheckDeviceSampleRateOrGetDefault(
            outDevice, sampleRate, maxChannels, Pa_GetDefaultOutputDevice(), "output",
            [](PaStreamParameters& params, double sr) { return Pa_IsFormatSupported(nullptr, &params, sr); });
    } else if (numIns && numOuts) {
        // if one device is specified, let's try to open another one on matching api
        TryMatchDeviceSameAPI(inDevice, outDevice, true);
        TryMatchDeviceSameAPI(outDevice, inDevice, false);

        bool apisAreDifferent = *inDevice != paNoDevice && *outDevice != paNoDevice
            && Pa_GetDeviceInfo(*inDevice)->hostApi != Pa_GetDeviceInfo(*outDevice)->hostApi;
        if (apisAreDifferent) {
            fprintf(stdout, "Requested devices %s and %s use different API. ", GetPaDeviceName(*inDevice).c_str(),
                    GetPaDeviceName(*outDevice).c_str());
            *outDevice = Pa_GetHostApiInfo(Pa_GetDeviceInfo(*inDevice)->hostApi)->defaultOutputDevice;
            fprintf(stdout, "Setting output device to %s.\n", GetPaDeviceName(*outDevice).c_str());
        }
        // check for matching sampleRate or requested sample rate
        if (*inDevice != paNoDevice && *outDevice != paNoDevice) {
            const auto in_parameters =
                MakePaStreamParameters(*inDevice, Pa_GetDeviceInfo(*inDevice)->maxInputChannels, 0);
            const auto out_parameters =
                MakePaStreamParameters(*outDevice, Pa_GetDeviceInfo(*outDevice)->maxOutputChannels, 0);
            if (sampleRate) {
                // check if devices can support requested SR
                PaError err = Pa_IsFormatSupported(&in_parameters, &out_parameters, sampleRate);
                if (err != paNoError) {
                    fprintf(stdout, "\nRequested sample rate %f for devices %s and %s is not supported.\n", sampleRate,
                            GetPaDeviceName(*inDevice).c_str(), GetPaDeviceName(*outDevice).c_str());
                    return err;
                }
            } else {
                // if we don't request SR, check if devices have matching SR
                auto inSR = Pa_GetDeviceInfo(*inDevice)->defaultSampleRate;
                auto outSR = Pa_GetDeviceInfo(*outDevice)->defaultSampleRate;
                if (uint32_t(inSR) != uint32_t(outSR)) {
                    // if defaults are different, check if both devices can be opened using the OUTPUT's SR
                    PaError err = Pa_IsFormatSupported(&in_parameters, &out_parameters, outSR);
                    if (err != paNoError) {
                        fprintf(stdout,
                                "\nRequested devices %s and %s use different sample rates. "
                                "Please set matching sample rates "
                                "in the Windows Sound Control Panel and try again.\n",
                                GetPaDeviceName(*inDevice).c_str(), GetPaDeviceName(*outDevice).c_str());
                        return err;
                    }
                }
            }
        }

        // in case we still don't have a proper device, use default devices
        if (*inDevice == paNoDevice || *outDevice == paNoDevice) {
            *inDevice = Pa_GetDefaultInputDevice();
            *outDevice = Pa_GetDefaultOutputDevice();
            if (*inDevice != paNoDevice && *outDevice != paNoDevice)
                fprintf(stdout, "Selecting default system input/output devices\n");
        }
    } else {
        // no inputs nor outputs
        *inDevice = paNoDevice;
        *outDevice = paNoDevice;
    }
    return paNoError;
}

PaStreamParameters MakePaStreamParameters(int device, int channelCount, double suggestedLatency) {
    PaStreamParameters streamParams;
    PaSampleFormat fmt = paFloat32 | paNonInterleaved;
    streamParams.device = device;
    streamParams.channelCount = channelCount;
    streamParams.sampleFormat = fmt;
    streamParams.suggestedLatency = suggestedLatency;
#ifdef __APPLE__
    static PaMacCoreStreamInfo macInfo;
    PaMacCore_SetupStreamInfo(&macInfo, paMacCorePro);
    streamParams.hostApiSpecificStreamInfo = &macInfo;
#else
    streamParams.hostApiSpecificStreamInfo = nullptr;
#endif
    return streamParams;
}