teek-sdl2 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/teek_sdl2/extconf.rb +11 -1
- data/ext/teek_sdl2/sdl2audio.c +433 -0
- data/ext/teek_sdl2/sdl2bridge.c +17 -5
- data/ext/teek_sdl2/sdl2gamepad.c +54 -0
- data/ext/teek_sdl2/sdl2surface.c +839 -0
- data/ext/teek_sdl2/sdl2text.c +60 -2
- data/ext/teek_sdl2/teek_sdl2.c +3 -0
- data/ext/teek_sdl2/teek_sdl2.h +1 -0
- data/lib/teek/sdl2/audio_stream.rb +117 -0
- data/lib/teek/sdl2/font.rb +11 -1
- data/lib/teek/sdl2/gamepad.rb +153 -0
- data/lib/teek/sdl2/music.rb +47 -0
- data/lib/teek/sdl2/sound.rb +28 -0
- data/lib/teek/sdl2/texture.rb +29 -0
- data/lib/teek/sdl2/version.rb +1 -1
- data/lib/teek/sdl2/viewport.rb +4 -2
- data/lib/teek/sdl2.rb +127 -0
- data/teek-sdl2.gemspec +3 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 723f31c93fcc52c30a208ef8ab42ed0d7ade64ac45bfda38cddad93cb18877c4
|
|
4
|
+
data.tar.gz: 1611a57531d9731651dec9391d93148a98a772106cbbcd044899c184d7cf65c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6cdbf88dae339da4bbcd88181dc04292f7edc82c7f48a6256d29d3cc728803264eaba6a0e64984dd7031c90eaf545ff27c7a7857207c3dfcf0ef7cc95c8a9a64
|
|
7
|
+
data.tar.gz: 4f4176408fc19ecb389e02b478fac2028980965b3910ff93a326e75449f66a00ccbae6d600c3cf686a7a4c67595047a3a54534a6366110a8ca37bde033cf7b2f
|
data/ext/teek_sdl2/extconf.rb
CHANGED
|
@@ -128,6 +128,16 @@ unless pkg_config('SDL2_mixer') || have_library('SDL2_mixer', 'Mix_OpenAudio', '
|
|
|
128
128
|
MSG
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
# SDL2_gfx for drawing primitives (circles, ellipses, polygons, etc.)
|
|
132
|
+
unless pkg_config('SDL2_gfx') || have_library('SDL2_gfx', 'filledCircleRGBA', 'SDL2/SDL2_gfxPrimitives.h')
|
|
133
|
+
abort <<~MSG
|
|
134
|
+
SDL2_gfx not found. Install it:
|
|
135
|
+
macOS: brew install sdl2_gfx
|
|
136
|
+
Debian: sudo apt-get install libsdl2-gfx-dev
|
|
137
|
+
Windows: pacman -S #{msys2_pkg_prefix}-SDL2_gfx (MSYS2)
|
|
138
|
+
MSG
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
$srcs = ['teek_sdl2.c', 'sdl2surface.c', 'sdl2bridge.c', 'sdl2text.c', 'sdl2pixels.c', 'sdl2image.c', 'sdl2mixer.c', 'sdl2audio.c', 'sdl2gamepad.c']
|
|
132
142
|
|
|
133
143
|
create_makefile('teek_sdl2')
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#include "teek_sdl2.h"
|
|
2
|
+
|
|
3
|
+
/* ---------------------------------------------------------
|
|
4
|
+
* SDL2 AudioStream — push-based real-time PCM audio output
|
|
5
|
+
*
|
|
6
|
+
* Wraps SDL_OpenAudioDevice + SDL_QueueAudio for streaming
|
|
7
|
+
* raw PCM data (emulators, synthesizers, procedural audio).
|
|
8
|
+
* Independent of SDL2_mixer — uses a separate audio device.
|
|
9
|
+
* --------------------------------------------------------- */
|
|
10
|
+
|
|
11
|
+
static VALUE cAudioStream;
|
|
12
|
+
|
|
13
|
+
static void
|
|
14
|
+
ensure_sdl_audio_init(void)
|
|
15
|
+
{
|
|
16
|
+
if (!(SDL_WasInit(SDL_INIT_AUDIO) & SDL_INIT_AUDIO)) {
|
|
17
|
+
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
|
|
18
|
+
rb_raise(rb_eRuntimeError, "SDL_InitSubSystem(AUDIO) failed: %s",
|
|
19
|
+
SDL_GetError());
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* ---------------------------------------------------------
|
|
25
|
+
* AudioStream (wraps SDL_AudioDeviceID)
|
|
26
|
+
* --------------------------------------------------------- */
|
|
27
|
+
|
|
28
|
+
struct sdl2_audio_stream {
|
|
29
|
+
SDL_AudioDeviceID device_id;
|
|
30
|
+
int frequency;
|
|
31
|
+
int channels;
|
|
32
|
+
SDL_AudioFormat format;
|
|
33
|
+
int bytes_per_sample;
|
|
34
|
+
int destroyed;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
static void
|
|
38
|
+
audio_stream_free(void *ptr)
|
|
39
|
+
{
|
|
40
|
+
struct sdl2_audio_stream *a = ptr;
|
|
41
|
+
if (!a->destroyed && a->device_id > 0) {
|
|
42
|
+
SDL_CloseAudioDevice(a->device_id);
|
|
43
|
+
a->device_id = 0;
|
|
44
|
+
a->destroyed = 1;
|
|
45
|
+
}
|
|
46
|
+
xfree(a);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static size_t
|
|
50
|
+
audio_stream_memsize(const void *ptr)
|
|
51
|
+
{
|
|
52
|
+
return sizeof(struct sdl2_audio_stream);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static const rb_data_type_t audio_stream_type = {
|
|
56
|
+
.wrap_struct_name = "TeekSDL2::AudioStream",
|
|
57
|
+
.function = {
|
|
58
|
+
.dmark = NULL,
|
|
59
|
+
.dfree = audio_stream_free,
|
|
60
|
+
.dsize = audio_stream_memsize,
|
|
61
|
+
},
|
|
62
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
static VALUE
|
|
66
|
+
audio_stream_alloc(VALUE klass)
|
|
67
|
+
{
|
|
68
|
+
struct sdl2_audio_stream *a;
|
|
69
|
+
VALUE obj = TypedData_Make_Struct(klass, struct sdl2_audio_stream,
|
|
70
|
+
&audio_stream_type, a);
|
|
71
|
+
a->device_id = 0;
|
|
72
|
+
a->frequency = 0;
|
|
73
|
+
a->channels = 0;
|
|
74
|
+
a->format = 0;
|
|
75
|
+
a->bytes_per_sample = 0;
|
|
76
|
+
a->destroyed = 0;
|
|
77
|
+
return obj;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static struct sdl2_audio_stream *
|
|
81
|
+
get_audio_stream(VALUE self)
|
|
82
|
+
{
|
|
83
|
+
struct sdl2_audio_stream *a;
|
|
84
|
+
TypedData_Get_Struct(self, struct sdl2_audio_stream, &audio_stream_type, a);
|
|
85
|
+
if (a->destroyed || a->device_id == 0) {
|
|
86
|
+
rb_raise(rb_eRuntimeError, "audio stream has been destroyed");
|
|
87
|
+
}
|
|
88
|
+
return a;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Helper: map Ruby symbol to SDL_AudioFormat + bytes_per_sample */
|
|
92
|
+
static int
|
|
93
|
+
resolve_format(VALUE sym, SDL_AudioFormat *out_fmt, int *out_bps)
|
|
94
|
+
{
|
|
95
|
+
ID id;
|
|
96
|
+
if (NIL_P(sym) || sym == Qundef) {
|
|
97
|
+
*out_fmt = AUDIO_S16SYS;
|
|
98
|
+
*out_bps = 2;
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
if (!SYMBOL_P(sym)) return 0;
|
|
102
|
+
|
|
103
|
+
id = SYM2ID(sym);
|
|
104
|
+
if (id == rb_intern("s16")) {
|
|
105
|
+
*out_fmt = AUDIO_S16SYS;
|
|
106
|
+
*out_bps = 2;
|
|
107
|
+
} else if (id == rb_intern("f32")) {
|
|
108
|
+
*out_fmt = AUDIO_F32SYS;
|
|
109
|
+
*out_bps = 4;
|
|
110
|
+
} else if (id == rb_intern("u8")) {
|
|
111
|
+
*out_fmt = AUDIO_U8;
|
|
112
|
+
*out_bps = 1;
|
|
113
|
+
} else {
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
* AudioStream.new(frequency: 44100, format: :s16, channels: 2)
|
|
121
|
+
*
|
|
122
|
+
* Opens a push-based audio output device. Starts paused —
|
|
123
|
+
* call #resume after queuing initial data.
|
|
124
|
+
*/
|
|
125
|
+
static VALUE
|
|
126
|
+
audio_stream_initialize(int argc, VALUE *argv, VALUE self)
|
|
127
|
+
{
|
|
128
|
+
struct sdl2_audio_stream *a;
|
|
129
|
+
TypedData_Get_Struct(self, struct sdl2_audio_stream, &audio_stream_type, a);
|
|
130
|
+
|
|
131
|
+
ensure_sdl_audio_init();
|
|
132
|
+
|
|
133
|
+
/* Defaults */
|
|
134
|
+
int frequency = 44100;
|
|
135
|
+
int channels = 2;
|
|
136
|
+
SDL_AudioFormat format = AUDIO_S16SYS;
|
|
137
|
+
int bps = 2;
|
|
138
|
+
|
|
139
|
+
/* Parse keyword arguments */
|
|
140
|
+
VALUE kwargs;
|
|
141
|
+
rb_scan_args(argc, argv, ":", &kwargs);
|
|
142
|
+
|
|
143
|
+
if (!NIL_P(kwargs)) {
|
|
144
|
+
ID keys[3];
|
|
145
|
+
VALUE vals[3];
|
|
146
|
+
keys[0] = rb_intern("frequency");
|
|
147
|
+
keys[1] = rb_intern("format");
|
|
148
|
+
keys[2] = rb_intern("channels");
|
|
149
|
+
|
|
150
|
+
rb_get_kwargs(kwargs, keys, 0, 3, vals);
|
|
151
|
+
|
|
152
|
+
if (vals[0] != Qundef) {
|
|
153
|
+
frequency = NUM2INT(vals[0]);
|
|
154
|
+
if (frequency <= 0) {
|
|
155
|
+
rb_raise(rb_eArgError, "frequency must be positive");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (vals[1] != Qundef) {
|
|
160
|
+
if (!resolve_format(vals[1], &format, &bps)) {
|
|
161
|
+
rb_raise(rb_eArgError,
|
|
162
|
+
"format must be :s16, :f32, or :u8");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (vals[2] != Qundef) {
|
|
167
|
+
channels = NUM2INT(vals[2]);
|
|
168
|
+
if (channels < 1 || channels > 2) {
|
|
169
|
+
rb_raise(rb_eArgError, "channels must be 1 or 2");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Open audio device */
|
|
175
|
+
SDL_AudioSpec desired;
|
|
176
|
+
SDL_memset(&desired, 0, sizeof(desired));
|
|
177
|
+
desired.freq = frequency;
|
|
178
|
+
desired.format = format;
|
|
179
|
+
desired.channels = (Uint8)channels;
|
|
180
|
+
desired.samples = 2048;
|
|
181
|
+
|
|
182
|
+
SDL_AudioDeviceID dev = SDL_OpenAudioDevice(
|
|
183
|
+
NULL, 0, &desired, NULL, 0);
|
|
184
|
+
if (dev == 0) {
|
|
185
|
+
rb_raise(rb_eRuntimeError, "SDL_OpenAudioDevice failed: %s",
|
|
186
|
+
SDL_GetError());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
a->device_id = dev;
|
|
190
|
+
a->frequency = frequency;
|
|
191
|
+
a->channels = channels;
|
|
192
|
+
a->format = format;
|
|
193
|
+
a->bytes_per_sample = bps;
|
|
194
|
+
|
|
195
|
+
return self;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* stream.queue(data) -> nil
|
|
200
|
+
*
|
|
201
|
+
* Push raw PCM data to the audio device.
|
|
202
|
+
* +data+ must be a binary String matching the stream's format and channels.
|
|
203
|
+
*/
|
|
204
|
+
static VALUE
|
|
205
|
+
audio_stream_queue(VALUE self, VALUE data)
|
|
206
|
+
{
|
|
207
|
+
struct sdl2_audio_stream *a = get_audio_stream(self);
|
|
208
|
+
|
|
209
|
+
StringValue(data);
|
|
210
|
+
if (RSTRING_LEN(data) == 0) return Qnil;
|
|
211
|
+
|
|
212
|
+
if (SDL_QueueAudio(a->device_id, RSTRING_PTR(data),
|
|
213
|
+
(Uint32)RSTRING_LEN(data)) < 0) {
|
|
214
|
+
rb_raise(rb_eRuntimeError, "SDL_QueueAudio failed: %s",
|
|
215
|
+
SDL_GetError());
|
|
216
|
+
}
|
|
217
|
+
return Qnil;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/*
|
|
221
|
+
* stream.queued_bytes -> Integer
|
|
222
|
+
*
|
|
223
|
+
* Bytes of audio data currently queued for playback.
|
|
224
|
+
*/
|
|
225
|
+
static VALUE
|
|
226
|
+
audio_stream_queued_bytes(VALUE self)
|
|
227
|
+
{
|
|
228
|
+
struct sdl2_audio_stream *a = get_audio_stream(self);
|
|
229
|
+
Uint32 bytes = SDL_GetQueuedAudioSize(a->device_id);
|
|
230
|
+
return UINT2NUM(bytes);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/*
|
|
234
|
+
* stream.queued_samples -> Integer
|
|
235
|
+
*
|
|
236
|
+
* Number of audio samples (frames) currently queued.
|
|
237
|
+
* One sample = one value per channel.
|
|
238
|
+
*/
|
|
239
|
+
static VALUE
|
|
240
|
+
audio_stream_queued_samples(VALUE self)
|
|
241
|
+
{
|
|
242
|
+
struct sdl2_audio_stream *a = get_audio_stream(self);
|
|
243
|
+
Uint32 bytes = SDL_GetQueuedAudioSize(a->device_id);
|
|
244
|
+
int frame_size = a->bytes_per_sample * a->channels;
|
|
245
|
+
return UINT2NUM(bytes / (Uint32)frame_size);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/*
|
|
249
|
+
* stream.resume -> nil
|
|
250
|
+
*
|
|
251
|
+
* Start or unpause audio playback.
|
|
252
|
+
*/
|
|
253
|
+
static VALUE
|
|
254
|
+
audio_stream_resume(VALUE self)
|
|
255
|
+
{
|
|
256
|
+
struct sdl2_audio_stream *a = get_audio_stream(self);
|
|
257
|
+
SDL_PauseAudioDevice(a->device_id, 0);
|
|
258
|
+
return Qnil;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/*
|
|
262
|
+
* stream.pause -> nil
|
|
263
|
+
*
|
|
264
|
+
* Pause audio playback. Queued data is preserved.
|
|
265
|
+
*/
|
|
266
|
+
static VALUE
|
|
267
|
+
audio_stream_pause(VALUE self)
|
|
268
|
+
{
|
|
269
|
+
struct sdl2_audio_stream *a = get_audio_stream(self);
|
|
270
|
+
SDL_PauseAudioDevice(a->device_id, 1);
|
|
271
|
+
return Qnil;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/*
|
|
275
|
+
* stream.playing? -> Boolean
|
|
276
|
+
*
|
|
277
|
+
* Whether the audio device is currently playing (not paused).
|
|
278
|
+
*/
|
|
279
|
+
static VALUE
|
|
280
|
+
audio_stream_playing_p(VALUE self)
|
|
281
|
+
{
|
|
282
|
+
struct sdl2_audio_stream *a = get_audio_stream(self);
|
|
283
|
+
SDL_AudioStatus status = SDL_GetAudioDeviceStatus(a->device_id);
|
|
284
|
+
return status == SDL_AUDIO_PLAYING ? Qtrue : Qfalse;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/*
|
|
288
|
+
* stream.clear -> nil
|
|
289
|
+
*
|
|
290
|
+
* Flush all queued audio data.
|
|
291
|
+
*/
|
|
292
|
+
static VALUE
|
|
293
|
+
audio_stream_clear(VALUE self)
|
|
294
|
+
{
|
|
295
|
+
struct sdl2_audio_stream *a = get_audio_stream(self);
|
|
296
|
+
SDL_ClearQueuedAudio(a->device_id);
|
|
297
|
+
return Qnil;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/*
|
|
301
|
+
* stream.frequency -> Integer
|
|
302
|
+
*
|
|
303
|
+
* Sample rate in Hz.
|
|
304
|
+
*/
|
|
305
|
+
static VALUE
|
|
306
|
+
audio_stream_frequency(VALUE self)
|
|
307
|
+
{
|
|
308
|
+
struct sdl2_audio_stream *a = get_audio_stream(self);
|
|
309
|
+
return INT2NUM(a->frequency);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/*
|
|
313
|
+
* stream.channels -> Integer
|
|
314
|
+
*
|
|
315
|
+
* Number of audio channels (1 = mono, 2 = stereo).
|
|
316
|
+
*/
|
|
317
|
+
static VALUE
|
|
318
|
+
audio_stream_channels(VALUE self)
|
|
319
|
+
{
|
|
320
|
+
struct sdl2_audio_stream *a = get_audio_stream(self);
|
|
321
|
+
return INT2NUM(a->channels);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/*
|
|
325
|
+
* stream.format -> Symbol
|
|
326
|
+
*
|
|
327
|
+
* Audio sample format (:s16, :f32, or :u8).
|
|
328
|
+
*/
|
|
329
|
+
static VALUE
|
|
330
|
+
audio_stream_format(VALUE self)
|
|
331
|
+
{
|
|
332
|
+
struct sdl2_audio_stream *a = get_audio_stream(self);
|
|
333
|
+
if (a->format == AUDIO_S16SYS) return ID2SYM(rb_intern("s16"));
|
|
334
|
+
if (a->format == AUDIO_F32SYS) return ID2SYM(rb_intern("f32"));
|
|
335
|
+
if (a->format == AUDIO_U8) return ID2SYM(rb_intern("u8"));
|
|
336
|
+
return ID2SYM(rb_intern("unknown"));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/*
|
|
340
|
+
* stream.destroy -> nil
|
|
341
|
+
*
|
|
342
|
+
* Close the audio device. Further method calls will raise.
|
|
343
|
+
*/
|
|
344
|
+
static VALUE
|
|
345
|
+
audio_stream_destroy(VALUE self)
|
|
346
|
+
{
|
|
347
|
+
struct sdl2_audio_stream *a;
|
|
348
|
+
TypedData_Get_Struct(self, struct sdl2_audio_stream, &audio_stream_type, a);
|
|
349
|
+
if (!a->destroyed && a->device_id > 0) {
|
|
350
|
+
SDL_CloseAudioDevice(a->device_id);
|
|
351
|
+
a->device_id = 0;
|
|
352
|
+
a->destroyed = 1;
|
|
353
|
+
}
|
|
354
|
+
return Qnil;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/*
|
|
358
|
+
* stream.destroyed? -> Boolean
|
|
359
|
+
*
|
|
360
|
+
* Whether the audio stream has been destroyed.
|
|
361
|
+
*/
|
|
362
|
+
static VALUE
|
|
363
|
+
audio_stream_destroyed_p(VALUE self)
|
|
364
|
+
{
|
|
365
|
+
struct sdl2_audio_stream *a;
|
|
366
|
+
TypedData_Get_Struct(self, struct sdl2_audio_stream, &audio_stream_type, a);
|
|
367
|
+
return a->destroyed ? Qtrue : Qfalse;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/*
|
|
371
|
+
* AudioStream.available? -> Boolean
|
|
372
|
+
*
|
|
373
|
+
* Returns true if at least one audio output device is available.
|
|
374
|
+
* Use this to probe before opening a stream on headless/CI systems.
|
|
375
|
+
*/
|
|
376
|
+
static VALUE
|
|
377
|
+
audio_stream_available_p(VALUE klass)
|
|
378
|
+
{
|
|
379
|
+
ensure_sdl_audio_init();
|
|
380
|
+
return SDL_GetNumAudioDevices(0) > 0 ? Qtrue : Qfalse;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/*
|
|
384
|
+
* AudioStream.device_count -> Integer
|
|
385
|
+
*
|
|
386
|
+
* Number of audio output devices detected by SDL2.
|
|
387
|
+
*/
|
|
388
|
+
static VALUE
|
|
389
|
+
audio_stream_device_count(VALUE klass)
|
|
390
|
+
{
|
|
391
|
+
ensure_sdl_audio_init();
|
|
392
|
+
return INT2NUM(SDL_GetNumAudioDevices(0));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/*
|
|
396
|
+
* AudioStream.driver_name -> String or nil
|
|
397
|
+
*
|
|
398
|
+
* Name of the current SDL2 audio driver (e.g. "wasapi", "dummy"),
|
|
399
|
+
* or nil if audio is not initialized.
|
|
400
|
+
*/
|
|
401
|
+
static VALUE
|
|
402
|
+
audio_stream_driver_name(VALUE klass)
|
|
403
|
+
{
|
|
404
|
+
const char *name = SDL_GetCurrentAudioDriver();
|
|
405
|
+
return name ? rb_str_new_cstr(name) : Qnil;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* --------------------------------------------------------- */
|
|
409
|
+
|
|
410
|
+
void
|
|
411
|
+
Init_sdl2audio(VALUE mTeekSDL2)
|
|
412
|
+
{
|
|
413
|
+
cAudioStream = rb_define_class_under(mTeekSDL2, "AudioStream", rb_cObject);
|
|
414
|
+
rb_define_alloc_func(cAudioStream, audio_stream_alloc);
|
|
415
|
+
|
|
416
|
+
rb_define_method(cAudioStream, "initialize", audio_stream_initialize, -1);
|
|
417
|
+
rb_define_method(cAudioStream, "queue", audio_stream_queue, 1);
|
|
418
|
+
rb_define_method(cAudioStream, "queued_bytes", audio_stream_queued_bytes, 0);
|
|
419
|
+
rb_define_method(cAudioStream, "queued_samples", audio_stream_queued_samples, 0);
|
|
420
|
+
rb_define_method(cAudioStream, "resume", audio_stream_resume, 0);
|
|
421
|
+
rb_define_method(cAudioStream, "pause", audio_stream_pause, 0);
|
|
422
|
+
rb_define_method(cAudioStream, "playing?", audio_stream_playing_p, 0);
|
|
423
|
+
rb_define_method(cAudioStream, "clear", audio_stream_clear, 0);
|
|
424
|
+
rb_define_method(cAudioStream, "frequency", audio_stream_frequency, 0);
|
|
425
|
+
rb_define_method(cAudioStream, "channels", audio_stream_channels, 0);
|
|
426
|
+
rb_define_method(cAudioStream, "format", audio_stream_format, 0);
|
|
427
|
+
rb_define_method(cAudioStream, "destroy", audio_stream_destroy, 0);
|
|
428
|
+
rb_define_method(cAudioStream, "destroyed?", audio_stream_destroyed_p, 0);
|
|
429
|
+
|
|
430
|
+
rb_define_singleton_method(cAudioStream, "available?", audio_stream_available_p, 0);
|
|
431
|
+
rb_define_singleton_method(cAudioStream, "device_count", audio_stream_device_count, 0);
|
|
432
|
+
rb_define_singleton_method(cAudioStream, "driver_name", audio_stream_driver_name, 0);
|
|
433
|
+
}
|
data/ext/teek_sdl2/sdl2bridge.c
CHANGED
|
@@ -9,18 +9,28 @@
|
|
|
9
9
|
* --------------------------------------------------------- */
|
|
10
10
|
|
|
11
11
|
/*
|
|
12
|
-
* Teek::SDL2.create_renderer_from_handle(native_handle) -> Renderer
|
|
12
|
+
* Teek::SDL2.create_renderer_from_handle(native_handle, vsync=true) -> Renderer
|
|
13
13
|
*
|
|
14
14
|
* Creates an SDL2 window embedded in the native window identified by
|
|
15
15
|
* native_handle (from Tk's 'winfo id'), then creates a GPU-accelerated
|
|
16
16
|
* renderer on it.
|
|
17
17
|
*
|
|
18
|
+
* When +vsync+ is true (the default), SDL_RENDERER_PRESENTVSYNC is set
|
|
19
|
+
* so SDL_RenderPresent blocks until the next display refresh. Pass false
|
|
20
|
+
* for applications that manage their own frame pacing (e.g. emulators
|
|
21
|
+
* syncing to an audio clock).
|
|
22
|
+
*
|
|
18
23
|
* The Ruby Viewport class is responsible for getting the handle and
|
|
19
24
|
* calling this. This C function just does the SDL2 work.
|
|
20
25
|
*/
|
|
21
26
|
static VALUE
|
|
22
|
-
bridge_create_renderer_from_handle(VALUE
|
|
27
|
+
bridge_create_renderer_from_handle(int argc, VALUE *argv, VALUE self)
|
|
23
28
|
{
|
|
29
|
+
VALUE handle_val, vsync_val;
|
|
30
|
+
rb_scan_args(argc, argv, "11", &handle_val, &vsync_val);
|
|
31
|
+
|
|
32
|
+
int use_vsync = NIL_P(vsync_val) ? 1 : RTEST(vsync_val);
|
|
33
|
+
|
|
24
34
|
ensure_sdl2_init();
|
|
25
35
|
|
|
26
36
|
/*
|
|
@@ -40,8 +50,10 @@ bridge_create_renderer_from_handle(VALUE self, VALUE handle_val)
|
|
|
40
50
|
rb_raise(rb_eRuntimeError, "SDL_CreateWindowFrom failed: %s", SDL_GetError());
|
|
41
51
|
}
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
Uint32 flags = SDL_RENDERER_ACCELERATED;
|
|
54
|
+
if (use_vsync) flags |= SDL_RENDERER_PRESENTVSYNC;
|
|
55
|
+
|
|
56
|
+
SDL_Renderer *sdl_ren = SDL_CreateRenderer(window, -1, flags);
|
|
45
57
|
if (!sdl_ren) {
|
|
46
58
|
/* Fall back to software if GPU not available */
|
|
47
59
|
sdl_ren = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
|
|
@@ -135,7 +147,7 @@ void
|
|
|
135
147
|
Init_sdl2bridge(VALUE mTeekSDL2)
|
|
136
148
|
{
|
|
137
149
|
rb_define_module_function(mTeekSDL2, "create_renderer_from_handle",
|
|
138
|
-
bridge_create_renderer_from_handle, 1);
|
|
150
|
+
bridge_create_renderer_from_handle, -1);
|
|
139
151
|
rb_define_module_function(mTeekSDL2, "poll_events", bridge_poll_events, 0);
|
|
140
152
|
rb_define_module_function(mTeekSDL2, "_event_check_fn_ptr", bridge_event_check_fn_ptr, 0);
|
|
141
153
|
rb_define_module_function(mTeekSDL2, "sdl_quit", bridge_sdl_quit, 0);
|
data/ext/teek_sdl2/sdl2gamepad.c
CHANGED
|
@@ -33,6 +33,15 @@ ensure_gc_init(void)
|
|
|
33
33
|
if (gc_subsystem_initialized) return;
|
|
34
34
|
|
|
35
35
|
if (!(SDL_WasInit(SDL_INIT_GAMECONTROLLER) & SDL_INIT_GAMECONTROLLER)) {
|
|
36
|
+
/*
|
|
37
|
+
* By default SDL drops joystick/gamecontroller events when its
|
|
38
|
+
* window doesn't have focus. Since we embed SDL inside Tk, other
|
|
39
|
+
* Tk windows (e.g. Settings) can take focus while the user still
|
|
40
|
+
* needs gamepad input. Allow events regardless of focus.
|
|
41
|
+
* https://wiki.libsdl.org/SDL2/SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS
|
|
42
|
+
*/
|
|
43
|
+
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
|
44
|
+
|
|
36
45
|
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) {
|
|
37
46
|
rb_raise(rb_eRuntimeError,
|
|
38
47
|
"SDL_InitSubSystem(GAMECONTROLLER) failed: %s",
|
|
@@ -340,6 +349,24 @@ gamepad_name(VALUE self)
|
|
|
340
349
|
return rb_str_new_cstr(name);
|
|
341
350
|
}
|
|
342
351
|
|
|
352
|
+
/*
|
|
353
|
+
* Gamepad#guid -> String
|
|
354
|
+
*
|
|
355
|
+
* Returns the GUID string that identifies this controller's model/type.
|
|
356
|
+
* Same model controllers share the same GUID. Useful as a config key
|
|
357
|
+
* for persisting per-controller settings.
|
|
358
|
+
*/
|
|
359
|
+
static VALUE
|
|
360
|
+
gamepad_guid(VALUE self)
|
|
361
|
+
{
|
|
362
|
+
struct sdl2_gamepad *gp = get_gamepad(self);
|
|
363
|
+
SDL_Joystick *joy = SDL_GameControllerGetJoystick(gp->controller);
|
|
364
|
+
SDL_JoystickGUID guid = SDL_JoystickGetGUID(joy);
|
|
365
|
+
char buf[33];
|
|
366
|
+
SDL_JoystickGetGUIDString(guid, buf, sizeof(buf));
|
|
367
|
+
return rb_str_new_cstr(buf);
|
|
368
|
+
}
|
|
369
|
+
|
|
343
370
|
/*
|
|
344
371
|
* Gamepad#attached? -> true or false
|
|
345
372
|
*
|
|
@@ -544,6 +571,30 @@ gamepad_s_poll_events(VALUE klass)
|
|
|
544
571
|
return INT2NUM(count);
|
|
545
572
|
}
|
|
546
573
|
|
|
574
|
+
/*
|
|
575
|
+
* Gamepad.update_state -> nil
|
|
576
|
+
*
|
|
577
|
+
* Refreshes the internal state of all open game controllers
|
|
578
|
+
* WITHOUT pumping the platform event loop.
|
|
579
|
+
*
|
|
580
|
+
* SDL_PollEvent → SDL_PumpEvents → pumps the Cocoa run loop on macOS,
|
|
581
|
+
* which steals events from other UI toolkits (e.g. Tk).
|
|
582
|
+
* SDL_GameControllerUpdate → SDL_JoystickUpdate only (no event pump).
|
|
583
|
+
* See: https://github.com/libsdl-org/SDL/blob/SDL2/src/joystick/SDL_gamecontroller.c
|
|
584
|
+
*
|
|
585
|
+
* After calling this, Gamepad#button? and Gamepad#axis return
|
|
586
|
+
* updated values. Use this instead of poll_events when you only
|
|
587
|
+
* need fresh controller state and don't need event callbacks.
|
|
588
|
+
*/
|
|
589
|
+
static VALUE
|
|
590
|
+
gamepad_s_update_state(VALUE klass)
|
|
591
|
+
{
|
|
592
|
+
if (gc_subsystem_initialized) {
|
|
593
|
+
SDL_GameControllerUpdate();
|
|
594
|
+
}
|
|
595
|
+
return Qnil;
|
|
596
|
+
}
|
|
597
|
+
|
|
547
598
|
/* ---------------------------------------------------------
|
|
548
599
|
* Callback registration
|
|
549
600
|
* --------------------------------------------------------- */
|
|
@@ -838,6 +889,8 @@ Init_sdl2gamepad(VALUE mTeekSDL2)
|
|
|
838
889
|
rb_define_singleton_method(cGamepad, "all", gamepad_s_all, 0);
|
|
839
890
|
rb_define_singleton_method(cGamepad, "poll_events",
|
|
840
891
|
gamepad_s_poll_events, 0);
|
|
892
|
+
rb_define_singleton_method(cGamepad, "update_state",
|
|
893
|
+
gamepad_s_update_state, 0);
|
|
841
894
|
rb_define_singleton_method(cGamepad, "buttons", gamepad_s_buttons, 0);
|
|
842
895
|
rb_define_singleton_method(cGamepad, "axes", gamepad_s_axes, 0);
|
|
843
896
|
|
|
@@ -857,6 +910,7 @@ Init_sdl2gamepad(VALUE mTeekSDL2)
|
|
|
857
910
|
|
|
858
911
|
/* Instance methods */
|
|
859
912
|
rb_define_method(cGamepad, "name", gamepad_name, 0);
|
|
913
|
+
rb_define_method(cGamepad, "guid", gamepad_guid, 0);
|
|
860
914
|
rb_define_method(cGamepad, "attached?", gamepad_attached_p, 0);
|
|
861
915
|
rb_define_method(cGamepad, "button?", gamepad_button_p, 1);
|
|
862
916
|
rb_define_method(cGamepad, "axis", gamepad_axis, 1);
|