Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
liquidsfz.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "ase/processor.hh"
3#include "ase/midievent.hh"
4#include "ase/internal.hh"
5
6#include <liquidsfz.hh>
7
8namespace {
9
10using namespace Ase;
11
12using LiquidSFZ::Synth;
13
14class LiquidSFZLoader
15{
16 enum { STATE_IDLE, STATE_LOAD };
17 std::atomic<int> state_ { STATE_IDLE };
18 std::atomic<int> quit_ { 0 };
20 std::thread thread_;
21
22 Synth &synth_;
23 String have_sfz_;
24 String want_sfz_;
25 uint want_sample_rate_ = 0;
26 uint have_sample_rate_ = 0;
27
28 void
29 run()
30 {
31 while (!quit_.load())
32 {
33 sem_.wait();
34 if (state_.load() == STATE_LOAD)
35 {
36 if (want_sfz_ != have_sfz_)
37 {
38 printerr ("LiquidSFZ: loading %s...", want_sfz_.c_str());
39 bool result = synth_.load (want_sfz_);
40 printerr ("%s\n", result ? "OK" : "FAIL");
41 // TODO: handle load error
42
43 have_sfz_ = want_sfz_;
44 }
45 if (want_sample_rate_ != have_sample_rate_)
46 {
47 synth_.set_sample_rate (want_sample_rate_);
48 have_sample_rate_ = want_sample_rate_;
49 }
50 state_.store (STATE_IDLE);
51 }
52 }
53 printerr ("LiquidSFZ: run() done\n");
54 }
55public:
56 LiquidSFZLoader (Synth &synth) :
57 synth_ (synth)
58 {
59 thread_ = std::thread (&LiquidSFZLoader::run, this);
60 want_sfz_.reserve (4096); // avoid allocations in audio thread
61 printerr ("LiquidSFZLoader()\n");
62 }
63 ~LiquidSFZLoader()
64 {
65 quit_.store (1);
66 sem_.post();
67 thread_.join();
68 printerr ("~LiquidSFZLoader()\n");
69 }
70 // called from audio thread
71 bool
72 idle()
73 {
74 if (state_.load() == STATE_IDLE)
75 {
76 if (want_sfz_ == have_sfz_ && (want_sample_rate_ == have_sample_rate_ || want_sfz_ == ""))
77 return true;
78 }
79 state_.store (STATE_LOAD);
80 sem_.post();
81 return false;
82 }
83 // called from audio thread
84 void
85 load (const String &sfz)
86 {
87 want_sfz_ = sfz;
88 }
89 // called from audio thread
90 void
91 set_sample_rate (uint sample_rate)
92 {
93 want_sample_rate_ = sample_rate;
94 }
95};
96
97// == LiquidSFZ ==
98// SFZ sampler using liquidsfz library
99class LiquidSFZ : public AudioProcessor {
100 OBusId stereo_out_;
101 Synth synth_;
102 bool synth_need_reset_ = false;
103 LiquidSFZLoader loader_;
104
105 enum Params {
106 INSTRUMENT = 1,
107 };
108
109 static constexpr const int PID_CC_OFFSET = 1000;
110
111 void
112 initialize (SpeakerArrangement busses) override
113 {
114 install_params (build_parameter_map());
115
116 loader_.set_sample_rate (sample_rate());
118 stereo_out_ = add_output_bus ("Stereo Out", SpeakerArrangement::STEREO);
119 assert_return (bus_info (stereo_out_).ident == "stereo_out");
120 }
121 /* TODO: the parameter map generated by this function depends on the .sfz file that is
122 * currently loaded in synth_ -> so we should rerun this after loading a new .sfz
123 */
125 build_parameter_map()
126 {
127 ParameterMap pmap;
128 pmap[INSTRUMENT] = Param { "instrument", _("Instrument"), _("Inst"), "", "", {}, "", {
129 String ("blurb=") + _("Instrument File Name"),
130 "extensions=sfz",
131 } };
132 auto ccs = synth_.list_ccs();
133 for (const auto& cc_info : ccs)
134 {
135 uint32_t pid = cc_info.cc() + PID_CC_OFFSET;
136 pmap[pid] = Param { string_format ("cc_%d", cc_info.cc()), cc_info.label(), cc_info.label(),
137 cc_info.default_value() / 127. * 100, "%", { 0, 100 } };
138 }
139 return pmap;
140 }
141 void
142 reset (uint64 target_stamp) override
143 {
144 synth_need_reset_ = true;
145 adjust_all_params();
146 }
147 void
148 adjust_param (uint32_t tag) override
149 {
150 switch (tag)
151 {
152 case INSTRUMENT:
153 loader_.load (text_param_from_quark (INSTRUMENT, irintf (get_param (tag))));
154 break;
155 }
156 }
157 void
158 generate_cc_event (const MidiEvent& event)
159 {
160 const int cc = event.param - PID_CC_OFFSET;
161 const int cc_value = std::clamp (irintf (event.pvalue * 0.01 * 127), 0, 127);
162 /* TODO: make a decision here how UI parameters (which ought to be automatable) map to CC values
163 *
164 * - should the liquidsfz synth maintain different CC state for different midi channels?
165 * - should automating a parameter affect all notes (or only these on specific channels)?
166 * - should CC events affect all notes (or only those on specific channels)?
167 * - should we support per note modulation?
168 *
169 * Right now, we map parameter changes to CC events on channel 0 only.
170 */
171 synth_.add_event_cc (event.frame, 0, cc, cc_value);
172 }
173 void
174 render (uint n_frames) override
175 {
176 // TODO: rework the logic so midi_event_input(), and in particular MidiMessage::PARAM_VALUE are processed unconditionally
177 // See also: https://github.com/tim-janik/anklang/issues/44#issuecomment-2176839260
178 if (loader_.idle())
179 {
180 if (synth_need_reset_)
181 {
182 synth_.system_reset();
183 synth_need_reset_ = false;
184 }
185
186 MidiEventInput evinput = midi_event_input();
187 for (const auto &ev : evinput)
188 {
189 switch (ev.message())
190 {
191 case MidiMessage::NOTE_OFF:
192 synth_.add_event_note_off (ev.frame, ev.channel, ev.key);
193 break;
194 case MidiMessage::NOTE_ON:
195 synth_.add_event_note_on (ev.frame, ev.channel, ev.key, std::clamp (irintf (ev.velocity * 127), 0, 127));
196 break;
197 case MidiMessage::ALL_NOTES_OFF:
198 case MidiMessage::ALL_SOUND_OFF:
199 synth_.all_sound_off(); // NOTE: there is no extra "all notes off" in liquidsfz
200 break;
201 case MidiMessage::PARAM_VALUE:
202 apply_event (ev);
203 adjust_param (ev.param);
204 if (ev.param >= PID_CC_OFFSET)
205 generate_cc_event (ev);
206 break;
207 default: ;
208 }
209 }
210
211 float *output[2] = {
212 oblock (stereo_out_, 0),
213 oblock (stereo_out_, 1)
214 };
215 synth_.process (output, n_frames);
216 }
217 else
218 {
219 float *left_out = oblock (stereo_out_, 0);
220 float *right_out = oblock (stereo_out_, 1);
221
222 floatfill (left_out, 0.f, n_frames);
223 floatfill (right_out, 0.f, n_frames);
224 }
225 }
226public:
227 LiquidSFZ (const ProcessorSetup &psetup) :
228 AudioProcessor (psetup),
229 loader_ (synth_)
230 {}
231 static void
232 static_info (AudioProcessorInfo &info)
233 {
234 info.version = "1";
235 info.label = "LiquidSFZ";
236 info.category = "Synth";
237 info.creator_name = "Stefan Westerfeld";
238 info.website_url = "https://anklang.testbit.eu";
239 }
240};
241
242static auto liquidsfz = register_audio_processor<LiquidSFZ> ("Ase::Devices::LiquidSFZ");
243
244} // Bse
T c_str(T... args)
T clamp(T... args)
Audio signal AudioProcessor base class, implemented by all effects and instruments.
Definition processor.hh:76
MidiEventInput midi_event_input()
Access the current MidiEvent inputs during render(), needs prepare_event_input().
Definition processor.cc:844
OBusId add_output_bus(CString uilabel, SpeakerArrangement speakerarrangement, const String &hints="", const String &blurb="")
Add an output bus with uilabel and channels configured via speakerarrangement.
Definition processor.cc:530
virtual void initialize(SpeakerArrangement busses)=0
Definition processor.cc:432
void apply_event(const MidiEvent &event)
Assign MidiEvent::PARAM_VALUE event values to parameters.
Definition processor.hh:387
void install_params(const AudioParams::Map &params)
Reset list of parameters, enqueues parameter value initializaiton events.
Definition processor.cc:213
float * oblock(OBusId b, uint c)
Definition processor.hh:561
int wait() noexcept
Unlock ScopedSemaphore.
Definition platform.cc:902
int post() noexcept
Unlock ScopedSemaphore.
Definition platform.cc:893
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define _(...)
Retrieve the translation of a C or C++ string.
Definition internal.hh:18
T join(T... args)
int run(const StringS &test_names)
Run named tests.
Definition testing.cc:256
The Anklang C++ API namespace.
Definition api.hh:9
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
CString website_url
Website of/about this AudioProcessor.
Definition processor.hh:35
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:25
void floatfill(float *dst, float f, size_t n)
Fill n values of dst with f.
Definition datautils.hh:29
SpeakerArrangement
Definition transport.hh:11
OBusId
ID type for AudioProcessor output buses, buses are numbered with increasing index.
Definition processor.hh:25
CString version
Version identifier.
Definition processor.hh:31
CString creator_name
Name of the creator.
Definition processor.hh:36
CString label
Preferred user interface name.
Definition processor.hh:30
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
int irintf(float f)
Definition mathutils.hh:14
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
CString category
Category to allow grouping for processors of similar function.
Definition processor.hh:32
Detailed information and common properties of AudioProcessor subclasses.
Definition processor.hh:29
T reserve(T... args)
typedef uint32_t
MidiEvent data structure.
Definition midievent.hh:50
uint frame
Offset into current block, delayed if negative.
Definition midievent.hh:53
Structured initializer for Parameter.
Definition parameter.hh:31
Parameter list construction helper.
Definition parameter.hh:93