From 856aad62dabb6e3509d34fe97c2b2c4d1b2e0834 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Thu, 11 Dec 2025 08:55:54 -0700 Subject: [PATCH] SMF import: attempt to find tempo-related metadata in any track --- libs/ardour/import.cc | 83 +++++++++++++++++++++++++++++----------- libs/evoral/SMF.cc | 25 ++++++++++++ libs/evoral/evoral/SMF.h | 1 + 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/libs/ardour/import.cc b/libs/ardour/import.cc index 60f99bc84a..3bf941dd12 100644 --- a/libs/ardour/import.cc +++ b/libs/ardour/import.cc @@ -469,47 +469,78 @@ write_midi_type0_data_to_one_file (Evoral::SMF* source, ImportStatus& status, si } +static bool +track_contains_tempo_or_key_metadata (Evoral::SMF* source, int track) +{ + if (source->seek_to_track (track+1) != 0) { + return false; + } + + uint8_t* buf = (uint8_t*) malloc (4); + uint32_t delta_t = 0; + uint32_t size = 4; + bool seen = false; + Evoral::event_id_t ignored_note_id; /* imported files either don't have noted IDs or we ignore them */ + + while (true) { + int ret = source->read_event (&delta_t, &size, &buf, &ignored_note_id); + + if (ret < 0) { // EOT + break; + } + + if (size == 0) { + /* meta event that is not for us */ + continue; + } + + if (Evoral::SMF::is_tempo_or_meter_related (buf, size)) { + seen = true; + break; + } + } + + free (buf); + return seen; +} + /* return true if only meta-data was found */ static bool write_midi_type1_data_to_one_file (Evoral::SMF* source, ImportStatus& status, std::shared_ptr smfs, - int track, bool split_midi_channels, int channel) + int track, bool split_midi_channels, int channel, int meta_track) { uint32_t bufsize = 4; uint8_t* buf = (uint8_t*) malloc (bufsize); bool had_meta = false; bool had_notes = false; - + uint32_t written = 0; Evoral::event_id_t ignored_note_id; /* imported files either don't have noted IDs or we ignore them */ + /* libsmf starts counting tracks at one, not zero */ + track++; + meta_track++; + + /* Check track number is legal. + */ + if (track > source->num_tracks()) { + return false; + } + Source::WriterLock target_lock (smfs->mutex()); smfs->mark_streaming_write_started (target_lock); smfs->drop_model (target_lock); - /* When a type 1 file has only one track, use it as-is. - * If there are more tracks, assume track 1 contains meta-data. - */ - int track_offset = (source->num_tracks() == 1) ? 1 : 2; - - track += track_offset; - 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()) { - return false; - } - /* Get metadata first */ - if (source->seek_to_track (1) == 0) { /* type 1 has metadata in track 1 */ + if (meta_track > 0 && source->seek_to_track (meta_track) == 0) { uint64_t t = 0; uint32_t delta_t = 0; uint32_t size = 0; + std::cerr << "For track " << track - 1 << " collecting and writing metadata from " << meta_track - 1 << std::endl; + while (!status.cancel) { size = bufsize; @@ -554,8 +585,6 @@ write_midi_type1_data_to_one_file (Evoral::SMF* source, ImportStatus& status, st } } - uint32_t written = 0; - if (source->seek_to_track (track) == 0) { uint64_t t = 0; @@ -669,6 +698,7 @@ write_midi_data_to_new_files (Evoral::SMF* source, ImportStatus& status, status.progress = 0.0f; size_t nfiles = newsrcs.size(); size_t n = 0; + int32_t meta_track = -1; switch (source->smf_format()) { case 0: @@ -695,11 +725,20 @@ write_midi_data_to_new_files (Evoral::SMF* source, ImportStatus& status, case 1: channel = 0; + for (uint16_t n = 0; n < source->num_tracks(); ++n) { + if (track_contains_tempo_or_key_metadata (source, n)) { + meta_track = n; + break; + } + } + + std::cerr << "tempo/keysig metadata appears to be in track " << meta_track << std::endl; + for (auto & newsrc : newsrcs) { std::shared_ptr smfs = std::dynamic_pointer_cast (newsrc); assert (smfs); - bool meta_only = write_midi_type1_data_to_one_file (source, status, smfs, n, split_midi_channels, channel); + bool meta_only = write_midi_type1_data_to_one_file (source, status, smfs, n, split_midi_channels, channel, meta_track); if (split_midi_channels && !meta_only) { channel = (channel + 1) % 16; diff --git a/libs/evoral/SMF.cc b/libs/evoral/SMF.cc index 05715f321b..c749b3f946 100644 --- a/libs/evoral/SMF.cc +++ b/libs/evoral/SMF.cc @@ -449,6 +449,31 @@ SMF::is_meta (uint8_t const* buf, uint32_t size) return false; } +bool +SMF::is_tempo_or_meter_related (uint8_t const* buf, uint32_t size) +{ + if (size < 2) { + return false; + } + + /* unlike the libsmf version of this functionality, this explicitly + * tests for known metadata event types, and only allows them. + */ + + if (buf[0] == 0xff) { + switch (buf[1]) { + case 0x51: /* set tempo */ + case 0x58: /* time signature */ + case 0x59: /* key signaturey */ + return true; + default: + break; + } + } + + return false; +} + int SMF::append_event_delta (uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id, bool allow_meta) { diff --git a/libs/evoral/evoral/SMF.h b/libs/evoral/evoral/SMF.h index 9914d54684..d11c3d4530 100644 --- a/libs/evoral/evoral/SMF.h +++ b/libs/evoral/evoral/SMF.h @@ -89,6 +89,7 @@ public: bool is_empty() const { return _empty; } static bool is_meta (uint8_t const * buf, uint32_t size); + static bool is_tempo_or_meter_related (uint8_t const * buf, uint32_t size); void begin_write(); int append_event_delta (uint32_t delta_t, uint32_t size, const uint8_t* buf, event_id_t note_id, bool allow_meta = false);