1 /*
2  * Copyright (C) 2014-2016 David Robillard <d@drobilla.net>
3  * Copyright (C) 2014-2017 Paul Davis <paul@linuxaudiosystems.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include "evoral/Event.h"
21 #include "midi++/channel.h"
22 #include "midi++/parser.h"
23 #include "midi++/port.h"
24 
25 #include "ardour/async_midi_port.h"
26 #include "ardour/event_type_map.h"
27 #include "ardour/midi_buffer.h"
28 #include "ardour/midi_port.h"
29 #include "ardour/midi_scene_change.h"
30 #include "ardour/midi_scene_changer.h"
31 #include "ardour/session.h"
32 
33 #include "pbd/i18n.h"
34 
35 using namespace ARDOUR;
36 
MIDISceneChanger(Session & s)37 MIDISceneChanger::MIDISceneChanger (Session& s)
38 	: SceneChanger (s)
39 	, _recording (true)
40 	, have_seen_bank_changes (false)
41 	, last_program_message_time (-1)
42 	, last_delivered_program (-1)
43 	, last_delivered_bank (-1)
44 
45 {
46 	/* catch any add/remove/clear etc. for all Locations */
47 	_session.locations()->changed.connect_same_thread (*this, boost::bind (&MIDISceneChanger::locations_changed, this));
48 	_session.locations()->added.connect_same_thread (*this, boost::bind (&MIDISceneChanger::locations_changed, this));
49 	_session.locations()->removed.connect_same_thread (*this, boost::bind (&MIDISceneChanger::locations_changed, this));
50 
51 	/* catch class-based signal that notifies of us changes in the scene change state of any Location */
52 	Location::scene_changed.connect_same_thread (*this, boost::bind (&MIDISceneChanger::locations_changed, this));
53 }
54 
~MIDISceneChanger()55 MIDISceneChanger::~MIDISceneChanger ()
56 {
57 }
58 
59 void
locations_changed()60 MIDISceneChanger::locations_changed ()
61 {
62 	_session.locations()->apply (*this, &MIDISceneChanger::gather);
63 }
64 
65 /** Use the session's list of locations to collect all patch changes.
66  *
67  * This is called whenever the locations change in anyway.
68  */
69 void
gather(const Locations::LocationList & locations)70 MIDISceneChanger::gather (const Locations::LocationList& locations)
71 {
72 	boost::shared_ptr<SceneChange> sc;
73 
74 	Glib::Threads::RWLock::WriterLock lm (scene_lock);
75 
76 	scenes.clear ();
77 
78 	for (Locations::LocationList::const_iterator l = locations.begin(); l != locations.end(); ++l) {
79 
80 		if ((sc = (*l)->scene_change()) != 0) {
81 
82 			boost::shared_ptr<MIDISceneChange> msc = boost::dynamic_pointer_cast<MIDISceneChange> (sc);
83 
84 			if (msc) {
85 
86 				if (msc->bank() >= 0) {
87 					have_seen_bank_changes = true;
88 				}
89 
90 				scenes.insert (std::make_pair ((*l)->start(), msc));
91 			}
92 		}
93 	}
94 }
95 
96 void
rt_deliver(MidiBuffer & mbuf,samplepos_t when,boost::shared_ptr<MIDISceneChange> msc)97 MIDISceneChanger::rt_deliver (MidiBuffer& mbuf, samplepos_t when, boost::shared_ptr<MIDISceneChange> msc)
98 {
99         if (!msc->active()) {
100                 return;
101         }
102 
103 	uint8_t buf[4];
104 	size_t cnt;
105 
106 	MIDIOutputActivity (); /* EMIT SIGNAL */
107 
108 	if ((cnt = msc->get_bank_msb_message (buf, sizeof (buf))) > 0) {
109 		mbuf.push_back (when, Evoral::MIDI_EVENT, cnt, buf);
110 
111 		if ((cnt = msc->get_bank_lsb_message (buf, sizeof (buf))) > 0) {
112 			mbuf.push_back (when, Evoral::MIDI_EVENT, cnt, buf);
113 		}
114 
115 		last_delivered_bank = msc->bank();
116 	}
117 
118 	if ((cnt = msc->get_program_message (buf, sizeof (buf))) > 0) {
119 		mbuf.push_back (when, Evoral::MIDI_EVENT, cnt, buf);
120 
121 		last_delivered_program = msc->program();
122 	}
123 }
124 
125 void
non_rt_deliver(boost::shared_ptr<MIDISceneChange> msc)126 MIDISceneChanger::non_rt_deliver (boost::shared_ptr<MIDISceneChange> msc)
127 {
128         if (!msc->active()) {
129                 return;
130         }
131 
132 	uint8_t buf[4];
133 	size_t cnt;
134 	boost::shared_ptr<AsyncMIDIPort> aport = boost::dynamic_pointer_cast<AsyncMIDIPort>(output_port);
135 
136 	/* We use zero as the timestamp for these messages because we are in a
137 	   non-RT/process context. Using zero means "deliver them as early as
138 	   possible" (practically speaking, in the next process callback).
139 	*/
140 
141 	MIDIOutputActivity (); /* EMIT SIGNAL */
142 
143 	if ((cnt = msc->get_bank_msb_message (buf, sizeof (buf))) > 0) {
144 		aport->write (buf, cnt, 0);
145 
146 		if ((cnt = msc->get_bank_lsb_message (buf, sizeof (buf))) > 0) {
147 			aport->write (buf, cnt, 0);
148 		}
149 
150 		last_delivered_bank = msc->bank();
151 	}
152 
153 	if ((cnt = msc->get_program_message (buf, sizeof (buf))) > 0) {
154 		aport->write (buf, cnt, 0);
155 		last_delivered_program = msc->program();
156 	}
157 }
158 
159 void
run(samplepos_t start,samplepos_t end)160 MIDISceneChanger::run (samplepos_t start, samplepos_t end)
161 {
162 	if (!output_port || recording() || !_session.transport_rolling()) {
163 		return;
164 	}
165 
166 	Glib::Threads::RWLock::ReaderLock lm (scene_lock, Glib::Threads::TRY_LOCK);
167 
168 	if (!lm.locked()) {
169 		return;
170 	}
171 
172 	/* get lower bound of events to consider */
173 
174 	Scenes::const_iterator i = scenes.lower_bound (start);
175 	MidiBuffer& mbuf (output_port->get_midi_buffer (end-start));
176 
177 	while (i != scenes.end()) {
178 
179 		if (i->first >= end) {
180 			break;
181 		}
182 
183 		rt_deliver (mbuf, i->first - start, i->second);
184 
185 		++i;
186 	}
187 }
188 
189 void
locate(samplepos_t pos)190 MIDISceneChanger::locate (samplepos_t pos)
191 {
192 	boost::shared_ptr<MIDISceneChange> msc;
193 
194 	{
195 		Glib::Threads::RWLock::ReaderLock lm (scene_lock);
196 
197 		if (scenes.empty()) {
198 			return;
199 		}
200 
201 		Scenes::const_iterator i = scenes.lower_bound (pos);
202 
203 		if (i != scenes.end()) {
204 
205 			if (i->first != pos) {
206 				/* i points to first scene with position > pos, so back
207 				 * up, if possible.
208 				 */
209 				if (i != scenes.begin()) {
210 					--i;
211 				} else {
212 					return;
213 				}
214 			}
215 		} else {
216 			/* go back to the final scene and use it */
217 			--i;
218 		}
219 
220 		msc = i->second;
221 	}
222 
223 	if (msc->program() != last_delivered_program || msc->bank() != last_delivered_bank) {
224 		non_rt_deliver (msc);
225 	}
226 }
227 
228 void
set_input_port(boost::shared_ptr<MidiPort> mp)229 MIDISceneChanger::set_input_port (boost::shared_ptr<MidiPort> mp)
230 {
231 	incoming_connections.drop_connections();
232 	input_port.reset ();
233 
234 	boost::shared_ptr<AsyncMIDIPort> async = boost::dynamic_pointer_cast<AsyncMIDIPort> (mp);
235 
236 	if (async) {
237 
238 		input_port = mp;
239 
240 		/* midi port is asynchronous. MIDI parsing will be carried out
241 		 * by the MIDI UI thread which will emit the relevant signals
242 		 * and thus invoke our callbacks as necessary.
243 		 */
244 
245 		for (int channel = 0; channel < 16; ++channel) {
246 			async->parser()->channel_bank_change[channel].connect_same_thread (incoming_connections, boost::bind (&MIDISceneChanger::bank_change_input, this, _1, _2, channel));
247 			async->parser()->channel_program_change[channel].connect_same_thread (incoming_connections, boost::bind (&MIDISceneChanger::program_change_input, this, _1, _2, channel));
248 		}
249 	}
250 }
251 
252 void
set_output_port(boost::shared_ptr<MidiPort> mp)253 MIDISceneChanger::set_output_port (boost::shared_ptr<MidiPort> mp)
254 {
255 	output_port = mp;
256 }
257 
258 void
set_recording(bool yn)259 MIDISceneChanger::set_recording (bool yn)
260 {
261 	_recording = yn;
262 }
263 
264 bool
recording() const265 MIDISceneChanger::recording() const
266 {
267 	return _session.transport_rolling() && _session.get_record_enabled();
268 }
269 
270 void
bank_change_input(MIDI::Parser &,unsigned short,int)271   MIDISceneChanger::bank_change_input (MIDI::Parser& /*parser*/, unsigned short, int)
272 {
273 	if (recording()) {
274 		have_seen_bank_changes = true;
275 	}
276 	MIDIInputActivity (); /* EMIT SIGNAL */
277 }
278 
279 void
program_change_input(MIDI::Parser & parser,MIDI::byte program,int channel)280 MIDISceneChanger::program_change_input (MIDI::Parser& parser, MIDI::byte program, int channel)
281 {
282 	samplecnt_t time = parser.get_timestamp ();
283 
284 	last_program_message_time = time;
285 
286 	if (!recording()) {
287 
288 		MIDIInputActivity (); /* EMIT SIGNAL */
289 
290 		int bank = -1;
291 		if (have_seen_bank_changes) {
292 			bank = boost::dynamic_pointer_cast<AsyncMIDIPort>(input_port)->channel (channel)->bank();
293 		}
294 
295 		jump_to (bank, program);
296 		return;
297 	}
298 
299 	Locations* locations (_session.locations ());
300 	Location* loc;
301 	bool new_mark = false;
302 
303 	/* check for marker at current location */
304 
305 	loc = locations->mark_at (time, Config->get_inter_scene_gap_samples());
306 
307 	if (!loc) {
308 		/* create a new marker at the desired position */
309 
310 		std::string new_name;
311 
312 		if (!locations->next_available_name (new_name, _("Scene "))) {
313 			std::cerr << "No new marker name available\n";
314 			return;
315 		}
316 
317 		loc = new Location (_session, time, time, new_name, Location::IsMark, 0);
318 		new_mark = true;
319 	}
320 
321 	int bank = -1;
322 	if (have_seen_bank_changes) {
323 		bank = boost::dynamic_pointer_cast<AsyncMIDIPort>(input_port)->channel (channel)->bank();
324 	}
325 
326 	MIDISceneChange* msc =new MIDISceneChange (channel, bank, program & 0x7f);
327 
328 	/* check for identical scene change so we can re-use color, if any */
329 
330 	Locations::LocationList copy (locations->list());
331 
332 	for (Locations::LocationList::const_iterator l = copy.begin(); l != copy.end(); ++l) {
333 		boost::shared_ptr<MIDISceneChange> sc = boost::dynamic_pointer_cast<MIDISceneChange>((*l)->scene_change());
334 
335 		if (sc && (*sc.get()) == *msc) {
336 			msc->set_color (sc->color ());
337 			break;
338 		}
339 	}
340 
341 	loc->set_scene_change (boost::shared_ptr<MIDISceneChange> (msc));
342 
343 	/* this will generate a "changed" signal to be emitted by locations,
344 	   and we will call ::gather() to update our list of MIDI events.
345 	*/
346 
347 	if (new_mark) {
348 		locations->add (loc);
349 	}
350 
351 	MIDIInputActivity (); /* EMIT SIGNAL */
352 }
353 
354 void
jump_to(int bank,int program)355 MIDISceneChanger::jump_to (int bank, int program)
356 {
357 	const Locations::LocationList& locations (_session.locations()->list());
358 	boost::shared_ptr<SceneChange> sc;
359 	samplepos_t where = max_samplepos;
360 
361 	for (Locations::LocationList::const_iterator l = locations.begin(); l != locations.end(); ++l) {
362 
363 		if ((sc = (*l)->scene_change()) != 0) {
364 
365 			boost::shared_ptr<MIDISceneChange> msc = boost::dynamic_pointer_cast<MIDISceneChange> (sc);
366 
367 			if (msc->bank() == bank && msc->program() == program && (*l)->start() < where) {
368 				where = (*l)->start();
369 			}
370 		}
371 	}
372 
373 	if (where != max_samplepos) {
374 		_session.request_locate (where);
375 	}
376 }
377