mirror of
https://github.com/Ardour/ardour.git
synced 2026-01-07 22:25:46 +01:00
Reimplement MIDI file import to retain metadata in the files written
Previosuly, all meta data was thrown away. Now we retain it, so that MIDI regions can ask about their source file's tempo & meter. Significant engineering rework of how this all works, relying on recently introduced API and API changes in SMF, SMFSource etc.
This commit is contained in:
parent
48cd807e6a
commit
79da488132
1 changed files with 260 additions and 78 deletions
|
|
@ -363,61 +363,132 @@ write_audio_data_to_new_files (ImportableSource* source, ImportStatus& status,
|
|||
}
|
||||
|
||||
static void
|
||||
write_midi_data_to_new_files (Evoral::SMF* source, ImportStatus& status,
|
||||
vector<std::shared_ptr<Source> >& newfiles,
|
||||
bool split_midi_channels)
|
||||
write_midi_type0_data_to_one_file (Evoral::SMF* source, ImportStatus& status, size_t n, size_t nfiles, std::shared_ptr<SMFSource> smfs, bool split_midi_channels, int channel)
|
||||
{
|
||||
uint32_t buf_size = 4;
|
||||
uint8_t* buf = (uint8_t*) malloc (buf_size);
|
||||
uint32_t bufsize = 4;
|
||||
uint8_t* buf = (uint8_t*) malloc (bufsize);
|
||||
bool had_meta = false;
|
||||
Evoral::event_id_t ignored_note_id; /* imported files either don't have noted IDs or we ignore them */
|
||||
|
||||
status.progress = 0.0f;
|
||||
|
||||
bool type0 = source->smf_format()==0;
|
||||
|
||||
int total_files = newfiles.size();
|
||||
Source::WriterLock target_lock (smfs->mutex());
|
||||
smfs->mark_streaming_write_started (target_lock);
|
||||
smfs->drop_model (target_lock);
|
||||
|
||||
try {
|
||||
vector<std::shared_ptr<Source> >::iterator s = newfiles.begin();
|
||||
|
||||
int cur_chan = 0;
|
||||
source->seek_to_start();
|
||||
|
||||
for (int i = 0; i < total_files; ++i) {
|
||||
uint64_t t = 0;
|
||||
uint32_t delta_t = 0;
|
||||
uint32_t size = 0;
|
||||
uint32_t written = 0;
|
||||
|
||||
int cur_track = i+1; //first Track of a type-1 file is metadata only. Start importing sourcefiles at Track index 1
|
||||
while (!status.cancel) {
|
||||
|
||||
if (split_midi_channels) { //if splitting channels we will need to fill 16x sources. empties will be disposed-of later
|
||||
cur_track = 1 + (int) floor((float)i/16.f); //calculate the Track needed for this sourcefile (offset by 1)
|
||||
size = bufsize;
|
||||
|
||||
int ret = source->read_event (&delta_t, &size, &buf, &ignored_note_id);
|
||||
|
||||
if (size > bufsize) {
|
||||
bufsize = size;
|
||||
}
|
||||
|
||||
std::shared_ptr<SMFSource> smfs = std::dynamic_pointer_cast<SMFSource> (*s);
|
||||
if (!smfs) {
|
||||
continue; //should never happen. The calling code should provide exactly the number of tracks&channels we need
|
||||
if (ret < 0) { // EOT
|
||||
break;
|
||||
}
|
||||
|
||||
Source::WriterLock source_lock(smfs->mutex());
|
||||
t += delta_t;
|
||||
|
||||
smfs->drop_model (source_lock);
|
||||
if (type0) {
|
||||
source->seek_to_start ();
|
||||
} else {
|
||||
source->seek_to_track (cur_track);
|
||||
/* if requested by user, each sourcefile gets only a single channel's data */
|
||||
|
||||
if (split_midi_channels) {
|
||||
uint8_t type = buf[0] & 0xf0;
|
||||
uint8_t chan = buf[0] & 0x0f;
|
||||
if (type >= 0x80 && type <= 0xE0) {
|
||||
if (chan != channel) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
smfs->append_event_beats (
|
||||
target_lock,
|
||||
Evoral::Event<Temporal::Beats>(
|
||||
Evoral::MIDI_EVENT,
|
||||
Temporal::Beats::ticks_at_rate(t, source->ppqn()),
|
||||
size,
|
||||
buf));
|
||||
|
||||
written++;
|
||||
|
||||
if (status.progress < 0.99) {
|
||||
status.progress += 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
if (had_meta || written) {
|
||||
|
||||
/* we wrote something */
|
||||
|
||||
smfs->mark_streaming_write_completed (target_lock, timecnt_t (source->duration()));
|
||||
|
||||
/* the streaming write that we've just finished
|
||||
* only wrote data to the SMF object, which is
|
||||
* ultimately an on-disk data structure. So now
|
||||
* we pull the data back from disk to build our
|
||||
* in-memory MidiModel version.
|
||||
*/
|
||||
|
||||
smfs->load_model (target_lock, true);
|
||||
|
||||
} else {
|
||||
info << string_compose (_("No usable MIDI data found for file %1 of %2"), n, nfiles) << endmsg;
|
||||
}
|
||||
|
||||
} catch (exception& e) {
|
||||
error << string_compose (_("MIDI file could not be written (best guess: %1)"), e.what()) << endmsg;
|
||||
}
|
||||
|
||||
free (buf);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
write_midi_type1_data_to_one_file (Evoral::SMF* source, ImportStatus& status, std::shared_ptr<SMFSource> smfs,
|
||||
int track, bool split_midi_channels, int channel)
|
||||
{
|
||||
uint32_t bufsize = 4;
|
||||
uint8_t* buf = (uint8_t*) malloc (bufsize);
|
||||
bool had_meta = false;
|
||||
Evoral::event_id_t ignored_note_id; /* imported files either don't have noted IDs or we ignore them */
|
||||
|
||||
Source::WriterLock target_lock (smfs->mutex());
|
||||
smfs->mark_streaming_write_started (target_lock);
|
||||
smfs->drop_model (target_lock);
|
||||
|
||||
try {
|
||||
|
||||
/* Check track number is legal. Remember, track 0 is metadata, so the number of
|
||||
* real tracks is one less than the number of tracks reported via libsmf.
|
||||
*/
|
||||
|
||||
if (track >= source->num_tracks() - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get metadata first */
|
||||
|
||||
if (source->seek_to_track (1) == 0) { /* type 1 has metadata in track 1 */
|
||||
|
||||
uint64_t t = 0;
|
||||
uint32_t delta_t = 0;
|
||||
uint32_t size = 0;
|
||||
bool first = true;
|
||||
|
||||
while (!status.cancel) {
|
||||
gint note_id_ignored; // imported files either don't have NoteID's or we ignore them.
|
||||
|
||||
size = buf_size;
|
||||
size = bufsize;
|
||||
|
||||
int ret = source->read_event (&delta_t, &size, &buf, ¬e_id_ignored);
|
||||
|
||||
if (size > buf_size) {
|
||||
buf_size = size;
|
||||
}
|
||||
int ret = source->read_event (&delta_t, &size, &buf, &ignored_note_id);
|
||||
|
||||
if (ret < 0) { // EOT
|
||||
break;
|
||||
|
|
@ -425,76 +496,187 @@ write_midi_data_to_new_files (Evoral::SMF* source, ImportStatus& status,
|
|||
|
||||
t += delta_t;
|
||||
|
||||
if (ret == 0) { // Meta
|
||||
if (size == 0) {
|
||||
/* meta event that is not for us */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* if requested by user, each sourcefile gets only a single channel's data */
|
||||
if (split_midi_channels) {
|
||||
uint8_t type = buf[0] & 0xf0;
|
||||
uint8_t chan = buf[0] & 0x0f;
|
||||
if (type >= 0x80 && type <= 0xE0) {
|
||||
if (chan != cur_chan) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (size > bufsize) {
|
||||
bufsize = size;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
smfs->mark_streaming_write_started (source_lock);
|
||||
first = false;
|
||||
}
|
||||
if (ret == 0) { // meta event
|
||||
|
||||
smfs->append_event_beats(
|
||||
source_lock,
|
||||
Evoral::Event<Temporal::Beats>(
|
||||
Evoral::MIDI_EVENT,
|
||||
Temporal::Beats::ticks_at_rate(t, source->ppqn()),
|
||||
size,
|
||||
buf));
|
||||
had_meta = true;
|
||||
|
||||
smfs->append_event_beats (
|
||||
target_lock,
|
||||
Evoral::Event<Temporal::Beats>(
|
||||
Evoral::MIDI_EVENT,
|
||||
Temporal::Beats::ticks_at_rate(t, source->ppqn()),
|
||||
size,
|
||||
buf), true); /* allow meta-events */
|
||||
}
|
||||
|
||||
if (status.progress < 0.99) {
|
||||
status.progress += 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
if (had_meta) {
|
||||
smfs->end_track (target_lock);
|
||||
}
|
||||
}
|
||||
|
||||
/* we wrote something */
|
||||
uint32_t written = 0;
|
||||
|
||||
smfs->mark_streaming_write_completed (source_lock, timecnt_t (source->duration()));
|
||||
if (source->seek_to_track (track+2) == 0) { /* type 1 has metadata in track 1, so the nth real track is track n+2 */
|
||||
|
||||
/* the streaming write that we've just finished
|
||||
* only wrote data to the SMF object, which is
|
||||
* ultimately an on-disk data structure. So now
|
||||
* we pull the data back from disk to build our
|
||||
* in-memory MidiModel version.
|
||||
*/
|
||||
uint64_t t = 0;
|
||||
uint32_t delta_t = 0;
|
||||
uint32_t size = 0;
|
||||
|
||||
smfs->load_model (source_lock, true);
|
||||
while (!status.cancel) {
|
||||
gint note_id_ignored; // imported files either don't have NoteID's or we ignore them.
|
||||
|
||||
if (status.cancel) {
|
||||
size = bufsize;
|
||||
|
||||
int ret = source->read_event (&delta_t, &size, &buf, ¬e_id_ignored);
|
||||
|
||||
if (ret < 0) { // EOT
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
info << string_compose (_("Track %1 of %2 contained no usable MIDI data"), i, total_files) << endmsg;
|
||||
}
|
||||
t += delta_t;
|
||||
|
||||
++s; // next source
|
||||
if (size == 0) {
|
||||
/* meta event, not for us */
|
||||
continue;
|
||||
}
|
||||
|
||||
++cur_chan;
|
||||
if (cur_chan > 15) {
|
||||
cur_chan=0;
|
||||
if (size > bufsize) {
|
||||
bufsize = size;
|
||||
}
|
||||
|
||||
if (ret > 0) { // non-meta event
|
||||
|
||||
/* if requested by user, each sourcefile gets only a single channel's data */
|
||||
|
||||
if (split_midi_channels) {
|
||||
uint8_t type = buf[0] & 0xf0;
|
||||
uint8_t chan = buf[0] & 0x0f;
|
||||
if (type >= 0x80 && type <= 0xE0) {
|
||||
if (chan != channel) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
smfs->append_event_beats (
|
||||
target_lock,
|
||||
Evoral::Event<Temporal::Beats>(
|
||||
Evoral::MIDI_EVENT,
|
||||
Temporal::Beats::ticks_at_rate(t, source->ppqn()),
|
||||
size,
|
||||
buf));
|
||||
|
||||
written++;
|
||||
}
|
||||
|
||||
if (status.progress < 0.99) {
|
||||
status.progress += 0.01;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cerr << "could not seek to " << track + 2 << std::endl;
|
||||
}
|
||||
|
||||
if (had_meta || written) {
|
||||
|
||||
/* we wrote something */
|
||||
|
||||
smfs->mark_streaming_write_completed (target_lock, timecnt_t (source->duration()));
|
||||
|
||||
/* the streaming write that we've just finished
|
||||
* only wrote data to the SMF object, which is
|
||||
* ultimately an on-disk data structure. So now
|
||||
* we pull the data back from disk to build our
|
||||
* in-memory MidiModel version.
|
||||
*/
|
||||
|
||||
smfs->load_model (target_lock, true);
|
||||
|
||||
} else {
|
||||
info << string_compose (_("Track %1 contained no usable MIDI data"), track) << endmsg;
|
||||
}
|
||||
|
||||
} catch (exception& e) {
|
||||
error << string_compose (_("MIDI file could not be written (best guess: %1)"), e.what()) << endmsg;
|
||||
}
|
||||
|
||||
if (buf) {
|
||||
free (buf);
|
||||
free (buf);
|
||||
}
|
||||
|
||||
static void
|
||||
write_midi_data_to_new_files (Evoral::SMF* source, ImportStatus& status,
|
||||
vector<std::shared_ptr<Source> >& newsrcs,
|
||||
bool split_midi_channels)
|
||||
{
|
||||
int track;
|
||||
int channel;
|
||||
|
||||
status.progress = 0.0f;
|
||||
size_t nfiles = newsrcs.size();
|
||||
size_t n = 0;
|
||||
|
||||
switch (source->smf_format()) {
|
||||
case 0:
|
||||
channel = 0;
|
||||
|
||||
for (auto & newsrc : newsrcs) {
|
||||
std::shared_ptr<SMFSource> smfs = std::dynamic_pointer_cast<SMFSource> (newsrc);
|
||||
assert (smfs);
|
||||
|
||||
write_midi_type0_data_to_one_file (source, status, n, nfiles, smfs, split_midi_channels, channel);
|
||||
|
||||
if (split_midi_channels) {
|
||||
channel = (channel + 1) % 16;
|
||||
}
|
||||
|
||||
if (status.cancel) {
|
||||
break;
|
||||
}
|
||||
|
||||
++n;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
track = 0;
|
||||
channel = 0;
|
||||
|
||||
for (auto & newsrc : newsrcs) {
|
||||
std::shared_ptr<SMFSource> smfs = std::dynamic_pointer_cast<SMFSource> (newsrc);
|
||||
assert (smfs);
|
||||
|
||||
write_midi_type1_data_to_one_file (source, status, smfs, track, split_midi_channels, channel);
|
||||
|
||||
track = (track + 1) % 16;
|
||||
|
||||
if (split_midi_channels) {
|
||||
channel = (channel + 1) % 16;
|
||||
}
|
||||
|
||||
if (status.cancel) {
|
||||
break;
|
||||
}
|
||||
|
||||
++n;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error << string_compose (_("MIDI file has unsupported SMF format type %1"), source->smf_format()) << endmsg;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -535,8 +717,8 @@ Session::deinterlace_midi_region (std::shared_ptr<MidiRegion> mr)
|
|||
|
||||
/* create new file paths for 16 potential channels of midi data */
|
||||
vector<string> smf_names;
|
||||
for (int i = 0; i<16; i++) {
|
||||
smf_names.push_back(string_compose("-ch%1", i+1));
|
||||
for (int i = 0; i < 16; i++) {
|
||||
smf_names.push_back (string_compose ("-ch%1", i+1));
|
||||
}
|
||||
vector<string> new_paths = get_paths_for_new_sources (false, source_path, 16, smf_names, true);
|
||||
|
||||
|
|
@ -654,8 +836,8 @@ Session::import_files (ImportStatus& status)
|
|||
/* Type0: we should prepare filenames for up to 16 channels in the file; we will throw out the empty ones later */
|
||||
if (status.split_midi_channels) {
|
||||
num_channels = 16;
|
||||
for (uint32_t i = 0; i<num_channels; i++) {
|
||||
smf_names.push_back( string_compose ("ch%1", 1+i ) ); //chanX
|
||||
for (uint32_t i = 0; i < num_channels; i++) {
|
||||
smf_names.push_back (string_compose ("ch%1", 1+i ) ); //chanX
|
||||
}
|
||||
} else {
|
||||
num_channels = 1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue