Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_allegro_4.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 */
17
25
26/* al_lock_sample_id() is part of Allegro 5's unstable API */
27#ifndef ALLEGRO_UNSTABLE
28#define ALLEGRO_UNSTABLE
29#endif
30
31#include <stdarg.h>
32#include <stddef.h>
33#include <stdlib.h>
34#include <string.h>
35#include <strings.h>
36#include <math.h>
37
38#include <allegro5/allegro.h>
39#include <allegro5/allegro_primitives.h>
40#include <allegro5/allegro_image.h>
41#include <allegro5/allegro_font.h>
42#include <allegro5/allegro_ttf.h>
43#include <allegro5/allegro_audio.h>
44#include <allegro5/allegro_acodec.h>
45
46#include "nilorea/n_common.h"
47#include "nilorea/n_log.h"
48#include "nilorea/n_module.h"
49#include "nilorea/n_allegro_4.h"
50
51#ifndef __windows__
52#include <dirent.h>
53#include <unistd.h>
54#endif
55
56/* The KEY_* values of the header are Allegro 5's key codes. */
57_Static_assert(KEY_A == ALLEGRO_KEY_A, "KEY_A");
58_Static_assert(KEY_0 == ALLEGRO_KEY_0, "KEY_0");
59_Static_assert(KEY_F1 == ALLEGRO_KEY_F1, "KEY_F1");
60_Static_assert(KEY_ESC == ALLEGRO_KEY_ESCAPE, "KEY_ESC");
61_Static_assert(KEY_BACKSPACE == ALLEGRO_KEY_BACKSPACE, "KEY_BACKSPACE");
62_Static_assert(KEY_TAB == ALLEGRO_KEY_TAB, "KEY_TAB");
63_Static_assert(KEY_ENTER == ALLEGRO_KEY_ENTER, "KEY_ENTER");
64_Static_assert(KEY_SPACE == ALLEGRO_KEY_SPACE, "KEY_SPACE");
65_Static_assert(KEY_INSERT == ALLEGRO_KEY_INSERT, "KEY_INSERT");
66_Static_assert(KEY_DEL == ALLEGRO_KEY_DELETE, "KEY_DEL");
67_Static_assert(KEY_HOME == ALLEGRO_KEY_HOME, "KEY_HOME");
68_Static_assert(KEY_END == ALLEGRO_KEY_END, "KEY_END");
69_Static_assert(KEY_PGUP == ALLEGRO_KEY_PGUP, "KEY_PGUP");
70_Static_assert(KEY_PGDN == ALLEGRO_KEY_PGDN, "KEY_PGDN");
71_Static_assert(KEY_LEFT == ALLEGRO_KEY_LEFT, "KEY_LEFT");
72_Static_assert(KEY_RIGHT == ALLEGRO_KEY_RIGHT, "KEY_RIGHT");
73_Static_assert(KEY_UP == ALLEGRO_KEY_UP, "KEY_UP");
74_Static_assert(KEY_DOWN == ALLEGRO_KEY_DOWN, "KEY_DOWN");
75_Static_assert(KEY_ENTER_PAD == ALLEGRO_KEY_PAD_ENTER, "KEY_ENTER_PAD");
76_Static_assert(KEY_LSHIFT == ALLEGRO_KEY_LSHIFT, "KEY_LSHIFT");
77_Static_assert(KEY_ALTGR == ALLEGRO_KEY_ALTGR, "KEY_ALTGR");
78_Static_assert(KEY_MAX == ALLEGRO_KEY_MAX, "KEY_MAX");
79
80/* Mask colour of 32 bit Allegro 4 bitmaps. */
81#define A4_MASK_COLOR 0x00FF00FF
82
83/* Private state of a BITMAP. */
84typedef struct A4_BMP {
85 /* the pixels */
86 ALLEGRO_BITMAP* bmp;
87 /* copy with the mask colour turned transparent, for masked draws */
88 ALLEGRO_BITMAP* masked;
89 /* TRUE when bmp changed since masked was built */
91 /* non NULL while bmp is locked for direct pixel access */
92 ALLEGRO_LOCKED_REGION* lock;
93 /* TRUE for the screen bitmap */
95} A4_BMP;
96
97/* Private state of a FONT. */
98struct FONT {
99 ALLEGRO_FONT* f;
100};
101
102/* Private state of a SAMPLE: the instances it was last played on. */
103#define A4_SAMPLE_IDS 16
104struct SAMPLE {
105 ALLEGRO_SAMPLE* spl;
106 ALLEGRO_SAMPLE_ID ids[A4_SAMPLE_IDS];
108 int next;
109};
110
111/* Private state of a MIDI: the file to hand to fluidsynth. */
112struct MIDI {
113 char* path;
114};
115
116#define A4P(b) ((A4_BMP*)((b)->priv))
117
120FONT* font = NULL;
121volatile char key[KEY_MAX];
122volatile int key_shifts = 0;
123volatile int mouse_x = 0;
124volatile int mouse_y = 0;
125volatile int mouse_z = 0;
126volatile int mouse_b = 0;
127
128static int a4_inited = 0;
129static ALLEGRO_DISPLAY* a4_display = NULL;
130static int a4_gl_mode = 0;
131static int a4_screen_dirty = 0;
132static int a4_cursor_shown = 0;
133static void (*a4_close_cb)(void) = NULL;
134/* screen -> display mapping (letterboxed uniform scale) */
135static float a4_scale = 1.0f;
136static float a4_off_x = 0.0f;
137static float a4_off_y = 0.0f;
138/* drawing state */
140static int a4_alpha_blender = 0;
141static int a4_trans_alpha = 255;
142
143/* input thread */
144static ALLEGRO_EVENT_QUEUE* a4_input_queue = NULL;
145static ALLEGRO_THREAD* a4_input_thread = NULL;
146static ALLEGRO_MUTEX* a4_input_mutex = NULL;
147#define A4_KEYBUF_SIZE 64
149static int a4_keybuf_start = 0;
150static int a4_keybuf_count = 0;
151static int a4_mickey_x = 0;
152static int a4_mickey_y = 0;
153static volatile int a4_close_requested = 0;
154/* the last left click, latched by the input thread so that the layer's own
155 * menus and boxes never miss a click shorter than a frame */
156static int a4_click_pending = 0;
157static int a4_click_x = 0;
158static int a4_click_y = 0;
159
160/* timer thread */
161#define A4_MAX_TIMERS 16
162typedef struct A4_TIMER {
163 void (*proc)(void);
164 double period;
165 double next;
166} A4_TIMER;
168static ALLEGRO_THREAD* a4_timer_thread = NULL;
169static ALLEGRO_MUTEX* a4_timer_mutex = NULL;
170
171/* sound */
172static int a4_sound_ok = 0;
173static int a4_digi_volume = 255;
174static int a4_midi_volume = 255;
175
176static void _a4_midi_shutdown(void);
177static void _a4_present(void);
178static void _a4_flush(void);
179
180/* File names. */
181
182/* Return a malloc'd copy of path with '\' turned into '/' and, when the
183 * name does not exist as written, each component matched case
184 * insensitively against the directory content. Windows file systems are
185 * case insensitive already, there only the separators are fixed. */
186static char* _a4_resolve(const char* path) {
187 if (!path) return NULL;
188 char* p = strdup(path);
189 if (!p) return NULL;
190#ifndef __windows__
191 for (char* c = p; *c; c++)
192 if (*c == '\\') *c = '/';
193 if (access(p, F_OK) == 0) return p;
194
195 size_t len = strlen(p);
196 char* out = malloc(len + 3);
197 if (!out) return p;
198 out[0] = '\0';
199 char* save = NULL;
200 char* work = strdup(p);
201 if (!work) {
202 free(out);
203 return p;
204 }
205 int absolute = (p[0] == '/');
206 if (absolute) strcpy(out, "/");
207 for (char* comp = strtok_r(work, "/", &save); comp; comp = strtok_r(NULL, "/", &save)) {
208 const char* dir = out[0] ? out : ".";
209 char candidate[4096];
210 snprintf(candidate, sizeof(candidate), "%s%s%s", out, (out[0] && out[strlen(out) - 1] != '/') ? "/" : "", comp);
211 const char* chosen = comp;
212 char found[256] = "";
213 if (access(candidate, F_OK) != 0) {
214 DIR* d = opendir(dir);
215 if (d) {
216 struct dirent* ent = NULL;
217 while ((ent = readdir(d)) != NULL) {
218 if (strcasecmp(ent->d_name, comp) == 0) {
219 snprintf(found, sizeof(found), "%s", ent->d_name);
220 break;
221 }
222 }
223 closedir(d);
224 }
225 if (found[0]) chosen = found;
226 }
227 size_t need = strlen(out) + strlen(chosen) + 2;
228 if (need > len + 3) {
229 char* bigger = realloc(out, need + 16);
230 if (!bigger) break;
231 out = bigger;
232 len = need + 13;
233 }
234 if (out[0] && out[strlen(out) - 1] != '/') strcat(out, "/");
235 strcat(out, chosen);
236 }
237 free(work);
238 free(p);
239 return out;
240#else
241 for (char* c = p; *c; c++)
242 if (*c == '/') *c = '\\';
243 return p;
244#endif
245}
246
247/* strndup() is not available everywhere (MinGW). */
248static char* _a4_strndup(const char* s, size_t n) {
249 char* r = malloc(n + 1);
250 if (!r) return NULL;
251 memcpy(r, s, n);
252 r[n] = '\0';
253 return r;
254}
255
256/* Colours. */
257
258uint32_t _a4_pixel_from_color(int color) {
259 uint32_t c = (uint32_t)color;
260 uint32_t a = c >> 24;
261 if (a == 0) a = 255;
262 return (a << 24) | (c & 0x00FFFFFFu);
263}
264
265static int _a4_color_from_pixel(uint32_t p) {
266 if ((p >> 24) == 255) return (int)(p & 0x00FFFFFFu);
267 return (int)p;
268}
269
270static ALLEGRO_COLOR _a4_rgba(int color, int alpha) {
271 uint32_t c = (uint32_t)color;
272 return al_map_rgba((unsigned char)((c >> 16) & 0xFF), (unsigned char)((c >> 8) & 0xFF), (unsigned char)(c & 0xFF), (unsigned char)alpha);
273}
274
275/* How a primitive is blended onto its target. */
276#define A4_BLEND_COPY 0
277#define A4_BLEND_ALPHA 1
278#define A4_BLEND_FADE 2
279
280/* The colour a primitive is drawn with, and in *blend how to blend it.
281 *
282 * Allegro 4's 32 bit blenders work in integers: an opacity n mixes with
283 * (n + 1) / 256 and the result is truncated, so drawing black at a tiny
284 * opacity over the whole buffer every frame (the classic fade / motion
285 * blur trick) always takes at least one step off each channel and trails
286 * fade out. GPU blending rounds to nearest instead, and 100 * (1 - 1/255)
287 * rounds back to 100: dark trails would never go away. So a translucent
288 * black is drawn with A4_BLEND_FADE, dest * (1 - n / 256) - 1 / 255,
289 * which lands on Allegro 4's dest - ceil(dest * (n + 1) / 256) (100 -> 99,
290 * 60 -> 59, 1 -> 0 for n = 1). The bias is a whole step because vertex
291 * colours are quantized to 8 bits, where half a step would round to 0. */
292static ALLEGRO_COLOR _a4_prim(int color, int* blend) {
294 int n = a4_alpha_blender ? (int)(((uint32_t)color) >> 24) : a4_trans_alpha;
295 float a = n > 0 ? (float)(n + 1) / 256.0f : 0.0f;
296 if (a > 1.0f) a = 1.0f;
297 if ((((uint32_t)color) & 0x00FFFFFFu) == 0 && n > 0 && n < 255) {
298 *blend = A4_BLEND_FADE;
299 float bias = 1.0f / 255.0f;
300 return al_map_rgba_f(bias, bias, bias, (float)n / 256.0f);
301 }
302 *blend = A4_BLEND_ALPHA;
303 uint32_t c = (uint32_t)color;
304 return al_map_rgba_f((float)((c >> 16) & 0xFF) / 255.0f, (float)((c >> 8) & 0xFF) / 255.0f, (float)(c & 0xFF) / 255.0f, a);
305 }
306 *blend = A4_BLEND_COPY;
307 return _a4_rgba(color, (int)(_a4_pixel_from_color(color) >> 24));
308}
309
310int makecol(int r, int g, int b) {
311 return ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
312}
313
314int makecol32(int r, int g, int b) {
315 return makecol(r, g, b);
316}
317
318int makeacol(int r, int g, int b, int a) {
319 return (int)(((uint32_t)(a & 0xFF) << 24) | (uint32_t)makecol(r, g, b));
320}
321
322int makeacol32(int r, int g, int b, int a) {
323 return makeacol(r, g, b, a);
324}
325
326int getr(int c) {
327 return (int)((((uint32_t)c) >> 16) & 0xFF);
328}
329
330int getg(int c) {
331 return (int)((((uint32_t)c) >> 8) & 0xFF);
332}
333
334int getb(int c) {
335 return (int)(((uint32_t)c) & 0xFF);
336}
337
338int geta(int c) {
339 return (int)(_a4_pixel_from_color(c) >> 24);
340}
341
342/* Bitmap state helpers. */
343
344static void _a4_unlock(BITMAP* b) {
345 if (!b || !b->priv) return;
346 A4_BMP* p = A4P(b);
347 if (p->lock) {
348 al_unlock_bitmap(p->bmp);
349 p->lock = NULL;
350 }
351}
352
353static ALLEGRO_LOCKED_REGION* _a4_lock(BITMAP* b) {
354 A4_BMP* p = A4P(b);
355 if (!p->lock) {
356 _a4_flush();
357 p->lock = al_lock_bitmap(p->bmp, ALLEGRO_PIXEL_FORMAT_ARGB_8888, ALLEGRO_LOCK_READWRITE);
358 }
359 return p->lock;
360}
361
362/* Make b the drawing target. The caller fetched its sources first. */
363static int _a4_target(BITMAP* b) {
364 if (!b || !b->priv) return FALSE;
365 A4_BMP* p = A4P(b);
366 _a4_flush();
367 _a4_unlock(b);
368 if (al_get_target_bitmap() != p->bmp) al_set_target_bitmap(p->bmp);
369 p->masked_dirty = 1;
370 if (p->is_screen) a4_screen_dirty = 1;
371 return TRUE;
372}
373
374/* After drawing on b: Allegro 4 showed screen drawing immediately. */
375static void _a4_done(const BITMAP* b) {
376 if (b && b->priv && A4P(b)->is_screen && !a4_gl_mode) _a4_present();
377}
378
379/* The Allegro 5 bitmap to draw b from, masked or not. */
380static ALLEGRO_BITMAP* _a4_source(BITMAP* b, int masked) {
381 if (!b || !b->priv) return NULL;
382 A4_BMP* p = A4P(b);
383 _a4_flush();
384 _a4_unlock(b);
385 if (!masked) return p->bmp;
386 if (!p->masked || p->masked_dirty) {
387 if (!p->masked) {
388 p->masked = al_create_bitmap(b->w, b->h);
389 if (!p->masked) return p->bmp;
390 }
391 ALLEGRO_STATE state;
392 al_store_state(&state, ALLEGRO_STATE_TARGET_BITMAP | ALLEGRO_STATE_BLENDER);
393 al_set_target_bitmap(p->masked);
394 al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
395 al_draw_bitmap(p->bmp, 0, 0, 0);
396 al_convert_mask_to_alpha(p->masked, al_map_rgb(255, 0, 255));
397 al_restore_state(&state);
398 p->masked_dirty = 0;
399 }
400 return p->masked;
401}
402
403static BITMAP* _a4_wrap(ALLEGRO_BITMAP* abmp) {
404 if (!abmp) return NULL;
405 BITMAP* b = NULL;
406 A4_BMP* p = NULL;
407 Malloc(b, BITMAP, 1);
408 Malloc(p, A4_BMP, 1);
409 if (!b || !p) {
410 Free(b);
411 Free(p);
412 al_destroy_bitmap(abmp);
413 return NULL;
414 }
415 p->bmp = abmp;
416 p->masked_dirty = 1;
417 b->w = al_get_bitmap_width(abmp);
418 b->h = al_get_bitmap_height(abmp);
419 b->line = NULL;
420 b->priv = p;
421 return b;
422}
423
424/* Blender for masked and alpha draws of non premultiplied pixels. */
425static void _a4_blend_alpha(void) {
426 al_set_separate_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
427}
428
429/* Blender for plain copies. */
430static void _a4_blend_copy(void) {
431 al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
432}
433
434static void _a4_set_blend(int blend) {
435 if (blend == A4_BLEND_FADE)
436 /* dest * (1 - src alpha) - src, alpha channel left alone */
437 al_set_separate_blender(ALLEGRO_DEST_MINUS_SRC, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA, ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ONE);
438 else if (blend == A4_BLEND_ALPHA)
440 else
442}
443
444/* The colour of a primitive drawn right away, blender set accordingly. */
445static ALLEGRO_COLOR _a4_prim_color(int color) {
446 int blend = A4_BLEND_COPY;
447 ALLEGRO_COLOR c = _a4_prim(color, &blend);
448 _a4_set_blend(blend);
449 return c;
450}
451
452/* Primitive batching. Allegro 4 programs draw thousands of lines and
453 * rectangles per frame, one call each, which Allegro 4 rasterized in
454 * software. Issuing one GPU draw per call is far too slow, so line(),
455 * rectfill(), rect(), triangle() and translucent putpixel() append
456 * triangles here, drawn in one al_draw_prim() call. The batch is flushed
457 * before anything else is drawn, before a bitmap is used as a source or
458 * locked, and before the screen is shown, so drawing order is unchanged. */
459#define A4_BATCH_MAX 6144
460static ALLEGRO_VERTEX a4_batch[A4_BATCH_MAX];
461static int a4_batch_n = 0;
462static BITMAP* a4_batch_target = NULL;
464
465static void _a4_flush(void) {
466 if (a4_batch_n > 0 && a4_batch_target && a4_batch_target->priv) {
467 ALLEGRO_BITMAP* t = A4P(a4_batch_target)->bmp;
468 if (al_get_target_bitmap() != t) al_set_target_bitmap(t);
470 al_draw_prim(a4_batch, NULL, NULL, 0, a4_batch_n, ALLEGRO_PRIM_TRIANGLE_LIST);
471 }
472 a4_batch_n = 0;
473 a4_batch_target = NULL;
474}
475
476/* Room for n vertices drawn on b with the given blend mode. */
477static ALLEGRO_VERTEX* _a4_reserve(BITMAP* b, int blend, int n) {
478 if (a4_batch_target != b || a4_batch_blend != blend || a4_batch_n + n > A4_BATCH_MAX) {
479 _a4_flush();
480 _a4_unlock(b);
481 a4_batch_target = b;
482 a4_batch_blend = blend;
483 }
484 A4_BMP* p = A4P(b);
485 p->masked_dirty = 1;
486 if (p->is_screen) a4_screen_dirty = 1;
487 ALLEGRO_VERTEX* v = &a4_batch[a4_batch_n];
488 a4_batch_n += n;
489 return v;
490}
491
492static void _a4_vertex(ALLEGRO_VERTEX* v, float x, float y, ALLEGRO_COLOR c) {
493 v->x = x;
494 v->y = y;
495 v->z = 0.0f;
496 v->u = 0.0f;
497 v->v = 0.0f;
498 v->color = c;
499}
500
501/* A quad given by its four corners in order. */
502static void _a4_quad(BITMAP* b, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, int color) {
503 int blend = A4_BLEND_COPY;
504 ALLEGRO_COLOR c = _a4_prim(color, &blend);
505 ALLEGRO_VERTEX* v = _a4_reserve(b, blend, 6);
506 _a4_vertex(&v[0], x1, y1, c);
507 _a4_vertex(&v[1], x2, y2, c);
508 _a4_vertex(&v[2], x3, y3, c);
509 _a4_vertex(&v[3], x1, y1, c);
510 _a4_vertex(&v[4], x3, y3, c);
511 _a4_vertex(&v[5], x4, y4, c);
512}
513
514/* An axis aligned box covering pixels [l, r) x [t, bt). */
515static void _a4_box(BITMAP* b, float l, float t, float r, float bt, int color) {
516 _a4_quad(b, l, t, r, t, r, bt, l, bt, color);
517}
518
519/* System. */
520
521static void _a4_input_handle(const ALLEGRO_EVENT* ev) {
522 switch (ev->type) {
523 case ALLEGRO_EVENT_KEY_DOWN:
524 if (ev->keyboard.keycode > 0 && ev->keyboard.keycode < KEY_MAX) key[ev->keyboard.keycode] = 1;
525 break;
526 case ALLEGRO_EVENT_KEY_UP:
527 if (ev->keyboard.keycode > 0 && ev->keyboard.keycode < KEY_MAX) key[ev->keyboard.keycode] = 0;
528 break;
529 case ALLEGRO_EVENT_KEY_CHAR: {
530 int ascii = (ev->keyboard.unichar > 0 && ev->keyboard.unichar < 256) ? ev->keyboard.unichar : 0;
531 int k = (ev->keyboard.keycode << 8) | ascii;
532 al_lock_mutex(a4_input_mutex);
536 }
537 al_unlock_mutex(a4_input_mutex);
538 break;
539 }
540 case ALLEGRO_EVENT_MOUSE_AXES:
541 case ALLEGRO_EVENT_MOUSE_WARPED:
542 mouse_x = (int)(((float)ev->mouse.x - a4_off_x) / a4_scale);
543 mouse_y = (int)(((float)ev->mouse.y - a4_off_y) / a4_scale);
544 mouse_z = ev->mouse.z;
545 al_lock_mutex(a4_input_mutex);
546 a4_mickey_x += (int)((float)ev->mouse.dx / a4_scale);
547 a4_mickey_y += (int)((float)ev->mouse.dy / a4_scale);
548 al_unlock_mutex(a4_input_mutex);
549 break;
550 case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
551 if (ev->mouse.button >= 1 && ev->mouse.button <= 3) mouse_b |= (1 << (ev->mouse.button - 1));
552 if (ev->mouse.button == 1) {
553 al_lock_mutex(a4_input_mutex);
555 a4_click_x = (int)(((float)ev->mouse.x - a4_off_x) / a4_scale);
556 a4_click_y = (int)(((float)ev->mouse.y - a4_off_y) / a4_scale);
557 al_unlock_mutex(a4_input_mutex);
558 }
559 break;
560 case ALLEGRO_EVENT_MOUSE_BUTTON_UP:
561 if (ev->mouse.button >= 1 && ev->mouse.button <= 3) mouse_b &= ~(1 << (ev->mouse.button - 1));
562 break;
563 case ALLEGRO_EVENT_DISPLAY_SWITCH_OUT:
564 memset((void*)key, 0, sizeof(key));
565 mouse_b = 0;
566 break;
567 case ALLEGRO_EVENT_DISPLAY_CLOSE:
570 break;
571 default:
572 break;
573 }
574}
575
576static void* _a4_input_proc(ALLEGRO_THREAD* thr, void* arg) {
577 (void)arg;
578 while (!al_get_thread_should_stop(thr)) {
579 ALLEGRO_EVENT ev;
580 if (al_wait_for_event_timed(a4_input_queue, &ev, 0.05f)) _a4_input_handle(&ev);
581 }
582 return NULL;
583}
584
585static void* _a4_timer_proc(ALLEGRO_THREAD* thr, void* arg) {
586 (void)arg;
587 while (!al_get_thread_should_stop(thr)) {
588 double now = al_get_time();
589 double wait = 0.01;
590 void (*due[A4_MAX_TIMERS])(void);
591 int nb_due = 0;
592 al_lock_mutex(a4_timer_mutex);
593 for (int i = 0; i < A4_MAX_TIMERS; i++) {
594 if (!a4_timers[i].proc) continue;
595 if (now >= a4_timers[i].next) {
596 due[nb_due++] = a4_timers[i].proc;
597 a4_timers[i].next += a4_timers[i].period;
598 if (a4_timers[i].next < now) a4_timers[i].next = now + a4_timers[i].period;
599 }
600 double left = a4_timers[i].next - now;
601 if (left < wait) wait = left;
602 }
603 al_unlock_mutex(a4_timer_mutex);
604 for (int i = 0; i < nb_due; i++) due[i]();
605 if (wait > 0.0) al_rest(wait);
606 }
607 return NULL;
608}
609
610int allegro_init(void) {
611 if (a4_inited) return 0;
612 if (!al_init()) {
613 snprintf(allegro_error, sizeof(allegro_error), "al_init failed");
614 return -1;
615 }
616 al_init_primitives_addon();
617 al_init_image_addon();
618 al_init_font_addon();
619 al_init_ttf_addon();
620 memset((void*)key, 0, sizeof(key));
621 memset(a4_timers, 0, sizeof(a4_timers));
622 a4_input_mutex = al_create_mutex();
623 a4_timer_mutex = al_create_mutex();
624 a4_input_queue = al_create_event_queue();
626 snprintf(allegro_error, sizeof(allegro_error), "could not create the input queue");
627 return -1;
628 }
629 a4_input_thread = al_create_thread(_a4_input_proc, NULL);
630 if (a4_input_thread) al_start_thread(a4_input_thread);
631 a4_timer_thread = al_create_thread(_a4_timer_proc, NULL);
632 if (a4_timer_thread) al_start_thread(a4_timer_thread);
633
634 FONT* f = NULL;
635 Malloc(f, FONT, 1);
636 if (f) {
637 f->f = al_create_builtin_font();
638 font = f;
639 }
640 a4_inited = 1;
641 atexit(allegro_exit);
642 return 0;
643}
644
645static void _a4_destroy_display(void) {
646 if (screen) {
648 screen = NULL;
649 }
650 if (a4_display) {
651 if (a4_input_queue) al_unregister_event_source(a4_input_queue, al_get_display_event_source(a4_display));
652 al_destroy_display(a4_display);
653 a4_display = NULL;
654 }
655}
656
657void allegro_exit(void) {
658 if (!a4_inited) return;
659 a4_inited = 0;
660 if (a4_timer_thread) {
661 al_destroy_thread(a4_timer_thread);
662 a4_timer_thread = NULL;
663 }
664 if (a4_input_thread) {
665 al_destroy_thread(a4_input_thread);
666 a4_input_thread = NULL;
667 }
668 remove_sound();
670 if (font) {
671 if (font->f) al_destroy_font(font->f);
672 Free(font);
673 font = NULL;
674 }
675 if (a4_input_queue) {
676 al_destroy_event_queue(a4_input_queue);
677 a4_input_queue = NULL;
678 }
679 if (a4_input_mutex) {
680 al_destroy_mutex(a4_input_mutex);
681 a4_input_mutex = NULL;
682 }
683 if (a4_timer_mutex) {
684 al_destroy_mutex(a4_timer_mutex);
685 a4_timer_mutex = NULL;
686 }
687}
688
689void set_uformat(int type) {
690 (void)type;
691}
692
693int ustricmp(const char* s1, const char* s2) {
694 return strcasecmp(s1, s2);
695}
696
697void set_color_depth(int depth) {
698 if (depth != 32) n_log(LOG_DEBUG, "color depth %d requested, using 32", depth);
699}
700
702 return 32;
703}
704
705static void _a4_compute_scale(void) {
706 if (!a4_display || !screen) return;
707 float dw = (float)al_get_display_width(a4_display);
708 float dh = (float)al_get_display_height(a4_display);
709 float sx = dw / (float)screen->w;
710 float sy = dh / (float)screen->h;
711 a4_scale = sx < sy ? sx : sy;
712 if (a4_scale <= 0.0f) a4_scale = 1.0f;
713 a4_off_x = (dw - (float)screen->w * a4_scale) / 2.0f;
714 a4_off_y = (dh - (float)screen->h * a4_scale) / 2.0f;
715}
716
717int set_gfx_mode(int card, int w, int h, int v_w, int v_h) {
718 (void)v_w;
719 (void)v_h;
720 if (!a4_inited && allegro_init() != 0) return -1;
722 if (card == GFX_TEXT) return 0;
723
724 int flags = ALLEGRO_WINDOWED;
725 if (card == GFX_AUTODETECT_FULLSCREEN || card == GFX_DIRECTX || card == GFX_OPENGL_FULLSCREEN) flags = ALLEGRO_FULLSCREEN_WINDOW;
726 al_set_new_display_flags(flags);
727 a4_display = al_create_display(w, h);
728 if (!a4_display) {
729 snprintf(allegro_error, sizeof(allegro_error), "could not create a %dx%d display", w, h);
730 return -1;
731 }
732 a4_gl_mode = (card == GFX_OPENGL_WINDOWED || card == GFX_OPENGL_FULLSCREEN || card == GFX_OPENGL);
733 al_register_event_source(a4_input_queue, al_get_display_event_source(a4_display));
734 if (a4_cursor_shown)
735 al_show_mouse_cursor(a4_display);
736 else
737 al_hide_mouse_cursor(a4_display);
738
739 screen = create_bitmap(w, h);
740 if (!screen) {
741 snprintf(allegro_error, sizeof(allegro_error), "could not create the screen bitmap");
743 return -1;
744 }
745 A4P(screen)->is_screen = 1;
747 _a4_present();
748 return 0;
749}
750
751void set_window_title(const char* name) {
752 if (a4_display && name) al_set_window_title(a4_display, name);
753}
754
755int set_close_button_callback(void (*proc)(void)) {
756 a4_close_cb = proc;
757 return 0;
758}
759
760/* Draw screen on the display, letterboxed, and flip. */
761static void _a4_present(void) {
762 if (!a4_display || !screen) return;
763 _a4_flush();
764 ALLEGRO_BITMAP* src = _a4_source(screen, FALSE);
765 al_set_target_backbuffer(a4_display);
766 al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
767 if (a4_off_x > 0.0f || a4_off_y > 0.0f) al_clear_to_color(al_map_rgb(0, 0, 0));
768 al_draw_scaled_bitmap(src, 0, 0, (float)screen->w, (float)screen->h, a4_off_x, a4_off_y, (float)screen->w * a4_scale, (float)screen->h * a4_scale, 0);
769 al_flip_display();
770 a4_screen_dirty = 0;
771}
772
774 return 0;
775}
776
779
782
783void allegro_gl_flip(void) {
785}
786
787/* Keyboard and mouse. */
788
790 if (!a4_inited && allegro_init() != 0) return -1;
791 if (!al_is_keyboard_installed() && !al_install_keyboard()) return -1;
792 al_register_event_source(a4_input_queue, al_get_keyboard_event_source());
793 return 0;
794}
795
796int poll_keyboard(void) {
797 return 0;
798}
799
800/* Take the latched left click, TRUE and its position if there was one. */
801static int _a4_take_click(int* x, int* y) {
802 if (!a4_input_mutex) return FALSE;
803 al_lock_mutex(a4_input_mutex);
804 int r = a4_click_pending;
805 if (r) {
806 *x = a4_click_x;
807 *y = a4_click_y;
809 }
810 al_unlock_mutex(a4_input_mutex);
811 return r;
812}
813
814int keypressed(void) {
815 if (!a4_input_mutex) return FALSE;
816 al_lock_mutex(a4_input_mutex);
817 int r = a4_keybuf_count > 0;
818 al_unlock_mutex(a4_input_mutex);
819 return r;
820}
821
822int readkey(void) {
823 if (!a4_input_mutex) return 0;
824 for (;;) {
825 al_lock_mutex(a4_input_mutex);
826 if (a4_keybuf_count > 0) {
827 int k = a4_keybuf[a4_keybuf_start];
830 al_unlock_mutex(a4_input_mutex);
831 return k;
832 }
833 al_unlock_mutex(a4_input_mutex);
834 al_rest(0.005);
835 }
836}
837
838void clear_keybuf(void) {
839 if (!a4_input_mutex) return;
840 al_lock_mutex(a4_input_mutex);
841 a4_keybuf_start = 0;
842 a4_keybuf_count = 0;
843 al_unlock_mutex(a4_input_mutex);
844}
845
846int install_mouse(void) {
847 if (!a4_inited && allegro_init() != 0) return -1;
848 if (!al_is_mouse_installed() && !al_install_mouse()) return -1;
849 al_register_event_source(a4_input_queue, al_get_mouse_event_source());
850 return 3;
851}
852
853int poll_mouse(void) {
854 return 0;
855}
856
857// The Allegro 4 signature takes a non const BITMAP*, kept for source compatibility.
858// cppcheck-suppress constParameterPointer
859// cppcheck-suppress constParameter
860void show_mouse(BITMAP* bmp) {
861 a4_cursor_shown = (bmp != NULL);
862 if (!a4_display) return;
863 if (a4_cursor_shown)
864 al_show_mouse_cursor(a4_display);
865 else
866 al_hide_mouse_cursor(a4_display);
867}
868
869void get_mouse_mickeys(int* mickeyx, int* mickeyy) {
870 if (!a4_input_mutex) return;
871 al_lock_mutex(a4_input_mutex);
872 if (mickeyx) *mickeyx = a4_mickey_x;
873 if (mickeyy) *mickeyy = a4_mickey_y;
874 a4_mickey_x = 0;
875 a4_mickey_y = 0;
876 al_unlock_mutex(a4_input_mutex);
877}
878
879void position_mouse(int x, int y) {
880 if (!a4_display) return;
881 al_set_mouse_xy(a4_display, (int)((float)x * a4_scale + a4_off_x), (int)((float)y * a4_scale + a4_off_y));
882 mouse_x = x;
883 mouse_y = y;
884}
885
886/* Timers. */
887
888int install_timer(void) {
889 return a4_inited ? 0 : allegro_init();
890}
891
892int install_int_ex(void (*proc)(void), long speed) {
893 if (!proc || speed <= 0) return -1;
894 if (!a4_inited && allegro_init() != 0) return -1;
895 double period = (double)speed / (double)TIMERS_PER_SECOND;
896 int ret = -1;
897 al_lock_mutex(a4_timer_mutex);
898 for (int i = 0; i < A4_MAX_TIMERS; i++) {
899 if (a4_timers[i].proc == proc) {
900 a4_timers[i].period = period;
901 ret = 0;
902 break;
903 }
904 }
905 for (int i = 0; ret != 0 && i < A4_MAX_TIMERS; i++) {
906 if (!a4_timers[i].proc) {
907 a4_timers[i].proc = proc;
908 a4_timers[i].period = period;
909 a4_timers[i].next = al_get_time() + period;
910 ret = 0;
911 }
912 }
913 al_unlock_mutex(a4_timer_mutex);
914 return ret;
915}
916
917int install_int(void (*proc)(void), int speed) {
918 return install_int_ex(proc, MSEC_TO_TIMER(speed));
919}
920
921void remove_int(void (*proc)(void)) {
922 if (!a4_timer_mutex) return;
923 al_lock_mutex(a4_timer_mutex);
924 for (int i = 0; i < A4_MAX_TIMERS; i++)
925 if (a4_timers[i].proc == proc) a4_timers[i].proc = NULL;
926 al_unlock_mutex(a4_timer_mutex);
927}
928
929void rest(unsigned int ms) {
930 al_rest((double)ms / 1000.0);
931}
932
933/* Bitmaps. */
934
935BITMAP* create_bitmap(int width, int height) {
936 if (width <= 0 || height <= 0) return NULL;
937 al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_ANY_32_WITH_ALPHA);
938 ALLEGRO_BITMAP* abmp = al_create_bitmap(width, height);
939 if (!abmp) {
940 snprintf(allegro_error, sizeof(allegro_error), "could not create a %dx%d bitmap", width, height);
941 return NULL;
942 }
943 ALLEGRO_STATE state;
944 al_store_state(&state, ALLEGRO_STATE_TARGET_BITMAP);
945 al_set_target_bitmap(abmp);
946 al_clear_to_color(al_map_rgb(0, 0, 0));
947 al_restore_state(&state);
948 return _a4_wrap(abmp);
949}
950
951BITMAP* create_video_bitmap(int width, int height) {
952 return create_bitmap(width, height);
953}
954
955BITMAP* create_bitmap_ex(int depth, int width, int height) {
956 (void)depth;
957 return create_bitmap(width, height);
958}
959
961 if (!bmp) return;
962 A4_BMP* p = A4P(bmp);
963 if (a4_batch_target == bmp) {
964 a4_batch_n = 0;
965 a4_batch_target = NULL;
966 }
967 if (p) {
968 _a4_unlock(bmp);
969 if (p->masked) al_destroy_bitmap(p->masked);
970 if (p->bmp) al_destroy_bitmap(p->bmp);
971 Free(p);
972 }
973 if (bmp == screen) screen = NULL;
974 Free(bmp);
975}
976
977BITMAP* load_bitmap(const char* filename, RGB* pal) {
978 (void)pal;
979 char* path = _a4_resolve(filename);
980 if (!path) return NULL;
981 al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_ANY_32_WITH_ALPHA);
982 ALLEGRO_BITMAP* abmp = al_load_bitmap_flags(path, ALLEGRO_NO_PREMULTIPLIED_ALPHA);
983 if (!abmp) {
984 snprintf(allegro_error, sizeof(allegro_error), "could not load %s", path);
985 n_log(LOG_ERR, "could not load bitmap %s", path);
986 }
987 free(path);
988 return _a4_wrap(abmp);
989}
990
991int save_bitmap(const char* filename, BITMAP* bmp, const RGB* pal) {
992 (void)pal;
993 if (!bmp || !filename) return -1;
994 _a4_unlock(bmp);
995 return al_save_bitmap(filename, A4P(bmp)->bmp) ? 0 : -1;
996}
997
999 (void)bmp;
1000 return 32;
1001}
1002
1004 (void)bmp;
1005 return A4_MASK_COLOR;
1006}
1007
1009 (void)bmp;
1010 return FALSE;
1011}
1012
1013void clear_to_color(BITMAP* bmp, int color) {
1014 if (!_a4_target(bmp)) return;
1015 al_clear_to_color(_a4_rgba(color, (int)(_a4_pixel_from_color(color) >> 24)));
1016 _a4_done(bmp);
1017}
1018
1019void clear(BITMAP* bmp) {
1020 clear_to_color(bmp, 0);
1021}
1022
1024 clear_to_color(bmp, 0);
1025}
1026
1027/* Draw a region of source onto dest, through a temporary copy when they
1028 * are the same bitmap (Allegro 4 allowed overlapping blits). */
1029static void _a4_draw_region(BITMAP* source, BITMAP* dest, int masked, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh, int flags) {
1030 if (!source || !dest || sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0) return;
1031 ALLEGRO_BITMAP* src = _a4_source(source, masked);
1032 ALLEGRO_BITMAP* tmp = NULL;
1033 if (source == dest) {
1034 tmp = al_create_bitmap((int)sw, (int)sh);
1035 if (!tmp) return;
1036 al_set_target_bitmap(tmp);
1037 al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
1038 al_draw_bitmap_region(src, sx, sy, sw, sh, 0, 0, 0);
1039 src = tmp;
1040 sx = 0;
1041 sy = 0;
1042 }
1043 if (_a4_target(dest)) {
1044 if (masked)
1046 else
1048 al_draw_scaled_bitmap(src, sx, sy, sw, sh, dx, dy, dw, dh, flags);
1049 }
1050 if (tmp) al_destroy_bitmap(tmp);
1051 _a4_done(dest);
1052}
1053
1054void blit(BITMAP* source, BITMAP* dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height) {
1055 _a4_draw_region(source, dest, FALSE, (float)source_x, (float)source_y, (float)width, (float)height, (float)dest_x, (float)dest_y, (float)width, (float)height, 0);
1056}
1057
1058void masked_blit(BITMAP* source, BITMAP* dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height) {
1059 _a4_draw_region(source, dest, TRUE, (float)source_x, (float)source_y, (float)width, (float)height, (float)dest_x, (float)dest_y, (float)width, (float)height, 0);
1060}
1061
1062void stretch_blit(BITMAP* source, BITMAP* dest, int source_x, int source_y, int source_width, int source_height, int dest_x, int dest_y, int dest_width, int dest_height) {
1063 _a4_draw_region(source, dest, FALSE, (float)source_x, (float)source_y, (float)source_width, (float)source_height, (float)dest_x, (float)dest_y, (float)dest_width, (float)dest_height, 0);
1064}
1065
1066void masked_stretch_blit(BITMAP* source, BITMAP* dest, int source_x, int source_y, int source_w, int source_h, int dest_x, int dest_y, int dest_w, int dest_h) {
1067 _a4_draw_region(source, dest, TRUE, (float)source_x, (float)source_y, (float)source_w, (float)source_h, (float)dest_x, (float)dest_y, (float)dest_w, (float)dest_h, 0);
1068}
1069
1070static void _a4_sprite(BITMAP* bmp, BITMAP* sprite, int x, int y, int flags) {
1071 if (!sprite) return;
1072 _a4_draw_region(sprite, bmp, TRUE, 0, 0, (float)sprite->w, (float)sprite->h, (float)x, (float)y, (float)sprite->w, (float)sprite->h, flags);
1073}
1074
1075void draw_sprite(BITMAP* bmp, BITMAP* sprite, int x, int y) {
1076 _a4_sprite(bmp, sprite, x, y, 0);
1077}
1078
1079void draw_sprite_h_flip(BITMAP* bmp, BITMAP* sprite, int x, int y) {
1080 _a4_sprite(bmp, sprite, x, y, ALLEGRO_FLIP_HORIZONTAL);
1081}
1082
1083void draw_sprite_v_flip(BITMAP* bmp, BITMAP* sprite, int x, int y) {
1084 _a4_sprite(bmp, sprite, x, y, ALLEGRO_FLIP_VERTICAL);
1085}
1086
1087void draw_sprite_vh_flip(BITMAP* bmp, BITMAP* sprite, int x, int y) {
1088 _a4_sprite(bmp, sprite, x, y, ALLEGRO_FLIP_HORIZONTAL | ALLEGRO_FLIP_VERTICAL);
1089}
1090
1091void stretch_sprite(BITMAP* bmp, BITMAP* sprite, int x, int y, int w, int h) {
1092 if (!sprite) return;
1093 _a4_draw_region(sprite, bmp, TRUE, 0, 0, (float)sprite->w, (float)sprite->h, (float)x, (float)y, (float)w, (float)h, 0);
1094}
1095
1096void draw_trans_sprite(BITMAP* bmp, BITMAP* sprite, int x, int y) {
1097 if (!sprite || !bmp || sprite == bmp) return;
1098 /* the alpha blender uses the sprite's own alpha channel, the trans
1099 * blender a constant opacity over the masked sprite */
1100 ALLEGRO_BITMAP* src = _a4_source(sprite, !a4_alpha_blender);
1101 if (!_a4_target(bmp)) return;
1103 if (a4_alpha_blender)
1104 al_draw_bitmap(src, (float)x, (float)y, 0);
1105 else
1106 al_draw_tinted_bitmap(src, al_map_rgba_f(1, 1, 1, (float)a4_trans_alpha / 255.0f), (float)x, (float)y, 0);
1107 _a4_done(bmp);
1108}
1109
1110static void _a4_rotated(BITMAP* bmp, BITMAP* sprite, float x, float y, float cx, float cy, fixed angle, fixed scale) {
1111 if (!sprite || !bmp || sprite == bmp) return;
1112 ALLEGRO_BITMAP* src = _a4_source(sprite, TRUE);
1113 if (!_a4_target(bmp)) return;
1115 float radians = (float)(fixtof(angle) * ALLEGRO_PI / 128.0);
1116 float s = (float)fixtof(scale);
1117 al_draw_scaled_rotated_bitmap(src, cx, cy, x, y, s, s, radians, 0);
1118 _a4_done(bmp);
1119}
1120
1121void rotate_sprite(BITMAP* bmp, BITMAP* sprite, int x, int y, fixed angle) {
1122 if (!sprite) return;
1123 _a4_rotated(bmp, sprite, (float)x + (float)sprite->w / 2.0f, (float)y + (float)sprite->h / 2.0f, (float)sprite->w / 2.0f, (float)sprite->h / 2.0f, angle, itofix(1));
1124}
1125
1126void rotate_scaled_sprite(BITMAP* bmp, BITMAP* sprite, int x, int y, fixed angle, fixed scale) {
1127 if (!sprite) return;
1128 float s = (float)fixtof(scale);
1129 _a4_rotated(bmp, sprite, (float)x + (float)sprite->w * s / 2.0f, (float)y + (float)sprite->h * s / 2.0f, (float)sprite->w / 2.0f, (float)sprite->h / 2.0f, angle, scale);
1130}
1131
1132void pivot_sprite(BITMAP* bmp, BITMAP* sprite, int x, int y, int cx, int cy, fixed angle) {
1133 _a4_rotated(bmp, sprite, (float)x, (float)y, (float)cx, (float)cy, angle, itofix(1));
1134}
1135
1136void pivot_scaled_sprite(BITMAP* bmp, BITMAP* sprite, int x, int y, int cx, int cy, fixed angle, fixed scale) {
1137 _a4_rotated(bmp, sprite, (float)x, (float)y, (float)cx, (float)cy, angle, scale);
1138}
1139
1140/* Primitives. Allegro 4 coordinates are inclusive pixel indexes. */
1141
1142void drawing_mode(int mode, BITMAP* pattern, int x_anchor, int y_anchor) {
1143 (void)pattern;
1144 (void)x_anchor;
1145 (void)y_anchor;
1147}
1148
1149void solid_mode(void) {
1151}
1152
1153void set_trans_blender(int r, int g, int b, int a) {
1154 (void)r;
1155 (void)g;
1156 (void)b;
1157 a4_alpha_blender = 0;
1158 a4_trans_alpha = a < 0 ? 0 : (a > 255 ? 255 : a);
1159}
1160
1162 a4_alpha_blender = 1;
1163}
1164
1165void putpixel(BITMAP* bmp, int x, int y, int color) {
1166 if (!bmp || x < 0 || y < 0 || x >= bmp->w || y >= bmp->h) return;
1168 if (!bmp->priv) return;
1169 _a4_box(bmp, (float)x, (float)y, (float)x + 1.0f, (float)y + 1.0f, color);
1170 _a4_done(bmp);
1171 return;
1172 }
1173 ALLEGRO_LOCKED_REGION* lr = _a4_lock(bmp);
1174 if (!lr) return;
1175 uint32_t* row = (uint32_t*)((char*)lr->data + (ptrdiff_t)y * lr->pitch);
1176 row[x] = _a4_pixel_from_color(color);
1177 A4P(bmp)->masked_dirty = 1;
1178 if (A4P(bmp)->is_screen) a4_screen_dirty = 1;
1179}
1180
1181int getpixel(BITMAP* bmp, int x, int y) {
1182 if (!bmp || x < 0 || y < 0 || x >= bmp->w || y >= bmp->h) return -1;
1183 const ALLEGRO_LOCKED_REGION* lr = _a4_lock(bmp);
1184 if (!lr) return -1;
1185 const uint32_t* row = (const uint32_t*)((const char*)lr->data + (ptrdiff_t)y * lr->pitch);
1186 return _a4_color_from_pixel(row[x]);
1187}
1188
1189void line(BITMAP* bmp, int x1, int y1, int x2, int y2, int color) {
1190 if (!bmp || !bmp->priv) return;
1191 /* a one pixel wide quad through the pixel centres, reaching half a
1192 * pixel past both ends: both end points are drawn, as in Allegro 4 */
1193 float ax = (float)x1 + 0.5f, ay = (float)y1 + 0.5f;
1194 float bx = (float)x2 + 0.5f, by = (float)y2 + 0.5f;
1195 float dx = bx - ax, dy = by - ay;
1196 float len = sqrtf(dx * dx + dy * dy);
1197 if (len < 0.5f) {
1198 _a4_box(bmp, (float)x1, (float)y1, (float)x1 + 1.0f, (float)y1 + 1.0f, color);
1199 } else {
1200 float ux = dx / len * 0.5f, uy = dy / len * 0.5f;
1201 ax -= ux;
1202 ay -= uy;
1203 bx += ux;
1204 by += uy;
1205 _a4_quad(bmp, ax - uy, ay + ux, bx - uy, by + ux, bx + uy, by - ux, ax + uy, ay - ux, color);
1206 }
1207 _a4_done(bmp);
1208}
1209
1210void hline(BITMAP* bmp, int x1, int y, int x2, int color) {
1211 rectfill(bmp, x1, y, x2, y, color);
1212}
1213
1214void vline(BITMAP* bmp, int x, int y1, int y2, int color) {
1215 rectfill(bmp, x, y1, x, y2, color);
1216}
1217
1218void rect(BITMAP* bmp, int x1, int y1, int x2, int y2, int color) {
1219 if (!bmp || !bmp->priv) return;
1220 if (x1 > x2) {
1221 int t = x1;
1222 x1 = x2;
1223 x2 = t;
1224 }
1225 if (y1 > y2) {
1226 int t = y1;
1227 y1 = y2;
1228 y2 = t;
1229 }
1230 _a4_box(bmp, (float)x1, (float)y1, (float)x2 + 1.0f, (float)y1 + 1.0f, color);
1231 if (y2 > y1) _a4_box(bmp, (float)x1, (float)y2, (float)x2 + 1.0f, (float)y2 + 1.0f, color);
1232 if (y2 - y1 > 1) {
1233 _a4_box(bmp, (float)x1, (float)y1 + 1.0f, (float)x1 + 1.0f, (float)y2, color);
1234 if (x2 > x1) _a4_box(bmp, (float)x2, (float)y1 + 1.0f, (float)x2 + 1.0f, (float)y2, color);
1235 }
1236 _a4_done(bmp);
1237}
1238
1239void rectfill(BITMAP* bmp, int x1, int y1, int x2, int y2, int color) {
1240 if (!bmp || !bmp->priv) return;
1241 if (x1 > x2) {
1242 int t = x1;
1243 x1 = x2;
1244 x2 = t;
1245 }
1246 if (y1 > y2) {
1247 int t = y1;
1248 y1 = y2;
1249 y2 = t;
1250 }
1251 _a4_box(bmp, (float)x1, (float)y1, (float)x2 + 1.0f, (float)y2 + 1.0f, color);
1252 _a4_done(bmp);
1253}
1254
1255void circle(BITMAP* bmp, int x, int y, int radius, int color) {
1256 if (!_a4_target(bmp)) return;
1257 al_draw_circle((float)x + 0.5f, (float)y + 0.5f, (float)radius, _a4_prim_color(color), 1.0f);
1258 _a4_done(bmp);
1259}
1260
1261void circlefill(BITMAP* bmp, int x, int y, int radius, int color) {
1262 if (!_a4_target(bmp)) return;
1263 al_draw_filled_circle((float)x + 0.5f, (float)y + 0.5f, (float)radius + 0.5f, _a4_prim_color(color));
1264 _a4_done(bmp);
1265}
1266
1267void ellipse(BITMAP* bmp, int x, int y, int rx, int ry, int color) {
1268 if (!_a4_target(bmp)) return;
1269 al_draw_ellipse((float)x + 0.5f, (float)y + 0.5f, (float)rx, (float)ry, _a4_prim_color(color), 1.0f);
1270 _a4_done(bmp);
1271}
1272
1273void ellipsefill(BITMAP* bmp, int x, int y, int rx, int ry, int color) {
1274 if (!_a4_target(bmp)) return;
1275 al_draw_filled_ellipse((float)x + 0.5f, (float)y + 0.5f, (float)rx + 0.5f, (float)ry + 0.5f, _a4_prim_color(color));
1276 _a4_done(bmp);
1277}
1278
1279void triangle(BITMAP* bmp, int x1, int y1, int x2, int y2, int x3, int y3, int color) {
1280 if (!bmp || !bmp->priv) return;
1281 int blend = A4_BLEND_COPY;
1282 ALLEGRO_COLOR c = _a4_prim(color, &blend);
1283 ALLEGRO_VERTEX* v = _a4_reserve(bmp, blend, 3);
1284 _a4_vertex(&v[0], (float)x1 + 0.5f, (float)y1 + 0.5f, c);
1285 _a4_vertex(&v[1], (float)x2 + 0.5f, (float)y2 + 0.5f, c);
1286 _a4_vertex(&v[2], (float)x3 + 0.5f, (float)y3 + 0.5f, c);
1287 _a4_done(bmp);
1288}
1289
1290/* Direct pixel access. */
1291
1292void bmp_select(BITMAP* bmp) {
1293 (void)bmp;
1294}
1295
1296uintptr_t bmp_write_line(BITMAP* bmp, int y) {
1297 if (!bmp || y < 0 || y >= bmp->h) return 0;
1298 ALLEGRO_LOCKED_REGION* lr = _a4_lock(bmp);
1299 if (!lr) return 0;
1300 A4P(bmp)->masked_dirty = 1;
1301 if (A4P(bmp)->is_screen) a4_screen_dirty = 1;
1302 return (uintptr_t)((char*)lr->data + (ptrdiff_t)y * lr->pitch);
1303}
1304
1305uintptr_t bmp_read_line(BITMAP* bmp, int y) {
1306 if (!bmp || y < 0 || y >= bmp->h) return 0;
1307 ALLEGRO_LOCKED_REGION* lr = _a4_lock(bmp);
1308 if (!lr) return 0;
1309 return (uintptr_t)((char*)lr->data + (ptrdiff_t)y * lr->pitch);
1310}
1311
1313 (void)bmp;
1314}
1315
1316/* Text. */
1317
1318static ALLEGRO_FONT* _a4_font(const FONT* f) {
1319 if (f && f->f) return f->f;
1320 return (font && font->f) ? font->f : NULL;
1321}
1322
1323int text_length(const FONT* f, const char* str) {
1324 ALLEGRO_FONT* af = _a4_font(f);
1325 return (af && str) ? al_get_text_width(af, str) : 0;
1326}
1327
1328int text_height(const FONT* f) {
1329 ALLEGRO_FONT* af = _a4_font(f);
1330 return af ? al_get_font_line_height(af) : 0;
1331}
1332
1333/* align: 0 left, 1 centre, 2 right */
1334static void _a4_text(BITMAP* bmp, const FONT* f, const char* s, int x, int y, int color, int bg, int align) {
1335 ALLEGRO_FONT* af = _a4_font(f);
1336 if (!af || !s) return;
1337 int w = al_get_text_width(af, s);
1338 int left = align == 1 ? x - w / 2 : (align == 2 ? x - w : x);
1339 if (bg != -1) {
1340 int mode = a4_draw_mode;
1342 rectfill(bmp, left, y, left + w - 1, y + al_get_font_line_height(af) - 1, bg);
1344 }
1345 if (!_a4_target(bmp)) return;
1346 /* font glyphs are premultiplied */
1347 al_set_separate_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA, ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
1348 ALLEGRO_COLOR c = (color == -1) ? al_map_rgb(255, 255, 255) : _a4_rgba(color, 255);
1349 al_draw_text(af, c, (float)left, (float)y, ALLEGRO_ALIGN_LEFT, s);
1350 _a4_done(bmp);
1351}
1352
1353void textout_ex(BITMAP* bmp, const FONT* f, const char* s, int x, int y, int color, int bg) {
1354 _a4_text(bmp, f, s, x, y, color, bg, 0);
1355}
1356
1357void textout_centre_ex(BITMAP* bmp, const FONT* f, const char* s, int x, int y, int color, int bg) {
1358 _a4_text(bmp, f, s, x, y, color, bg, 1);
1359}
1360
1361void textout_right_ex(BITMAP* bmp, const FONT* f, const char* s, int x, int y, int color, int bg) {
1362 _a4_text(bmp, f, s, x, y, color, bg, 2);
1363}
1364
1365/* the format arguments come straight from Allegro 4 callers */
1366#define A4_TEXTPRINTF(align) \
1367 char buf[1024]; \
1368 va_list ap; \
1369 va_start(ap, format); \
1370 vsnprintf(buf, sizeof(buf), format, ap); \
1371 va_end(ap); \
1372 _a4_text(bmp, f, buf, x, y, color, bg, align);
1373
1374void textprintf_ex(BITMAP* bmp, const FONT* f, int x, int y, int color, int bg, const char* format, ...) {
1375#pragma GCC diagnostic push
1376#pragma GCC diagnostic ignored "-Wformat-nonliteral"
1377 A4_TEXTPRINTF(0)
1378#pragma GCC diagnostic pop
1379}
1380
1381void textprintf_centre_ex(BITMAP* bmp, const FONT* f, int x, int y, int color, int bg, const char* format, ...) {
1382#pragma GCC diagnostic push
1383#pragma GCC diagnostic ignored "-Wformat-nonliteral"
1384 A4_TEXTPRINTF(1)
1385#pragma GCC diagnostic pop
1386}
1387
1388void textprintf_right_ex(BITMAP* bmp, const FONT* f, int x, int y, int color, int bg, const char* format, ...){
1389#pragma GCC diagnostic push
1390#pragma GCC diagnostic ignored "-Wformat-nonliteral"
1391 A4_TEXTPRINTF(2)
1392#pragma GCC diagnostic pop
1393}
1394
1395FONT* load_ttf_font(const char* filename, int points, int smooth) {
1396 char* path = _a4_resolve(filename);
1397 if (!path) return NULL;
1398 ALLEGRO_FONT* af = al_load_ttf_font(path, points, smooth == ALLEGTTF_NOSMOOTH ? ALLEGRO_TTF_MONOCHROME : 0);
1399 if (!af) {
1400 snprintf(allegro_error, sizeof(allegro_error), "could not load font %s", path);
1401 n_log(LOG_ERR, "could not load font %s", path);
1402 free(path);
1403 return NULL;
1404 }
1405 free(path);
1406 FONT* f = NULL;
1407 Malloc(f, FONT, 1);
1408 if (!f) {
1409 al_destroy_font(af);
1410 return NULL;
1411 }
1412 f->f = af;
1413 return f;
1414}
1415
1416FONT* load_ttf_font_mem(const char* filename, int points, int smooth) {
1417 return load_ttf_font(filename, points, smooth);
1418}
1419
1421 if (!f || f == font) return;
1422 if (f->f) al_destroy_font(f->f);
1423 Free(f);
1424}
1425
1426/* Message box. */
1427
1428void allegro_message(const char* format, ...) {
1429 char msg[4096];
1430 va_list ap;
1431 va_start(ap, format);
1432#pragma GCC diagnostic push
1433#pragma GCC diagnostic ignored "-Wformat-nonliteral"
1434 vsnprintf(msg, sizeof(msg), format, ap);
1435#pragma GCC diagnostic pop
1436 va_end(ap);
1437 fprintf(stderr, "%s\n", msg);
1438 if (!a4_display || !screen || !font) return;
1439
1440 /* split into lines, wrapping long ones */
1441 ALLEGRO_FONT* af = font->f;
1442 int lh = al_get_font_line_height(af) + 4;
1443 int maxw = screen->w - 60;
1444 char* lines[128];
1445 int nb = 0;
1446 for (char* p = msg; *p && nb < 128;) {
1447 char* end = strchr(p, '\n');
1448 size_t n = end ? (size_t)(end - p) : strlen(p);
1449 while (n > 0 && nb < 128) {
1450 size_t take = n;
1451 char tmp[1024];
1452 for (;;) {
1453 size_t c = take < sizeof(tmp) - 1 ? take : sizeof(tmp) - 1;
1454 memcpy(tmp, p, c);
1455 tmp[c] = '\0';
1456 if (al_get_text_width(af, tmp) <= maxw || take <= 1) break;
1457 take--;
1458 }
1459 lines[nb] = _a4_strndup(p, take);
1460 if (!lines[nb]) break;
1461 nb++;
1462 p += take;
1463 n -= take;
1464 }
1465 if (!end) break;
1466 if (n == 0 && end == p) {
1467 if (nb < 128) lines[nb++] = _a4_strndup("", 0);
1468 }
1469 p = end + 1;
1470 }
1471
1472 int bw = 0;
1473 for (int i = 0; i < nb; i++) {
1474 int w = lines[i] ? al_get_text_width(af, lines[i]) : 0;
1475 if (w > bw) bw = w;
1476 }
1477 const char* hint = "Press a key or click to continue";
1478 if (al_get_text_width(af, hint) > bw) bw = al_get_text_width(af, hint);
1479 bw += 40;
1480 int bh = (nb + 2) * lh + 30;
1481 int bx = (screen->w - bw) / 2;
1482 int by = (screen->h - bh) / 2;
1483
1484 BITMAP* saved = create_bitmap(screen->w, screen->h);
1485 if (saved) blit(screen, saved, 0, 0, 0, 0, screen->w, screen->h);
1486 int mode = a4_draw_mode;
1488 int gl = a4_gl_mode;
1489 a4_gl_mode = 1;
1490 rectfill(screen, bx, by, bx + bw - 1, by + bh - 1, makecol(32, 32, 48));
1491 rect(screen, bx, by, bx + bw - 1, by + bh - 1, makecol(200, 200, 220));
1492 for (int i = 0; i < nb; i++) {
1493 if (lines[i]) textout_ex(screen, font, lines[i], bx + 20, by + 15 + i * lh, makecol(255, 255, 255), -1);
1494 }
1495 textout_centre_ex(screen, font, hint, bx + bw / 2, by + bh - 15 - lh, makecol(160, 160, 190), -1);
1496 _a4_present();
1497
1498 /* wait for everything to be released, then for a new key or click */
1499 while ((mouse_b || keypressed()) && !a4_close_requested) {
1500 clear_keybuf();
1501 al_rest(0.01);
1502 }
1503 int mx = 0, my = 0;
1504 _a4_take_click(&mx, &my);
1505 while (!keypressed() && !mouse_b && !_a4_take_click(&mx, &my) && !a4_close_requested) al_rest(0.01);
1506 while (mouse_b && !a4_close_requested) al_rest(0.01);
1507 clear_keybuf();
1508 for (int i = 0; i < KEY_MAX; i++) key[i] = 0;
1509
1510 if (saved) {
1511 blit(saved, screen, 0, 0, 0, 0, screen->w, screen->h);
1512 destroy_bitmap(saved);
1513 }
1514 _a4_present();
1515 a4_gl_mode = gl;
1517 for (int i = 0; i < nb; i++) free(lines[i]);
1518}
1519
1520int alert(const char* s1, const char* s2, const char* s3, const char* b1, const char* b2, int c1, int c2) {
1521 const char* lines[3] = {s1, s2, s3};
1522 if (!a4_display || !screen || !font) {
1523 for (int i = 0; i < 3; i++)
1524 if (lines[i]) fprintf(stderr, "%s\n", lines[i]);
1525 return 1;
1526 }
1527 ALLEGRO_FONT* af = font->f;
1528 int lh = al_get_font_line_height(af) + 6;
1529 int nb_buttons = b2 ? 2 : 1;
1530 const char* labels[2] = {b1 ? b1 : "OK", b2};
1531 int bw = 0;
1532 for (int i = 0; i < 3; i++)
1533 if (lines[i] && al_get_text_width(af, lines[i]) > bw) bw = al_get_text_width(af, lines[i]);
1534 int btn_w = 0;
1535 for (int i = 0; i < nb_buttons; i++)
1536 if (al_get_text_width(af, labels[i]) + 24 > btn_w) btn_w = al_get_text_width(af, labels[i]) + 24;
1537 if (nb_buttons * btn_w + 20 > bw) bw = nb_buttons * btn_w + 20;
1538 bw += 40;
1539 int bh = 5 * lh + 30;
1540 int bx = (screen->w - bw) / 2, by = (screen->h - bh) / 2;
1541 int btn_y = by + bh - lh - 20;
1542 int btn_x[2];
1543 for (int i = 0; i < nb_buttons; i++) btn_x[i] = bx + (bw - nb_buttons * btn_w - (nb_buttons - 1) * 20) / 2 + i * (btn_w + 20);
1544
1545 BITMAP* saved = create_bitmap(screen->w, screen->h);
1546 if (saved) blit(screen, saved, 0, 0, 0, 0, screen->w, screen->h);
1547 int mode = a4_draw_mode, gl = a4_gl_mode, cursor = a4_cursor_shown;
1549 a4_gl_mode = 1;
1551 while ((mouse_b || keypressed()) && !a4_close_requested) {
1552 clear_keybuf();
1553 al_rest(0.01);
1554 }
1555 int cx = 0, cy = 0;
1556 _a4_take_click(&cx, &cy);
1557 int choice = 0;
1558 while (!choice) {
1559 if (a4_close_requested) {
1560 choice = nb_buttons;
1561 break;
1562 }
1563 int over = -1;
1564 for (int i = 0; i < nb_buttons; i++)
1565 if (mouse_x >= btn_x[i] && mouse_x < btn_x[i] + btn_w && mouse_y >= btn_y && mouse_y < btn_y + lh + 6) over = i;
1566 rectfill(screen, bx, by, bx + bw - 1, by + bh - 1, makecol(32, 32, 48));
1567 rect(screen, bx, by, bx + bw - 1, by + bh - 1, makecol(200, 200, 220));
1568 for (int i = 0; i < 3; i++)
1569 if (lines[i]) textout_centre_ex(screen, font, lines[i], bx + bw / 2, by + 15 + i * lh, makecol(255, 255, 255), -1);
1570 for (int i = 0; i < nb_buttons; i++) {
1571 rectfill(screen, btn_x[i], btn_y, btn_x[i] + btn_w - 1, btn_y + lh + 5, i == over ? makecol(90, 90, 140) : makecol(60, 60, 90));
1572 rect(screen, btn_x[i], btn_y, btn_x[i] + btn_w - 1, btn_y + lh + 5, makecol(200, 200, 220));
1573 textout_centre_ex(screen, font, labels[i], btn_x[i] + btn_w / 2, btn_y + 6, makecol(255, 255, 255), -1);
1574 }
1575 _a4_present();
1576 while (keypressed()) {
1577 int k = readkey();
1578 int ascii = k & 0xFF;
1579 if (c1 && ascii && (ascii == c1 || ascii == (c1 ^ 0x20))) choice = 1;
1580 if (nb_buttons > 1 && c2 && ascii && (ascii == c2 || ascii == (c2 ^ 0x20))) choice = 2;
1581 if ((k >> 8) == KEY_ENTER || (k >> 8) == KEY_ENTER_PAD) choice = 1;
1582 if ((k >> 8) == KEY_ESC) choice = nb_buttons;
1583 }
1584 if (_a4_take_click(&cx, &cy)) {
1585 for (int i = 0; i < nb_buttons; i++)
1586 if (cx >= btn_x[i] && cx < btn_x[i] + btn_w && cy >= btn_y && cy < btn_y + lh + 6) choice = i + 1;
1587 if (choice)
1588 while ((mouse_b & 1) && !a4_close_requested) al_rest(0.01);
1589 }
1590 al_rest(0.01);
1591 }
1592 while ((key[KEY_ESC] || key[KEY_ENTER]) && !a4_close_requested) al_rest(0.01);
1593 clear_keybuf();
1594 if (saved) {
1595 blit(saved, screen, 0, 0, 0, 0, screen->w, screen->h);
1596 destroy_bitmap(saved);
1597 }
1598 _a4_present();
1599 if (!cursor) show_mouse(NULL);
1600 a4_gl_mode = gl;
1602 return choice;
1603}
1604
1605/* Samples. */
1606
1607int install_sound(int digi, int midi, const char* cfg_path) {
1608 (void)midi;
1609 (void)cfg_path;
1610 if (digi == DIGI_NONE) return 0;
1611 if (!a4_inited && allegro_init() != 0) return -1;
1612 if (a4_sound_ok) return 0;
1613 if (!al_install_audio()) {
1614 snprintf(allegro_error, sizeof(allegro_error), "could not start the audio");
1615 return -1;
1616 }
1617 al_init_acodec_addon();
1618 if (!al_reserve_samples(32)) {
1619 snprintf(allegro_error, sizeof(allegro_error), "could not reserve audio samples");
1620 return -1;
1621 }
1622 a4_sound_ok = 1;
1623 return 0;
1624}
1625
1626void remove_sound(void) {
1627 if (!a4_sound_ok) return;
1629 al_stop_samples();
1630 al_uninstall_audio();
1631 a4_sound_ok = 0;
1632}
1633
1634SAMPLE* load_sample(const char* filename) {
1635 if (!a4_sound_ok) return NULL;
1636 char* path = _a4_resolve(filename);
1637 if (!path) return NULL;
1638 ALLEGRO_SAMPLE* s = al_load_sample(path);
1639 if (!s) {
1640 n_log(LOG_ERR, "could not load sample %s", path);
1641 free(path);
1642 return NULL;
1643 }
1644 free(path);
1645 SAMPLE* spl = NULL;
1646 Malloc(spl, SAMPLE, 1);
1647 if (!spl) {
1648 al_destroy_sample(s);
1649 return NULL;
1650 }
1651 spl->spl = s;
1652 return spl;
1653}
1654
1655void stop_sample(const SAMPLE* spl) {
1656 if (!spl || !a4_sound_ok) return;
1657 SAMPLE* s = (SAMPLE*)spl;
1658 for (int i = 0; i < A4_SAMPLE_IDS; i++) {
1659 if (s->used[i]) {
1660 al_stop_sample(&s->ids[i]);
1661 s->used[i] = 0;
1662 }
1663 }
1664}
1665
1667 if (!spl) return;
1668 stop_sample(spl);
1669 if (spl->spl) al_destroy_sample(spl->spl);
1670 Free(spl);
1671}
1672
1673static float _a4_gain(int vol) {
1674 return ((float)vol / 255.0f) * ((float)a4_digi_volume / 255.0f);
1675}
1676
1677static float _a4_pan(int pan) {
1678 float p = ((float)pan - 128.0f) / 127.0f;
1679 return p < -1.0f ? -1.0f : (p > 1.0f ? 1.0f : p);
1680}
1681
1682int play_sample(const SAMPLE* spl, int vol, int pan, int freq, int loop) {
1683 if (!spl || !a4_sound_ok) return -1;
1684 SAMPLE* s = (SAMPLE*)spl;
1685 ALLEGRO_SAMPLE_ID id;
1686 if (!al_play_sample(s->spl, _a4_gain(vol), _a4_pan(pan), (float)freq / 1000.0f, loop ? ALLEGRO_PLAYMODE_LOOP : ALLEGRO_PLAYMODE_ONCE, &id)) return -1;
1687 int slot = s->next;
1688 s->next = (s->next + 1) % A4_SAMPLE_IDS;
1689 s->ids[slot] = id;
1690 s->used[slot] = 1;
1691 return slot;
1692}
1693
1694void adjust_sample(const SAMPLE* spl, int vol, int pan, int freq, int loop) {
1695 if (!spl || !a4_sound_ok) return;
1696 SAMPLE* s = (SAMPLE*)spl;
1697 for (int i = 0; i < A4_SAMPLE_IDS; i++) {
1698 if (!s->used[i]) continue;
1699 ALLEGRO_SAMPLE_INSTANCE* inst = al_lock_sample_id(&s->ids[i]);
1700 if (inst) {
1701 al_set_sample_instance_gain(inst, _a4_gain(vol));
1702 al_set_sample_instance_pan(inst, _a4_pan(pan));
1703 al_set_sample_instance_speed(inst, (float)freq / 1000.0f);
1704 al_set_sample_instance_playmode(inst, loop ? ALLEGRO_PLAYMODE_LOOP : ALLEGRO_PLAYMODE_ONCE);
1705 }
1706 al_unlock_sample_id(&s->ids[i]);
1707 }
1708}
1709
1710/* MIDI through libfluidsynth, loaded at run time. */
1711
1712typedef void* (*fs_new_settings_f)(void);
1713typedef int (*fs_settings_setnum_f)(void*, const char*, double);
1714typedef int (*fs_settings_setstr_f)(void*, const char*, const char*);
1715typedef void* (*fs_new_synth_f)(void*);
1716typedef int (*fs_sfload_f)(void*, const char*, int);
1717typedef void* (*fs_new_player_f)(void*);
1718typedef int (*fs_player_add_f)(void*, const char*);
1719typedef int (*fs_player_int_f)(void*);
1720typedef int (*fs_player_set_loop_f)(void*, int);
1721typedef int (*fs_write_float_f)(void*, int, void*, int, int, void*, int, int);
1722typedef void (*fs_delete_f)(void*);
1723typedef void* (*fs_set_log_function_f)(int, void*, void*);
1724
1725static struct {
1726 int tried;
1727 int ok;
1728 N_MODULE* lib;
1729 fs_new_settings_f new_settings;
1730 fs_settings_setnum_f setnum;
1731 fs_settings_setstr_f setstr;
1732 fs_new_synth_f new_synth;
1733 fs_sfload_f sfload;
1734 fs_new_player_f new_player;
1735 fs_player_add_f player_add;
1736 fs_player_int_f player_play;
1737 fs_player_int_f player_stop;
1738 fs_player_set_loop_f player_set_loop;
1739 fs_write_float_f write_float;
1740 fs_player_int_f system_reset;
1741 fs_delete_f delete_player;
1742 fs_delete_f delete_synth;
1743 fs_delete_f delete_settings;
1744 fs_set_log_function_f set_log_function;
1745 void* settings;
1746 void* synth;
1747 void* player;
1748 MIDI* current;
1749 int paused;
1750 ALLEGRO_AUDIO_STREAM* stream;
1751 ALLEGRO_EVENT_QUEUE* queue;
1752 ALLEGRO_THREAD* thread;
1753 ALLEGRO_MUTEX* mutex;
1755
1756#define A4_MIDI_RATE 44100
1757#define A4_MIDI_FRAG 1024
1758
1760 static const char* names[] = {
1761#if defined(__windows__)
1762 "libfluidsynth-3.dll", "libfluidsynth-2.dll", "fluidsynth.dll"
1763#elif defined(__APPLE__)
1764 "libfluidsynth.3.dylib", "libfluidsynth.dylib"
1765#else
1766 "libfluidsynth.so.3", "libfluidsynth.so.2", "libfluidsynth.so"
1767#endif
1768 };
1769 for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
1770 N_MODULE* m = n_module_open(names[i]);
1771 if (m) return m;
1772 }
1773 return NULL;
1774}
1775
1776/* A symbol into a function pointer: memcpy, as n_module_symbol() advises. */
1777static int _a4_fluidsynth_sym(N_MODULE* lib, const char* name, void* fptr, size_t fsize) {
1778 void* p = n_module_symbol(lib, name);
1779 if (!p || fsize != sizeof(p)) return FALSE;
1780 memcpy(fptr, &p, fsize);
1781 return TRUE;
1782}
1783
1784#define A4_SYM(field, name) ok = ok && _a4_fluidsynth_sym(a4_fs.lib, name, &a4_fs.field, sizeof(a4_fs.field))
1785
1786static const char* _a4_soundfont(void) {
1787 static const char* candidates[] = {
1788 "/usr/share/sounds/sf2/default-GM.sf2",
1789 "/usr/share/sounds/sf2/FluidR3_GM.sf2",
1790 "/usr/share/sounds/sf2/TimGM6mb.sf2",
1791 "/usr/share/soundfonts/default.sf2",
1792 "/usr/share/soundfonts/FluidR3_GM.sf2",
1793 "/usr/local/share/soundfonts/default.sf2",
1794 "default.sf2"};
1795 const char* env = getenv("NILOREA_SOUNDFONT");
1796 if (env && env[0]) return env;
1797 for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) {
1798 FILE* f = fopen(candidates[i], "rb");
1799 if (f) {
1800 fclose(f);
1801 return candidates[i];
1802 }
1803 }
1804 return NULL;
1805}
1806
1807static void* _a4_midi_proc(ALLEGRO_THREAD* thr, void* arg) {
1808 (void)arg;
1809 while (!al_get_thread_should_stop(thr)) {
1810 ALLEGRO_EVENT ev;
1811 if (!al_wait_for_event_timed(a4_fs.queue, &ev, 0.05f)) continue;
1812 if (ev.type != ALLEGRO_EVENT_AUDIO_STREAM_FRAGMENT) continue;
1813 float* buf = al_get_audio_stream_fragment(a4_fs.stream);
1814 if (!buf) continue;
1815 al_lock_mutex(a4_fs.mutex);
1816 if (a4_fs.paused)
1817 memset(buf, 0, sizeof(float) * 2 * A4_MIDI_FRAG);
1818 else
1819 a4_fs.write_float(a4_fs.synth, A4_MIDI_FRAG, buf, 0, 2, buf, 1, 2);
1820 al_unlock_mutex(a4_fs.mutex);
1821 al_set_audio_stream_fragment(a4_fs.stream, buf);
1822 }
1823 return NULL;
1824}
1825
1826/* Bring fluidsynth up on first use. FALSE leaves MIDI silent. */
1827static int _a4_midi_init(void) {
1828 if (a4_fs.tried) return a4_fs.ok;
1829 a4_fs.tried = 1;
1830 if (!a4_sound_ok) return FALSE;
1831 const char* sf = _a4_soundfont();
1832 if (!sf) {
1833 n_log(LOG_NOTICE, "no General MIDI soundfont found (set NILOREA_SOUNDFONT), MIDI is silent");
1834 return FALSE;
1835 }
1836 a4_fs.lib = _a4_fluidsynth_open();
1837 if (!a4_fs.lib) {
1838 n_log(LOG_NOTICE, "libfluidsynth not found, MIDI is silent");
1839 return FALSE;
1840 }
1841 int ok = TRUE;
1842 A4_SYM(new_settings, "new_fluid_settings");
1843 A4_SYM(setnum, "fluid_settings_setnum");
1844 A4_SYM(setstr, "fluid_settings_setstr");
1845 A4_SYM(new_synth, "new_fluid_synth");
1846 A4_SYM(sfload, "fluid_synth_sfload");
1847 A4_SYM(new_player, "new_fluid_player");
1848 A4_SYM(player_add, "fluid_player_add");
1849 A4_SYM(player_play, "fluid_player_play");
1850 A4_SYM(player_stop, "fluid_player_stop");
1851 A4_SYM(player_set_loop, "fluid_player_set_loop");
1852 A4_SYM(write_float, "fluid_synth_write_float");
1853 A4_SYM(system_reset, "fluid_synth_system_reset");
1854 A4_SYM(delete_player, "delete_fluid_player");
1855 A4_SYM(delete_synth, "delete_fluid_synth");
1856 A4_SYM(delete_settings, "delete_fluid_settings");
1857 if (!ok) {
1858 n_log(LOG_NOTICE, "libfluidsynth lacks an expected symbol, MIDI is silent");
1859 return FALSE;
1860 }
1861 /* optional: keep fluidsynth's warnings about audio drivers it is not
1862 * asked to use (FLUID_WARN 2, FLUID_INFO 3, FLUID_DBG 4) off stderr */
1863 if (_a4_fluidsynth_sym(a4_fs.lib, "fluid_set_log_function", &a4_fs.set_log_function, sizeof(a4_fs.set_log_function))) {
1864 for (int level = 2; level <= 4; level++) a4_fs.set_log_function(level, NULL, NULL);
1865 }
1866 a4_fs.settings = a4_fs.new_settings();
1867 if (!a4_fs.settings) return FALSE;
1868 a4_fs.setnum(a4_fs.settings, "synth.sample-rate", (double)A4_MIDI_RATE);
1869 a4_fs.setnum(a4_fs.settings, "synth.gain", 0.5);
1870 /* advance the player with the rendered samples, not a system timer */
1871 a4_fs.setstr(a4_fs.settings, "player.timing-source", "sample");
1872 a4_fs.synth = a4_fs.new_synth(a4_fs.settings);
1873 if (!a4_fs.synth) return FALSE;
1874 if (a4_fs.sfload(a4_fs.synth, sf, 1) < 0) {
1875 n_log(LOG_NOTICE, "could not load soundfont %s, MIDI is silent", sf);
1876 return FALSE;
1877 }
1878 a4_fs.mutex = al_create_mutex();
1879 a4_fs.queue = al_create_event_queue();
1880 a4_fs.stream = al_create_audio_stream(4, A4_MIDI_FRAG, A4_MIDI_RATE, ALLEGRO_AUDIO_DEPTH_FLOAT32, ALLEGRO_CHANNEL_CONF_2);
1881 if (!a4_fs.mutex || !a4_fs.queue || !a4_fs.stream) return FALSE;
1882 al_set_audio_stream_gain(a4_fs.stream, (float)a4_midi_volume / 255.0f);
1883 if (!al_attach_audio_stream_to_mixer(a4_fs.stream, al_get_default_mixer())) return FALSE;
1884 al_register_event_source(a4_fs.queue, al_get_audio_stream_event_source(a4_fs.stream));
1885 a4_fs.thread = al_create_thread(_a4_midi_proc, NULL);
1886 if (!a4_fs.thread) return FALSE;
1887 al_start_thread(a4_fs.thread);
1888 n_log(LOG_DEBUG, "MIDI through fluidsynth with soundfont %s", sf);
1889 a4_fs.ok = TRUE;
1890 return TRUE;
1891}
1892
1893static void _a4_midi_stop_locked(void) {
1894 if (a4_fs.player) {
1895 a4_fs.player_stop(a4_fs.player);
1896 a4_fs.delete_player(a4_fs.player);
1897 a4_fs.player = NULL;
1898 }
1899 if (a4_fs.synth && a4_fs.ok) a4_fs.system_reset(a4_fs.synth);
1900 a4_fs.current = NULL;
1901 a4_fs.paused = 0;
1902}
1903
1904static void _a4_midi_shutdown(void) {
1905 if (a4_fs.thread) {
1906 al_destroy_thread(a4_fs.thread);
1907 a4_fs.thread = NULL;
1908 }
1909 if (a4_fs.mutex) _a4_midi_stop_locked();
1910 if (a4_fs.stream) {
1911 al_destroy_audio_stream(a4_fs.stream);
1912 a4_fs.stream = NULL;
1913 }
1914 if (a4_fs.queue) {
1915 al_destroy_event_queue(a4_fs.queue);
1916 a4_fs.queue = NULL;
1917 }
1918 if (a4_fs.mutex) {
1919 al_destroy_mutex(a4_fs.mutex);
1920 a4_fs.mutex = NULL;
1921 }
1922 if (a4_fs.synth) a4_fs.delete_synth(a4_fs.synth);
1923 if (a4_fs.settings) a4_fs.delete_settings(a4_fs.settings);
1924 a4_fs.synth = NULL;
1925 a4_fs.settings = NULL;
1926 n_module_close(&a4_fs.lib);
1927 memset(&a4_fs, 0, sizeof(a4_fs));
1928}
1929
1930MIDI* load_midi(const char* filename) {
1931 char* path = _a4_resolve(filename);
1932 if (!path) return NULL;
1933 FILE* f = fopen(path, "rb");
1934 if (!f) {
1935 n_log(LOG_ERR, "could not open MIDI file %s", path);
1936 free(path);
1937 return NULL;
1938 }
1939 fclose(f);
1940 MIDI* m = NULL;
1941 Malloc(m, MIDI, 1);
1942 if (!m) {
1943 free(path);
1944 return NULL;
1945 }
1946 m->path = path;
1947 return m;
1948}
1949
1950int play_midi(MIDI* midi, int loop) {
1951 if (!midi) {
1952 stop_midi();
1953 return 0;
1954 }
1955 if (!_a4_midi_init()) return 0;
1956 al_lock_mutex(a4_fs.mutex);
1958 a4_fs.player = a4_fs.new_player(a4_fs.synth);
1959 if (a4_fs.player) {
1960 a4_fs.player_add(a4_fs.player, midi->path);
1961 a4_fs.player_set_loop(a4_fs.player, loop ? -1 : 1);
1962 a4_fs.player_play(a4_fs.player);
1963 a4_fs.current = midi;
1964 }
1965 al_unlock_mutex(a4_fs.mutex);
1966 return 0;
1967}
1968
1969void stop_midi(void) {
1970 if (!a4_fs.ok) return;
1971 al_lock_mutex(a4_fs.mutex);
1973 al_unlock_mutex(a4_fs.mutex);
1974}
1975
1976void destroy_midi(MIDI* midi) {
1977 if (!midi) return;
1978 if (a4_fs.ok && a4_fs.current == midi) stop_midi();
1979 free(midi->path);
1980 Free(midi);
1981}
1982
1983void midi_pause(void) {
1984 if (!a4_fs.ok) return;
1985 al_lock_mutex(a4_fs.mutex);
1986 a4_fs.paused = 1;
1987 al_unlock_mutex(a4_fs.mutex);
1988}
1989
1990void midi_resume(void) {
1991 if (!a4_fs.ok) return;
1992 al_lock_mutex(a4_fs.mutex);
1993 a4_fs.paused = 0;
1994 al_unlock_mutex(a4_fs.mutex);
1995}
1996
1997void set_volume(int digi_volume, int midi_volume) {
1998 if (digi_volume >= 0) a4_digi_volume = digi_volume > 255 ? 255 : digi_volume;
1999 if (midi_volume >= 0) a4_midi_volume = midi_volume > 255 ? 255 : midi_volume;
2000 if (a4_fs.stream) al_set_audio_stream_gain(a4_fs.stream, (float)a4_midi_volume / 255.0f);
2001}
2002
2003/* Old nilorea Allegro 4 helpers. */
2004
2005int gfx_mode(int mode, int w, int h, int v_w, int v_h, int depth, double r, double g, double b, double a) {
2006 (void)a;
2007 set_color_depth(depth);
2008 if (set_gfx_mode(mode, w, h, v_w, v_h) != 0) {
2009 allegro_message("Could not set a %dx%d graphics mode: %s", w, h, allegro_error);
2010 return FALSE;
2011 }
2012 /* components were given either as 0-1 or as 0-255 */
2013 double scale = (r > 1.0 || g > 1.0 || b > 1.0) ? 1.0 : 255.0;
2014 int gl = a4_gl_mode;
2015 a4_gl_mode = 1;
2016 clear_to_color(screen, makecol((int)(r * scale), (int)(g * scale), (int)(b * scale)));
2017 a4_gl_mode = gl;
2018 _a4_present();
2019 return TRUE;
2020}
2021
2022int gfx_status(int action, int value) {
2023 if (action == SET) a4_gl_mode = (value == MODE_OGL);
2024 return a4_gl_mode ? MODE_OGL : MODE_STD;
2025}
2026
2027void fill_str(char* str, int c, size_t len) {
2028 if (str) memset(str, c, len);
2029}
2030
2031int a4_get_keyboard(char* keybuf, int* cur, int flags, int max) {
2032 (void)flags;
2033 if (!keybuf || !cur || max <= 0) return 0;
2034 if (*cur < 0) *cur = 0;
2035 if (*cur > max - 1) *cur = max - 1;
2036 int taken = 0;
2037 while (keypressed()) {
2038 int k = readkey();
2039 int ascii = k & 0xFF;
2040 taken++;
2041 if ((k >> 8) == KEY_BACKSPACE) {
2042 if (*cur > 0) (*cur)--;
2043 keybuf[*cur] = '\0';
2044 } else if (ascii >= 32 && ascii < 127 && *cur < max - 1) {
2045 keybuf[(*cur)++] = (char)ascii;
2046 keybuf[*cur] = '\0';
2047 }
2048 }
2049 return taken;
2050}
2051
2052/* Old nilorea INI driven menu. */
2053
2054typedef struct LEX_ITEM {
2058 int x;
2059 int y;
2060 int h_align; /* 0 left, 1 centre, 2 right: which part of the image is at x */
2061 int v_align; /* 0 top, 1 middle, 2 bottom: which side of y the image hangs */
2062} LEX_ITEM;
2063
2070
2071static int _a4_align(const char* v, int is_h) {
2072 if (!v) return 0;
2073 if (!strcasecmp(v, "center") || !strcasecmp(v, "centre") || !strcasecmp(v, "middle")) return 1;
2074 if (is_h && !strcasecmp(v, "right")) return 2;
2075 if (!is_h && !strcasecmp(v, "bottom")) return 2;
2076 return 0;
2077}
2078
2079/* A brighter copy of an item image, shown under the pointer. */
2081 BITMAP* b = create_bitmap(src->w, src->h);
2082 if (!b) return NULL;
2083 ALLEGRO_BITMAP* s = _a4_source(src, FALSE);
2084 _a4_target(b);
2085 al_clear_to_color(al_map_rgba(0, 0, 0, 0));
2087 al_draw_bitmap(s, 0, 0, 0);
2088 /* add 35% white where the image is not transparent */
2089 al_set_separate_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ONE, ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ONE);
2090 al_draw_tinted_bitmap(s, al_map_rgba_f(0.35f, 0.35f, 0.35f, 1.0f), 0, 0, 0);
2091 return b;
2092}
2093
2094LexMenu* lexLoadMenuFromFile(const char* file, const char* section) {
2095 if (!file || !section) return NULL;
2096 char* path = _a4_resolve(file);
2097 if (!path) return NULL;
2098 ALLEGRO_CONFIG* cfg = al_load_config_file(path);
2099 if (!cfg) {
2100 n_log(LOG_ERR, "could not load menu file %s", path);
2101 free(path);
2102 return NULL;
2103 }
2104 free(path);
2105 LexMenu* menu = NULL;
2106 Malloc(menu, LexMenu, 1);
2107 if (!menu) {
2108 al_destroy_config(cfg);
2109 return NULL;
2110 }
2111 const char* bg = al_get_config_value(cfg, section, "bg");
2112 if (bg) menu->bg = load_bitmap(bg, NULL);
2113
2114 int count = 0;
2115 for (;;) {
2116 char k[32];
2117 snprintf(k, sizeof(k), "item%d", count);
2118 if (!al_get_config_value(cfg, section, k)) break;
2119 count++;
2120 }
2121 if (count > 0) Malloc(menu->items, LEX_ITEM, (size_t)count);
2122 if (!menu->items) count = 0;
2123 for (int i = 0; i < count; i++) {
2124 char k[32];
2125 char sec[512];
2126 snprintf(k, sizeof(k), "item%d", i);
2127 snprintf(sec, sizeof(sec), "%s.%s", section, al_get_config_value(cfg, section, k));
2128 LEX_ITEM* it = &menu->items[i];
2129 const char* v = al_get_config_value(cfg, sec, "x");
2130 it->x = v ? atoi(v) : 0;
2131 v = al_get_config_value(cfg, sec, "y");
2132 it->y = v ? atoi(v) : 0;
2133 it->h_align = _a4_align(al_get_config_value(cfg, sec, "h_align"), TRUE);
2134 it->v_align = _a4_align(al_get_config_value(cfg, sec, "v_align"), FALSE);
2135 v = al_get_config_value(cfg, sec, "image");
2136 if (v) it->image = load_bitmap(v, NULL);
2137 if (it->image) it->over = _a4_highlight(it->image);
2138 v = al_get_config_value(cfg, sec, "sound");
2139 if (v) it->sound = load_sample(v);
2140 }
2141 menu->nb_items = count;
2142 al_destroy_config(cfg);
2143 return menu;
2144}
2145
2146BITMAP* lexMenuGetBitmap(LexMenu* menu, int item, int state) {
2147 if (!menu || item < 0 || item >= menu->nb_items) return NULL;
2148 return state == LEX_MENU_OVER ? menu->items[item].over : menu->items[item].image;
2149}
2150
2151/* Rectangle of an item in buf coordinates. */
2152static void _a4_item_rect(const LexMenu* menu, const BITMAP* buf, int i, float* x, float* y, float* w, float* h) {
2153 const LEX_ITEM* it = &menu->items[i];
2154 float sx = menu->bg ? (float)buf->w / (float)menu->bg->w : 1.0f;
2155 float sy = menu->bg ? (float)buf->h / (float)menu->bg->h : 1.0f;
2156 float iw = it->image ? (float)it->image->w : 0.0f;
2157 float ih = it->image ? (float)it->image->h : 0.0f;
2158 float ix = (float)it->x;
2159 float iy = (float)it->y;
2160 /* h_align says which part of the image is at x. v_align says on which
2161 * side of y the image hangs: "bottom" puts it below y, so y is its top
2162 * edge. That is how the menu files of the games that used the old
2163 * module place their buttons (Elementals' last button ends exactly on
2164 * its panel's bottom edge, The Fallings' first one clears the byline). */
2165 if (it->h_align == 1) ix -= iw / 2.0f;
2166 if (it->h_align == 2) ix -= iw;
2167 if (it->v_align == 1) iy -= ih / 2.0f;
2168 if (it->v_align == 0) iy -= ih;
2169 *x = ix * sx;
2170 *y = iy * sy;
2171 *w = iw * sx;
2172 *h = ih * sy;
2173}
2174
2175static int _a4_item_at(const LexMenu* menu, const BITMAP* buf, int mx, int my) {
2176 for (int i = 0; i < menu->nb_items; i++) {
2177 float x, y, w, h;
2178 _a4_item_rect(menu, buf, i, &x, &y, &w, &h);
2179 if ((float)mx >= x && (float)mx < x + w && (float)my >= y && (float)my < y + h) return i;
2180 }
2181 return -1;
2182}
2183
2184int lexMenuRun(LexMenu* menu, BITMAP* buf) {
2185 if (!menu || !buf || menu->nb_items <= 0) return -1;
2186 int cursor = a4_cursor_shown;
2187 show_mouse(buf);
2188 /* do not take the click or key that opened the menu as a choice */
2189 while ((mouse_b || key[KEY_ESC] || key[KEY_ENTER] || key[KEY_SPACE]) && !a4_close_requested) al_rest(0.01);
2190 clear_keybuf();
2191 int cx = 0, cy = 0;
2192 _a4_take_click(&cx, &cy);
2193 if (menu->selected < 0 || menu->selected >= menu->nb_items) menu->selected = 0;
2194 int chosen = -2;
2195 int last_mx = mouse_x, last_my = mouse_y;
2196 while (chosen == -2) {
2197 if (a4_close_requested) {
2198 chosen = -1;
2199 break;
2200 }
2201 int hover = _a4_item_at(menu, buf, mouse_x, mouse_y);
2202 if (hover >= 0 && (mouse_x != last_mx || mouse_y != last_my)) menu->selected = hover;
2203 last_mx = mouse_x;
2204 last_my = mouse_y;
2205 while (keypressed()) {
2206 int k = readkey() >> 8;
2207 if (k == KEY_UP) menu->selected = (menu->selected + menu->nb_items - 1) % menu->nb_items;
2208 if (k == KEY_DOWN) menu->selected = (menu->selected + 1) % menu->nb_items;
2209 if (k == KEY_ENTER || k == KEY_ENTER_PAD || k == KEY_SPACE) chosen = menu->selected;
2210 if (k == KEY_ESC) chosen = -1;
2211 }
2212 if (_a4_take_click(&cx, &cy)) {
2213 int clicked = _a4_item_at(menu, buf, cx, cy);
2214 if (clicked >= 0) {
2215 menu->selected = clicked;
2216 chosen = clicked;
2217 while ((mouse_b & 1) && !a4_close_requested) al_rest(0.01);
2218 }
2219 }
2220
2221 /* draw */
2222 if (menu->bg)
2223 stretch_blit(menu->bg, buf, 0, 0, menu->bg->w, menu->bg->h, 0, 0, buf->w, buf->h);
2224 else
2225 clear(buf);
2226 for (int i = 0; i < menu->nb_items; i++) {
2227 BITMAP* img = (i == menu->selected && menu->items[i].over) ? menu->items[i].over : menu->items[i].image;
2228 if (!img) continue;
2229 float x, y, w, h;
2230 _a4_item_rect(menu, buf, i, &x, &y, &w, &h);
2231 ALLEGRO_BITMAP* s = _a4_source(img, TRUE);
2232 _a4_target(buf);
2234 al_draw_scaled_bitmap(s, 0, 0, (float)img->w, (float)img->h, x, y, w, h, 0);
2235 }
2236 if (screen) {
2237 int gl = a4_gl_mode;
2238 a4_gl_mode = 1;
2239 stretch_blit(buf, screen, 0, 0, buf->w, buf->h, 0, 0, screen->w, screen->h);
2240 _a4_present();
2241 a4_gl_mode = gl;
2242 }
2243 al_rest(0.01);
2244 }
2245 if (chosen >= 0 && menu->items[chosen].sound) play_sample(menu->items[chosen].sound, 255, 128, 1000, 0);
2246 while (key[KEY_ESC] && !a4_close_requested) al_rest(0.01);
2247 clear_keybuf();
2248 if (!cursor) show_mouse(NULL);
2249 return chosen;
2250}
2251
2253 if (!menu) return NULL;
2254 for (int i = 0; i < menu->nb_items; i++) {
2255 destroy_bitmap(menu->items[i].image);
2256 destroy_bitmap(menu->items[i].over);
2257 destroy_sample(menu->items[i].sound);
2258 }
2259 Free(menu->items);
2260 destroy_bitmap(menu->bg);
2261 Free(menu);
2262 return NULL;
2263}
static int mode
static void type(N_GUI_CTX *gui, const char *s)
Type a whole string into whatever has focus.
static const char * row(N_GUI_TREE *tree, int index)
the text of a visible row, "" past the end
int w
width in pixels
int h
height in pixels
void * priv
private state of the compatibility layer
unsigned char ** line
always NULL: direct line access goes through bmp_write_line()
int makeacol32(int r, int g, int b, int a)
make a colour with alpha
#define KEY_RIGHT
key code
void allegro_gl_unset_allegro_mode(void)
no-op
#define KEY_A
key code
void stretch_blit(BITMAP *source, BITMAP *dest, int source_x, int source_y, int source_width, int source_height, int dest_x, int dest_y, int dest_width, int dest_height)
copy and scale a rectangle
int install_mouse(void)
start the input thread for the mouse
SAMPLE * load_sample(const char *filename)
load a WAV, OGG, FLAC or VOC file
uintptr_t bmp_write_line(BITMAP *bmp, int y)
lock the bitmap and return the address of line y, 32 bit ARGB pixels
FONT * load_ttf_font_mem(const char *filename, int points, int smooth)
same as load_ttf_font()
void draw_sprite_h_flip(BITMAP *bmp, BITMAP *sprite, int x, int y)
draw a sprite mirrored horizontally
#define MODE_STD
gfx_status() value: a non OpenGL mode
void destroy_bitmap(BITMAP *bmp)
free a bitmap
void set_trans_blender(int r, int g, int b, int a)
blend with a constant opacity a (0-255), r, g and b are ignored
void allegro_exit(void)
shut everything down
int is_memory_bitmap(BITMAP *bmp)
always FALSE: pixels are reached through bmp_write_line()
char allegro_error[256]
last error message, as in Allegro 4
BITMAP * create_video_bitmap(int width, int height)
same as create_bitmap()
int set_close_button_callback(void(*proc)(void))
call proc when the window close button is pressed
int a4_get_keyboard(char *keybuf, int *cur, int flags, int max)
read the typed keys into keybuf at *cur (printable ASCII, backspace erases), keeping it nul terminate...
int poll_mouse(void)
no-op, input is updated by a thread
uintptr_t bmp_read_line(BITMAP *bmp, int y)
lock the bitmap and return the address of line y, 32 bit ARGB pixels
#define KEY_BACKSPACE
key code
#define KEY_DOWN
key code
void bmp_unwrite_line(BITMAP *bmp)
no-op, the bitmap is unlocked when next drawn with the GPU
int install_int_ex(void(*proc)(void), long speed)
call proc every speed timer ticks, from the timer thread
BITMAP * create_bitmap(int width, int height)
create a w x h bitmap
int install_sound(int digi, int midi, const char *cfg_path)
start the audio, cfg_path is ignored
#define KEY_F1
key code
void destroy_sample(SAMPLE *spl)
free a sample, stopping it
int bitmap_mask_color(BITMAP *bmp)
the mask colour, magenta
int getr(int c)
red component
int readkey(void)
wait for a key: (scancode << 8) | ascii
void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color)
draw a line
volatile char key[227]
TRUE while the key is held.
int save_bitmap(const char *filename, BITMAP *bmp, const RGB *pal)
save a bitmap
void draw_sprite_vh_flip(BITMAP *bmp, BITMAP *sprite, int x, int y)
draw a sprite mirrored both ways
void textout_right_ex(BITMAP *bmp, const FONT *f, const char *s, int x, int y, int color, int bg)
draw a string ending at x
#define GFX_AUTODETECT_FULLSCREEN
fullscreen
int ustricmp(const char *s1, const char *s2)
case insensitive string compare
FONT * font
the default 8x8 font, valid after allegro_init()
void clear_keybuf(void)
empty the readkey() buffer
void set_uformat(int type)
no-op, kept for source compatibility
void textprintf_ex(BITMAP *bmp, const FONT *f, int x, int y, int color, int bg, const char *format,...)
draw a formatted string
void drawing_mode(int mode, BITMAP *pattern, int x_anchor, int y_anchor)
set the drawing mode of primitives, pattern arguments are ignored
#define TIMERS_PER_SECOND
ticks per second of install_int_ex() speeds
int makecol32(int r, int g, int b)
make an opaque colour
#define itofix(x)
int to fixed
#define DIGI_NONE
no digital sound
volatile int mouse_x
mouse x in screen coordinates
#define KEY_TAB
key code
#define DRAW_MODE_SOLID
drawing mode: plain copy
int poll_keyboard(void)
no-op, input is updated by a thread
#define KEY_0
key code
volatile int key_shifts
modifier flags, unused
void ellipse(BITMAP *bmp, int x, int y, int rx, int ry, int color)
draw an ellipse outline
void rotate_scaled_sprite(BITMAP *bmp, BITMAP *sprite, int x, int y, fixed angle, fixed scale)
rotate and scale a sprite around its centre
void destroy_midi(MIDI *midi)
free a MIDI song, stopping it if it plays
void remove_int(void(*proc)(void))
stop calling proc
int makeacol(int r, int g, int b, int a)
make a colour with alpha
#define SET
gfx_status() action: set the status (same value as n_common.h's)
void solid_mode(void)
same as drawing_mode(DRAW_MODE_SOLID, ...)
BITMAP * screen
the screen, valid after set_gfx_mode()
void textout_centre_ex(BITMAP *bmp, const FONT *f, const char *s, int x, int y, int color, int bg)
draw a string centred on x
void set_alpha_blender(void)
blend with the alpha channel of the source colour or sprite
#define KEY_ALTGR
key code
#define GFX_OPENGL_WINDOWED
AllegroGL windowed: screen is shown on allegro_gl_flip() only.
#define DRAW_MODE_TRANS
drawing mode: blend with the current blender
#define KEY_MAX
number of entries in key[]
void masked_stretch_blit(BITMAP *source, BITMAP *dest, int source_x, int source_y, int source_w, int source_h, int dest_x, int dest_y, int dest_w, int dest_h)
copy and scale a rectangle, skipping mask colour pixels
void get_mouse_mickeys(int *mickeyx, int *mickeyy)
movement since the last call
#define GFX_OPENGL
AllegroGL, windowed.
int install_keyboard(void)
start the input thread for the keyboard
BITMAP * create_bitmap_ex(int depth, int width, int height)
same as create_bitmap(), depth is ignored
void vline(BITMAP *bmp, int x, int y1, int y2, int color)
draw a vertical line
void allegro_message(const char *format,...)
show a message: a box over the screen, or stderr without a display
int set_gfx_mode(int card, int w, int h, int v_w, int v_h)
open a display of w x h pixels, or close it with GFX_TEXT
void position_mouse(int x, int y)
move the mouse pointer
void set_volume(int digi_volume, int midi_volume)
set the volumes (0-255), a negative value leaves one unchanged
int allegro_init(void)
init the layer and Allegro 5
int install_timer(void)
no-op, timers are always available
void circle(BITMAP *bmp, int x, int y, int radius, int color)
draw a circle outline
int lexMenuRun(LexMenu *menu, BITMAP *buf)
run the menu, drawing into buf: the chosen item index, -1 on escape
void adjust_sample(const SAMPLE *spl, int vol, int pan, int freq, int loop)
change the settings of the playing instances of a sample
void clear_bitmap(BITMAP *bmp)
clear to black
void set_window_title(const char *name)
set the window title
int makecol(int r, int g, int b)
make an opaque colour
int play_sample(const SAMPLE *spl, int vol, int pan, int freq, int loop)
play a sample: vol and pan 0-255, freq 1000 = normal speed
void blit(BITMAP *source, BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height)
copy a rectangle
void allegro_gl_set_allegro_mode(void)
no-op
int bitmap_color_depth(BITMAP *bmp)
always 32
#define ALLEGTTF_NOSMOOTH
no anti aliasing
void masked_blit(BITMAP *source, BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height)
copy a rectangle, skipping mask colour pixels
#define KEY_PGDN
key code
#define fixtof(x)
fixed to double
#define KEY_ESC
key code
void ellipsefill(BITMAP *bmp, int x, int y, int rx, int ry, int color)
draw a filled ellipse
int get_color_depth(void)
return the color depth (always 32)
void stop_sample(const SAMPLE *spl)
stop every playing instance of a sample
#define KEY_SPACE
key code
int install_int(void(*proc)(void), int speed)
call proc every speed milliseconds, from the timer thread
void textprintf_centre_ex(BITMAP *bmp, const FONT *f, int x, int y, int color, int bg, const char *format,...)
draw a formatted string centred on x
int32_t fixed
16.16 fixed point number
int getb(int c)
blue component
#define KEY_END
key code
int gfx_mode(int mode, int w, int h, int v_w, int v_h, int depth, double r, double g, double b, double a)
set the color depth and the graphics mode in one go, clearing the screen to (r,g,b),...
#define GFX_TEXT
leave graphics mode
void destroy_font(FONT *f)
free a font loaded with load_ttf_font()
void bmp_select(BITMAP *bmp)
no-op
LexMenu * lexMenuDestroy(LexMenu *menu)
free a menu, returns NULL
volatile int mouse_b
button flags: 1 left, 2 right, 4 middle
void remove_sound(void)
stop the audio
void midi_pause(void)
pause the MIDI song
void pivot_scaled_sprite(BITMAP *bmp, BITMAP *sprite, int x, int y, int cx, int cy, fixed angle, fixed scale)
rotate and scale a sprite around (cx,cy) in the sprite, drawn at (x,y)
int install_allegro_gl(void)
no-op, the layer always uses Allegro 5's own driver
MIDI * load_midi(const char *filename)
load a MIDI file
int getg(int c)
green component
void fill_str(char *str, int c, size_t len)
fill len bytes of str with c
void draw_sprite(BITMAP *bmp, BITMAP *sprite, int x, int y)
draw a sprite
void rotate_sprite(BITMAP *bmp, BITMAP *sprite, int x, int y, fixed angle)
rotate a sprite around its centre, angle in 256ths of a turn, clockwise
#define KEY_INSERT
key code
int alert(const char *s1, const char *s2, const char *s3, const char *b1, const char *b2, int c1, int c2)
a box with up to three lines and one or two buttons (b2 may be NULL), chosen with the mouse,...
#define KEY_ENTER_PAD
key code
volatile int mouse_y
mouse y in screen coordinates
void midi_resume(void)
resume the MIDI song
void textprintf_right_ex(BITMAP *bmp, const FONT *f, int x, int y, int color, int bg, const char *format,...)
draw a formatted string ending at x
int keypressed(void)
TRUE if a key is waiting in the readkey() buffer.
uint32_t _a4_pixel_from_color(int color)
store a colour as it is kept in a bitmap (opaque when its alpha byte is 0)
BITMAP * load_bitmap(const char *filename, RGB *pal)
load a BMP, PCX, TGA, PNG or JPG file, pal is ignored
void stretch_sprite(BITMAP *bmp, BITMAP *sprite, int x, int y, int w, int h)
draw a sprite scaled to w x h
void putpixel(BITMAP *bmp, int x, int y, int color)
write a pixel
int gfx_status(int action, int value)
with GET, MODE_OGL when the current mode is an AllegroGL one, else MODE_STD
#define KEY_ENTER
key code
void set_color_depth(int depth)
set the color depth, only 32 is honoured
void clear_to_color(BITMAP *bmp, int color)
clear to a colour
void rectfill(BITMAP *bmp, int x1, int y1, int x2, int y2, int color)
draw a filled rectangle
#define KEY_PGUP
key code
void hline(BITMAP *bmp, int x1, int y, int x2, int color)
draw a horizontal line
#define GFX_DIRECTX
DirectX fullscreen, mapped to fullscreen.
void triangle(BITMAP *bmp, int x1, int y1, int x2, int y2, int x3, int y3, int color)
draw a filled triangle
FONT * load_ttf_font(const char *filename, int points, int smooth)
load a TrueType font at the given pixel size
#define KEY_DEL
key code
void pivot_sprite(BITMAP *bmp, BITMAP *sprite, int x, int y, int cx, int cy, fixed angle)
rotate a sprite around (cx,cy) in the sprite, drawn at (x,y)
#define MODE_OGL
gfx_status() value: an AllegroGL mode
int text_height(const FONT *f)
height of the font in pixels
void rest(unsigned int ms)
sleep ms milliseconds
int text_length(const FONT *f, const char *str)
width of str in pixels
int geta(int c)
alpha component, 255 for an opaque colour
void circlefill(BITMAP *bmp, int x, int y, int radius, int color)
draw a filled circle
void textout_ex(BITMAP *bmp, const FONT *f, const char *s, int x, int y, int color, int bg)
draw a string
LexMenu * lexLoadMenuFromFile(const char *file, const char *section)
load menu section from INI file file, NULL on error
#define ALLEGRO_ERROR_SIZE
size of the allegro_error buffer
Definition n_allegro_4.h:80
#define MSEC_TO_TIMER(x)
convert milliseconds to timer ticks
void draw_sprite_v_flip(BITMAP *bmp, BITMAP *sprite, int x, int y)
draw a sprite mirrored vertically
volatile int mouse_z
mouse wheel position
void draw_trans_sprite(BITMAP *bmp, BITMAP *sprite, int x, int y)
draw a sprite with the current blender (set_alpha_blender / set_trans_blender)
#define KEY_LSHIFT
key code
void show_mouse(BITMAP *bmp)
show the system cursor (bmp != NULL) or hide it (NULL)
void clear(BITMAP *bmp)
clear to black
int getpixel(BITMAP *bmp, int x, int y)
read a pixel, -1 outside the bitmap
#define KEY_LEFT
key code
void allegro_gl_flip(void)
show the screen bitmap
#define KEY_HOME
key code
void stop_midi(void)
stop the MIDI song
BITMAP * lexMenuGetBitmap(LexMenu *menu, int item, int state)
the image of item item in state state, NULL if there is none
#define KEY_UP
key code
#define LEX_MENU_OVER
item state for lexMenuGetBitmap(): under the pointer
void rect(BITMAP *bmp, int x1, int y1, int x2, int y2, int color)
draw a rectangle outline
#define GFX_OPENGL_FULLSCREEN
AllegroGL fullscreen: screen is shown on allegro_gl_flip() only.
int play_midi(MIDI *midi, int loop)
play a MIDI song, replacing the current one, NULL stops
Allegro 4 bitmap.
palette entry, unused in 32 bit mode
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
Definition n_common.h:204
#define Free(__ptr)
Free Handler to get errors.
Definition n_common.h:263
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#define LOG_ERR
error conditions
Definition n_log.h:100
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
N_MODULE * n_module_open(const char *path)
Open a shared library.
Definition n_module.c:68
int n_module_close(N_MODULE **module)
Close a library and free its handle.
Definition n_module.c:136
void * n_module_symbol(N_MODULE *module, const char *name)
Find a symbol in an opened library.
Definition n_module.c:110
static int _a4_target(BITMAP *b)
static ALLEGRO_THREAD * a4_timer_thread
static float _a4_pan(int pan)
static ALLEGRO_VERTEX a4_batch[6144]
static int a4_screen_dirty
static void _a4_box(BITMAP *b, float l, float t, float r, float bt, int color)
void *(* fs_set_log_function_f)(int, void *, void *)
#define A4_KEYBUF_SIZE
int used[16]
void *(* fs_new_player_f)(void *)
static int a4_click_pending
static int a4_cursor_shown
static int a4_mickey_y
#define A4_MIDI_RATE
static int _a4_item_at(const LexMenu *menu, const BITMAP *buf, int mx, int my)
static int a4_midi_volume
static ALLEGRO_COLOR _a4_prim_color(int color)
#define A4_BLEND_FADE
static void _a4_midi_shutdown(void)
ALLEGRO_BITMAP * masked
Definition n_allegro_4.c:88
double period
int(* fs_player_int_f)(void *)
static char * _a4_resolve(const char *path)
#define A4P(b)
static int a4_keybuf[64]
static BITMAP * a4_batch_target
static BITMAP * _a4_wrap(ALLEGRO_BITMAP *abmp)
static volatile int a4_close_requested
static ALLEGRO_COLOR _a4_rgba(int color, int alpha)
static int a4_mickey_x
int(* fs_settings_setstr_f)(void *, const char *, const char *)
LEX_ITEM * items
static struct @346262156126311217342151060122117020361320217156 a4_fs
int is_screen
Definition n_allegro_4.c:94
static int a4_sound_ok
static ALLEGRO_EVENT_QUEUE * a4_input_queue
char * path
static int a4_click_x
static void * _a4_timer_proc(ALLEGRO_THREAD *thr, void *arg)
static ALLEGRO_DISPLAY * a4_display
static void _a4_vertex(ALLEGRO_VERTEX *v, float x, float y, ALLEGRO_COLOR c)
static void _a4_quad(BITMAP *b, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, int color)
static void _a4_text(BITMAP *bmp, const FONT *f, const char *s, int x, int y, int color, int bg, int align)
BITMAP * bg
int(* fs_sfload_f)(void *, const char *, int)
static void _a4_unlock(BITMAP *b)
static int a4_inited
static ALLEGRO_MUTEX * a4_input_mutex
static void _a4_destroy_display(void)
static void _a4_done(const BITMAP *b)
static int a4_keybuf_start
static int _a4_take_click(int *x, int *y)
static int a4_batch_n
static void _a4_blend_alpha(void)
static ALLEGRO_MUTEX * a4_timer_mutex
BITMAP * over
static float a4_scale
int(* fs_player_set_loop_f)(void *, int)
#define A4_BATCH_MAX
static A4_TIMER a4_timers[16]
int masked_dirty
Definition n_allegro_4.c:90
static void _a4_midi_stop_locked(void)
#define A4_BLEND_ALPHA
static void _a4_rotated(BITMAP *bmp, BITMAP *sprite, float x, float y, float cx, float cy, fixed angle, fixed scale)
static void(* a4_close_cb)(void)
static int a4_trans_alpha
ALLEGRO_LOCKED_REGION * lock
Definition n_allegro_4.c:92
static int a4_batch_blend
static ALLEGRO_FONT * _a4_font(const FONT *f)
static ALLEGRO_BITMAP * _a4_source(BITMAP *b, int masked)
static int a4_gl_mode
static ALLEGRO_LOCKED_REGION * _a4_lock(BITMAP *b)
static int a4_keybuf_count
static int a4_click_y
void(* fs_delete_f)(void *)
static void _a4_sprite(BITMAP *bmp, BITMAP *sprite, int x, int y, int flags)
#define A4_TEXTPRINTF(align)
int(* fs_write_float_f)(void *, int, void *, int, int, void *, int, int)
static float a4_off_y
static void * _a4_midi_proc(ALLEGRO_THREAD *thr, void *arg)
ALLEGRO_FONT * f
Definition n_allegro_4.c:99
ALLEGRO_SAMPLE_ID ids[16]
void *(* fs_new_synth_f)(void *)
BITMAP * image
static void _a4_blend_copy(void)
SAMPLE * sound
static float a4_off_x
static void _a4_present(void)
static void _a4_set_blend(int blend)
static const char * _a4_soundfont(void)
double next
#define A4_SYM(field, name)
ALLEGRO_BITMAP * bmp
Definition n_allegro_4.c:86
#define A4_MASK_COLOR
Definition n_allegro_4.c:81
#define A4_MIDI_FRAG
static float _a4_gain(int vol)
#define A4_MAX_TIMERS
static void _a4_draw_region(BITMAP *source, BITMAP *dest, int masked, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh, int flags)
static BITMAP * _a4_highlight(BITMAP *src)
static int _a4_midi_init(void)
static void _a4_item_rect(const LexMenu *menu, const BITMAP *buf, int i, float *x, float *y, float *w, float *h)
static ALLEGRO_COLOR _a4_prim(int color, int *blend)
static ALLEGRO_THREAD * a4_input_thread
static int _a4_fluidsynth_sym(N_MODULE *lib, const char *name, void *fptr, size_t fsize)
void *(* fs_new_settings_f)(void)
static void _a4_compute_scale(void)
#define A4_SAMPLE_IDS
static N_MODULE * _a4_fluidsynth_open(void)
int(* fs_settings_setnum_f)(void *, const char *, double)
#define A4_BLEND_COPY
static int _a4_align(const char *v, int is_h)
static int a4_draw_mode
static void * _a4_input_proc(ALLEGRO_THREAD *thr, void *arg)
static int _a4_color_from_pixel(uint32_t p)
static int a4_digi_volume
static void _a4_flush(void)
static void _a4_input_handle(const ALLEGRO_EVENT *ev)
static char * _a4_strndup(const char *s, size_t n)
int(* fs_player_add_f)(void *, const char *)
static ALLEGRO_VERTEX * _a4_reserve(BITMAP *b, int blend, int n)
static int a4_alpha_blender
void(* proc)(void)
ALLEGRO_SAMPLE * spl
a font: the built in one, or a TTF font loaded with load_ttf_font()
Definition n_allegro_4.c:98
an INI driven menu
a MIDI song
a sound sample
Allegro 4 compatibility layer on top of Allegro 5.
Common headers and low-level functions & define.
Generic log system.
an opened shared library
Definition n_module.c:40
Load a shared library at runtime and find its symbols.