Cleanup
[carla.git] / source / modules / ysfx / sources / ysfx.cpp
blob6c645ecbca4dc925be9fcfb35d8d6ffb7fbc53e8
1 // Copyright 2021 Jean Pierre Cimalando
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 // SPDX-License-Identifier: Apache-2.0
18 #include "ysfx.hpp"
19 #include "ysfx_config.hpp"
20 #include "ysfx_eel_utils.hpp"
21 #include <type_traits>
22 #include <algorithm>
23 #include <functional>
24 #include <deque>
25 #include <set>
26 #include <new>
27 #include <stdexcept>
28 #include <cstring>
29 #include <cassert>
31 static_assert(std::is_same<EEL_F, ysfx_real>::value,
32 "ysfx_real is incorrectly defined");
34 enum {
35 ysfx_max_file_handles = 64, // change if it needs more
38 //------------------------------------------------------------------------------
39 static thread_local ysfx_thread_id_t ysfx_thread_id;
41 ysfx_thread_id_t ysfx_get_thread_id()
43 return ysfx_thread_id;
46 void ysfx_set_thread_id(ysfx_thread_id_t id)
48 ysfx_thread_id = id;
51 //------------------------------------------------------------------------------
52 struct ysfx_api_initializer {
53 private:
54 ysfx_api_initializer();
55 ~ysfx_api_initializer();
56 public:
57 static void init_once();
60 ysfx_api_initializer::ysfx_api_initializer()
62 if (NSEEL_init() != 0)
63 throw std::runtime_error("NSEEL_init");
65 ysfx_api_init_eel();
66 ysfx_api_init_reaper();
67 ysfx_api_init_file();
68 ysfx_api_init_gfx();
71 ysfx_api_initializer::~ysfx_api_initializer()
73 NSEEL_quit();
76 void ysfx_api_initializer::init_once()
78 static ysfx_api_initializer init;
81 //------------------------------------------------------------------------------
82 ysfx_t *ysfx_new(ysfx_config_t *config)
84 ysfx_u fx{new ysfx_t};
86 ysfx_config_add_ref(config);
87 fx->config.reset(config);
89 fx->string_ctx.reset(ysfx_eel_string_context_new());
91 ysfx_api_initializer::init_once();
93 NSEEL_VMCTX vm = NSEEL_VM_alloc();
94 if (!vm)
95 throw std::bad_alloc();
96 fx->vm.reset(vm);
98 NSEEL_VM_SetCustomFuncThis(vm, fx.get());
100 ysfx_eel_string_initvm(vm);
102 #if !defined(YSFX_NO_GFX)
103 fx->gfx.state.reset(ysfx_gfx_state_new(fx.get()));
104 #endif
106 auto var_resolver = [](void *userdata, const char *name) -> EEL_F * {
107 ysfx_t *fx = (ysfx_t *)userdata;
108 auto it = fx->source.slider_alias.find(name);
109 if (it != fx->source.slider_alias.end())
110 return fx->var.slider[it->second];
111 return nullptr;
113 NSEEL_VM_set_var_resolver(vm, var_resolver, fx.get());
115 for (uint32_t i = 0; i < ysfx_max_channels; ++i) {
116 std::string name = "spl" + std::to_string(i);
117 EEL_F *var = NSEEL_VM_regvar(vm, name.c_str());
118 *(fx->var.spl[i] = var) = 0;
120 for (uint32_t i = 0; i < ysfx_max_sliders; ++i) {
121 std::string name = "slider" + std::to_string(i + 1);
122 EEL_F *var = NSEEL_VM_regvar(vm, name.c_str());
123 *(fx->var.slider[i] = var) = 0;
124 fx->slider_of_var[var] = i;
127 #define AUTOVAR(name, value) *(fx->var.name = NSEEL_VM_regvar(vm, #name)) = (value)
128 AUTOVAR(srate, fx->sample_rate);
129 AUTOVAR(num_ch, fx->valid_input_channels);
130 AUTOVAR(samplesblock, fx->block_size);
131 AUTOVAR(trigger, 0);
132 AUTOVAR(tempo, 120);
133 AUTOVAR(play_state, 1);
134 AUTOVAR(play_position, 0);
135 AUTOVAR(beat_position, 0);
136 AUTOVAR(ts_num, 0);
137 AUTOVAR(ts_denom, 4);
138 AUTOVAR(ext_noinit, 0);
139 AUTOVAR(ext_nodenorm, 0);
140 AUTOVAR(ext_midi_bus, 0);
141 AUTOVAR(midi_bus, 0);
142 AUTOVAR(pdc_delay, 0);
143 AUTOVAR(pdc_bot_ch, 0);
144 AUTOVAR(pdc_top_ch, 0);
145 AUTOVAR(pdc_midi, 0);
146 // gfx variables
147 AUTOVAR(gfx_r, 0);
148 AUTOVAR(gfx_g, 0);
149 AUTOVAR(gfx_b, 0);
150 AUTOVAR(gfx_a, 0);
151 AUTOVAR(gfx_a2, 0);
152 AUTOVAR(gfx_w, 0);
153 AUTOVAR(gfx_h, 0);
154 AUTOVAR(gfx_x, 0);
155 AUTOVAR(gfx_y, 0);
156 AUTOVAR(gfx_mode, 0);
157 AUTOVAR(gfx_clear, 0);
158 AUTOVAR(gfx_texth, 0);
159 AUTOVAR(gfx_dest, 0);
160 AUTOVAR(gfx_ext_retina, 0);
161 AUTOVAR(mouse_x, 0);
162 AUTOVAR(mouse_y, 0);
163 AUTOVAR(mouse_cap, 0);
164 AUTOVAR(mouse_wheel, 0);
165 AUTOVAR(mouse_hwheel, 0);
166 #undef AUTOVAR
168 fx->midi.in.reset(new ysfx_midi_buffer_t);
169 fx->midi.out.reset(new ysfx_midi_buffer_t);
170 ysfx_set_midi_capacity(fx.get(), 1024, true);
172 fx->file.list.reserve(16);
173 fx->file.list.emplace_back(new ysfx_serializer_t(fx->vm.get()));
175 return fx.release();
178 void ysfx_free(ysfx_t *fx)
180 if (!fx)
181 return;
183 if (fx->ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1)
184 delete fx;
187 void ysfx_add_ref(ysfx_t *fx)
189 fx->ref_count.fetch_add(1, std::memory_order_relaxed);
192 ysfx_config_t *ysfx_get_config(ysfx_t *fx)
194 return fx->config.get();
197 bool ysfx_load_file(ysfx_t *fx, const char *filepath, uint32_t loadopts)
199 ysfx_unload(fx);
201 //--------------------------------------------------------------------------
202 // failure guard
204 auto fail_guard = ysfx::defer([fx]() { ysfx_unload_source(fx); });
206 //--------------------------------------------------------------------------
207 // load the main file
209 ysfx::file_uid main_uid;
212 ysfx_source_unit_u main{new ysfx_source_unit_t};
214 ysfx::FILE_u stream{ysfx::fopen_utf8(filepath, "rb")};
215 if (!stream || !ysfx::get_stream_file_uid(stream.get(), main_uid)) {
216 ysfx_logf(*fx->config, ysfx_log_error, "%s: cannot open file for reading", ysfx::path_file_name(filepath).c_str());
217 return false;
220 ysfx::stdio_text_reader reader(stream.get());
222 ysfx_parse_error error;
223 if (!ysfx_parse_toplevel(reader, main->toplevel, &error)) {
224 ysfx_logf(*fx->config, ysfx_log_error, "%s:%u: %s", ysfx::path_file_name(filepath).c_str(), error.line + 1, error.message.c_str());
225 return false;
227 ysfx_parse_header(main->toplevel.header.get(), main->header);
229 // validity check
230 if (main->header.desc.empty()) {
231 ysfx_logf(*fx->config, ysfx_log_warning, "%s: the required `desc` field is missing", ysfx::path_file_name(filepath).c_str());
232 main->header.desc = ysfx::path_file_name(filepath);
235 if (loadopts & ysfx_load_ignoring_imports)
236 main->header.imports.clear();
238 // if no pins are specified and we have @sample, the default is stereo
239 if (main->toplevel.sample && !main->header.explicit_pins &&
240 main->header.in_pins.empty() && main->header.out_pins.empty())
242 main->header.in_pins = {"JS input 1", "JS input 2"};
243 main->header.out_pins = {"JS output 1", "JS output 2"};
246 // register variables aliased to sliders
247 for (uint32_t i = 0; i < ysfx_max_sliders; ++i) {
248 if (main->header.sliders[i].exists) {
249 if (!main->header.sliders[i].var.empty())
250 fx->source.slider_alias.insert({main->header.sliders[i].var, i});
254 fx->source.main = std::move(main);
255 fx->source.main_file_path.assign(filepath);
257 // find the bank file, if present
258 ysfx::case_resolve(
259 ysfx::path_directory(filepath).c_str(),
260 (ysfx::path_file_name(filepath) + ".rpl").c_str(),
261 fx->source.bank_path);
263 // fill the file enums with the contents of directories
264 ysfx_fill_file_enums(fx);
266 // find incorrect enums and fix them
267 ysfx_fix_invalid_enums(fx);
269 // set the initial mask of visible sliders
270 ysfx_update_slider_visibility_mask(fx);
273 //--------------------------------------------------------------------------
274 // load the imports
276 // we load the imports recursively using post-order
278 static constexpr uint32_t max_import_level = 32;
279 std::set<ysfx::file_uid> seen;
281 std::function<bool(const std::string &, const std::string &, uint32_t)> do_next_import =
282 [fx, &seen, &do_next_import]
283 (const std::string &name, const std::string &origin, uint32_t level) -> bool
285 if (level >= max_import_level) {
286 ysfx_logf(*fx->config, ysfx_log_error, "%s: %s", ysfx::path_file_name(origin.c_str()).c_str(), "too many import levels");
287 return false;
290 std::string imported_path = ysfx_resolve_import_path(fx, name, origin);
291 if (imported_path.empty()) {
292 ysfx_logf(*fx->config, ysfx_log_error, "%s: cannot find import: %s", ysfx::path_file_name(origin.c_str()).c_str(), name.c_str());
293 return false;
296 ysfx::file_uid imported_uid;
297 ysfx::FILE_u stream{ysfx::fopen_utf8(imported_path.c_str(), "rb")};
298 if (!stream || !ysfx::get_stream_file_uid(stream.get(), imported_uid)) {
299 ysfx_logf(*fx->config, ysfx_log_error, "%s: cannot open file for reading", ysfx::path_file_name(imported_path.c_str()).c_str());
300 return false;
303 // this file was already visited, skip
304 if (!seen.insert(imported_uid).second)
305 return true;
307 // parse it
308 ysfx_source_unit_u unit{new ysfx_source_unit_t};
309 ysfx::stdio_text_reader reader(stream.get());
311 ysfx_parse_error error;
312 if (!ysfx_parse_toplevel(reader, unit->toplevel, &error)) {
313 ysfx_logf(*fx->config, ysfx_log_error, "%s:%u: %s", ysfx::path_file_name(imported_path.c_str()).c_str(), error.line + 1, error.message.c_str());
314 return false;
316 ysfx_parse_header(unit->toplevel.header.get(), unit->header);
318 // process the imported dependencies, *first*
319 for (const std::string &name : unit->header.imports) {
320 if (!do_next_import(name, imported_path.c_str(), level + 1))
321 return false;
324 // add it to the import sources, *second*
325 fx->source.imports.push_back(std::move(unit));
327 return true;
330 for (const std::string &name : fx->source.main->header.imports) {
331 if (!do_next_import(name, filepath, 0))
332 return false;
335 //--------------------------------------------------------------------------
336 // initialize the sliders to defaults
338 for (uint32_t i = 0; i < ysfx_max_sliders; ++i)
339 *fx->var.slider[i] = fx->source.main->header.sliders[i].def;
341 //--------------------------------------------------------------------------
343 fail_guard.disarm();
344 return true;
347 bool ysfx_compile(ysfx_t *fx, uint32_t compileopts)
349 ysfx_unload_code(fx);
351 if (!fx->source.main) {
352 ysfx_logf(*fx->config, ysfx_log_error, "???: no source is loaded, cannot compile");
353 return false;
356 //--------------------------------------------------------------------------
357 // failure guard
359 auto fail_guard = ysfx::defer([fx]() { ysfx_unload_code(fx); });
361 //--------------------------------------------------------------------------
362 // configure VM
364 NSEEL_VMCTX vm = fx->vm.get();
367 uint32_t maxmem = fx->source.main->header.options.maxmem;
368 if (maxmem == 0)
369 maxmem = 8 * 1024 * 1024;
370 if (maxmem > 32 * 1024 * 1024)
371 maxmem = 32 * 1024 * 1024;
373 NSEEL_VM_setramsize(vm, (int)maxmem);
376 //--------------------------------------------------------------------------
377 // compile
379 auto compile_section =
380 [fx](ysfx_section_t *section, const char *name, NSEEL_CODEHANDLE_u &dest) -> bool
382 NSEEL_VMCTX vm = fx->vm.get();
383 if (section->text.empty()) {
384 // NOTE: check for empty source, which would return null code
385 dest.reset();
386 return true;
388 NSEEL_CODEHANDLE_u code{NSEEL_code_compile_ex(vm, section->text.c_str(), section->line_offset, NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS)};
389 if (!code) {
390 ysfx_logf(*fx->config, ysfx_log_error, "%s: %s", name, NSEEL_code_getcodeerror(vm));
391 return false;
393 dest = std::move(code);
394 return true;
397 // compile the multiple @init sections, imports first
399 std::vector<ysfx_section_t *> secs;
400 secs.reserve(fx->source.imports.size() + 1);
402 // collect init sections: imports first, main second
403 for (size_t i = 0; i < fx->source.imports.size(); ++i)
404 secs.push_back(fx->source.imports[i]->toplevel.init.get());
405 secs.push_back(fx->source.main->toplevel.init.get());
407 for (ysfx_section_t *sec : secs) {
408 NSEEL_CODEHANDLE_u code;
409 if (sec && !compile_section(sec, "@init", code))
410 return false;
411 fx->code.init.push_back(std::move(code));
415 // compile the other sections, single
416 // a non-@init section is searched in the main file first;
417 // if not found, it's inherited from the first import which has it.
418 ysfx_section_t *slider = ysfx_search_section(fx, ysfx_section_slider);
419 ysfx_section_t *block = ysfx_search_section(fx, ysfx_section_block);
420 ysfx_section_t *sample = ysfx_search_section(fx, ysfx_section_sample);
421 ysfx_section_t *gfx = nullptr;
422 ysfx_section_t *serialize = nullptr;
423 if ((compileopts & ysfx_compile_no_gfx) == 0)
424 gfx = ysfx_search_section(fx, ysfx_section_gfx);
425 if ((compileopts & ysfx_compile_no_serialize) == 0)
426 serialize = ysfx_search_section(fx, ysfx_section_serialize);
428 if (slider && !compile_section(slider, "@slider", fx->code.slider))
429 return false;
430 if (block && !compile_section(block, "@block", fx->code.block))
431 return false;
432 if (sample && !compile_section(sample, "@sample", fx->code.sample))
433 return false;
434 if (gfx && !compile_section(gfx, "@gfx", fx->code.gfx))
435 return false;
436 if (serialize && !compile_section(serialize, "@serialize", fx->code.serialize))
437 return false;
439 fx->code.compiled = true;
440 fx->is_freshly_compiled = true;
441 fx->must_compute_init = true;
444 ysfx_eel_string_context_update_named_vars(fx->string_ctx.get(), vm);
446 fail_guard.disarm();
447 return true;
450 bool ysfx_is_compiled(ysfx_t *fx)
452 return fx->code.compiled;
455 void ysfx_unload_source(ysfx_t *fx)
457 fx->source = {};
460 void ysfx_unload_code(ysfx_t *fx)
462 #if !defined(YSFX_NO_GFX)
463 // get rid of gfx first, to prevent a UI thread from trying
464 // to access VM and invoke code
466 std::lock_guard<ysfx::mutex> lock{fx->gfx.mutex};
467 fx->gfx.ready = false;
468 fx->gfx.wants_retina = false;
469 fx->gfx.must_init.store(false);
471 #endif
473 fx->code = {};
475 fx->is_freshly_compiled = false;
476 fx->must_compute_init = false;
477 fx->must_compute_slider = false;
479 NSEEL_VMCTX vm = fx->vm.get();
480 NSEEL_code_compile_ex(vm, nullptr, 0, NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS_RESET);
481 NSEEL_VM_remove_unused_vars(vm);
482 NSEEL_VM_remove_all_nonreg_vars(vm);
483 NSEEL_VM_freeRAM(vm);
486 void ysfx_unload(ysfx_t *fx)
488 ysfx_unload_code(fx);
489 ysfx_unload_source(fx);
492 bool ysfx_is_loaded(ysfx_t *fx)
494 return fx->source.main != nullptr;
497 void ysfx_fill_file_enums(ysfx_t *fx)
499 if (fx->config->data_root.empty())
500 return;
502 for (uint32_t i = 0; i < ysfx_max_sliders; ++i) {
503 ysfx_slider_t &slider = fx->source.main->header.sliders[i];
504 if (slider.path.empty())
505 continue;
507 std::string dirpath = ysfx::path_ensure_final_separator((fx->config->data_root + slider.path).c_str());
508 ysfx::string_list entries = ysfx::list_directory(dirpath.c_str());
510 for (const std::string &filename : entries) {
511 if (!filename.empty() && ysfx::is_path_separator(filename.back()))
512 continue;
514 std::string filepath = dirpath + filename;
516 ysfx_file_type_t ftype = ysfx_detect_file_type(fx, filepath.c_str(), nullptr);
517 if (ftype == ysfx_file_type_none)
518 continue;
520 slider.enum_names.push_back(std::move(filename));
523 if (!slider.enum_names.empty())
524 slider.max = (EEL_F)(slider.enum_names.size() - 1);
528 void ysfx_fix_invalid_enums(ysfx_t *fx)
530 //NOTE: regardless of the range of enum sliders in source, it is <0,N-1,1>
531 // if there is a mismatch, correct and output a warning
533 for (uint32_t i = 0; i < ysfx_max_sliders; ++i) {
534 ysfx_slider_t &slider = fx->source.main->header.sliders[i];
535 if (!slider.is_enum)
536 continue;
538 uint32_t count = (uint32_t)slider.enum_names.size();
539 if (count == 0) {
540 bool is_file = !slider.path.empty();
541 ysfx_logf(*fx->config, ysfx_log_warning, "slider%u: the enumeration does not contain any %s", i + 1, is_file ? "files" : "items");
542 slider.enum_names.emplace_back();
543 slider.min = 0;
544 slider.max = 0;
545 slider.inc = 1;
547 else if (slider.min != 0 || slider.inc != 1 || slider.max != (EEL_F)(count - 1)) {
548 ysfx_logf(*fx->config, ysfx_log_warning, "slider%u: the enumeration has an invalid range", i + 1);
549 slider.min = 0;
550 slider.max = (EEL_F)(count - 1);
551 slider.inc = 1;
556 const char *ysfx_get_name(ysfx_t *fx)
558 ysfx_source_unit_t *main = fx->source.main.get();
559 if (!main)
560 return "";
561 return main->header.desc.c_str();
564 const char *ysfx_get_file_path(ysfx_t *fx)
566 return fx->source.main_file_path.c_str();
569 const char *ysfx_get_author(ysfx_t *fx)
571 ysfx_source_unit_t *main = fx->source.main.get();
572 if (!main)
573 return "";
574 return main->header.author.c_str();
577 uint32_t ysfx_get_tags(ysfx_t *fx, const char **dest, uint32_t destsize)
579 ysfx_source_unit_t *main = fx->source.main.get();
580 if (!main)
581 return 0;
583 uint32_t count = (uint32_t)main->header.tags.size();
585 uint32_t copysize = (destsize < count) ? destsize : count;
586 for (uint32_t i = 0; i < copysize; ++i)
587 dest[i] = main->header.tags[i].c_str();
589 return count;
592 const char *ysfx_get_tag(ysfx_t *fx, uint32_t index)
594 ysfx_source_unit_t *main = fx->source.main.get();
595 if (!main || index >= main->header.tags.size())
596 return "";
597 return main->header.tags[index].c_str();
600 uint32_t ysfx_get_num_inputs(ysfx_t *fx)
602 ysfx_source_unit_t *main = fx->source.main.get();
603 if (!main)
604 return 0;
605 return (uint32_t)main->header.in_pins.size();
608 uint32_t ysfx_get_num_outputs(ysfx_t *fx)
610 ysfx_source_unit_t *main = fx->source.main.get();
611 if (!main)
612 return 0;
613 return (uint32_t)main->header.out_pins.size();
616 const char *ysfx_get_input_name(ysfx_t *fx, uint32_t index)
618 ysfx_source_unit_t *main = fx->source.main.get();
619 if (!main || index >= main->header.in_pins.size())
620 return "";
621 return main->header.in_pins[index].c_str();
624 const char *ysfx_get_output_name(ysfx_t *fx, uint32_t index)
626 ysfx_source_unit_t *main = fx->source.main.get();
627 if (!main || index >= main->header.out_pins.size())
628 return "";
629 return main->header.out_pins[index].c_str();
632 bool ysfx_wants_meters(ysfx_t *fx)
634 ysfx_source_unit_t *main = fx->source.main.get();
635 if (!main)
636 return false;
638 return !main->header.options.no_meter;
641 bool ysfx_get_gfx_dim(ysfx_t *fx, uint32_t dim[2])
643 ysfx_toplevel_t *origin = nullptr;
644 ysfx_section_t *sec = ysfx_search_section(fx, ysfx_section_gfx, &origin);
646 if (!sec) {
647 if (dim) {
648 dim[0] = 0;
649 dim[1] = 0;
651 return false;
654 if (dim) {
655 dim[0] = origin->gfx_w;
656 dim[1] = origin->gfx_h;
658 return true;
661 ysfx_section_t *ysfx_search_section(ysfx_t *fx, uint32_t type, ysfx_toplevel_t **origin)
663 if (!fx->source.main)
664 return nullptr;
666 auto search =
667 [fx](ysfx_section_t *(*test)(ysfx_toplevel_t &tl), ysfx_toplevel_t **origin) -> ysfx_section_t *
669 ysfx_toplevel_t *tl = &fx->source.main->toplevel;
670 ysfx_section_t *sec = test(*tl);
671 for (size_t i = 0; !sec && i < fx->source.imports.size(); ++i) {
672 tl = &fx->source.imports[i]->toplevel;
673 sec = test(*tl);
675 if (origin)
676 *origin = sec ? tl : nullptr;
677 return sec;
680 switch (type) {
681 case ysfx_section_init:
682 return search([](ysfx_toplevel_t &tl) { return tl.init.get(); }, origin);
683 case ysfx_section_slider:
684 return search([](ysfx_toplevel_t &tl) { return tl.slider.get(); }, origin);
685 case ysfx_section_block:
686 return search([](ysfx_toplevel_t &tl) { return tl.block.get(); }, origin);
687 case ysfx_section_sample:
688 return search([](ysfx_toplevel_t &tl) { return tl.sample.get(); }, origin);
689 case ysfx_section_gfx:
690 return search([](ysfx_toplevel_t &tl) { return tl.gfx.get(); }, origin);
691 case ysfx_section_serialize:
692 return search([](ysfx_toplevel_t &tl) { return tl.serialize.get(); }, origin);
693 default:
694 return nullptr;
698 bool ysfx_has_section(ysfx_t *fx, uint32_t type)
700 return ysfx_search_section(fx, type) != nullptr;
703 bool ysfx_slider_exists(ysfx_t *fx, uint32_t index)
705 ysfx_source_unit_t *main = fx->source.main.get();
706 if (index >= ysfx_max_sliders || !main)
707 return false;
709 ysfx_slider_t &slider = main->header.sliders[index];
710 return slider.exists;
713 const char *ysfx_slider_get_name(ysfx_t *fx, uint32_t index)
715 ysfx_source_unit_t *main = fx->source.main.get();
716 if (index >= ysfx_max_sliders || !main)
717 return "";
719 ysfx_slider_t &slider = main->header.sliders[index];
720 return slider.desc.c_str();
723 bool ysfx_slider_get_range(ysfx_t *fx, uint32_t index, ysfx_slider_range_t *range)
725 ysfx_source_unit_t *main = fx->source.main.get();
726 if (index >= ysfx_max_sliders || !main)
727 return false;
729 ysfx_slider_t &slider = main->header.sliders[index];
730 range->def = slider.def;
731 range->min = slider.min;
732 range->max = slider.max;
733 range->inc = slider.inc;
734 return true;
737 bool ysfx_slider_is_enum(ysfx_t *fx, uint32_t index)
739 ysfx_source_unit_t *main = fx->source.main.get();
740 if (index >= ysfx_max_sliders || !main)
741 return false;
743 ysfx_slider_t &slider = main->header.sliders[index];
744 return slider.is_enum;
747 uint32_t ysfx_slider_get_enum_names(ysfx_t *fx, uint32_t index, const char **dest, uint32_t destsize)
749 ysfx_source_unit_t *main = fx->source.main.get();
750 if (index >= ysfx_max_sliders || !main)
751 return 0;
753 ysfx_slider_t &slider = main->header.sliders[index];
754 uint32_t count = (uint32_t)slider.enum_names.size();
756 uint32_t copysize = (destsize < count) ? destsize : count;
757 for (uint32_t i = 0; i < copysize; ++i)
758 dest[i] = slider.enum_names[i].c_str();
760 return count;
763 const char *ysfx_slider_get_enum_name(ysfx_t *fx, uint32_t slider_index, uint32_t enum_index)
765 ysfx_source_unit_t *main = fx->source.main.get();
766 if (slider_index >= ysfx_max_sliders || !main)
767 return 0;
769 ysfx_slider_t &slider = main->header.sliders[slider_index];
770 if (enum_index >= slider.enum_names.size())
771 return "";
773 return slider.enum_names[enum_index].c_str();
776 bool ysfx_slider_is_path(ysfx_t *fx, uint32_t index)
778 ysfx_source_unit_t *main = fx->source.main.get();
779 if (index >= ysfx_max_sliders || !main)
780 return false;
782 ysfx_slider_t &slider = main->header.sliders[index];
783 return !slider.path.empty();
786 bool ysfx_slider_is_initially_visible(ysfx_t *fx, uint32_t index)
788 ysfx_source_unit_t *main = fx->source.main.get();
789 if (index >= ysfx_max_sliders || !main)
790 return false;
792 ysfx_slider_t &slider = main->header.sliders[index];
793 return slider.initially_visible;
796 ysfx_real ysfx_slider_get_value(ysfx_t *fx, uint32_t index)
798 if (index >= ysfx_max_sliders)
799 return 0;
800 return *fx->var.slider[index];
803 void ysfx_slider_set_value(ysfx_t *fx, uint32_t index, ysfx_real value)
805 if (index >= ysfx_max_sliders)
806 return;
807 if (*fx->var.slider[index] != value) {
808 *fx->var.slider[index] = value;
809 fx->must_compute_slider = true;
813 std::string ysfx_resolve_import_path(ysfx_t *fx, const std::string &name, const std::string &origin)
815 std::vector<std::string> dirs;
817 // create the list of search directories
819 dirs.reserve(2);
821 if (!origin.empty())
822 dirs.push_back(ysfx::path_directory(origin.c_str()));
824 const std::string &import_root = fx->config->import_root;
825 if (!import_root.empty() && dirs[0] != import_root)
826 dirs.push_back(import_root);
829 // the search should be case-insensitive
830 static constexpr bool nocase = true;
832 static auto *check_existence = +[](const std::string &dir, const std::string &file, std::string &result_path) -> int {
833 if (nocase)
834 return ysfx::case_resolve(dir.c_str(), file.c_str(), result_path);
835 else {
836 result_path = dir + file;
837 return ysfx::exists(result_path.c_str());
841 // search for the file in these directories directly
842 for (const std::string &dir : dirs) {
843 std::string resolved;
844 if (check_existence(dir, name, resolved))
845 return resolved;
848 // search for the file recursively
849 for (const std::string &dir : dirs) {
850 struct visit_data {
851 const std::string *name = nullptr;
852 std::string resolved;
854 visit_data vd;
855 vd.name = &name;
856 auto visit = [](const std::string &dir, void *data) -> bool {
857 visit_data &vd = *(visit_data *)data;
858 std::string resolved;
859 if (check_existence(dir, *vd.name, resolved)) {
860 vd.resolved = std::move(resolved);
861 return false;
863 return true;
865 ysfx::visit_directories(dir.c_str(), +visit, &vd);
866 if (!vd.resolved.empty())
867 return vd.resolved;
870 return std::string{};
874 uint32_t ysfx_get_block_size(ysfx_t *fx)
876 return fx->block_size;
879 ysfx_real ysfx_get_sample_rate(ysfx_t *fx)
881 return fx->sample_rate;
884 void ysfx_set_block_size(ysfx_t *fx, uint32_t blocksize)
886 if (fx->block_size != blocksize) {
887 fx->block_size = blocksize;
888 fx->must_compute_init = true;
892 void ysfx_set_sample_rate(ysfx_t *fx, ysfx_real samplerate)
894 if (fx->sample_rate != samplerate) {
895 fx->sample_rate = samplerate;
896 fx->must_compute_init = true;
900 void ysfx_set_midi_capacity(ysfx_t *fx, uint32_t capacity, bool extensible)
902 ysfx_midi_reserve(fx->midi.in.get(), capacity, extensible);
903 ysfx_midi_reserve(fx->midi.out.get(), capacity, extensible);
906 void ysfx_init(ysfx_t *fx)
908 if (!fx->code.compiled)
909 return;
911 *fx->var.samplesblock = (EEL_F)fx->block_size;
912 *fx->var.srate = fx->sample_rate;
914 *fx->var.pdc_delay = 0;
915 *fx->var.pdc_bot_ch = 0;
916 *fx->var.pdc_top_ch = 0;
917 *fx->var.pdc_midi = 0;
919 if (fx->is_freshly_compiled) {
920 ysfx_first_init(fx);
921 fx->is_freshly_compiled = false;
924 ysfx_clear_files(fx);
926 for (size_t i = 0; i < fx->code.init.size(); ++i)
927 NSEEL_code_execute(fx->code.init[i].get());
929 fx->must_compute_init = false;
930 fx->must_compute_slider = true;
932 #if !defined(YSFX_NO_GFX)
933 // do initializations on next @gfx, on the gfx thread
934 // release-acquire order is for VM `gfx_*` variables and `wants_retina`
935 fx->gfx.wants_retina = *fx->var.gfx_ext_retina > 0;
936 fx->gfx.must_init.store(true, std::memory_order_release);
937 #endif
940 void ysfx_first_init(ysfx_t *fx)
942 assert(fx->code.compiled);
943 assert(fx->is_freshly_compiled);
945 fx->slider.automate_mask.store(0);
946 fx->slider.change_mask.store(0);
947 ysfx_update_slider_visibility_mask(fx);
950 ysfx_real ysfx_get_pdc_delay(ysfx_t *fx)
952 ysfx_real value = *fx->var.pdc_delay;
953 return (value > 0) ? value : 0;
956 void ysfx_get_pdc_channels(ysfx_t *fx, uint32_t channels[2])
958 if (!channels)
959 return;
961 int64_t bot = (int64_t)*fx->var.pdc_bot_ch;
962 bot = (bot > 0) ? bot : 0;
963 bot = (bot < ysfx_max_channels) ? bot : ysfx_max_channels;
964 channels[0] = (uint32_t)bot;
966 int64_t top = (int64_t)*fx->var.pdc_top_ch;
967 top = (top > bot) ? top : bot;
968 top = (top < ysfx_max_channels) ? top : ysfx_max_channels;
969 channels[1] = (uint32_t)top;
972 bool ysfx_get_pdc_midi(ysfx_t *fx)
974 return (bool)*fx->var.pdc_midi;
977 void ysfx_update_slider_visibility_mask(ysfx_t *fx)
979 uint64_t visible = 0;
980 for (uint32_t i = 0; i < ysfx_max_sliders; ++i) {
981 ysfx_slider_t &slider = fx->source.main->header.sliders[i];
982 visible |= (uint64_t)slider.initially_visible << i;
984 fx->slider.visible_mask.store(visible);
987 void ysfx_set_time_info(ysfx_t *fx, const ysfx_time_info_t *info)
989 uint32_t prev_state = (uint32_t)*fx->var.play_state;
990 uint32_t new_state = info->playback_state;
992 // unless `ext_noinit`, we should call @init every transport restart
993 if (!*fx->var.ext_noinit) {
994 auto is_running = [](uint32_t state) {
995 return state == ysfx_playback_playing ||
996 state == ysfx_playback_recording;
998 if (!is_running(prev_state) && is_running(new_state))
999 fx->must_compute_init = true;
1002 *fx->var.tempo = info->tempo;
1003 *fx->var.play_state = (EEL_F)new_state;
1004 *fx->var.play_position = info->time_position;
1005 *fx->var.beat_position = info->beat_position;
1006 *fx->var.ts_num = (EEL_F)info->time_signature[0];
1007 *fx->var.ts_denom = (EEL_F)info->time_signature[1];
1010 bool ysfx_send_midi(ysfx_t *fx, const ysfx_midi_event_t *event)
1012 return ysfx_midi_push(fx->midi.in.get(), event);
1015 bool ysfx_receive_midi(ysfx_t *fx, ysfx_midi_event_t *event)
1017 return ysfx_midi_get_next(fx->midi.out.get(), event);
1020 bool ysfx_receive_midi_from_bus(ysfx_t *fx, uint32_t bus, ysfx_midi_event_t *event)
1022 return ysfx_midi_get_next_from_bus(fx->midi.out.get(), 0, event);
1025 uint32_t ysfx_current_midi_bus(ysfx_t *fx)
1027 uint32_t bus = 0;
1028 if (*fx->var.ext_midi_bus)
1029 bus = (int32_t)*fx->var.midi_bus;
1030 return bus;
1033 bool ysfx_send_trigger(ysfx_t *fx, uint32_t index)
1035 if (index >= ysfx_max_triggers)
1036 return false;
1038 fx->triggers |= 1u << index;
1039 return true;
1042 uint64_t ysfx_fetch_slider_changes(ysfx_t *fx)
1044 return fx->slider.change_mask.exchange(0);
1047 uint64_t ysfx_fetch_slider_automations(ysfx_t *fx)
1049 return fx->slider.automate_mask.exchange(0);
1052 uint64_t ysfx_get_slider_visibility(ysfx_t *fx)
1054 return fx->slider.visible_mask.load();
1057 template <class Real>
1058 void ysfx_process_generic(ysfx_t *fx, const Real *const *ins, Real *const *outs, uint32_t num_ins, uint32_t num_outs, uint32_t num_frames)
1060 ysfx_set_thread_id(ysfx_thread_id_dsp);
1062 // prepare MIDI input for reading, output for writing
1063 assert(fx->midi.in->read_pos == 0);
1064 ysfx_midi_clear(fx->midi.out.get());
1066 // prepare triggers
1067 *fx->var.trigger = (EEL_F)fx->triggers;
1068 fx->triggers = 0;
1070 if (!fx->code.compiled) {
1071 for (uint32_t ch = 0; ch < num_outs; ++ch)
1072 memset(outs[ch], 0, num_frames * sizeof(Real));
1074 else {
1075 // compute @init if needed
1076 if (fx->must_compute_init)
1077 ysfx_init(fx);
1079 const uint32_t orig_num_outs = num_outs;
1080 const uint32_t num_code_ins = (uint32_t)fx->source.main->header.in_pins.size();
1081 const uint32_t num_code_outs = (uint32_t)fx->source.main->header.out_pins.size();
1082 if (num_ins > num_code_ins)
1083 num_ins = num_code_ins;
1084 if (num_outs > num_code_outs)
1085 num_outs = num_code_outs;
1087 fx->valid_input_channels = num_ins;
1089 *fx->var.samplesblock = (EEL_F)num_frames;
1090 *fx->var.num_ch = (EEL_F)num_ins;
1092 // compute @slider if needed
1093 if (fx->must_compute_slider) {
1094 NSEEL_code_execute(fx->code.slider.get());
1095 fx->must_compute_slider = false;
1098 // compute @block
1099 NSEEL_code_execute(fx->code.block.get());
1101 // compute @sample, once per frame
1102 if (fx->code.sample) {
1103 EEL_F **spl = fx->var.spl;
1104 for (uint32_t i = 0; i < num_frames; ++i) {
1105 for (uint32_t ch = 0; ch < num_ins; ++ch)
1106 *spl[ch] = (EEL_F)ins[ch][i];
1107 for (uint32_t ch = num_ins; ch < num_code_ins; ++ch)
1108 *spl[ch] = 0;
1109 NSEEL_code_execute(fx->code.sample.get());
1110 for (uint32_t ch = 0; ch < num_outs; ++ch)
1111 outs[ch][i] = (Real)*spl[ch];
1115 // clear any output channels above the maximum count
1116 for (uint32_t ch = num_outs; ch < orig_num_outs; ++ch)
1117 memset(outs[ch], 0, num_frames * sizeof(Real));
1120 // prepare MIDI input for writing, output for reading
1121 assert(fx->midi.out->read_pos == 0);
1122 ysfx_midi_clear(fx->midi.in.get());
1124 ysfx_set_thread_id(ysfx_thread_id_none);
1127 void ysfx_process_float(ysfx_t *fx, const float *const *ins, float *const *outs, uint32_t num_ins, uint32_t num_outs, uint32_t num_frames)
1129 ysfx_process_generic<float>(fx, ins, outs, num_ins, num_outs, num_frames);
1132 void ysfx_process_double(ysfx_t *fx, const double *const *ins, double *const *outs, uint32_t num_ins, uint32_t num_outs, uint32_t num_frames)
1134 ysfx_process_generic<double>(fx, ins, outs, num_ins, num_outs, num_frames);
1137 void ysfx_clear_files(ysfx_t *fx)
1139 std::lock_guard<ysfx::mutex> list_lock(fx->file.list_mutex);
1141 // delete all except the serializer
1142 while (fx->file.list.size() > 1) {
1143 ysfx_file_t *file = fx->file.list.back().get();
1144 std::unique_ptr<ysfx::mutex> file_mutex;
1145 std::unique_lock<ysfx::mutex> file_lock;
1146 if (file) {
1147 file_lock = std::unique_lock<ysfx::mutex>{*fx->file.list.back()->m_mutex};
1148 file_mutex = std::move(fx->file.list.back()->m_mutex);
1150 fx->file.list.pop_back();
1154 ysfx_file_t *ysfx_get_file(ysfx_t *fx, uint32_t handle, std::unique_lock<ysfx::mutex> &lock, std::unique_lock<ysfx::mutex> *list_lock)
1156 std::unique_lock<ysfx::mutex> local_list_lock;
1157 if (list_lock)
1158 *list_lock = std::unique_lock<ysfx::mutex>(fx->file.list_mutex);
1159 else
1160 local_list_lock = std::unique_lock<ysfx::mutex>(fx->file.list_mutex);
1161 if (handle >= fx->file.list.size())
1162 return nullptr;
1163 ysfx_file_t *file = fx->file.list[handle].get();
1164 if (!file)
1165 return nullptr;
1166 lock = std::unique_lock<ysfx::mutex>{*file->m_mutex};
1167 return file;
1170 int32_t ysfx_insert_file(ysfx_t *fx, ysfx_file_t *file)
1172 std::lock_guard<ysfx::mutex> lock(fx->file.list_mutex);
1175 size_t noneidx = ~(size_t)0;
1176 size_t freeidx = noneidx;
1178 for (size_t i = 0, n = fx->file.list.size(); i < n && freeidx == noneidx; ++i) {
1179 if (!fx->file.list[i])
1180 freeidx = i;
1182 if (freeidx != noneidx) {
1183 fx->file.list[freeidx].reset(file);
1184 return (uint32_t)freeidx;
1187 enum { max_file_handles = 64 };
1189 size_t pos = fx->file.list.size();
1190 if (pos >= max_file_handles)
1191 return -1;
1193 fx->file.list.emplace_back(file);
1194 return (uint32_t)pos;
1197 bool ysfx_load_state(ysfx_t *fx, ysfx_state_t *state)
1199 if (!fx->code.compiled)
1200 return false;
1202 // restore the serialization
1203 std::string buffer((char *)state->data, state->data_size);
1205 // restore the sliders
1206 for (uint32_t i = 0; i < ysfx_max_sliders; ++i)
1207 *fx->var.slider[i] = fx->source.main->header.sliders[i].def;
1209 for (uint32_t i = 0, n = state->slider_count; i < n; ++i) {
1210 uint32_t j = state->sliders[i].index;
1211 if (j < ysfx_max_sliders && fx->source.main->header.sliders[j].exists)
1212 *fx->var.slider[j] = state->sliders[i].value;
1214 fx->must_compute_slider = true;
1216 // invoke @serialize
1218 std::unique_lock<ysfx::mutex> lock;
1219 ysfx_serializer_t *serializer = static_cast<ysfx_serializer_t *>(ysfx_get_file(fx, 0, lock));
1220 assert(serializer);
1221 serializer->begin(false, buffer);
1222 lock.unlock();
1223 ysfx_serialize(fx);
1224 lock.lock();
1225 serializer->end();
1228 return true;
1231 ysfx_state_t *ysfx_save_state(ysfx_t *fx)
1233 if (!fx->code.compiled)
1234 return nullptr;
1236 std::string buffer;
1238 // invoke @serialize
1240 std::unique_lock<ysfx::mutex> lock;
1241 ysfx_serializer_t *serializer = static_cast<ysfx_serializer_t *>(ysfx_get_file(fx, 0, lock));
1242 assert(serializer);
1243 serializer->begin(true, buffer);
1244 lock.unlock();
1245 ysfx_serialize(fx);
1246 lock.lock();
1247 serializer->end();
1250 // save the sliders
1251 ysfx_state_u state{new ysfx_state_t};
1252 uint32_t slider_count = 0;
1253 for (uint32_t i = 0; i < ysfx_max_sliders; ++i)
1254 slider_count += fx->source.main->header.sliders[i].exists;
1256 state->sliders = new ysfx_state_slider_t[slider_count]{};
1257 state->slider_count = slider_count;
1259 for (uint32_t i = 0, j = 0; i < slider_count; ++i) {
1260 if (fx->source.main->header.sliders[i].exists) {
1261 state->sliders[j].index = i;
1262 state->sliders[j].value = *fx->var.slider[i];
1263 ++j;
1267 // save the serialization
1268 state->data_size = buffer.size();
1269 state->data = new uint8_t[state->data_size];
1270 memcpy(state->data, buffer.data(), state->data_size);
1273 return state.release();
1276 void ysfx_state_free(ysfx_state_t *state)
1278 if (!state)
1279 return;
1281 delete[] state->sliders;
1282 delete[] state->data;
1283 delete state;
1286 ysfx_state_t *ysfx_state_dup(ysfx_state_t *state_in)
1288 if (!state_in)
1289 return nullptr;
1291 ysfx_state_u state_out{new ysfx_state_t};
1293 uint32_t slider_count = state_out->slider_count = state_in->slider_count;
1294 size_t data_size = state_out->data_size = state_in->data_size;
1296 state_out->sliders = new ysfx_state_slider_t[slider_count];
1297 memcpy(state_out->sliders, state_in->sliders, slider_count * sizeof(ysfx_state_slider_t));
1299 state_out->data = new uint8_t[data_size];
1300 memcpy(state_out->data, state_in->data, data_size);
1302 return state_out.release();
1305 void ysfx_serialize(ysfx_t *fx)
1307 if (fx->code.serialize) {
1308 if (fx->must_compute_init)
1309 ysfx_init(fx);
1310 NSEEL_code_execute(fx->code.serialize.get());
1314 uint32_t ysfx_get_slider_of_var(ysfx_t *fx, EEL_F *var)
1316 auto it = fx->slider_of_var.find(var);
1317 if (it == fx->slider_of_var.end())
1318 return ~(uint32_t)0;
1319 return it->second;
1322 const char *ysfx_get_bank_path(ysfx_t *fx)
1324 return fx->source.bank_path.c_str();
1327 void ysfx_enum_vars(ysfx_t *fx, ysfx_enum_vars_callback_t *callback, void *userdata)
1329 NSEEL_VM_enumallvars(fx->vm.get(), callback, userdata);
1332 ysfx_real *ysfx_find_var(ysfx_t *fx, const char *name)
1334 struct find_data {
1335 ysfx_real *var = nullptr;
1336 const char *name = nullptr;
1338 find_data fd;
1339 fd.name = name;
1340 auto callback = [](const char *name, EEL_F *var, void *userdata) -> int {
1341 find_data *fd = (find_data *)userdata;
1342 if (strcmp(name, fd->name) != 0)
1343 return 1;
1344 fd->var = var;
1345 return 0;
1347 NSEEL_VM_enumallvars(fx->vm.get(), +callback, &fd);
1348 return fd.var;
1351 void ysfx_read_vmem(ysfx_t *fx, uint32_t addr, ysfx_real *dest, uint32_t count)
1353 ysfx_eel_ram_reader reader(fx->vm.get(), addr);
1354 for (uint32_t i = 0; i < count; ++i)
1355 dest[i] = reader.read_next();
1358 bool ysfx_find_data_file(ysfx_t *fx, EEL_F *file, std::string &result)
1360 // 3 possibilities for file
1361 // - slider
1362 // - index of filename
1363 // - string
1365 std::string filepart;
1367 bool accept_absolute = false;
1368 bool accept_relative = false;
1370 int32_t index = ysfx_eel_round<int32_t>(*file);
1371 uint32_t slideridx = ysfx_get_slider_of_var(fx, file);
1372 ysfx_slider_t *slider = nullptr;
1374 if (slideridx != ~(uint32_t)0)
1375 slider = &fx->source.main->header.sliders[slideridx];
1377 if (slider && !slider->path.empty()) {
1378 int32_t value = ysfx_eel_round<int32_t>(*fx->var.slider[slideridx]);
1379 if (value < 0 || (uint32_t)value >= slider->enum_names.size())
1380 return false;
1382 filepart = slider->path + '/' + slider->enum_names[(uint32_t)value];
1383 accept_relative = true;
1385 else if (index >= 0 && (uint32_t)index < fx->source.main->header.filenames.size()) {
1386 filepart = fx->source.main->header.filenames[(uint32_t)index];
1387 accept_relative = true;
1389 else if (ysfx_string_get(fx, *file, filepart)) {
1390 accept_absolute = true;
1391 accept_relative = true;
1393 else
1394 return false;
1396 std::vector<std::string> filecandidates;
1397 filecandidates.reserve(2);
1399 if (accept_absolute && !ysfx::path_is_relative(filepart.c_str()))
1400 filecandidates.push_back(filepart);
1401 else if (accept_relative) {
1402 filecandidates.push_back(ysfx::path_directory(fx->source.main_file_path.c_str()) + filepart);
1403 if (!fx->config->data_root.empty())
1404 filecandidates.push_back(fx->config->data_root + filepart);
1407 for (const std::string &filepath : filecandidates) {
1408 if (ysfx::exists(filepath.c_str())) {
1409 result.assign(filepath);
1410 return true;
1414 return false;
1417 ysfx_file_type_t ysfx_detect_file_type(ysfx_t *fx, const char *path, void **fmtobj)
1419 if (ysfx::path_has_suffix(path, "txt"))
1420 return ysfx_file_type_txt;
1421 if (ysfx::path_has_suffix(path, "raw"))
1422 return ysfx_file_type_raw;
1423 for (ysfx_audio_format_t &fmt : fx->config->audio_formats) {
1424 if (fmt.can_handle(path)) {
1425 if (fmtobj)
1426 *fmtobj = &fmt;
1427 return ysfx_file_type_audio;
1430 return ysfx_file_type_none;
1433 void ysfx_gfx_setup(ysfx_t *fx, ysfx_gfx_config_t *gc)
1435 #if !defined(YSFX_NO_GFX)
1436 bool doinit = false;
1437 ysfx_scoped_gfx_t scope{fx, doinit};
1439 ysfx_gfx_state_set_bitmap(fx->gfx.state.get(), gc->pixels, gc->pixel_width, gc->pixel_height, gc->pixel_stride);
1440 ysfx_real scale = fx->gfx.wants_retina ? gc->scale_factor : 1;
1441 ysfx_gfx_state_set_scale_factor(fx->gfx.state.get(), scale);
1443 ysfx_gfx_state_set_callback_data(fx->gfx.state.get(), gc->user_data);
1444 ysfx_gfx_state_set_show_menu_callback(fx->gfx.state.get(), gc->show_menu);
1445 ysfx_gfx_state_set_set_cursor_callback(fx->gfx.state.get(), gc->set_cursor);
1446 ysfx_gfx_state_set_get_drop_file_callback(fx->gfx.state.get(), gc->get_drop_file);
1447 #else
1448 (void)fx;
1449 (void)gc;
1450 #endif
1453 bool ysfx_gfx_wants_retina(ysfx_t *fx)
1455 #if !defined(YSFX_NO_GFX)
1456 return fx->gfx.wants_retina;
1457 #else
1458 (void)fx;
1459 return false;
1460 #endif
1463 void ysfx_gfx_add_key(ysfx_t *fx, uint32_t mods, uint32_t key, bool press)
1465 #if !defined(YSFX_NO_GFX)
1466 bool doinit = true;
1467 ysfx_scoped_gfx_t scope{fx, doinit};
1469 if (!fx->gfx.ready)
1470 return;
1472 ysfx_gfx_state_add_key(fx->gfx.state.get(), mods, key, press);
1473 #else
1474 (void)fx;
1475 (void)mods;
1476 (void)key;
1477 #endif
1480 void ysfx_gfx_update_mouse(ysfx_t *fx, uint32_t mods, int32_t xpos, int32_t ypos, uint32_t buttons, ysfx_real wheel, ysfx_real hwheel)
1482 #if !defined(YSFX_NO_GFX)
1483 bool doinit = true;
1484 ysfx_scoped_gfx_t scope{fx, doinit};
1486 if (!fx->gfx.ready)
1487 return;
1489 *fx->var.mouse_x = (EEL_F)xpos;
1490 *fx->var.mouse_y = (EEL_F)ypos;
1491 *fx->var.mouse_wheel += 120 * wheel;
1492 *fx->var.mouse_hwheel += 120 * hwheel;
1494 uint32_t mouse_cap = 0;
1495 if (mods & ysfx_mod_shift)
1496 mouse_cap |= 8;
1497 if (mods & ysfx_mod_ctrl)
1498 mouse_cap |= 4;
1499 if (mods & ysfx_mod_alt)
1500 mouse_cap |= 16;
1501 if (mods & ysfx_mod_super)
1502 mouse_cap |= 32;
1503 if (buttons & ysfx_button_left)
1504 mouse_cap |= 1;
1505 if (buttons & ysfx_button_middle)
1506 mouse_cap |= 64;
1507 if (buttons & ysfx_button_right)
1508 mouse_cap |= 2;
1509 *fx->var.mouse_cap = (EEL_F)mouse_cap;
1511 #else
1512 (void)fx;
1513 (void)mods;
1514 (void)xpos;
1515 (void)ypos;
1516 (void)buttons;
1517 (void)wheel;
1518 (void)hwheel;
1519 #endif
1522 bool ysfx_gfx_run(ysfx_t *fx)
1524 #if !defined(YSFX_NO_GFX)
1525 bool doinit = true;
1526 ysfx_scoped_gfx_t scope{fx, doinit};
1528 if (!fx->gfx.ready)
1529 return false;
1531 ysfx_gfx_prepare(fx);
1532 NSEEL_code_execute(fx->code.gfx.get());
1534 return ysfx_gfx_state_is_dirty(fx->gfx.state.get());
1535 #else
1536 return false;
1537 (void)fx;
1538 #endif