Created
November 28, 2020 18:03
-
-
Save Ohjurot/b0c04dfbd25fb71bc0da50947d313d1b to your computer and use it in GitHub Desktop.
Minimal demo to get the DualSense controller vibrating using the haptic feedback (Audio device channel 3 and 4)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Windows.h> | |
#include <avrt.h> | |
#include <string> | |
#include <sstream> | |
#include <initguid.h> | |
#include <Mmdeviceapi.h> | |
#include <audioclient.h> | |
#include <Functiondiscoverykeys_devpkey.h> | |
#pragma comment(lib, "Avrt.lib") | |
#define COM_RELEASE(com) if(com){com->Release(); com = NULL;} | |
#define TWO_PI (3.14159265359f * 2) | |
// Copy and paste your dual sense device id here (Probably the only 4 channel audio device on your computer. The programm will list every availible device on startup. | |
LPCWSTR deviceId = L"{0.0.0.00000000}.{3d996160-5a02-4448-ad85-371b2dde8275}"; | |
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); | |
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); | |
const IID IID_IAudioClient = __uuidof(IAudioClient); | |
typedef std::wstringstream wstrBuilder; | |
class Console { | |
public: | |
Console() { | |
AllocConsole(); | |
consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); | |
} | |
~Console() { | |
FreeConsole(); | |
} | |
void writeLine(LPCWSTR text) { | |
write(text); | |
write(L"\n"); | |
} | |
void writeLine(wstrBuilder& builder) { | |
writeLine(builder.str().c_str()); | |
builder.str(L""); | |
} | |
void write(LPCWSTR text) { | |
WriteConsoleW(consoleHandle, text, wcslen(text), NULL, NULL); | |
} | |
void write(wstrBuilder& builder) { | |
write(builder.str().c_str()); | |
builder.str(L""); | |
} | |
private: | |
HANDLE consoleHandle; | |
}; | |
LARGE_INTEGER deltaTimeUs(LARGE_INTEGER start, LARGE_INTEGER current, LARGE_INTEGER freq) { | |
LARGE_INTEGER delta; | |
delta.QuadPart = current.QuadPart - start.QuadPart; | |
delta.QuadPart *= 1000000; | |
delta.QuadPart /= freq.QuadPart; | |
return delta; | |
} | |
INT WINAPI wWinMain(HINSTANCE _In_ hInstance, HINSTANCE _In_opt_ hPrevInstance, LPWSTR _In_ cmdArgs, INT _In_ cmdShow) { | |
// Console | |
Console console; | |
wstrBuilder builder; | |
// Init app | |
CoInitialize(NULL); | |
HRESULT hr = S_OK; | |
// Get device enum | |
IMMDeviceEnumerator* ptrEnum = NULL; | |
if (FAILED(hr = CoCreateInstance( | |
CLSID_MMDeviceEnumerator, | |
NULL, | |
CLSCTX_ALL, | |
IID_PPV_ARGS(&ptrEnum) | |
))) { | |
return -1; | |
} | |
// Enum collection | |
IMMDeviceCollection* ptrCollection = NULL; | |
if (SUCCEEDED(ptrEnum->EnumAudioEndpoints(EDataFlow::eRender, DEVICE_STATE_ACTIVE, &ptrCollection))) { | |
UINT numDevice = 0; | |
ptrCollection->GetCount(&numDevice); | |
builder << L"Found " << numDevice << L" audio devices"; | |
console.writeLine(builder); | |
// Target client | |
IAudioClient* ptrAudioClient = NULL; | |
WAVEFORMATEXTENSIBLE waveFormate; | |
// Enum device | |
for (UINT i = 0; i < numDevice; i++) { | |
IMMDevice* ptrDevice = NULL; | |
// Check device | |
if (SUCCEEDED(ptrCollection->Item(i, &ptrDevice))) { | |
IAudioClient* ptrAudioClientTmp = NULL; | |
LPWSTR id = NULL; | |
ptrDevice->GetId(&id); | |
ptrDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&ptrAudioClientTmp); | |
// Get properties | |
IPropertyStore* ptrPropStore = NULL; | |
if (SUCCEEDED(ptrDevice->OpenPropertyStore(STGM_READ, &ptrPropStore))) { | |
builder << std::endl << L"[" << i << L"] "; | |
PROPVARIANT var; | |
PropVariantInit(&var); | |
if (SUCCEEDED(ptrPropStore->GetValue(PKEY_Device_FriendlyName, &var))) { | |
builder << L"Device name: " << var.pwszVal; | |
} | |
PropVariantClear(&var); | |
PropVariantInit(&var); | |
if (SUCCEEDED(ptrPropStore->GetValue(PKEY_AudioEngine_DeviceFormat, &var))) { | |
WAVEFORMATEX* ptrFex = (WAVEFORMATEX*)var.blob.pBlobData; | |
builder << std::endl << L"Chanels: " << ptrFex->nChannels << L" Bit depth: " << ptrFex->wBitsPerSample << L" Frequency: " << ptrFex->nSamplesPerSec << std::endl; | |
if (wcscmp(id, deviceId) == 0) { | |
if (ptrFex->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { | |
memcpy(&waveFormate, var.blob.pBlobData, sizeof(WAVEFORMATEXTENSIBLE)); | |
} | |
else { | |
memcpy(&waveFormate, var.blob.pBlobData, sizeof(WAVEFORMATEX)); | |
} | |
} | |
} | |
PropVariantClear(&var); | |
} | |
else { | |
builder << L"[" << i << L"] Failed to interface device!"; | |
} | |
builder << L"ID: " << id; | |
COM_RELEASE(ptrPropStore); | |
// Copy to target | |
if (wcscmp(id, deviceId) == 0) { | |
ptrAudioClient = ptrAudioClientTmp; | |
ptrAudioClientTmp->AddRef(); | |
} | |
COM_RELEASE(ptrAudioClientTmp); | |
} | |
else { | |
builder << L"[" << i << L"] Failed to interface device!"; | |
} | |
// clenup device | |
COM_RELEASE(ptrDevice); | |
console.writeLine(builder); | |
} | |
if (ptrAudioClient) { | |
// Info | |
builder << std::endl << std::endl << L"Found device! Open succeded"; | |
console.writeLine(builder); | |
// Time | |
REFERENCE_TIME hnsDefaultPeriode = 0; | |
REFERENCE_TIME hnsMinPeriode = 0; | |
ptrAudioClient->GetDevicePeriod(&hnsDefaultPeriode, &hnsMinPeriode); | |
builder << L"Default period: " << hnsDefaultPeriode << L" Min period: " << hnsMinPeriode; | |
console.writeLine(builder); | |
// Init exclusive | |
hr = ptrAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, hnsDefaultPeriode, hnsDefaultPeriode, (WAVEFORMATEX*)&waveFormate, NULL); | |
HANDLE audioEvent = CreateEvent(NULL, FALSE, FALSE, NULL); | |
hr = ptrAudioClient->SetEventHandle(audioEvent); | |
// Get buffer size | |
UINT bufferFrameCount = 0; | |
hr = ptrAudioClient->GetBufferSize(&bufferFrameCount); | |
// Get render client | |
IAudioRenderClient* ptrRenderClient = NULL; | |
hr = ptrAudioClient->GetService(IID_PPV_ARGS(&ptrRenderClient)); | |
// Load first data into buffer (Null for now) | |
BYTE* ptrBuffer = NULL; | |
hr = ptrRenderClient->GetBuffer(bufferFrameCount, &ptrBuffer); | |
// For now silence | |
hr = ptrRenderClient->ReleaseBuffer(bufferFrameCount, AUDCLNT_BUFFERFLAGS_SILENT); | |
ptrBuffer = NULL; | |
// Evaluate thread propertyls | |
DWORD avThreadIndex = 0; | |
AvSetMmThreadCharacteristicsW(L"Pro Audio", &avThreadIndex); | |
// Play | |
hr = ptrAudioClient->Start(); | |
// Keep track of playing | |
BOOL keepPlaying = true; | |
LARGE_INTEGER startPoint, currentPoint; | |
LARGE_INTEGER timeFrequency; | |
QueryPerformanceFrequency(&timeFrequency); | |
QueryPerformanceCounter(&startPoint); | |
currentPoint = startPoint; | |
LARGE_INTEGER sampledTime; | |
sampledTime.QuadPart = 0; | |
LARGE_INTEGER sampleOnOff; | |
sampleOnOff.QuadPart = 50000; | |
LARGE_INTEGER sampleOnOffCounter; | |
sampleOnOffCounter.QuadPart = 0; | |
BOOL off = FALSE; | |
UINT16 freqX = 20; | |
// Time | |
while (deltaTimeUs(startPoint, currentPoint, timeFrequency).QuadPart <= 5 * 1000 * 1000) { | |
// Wait for audio updata | |
if (WaitForSingleObject(audioEvent, 1000) == WAIT_OBJECT_0) { | |
// Get buffer | |
hr = ptrRenderClient->GetBuffer(bufferFrameCount, &ptrBuffer); | |
// For loo | |
for (UINT i = 0; i < bufferFrameCount; i++) { | |
// Sample sine | |
FLOAT scale = (sampledTime.QuadPart % (1000000 / freqX)) / (FLOAT)(1000000 / freqX); | |
UINT16 value = UINT16_MAX * (0.5f * sin(scale * TWO_PI) + 0.5f); | |
// INT16 value = UINT16_MAX * ((scale >= 0.5f) ? 1.0f : 0.0f); | |
if (sampleOnOffCounter.QuadPart >= sampleOnOff.QuadPart && value <= 0.05f) { | |
off = !off; | |
sampleOnOffCounter.QuadPart = 0; | |
} | |
// Set each chanell | |
BYTE* ptrStart = &ptrBuffer[(4 * 2) * i]; | |
if (off) { | |
*((UINT16*)&ptrStart[0]) = 0; | |
*((UINT16*)&ptrStart[2]) = 0; | |
*((UINT16*)&ptrStart[4]) = 0; | |
*((UINT16*)&ptrStart[6]) = 0; | |
} | |
else { | |
*((UINT16*)&ptrStart[0]) = 0; | |
*((UINT16*)&ptrStart[2]) = 0; | |
*((UINT16*)&ptrStart[4]) = value; | |
*((UINT16*)&ptrStart[6]) = value; | |
} | |
// Increate sample time | |
sampledTime.QuadPart += (1000000 / waveFormate.Format.nSamplesPerSec); | |
sampleOnOffCounter.QuadPart += (1000000 / waveFormate.Format.nSamplesPerSec); | |
} | |
// Release buffer | |
hr = ptrRenderClient->ReleaseBuffer(bufferFrameCount, NULL); | |
} | |
// Updata time | |
QueryPerformanceCounter(¤tPoint); | |
} | |
// Wait | |
Sleep(1000 / waveFormate.Format.nSamplesPerSec); | |
// Free | |
COM_RELEASE(ptrRenderClient); | |
ptrAudioClient->Stop(); | |
ptrAudioClient->Reset(); | |
} | |
COM_RELEASE(ptrAudioClient); | |
} | |
// Rlease | |
COM_RELEASE(ptrCollection); | |
COM_RELEASE(ptrEnum); | |
// End | |
CoUninitialize(); | |
system("pause"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment