Skip to content

Instantly share code, notes, and snippets.

@Ohjurot
Created November 28, 2020 18:03
Show Gist options
  • Save Ohjurot/b0c04dfbd25fb71bc0da50947d313d1b to your computer and use it in GitHub Desktop.
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)
#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);
// Print
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(&currentPoint);
}
// 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