Skip to content

Commit c98fff1

Browse files
Added audio device change fix and moved/deleted stuff
1 parent 05b955b commit c98fff1

21 files changed

+314
-382
lines changed

assets/shared/music/Stargazer.mp3

300 KB
Binary file not shown.

assets/shared/music/Stargazer.ogg

161 KB
Binary file not shown.
-301 KB
Binary file not shown.
-167 KB
Binary file not shown.
294 KB
Binary file not shown.
139 KB
Binary file not shown.

source/InitState.hx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
package;
22

3-
import flixel.FlxG;
43
import flixel.FlxState;
54
import flixel.math.FlxMath;
65
import flixel.system.FlxAssets;
7-
import flixel.tweens.FlxTween;
86
import lime.app.Application;
97
import openfl.Lib;
108
import openfl.display.StageQuality;
119
import openfl.display.StageScaleMode;
1210
import starcore.backend.api.DiscordClient;
1311
import starcore.backend.data.ClientPrefs;
12+
import starcore.backend.util.AudioUtil;
1413
import starcore.backend.util.CacheUtil;
1514
import starcore.backend.util.FlixelUtil;
1615
import starcore.backend.util.LoggerUtil;
1716
import starcore.backend.util.PathUtil;
1817
import starcore.menus.MainMenuState;
19-
import starcore.shaders.*;
2018
#if web
2119
import js.Browser;
2220
#end
@@ -43,6 +41,9 @@ class InitState extends FlxState
4341
// cause null errors and crash the game!
4442
ClientPrefs.loadAll();
4543

44+
// Configure and setup the Audio utility class.
45+
AudioUtil.initAudioFix();
46+
4647
// Assign and configure Flixel settings.
4748
configureFlixelSettings();
4849

@@ -74,7 +75,7 @@ class InitState extends FlxState
7475
// NOTE: Maybe use a custom cursor (that isn't Flixel's)?
7576
FlxG.mouse.useSystemCursor = true;
7677

77-
// Set auto pause to false.
78+
// Set auto pause to false (we NEVER want this enabled).
7879
FlxG.autoPause = false;
7980

8081
// Set the stage and scaling modes.

