1 /*
2   audio.directsound (2007-12-26)
3   author: byuu
4 */
5 
6 #include <dsound.h>
7 
8 namespace ruby {
9 
10 class pAudioDS {
11 public:
12   LPDIRECTSOUND ds;
13   LPDIRECTSOUNDBUFFER dsb_p, dsb_b;
14   DSBUFFERDESC dsbd;
15   WAVEFORMATEX wfx;
16 
17   struct {
18     unsigned rings;
19     unsigned latency;
20 
21     uint32_t *buffer;
22     unsigned bufferoffset;
23 
24     unsigned readring;
25     unsigned writering;
26     int distance;
27   } device;
28 
29   struct {
30     HWND handle;
31     bool synchronize;
32     unsigned frequency;
33     unsigned latency;
34   } settings;
35 
cap(const string & name)36   bool cap(const string& name) {
37     if(name == Audio::Handle) return true;
38     if(name == Audio::Synchronize) return true;
39     if(name == Audio::Frequency) return true;
40     if(name == Audio::Latency) return true;
41     return false;
42   }
43 
get(const string & name)44   any get(const string& name) {
45     if(name == Audio::Handle) return (uintptr_t)settings.handle;
46     if(name == Audio::Synchronize) return settings.synchronize;
47     if(name == Audio::Frequency) return settings.frequency;
48     if(name == Audio::Latency) return settings.latency;
49     return false;
50   }
51 
set(const string & name,const any & value)52   bool set(const string& name, const any& value) {
53     if(name == Audio::Handle) {
54       settings.handle = (HWND)any_cast<uintptr_t>(value);
55       return true;
56     }
57 
58     if(name == Audio::Synchronize) {
59       settings.synchronize = any_cast<bool>(value);
60       if(ds) clear();
61       return true;
62     }
63 
64     if(name == Audio::Frequency) {
65       settings.frequency = any_cast<unsigned>(value);
66       if(ds) init();
67       return true;
68     }
69 
70     if(name == Audio::Latency) {
71       settings.latency = any_cast<unsigned>(value);
72       if(ds) init();
73       return true;
74     }
75 
76     return false;
77   }
78 
sample(uint16_t left,uint16_t right)79   void sample(uint16_t left, uint16_t right) {
80     device.buffer[device.bufferoffset++] = left + (right << 16);
81     if(device.bufferoffset < device.latency) return;
82     device.bufferoffset = 0;
83 
84     DWORD pos, size;
85     void *output;
86 
87     if(settings.synchronize == true) {
88       //wait until playback buffer has an empty ring to write new audio data to
89       while(device.distance >= device.rings - 1) {
90         dsb_b->GetCurrentPosition(&pos, 0);
91         unsigned activering = pos / (device.latency * 4);
92         if(activering == device.readring) {
93           if(settings.synchronize == false) Sleep(1);
94           continue;
95         }
96 
97         //subtract number of played rings from ring distance counter
98         device.distance -= (device.rings + activering - device.readring) % device.rings;
99         device.readring = activering;
100 
101         if(device.distance < 2) {
102           //buffer underflow; set max distance to recover quickly
103           device.distance  = device.rings - 1;
104           device.writering = (device.rings + device.readring - 1) % device.rings;
105           break;
106         }
107       }
108     }
109 
110     device.writering = (device.writering + 1) % device.rings;
111     device.distance  = (device.distance  + 1) % device.rings;
112 
113     if(dsb_b->Lock(device.writering * device.latency * 4, device.latency * 4, &output, &size, 0, 0, 0) == DS_OK) {
114       memcpy(output, device.buffer, device.latency * 4);
115       dsb_b->Unlock(output, size, 0, 0);
116     }
117   }
118 
clear()119   void clear() {
120     device.readring  = 0;
121     device.writering = device.rings - 1;
122     device.distance  = device.rings - 1;
123 
124     device.bufferoffset = 0;
125     if(device.buffer) memset(device.buffer, 0, device.latency * device.rings * 4);
126 
127     if(!dsb_b) return;
128     dsb_b->Stop();
129     dsb_b->SetCurrentPosition(0);
130 
131     DWORD size;
132     void *output;
133     dsb_b->Lock(0, device.latency * device.rings * 4, &output, &size, 0, 0, 0);
134     memset(output, 0, size);
135     dsb_b->Unlock(output, size, 0, 0);
136 
137     dsb_b->Play(0, 0, DSBPLAY_LOOPING);
138   }
139 
init()140   bool init() {
141     term();
142 
143     device.rings   = 8;
144     device.latency = settings.frequency * settings.latency / device.rings / 1000.0 + 0.5;
145     device.buffer  = new uint32_t[device.latency * device.rings];
146     device.bufferoffset = 0;
147 
148     DirectSoundCreate(0, &ds, 0);
149     ds->SetCooperativeLevel((HWND)settings.handle, DSSCL_PRIORITY);
150 
151     memset(&dsbd, 0, sizeof(dsbd));
152     dsbd.dwSize        = sizeof(dsbd);
153     dsbd.dwFlags       = DSBCAPS_PRIMARYBUFFER;
154     dsbd.dwBufferBytes = 0;
155     dsbd.lpwfxFormat   = 0;
156     ds->CreateSoundBuffer(&dsbd, &dsb_p, 0);
157 
158     memset(&wfx, 0, sizeof(wfx));
159     wfx.wFormatTag      = WAVE_FORMAT_PCM;
160     wfx.nChannels       = 2;
161     wfx.nSamplesPerSec  = settings.frequency;
162     wfx.wBitsPerSample  = 16;
163     wfx.nBlockAlign     = wfx.wBitsPerSample / 8 * wfx.nChannels;
164     wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
165     dsb_p->SetFormat(&wfx);
166 
167     memset(&dsbd, 0, sizeof(dsbd));
168     dsbd.dwSize  = sizeof(dsbd);
169     dsbd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLFREQUENCY | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE;
170     dsbd.dwBufferBytes   = device.latency * device.rings * sizeof(uint32_t);
171     dsbd.guid3DAlgorithm = GUID_NULL;
172     dsbd.lpwfxFormat     = &wfx;
173     ds->CreateSoundBuffer(&dsbd, &dsb_b, 0);
174     dsb_b->SetFrequency(settings.frequency);
175     dsb_b->SetCurrentPosition(0);
176 
177     clear();
178     return true;
179   }
180 
term()181   void term() {
182     if(device.buffer) {
183       delete[] device.buffer;
184       device.buffer = 0;
185     }
186 
187     if(dsb_b) { dsb_b->Stop(); dsb_b->Release(); dsb_b = 0; }
188     if(dsb_p) { dsb_p->Stop(); dsb_p->Release(); dsb_p = 0; }
189     if(ds) { ds->Release(); ds = 0; }
190   }
191 
pAudioDS()192   pAudioDS() {
193     ds = 0;
194     dsb_p = 0;
195     dsb_b = 0;
196 
197     device.buffer = 0;
198     device.bufferoffset = 0;
199     device.readring = 0;
200     device.writering = 0;
201     device.distance = 0;
202 
203     settings.handle = GetDesktopWindow();
204     settings.synchronize = false;
205     settings.frequency = 22050;
206     settings.latency = 120;
207   }
208 };
209 
210 DeclareAudio(DS)
211 
212 };
213