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

« « « Anklang Documentation
Loading...
Searching...
No Matches
midilib.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 "midilib.hh"
3#include "server.hh"
4#include "internal.hh"
5
6#define MDEBUG(...) Ase::debug ("midifeed", __VA_ARGS__)
7
8namespace Ase {
9
11namespace MidiLib {
12
13struct TickEvent {
14 int64_t tick;
15 MidiEvent event;
16};
17template<ssize_t DIR>
20 operator() (const TickEvent &a, const TickEvent &b) const
21 {
22 if (DIR > 0)
23 return int64_t (a.tick) - int64_t (b.tick);
24 else
25 return int64_t (b.tick) - int64_t (a.tick);
26 }
27};
28// static const CmpTickEvents<+1> forward_cmp_ticks;
29static const CmpTickEvents<-1> backward_cmp_ticks;
30
31// == MidiProducerImpl ==
34 MidiFeedP feed_;
35 Position *position_ = nullptr;
36 int64 generator_start_ = -1;
37 bool must_flush = false;
38 std::vector<TickEvent> future_stack; // newest events at back()
39 FastMemory::Block position_block_;
40public:
41 MidiProducerImpl (const ProcessorSetup &psetup) :
42 MidiProducerIface (psetup)
43 {
44 position_block_ = SERVER->telemem_allocate (sizeof (Position));
45 position_ = new (position_block_.block_start) Position {};
46 future_stack.reserve (64); // likely enough to avoid malloc
47 }
49 {
50 position_->~Position();
51 position_ = nullptr;
52 SERVER->telemem_release (position_block_);
53 }
54 static void
55 static_info (AudioProcessorInfo &info)
56 {} // avoid public listing
57 void
64 void
65 update_feed (MidiFeedP &feed) override
66 {
67 // save current play_position
68 const int64 last_play_position = position_->current >= 0 ? feed_->generators[position_->current].play_position() : 0;
69 // swap shared_ptr so lengthy dtors are executed in another thread
70 MidiFeedP old = feed_;
71 std::swap (feed_, feed);
72 // restore current play_position
73 if (position_->current >= 0)
74 {
75 if (feed_ && size_t (position_->current) < feed_->generators.size())
76 feed_->generators[position_->current].jumpto (last_play_position);
77 else
78 {
79 must_flush = must_flush || position_->current != -1;
80 position_->current = -1;
81 }
82 }
83 }
84 void
85 reset (uint64 target_stamp) override
86 {
87 position_->next = -1;
88 position_->current = -1;
89 position_->tick = -M52MAX;
90 future_stack.clear();
91 must_flush = false;
92 }
93 void
94 start () override
95 {
96 if (feed_ && feed_->generators.size())
97 {
98 if (position_->current < 0)
99 {
100 position_->current = 0;
101 generator_start_ = transport().current_bar_tick;
102 feed_->generators[position_->current].jumpto (0);
103 }
104 }
105 // TODO: handle start within bars
106 }
107 void
108 stop (bool restart) override
109 {
110 position_->tick = -M52MAX;
111 must_flush = true;
112 if (restart)
113 {
114 position_->current = -1;
115 generator_start_ = -1;
116 }
117 }
118 void
119 render (uint n_frames) override
120 {
121 const AudioTransport &transport = this->transport();
122 MidiEventInput evinput = midi_event_input(); // treat MIDI input as MIDI through
123 MidiEventOutput &evout = midi_event_output(); // needs prepare_event_output()
124 const int64 begin_tick = transport.current_tick;
125 const int64 end_tick = transport.current_tick + transport.sample_to_tick (n_frames);
126 const int64 bpm = transport.current_bpm;
127 // flush NOTE_OFF events
128 if (ASE_UNLIKELY (must_flush || bpm <= 0))
129 {
130 must_flush = false;
131 for (ssize_t i = future_stack.size() - 1; i >= 0; i--)
132 {
133 TickEvent tnote = future_stack[i];
134 const int64 frame0 = 0;
135 if (tnote.event.type == MidiEvent::NOTE_OFF)
136 {
137 evout.append_unsorted (frame0, tnote.event);
138 MDEBUG ("FLUSH: t=%d ev=%s f=%d\n", tnote.tick, tnote.event.to_string(), frame0);
139 }
140 }
141 future_stack.resize (0);
142 }
143 // enqueue pending NOTE_OFF events
144 while (future_stack.size() && future_stack.back().tick < end_tick)
145 {
146 TickEvent tnote = future_stack.back();
147 future_stack.pop_back();
148 const int64 frame = transport.sample_from_tick (tnote.tick - begin_tick);
149 assert_paranoid (frame >= 0 && frame <= 4095);
150 MDEBUG ("POP: t=%d ev=%s f=%d\n", tnote.tick, tnote.event.to_string(), frame);
151 evout.append_unsorted (frame, tnote.event);
152 }
153 // enqueue pending MIDI input events
154 for (const MidiEvent &mevent : evinput)
155 {
156 MDEBUG ("THROUGH: f=%+3d ev=%s\n", mevent.frame, mevent.to_string());
157 evout.append (mevent.frame, mevent);
158 }
159 // enqueue new events, keep queue of future events generated on the fly (NOTE-OFF)
160 if (ASE_ISLIKELY (feed_ && feed_->generators.size() &&
161 bpm > 0 && position_->current >= 0 &&
162 generator_start_ >= 0)) // in playback
163 {
164 // generate up to end_tick
165 while (position_->current >= 0 &&
166 generator_start_ + feed_->generators[position_->current].play_position() < end_tick)
167 {
168 // handler for incoming events
169 auto qevent = [begin_tick, end_tick, &transport, &evout, this] (int64 cliptick, MidiEvent &event) {
170 const int64 etick = generator_start_ + cliptick; // Generator tick to Engine tick
171 if (etick < end_tick)
172 {
173 const int64 frame = transport.sample_from_tick (etick - begin_tick);
174 assert_paranoid (frame >= 0 && frame <= 4095);
175 // interleave with earlier MIDI through events
176 evout.append_unsorted (frame, event);
177 MDEBUG ("NOW: t=%d ev=%s f=%d\n", etick, event.to_string(), frame);
178 }
179 else
180 {
181 TickEvent future_event { etick, event };
182 Aux::insert_sorted (future_stack, future_event, backward_cmp_ticks);
183 MDEBUG ("FUT: t=%d ev=%s f=%d\n", etick, event.to_string(), transport.sample_from_tick (etick - begin_tick));
184 }
185 };
186 // generate events for this block
187 const int64 advanced = feed_->generators[position_->current].generate (end_tick - generator_start_, qevent);
188 (void) advanced;
189 // handle generator succession
190 if (feed_->generators[position_->current].done())
191 {
192 const int64 play_point = generator_start_ + feed_->generators[position_->current].play_position();
193 assert_paranoid (play_point >= begin_tick && play_point <= end_tick);
194 position_->current = feed_->scout.advance (position_->current);
195 if (position_->current >= 0)
196 {
197 generator_start_ = transport.current_bar_tick;
198 while (generator_start_ < play_point)
199 generator_start_ += transport.tick_sig.bar_ticks();
200 feed_->generators[position_->current].jumpto (0);
201 if (feed_->generators[position_->current].done())
202 position_->current = -1;
203 }
204 if (position_->current == -1)
205 generator_start_ = -1;
206 position_->next = -1;
207 }
208 // position_->tick = r.etick - generator_start_; // externel tick
209 if (position_->current >= 0)
210 position_->tick = feed_->generators[position_->current].clip_position(); // internal
211 else
212 position_->tick = -M52MAX;
213 }
214 }
215 // ensure ascending event tick order
216 evout.ensure_order();
217 }
218 Position*
219 position () const override
220 {
221 return position_;
222 }
223};
224
225static auto midilib_midiinputimpl = register_audio_processor<MidiProducerImpl>();
226
227} // MidiLib
228} // Ase
void remove_all_buses()
Remove existing bus configurations, useful at the start of configure().
Definition processor.cc:646
MidiEventInput midi_event_input()
Access the current MidiEvent inputs during render(), needs prepare_event_input().
Definition processor.cc:844
MidiEventOutput & midi_event_output()
Access the current output EventStream during render(), needs prepare_event_output().
Definition processor.cc:859
const AudioTransport & transport() const
Sample rate mixing frequency in Hz as unsigned, used for render().
Definition processor.hh:432
void prepare_event_output()
Definition processor.cc:467
Generator for MIDI events.
Definition clip.hh:64
A stream of writable MidiEvent structures.
Definition midievent.hh:96
bool append_unsorted(int16_t frame, const MidiEvent &event)
Definition midievent.cc:192
void ensure_order()
Fix event order after append_unsorted() returned true.
Definition midievent.cc:205
void append(int16_t frame, const MidiEvent &event)
Append an MidiEvent with conscutive frame time stamp.
Definition midievent.cc:183
An in-order MidiEvent reader for multiple MidiEvent sources.
Definition midievent.hh:117
void render(uint n_frames) override
Definition midilib.cc:119
void initialize(SpeakerArrangement busses) override
Definition midilib.cc:58
void reset(uint64 target_stamp) override
Reset all state variables.
Definition midilib.cc:85
#define ASE_UNLIKELY(expr)
Compiler hint to optimize for expr evaluating to false.
Definition cxxaux.hh:46
#define ASE_ISLIKELY(expr)
Compiler hint to optimize for expr evaluating to true.
Definition cxxaux.hh:45
#define assert_paranoid(expr)
Issue an assertion warning if expr evaluates to false, check might be disabled in production.
Definition internal.hh:35
std::vector< T >::iterator insert_sorted(std::vector< T > &vec, const T &value, Compare compare)
Insert value into sorted vec using binary_lookup_insertion_pos() with compare.
Definition utils.hh:376
The Anklang C++ API namespace.
Definition api.hh:9
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:25
SpeakerArrangement
Definition transport.hh:11
int64_t int64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:29
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
Detailed information and common properties of AudioProcessor subclasses.
Definition processor.hh:29
typedef int64_t
Transport information for AudioSignal processing.
Definition transport.hh:113
float current_bpm
Running tempo in beats per minute.
Definition transport.hh:129
Reference for an allocated memory block.
Definition memory.hh:90
MidiEvent data structure.
Definition midievent.hh:50
MidiEventType type
MidiEvent type, one of the MidiEventType members.
Definition midievent.hh:55
T swap(T... args)
typedef ssize_t