Merge lp:~zequence/ubuntu/utopic/ardour3/fix-for-1450992 into lp:ubuntu/utopic/ardour3
- Utopic (14.10)
- fix-for-1450992
- Merge into utopic
Proposed by
Kaj Ailomaa
Status: | Needs review |
---|---|
Proposed branch: | lp:~zequence/ubuntu/utopic/ardour3/fix-for-1450992 |
Merge into: | lp:ubuntu/utopic/ardour3 |
Diff against target: |
2954 lines (+1560/-389) 39 files modified
.pc/applied-patches (+1/-0) .pc/midi-data-loss.patch/libs/ardour/smf_source.cc (+764/-0) debian/changelog (+20/-0) debian/control (+1/-1) debian/control.in (+1/-1) debian/patches/midi-data-loss.patch (+62/-0) debian/patches/series (+1/-0) gtk2_ardour/add_video_dialog.cc (+4/-1) gtk2_ardour/editor.h (+1/-1) gtk2_ardour/editor_audio_import.cc (+30/-18) gtk2_ardour/editor_rulers.cc (+15/-15) gtk2_ardour/midi_time_axis.cc (+41/-1) gtk2_ardour/mnemonic-us.bindings.in (+1/-1) gtk2_ardour/video_timeline.cc (+33/-4) libs/ardour/ardour/audiofilesource.h (+6/-0) libs/ardour/ardour/file_source.h (+4/-0) libs/ardour/ardour/session.h (+6/-8) libs/ardour/ardour/smf_source.h (+0/-9) libs/ardour/ardour/sndfilesource.h (+10/-1) libs/ardour/ardour/source.h (+2/-1) libs/ardour/ardour/source_factory.h (+3/-0) libs/ardour/audio_diskstream.cc (+26/-12) libs/ardour/audiofilesource.cc (+17/-0) libs/ardour/diskstream.cc (+0/-1) libs/ardour/enums.cc (+1/-0) libs/ardour/file_source.cc (+35/-11) libs/ardour/filter.cc (+2/-3) libs/ardour/import.cc (+26/-76) libs/ardour/midi_diskstream.cc (+2/-2) libs/ardour/session.cc (+100/-120) libs/ardour/session_state.cc (+49/-41) libs/ardour/smf_source.cc (+78/-52) libs/ardour/sndfilesource.cc (+36/-0) libs/ardour/source_factory.cc (+33/-0) libs/ardour/tempo.cc (+10/-5) libs/midi++2/midnam_patch.cc (+3/-4) patchfiles/Korg_Volca_Bass.midnam (+37/-0) patchfiles/Korg_Volca_Beats.midnam (+58/-0) patchfiles/Korg_Volca_Keys.midnam (+41/-0) |
To merge this branch: | bzr merge lp:~zequence/ubuntu/utopic/ardour3/fix-for-1450992 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Micah Gersten (community) | Disapprove | ||
Review via email: mp+258100@code.launchpad.net |
Commit message
Description of the change
Fix for bug 1450992.
This is the last release of ardour3.
To post a comment you must log in.
Unmerged revisions
- 15. By Adrian Knoth
-
Fix MIDI data loss when editing (Closes: #772118)
- 14. By Adrian Knoth
-
Reupload with waf fixes from 3.5.380~dfsg-2 and -3
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.pc/applied-patches' |
2 | --- .pc/applied-patches 2014-07-11 12:46:37 +0000 |
3 | +++ .pc/applied-patches 2015-05-02 14:25:06 +0000 |
4 | @@ -1,3 +1,4 @@ |
5 | waf.patch |
6 | wscript.patch |
7 | log-stdout.patch |
8 | +midi-data-loss.patch |
9 | |
10 | === added directory '.pc/midi-data-loss.patch' |
11 | === added directory '.pc/midi-data-loss.patch/libs' |
12 | === added directory '.pc/midi-data-loss.patch/libs/ardour' |
13 | === added file '.pc/midi-data-loss.patch/libs/ardour/smf_source.cc' |
14 | --- .pc/midi-data-loss.patch/libs/ardour/smf_source.cc 1970-01-01 00:00:00 +0000 |
15 | +++ .pc/midi-data-loss.patch/libs/ardour/smf_source.cc 2015-05-02 14:25:06 +0000 |
16 | @@ -0,0 +1,764 @@ |
17 | +/* |
18 | + Copyright (C) 2006 Paul Davis |
19 | + Author: David Robillard |
20 | + |
21 | + This program is free software; you can redistribute it and/or modify |
22 | + it under the terms of the GNU General Public License as published by |
23 | + the Free Software Foundation; either version 2 of the License, or |
24 | + (at your option) any later version. |
25 | + |
26 | + This program is distributed in the hope that it will be useful, |
27 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
28 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
29 | + GNU General Public License for more details. |
30 | + |
31 | + You should have received a copy of the GNU General Public License |
32 | + along with this program; if not, write to the Free Software |
33 | + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
34 | + |
35 | +*/ |
36 | + |
37 | +#include <vector> |
38 | + |
39 | +#include <sys/time.h> |
40 | +#include <sys/stat.h> |
41 | +#include <unistd.h> |
42 | +#include <errno.h> |
43 | +#include <regex.h> |
44 | + |
45 | +#include "pbd/pathscanner.h" |
46 | +#include "pbd/stl_delete.h" |
47 | +#include "pbd/strsplit.h" |
48 | + |
49 | +#include <glibmm/miscutils.h> |
50 | +#include <glibmm/fileutils.h> |
51 | + |
52 | +#include "evoral/Control.hpp" |
53 | +#include "evoral/evoral/SMF.hpp" |
54 | + |
55 | +#include "ardour/event_type_map.h" |
56 | +#include "ardour/midi_model.h" |
57 | +#include "ardour/midi_ring_buffer.h" |
58 | +#include "ardour/midi_state_tracker.h" |
59 | +#include "ardour/session.h" |
60 | +#include "ardour/smf_source.h" |
61 | +#include "ardour/debug.h" |
62 | + |
63 | +#include "i18n.h" |
64 | + |
65 | +using namespace ARDOUR; |
66 | +using namespace Glib; |
67 | +using namespace PBD; |
68 | +using namespace Evoral; |
69 | + |
70 | +/** Constructor used for new internal-to-session files. File cannot exist. */ |
71 | +SMFSource::SMFSource (Session& s, const string& path, Source::Flag flags) |
72 | + : Source(s, DataType::MIDI, path, flags) |
73 | + , MidiSource(s, path, flags) |
74 | + , FileSource(s, DataType::MIDI, path, string(), flags) |
75 | + , Evoral::SMF() |
76 | + , _last_ev_time_beats(0.0) |
77 | + , _last_ev_time_frames(0) |
78 | + , _smf_last_read_end (0) |
79 | + , _smf_last_read_time (0) |
80 | +{ |
81 | + /* note that origin remains empty */ |
82 | + |
83 | + if (init (_path, false)) { |
84 | + throw failed_constructor (); |
85 | + } |
86 | + |
87 | + assert (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)); |
88 | + existence_check (); |
89 | + |
90 | + _flags = Source::Flag (_flags | Empty); |
91 | + |
92 | + /* file is not opened until write */ |
93 | + |
94 | + if (flags & Writable) { |
95 | + return; |
96 | + } |
97 | + |
98 | + if (open (_path)) { |
99 | + throw failed_constructor (); |
100 | + } |
101 | + |
102 | + _open = true; |
103 | +} |
104 | + |
105 | +/** Constructor used for external-to-session files. File must exist. */ |
106 | +SMFSource::SMFSource (Session& s, const string& path) |
107 | + : Source(s, DataType::MIDI, path, Source::Flag (0)) |
108 | + , MidiSource(s, path, Source::Flag (0)) |
109 | + , FileSource(s, DataType::MIDI, path, string(), Source::Flag (0)) |
110 | + , Evoral::SMF() |
111 | + , _last_ev_time_beats(0.0) |
112 | + , _last_ev_time_frames(0) |
113 | + , _smf_last_read_end (0) |
114 | + , _smf_last_read_time (0) |
115 | +{ |
116 | + /* note that origin remains empty */ |
117 | + |
118 | + if (init (_path, true)) { |
119 | + throw failed_constructor (); |
120 | + } |
121 | + |
122 | + assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)); |
123 | + existence_check (); |
124 | + |
125 | + if (_flags & Writable) { |
126 | + /* file is not opened until write */ |
127 | + return; |
128 | + } |
129 | + |
130 | + if (open (_path)) { |
131 | + throw failed_constructor (); |
132 | + } |
133 | + |
134 | + _open = true; |
135 | +} |
136 | + |
137 | +/** Constructor used for existing internal-to-session files. */ |
138 | +SMFSource::SMFSource (Session& s, const XMLNode& node, bool must_exist) |
139 | + : Source(s, node) |
140 | + , MidiSource(s, node) |
141 | + , FileSource(s, node, must_exist) |
142 | + , _last_ev_time_beats(0.0) |
143 | + , _last_ev_time_frames(0) |
144 | + , _smf_last_read_end (0) |
145 | + , _smf_last_read_time (0) |
146 | +{ |
147 | + if (set_state(node, Stateful::loading_state_version)) { |
148 | + throw failed_constructor (); |
149 | + } |
150 | + |
151 | + /* we expect the file to exist, but if no MIDI data was ever added |
152 | + it will have been removed at last session close. so, we don't |
153 | + require it to exist if it was marked Empty. |
154 | + */ |
155 | + |
156 | + try { |
157 | + |
158 | + if (init (_path, true)) { |
159 | + throw failed_constructor (); |
160 | + } |
161 | + |
162 | + } catch (MissingSource& err) { |
163 | + |
164 | + if (_flags & Source::Empty) { |
165 | + /* we don't care that the file was not found, because |
166 | + it was empty. But FileSource::init() will have |
167 | + failed to set our _path correctly, so we have to do |
168 | + this ourselves. Use the first entry in the search |
169 | + path for MIDI files, which is assumed to be the |
170 | + correct "main" location. |
171 | + */ |
172 | + std::vector<Glib::ustring> sdirs; |
173 | + split (s.source_search_path (DataType::MIDI), sdirs, ':'); |
174 | + if (sdirs.empty()) { |
175 | + fatal << _("Empty MIDI source search path!") << endmsg; |
176 | + /*NOTREACHED*/ |
177 | + } |
178 | + _path = Glib::build_filename (sdirs.front(), _path); |
179 | + /* This might be important, too */ |
180 | + _file_is_new = true; |
181 | + } else { |
182 | + /* pass it on */ |
183 | + throw; |
184 | + } |
185 | + } |
186 | + |
187 | + if (!(_flags & Source::Empty)) { |
188 | + assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)); |
189 | + existence_check (); |
190 | + } else { |
191 | + assert (_flags & Source::Writable); |
192 | + /* file will be opened on write */ |
193 | + return; |
194 | + } |
195 | + |
196 | + if (open(_path)) { |
197 | + throw failed_constructor (); |
198 | + } |
199 | + |
200 | + _open = true; |
201 | +} |
202 | + |
203 | +SMFSource::~SMFSource () |
204 | +{ |
205 | + if (removable()) { |
206 | + unlink (_path.c_str()); |
207 | + } |
208 | +} |
209 | + |
210 | +int |
211 | +SMFSource::open_for_write () |
212 | +{ |
213 | + if (create (_path)) { |
214 | + return -1; |
215 | + } |
216 | + _open = true; |
217 | + return 0; |
218 | +} |
219 | + |
220 | +/** All stamps in audio frames */ |
221 | +framecnt_t |
222 | +SMFSource::read_unlocked (Evoral::EventSink<framepos_t>& destination, |
223 | + framepos_t const source_start, |
224 | + framepos_t start, |
225 | + framecnt_t duration, |
226 | + MidiStateTracker* tracker) const |
227 | +{ |
228 | + int ret = 0; |
229 | + uint64_t time = 0; // in SMF ticks, 1 tick per _ppqn |
230 | + |
231 | + if (writable() && !_open) { |
232 | + /* nothing to read since nothing has ben written */ |
233 | + return duration; |
234 | + } |
235 | + |
236 | + DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: start %1 duration %2\n", start, duration)); |
237 | + |
238 | + // Output parameters for read_event (which will allocate scratch in buffer as needed) |
239 | + uint32_t ev_delta_t = 0; |
240 | + uint32_t ev_type = 0; |
241 | + uint32_t ev_size = 0; |
242 | + uint8_t* ev_buffer = 0; |
243 | + |
244 | + size_t scratch_size = 0; // keep track of scratch to minimize reallocs |
245 | + |
246 | + BeatsFramesConverter converter(_session.tempo_map(), source_start); |
247 | + |
248 | + const uint64_t start_ticks = (uint64_t)(converter.from(start) * ppqn()); |
249 | + DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: start in ticks %1\n", start_ticks)); |
250 | + |
251 | + if (_smf_last_read_end == 0 || start != _smf_last_read_end) { |
252 | + DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: seek to %1\n", start)); |
253 | + Evoral::SMF::seek_to_start(); |
254 | + while (time < start_ticks) { |
255 | + gint ignored; |
256 | + |
257 | + ret = read_event(&ev_delta_t, &ev_size, &ev_buffer, &ignored); |
258 | + if (ret == -1) { // EOF |
259 | + _smf_last_read_end = start + duration; |
260 | + return duration; |
261 | + } |
262 | + time += ev_delta_t; // accumulate delta time |
263 | + } |
264 | + } else { |
265 | + DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: set time to %1\n", _smf_last_read_time)); |
266 | + time = _smf_last_read_time; |
267 | + } |
268 | + |
269 | + _smf_last_read_end = start + duration; |
270 | + |
271 | + while (true) { |
272 | + gint ignored; /* XXX don't ignore note id's ??*/ |
273 | + |
274 | + ret = read_event(&ev_delta_t, &ev_size, &ev_buffer, &ignored); |
275 | + if (ret == -1) { // EOF |
276 | + break; |
277 | + } |
278 | + |
279 | + time += ev_delta_t; // accumulate delta time |
280 | + _smf_last_read_time = time; |
281 | + |
282 | + if (ret == 0) { // meta-event (skipped, just accumulate time) |
283 | + continue; |
284 | + } |
285 | + |
286 | + ev_type = EventTypeMap::instance().midi_event_type(ev_buffer[0]); |
287 | + |
288 | + DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked delta %1, time %2, buf[0] %3, type %4\n", |
289 | + ev_delta_t, time, ev_buffer[0], ev_type)); |
290 | + |
291 | + assert(time >= start_ticks); |
292 | + |
293 | + /* Note that we add on the source start time (in session frames) here so that ev_frame_time |
294 | + is in session frames. |
295 | + */ |
296 | + const framepos_t ev_frame_time = converter.to(time / (double)ppqn()) + source_start; |
297 | + |
298 | + if (ev_frame_time < start + duration) { |
299 | + destination.write (ev_frame_time, ev_type, ev_size, ev_buffer); |
300 | + |
301 | + if (tracker) { |
302 | + if (ev_buffer[0] & MIDI_CMD_NOTE_ON) { |
303 | + tracker->add (ev_buffer[1], ev_buffer[0] & 0xf); |
304 | + } else if (ev_buffer[0] & MIDI_CMD_NOTE_OFF) { |
305 | + tracker->remove (ev_buffer[1], ev_buffer[0] & 0xf); |
306 | + } |
307 | + } |
308 | + } else { |
309 | + break; |
310 | + } |
311 | + |
312 | + if (ev_size > scratch_size) { |
313 | + scratch_size = ev_size; |
314 | + } |
315 | + ev_size = scratch_size; // ensure read_event only allocates if necessary |
316 | + } |
317 | + |
318 | + return duration; |
319 | +} |
320 | + |
321 | +framecnt_t |
322 | +SMFSource::write_unlocked (MidiRingBuffer<framepos_t>& source, |
323 | + framepos_t position, |
324 | + framecnt_t cnt) |
325 | +{ |
326 | + if (!_writing) { |
327 | + mark_streaming_write_started (); |
328 | + } |
329 | + |
330 | + framepos_t time; |
331 | + Evoral::EventType type; |
332 | + uint32_t size; |
333 | + |
334 | + size_t buf_capacity = 4; |
335 | + uint8_t* buf = (uint8_t*)malloc(buf_capacity); |
336 | + |
337 | + if (_model && !_model->writing()) { |
338 | + _model->start_write(); |
339 | + } |
340 | + |
341 | + Evoral::MIDIEvent<framepos_t> ev; |
342 | + while (true) { |
343 | + /* Get the event time, in frames since session start but ignoring looping. */ |
344 | + bool ret; |
345 | + if (!(ret = source.peek ((uint8_t*)&time, sizeof (time)))) { |
346 | + /* Ring is empty, no more events. */ |
347 | + break; |
348 | + } |
349 | + |
350 | + if ((cnt != max_framecnt) && |
351 | + (time > position + _capture_length + cnt)) { |
352 | + /* The diskstream doesn't want us to write everything, and this |
353 | + event is past the end of this block, so we're done for now. */ |
354 | + break; |
355 | + } |
356 | + |
357 | + /* Read the time, type, and size of the event. */ |
358 | + if (!(ret = source.read_prefix (&time, &type, &size))) { |
359 | + error << _("Unable to read event prefix, corrupt MIDI ring") << endmsg; |
360 | + break; |
361 | + } |
362 | + |
363 | + /* Enlarge body buffer if necessary now that we know the size. */ |
364 | + if (size > buf_capacity) { |
365 | + buf_capacity = size; |
366 | + buf = (uint8_t*)realloc(buf, size); |
367 | + } |
368 | + |
369 | + /* Read the event body into buffer. */ |
370 | + ret = source.read_contents(size, buf); |
371 | + if (!ret) { |
372 | + error << _("Event has time and size but no body, corrupt MIDI ring") << endmsg; |
373 | + break; |
374 | + } |
375 | + |
376 | + /* Convert event time from absolute to source relative. */ |
377 | + if (time < position) { |
378 | + error << _("Event time is before MIDI source position") << endmsg; |
379 | + break; |
380 | + } |
381 | + time -= position; |
382 | + |
383 | + ev.set(buf, size, time); |
384 | + ev.set_event_type(EventTypeMap::instance().midi_event_type(ev.buffer()[0])); |
385 | + ev.set_id(Evoral::next_event_id()); |
386 | + |
387 | + if (!(ev.is_channel_event() || ev.is_smf_meta_event() || ev.is_sysex())) { |
388 | + continue; |
389 | + } |
390 | + |
391 | + append_event_unlocked_frames(ev, position); |
392 | + } |
393 | + |
394 | + Evoral::SMF::flush (); |
395 | + free (buf); |
396 | + |
397 | + return cnt; |
398 | +} |
399 | + |
400 | +/** Append an event with a timestamp in beats (double) */ |
401 | +void |
402 | +SMFSource::append_event_unlocked_beats (const Evoral::Event<double>& ev) |
403 | +{ |
404 | + if (!_writing || ev.size() == 0) { |
405 | + return; |
406 | + } |
407 | + |
408 | + /*printf("SMFSource: %s - append_event_unlocked_beats ID = %d time = %lf, size = %u, data = ", |
409 | + name().c_str(), ev.id(), ev.time(), ev.size()); |
410 | + for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");*/ |
411 | + |
412 | + if (ev.time() < _last_ev_time_beats) { |
413 | + warning << string_compose(_("Skipping event with unordered time %1"), ev.time()) |
414 | + << endmsg; |
415 | + return; |
416 | + } |
417 | + |
418 | + Evoral::event_id_t event_id; |
419 | + |
420 | + if (ev.id() < 0) { |
421 | + event_id = Evoral::next_event_id(); |
422 | + } else { |
423 | + event_id = ev.id(); |
424 | + } |
425 | + |
426 | + if (_model) { |
427 | + _model->append (ev, event_id); |
428 | + } |
429 | + |
430 | + _length_beats = max(_length_beats, ev.time()); |
431 | + |
432 | + const double delta_time_beats = ev.time() - _last_ev_time_beats; |
433 | + const uint32_t delta_time_ticks = (uint32_t)lrint(delta_time_beats * (double)ppqn()); |
434 | + |
435 | + Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id); |
436 | + _last_ev_time_beats = ev.time(); |
437 | + _flags = Source::Flag (_flags & ~Empty); |
438 | +} |
439 | + |
440 | +/** Append an event with a timestamp in frames (framepos_t) */ |
441 | +void |
442 | +SMFSource::append_event_unlocked_frames (const Evoral::Event<framepos_t>& ev, framepos_t position) |
443 | +{ |
444 | + if (!_writing || ev.size() == 0) { |
445 | + return; |
446 | + } |
447 | + |
448 | + // printf("SMFSource: %s - append_event_unlocked_frames ID = %d time = %u, size = %u, data = ", |
449 | + // name().c_str(), ev.id(), ev.time(), ev.size()); |
450 | + // for (size_t i=0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n"); |
451 | + |
452 | + if (ev.time() < _last_ev_time_frames) { |
453 | + warning << string_compose(_("Skipping event with unordered time %1"), ev.time()) |
454 | + << endmsg; |
455 | + return; |
456 | + } |
457 | + |
458 | + BeatsFramesConverter converter(_session.tempo_map(), position); |
459 | + const double ev_time_beats = converter.from(ev.time()); |
460 | + Evoral::event_id_t event_id; |
461 | + |
462 | + if (ev.id() < 0) { |
463 | + event_id = Evoral::next_event_id(); |
464 | + } else { |
465 | + event_id = ev.id(); |
466 | + } |
467 | + |
468 | + if (_model) { |
469 | + const Evoral::Event<double> beat_ev (ev.event_type(), |
470 | + ev_time_beats, |
471 | + ev.size(), |
472 | + const_cast<uint8_t*>(ev.buffer())); |
473 | + _model->append (beat_ev, event_id); |
474 | + } |
475 | + |
476 | + _length_beats = max(_length_beats, ev_time_beats); |
477 | + |
478 | + const Evoral::MusicalTime last_time_beats = converter.from (_last_ev_time_frames); |
479 | + const Evoral::MusicalTime delta_time_beats = ev_time_beats - last_time_beats; |
480 | + const uint32_t delta_time_ticks = (uint32_t)(lrint(delta_time_beats * (double)ppqn())); |
481 | + |
482 | + Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id); |
483 | + _last_ev_time_frames = ev.time(); |
484 | + _flags = Source::Flag (_flags & ~Empty); |
485 | +} |
486 | + |
487 | +XMLNode& |
488 | +SMFSource::get_state () |
489 | +{ |
490 | + XMLNode& node = MidiSource::get_state(); |
491 | + node.add_property (X_("origin"), _origin); |
492 | + return node; |
493 | +} |
494 | + |
495 | +int |
496 | +SMFSource::set_state (const XMLNode& node, int version) |
497 | +{ |
498 | + if (Source::set_state (node, version)) { |
499 | + return -1; |
500 | + } |
501 | + |
502 | + if (MidiSource::set_state (node, version)) { |
503 | + return -1; |
504 | + } |
505 | + |
506 | + if (FileSource::set_state (node, version)) { |
507 | + return -1; |
508 | + } |
509 | + |
510 | + return 0; |
511 | +} |
512 | + |
513 | +void |
514 | +SMFSource::mark_streaming_midi_write_started (NoteMode mode) |
515 | +{ |
516 | + /* CALLER MUST HOLD LOCK */ |
517 | + |
518 | + if (!_open && open_for_write()) { |
519 | + error << string_compose (_("cannot open MIDI file %1 for write"), _path) << endmsg; |
520 | + /* XXX should probably throw or return something */ |
521 | + return; |
522 | + } |
523 | + |
524 | + MidiSource::mark_streaming_midi_write_started (mode); |
525 | + Evoral::SMF::begin_write (); |
526 | + _last_ev_time_beats = 0.0; |
527 | + _last_ev_time_frames = 0; |
528 | +} |
529 | + |
530 | +void |
531 | +SMFSource::mark_streaming_write_completed () |
532 | +{ |
533 | + mark_midi_streaming_write_completed (Evoral::Sequence<Evoral::MusicalTime>::DeleteStuckNotes); |
534 | +} |
535 | + |
536 | +void |
537 | +SMFSource::mark_midi_streaming_write_completed (Evoral::Sequence<Evoral::MusicalTime>::StuckNoteOption stuck_notes_option, Evoral::MusicalTime when) |
538 | +{ |
539 | + Glib::Threads::Mutex::Lock lm (_lock); |
540 | + MidiSource::mark_midi_streaming_write_completed (stuck_notes_option, when); |
541 | + |
542 | + if (!writable()) { |
543 | + warning << string_compose ("attempt to write to unwritable SMF file %1", _path) << endmsg; |
544 | + return; |
545 | + } |
546 | + |
547 | + if (_model) { |
548 | + _model->set_edited(false); |
549 | + } |
550 | + |
551 | + Evoral::SMF::end_write (); |
552 | + |
553 | + /* data in the file now, not removable */ |
554 | + |
555 | + mark_nonremovable (); |
556 | +} |
557 | + |
558 | +bool |
559 | +SMFSource::valid_midi_file (const string& file) |
560 | +{ |
561 | + if (safe_midi_file_extension (file) ) { |
562 | + return (SMF::test (file) ); |
563 | + } |
564 | + return false; |
565 | +} |
566 | + |
567 | +bool |
568 | +SMFSource::safe_midi_file_extension (const string& file) |
569 | +{ |
570 | + static regex_t compiled_pattern; |
571 | + static bool compile = true; |
572 | + const int nmatches = 2; |
573 | + regmatch_t matches[nmatches]; |
574 | + |
575 | + if (Glib::file_test (file, Glib::FILE_TEST_EXISTS)) { |
576 | + if (!Glib::file_test (file, Glib::FILE_TEST_IS_REGULAR)) { |
577 | + /* exists but is not a regular file */ |
578 | + return false; |
579 | + } |
580 | + } |
581 | + |
582 | + if (compile && regcomp (&compiled_pattern, "\\.[mM][iI][dD][iI]?$", REG_EXTENDED)) { |
583 | + return false; |
584 | + } else { |
585 | + compile = false; |
586 | + } |
587 | + |
588 | + if (regexec (&compiled_pattern, file.c_str(), nmatches, matches, 0)) { |
589 | + return false; |
590 | + } |
591 | + |
592 | + return true; |
593 | +} |
594 | + |
595 | +static bool compare_eventlist ( |
596 | + const std::pair< Evoral::Event<double>*, gint >& a, |
597 | + const std::pair< Evoral::Event<double>*, gint >& b) { |
598 | + return ( a.first->time() < b.first->time() ); |
599 | +} |
600 | + |
601 | +void |
602 | +SMFSource::load_model (bool lock, bool force_reload) |
603 | +{ |
604 | + if (_writing) { |
605 | + return; |
606 | + } |
607 | + |
608 | + boost::shared_ptr<Glib::Threads::Mutex::Lock> lm; |
609 | + if (lock) |
610 | + lm = boost::shared_ptr<Glib::Threads::Mutex::Lock>(new Glib::Threads::Mutex::Lock(_lock)); |
611 | + |
612 | + if (_model && !force_reload) { |
613 | + return; |
614 | + } |
615 | + |
616 | + if (!_model) { |
617 | + _model = boost::shared_ptr<MidiModel> (new MidiModel (shared_from_this ())); |
618 | + } else { |
619 | + _model->clear(); |
620 | + } |
621 | + |
622 | + if (writable() && !_open) { |
623 | + return; |
624 | + } |
625 | + |
626 | + _model->start_write(); |
627 | + Evoral::SMF::seek_to_start(); |
628 | + |
629 | + uint64_t time = 0; /* in SMF ticks */ |
630 | + Evoral::Event<double> ev; |
631 | + |
632 | + uint32_t scratch_size = 0; // keep track of scratch and minimize reallocs |
633 | + |
634 | + uint32_t delta_t = 0; |
635 | + uint32_t size = 0; |
636 | + uint8_t* buf = NULL; |
637 | + int ret; |
638 | + gint event_id; |
639 | + bool have_event_id; |
640 | + |
641 | + // TODO simplify event allocation |
642 | + std::list< std::pair< Evoral::Event<double>*, gint > > eventlist; |
643 | + |
644 | + for (unsigned i = 1; i <= num_tracks(); ++i) { |
645 | + if (seek_to_track(i)) continue; |
646 | + |
647 | + time = 0; |
648 | + have_event_id = false; |
649 | + |
650 | + while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) { |
651 | + |
652 | + time += delta_t; |
653 | + |
654 | + if (ret == 0) { |
655 | + /* meta-event : did we get an event ID ? */ |
656 | + if (event_id >= 0) { |
657 | + have_event_id = true; |
658 | + } |
659 | + continue; |
660 | + } |
661 | + |
662 | + if (ret > 0) { |
663 | + /* not a meta-event */ |
664 | + |
665 | + if (!have_event_id) { |
666 | + event_id = Evoral::next_event_id(); |
667 | + } |
668 | + uint32_t event_type = EventTypeMap::instance().midi_event_type(buf[0]); |
669 | + double event_time = time / (double) ppqn(); |
670 | +#ifndef NDEBUG |
671 | + std::string ss; |
672 | + |
673 | + for (uint32_t xx = 0; xx < size; ++xx) { |
674 | + char b[8]; |
675 | + snprintf (b, sizeof (b), "0x%x ", buf[xx]); |
676 | + ss += b; |
677 | + } |
678 | + |
679 | + DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF %6 load model delta %1, time %2, size %3 buf %4, type %5\n", |
680 | + delta_t, time, size, ss , event_type, name())); |
681 | +#endif |
682 | + |
683 | + eventlist.push_back(make_pair ( |
684 | + new Evoral::Event<double> ( |
685 | + event_type, event_time, |
686 | + size, buf, true) |
687 | + , event_id)); |
688 | + |
689 | + // Set size to max capacity to minimize allocs in read_event |
690 | + scratch_size = std::max(size, scratch_size); |
691 | + size = scratch_size; |
692 | + |
693 | + _length_beats = max(_length_beats, event_time); |
694 | + } |
695 | + |
696 | + /* event ID's must immediately precede the event they are for */ |
697 | + have_event_id = false; |
698 | + } |
699 | + } |
700 | + |
701 | + eventlist.sort(compare_eventlist); |
702 | + |
703 | + std::list< std::pair< Evoral::Event<double>*, gint > >::iterator it; |
704 | + for (it=eventlist.begin(); it!=eventlist.end(); ++it) { |
705 | + _model->append (*it->first, it->second); |
706 | + delete it->first; |
707 | + } |
708 | + |
709 | + _model->end_write (Evoral::Sequence<Evoral::MusicalTime>::ResolveStuckNotes, _length_beats); |
710 | + _model->set_edited (false); |
711 | + |
712 | + _model_iter = _model->begin(); |
713 | + |
714 | + free(buf); |
715 | +} |
716 | + |
717 | +void |
718 | +SMFSource::destroy_model () |
719 | +{ |
720 | + //cerr << _name << " destroying model " << _model.get() << endl; |
721 | + _model.reset(); |
722 | +} |
723 | + |
724 | +void |
725 | +SMFSource::flush_midi () |
726 | +{ |
727 | + if (!writable() || _length_beats == 0.0) { |
728 | + return; |
729 | + } |
730 | + |
731 | + ensure_disk_file (); |
732 | + |
733 | + Evoral::SMF::end_write (); |
734 | + /* data in the file means its no longer removable */ |
735 | + mark_nonremovable (); |
736 | +} |
737 | + |
738 | +void |
739 | +SMFSource::set_path (const string& p) |
740 | +{ |
741 | + FileSource::set_path (p); |
742 | + SMF::set_path (_path); |
743 | +} |
744 | + |
745 | +/** Ensure that this source has some file on disk, even if it's just a SMF header */ |
746 | +void |
747 | +SMFSource::ensure_disk_file () |
748 | +{ |
749 | + if (!writable()) { |
750 | + return; |
751 | + } |
752 | + |
753 | + if (_model) { |
754 | + /* We have a model, so write it to disk; see MidiSource::session_saved |
755 | + for an explanation of what we are doing here. |
756 | + */ |
757 | + boost::shared_ptr<MidiModel> mm = _model; |
758 | + _model.reset (); |
759 | + mm->sync_to_source (); |
760 | + _model = mm; |
761 | + } else { |
762 | + /* No model; if it's not already open, it's an empty source, so create |
763 | + and open it for writing. |
764 | + */ |
765 | + if (!_open) { |
766 | + open_for_write (); |
767 | + } |
768 | + } |
769 | +} |
770 | + |
771 | +void |
772 | +SMFSource::prevent_deletion () |
773 | +{ |
774 | + /* Unlike the audio case, the MIDI file remains mutable (because we can |
775 | + edit MIDI data) |
776 | + */ |
777 | + |
778 | + _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy)); |
779 | +} |
780 | + |
781 | |
782 | === modified file 'debian/changelog' |
783 | --- debian/changelog 2014-07-11 12:46:37 +0000 |
784 | +++ debian/changelog 2015-05-02 14:25:06 +0000 |
785 | @@ -1,3 +1,23 @@ |
786 | +ardour3 (3.5.403~dfsg-3) unstable; urgency=medium |
787 | + |
788 | + * Fix MIDI data loss when editing (Closes: #772118) |
789 | + |
790 | + -- Adrian Knoth <adi@drcomp.erfurt.thur.de> Thu, 04 Dec 2014 19:34:45 +0100 |
791 | + |
792 | +ardour3 (3.5.403~dfsg-2) unstable; urgency=high |
793 | + |
794 | + * Reupload with waf fixes from 3.5.380~dfsg-2 and -3 |
795 | + |
796 | + -- Adrian Knoth <adi@drcomp.erfurt.thur.de> Fri, 03 Oct 2014 14:01:07 +0200 |
797 | + |
798 | +ardour3 (3.5.403~dfsg-1) unstable; urgency=high |
799 | + |
800 | + * Imported Upstream version 3.5.403~dfsg |
801 | + * Critical bugfix release. All users are recommended to upgrade. |
802 | + * Details: https://community.ardour.org/node/8457 |
803 | + |
804 | + -- Adrian Knoth <adi@drcomp.erfurt.thur.de> Fri, 03 Oct 2014 13:02:45 +0200 |
805 | + |
806 | ardour3 (3.5.380~dfsg-3) unstable; urgency=medium |
807 | |
808 | * Prevent build failures in slower architectures by |
809 | |
810 | === modified file 'debian/control' |
811 | --- debian/control 2014-01-24 16:31:54 +0000 |
812 | +++ debian/control 2015-05-02 14:25:06 +0000 |
813 | @@ -49,7 +49,7 @@ |
814 | python-isodate, |
815 | libpcre3-dev, |
816 | python-rdflib |
817 | -Standards-Version: 3.9.4 |
818 | +Standards-Version: 3.9.6 |
819 | Homepage: http://www.ardour.org/ |
820 | Vcs-Git: git://anonscm.debian.org/pkg-multimedia/ardour3.git |
821 | Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-multimedia/ardour3.git |
822 | |
823 | === modified file 'debian/control.in' |
824 | --- debian/control.in 2014-01-24 16:31:54 +0000 |
825 | +++ debian/control.in 2015-05-02 14:25:06 +0000 |
826 | @@ -7,7 +7,7 @@ |
827 | JaromĂr Mikeš <mira.mikes@seznam.cz> |
828 | Build-Depends: |
829 | @cdbs@ |
830 | -Standards-Version: 3.9.4 |
831 | +Standards-Version: 3.9.6 |
832 | Homepage: http://www.ardour.org/ |
833 | Vcs-Git: git://anonscm.debian.org/pkg-multimedia/ardour3.git |
834 | Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-multimedia/ardour3.git |
835 | |
836 | === added file 'debian/patches/midi-data-loss.patch' |
837 | --- debian/patches/midi-data-loss.patch 1970-01-01 00:00:00 +0000 |
838 | +++ debian/patches/midi-data-loss.patch 2015-05-02 14:25:06 +0000 |
839 | @@ -0,0 +1,62 @@ |
840 | +From: David Robillard <d@drobilla.net> |
841 | +Description: Fix MIDI data loss when editing |
842 | +Forwarded: not-needed |
843 | +Last-Update: 2014-12-04 |
844 | +Bug-Vendor: http://tracker.ardour.org/view.php?id=5669 |
845 | +Applied-Upstream: 3.5.404; https://github.com/Ardour/ardour/commit/94954f852ead97bcda7afa548d543222733228ef |
846 | +--- ardour3.orig/libs/ardour/smf_source.cc |
847 | ++++ ardour3/libs/ardour/smf_source.cc |
848 | +@@ -393,10 +393,22 @@ SMFSource::append_event_unlocked_beats ( |
849 | + name().c_str(), ev.id(), ev.time(), ev.size()); |
850 | + for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");*/ |
851 | + |
852 | +- if (ev.time() < _last_ev_time_beats) { |
853 | +- warning << string_compose(_("Skipping event with unordered time %1"), ev.time()) |
854 | +- << endmsg; |
855 | +- return; |
856 | ++ double time = ev.time(); |
857 | ++ if (time < _last_ev_time_beats) { |
858 | ++ const double difference = _last_ev_time_beats - time; |
859 | ++ if (difference / (double)ppqn() < 1.0) { |
860 | ++ /* Close enough. This problem occurs because Sequence is not |
861 | ++ actually ordered due to fuzzy time comparison. I'm pretty sure |
862 | ++ this is inherently a bad idea which causes problems all over the |
863 | ++ place, but tolerate it here for now anyway. */ |
864 | ++ time = _last_ev_time_beats; |
865 | ++ } else { |
866 | ++ /* Out of order by more than a tick. */ |
867 | ++ warning << string_compose(_("Skipping event with unordered beat time %1 < %2 (off by %3 beats, %4 ticks)"), |
868 | ++ ev.time(), _last_ev_time_beats, difference, difference / (double)ppqn()) |
869 | ++ << endmsg; |
870 | ++ return; |
871 | ++ } |
872 | + } |
873 | + |
874 | + Evoral::event_id_t event_id; |
875 | +@@ -411,13 +423,13 @@ SMFSource::append_event_unlocked_beats ( |
876 | + _model->append (ev, event_id); |
877 | + } |
878 | + |
879 | +- _length_beats = max(_length_beats, ev.time()); |
880 | ++ _length_beats = max(_length_beats, time); |
881 | + |
882 | +- const double delta_time_beats = ev.time() - _last_ev_time_beats; |
883 | ++ const double delta_time_beats = time - _last_ev_time_beats; |
884 | + const uint32_t delta_time_ticks = (uint32_t)lrint(delta_time_beats * (double)ppqn()); |
885 | + |
886 | + Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id); |
887 | +- _last_ev_time_beats = ev.time(); |
888 | ++ _last_ev_time_beats = time; |
889 | + _flags = Source::Flag (_flags & ~Empty); |
890 | + } |
891 | + |
892 | +@@ -434,7 +446,8 @@ SMFSource::append_event_unlocked_frames |
893 | + // for (size_t i=0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n"); |
894 | + |
895 | + if (ev.time() < _last_ev_time_frames) { |
896 | +- warning << string_compose(_("Skipping event with unordered time %1"), ev.time()) |
897 | ++ warning << string_compose(_("Skipping event with unordered frame time %1 < %2"), |
898 | ++ ev.time(), _last_ev_time_frames) |
899 | + << endmsg; |
900 | + return; |
901 | + } |
902 | |
903 | === modified file 'debian/patches/series' |
904 | --- debian/patches/series 2014-07-11 12:46:37 +0000 |
905 | +++ debian/patches/series 2015-05-02 14:25:06 +0000 |
906 | @@ -1,3 +1,4 @@ |
907 | waf.patch |
908 | wscript.patch |
909 | log-stdout.patch |
910 | +midi-data-loss.patch |
911 | |
912 | === modified file 'gtk2_ardour/add_video_dialog.cc' |
913 | --- gtk2_ardour/add_video_dialog.cc 2013-09-21 19:05:02 +0000 |
914 | +++ gtk2_ardour/add_video_dialog.cc 2015-05-02 14:25:06 +0000 |
915 | @@ -258,7 +258,8 @@ |
916 | ".ogg" , ".OGG" , |
917 | ".ogv" , ".OGV" , |
918 | ".mpg" , ".MPG" , |
919 | - ".mov" , ".MOV" , |
920 | + ".mpeg" , ".MPEG" , |
921 | + ".mts" , ".MTS" , |
922 | ".mp4" , ".MP4" , |
923 | ".mkv" , ".MKV" , |
924 | ".vob" , ".VOB" , |
925 | @@ -272,6 +273,8 @@ |
926 | ".dv" , ".DV" , |
927 | ".dirac" , ".DIRAC" , |
928 | ".webm" , ".WEBM" , |
929 | + ".wmv" , ".wmv" , |
930 | + ".ts" , ".ts" , |
931 | }; |
932 | |
933 | for (size_t n = 0; n < sizeof(suffixes)/sizeof(suffixes[0]); ++n) { |
934 | |
935 | === modified file 'gtk2_ardour/editor.h' |
936 | --- gtk2_ardour/editor.h 2014-01-24 16:31:54 +0000 |
937 | +++ gtk2_ardour/editor.h 2015-05-02 14:25:06 +0000 |
938 | @@ -1262,7 +1262,7 @@ |
939 | int target_regions, int target_tracks, boost::shared_ptr<ARDOUR::Track>&, bool add_channel_suffix); |
940 | |
941 | int finish_bringing_in_material (boost::shared_ptr<ARDOUR::Region> region, uint32_t, uint32_t, framepos_t& pos, Editing::ImportMode mode, |
942 | - boost::shared_ptr<ARDOUR::Track>& existing_track); |
943 | + boost::shared_ptr<ARDOUR::Track>& existing_track, const std::string& new_track_name); |
944 | |
945 | boost::shared_ptr<ARDOUR::AudioTrack> get_nth_selected_audio_track (int nth) const; |
946 | boost::shared_ptr<ARDOUR::MidiTrack> get_nth_selected_midi_track (int nth) const; |
947 | |
948 | === modified file 'gtk2_ardour/editor_audio_import.cc' |
949 | --- gtk2_ardour/editor_audio_import.cc 2014-01-24 16:31:54 +0000 |
950 | +++ gtk2_ardour/editor_audio_import.cc 2015-05-02 14:25:06 +0000 |
951 | @@ -611,7 +611,7 @@ |
952 | |
953 | boost::shared_ptr<Source> s; |
954 | |
955 | - if ((s = _session->source_by_path_and_channel (path, n)) == 0) { |
956 | + if ((s = _session->audio_source_by_path_and_channel (path, n)) == 0) { |
957 | |
958 | source = boost::dynamic_pointer_cast<AudioFileSource> ( |
959 | SourceFactory::createExternal (DataType::AUDIO, *_session, |
960 | @@ -657,7 +657,8 @@ |
961 | uint32_t input_chan = 0; |
962 | uint32_t output_chan = 0; |
963 | bool use_timestamp; |
964 | - |
965 | + vector<string> track_names; |
966 | + |
967 | use_timestamp = (pos == -1); |
968 | |
969 | // kludge (for MIDI we're abusing "channel" for "track" here) |
970 | @@ -694,6 +695,11 @@ |
971 | |
972 | regions.push_back (r); |
973 | |
974 | + /* if we're creating a new track, name it after the cleaned-up |
975 | + * and "merged" region name. |
976 | + */ |
977 | + |
978 | + track_names.push_back (region_name); |
979 | |
980 | } else if (target_regions == -1 || target_regions > 1) { |
981 | |
982 | @@ -724,29 +730,26 @@ |
983 | region_name = (*x)->name(); |
984 | } |
985 | |
986 | - switch (sources.size()) { |
987 | - /* zero and one channel handled |
988 | - by previous if() condition |
989 | - */ |
990 | - case 2: |
991 | + if (sources.size() == 2) { |
992 | if (n == 0) { |
993 | region_name += "-L"; |
994 | } else { |
995 | region_name += "-R"; |
996 | } |
997 | - break; |
998 | - default: |
999 | - region_name += (char) '-'; |
1000 | - region_name += (char) ('1' + n); |
1001 | - break; |
1002 | + } else if (sources.size() > 2) { |
1003 | + region_name += string_compose ("-%1", n+1); |
1004 | } |
1005 | |
1006 | + track_names.push_back (region_name); |
1007 | + |
1008 | } else { |
1009 | if (fs) { |
1010 | region_name = region_name_from_path (fs->path(), false, false, sources.size(), n); |
1011 | - } else{ |
1012 | + } else { |
1013 | region_name = (*x)->name(); |
1014 | } |
1015 | + |
1016 | + track_names.push_back (PBD::basename_nosuffix (paths[n])); |
1017 | } |
1018 | |
1019 | PropertyList plist; |
1020 | @@ -798,6 +801,12 @@ |
1021 | framepos_t rlen = 0; |
1022 | |
1023 | begin_reversible_command (Operations::insert_file); |
1024 | + |
1025 | + /* we only use tracks names when importing to new tracks, but we |
1026 | + * require that one is defined for every region, just to keep |
1027 | + * the API simpler. |
1028 | + */ |
1029 | + assert (regions.size() == track_names.size()); |
1030 | |
1031 | for (vector<boost::shared_ptr<Region> >::iterator r = regions.begin(); r != regions.end(); ++r, ++n) { |
1032 | boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (*r); |
1033 | @@ -830,9 +839,8 @@ |
1034 | pos = get_preferred_edit_position (); |
1035 | } |
1036 | } |
1037 | - |
1038 | - |
1039 | - finish_bringing_in_material (*r, input_chan, output_chan, pos, mode, track); |
1040 | + |
1041 | + finish_bringing_in_material (*r, input_chan, output_chan, pos, mode, track, track_names[n]); |
1042 | |
1043 | rlen = (*r)->length(); |
1044 | |
1045 | @@ -859,7 +867,7 @@ |
1046 | |
1047 | int |
1048 | Editor::finish_bringing_in_material (boost::shared_ptr<Region> region, uint32_t in_chans, uint32_t out_chans, framepos_t& pos, |
1049 | - ImportMode mode, boost::shared_ptr<Track>& existing_track) |
1050 | + ImportMode mode, boost::shared_ptr<Track>& existing_track, const string& new_track_name) |
1051 | { |
1052 | boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion>(region); |
1053 | boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(region); |
1054 | @@ -916,7 +924,11 @@ |
1055 | existing_track = mt.front(); |
1056 | } |
1057 | |
1058 | - existing_track->set_name (region->name()); |
1059 | + if (!new_track_name.empty()) { |
1060 | + existing_track->set_name (new_track_name); |
1061 | + } else { |
1062 | + existing_track->set_name (region->name()); |
1063 | + } |
1064 | } |
1065 | |
1066 | boost::shared_ptr<Playlist> playlist = existing_track->playlist(); |
1067 | |
1068 | === modified file 'gtk2_ardour/editor_rulers.cc' |
1069 | --- gtk2_ardour/editor_rulers.cc 2013-09-21 19:05:02 +0000 |
1070 | +++ gtk2_ardour/editor_rulers.cc 2015-05-02 14:25:06 +0000 |
1071 | @@ -1862,13 +1862,13 @@ |
1072 | long millisecs; |
1073 | |
1074 | left = sample; |
1075 | - hrs = left / (sample_rate * 60 * 60); |
1076 | - left -= hrs * sample_rate * 60 * 60; |
1077 | - mins = left / (sample_rate * 60); |
1078 | - left -= mins * sample_rate * 60; |
1079 | - secs = left / sample_rate; |
1080 | - left -= secs * sample_rate; |
1081 | - millisecs = left * 1000 / sample_rate; |
1082 | + hrs = left / (sample_rate * 60 * 60 * 1000); |
1083 | + left -= hrs * sample_rate * 60 * 60 * 1000; |
1084 | + mins = left / (sample_rate * 60 * 1000); |
1085 | + left -= mins * sample_rate * 60 * 1000; |
1086 | + secs = left / (sample_rate * 1000); |
1087 | + left -= secs * sample_rate * 1000; |
1088 | + millisecs = left / sample_rate; |
1089 | |
1090 | *millisecs_p = millisecs; |
1091 | *secs_p = secs; |
1092 | @@ -1888,7 +1888,7 @@ |
1093 | return; |
1094 | } |
1095 | |
1096 | - fr = _session->frame_rate(); |
1097 | + fr = _session->frame_rate() * 1000; |
1098 | |
1099 | /* to prevent 'flashing' */ |
1100 | if (lower > (spacer = (framepos_t)(128 * Editor::get_current_zoom ()))) { |
1101 | @@ -1897,7 +1897,7 @@ |
1102 | lower = 0; |
1103 | } |
1104 | upper += spacer; |
1105 | - framecnt_t const range = upper - lower; |
1106 | + framecnt_t const range = (upper - lower) * 1000; |
1107 | |
1108 | if (range < (fr / 50)) { |
1109 | minsec_mark_interval = fr / 1000; /* show 1/1000 seconds */ |
1110 | @@ -1997,7 +1997,7 @@ |
1111 | } |
1112 | |
1113 | *marks = (GtkCustomRulerMark *) g_malloc (sizeof(GtkCustomRulerMark) * minsec_nmarks); |
1114 | - pos = ((((framepos_t) floor(lower)) + (minsec_mark_interval/2))/minsec_mark_interval) * minsec_mark_interval; |
1115 | + pos = (((1000 * (framepos_t) floor(lower)) + (minsec_mark_interval/2))/minsec_mark_interval) * minsec_mark_interval; |
1116 | switch (minsec_ruler_scale) { |
1117 | case minsec_show_seconds: |
1118 | for (n = 0; n < minsec_nmarks; pos += minsec_mark_interval, ++n) { |
1119 | @@ -2014,7 +2014,7 @@ |
1120 | (*marks)[n].style = GtkCustomRulerMarkMicro; |
1121 | } |
1122 | (*marks)[n].label = g_strdup (buf); |
1123 | - (*marks)[n].position = pos; |
1124 | + (*marks)[n].position = pos/1000.0; |
1125 | } |
1126 | break; |
1127 | case minsec_show_minutes: |
1128 | @@ -2032,7 +2032,7 @@ |
1129 | (*marks)[n].style = GtkCustomRulerMarkMicro; |
1130 | } |
1131 | (*marks)[n].label = g_strdup (buf); |
1132 | - (*marks)[n].position = pos; |
1133 | + (*marks)[n].position = pos/1000.0; |
1134 | } |
1135 | break; |
1136 | case minsec_show_hours: |
1137 | @@ -2046,14 +2046,14 @@ |
1138 | (*marks)[n].style = GtkCustomRulerMarkMicro; |
1139 | } |
1140 | (*marks)[n].label = g_strdup (buf); |
1141 | - (*marks)[n].position = pos; |
1142 | + (*marks)[n].position = pos/1000.0; |
1143 | } |
1144 | break; |
1145 | case minsec_show_frames: |
1146 | for (n = 0; n < minsec_nmarks; pos += minsec_mark_interval, ++n) { |
1147 | sample_to_clock_parts (pos, _session->frame_rate(), &hrs, &mins, &secs, &millisecs); |
1148 | if (millisecs % minsec_mark_modulo == 0) { |
1149 | - if (secs == 0) { |
1150 | + if (millisecs == 0) { |
1151 | (*marks)[n].style = GtkCustomRulerMarkMajor; |
1152 | } else { |
1153 | (*marks)[n].style = GtkCustomRulerMarkMinor; |
1154 | @@ -2064,7 +2064,7 @@ |
1155 | (*marks)[n].style = GtkCustomRulerMarkMicro; |
1156 | } |
1157 | (*marks)[n].label = g_strdup (buf); |
1158 | - (*marks)[n].position = pos; |
1159 | + (*marks)[n].position = pos/1000.0; |
1160 | } |
1161 | break; |
1162 | } |
1163 | |
1164 | === modified file 'gtk2_ardour/midi_time_axis.cc' |
1165 | --- gtk2_ardour/midi_time_axis.cc 2014-05-15 14:33:48 +0000 |
1166 | +++ gtk2_ardour/midi_time_axis.cc 2015-05-02 14:25:06 +0000 |
1167 | @@ -757,7 +757,7 @@ |
1168 | Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl); |
1169 | ctl_items.push_back ( |
1170 | CheckMenuElem ( |
1171 | - string_compose ("<b>%1</b>: %2 [%3]", ctl, name, int (chn)), |
1172 | + string_compose ("<b>%1</b>: %2 [%3]", ctl, name, int (chn + 1)), |
1173 | sigc::bind ( |
1174 | sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track), |
1175 | fully_qualified_param))); |
1176 | @@ -1199,16 +1199,56 @@ |
1177 | void |
1178 | MidiTimeAxisView::show_all_automation (bool apply_to_selection) |
1179 | { |
1180 | + using namespace MIDI::Name; |
1181 | + |
1182 | if (apply_to_selection) { |
1183 | _editor.get_selection().tracks.foreach_midi_time_axis ( |
1184 | boost::bind (&MidiTimeAxisView::show_all_automation, _1, false)); |
1185 | } else { |
1186 | if (midi_track()) { |
1187 | + // Show existing automation |
1188 | const set<Evoral::Parameter> params = midi_track()->midi_playlist()->contained_automation(); |
1189 | |
1190 | for (set<Evoral::Parameter>::const_iterator i = params.begin(); i != params.end(); ++i) { |
1191 | create_automation_child(*i, true); |
1192 | } |
1193 | + |
1194 | + // Show automation for all controllers named in midnam file |
1195 | + boost::shared_ptr<MasterDeviceNames> device_names = get_device_names(); |
1196 | + if (gui_property (X_("midnam-model-name")) != "Generic" && |
1197 | + device_names && !device_names->controls().empty()) { |
1198 | + const std::string device_mode = _midnam_custom_device_mode_selector.get_active_text(); |
1199 | + const uint16_t selected_channels = midi_track()->get_playback_channel_mask(); |
1200 | + for (uint32_t chn = 0; chn < 16; ++chn) { |
1201 | + if ((selected_channels & (0x0001 << chn)) == 0) { |
1202 | + // Channel not in use |
1203 | + continue; |
1204 | + } |
1205 | + |
1206 | + boost::shared_ptr<ChannelNameSet> chan_names = device_names->channel_name_set_by_channel( |
1207 | + device_mode, chn); |
1208 | + if (!chan_names) { |
1209 | + continue; |
1210 | + } |
1211 | + |
1212 | + boost::shared_ptr<ControlNameList> control_names = device_names->control_name_list( |
1213 | + chan_names->control_list_name()); |
1214 | + if (!control_names) { |
1215 | + continue; |
1216 | + } |
1217 | + |
1218 | + for (ControlNameList::Controls::const_iterator c = control_names->controls().begin(); |
1219 | + c != control_names->controls().end(); |
1220 | + ++c) { |
1221 | + const uint16_t ctl = c->second->number(); |
1222 | + if (ctl != MIDI_CTL_MSB_BANK && ctl != MIDI_CTL_LSB_BANK) { |
1223 | + /* Skip bank select controllers since they're handled specially */ |
1224 | + const Evoral::Parameter param(MidiCCAutomation, chn, ctl); |
1225 | + create_automation_child(param, true); |
1226 | + } |
1227 | + } |
1228 | + } |
1229 | + } |
1230 | } |
1231 | |
1232 | RouteTimeAxisView::show_all_automation (); |
1233 | |
1234 | === modified file 'gtk2_ardour/mnemonic-us.bindings.in' |
1235 | --- gtk2_ardour/mnemonic-us.bindings.in 2014-05-15 14:33:48 +0000 |
1236 | +++ gtk2_ardour/mnemonic-us.bindings.in 2015-05-02 14:25:06 +0000 |
1237 | @@ -339,7 +339,7 @@ |
1238 | @trans|Transport/ToggleAutoPlay|5|toggle auto play |
1239 | @trans|Transport/ToggleAutoReturn|6|toggle auto return |
1240 | @trans|Transport/ToggleClick|7|toggle click (metronome) |
1241 | -@ranges|Editor/set-tempo-from-region|9|set tempo (1 bar) from region(s) |
1242 | +@ranges|Region/set-tempo-from-region|9|set tempo (1 bar) from region(s) |
1243 | @ranges|Editor/set-tempo-from-edit-range|0|set tempo (1 bar) from edit range |
1244 | |
1245 | ; mouse stuff |
1246 | |
1247 | === modified file 'gtk2_ardour/video_timeline.cc' |
1248 | --- gtk2_ardour/video_timeline.cc 2014-05-15 14:33:48 +0000 |
1249 | +++ gtk2_ardour/video_timeline.cc 2015-05-02 14:25:06 +0000 |
1250 | @@ -39,6 +39,10 @@ |
1251 | #include <pthread.h> |
1252 | #include <curl/curl.h> |
1253 | |
1254 | +#ifdef PLATFORM_WINDOWS |
1255 | +#include <windows.h> |
1256 | +#endif |
1257 | + |
1258 | #include "i18n.h" |
1259 | |
1260 | using namespace std; |
1261 | @@ -718,20 +722,45 @@ |
1262 | VideoTimeLine::find_xjadeo () { |
1263 | std::string xjadeo_file_path; |
1264 | if (getenv("XJREMOTE")) { |
1265 | - _xjadeo_bin = strdup(getenv("XJREMOTE")); // XXX TODO: free it?! |
1266 | - } else if (find_file_in_search_path (SearchPath(Glib::getenv("PATH")), X_("xjremote"), xjadeo_file_path)) { |
1267 | - _xjadeo_bin = xjadeo_file_path; |
1268 | + _xjadeo_bin = getenv("XJREMOTE"); |
1269 | + } |
1270 | + else if (find_file_in_search_path (SearchPath(Glib::getenv("PATH")), X_("xjremote"), xjadeo_file_path)) { |
1271 | + _xjadeo_bin = xjadeo_file_path; |
1272 | + } |
1273 | + else if (find_file_in_search_path (SearchPath(Glib::getenv("PATH")), X_("xjadeo"), xjadeo_file_path)) { |
1274 | + _xjadeo_bin = xjadeo_file_path; |
1275 | + } |
1276 | +#ifdef __APPLE__ |
1277 | + else if (Glib::file_test(X_("/Applications/Xjadeo.app/Contents/MacOS/xjremote"), Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_EXECUTABLE)) { |
1278 | + _xjadeo_bin = X_("/Applications/Xjadeo.app/Contents/MacOS/xjremote"); |
1279 | } |
1280 | else if (Glib::file_test(X_("/Applications/Jadeo.app/Contents/MacOS/xjremote"), Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_EXECUTABLE)) { |
1281 | _xjadeo_bin = X_("/Applications/Jadeo.app/Contents/MacOS/xjremote"); |
1282 | } |
1283 | - /* TODO: win32: allow to configure PATH to xjremote */ |
1284 | +#endif |
1285 | +#ifdef PLATFORM_WINDOWS |
1286 | + else { |
1287 | + HKEY key; |
1288 | + DWORD size = PATH_MAX; |
1289 | + char path[PATH_MAX+1]; |
1290 | + xjadeo_file_path = X_(""); |
1291 | + if ( (ERROR_SUCCESS == RegOpenKeyExA (HKEY_LOCAL_MACHINE, "Software\\RSS\\xjadeo", 0, KEY_READ, &key)) |
1292 | + && (ERROR_SUCCESS == RegQueryValueExA (key, "Install_Dir", 0, NULL, (LPBYTE)path, &size)) |
1293 | + ) |
1294 | + { |
1295 | + xjadeo_file_path = g_build_filename(path, X_("xjadeo.exe")); |
1296 | + } |
1297 | + } |
1298 | + if (Glib::file_test(xjadeo_file_path, Glib::FILE_TEST_EXISTS)) { |
1299 | + _xjadeo_bin = xjadeo_file_path; |
1300 | + } |
1301 | else if (Glib::file_test(X_("C:\\Program Files\\xjadeo\\xjremote.exe"), Glib::FILE_TEST_EXISTS)) { |
1302 | _xjadeo_bin = X_("C:\\Program Files\\xjadeo\\xjremote.exe"); |
1303 | } |
1304 | else if (Glib::file_test(X_("C:\\Program Files\\xjadeo\\xjremote.bat"), Glib::FILE_TEST_EXISTS)) { |
1305 | _xjadeo_bin = X_("C:\\Program Files\\xjadeo\\xjremote.bat"); |
1306 | } |
1307 | +#endif |
1308 | else { |
1309 | _xjadeo_bin = X_(""); |
1310 | warning << _("Video-monitor 'xjadeo' was not found. Please install http://xjadeo.sf.net/ " |
1311 | |
1312 | === modified file 'libs/ardour/ardour/audiofilesource.h' |
1313 | --- libs/ardour/ardour/audiofilesource.h 2014-05-15 14:33:48 +0000 |
1314 | +++ libs/ardour/ardour/audiofilesource.h 2015-05-02 14:25:06 +0000 |
1315 | @@ -93,6 +93,12 @@ |
1316 | /** Constructor to be called for existing in-session files */ |
1317 | AudioFileSource (Session&, const XMLNode&, bool must_exist = true); |
1318 | |
1319 | + /** Constructor to be called for crash recovery. Final argument is not |
1320 | + * used but exists to differentiate from the external-to-session |
1321 | + * constructor above. |
1322 | + */ |
1323 | + AudioFileSource (Session&, const std::string& path, Source::Flag flags, bool); |
1324 | + |
1325 | int init (const std::string& idstr, bool must_exist); |
1326 | |
1327 | virtual void set_header_timeline_position () = 0; |
1328 | |
1329 | === modified file 'libs/ardour/ardour/file_source.h' |
1330 | --- libs/ardour/ardour/file_source.h 2014-05-15 14:33:48 +0000 |
1331 | +++ libs/ardour/ardour/file_source.h 2015-05-02 14:25:06 +0000 |
1332 | @@ -85,6 +85,10 @@ |
1333 | void existence_check (); |
1334 | virtual void prevent_deletion (); |
1335 | |
1336 | + /** Rename the file on disk referenced by this source to \param newname |
1337 | + */ |
1338 | + int rename (const std::string& name); |
1339 | + |
1340 | protected: |
1341 | FileSource (Session& session, DataType type, |
1342 | const std::string& path, |
1343 | |
1344 | === modified file 'libs/ardour/ardour/session.h' |
1345 | --- libs/ardour/ardour/session.h 2014-05-15 14:33:48 +0000 |
1346 | +++ libs/ardour/ardour/session.h 2015-05-02 14:25:06 +0000 |
1347 | @@ -195,10 +195,10 @@ |
1348 | std::string peak_path (std::string) const; |
1349 | |
1350 | std::string peak_path_from_audio_path (std::string) const; |
1351 | - std::string new_audio_source_name (const std::string&, uint32_t nchans, uint32_t chan, bool destructive); |
1352 | - std::string new_midi_source_name (const std::string&); |
1353 | - std::string new_source_path_from_name (DataType type, const std::string&); |
1354 | + std::string new_audio_source_path (const std::string&, uint32_t nchans, uint32_t chan, bool destructive, bool take_required); |
1355 | + std::string new_midi_source_path (const std::string&); |
1356 | RouteList new_route_from_template (uint32_t how_many, const std::string& template_path, const std::string& name); |
1357 | + std::vector<std::string> get_paths_for_new_sources (bool allow_replacing, const std::string& import_file_path, uint32_t channels); |
1358 | |
1359 | void process (pframes_t nframes); |
1360 | |
1361 | @@ -526,8 +526,6 @@ |
1362 | |
1363 | boost::shared_ptr<Region> find_whole_file_parent (boost::shared_ptr<Region const>) const; |
1364 | |
1365 | - std::string path_from_region_name (DataType type, std::string name, std::string identifier); |
1366 | - |
1367 | boost::shared_ptr<Region> XMLRegionFactory (const XMLNode&, bool full); |
1368 | boost::shared_ptr<AudioRegion> XMLAudioRegionFactory (const XMLNode&, bool full); |
1369 | boost::shared_ptr<MidiRegion> XMLMidiRegionFactory (const XMLNode&, bool full); |
1370 | @@ -584,8 +582,8 @@ |
1371 | boost::shared_ptr<MidiSource> create_midi_source_by_stealing_name (boost::shared_ptr<Track>); |
1372 | |
1373 | boost::shared_ptr<Source> source_by_id (const PBD::ID&); |
1374 | - boost::shared_ptr<AudioFileSource> source_by_path_and_channel (const std::string&, uint16_t) const; |
1375 | - boost::shared_ptr<MidiSource> source_by_path (const std::string&) const; |
1376 | + boost::shared_ptr<AudioFileSource> audio_source_by_path_and_channel (const std::string&, uint16_t) const; |
1377 | + boost::shared_ptr<MidiSource> midi_source_by_path (const std::string&) const; |
1378 | uint32_t count_sources_by_origin (const std::string&); |
1379 | |
1380 | void add_playlist (boost::shared_ptr<Playlist>, bool unused = false); |
1381 | @@ -1433,7 +1431,7 @@ |
1382 | |
1383 | bool no_questions_about_missing_files; |
1384 | |
1385 | - std::string get_best_session_directory_for_new_source (); |
1386 | + std::string get_best_session_directory_for_new_audio (); |
1387 | |
1388 | mutable gint _playback_load; |
1389 | mutable gint _capture_load; |
1390 | |
1391 | === modified file 'libs/ardour/ardour/smf_source.h' |
1392 | --- libs/ardour/ardour/smf_source.h 2014-05-15 14:33:48 +0000 |
1393 | +++ libs/ardour/ardour/smf_source.h 2015-05-02 14:25:06 +0000 |
1394 | @@ -47,15 +47,6 @@ |
1395 | |
1396 | virtual ~SMFSource (); |
1397 | |
1398 | - /** Rename the file on disk referenced by this source to \param newname |
1399 | - * |
1400 | - * This method exists only for MIDI file sources, not for audio, which |
1401 | - * can never be renamed. It exists for MIDI so that we can get |
1402 | - * consistent and sane region/source numbering when regions are added |
1403 | - * manually (which never happens with audio). |
1404 | - */ |
1405 | - int rename (const std::string& name); |
1406 | - |
1407 | bool safe_file_extension (const std::string& path) const { |
1408 | return safe_midi_file_extension(path); |
1409 | } |
1410 | |
1411 | === modified file 'libs/ardour/ardour/sndfilesource.h' |
1412 | --- libs/ardour/ardour/sndfilesource.h 2013-09-21 19:05:02 +0000 |
1413 | +++ libs/ardour/ardour/sndfilesource.h 2015-05-02 14:25:06 +0000 |
1414 | @@ -38,7 +38,16 @@ |
1415 | SampleFormat samp_format, HeaderFormat hdr_format, framecnt_t rate, |
1416 | Flag flags = SndFileSource::default_writable_flags); |
1417 | |
1418 | - /** Constructor to be called for existing in-session files */ |
1419 | + /* Constructor to be called for recovering files being used for |
1420 | + * capture. They are in-session, they already exist, they should not |
1421 | + * be writable. They are an odd hybrid (from a constructor point of |
1422 | + * view) of the previous two constructors. |
1423 | + */ |
1424 | + SndFileSource (Session&, const std::string& path, int chn); |
1425 | + |
1426 | + /** Constructor to be called for existing in-session files during |
1427 | + * session loading |
1428 | + */ |
1429 | SndFileSource (Session&, const XMLNode&); |
1430 | |
1431 | ~SndFileSource (); |
1432 | |
1433 | === modified file 'libs/ardour/ardour/source.h' |
1434 | --- libs/ardour/ardour/source.h 2013-09-21 19:05:02 +0000 |
1435 | +++ libs/ardour/ardour/source.h 2015-05-02 14:25:06 +0000 |
1436 | @@ -47,7 +47,8 @@ |
1437 | RemovableIfEmpty = 0x10, |
1438 | RemoveAtDestroy = 0x20, |
1439 | NoPeakFile = 0x40, |
1440 | - Destructive = 0x80 |
1441 | + Destructive = 0x80, |
1442 | + Empty = 0x100, /* used for MIDI only */ |
1443 | }; |
1444 | |
1445 | Source (Session&, DataType type, const std::string& name, Flag flags=Flag(0)); |
1446 | |
1447 | === modified file 'libs/ardour/ardour/source_factory.h' |
1448 | --- libs/ardour/ardour/source_factory.h 2013-09-21 19:05:02 +0000 |
1449 | +++ libs/ardour/ardour/source_factory.h 2015-05-02 14:25:06 +0000 |
1450 | @@ -57,6 +57,9 @@ |
1451 | bool destructive, framecnt_t rate, bool announce = true, bool async = false); |
1452 | |
1453 | |
1454 | + static boost::shared_ptr<Source> createForRecovery |
1455 | + (DataType type, Session&, const std::string& path, int chn); |
1456 | + |
1457 | static boost::shared_ptr<Source> createFromPlaylist |
1458 | (DataType type, Session& s, boost::shared_ptr<Playlist> p, const PBD::ID& orig, const std::string& name, |
1459 | uint32_t chn, frameoffset_t start, framecnt_t len, bool copy, bool defer_peaks); |
1460 | |
1461 | === modified file 'libs/ardour/audio_diskstream.cc' |
1462 | --- libs/ardour/audio_diskstream.cc 2014-05-15 14:33:48 +0000 |
1463 | +++ libs/ardour/audio_diskstream.cc 2015-05-02 14:25:06 +0000 |
1464 | @@ -2181,11 +2181,16 @@ |
1465 | continue; |
1466 | } |
1467 | |
1468 | + /* XXX as of June 2014, we always record to mono |
1469 | + files. Since this Source is being created as part of |
1470 | + crash recovery, we know that we need the first |
1471 | + channel (the final argument to the SourceFactory |
1472 | + call below). If we ever support non-mono files for |
1473 | + capture, this will need rethinking. |
1474 | + */ |
1475 | + |
1476 | try { |
1477 | - fs = boost::dynamic_pointer_cast<AudioFileSource> ( |
1478 | - SourceFactory::createWritable ( |
1479 | - DataType::AUDIO, _session, |
1480 | - prop->value(), false, _session.frame_rate())); |
1481 | + fs = boost::dynamic_pointer_cast<AudioFileSource> (SourceFactory::createForRecovery (DataType::AUDIO, _session, prop->value(), 0)); |
1482 | } |
1483 | |
1484 | catch (failed_constructor& err) { |
1485 | @@ -2216,21 +2221,31 @@ |
1486 | return -1; |
1487 | } |
1488 | |
1489 | - boost::shared_ptr<AudioRegion> region; |
1490 | - |
1491 | try { |
1492 | |
1493 | + boost::shared_ptr<AudioRegion> wf_region; |
1494 | + boost::shared_ptr<AudioRegion> region; |
1495 | + |
1496 | + /* First create the whole file region */ |
1497 | + |
1498 | PropertyList plist; |
1499 | - |
1500 | + |
1501 | plist.add (Properties::start, 0); |
1502 | plist.add (Properties::length, first_fs->length (first_fs->timeline_position())); |
1503 | plist.add (Properties::name, region_name_from_path (first_fs->name(), true)); |
1504 | |
1505 | + wf_region = boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (pending_sources, plist)); |
1506 | + |
1507 | + wf_region->set_automatic (true); |
1508 | + wf_region->set_whole_file (true); |
1509 | + wf_region->special_set_position (position); |
1510 | + |
1511 | + /* Now create a region that isn't the whole file for adding to |
1512 | + * the playlist */ |
1513 | + |
1514 | region = boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (pending_sources, plist)); |
1515 | - |
1516 | - region->set_automatic (true); |
1517 | - region->set_whole_file (true); |
1518 | - region->special_set_position (0); |
1519 | + |
1520 | + _playlist->add_region (region, position); |
1521 | } |
1522 | |
1523 | catch (failed_constructor& err) { |
1524 | @@ -2241,7 +2256,6 @@ |
1525 | return -1; |
1526 | } |
1527 | |
1528 | - _playlist->add_region (region, position); |
1529 | |
1530 | return 0; |
1531 | } |
1532 | |
1533 | === modified file 'libs/ardour/audiofilesource.cc' |
1534 | --- libs/ardour/audiofilesource.cc 2013-09-21 19:05:02 +0000 |
1535 | +++ libs/ardour/audiofilesource.cc 2015-05-02 14:25:06 +0000 |
1536 | @@ -36,6 +36,7 @@ |
1537 | #include "pbd/stl_delete.h" |
1538 | #include "pbd/strsplit.h" |
1539 | #include "pbd/shortpath.h" |
1540 | +#include "pbd/stacktrace.h" |
1541 | #include "pbd/enumwriter.h" |
1542 | |
1543 | #include <sndfile.h> |
1544 | @@ -114,6 +115,22 @@ |
1545 | } |
1546 | } |
1547 | |
1548 | +/** Constructor used for existing internal-to-session files during crash |
1549 | + * recovery. File must exist |
1550 | + */ |
1551 | +AudioFileSource::AudioFileSource (Session& s, const string& path, Source::Flag flags, bool /* ignored-exists-for-prototype differentiation */) |
1552 | + : Source (s, DataType::AUDIO, path, flags) |
1553 | + , AudioSource (s, path) |
1554 | + , FileSource (s, DataType::AUDIO, path, string(), flags) |
1555 | +{ |
1556 | + /* note that origin remains empty */ |
1557 | + |
1558 | + if (init (_path, true)) { |
1559 | + throw failed_constructor (); |
1560 | + } |
1561 | +} |
1562 | + |
1563 | + |
1564 | /** Constructor used for existing internal-to-session files via XML. File must exist. */ |
1565 | AudioFileSource::AudioFileSource (Session& s, const XMLNode& node, bool must_exist) |
1566 | : Source (s, node) |
1567 | |
1568 | === modified file 'libs/ardour/diskstream.cc' |
1569 | --- libs/ardour/diskstream.cc 2013-09-21 19:05:02 +0000 |
1570 | +++ libs/ardour/diskstream.cc 2015-05-02 14:25:06 +0000 |
1571 | @@ -739,4 +739,3 @@ |
1572 | { |
1573 | g_atomic_int_set (&_record_enabled, 0); |
1574 | } |
1575 | - |
1576 | |
1577 | === modified file 'libs/ardour/enums.cc' |
1578 | --- libs/ardour/enums.cc 2013-12-23 22:07:52 +0000 |
1579 | +++ libs/ardour/enums.cc 2015-05-02 14:25:06 +0000 |
1580 | @@ -447,6 +447,7 @@ |
1581 | REGISTER_CLASS_ENUM (Source, RemoveAtDestroy); |
1582 | REGISTER_CLASS_ENUM (Source, NoPeakFile); |
1583 | REGISTER_CLASS_ENUM (Source, Destructive); |
1584 | + REGISTER_CLASS_ENUM (Source, Empty); |
1585 | REGISTER_BITS (_Source_Flag); |
1586 | |
1587 | REGISTER_ENUM (FadeLinear); |
1588 | |
1589 | === modified file 'libs/ardour/file_source.cc' |
1590 | --- libs/ardour/file_source.cc 2014-05-15 14:33:48 +0000 |
1591 | +++ libs/ardour/file_source.cc 2015-05-02 14:25:06 +0000 |
1592 | @@ -56,7 +56,7 @@ |
1593 | FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag) |
1594 | : Source(session, type, path, flag) |
1595 | , _path (path) |
1596 | - , _file_is_new (!origin.empty()) // origin empty => new file VS. origin !empty => new file |
1597 | + , _file_is_new (!origin.empty()) // if origin is left unspecified (empty string) then file must exist |
1598 | , _channel (0) |
1599 | , _origin (origin) |
1600 | , _open (false) |
1601 | @@ -214,7 +214,7 @@ |
1602 | |
1603 | if (move_dependents_to_trash() != 0) { |
1604 | /* try to back out */ |
1605 | - rename (newpath.c_str(), _path.c_str()); |
1606 | + ::rename (newpath.c_str(), _path.c_str()); |
1607 | return -1; |
1608 | } |
1609 | |
1610 | @@ -256,8 +256,6 @@ |
1611 | |
1612 | split (search_path, dirs, ':'); |
1613 | |
1614 | - hits.clear (); |
1615 | - |
1616 | for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) { |
1617 | |
1618 | fullpath = Glib::build_filename (*i, path); |
1619 | @@ -312,9 +310,9 @@ |
1620 | /* no match: error */ |
1621 | |
1622 | if (must_exist) { |
1623 | - error << string_compose( |
1624 | - _("Filesource: cannot find required file (%1): while searching %2"), |
1625 | - path, search_path) << endmsg; |
1626 | + /* do not generate an error here, leave that to |
1627 | + whoever deals with the false return value. |
1628 | + */ |
1629 | goto out; |
1630 | } else { |
1631 | isnew = true; |
1632 | @@ -325,16 +323,17 @@ |
1633 | |
1634 | keeppath = de_duped_hits[0]; |
1635 | } |
1636 | - |
1637 | - } else { |
1638 | + |
1639 | + } else { |
1640 | keeppath = path; |
1641 | } |
1642 | |
1643 | /* Current find() is unable to parse relative path names to yet non-existant |
1644 | sources. QuickFix(tm) |
1645 | */ |
1646 | - if (keeppath == "") { |
1647 | - if (must_exist) { |
1648 | + |
1649 | + if (keeppath.empty()) { |
1650 | + if (must_exist) { |
1651 | error << "FileSource::find(), keeppath = \"\", but the file must exist" << endl; |
1652 | } else { |
1653 | keeppath = path; |
1654 | @@ -581,3 +580,28 @@ |
1655 | return true; |
1656 | } |
1657 | |
1658 | +int |
1659 | +FileSource::rename (const string& newpath) |
1660 | +{ |
1661 | + Glib::Threads::Mutex::Lock lm (_lock); |
1662 | + string oldpath = _path; |
1663 | + |
1664 | + // Test whether newpath exists, if yes notify the user but continue. |
1665 | + if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) { |
1666 | + error << string_compose (_("Programming error! %1 tried to rename a file over another file! It's safe to continue working, but please report this to the developers."), PROGRAM_NAME) << endmsg; |
1667 | + return -1; |
1668 | + } |
1669 | + |
1670 | + if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) { |
1671 | + /* rename only needed if file exists on disk */ |
1672 | + if (::rename (oldpath.c_str(), newpath.c_str()) != 0) { |
1673 | + error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg; |
1674 | + return -1; |
1675 | + } |
1676 | + } |
1677 | + |
1678 | + _name = Glib::path_get_basename (newpath); |
1679 | + _path = newpath; |
1680 | + |
1681 | + return 0; |
1682 | +} |
1683 | |
1684 | === modified file 'libs/ardour/filter.cc' |
1685 | --- libs/ardour/filter.cc 2013-09-21 19:05:02 +0000 |
1686 | +++ libs/ardour/filter.cc 2015-05-02 14:25:06 +0000 |
1687 | @@ -59,10 +59,9 @@ |
1688 | } |
1689 | } |
1690 | |
1691 | - string path = session.path_from_region_name (region->data_type(), |
1692 | - PBD::basename_nosuffix (names[i]), string ("")); |
1693 | + string path = session.new_audio_source_path (name, region->n_channels(), i, false, false); |
1694 | |
1695 | - if (path.length() == 0) { |
1696 | + if (path.empty()) { |
1697 | error << string_compose (_("filter: error creating name for new file based on %1"), region->name()) |
1698 | << endmsg; |
1699 | return -1; |
1700 | |
1701 | === modified file 'libs/ardour/import.cc' |
1702 | --- libs/ardour/import.cc 2013-09-21 19:05:02 +0000 |
1703 | +++ libs/ardour/import.cc 2015-05-02 14:25:06 +0000 |
1704 | @@ -116,78 +116,31 @@ |
1705 | } |
1706 | } |
1707 | |
1708 | -static std::string |
1709 | -get_non_existent_filename (HeaderFormat hf, DataType type, const bool allow_replacing, const std::string& destdir, const std::string& basename, uint channel, uint channels) |
1710 | -{ |
1711 | - char buf[PATH_MAX+1]; |
1712 | - bool goodfile = false; |
1713 | - string base = basename; |
1714 | - string ext = native_header_format_extension (hf, type); |
1715 | - uint32_t cnt = 1; |
1716 | - |
1717 | - do { |
1718 | - |
1719 | - if (type == DataType::AUDIO && channels == 2) { |
1720 | - if (channel == 0) { |
1721 | - if (cnt == 1) { |
1722 | - snprintf (buf, sizeof(buf), "%s-L%s", base.c_str(), ext.c_str()); |
1723 | - } else { |
1724 | - snprintf (buf, sizeof(buf), "%s-%d-L%s", base.c_str(), cnt, ext.c_str()); |
1725 | - } |
1726 | - } else { |
1727 | - if (cnt == 1) { |
1728 | - snprintf (buf, sizeof(buf), "%s-R%s", base.c_str(), ext.c_str()); |
1729 | - } else { |
1730 | - snprintf (buf, sizeof(buf), "%s-%d-R%s", base.c_str(), cnt, ext.c_str()); |
1731 | - } |
1732 | - } |
1733 | - } else if (channels > 1) { |
1734 | - if (cnt == 1) { |
1735 | - snprintf (buf, sizeof(buf), "%s-c%d%s", base.c_str(), channel, ext.c_str()); |
1736 | - } else { |
1737 | - snprintf (buf, sizeof(buf), "%s-%d-c%d%s", base.c_str(), cnt, channel, ext.c_str()); |
1738 | - } |
1739 | - } else { |
1740 | - if (cnt == 1) { |
1741 | - snprintf (buf, sizeof(buf), "%s%s", base.c_str(), ext.c_str()); |
1742 | - } else { |
1743 | - snprintf (buf, sizeof(buf), "%s-%d%s", base.c_str(), cnt, ext.c_str()); |
1744 | - } |
1745 | - } |
1746 | - |
1747 | - string tempname = destdir + "/" + buf; |
1748 | - |
1749 | - if (!allow_replacing && Glib::file_test (tempname, Glib::FILE_TEST_EXISTS)) { |
1750 | - |
1751 | - cnt++; |
1752 | - |
1753 | - } else { |
1754 | - |
1755 | - goodfile = true; |
1756 | - } |
1757 | - |
1758 | - } while (!goodfile); |
1759 | - |
1760 | - return buf; |
1761 | -} |
1762 | - |
1763 | -static vector<string> |
1764 | -get_paths_for_new_sources (HeaderFormat hf, const bool allow_replacing, const string& import_file_path, const string& session_dir, uint channels) |
1765 | +vector<string> |
1766 | +Session::get_paths_for_new_sources (bool /*allow_replacing*/, const string& import_file_path, uint32_t channels) |
1767 | { |
1768 | vector<string> new_paths; |
1769 | const string basename = basename_nosuffix (import_file_path); |
1770 | |
1771 | - SessionDirectory sdir(session_dir); |
1772 | - |
1773 | for (uint n = 0; n < channels; ++n) { |
1774 | |
1775 | const DataType type = SMFSource::safe_midi_file_extension (import_file_path) ? DataType::MIDI : DataType::AUDIO; |
1776 | - |
1777 | - std::string filepath = (type == DataType::MIDI) |
1778 | - ? sdir.midi_path() : sdir.sound_path(); |
1779 | - |
1780 | - filepath = Glib::build_filename (filepath, |
1781 | - get_non_existent_filename (hf, type, allow_replacing, filepath, basename, n, channels)); |
1782 | + string filepath; |
1783 | + |
1784 | + switch (type) { |
1785 | + case DataType::MIDI: |
1786 | + filepath = new_midi_source_path (basename); |
1787 | + break; |
1788 | + case DataType::AUDIO: |
1789 | + filepath = new_audio_source_path (basename, channels, n, false, false); |
1790 | + break; |
1791 | + } |
1792 | + |
1793 | + if (filepath.empty()) { |
1794 | + error << string_compose (_("Cannot find new filename for imported file %1"), import_file_path) << endmsg; |
1795 | + return vector<string>(); |
1796 | + } |
1797 | + |
1798 | new_paths.push_back (filepath); |
1799 | } |
1800 | |
1801 | @@ -201,7 +154,7 @@ |
1802 | for (vector<string>::const_iterator i = new_paths.begin(); |
1803 | i != new_paths.end(); ++i) |
1804 | { |
1805 | - boost::shared_ptr<Source> source = session->source_by_path_and_channel(*i, 0); |
1806 | + boost::shared_ptr<Source> source = session->audio_source_by_path_and_channel(*i, 0); |
1807 | |
1808 | if (source == 0) { |
1809 | error << string_compose(_("Could not find a source for %1 even though we are updating this file!"), (*i)) << endl; |
1810 | @@ -274,12 +227,12 @@ |
1811 | { |
1812 | const framecnt_t nframes = ResampledImportableSource::blocksize; |
1813 | boost::shared_ptr<AudioFileSource> afs; |
1814 | - uint channels = source->channels(); |
1815 | + uint32_t channels = source->channels(); |
1816 | |
1817 | boost::scoped_array<float> data(new float[nframes * channels]); |
1818 | vector<boost::shared_array<Sample> > channel_data; |
1819 | |
1820 | - for (uint n = 0; n < channels; ++n) { |
1821 | + for (uint32_t n = 0; n < channels; ++n) { |
1822 | channel_data.push_back(boost::shared_array<Sample>(new Sample[nframes])); |
1823 | } |
1824 | |
1825 | @@ -323,14 +276,14 @@ |
1826 | progress_multiplier = 0.5; |
1827 | progress_base = 0.5; |
1828 | } |
1829 | - |
1830 | - uint read_count = 0; |
1831 | + |
1832 | + framecnt_t read_count = 0; |
1833 | |
1834 | while (!status.cancel) { |
1835 | |
1836 | framecnt_t nread, nfread; |
1837 | - uint x; |
1838 | - uint chn; |
1839 | + uint32_t x; |
1840 | + uint32_t chn; |
1841 | |
1842 | if ((nread = source->read (data.get(), nframes)) == 0) { |
1843 | break; |
1844 | @@ -513,10 +466,7 @@ |
1845 | } |
1846 | } |
1847 | |
1848 | - vector<string> new_paths = get_paths_for_new_sources (config.get_native_file_header_format(), |
1849 | - status.replace_existing_source, *p, |
1850 | - get_best_session_directory_for_new_source (), |
1851 | - channels); |
1852 | + vector<string> new_paths = get_paths_for_new_sources (status.replace_existing_source, *p, channels); |
1853 | Sources newfiles; |
1854 | framepos_t natural_position = source ? source->natural_position() : 0; |
1855 | |
1856 | |
1857 | === modified file 'libs/ardour/midi_diskstream.cc' |
1858 | --- libs/ardour/midi_diskstream.cc 2014-05-15 14:33:48 +0000 |
1859 | +++ libs/ardour/midi_diskstream.cc 2015-05-02 14:25:06 +0000 |
1860 | @@ -1234,9 +1234,9 @@ |
1861 | */ |
1862 | |
1863 | try { |
1864 | - string new_name = _session.new_midi_source_name (name()); |
1865 | + string new_path = _session.new_midi_source_path (name()); |
1866 | |
1867 | - if (_write_source->rename (new_name)) { |
1868 | + if (_write_source->rename (new_path)) { |
1869 | return string(); |
1870 | } |
1871 | } catch (...) { |
1872 | |
1873 | === modified file 'libs/ardour/session.cc' |
1874 | --- libs/ardour/session.cc 2014-05-15 14:33:48 +0000 |
1875 | +++ libs/ardour/session.cc 2015-05-02 14:25:06 +0000 |
1876 | @@ -36,6 +36,7 @@ |
1877 | |
1878 | #include <boost/algorithm/string/erase.hpp> |
1879 | |
1880 | +#include "pbd/convert.h" |
1881 | #include "pbd/error.h" |
1882 | #include "pbd/boost_debug.h" |
1883 | #include "pbd/pathscanner.h" |
1884 | @@ -3305,7 +3306,7 @@ |
1885 | } |
1886 | |
1887 | boost::shared_ptr<AudioFileSource> |
1888 | -Session::source_by_path_and_channel (const string& path, uint16_t chn) const |
1889 | +Session::audio_source_by_path_and_channel (const string& path, uint16_t chn) const |
1890 | { |
1891 | /* Restricted to audio files because only audio sources have channel |
1892 | as a property. |
1893 | @@ -3326,7 +3327,7 @@ |
1894 | } |
1895 | |
1896 | boost::shared_ptr<MidiSource> |
1897 | -Session::source_by_path (const std::string& path) const |
1898 | +Session::midi_source_by_path (const std::string& path) const |
1899 | { |
1900 | /* Restricted to MIDI files because audio sources require a channel |
1901 | for unique identification, in addition to a path. |
1902 | @@ -3366,30 +3367,6 @@ |
1903 | return cnt; |
1904 | } |
1905 | |
1906 | -/** Return the full path (in some session directory) for a new within-session source. |
1907 | - * \a name must be a session-unique name that does not contain slashes |
1908 | - * (e.g. as returned by new_*_source_name) |
1909 | - */ |
1910 | -string |
1911 | -Session::new_source_path_from_name (DataType type, const string& name) |
1912 | -{ |
1913 | - assert(name.find("/") == string::npos); |
1914 | - |
1915 | - SessionDirectory sdir(get_best_session_directory_for_new_source()); |
1916 | - |
1917 | - std::string p; |
1918 | - if (type == DataType::AUDIO) { |
1919 | - p = sdir.sound_path(); |
1920 | - } else if (type == DataType::MIDI) { |
1921 | - p = sdir.midi_path(); |
1922 | - } else { |
1923 | - error << "Unknown source type, unable to create file path" << endmsg; |
1924 | - return ""; |
1925 | - } |
1926 | - |
1927 | - return Glib::build_filename (p, name); |
1928 | -} |
1929 | - |
1930 | string |
1931 | Session::peak_path (string base) const |
1932 | { |
1933 | @@ -3398,18 +3375,20 @@ |
1934 | |
1935 | /** Return a unique name based on \a base for a new internal audio source */ |
1936 | string |
1937 | -Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t chan, bool destructive) |
1938 | +Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required) |
1939 | { |
1940 | uint32_t cnt; |
1941 | - char buf[PATH_MAX+1]; |
1942 | - const uint32_t limit = 10000; |
1943 | + string possible_name; |
1944 | + const uint32_t limit = 9999; // arbitrary limit on number of files with the same basic name |
1945 | string legalized; |
1946 | string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO); |
1947 | + bool some_related_source_name_exists = false; |
1948 | |
1949 | - buf[0] = '\0'; |
1950 | + possible_name[0] = '\0'; |
1951 | legalized = legalize_for_path (base); |
1952 | |
1953 | // Find a "version" of the base name that doesn't exist in any of the possible directories. |
1954 | + |
1955 | for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) { |
1956 | |
1957 | vector<space_and_path>::iterator i; |
1958 | @@ -3417,47 +3396,37 @@ |
1959 | |
1960 | for (i = session_dirs.begin(); i != session_dirs.end(); ++i) { |
1961 | |
1962 | + ostringstream sstr; |
1963 | + |
1964 | if (destructive) { |
1965 | - |
1966 | - if (nchan < 2) { |
1967 | - snprintf (buf, sizeof(buf), "T%04d-%s%s", |
1968 | - cnt, legalized.c_str(), ext.c_str()); |
1969 | - } else if (nchan == 2) { |
1970 | - if (chan == 0) { |
1971 | - snprintf (buf, sizeof(buf), "T%04d-%s%%L%s", |
1972 | - cnt, legalized.c_str(), ext.c_str()); |
1973 | - } else { |
1974 | - snprintf (buf, sizeof(buf), "T%04d-%s%%R%s", |
1975 | - cnt, legalized.c_str(), ext.c_str()); |
1976 | - } |
1977 | - } else if (nchan < 26) { |
1978 | - snprintf (buf, sizeof(buf), "T%04d-%s%%%c%s", |
1979 | - cnt, legalized.c_str(), 'a' + chan, ext.c_str()); |
1980 | - } else { |
1981 | - snprintf (buf, sizeof(buf), "T%04d-%s%s", |
1982 | - cnt, legalized.c_str(), ext.c_str()); |
1983 | - } |
1984 | - |
1985 | + sstr << 'T'; |
1986 | + sstr << setfill ('0') << setw (4) << cnt; |
1987 | + sstr << legalized; |
1988 | } else { |
1989 | - |
1990 | - if (nchan < 2) { |
1991 | - snprintf (buf, sizeof(buf), "%s-%u%s", legalized.c_str(), cnt, ext.c_str()); |
1992 | - } else if (nchan == 2) { |
1993 | - if (chan == 0) { |
1994 | - snprintf (buf, sizeof(buf), "%s-%u%%L%s", legalized.c_str(), cnt, ext.c_str()); |
1995 | - } else { |
1996 | - snprintf (buf, sizeof(buf), "%s-%u%%R%s", legalized.c_str(), cnt, ext.c_str()); |
1997 | - } |
1998 | - } else if (nchan < 26) { |
1999 | - snprintf (buf, sizeof(buf), "%s-%u%%%c%s", legalized.c_str(), cnt, 'a' + chan, ext.c_str()); |
2000 | - } else { |
2001 | - snprintf (buf, sizeof(buf), "%s-%u%s", legalized.c_str(), cnt, ext.c_str()); |
2002 | + sstr << legalized; |
2003 | + |
2004 | + if (take_required || some_related_source_name_exists) { |
2005 | + sstr << '-'; |
2006 | + sstr << cnt; |
2007 | } |
2008 | } |
2009 | - |
2010 | + |
2011 | + if (nchan == 2) { |
2012 | + if (chan == 0) { |
2013 | + sstr << "%L"; |
2014 | + } else { |
2015 | + sstr << "%R"; |
2016 | + } |
2017 | + } else if (nchan > 2 && nchan < 26) { |
2018 | + sstr << '%'; |
2019 | + sstr << 'a' + chan; |
2020 | + } |
2021 | + |
2022 | + sstr << ext; |
2023 | + |
2024 | + possible_name = sstr.str(); |
2025 | SessionDirectory sdir((*i).path); |
2026 | - |
2027 | - string spath = sdir.sound_path(); |
2028 | + const string spath = sdir.sound_path(); |
2029 | |
2030 | /* note that we search *without* the extension so that |
2031 | we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf" |
2032 | @@ -3465,7 +3434,7 @@ |
2033 | a file format change. |
2034 | */ |
2035 | |
2036 | - if (matching_unsuffixed_filename_exists_in (spath, buf)) { |
2037 | + if (matching_unsuffixed_filename_exists_in (spath, possible_name)) { |
2038 | existing++; |
2039 | break; |
2040 | } |
2041 | @@ -3479,9 +3448,9 @@ |
2042 | * notions of their removability. |
2043 | */ |
2044 | |
2045 | - string possible_path = Glib::build_filename (spath, buf); |
2046 | + string possible_path = Glib::build_filename (spath, possible_name); |
2047 | |
2048 | - if (source_by_path (possible_path)) { |
2049 | + if (audio_source_by_path_and_channel (possible_path, chan)) { |
2050 | existing++; |
2051 | break; |
2052 | } |
2053 | @@ -3491,6 +3460,8 @@ |
2054 | break; |
2055 | } |
2056 | |
2057 | + some_related_source_name_exists = true; |
2058 | + |
2059 | if (cnt > limit) { |
2060 | error << string_compose( |
2061 | _("There are already %1 recordings for %2, which I consider too many."), |
2062 | @@ -3500,32 +3471,31 @@ |
2063 | } |
2064 | } |
2065 | |
2066 | - return Glib::path_get_basename (buf); |
2067 | -} |
2068 | - |
2069 | -/** Create a new within-session audio source */ |
2070 | -boost::shared_ptr<AudioFileSource> |
2071 | -Session::create_audio_source_for_session (size_t n_chans, string const & n, uint32_t chan, bool destructive) |
2072 | -{ |
2073 | - const string name = new_audio_source_name (n, n_chans, chan, destructive); |
2074 | - const string path = new_source_path_from_name(DataType::AUDIO, name); |
2075 | - |
2076 | - return boost::dynamic_pointer_cast<AudioFileSource> ( |
2077 | - SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate())); |
2078 | + /* We've established that the new name does not exist in any session |
2079 | + * directory, so now find out which one we should use for this new |
2080 | + * audio source. |
2081 | + */ |
2082 | + |
2083 | + SessionDirectory sdir (get_best_session_directory_for_new_audio()); |
2084 | + |
2085 | + std::string s = Glib::build_filename (sdir.sound_path(), possible_name); |
2086 | + |
2087 | + return s; |
2088 | } |
2089 | |
2090 | /** Return a unique name based on \a owner_name for a new internal MIDI source */ |
2091 | string |
2092 | -Session::new_midi_source_name (const string& owner_name) |
2093 | +Session::new_midi_source_path (const string& base) |
2094 | { |
2095 | uint32_t cnt; |
2096 | char buf[PATH_MAX+1]; |
2097 | const uint32_t limit = 10000; |
2098 | string legalized; |
2099 | + string possible_path; |
2100 | string possible_name; |
2101 | |
2102 | buf[0] = '\0'; |
2103 | - legalized = legalize_for_path (owner_name); |
2104 | + legalized = legalize_for_path (base); |
2105 | |
2106 | // Find a "version" of the file name that doesn't exist in any of the possible directories. |
2107 | |
2108 | @@ -3533,7 +3503,7 @@ |
2109 | |
2110 | vector<space_and_path>::iterator i; |
2111 | uint32_t existing = 0; |
2112 | - |
2113 | + |
2114 | for (i = session_dirs.begin(); i != session_dirs.end(); ++i) { |
2115 | |
2116 | SessionDirectory sdir((*i).path); |
2117 | @@ -3541,13 +3511,13 @@ |
2118 | snprintf (buf, sizeof(buf), "%s-%u.mid", legalized.c_str(), cnt); |
2119 | possible_name = buf; |
2120 | |
2121 | - std::string possible_path = Glib::build_filename (sdir.midi_path(), possible_name); |
2122 | + possible_path = Glib::build_filename (sdir.midi_path(), possible_name); |
2123 | |
2124 | if (Glib::file_test (possible_path, Glib::FILE_TEST_EXISTS)) { |
2125 | existing++; |
2126 | } |
2127 | |
2128 | - if (source_by_path (possible_path)) { |
2129 | + if (midi_source_by_path (possible_path)) { |
2130 | existing++; |
2131 | } |
2132 | } |
2133 | @@ -3559,31 +3529,47 @@ |
2134 | if (cnt > limit) { |
2135 | error << string_compose( |
2136 | _("There are already %1 recordings for %2, which I consider too many."), |
2137 | - limit, owner_name) << endmsg; |
2138 | + limit, base) << endmsg; |
2139 | destroy (); |
2140 | - throw failed_constructor(); |
2141 | + return 0; |
2142 | } |
2143 | } |
2144 | |
2145 | - return possible_name; |
2146 | -} |
2147 | - |
2148 | + /* No need to "find best location" for software/app-based RAID, because |
2149 | + MIDI is so small that we always put it in the same place. |
2150 | + */ |
2151 | + |
2152 | + return possible_path; |
2153 | +} |
2154 | + |
2155 | + |
2156 | +/** Create a new within-session audio source */ |
2157 | +boost::shared_ptr<AudioFileSource> |
2158 | +Session::create_audio_source_for_session (size_t n_chans, string const & base, uint32_t chan, bool destructive) |
2159 | +{ |
2160 | + const string path = new_audio_source_path (base, n_chans, chan, destructive, true); |
2161 | + |
2162 | + if (!path.empty()) { |
2163 | + return boost::dynamic_pointer_cast<AudioFileSource> ( |
2164 | + SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate())); |
2165 | + } else { |
2166 | + throw failed_constructor (); |
2167 | + } |
2168 | +} |
2169 | |
2170 | /** Create a new within-session MIDI source */ |
2171 | boost::shared_ptr<MidiSource> |
2172 | Session::create_midi_source_for_session (string const & basic_name) |
2173 | { |
2174 | - std::string name; |
2175 | - |
2176 | - if (name.empty()) { |
2177 | - name = new_midi_source_name (basic_name); |
2178 | + const string path = new_midi_source_path (basic_name); |
2179 | + |
2180 | + if (!path.empty()) { |
2181 | + return boost::dynamic_pointer_cast<SMFSource> ( |
2182 | + SourceFactory::createWritable ( |
2183 | + DataType::MIDI, *this, path, false, frame_rate())); |
2184 | + } else { |
2185 | + throw failed_constructor (); |
2186 | } |
2187 | - |
2188 | - const string path = new_source_path_from_name (DataType::MIDI, name); |
2189 | - |
2190 | - return boost::dynamic_pointer_cast<SMFSource> ( |
2191 | - SourceFactory::createWritable ( |
2192 | - DataType::MIDI, *this, path, false, frame_rate())); |
2193 | } |
2194 | |
2195 | /** Create a new within-session MIDI source */ |
2196 | @@ -3617,7 +3603,13 @@ |
2197 | return boost::shared_ptr<MidiSource>(); |
2198 | } |
2199 | |
2200 | - const string path = new_source_path_from_name (DataType::MIDI, name); |
2201 | + /* MIDI files are small, just put them in the first location of the |
2202 | + session source search path. |
2203 | + */ |
2204 | + vector<string> dirs; |
2205 | + |
2206 | + split (source_search_path(DataType::MIDI), dirs, ':'); |
2207 | + const string path = Glib::build_filename (dirs.front(), name); |
2208 | |
2209 | return boost::dynamic_pointer_cast<SMFSource> ( |
2210 | SourceFactory::createWritable ( |
2211 | @@ -4123,18 +4115,13 @@ |
2212 | boost::shared_ptr<Region> result; |
2213 | boost::shared_ptr<Playlist> playlist; |
2214 | boost::shared_ptr<AudioFileSource> fsource; |
2215 | - uint32_t x; |
2216 | - char buf[PATH_MAX+1]; |
2217 | ChanCount diskstream_channels (track.n_channels()); |
2218 | framepos_t position; |
2219 | framecnt_t this_chunk; |
2220 | framepos_t to_do; |
2221 | BufferSet buffers; |
2222 | - SessionDirectory sdir(get_best_session_directory_for_new_source ()); |
2223 | - const string sound_dir = sdir.sound_path(); |
2224 | framepos_t len = end - start; |
2225 | bool need_block_size_reset = false; |
2226 | - string ext; |
2227 | ChanCount const max_proc = track.max_processor_streams (); |
2228 | |
2229 | if (end <= start) { |
2230 | @@ -4155,29 +4142,22 @@ |
2231 | goto out; |
2232 | } |
2233 | |
2234 | - ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO); |
2235 | - |
2236 | for (uint32_t chan_n = 0; chan_n < diskstream_channels.n_audio(); ++chan_n) { |
2237 | |
2238 | - for (x = 0; x < 99999; ++x) { |
2239 | - snprintf (buf, sizeof(buf), "%s/%s-%d-bounce-%" PRIu32 "%s", sound_dir.c_str(), playlist->name().c_str(), chan_n, x+1, ext.c_str()); |
2240 | - if (!Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) { |
2241 | - break; |
2242 | - } |
2243 | - } |
2244 | - |
2245 | - if (x == 99999) { |
2246 | - error << string_compose (_("too many bounced versions of playlist \"%1\""), playlist->name()) << endmsg; |
2247 | + string base_name = string_compose ("%1-%2-bounce", playlist->name(), chan_n); |
2248 | + string path = new_audio_source_path (base_name, diskstream_channels.n_audio(), chan_n, false, true); |
2249 | + |
2250 | + if (path.empty()) { |
2251 | goto out; |
2252 | } |
2253 | |
2254 | try { |
2255 | fsource = boost::dynamic_pointer_cast<AudioFileSource> ( |
2256 | - SourceFactory::createWritable (DataType::AUDIO, *this, buf, false, frame_rate())); |
2257 | + SourceFactory::createWritable (DataType::AUDIO, *this, path, false, frame_rate())); |
2258 | } |
2259 | |
2260 | catch (failed_constructor& err) { |
2261 | - error << string_compose (_("cannot create new audio file \"%1\" for %2"), buf, track.name()) << endmsg; |
2262 | + error << string_compose (_("cannot create new audio file \"%1\" for %2"), path, track.name()) << endmsg; |
2263 | goto out; |
2264 | } |
2265 | |
2266 | |
2267 | === modified file 'libs/ardour/session_state.cc' |
2268 | --- libs/ardour/session_state.cc 2014-02-25 14:13:18 +0000 |
2269 | +++ libs/ardour/session_state.cc 2015-05-02 14:25:06 +0000 |
2270 | @@ -74,6 +74,7 @@ |
2271 | #include "pbd/stacktrace.h" |
2272 | #include "pbd/convert.h" |
2273 | #include "pbd/clear_dir.h" |
2274 | +#include "pbd/strsplit.h" |
2275 | |
2276 | #include "ardour/amp.h" |
2277 | #include "ardour/audio_diskstream.h" |
2278 | @@ -1830,47 +1831,15 @@ |
2279 | return *node; |
2280 | } |
2281 | |
2282 | -string |
2283 | -Session::path_from_region_name (DataType type, string name, string identifier) |
2284 | -{ |
2285 | - char buf[PATH_MAX+1]; |
2286 | - uint32_t n; |
2287 | - SessionDirectory sdir(get_best_session_directory_for_new_source()); |
2288 | - std::string source_dir = ((type == DataType::AUDIO) |
2289 | - ? sdir.sound_path() : sdir.midi_path()); |
2290 | - |
2291 | - string ext = native_header_format_extension (config.get_native_file_header_format(), type); |
2292 | - |
2293 | - for (n = 0; n < 999999; ++n) { |
2294 | - if (identifier.length()) { |
2295 | - snprintf (buf, sizeof(buf), "%s%s%" PRIu32 "%s", name.c_str(), |
2296 | - identifier.c_str(), n, ext.c_str()); |
2297 | - } else { |
2298 | - snprintf (buf, sizeof(buf), "%s-%" PRIu32 "%s", name.c_str(), |
2299 | - n, ext.c_str()); |
2300 | - } |
2301 | - |
2302 | - std::string source_path = Glib::build_filename (source_dir, buf); |
2303 | - |
2304 | - if (!Glib::file_test (source_path, Glib::FILE_TEST_EXISTS)) { |
2305 | - return source_path; |
2306 | - } |
2307 | - } |
2308 | - |
2309 | - error << string_compose (_("cannot create new file from region name \"%1\" with ident = \"%2\": too many existing files with similar names"), |
2310 | - name, identifier) |
2311 | - << endmsg; |
2312 | - |
2313 | - return ""; |
2314 | -} |
2315 | - |
2316 | - |
2317 | int |
2318 | Session::load_sources (const XMLNode& node) |
2319 | { |
2320 | XMLNodeList nlist; |
2321 | XMLNodeConstIterator niter; |
2322 | - boost::shared_ptr<Source> source; |
2323 | + boost::shared_ptr<Source> source; /* don't need this but it stops some |
2324 | + * versions of gcc complaining about |
2325 | + * discarded return values. |
2326 | + */ |
2327 | |
2328 | nlist = node.children(); |
2329 | |
2330 | @@ -1887,9 +1856,15 @@ |
2331 | |
2332 | int user_choice; |
2333 | |
2334 | + if (err.type == DataType::MIDI && Glib::path_is_absolute (err.path)) { |
2335 | + error << string_compose (_("A external MIDI file is missing. %1 cannot currently recover from missing external MIDI files"), |
2336 | + PROGRAM_NAME) << endmsg; |
2337 | + return -1; |
2338 | + } |
2339 | + |
2340 | if (!no_questions_about_missing_files) { |
2341 | - user_choice = MissingFile (this, err.path, err.type).get_value_or (-1); |
2342 | - } else { |
2343 | + user_choice = MissingFile (this, err.path, err.type).get_value_or (-1); |
2344 | + } else { |
2345 | user_choice = -2; |
2346 | } |
2347 | |
2348 | @@ -1914,8 +1889,41 @@ |
2349 | |
2350 | case -1: |
2351 | default: |
2352 | - warning << _("A sound file is missing. It will be replaced by silence.") << endmsg; |
2353 | - source = SourceFactory::createSilent (*this, **niter, max_framecnt, _current_frame_rate); |
2354 | + switch (err.type) { |
2355 | + |
2356 | + case DataType::AUDIO: |
2357 | + source = SourceFactory::createSilent (*this, **niter, max_framecnt, _current_frame_rate); |
2358 | + break; |
2359 | + |
2360 | + case DataType::MIDI: |
2361 | + /* The MIDI file is actually missing so |
2362 | + * just create a new one in the same |
2363 | + * location. Do not announce its |
2364 | + */ |
2365 | + string fullpath; |
2366 | + |
2367 | + if (!Glib::path_is_absolute (err.path)) { |
2368 | + vector<Glib::ustring> sdirs; |
2369 | + split (source_search_path (DataType::MIDI), sdirs, ':'); |
2370 | + if (sdirs.empty()) { |
2371 | + fatal << _("Empty MIDI source search path!") << endmsg; |
2372 | + /*NOTREACHED*/ |
2373 | + } |
2374 | + fullpath = Glib::build_filename (sdirs.front(), err.path); |
2375 | + } else { |
2376 | + /* this should be an unrecoverable error: we would be creating a MIDI file outside |
2377 | + the session tree. |
2378 | + */ |
2379 | + return -1; |
2380 | + } |
2381 | + /* Note that we do not announce the source just yet - we need to reset its ID before we do that */ |
2382 | + source = SourceFactory::createWritable (DataType::MIDI, *this, fullpath, false, _current_frame_rate, false, false); |
2383 | + /* reset ID to match the missing one */ |
2384 | + source->set_id (**niter); |
2385 | + /* Now we can announce it */ |
2386 | + SourceFactory::SourceCreated (source); |
2387 | + break; |
2388 | + } |
2389 | break; |
2390 | } |
2391 | } |
2392 | @@ -2053,7 +2061,7 @@ |
2393 | } |
2394 | |
2395 | string |
2396 | -Session::get_best_session_directory_for_new_source () |
2397 | +Session::get_best_session_directory_for_new_audio () |
2398 | { |
2399 | vector<space_and_path>::iterator i; |
2400 | string result = _session_dir->root_path(); |
2401 | |
2402 | === modified file 'libs/ardour/smf_source.cc' |
2403 | --- libs/ardour/smf_source.cc 2014-05-15 14:33:48 +0000 |
2404 | +++ libs/ardour/smf_source.cc 2015-05-02 14:25:06 +0000 |
2405 | @@ -71,6 +71,8 @@ |
2406 | assert (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS)); |
2407 | existence_check (); |
2408 | |
2409 | + _flags = Source::Flag (_flags | Empty); |
2410 | + |
2411 | /* file is not opened until write */ |
2412 | |
2413 | if (flags & Writable) { |
2414 | @@ -97,16 +99,15 @@ |
2415 | { |
2416 | /* note that origin remains empty */ |
2417 | |
2418 | - if (init (_path, false)) { |
2419 | + if (init (_path, true)) { |
2420 | throw failed_constructor (); |
2421 | } |
2422 | |
2423 | assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)); |
2424 | existence_check (); |
2425 | |
2426 | - /* file is not opened until write */ |
2427 | - |
2428 | if (_flags & Writable) { |
2429 | + /* file is not opened until write */ |
2430 | return; |
2431 | } |
2432 | |
2433 | @@ -131,12 +132,50 @@ |
2434 | throw failed_constructor (); |
2435 | } |
2436 | |
2437 | - if (init (_path, true)) { |
2438 | - throw failed_constructor (); |
2439 | - } |
2440 | - |
2441 | - assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)); |
2442 | - existence_check (); |
2443 | + /* we expect the file to exist, but if no MIDI data was ever added |
2444 | + it will have been removed at last session close. so, we don't |
2445 | + require it to exist if it was marked Empty. |
2446 | + */ |
2447 | + |
2448 | + try { |
2449 | + |
2450 | + if (init (_path, true)) { |
2451 | + throw failed_constructor (); |
2452 | + } |
2453 | + |
2454 | + } catch (MissingSource& err) { |
2455 | + |
2456 | + if (_flags & Source::Empty) { |
2457 | + /* we don't care that the file was not found, because |
2458 | + it was empty. But FileSource::init() will have |
2459 | + failed to set our _path correctly, so we have to do |
2460 | + this ourselves. Use the first entry in the search |
2461 | + path for MIDI files, which is assumed to be the |
2462 | + correct "main" location. |
2463 | + */ |
2464 | + std::vector<Glib::ustring> sdirs; |
2465 | + split (s.source_search_path (DataType::MIDI), sdirs, ':'); |
2466 | + if (sdirs.empty()) { |
2467 | + fatal << _("Empty MIDI source search path!") << endmsg; |
2468 | + /*NOTREACHED*/ |
2469 | + } |
2470 | + _path = Glib::build_filename (sdirs.front(), _path); |
2471 | + /* This might be important, too */ |
2472 | + _file_is_new = true; |
2473 | + } else { |
2474 | + /* pass it on */ |
2475 | + throw; |
2476 | + } |
2477 | + } |
2478 | + |
2479 | + if (!(_flags & Source::Empty)) { |
2480 | + assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)); |
2481 | + existence_check (); |
2482 | + } else { |
2483 | + assert (_flags & Source::Writable); |
2484 | + /* file will be opened on write */ |
2485 | + return; |
2486 | + } |
2487 | |
2488 | if (open(_path)) { |
2489 | throw failed_constructor (); |
2490 | @@ -354,10 +393,22 @@ |
2491 | name().c_str(), ev.id(), ev.time(), ev.size()); |
2492 | for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");*/ |
2493 | |
2494 | - if (ev.time() < _last_ev_time_beats) { |
2495 | - warning << string_compose(_("Skipping event with unordered time %1"), ev.time()) |
2496 | - << endmsg; |
2497 | - return; |
2498 | + double time = ev.time(); |
2499 | + if (time < _last_ev_time_beats) { |
2500 | + const double difference = _last_ev_time_beats - time; |
2501 | + if (difference / (double)ppqn() < 1.0) { |
2502 | + /* Close enough. This problem occurs because Sequence is not |
2503 | + actually ordered due to fuzzy time comparison. I'm pretty sure |
2504 | + this is inherently a bad idea which causes problems all over the |
2505 | + place, but tolerate it here for now anyway. */ |
2506 | + time = _last_ev_time_beats; |
2507 | + } else { |
2508 | + /* Out of order by more than a tick. */ |
2509 | + warning << string_compose(_("Skipping event with unordered beat time %1 < %2 (off by %3 beats, %4 ticks)"), |
2510 | + ev.time(), _last_ev_time_beats, difference, difference / (double)ppqn()) |
2511 | + << endmsg; |
2512 | + return; |
2513 | + } |
2514 | } |
2515 | |
2516 | Evoral::event_id_t event_id; |
2517 | @@ -372,13 +423,14 @@ |
2518 | _model->append (ev, event_id); |
2519 | } |
2520 | |
2521 | - _length_beats = max(_length_beats, ev.time()); |
2522 | + _length_beats = max(_length_beats, time); |
2523 | |
2524 | - const double delta_time_beats = ev.time() - _last_ev_time_beats; |
2525 | + const double delta_time_beats = time - _last_ev_time_beats; |
2526 | const uint32_t delta_time_ticks = (uint32_t)lrint(delta_time_beats * (double)ppqn()); |
2527 | |
2528 | Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id); |
2529 | - _last_ev_time_beats = ev.time(); |
2530 | + _last_ev_time_beats = time; |
2531 | + _flags = Source::Flag (_flags & ~Empty); |
2532 | } |
2533 | |
2534 | /** Append an event with a timestamp in frames (framepos_t) */ |
2535 | @@ -394,7 +446,8 @@ |
2536 | // for (size_t i=0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n"); |
2537 | |
2538 | if (ev.time() < _last_ev_time_frames) { |
2539 | - warning << string_compose(_("Skipping event with unordered time %1"), ev.time()) |
2540 | + warning << string_compose(_("Skipping event with unordered frame time %1 < %2"), |
2541 | + ev.time(), _last_ev_time_frames) |
2542 | << endmsg; |
2543 | return; |
2544 | } |
2545 | @@ -425,6 +478,7 @@ |
2546 | |
2547 | Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id); |
2548 | _last_ev_time_frames = ev.time(); |
2549 | + _flags = Source::Flag (_flags & ~Empty); |
2550 | } |
2551 | |
2552 | XMLNode& |
2553 | @@ -667,10 +721,12 @@ |
2554 | void |
2555 | SMFSource::flush_midi () |
2556 | { |
2557 | - if (!writable() || (writable() && !_open)) { |
2558 | + if (!writable() || _length_beats == 0.0) { |
2559 | return; |
2560 | } |
2561 | |
2562 | + ensure_disk_file (); |
2563 | + |
2564 | Evoral::SMF::end_write (); |
2565 | /* data in the file means its no longer removable */ |
2566 | mark_nonremovable (); |
2567 | @@ -687,6 +743,10 @@ |
2568 | void |
2569 | SMFSource::ensure_disk_file () |
2570 | { |
2571 | + if (!writable()) { |
2572 | + return; |
2573 | + } |
2574 | + |
2575 | if (_model) { |
2576 | /* We have a model, so write it to disk; see MidiSource::session_saved |
2577 | for an explanation of what we are doing here. |
2578 | @@ -702,9 +762,6 @@ |
2579 | if (!_open) { |
2580 | open_for_write (); |
2581 | } |
2582 | - |
2583 | - /* Flush, which will definitely put something on disk */ |
2584 | - flush_midi (); |
2585 | } |
2586 | } |
2587 | |
2588 | @@ -718,34 +775,3 @@ |
2589 | _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy)); |
2590 | } |
2591 | |
2592 | -int |
2593 | -SMFSource::rename (const string& newname) |
2594 | -{ |
2595 | - Glib::Threads::Mutex::Lock lm (_lock); |
2596 | - string oldpath = _path; |
2597 | - string newpath = _session.new_source_path_from_name (DataType::MIDI, newname); |
2598 | - |
2599 | - if (newpath.empty()) { |
2600 | - error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg; |
2601 | - return -1; |
2602 | - } |
2603 | - |
2604 | - // Test whether newpath exists, if yes notify the user but continue. |
2605 | - if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) { |
2606 | - error << string_compose (_("Programming error! %1 tried to rename a file over another file! It's safe to continue working, but please report this to the developers."), PROGRAM_NAME) << endmsg; |
2607 | - return -1; |
2608 | - } |
2609 | - |
2610 | - if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) { |
2611 | - /* rename only needed if file exists on disk */ |
2612 | - if (::rename (oldpath.c_str(), newpath.c_str()) != 0) { |
2613 | - error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg; |
2614 | - return -1; |
2615 | - } |
2616 | - } |
2617 | - |
2618 | - _name = Glib::path_get_basename (newpath); |
2619 | - _path = newpath; |
2620 | - |
2621 | - return 0; |
2622 | -} |
2623 | |
2624 | === modified file 'libs/ardour/sndfilesource.cc' |
2625 | --- libs/ardour/sndfilesource.cc 2014-05-15 14:33:48 +0000 |
2626 | +++ libs/ardour/sndfilesource.cc 2015-05-02 14:25:06 +0000 |
2627 | @@ -183,6 +183,34 @@ |
2628 | } |
2629 | } |
2630 | |
2631 | +/** Constructor to be called for recovering files being used for |
2632 | + * capture. They are in-session, they already exist, they should not |
2633 | + * be writable. They are an odd hybrid (from a constructor point of |
2634 | + * view) of the previous two constructors. |
2635 | + */ |
2636 | +SndFileSource::SndFileSource (Session& s, const string& path, int chn) |
2637 | + : Source (s, DataType::AUDIO, path, Flag (0)) |
2638 | + /* the final boolean argument is not used, its value is irrelevant. see audiofilesource.h for explanation */ |
2639 | + , AudioFileSource (s, path, Flag (0)) |
2640 | + , _descriptor (0) |
2641 | + , _broadcast_info (0) |
2642 | + , _capture_start (false) |
2643 | + , _capture_end (false) |
2644 | + , file_pos (0) |
2645 | + , xfade_buf (0) |
2646 | +{ |
2647 | + _channel = chn; |
2648 | + |
2649 | + init_sndfile (); |
2650 | + |
2651 | + assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)); |
2652 | + existence_check (); |
2653 | + |
2654 | + if (open()) { |
2655 | + throw failed_constructor (); |
2656 | + } |
2657 | +} |
2658 | + |
2659 | void |
2660 | SndFileSource::init_sndfile () |
2661 | { |
2662 | @@ -256,6 +284,14 @@ |
2663 | delete _broadcast_info; |
2664 | _broadcast_info = 0; |
2665 | _flags = Flag (_flags & ~Broadcast); |
2666 | + } |
2667 | + |
2668 | + /* Set the broadcast flag if the BWF info is already there. We need |
2669 | + * this when recovering or using existing files. |
2670 | + */ |
2671 | + |
2672 | + if (bwf_info_exists) { |
2673 | + _flags = Flag (_flags | Broadcast); |
2674 | } |
2675 | |
2676 | if (writable()) { |
2677 | |
2678 | === modified file 'libs/ardour/source_factory.cc' |
2679 | --- libs/ardour/source_factory.cc 2014-05-15 14:33:48 +0000 |
2680 | +++ libs/ardour/source_factory.cc 2015-05-02 14:25:06 +0000 |
2681 | @@ -342,6 +342,39 @@ |
2682 | } |
2683 | |
2684 | boost::shared_ptr<Source> |
2685 | +SourceFactory::createForRecovery (DataType type, Session& s, const std::string& path, int chn) |
2686 | +{ |
2687 | + /* this might throw failed_constructor(), which is OK */ |
2688 | + |
2689 | + if (type == DataType::AUDIO) { |
2690 | + Source* src = new SndFileSource (s, path, chn); |
2691 | + |
2692 | +#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS |
2693 | + // boost_debug_shared_ptr_mark_interesting (src, "Source"); |
2694 | +#endif |
2695 | + boost::shared_ptr<Source> ret (src); |
2696 | + |
2697 | + if (setup_peakfile (ret, false)) { |
2698 | + return boost::shared_ptr<Source>(); |
2699 | + } |
2700 | + |
2701 | + // no analysis data - this is still basically a new file (we |
2702 | + // crashed while recording. |
2703 | + |
2704 | + // always announce these files |
2705 | + |
2706 | + SourceCreated (ret); |
2707 | + |
2708 | + return ret; |
2709 | + |
2710 | + } else if (type == DataType::MIDI) { |
2711 | + error << _("Recovery attempted on a MIDI file - not implemented") << endmsg; |
2712 | + } |
2713 | + |
2714 | + return boost::shared_ptr<Source> (); |
2715 | +} |
2716 | + |
2717 | +boost::shared_ptr<Source> |
2718 | SourceFactory::createFromPlaylist (DataType type, Session& s, boost::shared_ptr<Playlist> p, const PBD::ID& orig, const std::string& name, |
2719 | uint32_t chn, frameoffset_t start, framecnt_t len, bool copy, bool defer_peaks) |
2720 | { |
2721 | |
2722 | === modified file 'libs/ardour/tempo.cc' |
2723 | --- libs/ardour/tempo.cc 2013-09-21 19:05:02 +0000 |
2724 | +++ libs/ardour/tempo.cc 2015-05-02 14:25:06 +0000 |
2725 | @@ -884,6 +884,7 @@ |
2726 | TempoSection* ts; |
2727 | MeterSection* ms; |
2728 | double beat_frames; |
2729 | + double current_frame_exact; |
2730 | framepos_t bar_start_frame; |
2731 | |
2732 | DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Extend map to %1 from %2 = %3\n", end, current, current_frame)); |
2733 | @@ -895,11 +896,13 @@ |
2734 | } |
2735 | |
2736 | beat_frames = meter->frames_per_grid (*tempo,_frame_rate); |
2737 | + current_frame_exact = current_frame; |
2738 | |
2739 | while (current_frame < end) { |
2740 | |
2741 | current.beats++; |
2742 | - current_frame += beat_frames; |
2743 | + current_frame_exact += beat_frames; |
2744 | + current_frame = llrint(current_frame_exact); |
2745 | |
2746 | if (current.beats > meter->divisions_per_bar()) { |
2747 | current.bars++; |
2748 | @@ -942,7 +945,8 @@ |
2749 | tempo->start(), current_frame, tempo->bar_offset())); |
2750 | |
2751 | /* back up to previous beat */ |
2752 | - current_frame -= beat_frames; |
2753 | + current_frame_exact -= beat_frames; |
2754 | + current_frame = llrint(current_frame_exact); |
2755 | |
2756 | /* set tempo section location |
2757 | * based on offset from last |
2758 | @@ -963,7 +967,8 @@ |
2759 | |
2760 | double offset_within_old_beat = (tempo->frame() - current_frame) / beat_frames; |
2761 | |
2762 | - current_frame += (offset_within_old_beat * beat_frames) + ((1.0 - offset_within_old_beat) * next_beat_frames); |
2763 | + current_frame_exact += (offset_within_old_beat * beat_frames) + ((1.0 - offset_within_old_beat) * next_beat_frames); |
2764 | + current_frame = llrint(current_frame_exact); |
2765 | |
2766 | /* next metric doesn't have to |
2767 | * match this precisely to |
2768 | @@ -1012,11 +1017,11 @@ |
2769 | |
2770 | if (current.beats == 1) { |
2771 | DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Bar at %1|1 @ %2\n", current.bars, current_frame)); |
2772 | - _map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), current.bars, 1)); |
2773 | + _map.push_back (BBTPoint (*meter, *tempo, current_frame, current.bars, 1)); |
2774 | bar_start_frame = current_frame; |
2775 | } else { |
2776 | DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Beat at %1|%2 @ %3\n", current.bars, current.beats, current_frame)); |
2777 | - _map.push_back (BBTPoint (*meter, *tempo, (framepos_t) llrint(current_frame), current.bars, current.beats)); |
2778 | + _map.push_back (BBTPoint (*meter, *tempo, current_frame, current.bars, current.beats)); |
2779 | } |
2780 | |
2781 | if (next_metric == metrics.end()) { |
2782 | |
2783 | === modified file 'libs/midi++2/midnam_patch.cc' |
2784 | --- libs/midi++2/midnam_patch.cc 2014-01-24 16:31:54 +0000 |
2785 | +++ libs/midi++2/midnam_patch.cc 2015-05-02 14:25:06 +0000 |
2786 | @@ -724,14 +724,13 @@ |
2787 | return ""; |
2788 | } |
2789 | |
2790 | + boost::shared_ptr<const NoteNameList> note_names; |
2791 | boost::shared_ptr<const Patch> patch( |
2792 | find_patch(mode_name, channel, PatchPrimaryKey(program, bank))); |
2793 | - if (!patch) { |
2794 | - return ""; |
2795 | + if (patch) { |
2796 | + note_names = note_name_list(patch->note_list_name()); |
2797 | } |
2798 | |
2799 | - boost::shared_ptr<const NoteNameList> note_names( |
2800 | - note_name_list(patch->note_list_name())); |
2801 | if (!note_names) { |
2802 | /* No note names specific to this patch, check the ChannelNameSet */ |
2803 | boost::shared_ptr<ChannelNameSet> chan_names = channel_name_set_by_channel( |
2804 | |
2805 | === added file 'patchfiles/Korg_Volca_Bass.midnam' |
2806 | --- patchfiles/Korg_Volca_Bass.midnam 1970-01-01 00:00:00 +0000 |
2807 | +++ patchfiles/Korg_Volca_Bass.midnam 2015-05-02 14:25:06 +0000 |
2808 | @@ -0,0 +1,37 @@ |
2809 | +<?xml version="1.0" encoding="UTF-8"?> |
2810 | +<!DOCTYPE MIDINameDocument PUBLIC "-//MIDI Manufacturers Association//DTD MIDINameDocument 1.0//EN" "http://www.midi.org/dtds/MIDINameDocument10.dtd"> |
2811 | +<MIDINameDocument> |
2812 | + <Author>David Robillard</Author> |
2813 | + <MasterDeviceNames> |
2814 | + <Manufacturer>Korg</Manufacturer> |
2815 | + <Model>Volca Bass</Model> |
2816 | + <CustomDeviceMode Name="Default"> |
2817 | + <ChannelNameSetAssignments> |
2818 | + <ChannelNameSetAssign Channel="1" NameSet="Names"/> |
2819 | + </ChannelNameSetAssignments> |
2820 | + </CustomDeviceMode> |
2821 | + <ChannelNameSet Name="Names"> |
2822 | + <AvailableForChannels> |
2823 | + <AvailableChannel Channel="1" Available="true"/> |
2824 | + </AvailableForChannels> |
2825 | + <UsesControlNameList Name="Controls"/> |
2826 | + <PatchBank Name="User Patches"> |
2827 | + <PatchNameList Name="User Patches"/> |
2828 | + </PatchBank> |
2829 | + </ChannelNameSet> |
2830 | + <ControlNameList Name="Controls"> |
2831 | + <Control Type="7bit" Number="5" Name="Slide Time"/> |
2832 | + <Control Type="7bit" Number="11" Name="Expression"/> |
2833 | + <Control Type="7bit" Number="40" Name="Octave"/> |
2834 | + <Control Type="7bit" Number="41" Name="LFO Rate"/> |
2835 | + <Control Type="7bit" Number="42" Name="LFO Int"/> |
2836 | + <Control Type="7bit" Number="43" Name="VCO Pitch 1"/> |
2837 | + <Control Type="7bit" Number="44" Name="VCO Pitch 2"/> |
2838 | + <Control Type="7bit" Number="45" Name="VCO Pitch 3"/> |
2839 | + <Control Type="7bit" Number="46" Name="EG Attack"/> |
2840 | + <Control Type="7bit" Number="47" Name="EG Decay/Release"/> |
2841 | + <Control Type="7bit" Number="48" Name="Cutoff EG Int"/> |
2842 | + <Control Type="7bit" Number="49" Name="Gate Time"/> |
2843 | + </ControlNameList> |
2844 | + </MasterDeviceNames> |
2845 | +</MIDINameDocument> |
2846 | |
2847 | === added file 'patchfiles/Korg_Volca_Beats.midnam' |
2848 | --- patchfiles/Korg_Volca_Beats.midnam 1970-01-01 00:00:00 +0000 |
2849 | +++ patchfiles/Korg_Volca_Beats.midnam 2015-05-02 14:25:06 +0000 |
2850 | @@ -0,0 +1,58 @@ |
2851 | +<?xml version="1.0" encoding="UTF-8"?> |
2852 | +<!DOCTYPE MIDINameDocument PUBLIC "-//MIDI Manufacturers Association//DTD MIDINameDocument 1.0//EN" "http://www.midi.org/dtds/MIDINameDocument10.dtd"> |
2853 | +<MIDINameDocument> |
2854 | + <Author>David Robillard</Author> |
2855 | + <MasterDeviceNames> |
2856 | + <Manufacturer>Korg</Manufacturer> |
2857 | + <Model>Volca Beats</Model> |
2858 | + <CustomDeviceMode Name="Default"> |
2859 | + <ChannelNameSetAssignments> |
2860 | + <ChannelNameSetAssign Channel="10" NameSet="Names"/> |
2861 | + </ChannelNameSetAssignments> |
2862 | + </CustomDeviceMode> |
2863 | + <ChannelNameSet Name="Names"> |
2864 | + <AvailableForChannels> |
2865 | + <AvailableChannel Channel="10" Available="true"/> |
2866 | + </AvailableForChannels> |
2867 | + <UsesNoteNameList Name="Notes"/> |
2868 | + <UsesControlNameList Name="Controls"/> |
2869 | + <PatchBank Name="User Patches"> |
2870 | + <PatchNameList Name="User Patches"/> |
2871 | + </PatchBank> |
2872 | + </ChannelNameSet> |
2873 | + <NoteNameList Name="Notes"> |
2874 | + <Note Number="37" Name="Kick"/> |
2875 | + <Note Number="39" Name="Snare"/> |
2876 | + <Note Number="40" Name="Clap"/> |
2877 | + <Note Number="43" Name="Closed Hi-hat"/> |
2878 | + <Note Number="44" Name="Low Tom"/> |
2879 | + <Note Number="47" Name="Open Hi-hat"/> |
2880 | + <Note Number="50" Name="Crash Cymbal"/> |
2881 | + <Note Number="51" Name="High Tom"/> |
2882 | + <Note Number="68" Name="AgogĂ´"/> |
2883 | + <Note Number="76" Name="Claves"/> |
2884 | + </NoteNameList> |
2885 | + <ControlNameList Name="Controls"> |
2886 | + <Control Type="7bit" Number="40" Name="Kick Level"/> |
2887 | + <Control Type="7bit" Number="41" Name="Snare Level"/> |
2888 | + <Control Type="7bit" Number="42" Name="Low Tom Level"/> |
2889 | + <Control Type="7bit" Number="43" Name="High Tom Level"/> |
2890 | + <Control Type="7bit" Number="44" Name="Closed Hat Level"/> |
2891 | + <Control Type="7bit" Number="45" Name="Open Hat Level"/> |
2892 | + <Control Type="7bit" Number="46" Name="Clap Level"/> |
2893 | + <Control Type="7bit" Number="47" Name="Claves Level"/> |
2894 | + <Control Type="7bit" Number="48" Name="Agogo Level"/> |
2895 | + <Control Type="7bit" Number="49" Name="Crash Level"/> |
2896 | + <Control Type="7bit" Number="50" Name="Clap PCM Speed"/> |
2897 | + <Control Type="7bit" Number="51" Name="Claves PCM Speed"/> |
2898 | + <Control Type="7bit" Number="52" Name="Agogo PCM Speed"/> |
2899 | + <Control Type="7bit" Number="53" Name="Crash PCM Speed"/> |
2900 | + <Control Type="7bit" Number="54" Name="Stutter Time"/> |
2901 | + <Control Type="7bit" Number="55" Name="Stutter Depth"/> |
2902 | + <Control Type="7bit" Number="56" Name="Tom Decay"/> |
2903 | + <Control Type="7bit" Number="57" Name="Closed Hat Decay"/> |
2904 | + <Control Type="7bit" Number="58" Name="Open Hat Decay"/> |
2905 | + <Control Type="7bit" Number="59" Name="Hat Grain"/> |
2906 | + </ControlNameList> |
2907 | + </MasterDeviceNames> |
2908 | +</MIDINameDocument> |
2909 | |
2910 | === added file 'patchfiles/Korg_Volca_Keys.midnam' |
2911 | --- patchfiles/Korg_Volca_Keys.midnam 1970-01-01 00:00:00 +0000 |
2912 | +++ patchfiles/Korg_Volca_Keys.midnam 2015-05-02 14:25:06 +0000 |
2913 | @@ -0,0 +1,41 @@ |
2914 | +<?xml version="1.0" encoding="UTF-8"?> |
2915 | +<!DOCTYPE MIDINameDocument PUBLIC "-//MIDI Manufacturers Association//DTD MIDINameDocument 1.0//EN" "http://www.midi.org/dtds/MIDINameDocument10.dtd"> |
2916 | +<MIDINameDocument> |
2917 | + <Author>David Robillard</Author> |
2918 | + <MasterDeviceNames> |
2919 | + <Manufacturer>Korg</Manufacturer> |
2920 | + <Model>Volca Keys</Model> |
2921 | + <CustomDeviceMode Name="Default"> |
2922 | + <ChannelNameSetAssignments> |
2923 | + <ChannelNameSetAssign Channel="1" NameSet="Names"/> |
2924 | + </ChannelNameSetAssignments> |
2925 | + </CustomDeviceMode> |
2926 | + <ChannelNameSet Name="Names"> |
2927 | + <AvailableForChannels> |
2928 | + <AvailableChannel Channel="1" Available="true"/> |
2929 | + </AvailableForChannels> |
2930 | + <UsesControlNameList Name="Controls"/> |
2931 | + <PatchBank Name="User Patches"> |
2932 | + <PatchNameList Name="User Patches"/> |
2933 | + </PatchBank> |
2934 | + </ChannelNameSet> |
2935 | + <ControlNameList Name="Controls"> |
2936 | + <Control Type="7bit" Number="5" Name="Portamento"/> |
2937 | + <Control Type="7bit" Number="11" Name="Expression"/> |
2938 | + <Control Type="7bit" Number="40" Name="Voice"/> |
2939 | + <Control Type="7bit" Number="41" Name="Octave"/> |
2940 | + <Control Type="7bit" Number="42" Name="Detune"/> |
2941 | + <Control Type="7bit" Number="43" Name="VCO EG Int"/> |
2942 | + <Control Type="7bit" Number="44" Name="Cutoff"/> |
2943 | + <Control Type="7bit" Number="45" Name="VCF EG Int"/> |
2944 | + <Control Type="7bit" Number="46" Name="LFO Rate"/> |
2945 | + <Control Type="7bit" Number="47" Name="LFO Pitch Int"/> |
2946 | + <Control Type="7bit" Number="48" Name="LFO Cutoff Int"/> |
2947 | + <Control Type="7bit" Number="49" Name="EG Attack"/> |
2948 | + <Control Type="7bit" Number="50" Name="EG Decay/Release"/> |
2949 | + <Control Type="7bit" Number="51" Name="EG Sustain"/> |
2950 | + <Control Type="7bit" Number="52" Name="Delay Time"/> |
2951 | + <Control Type="7bit" Number="53" Name="Delay Feedback"/> |
2952 | + </ControlNameList> |
2953 | + </MasterDeviceNames> |
2954 | +</MIDINameDocument> |
Rejecting in favor of a backport SRU