source/import.hx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// ===============================================================================================
2+
// This is a file which automatically imports common classes used throughout the project.
3+
// You can add your own imports here if you want them to be globally available, that way you
4+
// don't have to import them in every file!
5+
// ===============================================================================================
6+
7+
package;
8+
9+
//
10+
// FLIXEL IMPORTS
11+
// =============================
12+
13+
import flixel.FlxG;
14+
import flixel.FlxSprite;
15+
import flixel.tweens.FlxTween;
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
package starcore.backend.util;
2+
3+
import flixel.sound.FlxSound;
4+
import flixel.util.FlxSignal;
5+
import lime.media.AudioBuffer;
6+
import lime.media.AudioManager;
7+
import lime.utils.UInt8Array;
8+
import openfl.media.Sound;
9+
10+
/**
11+
* Type definition for storing sound regeneration data.
12+
*/
13+
#if (windows && cpp)
14+
typedef RegenSoundData =
15+
{
16+
var sound:FlxSound;
17+
var isPlaying:Bool;
18+
var time:Float;
19+
};
20+
21+
/**
22+
* Utility class for handling the game's audio, such as restarting audio on device change.
23+
*
24+
* THIS WAS NOT MADE BY ME! Credits go to cyn0x8 for this class.
25+
* @see https://github.com/cyn0x8
26+
* @see https://github.com/FunkinCrew/Funkin/pull/5569
27+
*/
28+
@:buildXml('
29+
<target id="haxe">
30+
<lib name="ole32.lib" if="windows"/>
31+
</target>
32+
')
33+
@:cppFileCode('
34+
#include <string>
35+
#include "mmdeviceapi.h"
36+
37+
bool _audioDeviceChanged = false;
38+
class AudioFixClient : public IMMNotificationClient {
39+
public:
40+
41+
AudioFixClient() : _refCount(1), _pDeviceEnum(nullptr) {
42+
HRESULT result = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (void**)&_pDeviceEnum);
43+
if (result == S_OK) _pDeviceEnum->RegisterEndpointNotificationCallback(this);
44+
updateCurrentDeviceID();
45+
}
46+
47+
~AudioFixClient() {
48+
if (_pDeviceEnum != nullptr) {
49+
_pDeviceEnum->UnregisterEndpointNotificationCallback(this);
50+
_pDeviceEnum->Release();
51+
_pDeviceEnum = nullptr;
52+
}
53+
}
54+
55+
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) {
56+
if (flow == eRender && role == eConsole && pwstrDefaultDeviceId != nullptr) {
57+
if (_currentDeviceID.compare(pwstrDefaultDeviceId) != 0) {
58+
_audioDeviceChanged = true;
59+
}
60+
}
61+
62+
return S_OK;
63+
}
64+
65+
ULONG STDMETHODCALLTYPE AddRef() {
66+
return InterlockedIncrement(&_refCount);
67+
}
68+
69+
ULONG STDMETHODCALLTYPE Release() {
70+
ULONG ulRef = InterlockedDecrement(&_refCount);
71+
if (0 == ulRef) delete this;
72+
return ulRef;
73+
}
74+
75+
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface) {
76+
if (IID_IUnknown == riid) {
77+
AddRef();
78+
*ppvInterface = (IUnknown*)this;
79+
} else if (__uuidof(IMMNotificationClient) == riid) {
80+
AddRef();
81+
*ppvInterface = (IMMNotificationClient*)this;
82+
} else {
83+
*ppvInterface = NULL;
84+
return E_NOINTERFACE;
85+
}
86+
87+
return S_OK;
88+
}
89+
90+
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) {
91+
return S_OK;
92+
}
93+
94+
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) {
95+
return S_OK;
96+
}
97+
98+
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) {
99+
return S_OK;
100+
}
101+
102+
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) {
103+
return S_OK;
104+
}
105+
106+
void updateCurrentDeviceID() {
107+
if (_pDeviceEnum == nullptr) return;
108+
IMMDevice* _pDevice = nullptr;
109+
LPWSTR _deviceId = nullptr;
110+
HRESULT result = _pDeviceEnum->GetDefaultAudioEndpoint(eRender, eConsole, &_pDevice);
111+
if (SUCCEEDED(result) && _pDevice != nullptr) {
112+
result = _pDevice->GetId(&_deviceId);
113+
if (SUCCEEDED(result) && _deviceId != nullptr) {
114+
_currentDeviceID = _deviceId;
115+
CoTaskMemFree(_deviceId);
116+
}
117+
118+
_pDevice->Release();
119+
}
120+
}
121+
122+
private:
123+
124+
std::wstring _currentDeviceID;
125+
IMMDeviceEnumerator* _pDeviceEnum;
126+
127+
LONG _refCount;
128+
};
129+
130+
AudioFixClient* curAudioFix;
131+
')
132+
#end
133+
@:nullSafety
134+
final class AudioUtil
135+
{
136+
#if (windows && cpp)
137+
/**
138+
* Signal dispatched when the current audio device is changed, after an attempted restart.
139+
*/
140+
public static final audioDeviceChangeSignal:FlxSignal = new FlxSignal();
141+
142+
/**
143+
* Whether the current audio device has changed.
144+
*/
145+
static var audioDeviceChanged(get, set):Bool;
146+
147+
function new() {}
148+
149+
/**
150+
* Gets whether the current audio device has changed.
151+
*
152+
* @return If the audio device has changed.
153+
*/
154+
public static function get_audioDeviceChanged():Bool
155+
{
156+
return cast untyped __cpp__('_audioDeviceChanged');
157+
}
158+
159+
static function set_audioDeviceChanged(v:Bool):Bool
160+
{
161+
untyped __cpp__('_audioDeviceChanged = (bool)v;');
162+
return v;
163+
}
164+
165+
static var initializedAudioFix:Bool = false;
166+
167+
/**
168+
* Initializes the audio fix client to handle audio device changes.
169+
* This should be called once at the start of the application.
170+
*/
171+
public static function initAudioFix():Void
172+
{
173+
if (initializedAudioFix)
174+
{
175+
return;
176+
}
177+
178+
LoggerUtil.log('Initializing audio device change detection');
179+
180+
untyped __cpp__('if (curAudioFix == nullptr) curAudioFix = new AudioFixClient();');
181+
182+
FlxG.signals.preUpdate.add(function():Void
183+
{
184+
if (audioDeviceChanged)
185+
{
186+
LoggerUtil.log('AUDIO DEVICE CHANGE DETECTED', INFO, false);
187+
LoggerUtil.log('Restarting audio system');
188+
restartAudio();
189+
}
190+
});
191+
192+
initializedAudioFix = true;
193+
}
194+
195+
/**
196+
* Restarts the audio system and regenerates all sounds.
197+
*/
198+
public static function restartAudio():Void
199+
{
200+
final curSounds:Array<FlxSound> = new Array<FlxSound>();
201+
202+
@:privateAccess
203+
for (sound in FlxG.sound.list)
204+
{
205+
if (sound != null && sound.exists)
206+
{
207+
DataUtil.pushUniqueElement(curSounds, sound);
208+
}
209+
}
210+
for (sound in FlxG.sound.list)
211+
{
212+
if (sound != null && sound.exists)
213+
{
214+
DataUtil.pushUniqueElement(curSounds, sound);
215+
}
216+
}
217+
if (FlxG.sound.music != null && FlxG.sound.music.exists)
218+
{
219+
DataUtil.pushUniqueElement(curSounds, FlxG.sound.music);
220+
}
221+
222+
final regenData:Array<RegenSoundData> = new Array<RegenSoundData>();
223+
for (sound in curSounds)
224+
{
225+
regenData.push({sound: sound, isPlaying: sound.playing, time: sound.time});
226+
sound.pause();
227+
}
228+
229+
AudioManager.shutdown();
230+
AudioManager.init();
231+
232+
untyped __cpp__('if (curAudioFix != nullptr) curAudioFix->updateCurrentDeviceID();');
233+
234+
for (entry in regenData)
235+
{
236+
final sound:FlxSound = entry.sound;
237+
@:privateAccess regenSound(sound._sound);
238+
239+
if (entry.isPlaying)
240+
{
241+
sound.play(true, entry.time);
242+
}
243+
244+
sound.time = entry.time;
245+
}
246+
247+
// TODO: Do garbage collection here.
248+
249+
audioDeviceChanged = false;
250+
audioDeviceChangeSignal.dispatch();
251+
}
252+
253+
/**
254+
* Refreshes the sound buffer of a given `Sound`.
255+
*
256+
* @param sound The sound to refresh.
257+
*/
258+
public static function regenSound(sound:Null<Sound>):Void
259+
{
260+
if (sound != null)
261+
{
262+
@:privateAccess final curBuffer:Null<AudioBuffer> = sound.__buffer;
263+
if (curBuffer != null)
264+
{
265+
final newBuffer:AudioBuffer = new AudioBuffer();
266+
newBuffer.bitsPerSample = curBuffer.bitsPerSample;
267+
newBuffer.channels = curBuffer.channels;
268+
newBuffer.data = UInt8Array.fromBytes(curBuffer.data.toBytes());
269+
newBuffer.sampleRate = curBuffer.sampleRate;
270+
newBuffer.src = curBuffer.src;
271+
@:privateAccess sound.__buffer = newBuffer;
272+
}
273+
}
274+
}
275+
#end
276+
}

source/starcore/backend/util/DataUtil.hx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,21 @@ final class DataUtil
127127
{
128128
return (Reflect.hasField(object, field)) ? Reflect.field(object, field) : defaultValue;
129129
}
130+
131+
/**
132+
* Push an element to an array if it is not already present.
133+
*
134+
* @param input The array to push the new value to.
135+
* @param element The element to push.
136+
* @return Bool If the value was added or not.
137+
*/
138+
public static function pushUniqueElement<T>(input:Array<T>, element:T):Bool
139+
{
140+
if (input.contains(element))
141+
{
142+
return false;
143+
}
144+
input.push(element);
145+
return true;
146+
}
130147
}

0 commit comments

Comments
 (0)