summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/ardour/SConscript1
-rw-r--r--libs/ardour/ardour/midi_event.h7
-rw-r--r--libs/ardour/ardour/midi_util.h2
-rw-r--r--libs/ardour/ardour/smf_reader.h89
-rw-r--r--libs/ardour/ardour/smf_source.h6
-rw-r--r--libs/ardour/ardour/source.h7
-rw-r--r--libs/ardour/import.cc202
-rw-r--r--libs/ardour/midi_diskstream.cc2
-rw-r--r--libs/ardour/midi_model.cc2
-rw-r--r--libs/ardour/playlist.cc2
-rw-r--r--libs/ardour/region_factory.cc2
-rw-r--r--libs/ardour/smf_reader.cc294
-rw-r--r--libs/ardour/smf_source.cc18
13 files changed, 574 insertions, 60 deletions
diff --git a/libs/ardour/SConscript b/libs/ardour/SConscript
index 7521bd728a..942905c341 100644
--- a/libs/ardour/SConscript
+++ b/libs/ardour/SConscript
@@ -129,6 +129,7 @@ session_time.cc
session_transport.cc
session_utils.cc
silentfilesource.cc
+smf_reader.cc
smf_source.cc
sndfile_helpers.cc
sndfilesource.cc
diff --git a/libs/ardour/ardour/midi_event.h b/libs/ardour/ardour/midi_event.h
index ee6ea7b749..7dafb0fbcf 100644
--- a/libs/ardour/ardour/midi_event.h
+++ b/libs/ardour/ardour/midi_event.h
@@ -86,7 +86,7 @@ struct MidiEvent {
if (_owns_buffer) {
if (copy._buffer) {
if (!_buffer || _size < copy._size)
- _buffer = (Byte*)realloc(_buffer, copy._size);
+ _buffer = (Byte*)::realloc(_buffer, copy._size);
memcpy(_buffer, copy._buffer, copy._size);
} else {
free(_buffer);
@@ -130,6 +130,11 @@ struct MidiEvent {
_owns_buffer = own;
}
+ inline void realloc(size_t size) {
+ assert(_owns_buffer);
+ _buffer = (Byte*) ::realloc(_buffer, size);
+ }
+
#else
inline void set_buffer(Byte* buf) { _buffer = buf; }
diff --git a/libs/ardour/ardour/midi_util.h b/libs/ardour/ardour/midi_util.h
index d85ab128fe..1f121fbd68 100644
--- a/libs/ardour/ardour/midi_util.h
+++ b/libs/ardour/ardour/midi_util.h
@@ -28,7 +28,7 @@ namespace ARDOUR {
/** Return the size of the given event NOT including the status byte,
* or -1 if unknown (eg sysex)
*/
-int
+static inline int
midi_event_size(unsigned char status)
{
if (status >= 0x80 && status <= 0xE0) {
diff --git a/libs/ardour/ardour/smf_reader.h b/libs/ardour/ardour/smf_reader.h
new file mode 100644
index 0000000000..806844004e
--- /dev/null
+++ b/libs/ardour/ardour/smf_reader.h
@@ -0,0 +1,89 @@
+/*
+ Copyright (C) 2008 Paul Davis
+ Written by Dave Robillard
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef __ardour_smf_reader_h__
+#define __ardour_smf_reader_h__
+
+#include <exception>
+#include <stdexcept>
+#include <string>
+#include <inttypes.h>
+
+namespace ARDOUR {
+
+
+/** Standard MIDI File (Type 0) Reader
+ *
+ * Currently this only reads SMF files with tempo-based timing.
+ */
+class SMFReader {
+public:
+ class PrematureEOF : public std::exception {
+ const char* what() const throw() { return "Unexpected end of file"; }
+ };
+ class CorruptFile : public std::exception {
+ const char* what() const throw() { return "Corrupted file"; }
+ };
+ class UnsupportedTime : public std::exception {
+ const char* what() const throw() { return "Unsupported time stamp type (SMPTE)"; }
+ };
+
+ SMFReader(const std::string filename="");
+ ~SMFReader();
+
+ bool open(const std::string& filename) throw (std::logic_error, UnsupportedTime);
+
+ bool seek_to_track(unsigned track) throw (std::logic_error);
+
+ const std::string& filename() const { return _filename; };
+
+ //TimeUnit unit() const { return _unit; }
+ uint16_t type() const { return _type; }
+ uint16_t ppqn() const { return _ppqn; }
+ uint16_t num_tracks() const { return _num_tracks; }
+
+ int read_event(size_t buf_len,
+ uint8_t* buf,
+ uint32_t* ev_size,
+ uint32_t* ev_delta_time)
+ throw (std::logic_error, PrematureEOF, CorruptFile);
+
+ void close();
+
+protected:
+ /** size of SMF header, including MTrk chunk header */
+ static const uint32_t HEADER_SIZE = 22;
+
+ uint32_t read_var_len() const throw(PrematureEOF);
+
+ std::string _filename;
+ FILE* _fd;
+ //TimeUnit _unit;
+ uint16_t _type;
+ uint16_t _ppqn;
+ uint16_t _num_tracks;
+ uint32_t _track;
+ uint32_t _track_size;
+};
+
+
+} // namespace ARDOUR
+
+#endif /* __ardour_smf_reader_h__ */
+
diff --git a/libs/ardour/ardour/smf_source.h b/libs/ardour/ardour/smf_source.h
index bb3950f2ee..062e812643 100644
--- a/libs/ardour/ardour/smf_source.h
+++ b/libs/ardour/ardour/smf_source.h
@@ -63,8 +63,10 @@ class SMFSource : public MidiSource {
bool set_name (const std::string& newname) { return (set_source_name(newname, false) == 0); }
int set_source_name (string newname, bool destructive);
+
+ static bool safe_file_extension (const Glib::ustring& path);
- string path() const { return _path; }
+ Glib::ustring path() const { return _path; }
void set_allow_remove_if_empty (bool yn);
void mark_for_remove();
@@ -120,7 +122,7 @@ class SMFSource : public MidiSource {
static const uint16_t _ppqn = 19200;
uint16_t _channel;
- string _path;
+ Glib::ustring _path;
Flag _flags;
string _take_id;
bool _allow_remove_if_empty;
diff --git a/libs/ardour/ardour/source.h b/libs/ardour/ardour/source.h
index 3109cb00ff..a2ee96bf63 100644
--- a/libs/ardour/ardour/source.h
+++ b/libs/ardour/ardour/source.h
@@ -50,8 +50,9 @@ class Source : public SessionObject, public ARDOUR::Readable
time_t timestamp() const { return _timestamp; }
void stamp (time_t when) { _timestamp = when; }
- /** @return the number of items in this source */
nframes_t length() const { return _length; }
+
+ virtual Glib::ustring path() const = 0;
virtual nframes_t natural_position() const { return 0; }
@@ -88,10 +89,10 @@ class Source : public SessionObject, public ARDOUR::Readable
AnalysisFeatureList transients;
std::string get_transients_path() const;
int load_transients (const std::string&);
+
+ void update_length (nframes_t pos, nframes_t cnt);
protected:
- void update_length (nframes_t pos, nframes_t cnt);
-
DataType _type;
time_t _timestamp;
nframes_t _length;
diff --git a/libs/ardour/import.cc b/libs/ardour/import.cc
index 2bc3a00a7a..d4a1bc72e7 100644
--- a/libs/ardour/import.cc
+++ b/libs/ardour/import.cc
@@ -39,7 +39,9 @@
#include <ardour/ardour.h>
#include <ardour/session.h>
+#include <ardour/session_directory.h>
#include <ardour/audio_diskstream.h>
+#include <ardour/audioengine.h>
#include <ardour/sndfilesource.h>
#include <ardour/sndfile_helpers.h>
#include <ardour/audioregion.h>
@@ -47,6 +49,9 @@
#include <ardour/source_factory.h>
#include <ardour/resampled_source.h>
#include <ardour/analyser.h>
+#include <ardour/smf_reader.h>
+#include <ardour/smf_source.h>
+#include <ardour/tempo.h>
#include "i18n.h"
@@ -66,23 +71,25 @@ open_importable_source (const string& path, nframes_t samplerate, ARDOUR::SrcQua
}
static std::string
-get_non_existent_filename (const bool allow_replacing, const std::string destdir, const std::string& basename, uint channel, uint channels)
+get_non_existent_filename (DataType type, const bool allow_replacing, const std::string destdir, const std::string& basename, uint channel, uint channels)
{
char buf[PATH_MAX+1];
bool goodfile = false;
string base(basename);
+ const char* ext = (type == DataType::AUDIO) ? "wav" : "mid";
do {
- if (channels == 2) {
+
+ if (type == DataType::AUDIO && channels == 2) {
if (channel == 0) {
snprintf (buf, sizeof(buf), "%s-L.wav", base.c_str());
} else {
snprintf (buf, sizeof(buf), "%s-R.wav", base.c_str());
}
} else if (channels > 1) {
- snprintf (buf, sizeof(buf), "%s-c%d.wav", base.c_str(), channel+1);
+ snprintf (buf, sizeof(buf), "%s-c%d.%s", base.c_str(), channel, ext);
} else {
- snprintf (buf, sizeof(buf), "%s.wav", base.c_str());
+ snprintf (buf, sizeof(buf), "%s.%s", base.c_str(), ext);
}
@@ -112,13 +119,20 @@ get_paths_for_new_sources (const bool allow_replacing, const string& import_file
vector<string> new_paths;
const string basename = basename_nosuffix (import_file_path);
+ SessionDirectory sdir(session_dir);
+
for (uint n = 0; n < channels; ++n) {
- std::string filepath;
+ const DataType type = (import_file_path.rfind(".mid") != string::npos)
+ ? DataType::MIDI : DataType::AUDIO;
+
+ std::string filepath = (type == DataType::MIDI)
+ ? sdir.midi_path().to_string() : sdir.sound_path().to_string();
- filepath = session_dir;
filepath += '/';
- filepath += get_non_existent_filename (allow_replacing, session_dir, basename, n, channels);
+ filepath += get_non_existent_filename (type, allow_replacing, filepath, basename, n, channels);
+
+ cout << "NEW SOURCE PATH: " << filepath << endl;
new_paths.push_back (filepath);
}
@@ -128,7 +142,7 @@ get_paths_for_new_sources (const bool allow_replacing, const string& import_file
static bool
map_existing_mono_sources (const vector<string>& new_paths, Session& sess,
- uint samplerate, vector<boost::shared_ptr<AudioFileSource> >& newfiles, Session *session)
+ uint samplerate, vector<boost::shared_ptr<Source> >& newfiles, Session *session)
{
for (vector<string>::const_iterator i = new_paths.begin();
i != new_paths.end(); ++i)
@@ -140,14 +154,14 @@ map_existing_mono_sources (const vector<string>& new_paths, Session& sess,
return false;
}
- newfiles.push_back(boost::dynamic_pointer_cast<AudioFileSource>(source));
+ newfiles.push_back(boost::dynamic_pointer_cast<Source>(source));
}
return true;
}
static bool
create_mono_sources_for_writing (const vector<string>& new_paths, Session& sess,
- uint samplerate, vector<boost::shared_ptr<AudioFileSource> >& newfiles)
+ uint samplerate, vector<boost::shared_ptr<Source> >& newfiles)
{
for (vector<string>::const_iterator i = new_paths.begin();
i != new_paths.end(); ++i)
@@ -156,13 +170,16 @@ create_mono_sources_for_writing (const vector<string>& new_paths, Session& sess,
try
{
+ const DataType type = ((*i).rfind(".mid") != string::npos)
+ ? DataType::MIDI : DataType::AUDIO;
+
source = SourceFactory::createWritable (
- DataType::AUDIO,
- sess,
- i->c_str(),
- false, // destructive
- samplerate
- );
+ type,
+ sess,
+ i->c_str(),
+ false, // destructive
+ samplerate
+ );
}
catch (const failed_constructor& err)
{
@@ -170,7 +187,7 @@ create_mono_sources_for_writing (const vector<string>& new_paths, Session& sess,
return false;
}
- newfiles.push_back(boost::dynamic_pointer_cast<AudioFileSource>(source));
+ newfiles.push_back(boost::dynamic_pointer_cast<Source>(source));
}
return true;
}
@@ -197,9 +214,10 @@ compose_status_message (const string& path,
static void
write_audio_data_to_new_files (ImportableSource* source, Session::import_status& status,
- vector<boost::shared_ptr<AudioFileSource> >& newfiles)
+ vector<boost::shared_ptr<Source> >& newfiles)
{
const nframes_t nframes = ResampledImportableSource::blocksize;
+ boost::shared_ptr<AudioFileSource> afs;
uint channels = source->channels();
boost::scoped_array<float> data(new float[nframes * channels]);
@@ -236,7 +254,9 @@ write_audio_data_to_new_files (ImportableSource* source, Session::import_status&
/* flush to disk */
for (chn = 0; chn < channels; ++chn) {
- newfiles[chn]->write (channel_data[chn].get(), nfread);
+ if ((afs = boost::dynamic_pointer_cast<AudioFileSource>(newfiles[chn])) != 0) {
+ afs->write (channel_data[chn].get(), nfread);
+ }
}
read_count += nread;
@@ -245,9 +265,71 @@ write_audio_data_to_new_files (ImportableSource* source, Session::import_status&
}
static void
-remove_file_source (boost::shared_ptr<AudioFileSource> file_source)
+write_midi_data_to_new_files (SMFReader* source, Session::import_status& status,
+ vector<boost::shared_ptr<Source> >& newfiles)
{
- ::unlink (file_source->path().c_str());
+ MidiEvent ev(0.0, 4, NULL, true);
+
+ uint64_t t = 0;
+ uint32_t delta_t = 0;
+ uint32_t size = 0;
+
+ status.progress = 0.0f;
+
+ try {
+
+ for (unsigned i = 1; i <= source->num_tracks(); ++i) {
+
+ boost::shared_ptr<SMFSource> smfs = boost::dynamic_pointer_cast<SMFSource>(newfiles[i-1]);
+
+ source->seek_to_track(i);
+
+ while (!status.cancel) {
+
+ if (source->read_event(4, ev.buffer(), &size, &delta_t) < 0)
+ break;
+
+ // FIXME: kluuudge
+ if (ev.channel() != 0) {
+ cout << "Skipping event with channel " << ev.channel() << endl;
+ continue;
+ }
+
+ t += delta_t;
+ ev.time() = t * (double)source->ppqn();
+ ev.size() = size;
+
+ smfs->append_event_unlocked(ev);
+ if (status.progress < 0.99)
+ status.progress += 0.01;
+ }
+
+ nframes_t timeline_position = 0; // FIXME: ?
+
+ // FIXME: kluuuuudge: assumes tempo never changes after start
+ const double frames_per_beat = smfs->session().tempo_map().tempo_at(
+ timeline_position).frames_per_beat(
+ smfs->session().engine().frame_rate(),
+ smfs->session().tempo_map().meter_at(timeline_position));
+
+ smfs->update_length(0, (t * source->ppqn()) * frames_per_beat);
+
+ smfs->flush_header();
+ smfs->flush_footer();
+
+ if (status.cancel)
+ break;
+ }
+
+ } catch (...) {
+ error << "Corrupt MIDI file " << source->filename() << endl;
+ }
+}
+
+static void
+remove_file_source (boost::shared_ptr<Source> source)
+{
+ ::unlink (source->path().c_str());
}
// This function is still unable to cleanly update an existing source, even though
@@ -258,8 +340,10 @@ void
Session::import_audiofiles (import_status& status)
{
uint32_t cnt = 1;
- typedef vector<boost::shared_ptr<AudioFileSource> > AudioSources;
- AudioSources all_new_sources;
+ typedef vector<boost::shared_ptr<Source> > Sources;
+ Sources all_new_sources;
+ boost::shared_ptr<AudioFileSource> afs;
+ uint channels = 0;
status.sources.clear ();
@@ -268,24 +352,44 @@ Session::import_audiofiles (import_status& status)
++p, ++cnt)
{
std::auto_ptr<ImportableSource> source;
-
- try
- {
- source = open_importable_source (*p, frame_rate(), status.quality);
- }
- catch (const failed_constructor& err)
- {
- error << string_compose(_("Import: cannot open input sound file \"%1\""), (*p)) << endmsg;
- status.done = status.cancel = true;
- return;
+ std::auto_ptr<SMFReader> smf_reader;
+
+ const DataType type = ((*p).rfind(".mid") != string::npos)
+ ? DataType::MIDI : DataType::AUDIO;
+
+ if (type == DataType::AUDIO) {
+ try
+ {
+ source = open_importable_source (*p, frame_rate(), status.quality);
+ channels = source->channels();
+ } catch (const failed_constructor& err)
+ {
+ error << string_compose(_("Import: cannot open input sound file \"%1\""), (*p)) << endmsg;
+ status.done = status.cancel = true;
+ return;
+ }
+ } else {
+ try
+ {
+ smf_reader = std::auto_ptr<SMFReader>(new SMFReader(*p));
+ channels = smf_reader->num_tracks();
+ } catch (const SMFReader::UnsupportedTime& err) {
+ error << _("Import: unsupported MIDI time stamp format") << endmsg;
+ status.done = status.cancel = true;
+ return;
+ } catch (...) {
+ error << _("Import: error reading MIDI file") << endmsg;
+ status.done = status.cancel = true;
+ return;
+ }
}
vector<string> new_paths = get_paths_for_new_sources (status.replace_existing_source,
*p,
get_best_session_directory_for_new_source (),
- source->channels());
+ channels);
- AudioSources newfiles;
+ Sources newfiles;
if (status.replace_existing_source) {
fatal << "THIS IS NOT IMPLEMENTED YET, IT SHOULD NEVER GET CALLED!!! DYING!" << endl;
@@ -299,14 +403,20 @@ Session::import_audiofiles (import_status& status)
if (status.cancel) break;
- for (AudioSources::iterator i = newfiles.begin(); i != newfiles.end(); ++i) {
- (*i)->prepare_for_peakfile_writes ();
+ for (Sources::iterator i = newfiles.begin(); i != newfiles.end(); ++i) {
+ if ((afs = boost::dynamic_pointer_cast<AudioFileSource>(*i)) != 0) {
+ afs->prepare_for_peakfile_writes ();
+ }
}
- status.doing_what = compose_status_message (*p, source->samplerate(),
- frame_rate(), cnt, status.paths.size());
-
- write_audio_data_to_new_files (source.get(), status, newfiles);
+ if (source.get()) { // audio
+ status.doing_what = compose_status_message (*p, source->samplerate(),
+ frame_rate(), cnt, status.paths.size());
+ write_audio_data_to_new_files (source.get(), status, newfiles);
+ } else if (smf_reader.get()) { // midi
+ status.doing_what = string_compose(_("loading MIDI file %1"), *p);
+ write_midi_data_to_new_files (smf_reader.get(), status, newfiles);
+ }
}
if (!status.cancel) {
@@ -318,10 +428,14 @@ Session::import_audiofiles (import_status& status)
/* flush the final length(s) to the header(s) */
- for (AudioSources::iterator x = all_new_sources.begin(); x != all_new_sources.end(); ++x)
+ for (Sources::iterator x = all_new_sources.begin(); x != all_new_sources.end(); ++x)
{
- (*x)->update_header(0, *now, xnow);
- (*x)->done_with_peakfile_writes ();
+ cout << "NEW SOURCE: " << (*x)->path() << endl;
+
+ if ((afs = boost::dynamic_pointer_cast<AudioFileSource>(*x)) != 0) {
+ afs->update_header(0, *now, xnow);
+ afs->done_with_peakfile_writes ();
+ }
/* now that there is data there, requeue the file for analysis */
diff --git a/libs/ardour/midi_diskstream.cc b/libs/ardour/midi_diskstream.cc
index ea340598ac..921824c615 100644
--- a/libs/ardour/midi_diskstream.cc
+++ b/libs/ardour/midi_diskstream.cc
@@ -670,7 +670,7 @@ MidiDiskstream::set_pending_overwrite (bool yn)
int
MidiDiskstream::overwrite_existing_buffers ()
{
- cerr << "MDS: overwrite_existing_buffers() (does nothing)" << endl;
+ //cerr << "MDS: overwrite_existing_buffers() (does nothing)" << endl;
return 0;
}
diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc
index 9a87e89ab8..51793a4189 100644
--- a/libs/ardour/midi_model.cc
+++ b/libs/ardour/midi_model.cc
@@ -433,7 +433,7 @@ MidiModel::append_note_off_unlocked(double time, uint8_t note_num)
Note& note = *_notes[*n].get();
//cerr << (unsigned)(uint8_t)note.note() << " ? " << (unsigned)note_num << endl;
if (note.note() == note_num) {
- assert(time > note.time());
+ assert(time >= note.time());
note.set_duration(time - note.time());
_write_notes.erase(n);
//cerr << "MM resolved note, duration: " << note.duration() << endl;
diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc
index 65279499ec..7bc4287a58 100644
--- a/libs/ardour/playlist.cc
+++ b/libs/ardour/playlist.cc
@@ -522,6 +522,8 @@ Playlist::set_region_ownership ()
void
Playlist::add_region_internal (boost::shared_ptr<Region> region, nframes_t position)
{
+ assert(region->data_type() == _type);
+
RegionSortByPosition cmp;
nframes_t old_length = 0;
diff --git a/libs/ardour/region_factory.cc b/libs/ardour/region_factory.cc
index bd4b0873a7..bb40fb16cf 100644
--- a/libs/ardour/region_factory.cc
+++ b/libs/ardour/region_factory.cc
@@ -108,6 +108,8 @@ RegionFactory::create (Session& session, XMLNode& node, bool yn)
boost::shared_ptr<Region>
RegionFactory::create (const SourceList& srcs, nframes_t start, nframes_t length, const string& name, layer_t layer, Region::Flag flags, bool announce)
{
+ cerr << "CREATE REGION " << name << " " << start << " * " << length << endl;
+
if (srcs.empty()) {
return boost::shared_ptr<Region>();
}
diff --git a/libs/ardour/smf_reader.cc b/libs/ardour/smf_reader.cc
new file mode 100644
index 0000000000..21ca370e6c
--- /dev/null
+++ b/libs/ardour/smf_reader.cc
@@ -0,0 +1,294 @@
+/*
+ Copyright (C) 2008 Paul Davis
+ Written by Dave Robillard
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <cstdio>
+#include <cassert>
+#include <iostream>
+#include <glibmm/miscutils.h>
+#include <ardour/smf_reader.h>
+#include <ardour/midi_events.h>
+#include <ardour/midi_util.h>
+
+using namespace std;
+
+namespace ARDOUR {
+
+
+SMFReader::SMFReader(const std::string filename)
+ : _fd(NULL)
+ //, _unit(TimeUnit::BEATS, 192)
+ , _ppqn(0)
+ , _track(0)
+ , _track_size(0)
+{
+ if (filename.length() > 0) {
+ open(filename);
+ }
+}
+
+
+SMFReader::~SMFReader()
+{
+ if (_fd)
+ close();
+}
+
+
+bool
+SMFReader::open(const string& filename) throw (logic_error, UnsupportedTime)
+{
+ if (_fd)
+ throw logic_error("Attempt to start new read while write in progress.");
+
+ cout << "Opening SMF file " << filename << " for reading." << endl;
+
+ _fd = fopen(filename.c_str(), "r+");
+
+ if (_fd) {
+ // Read type (bytes 8..9)
+ fseek(_fd, 0, SEEK_SET);
+ char mthd[5];
+ mthd[4] = '\0';
+ fread(mthd, 1, 4, _fd);
+ if (strcmp(mthd, "MThd")) {
+ cerr << filename << " is not an SMF file, aborting." << endl;
+ fclose(_fd);
+ _fd = NULL;
+ return false;
+ }
+
+ // Read type (bytes 8..9)
+ fseek(_fd, 8, SEEK_SET);
+ uint16_t type_be = 0;
+ fread(&type_be, 2, 1, _fd);
+ _type = GUINT16_FROM_BE(type_be);
+
+ // Read number of tracks (bytes 10..11)
+ uint16_t num_tracks_be = 0;
+ fread(&num_tracks_be, 2, 1, _fd);
+ _num_tracks = GUINT16_FROM_BE(num_tracks_be);
+
+ // Read PPQN (bytes 12..13)
+
+ uint16_t ppqn_be = 0;
+ fread(&ppqn_be, 2, 1, _fd);
+ _ppqn = GUINT16_FROM_BE(ppqn_be);
+
+ // TODO: Absolute (SMPTE seconds) time support
+ if ((_ppqn & 0x8000) != 0)
+ throw UnsupportedTime();
+
+ //_unit = TimeUnit::beats(_ppqn);
+
+ seek_to_track(1);
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+/** Seek to the start of a given track, starting from 1.
+ * Returns true if specified track was found.
+ */
+bool
+SMFReader::seek_to_track(unsigned track) throw (std::logic_error)
+{
+ if (track == 0)
+ throw logic_error("Seek to track 0 out of range (must be >= 1)");
+
+ if (!_fd)
+ throw logic_error("Attempt to seek to track on unopened SMF file.");
+
+ unsigned track_pos = 0;
+
+ fseek(_fd, 14, SEEK_SET);
+ char id[5];
+ id[4] = '\0';
+ uint32_t chunk_size = 0;
+
+ while (!feof(_fd)) {
+ fread(id, 1, 4, _fd);
+
+ if (!strcmp(id, "MTrk")) {
+ ++track_pos;
+ //std::cerr << "Found track " << track_pos << endl;
+ } else {
+ std::cerr << "Unknown chunk ID " << id << endl;
+ }
+
+ uint32_t chunk_size_be;
+ fread(&chunk_size_be, 4, 1, _fd);
+ chunk_size = GUINT32_FROM_BE(chunk_size_be);
+
+ if (track_pos == track)
+ break;
+
+ fseek(_fd, chunk_size, SEEK_CUR);
+ }
+
+ if (!feof(_fd) && track_pos == track) {
+ _track = track;
+ _track_size = chunk_size;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+/** Read an event from the current position in file.
+ *
+ * File position MUST be at the beginning of a delta time, or this will die very messily.
+ * ev.buffer must be of size ev.size, and large enough for the event. The returned event
+ * will have it's time field set to it's delta time (so it's the caller's responsibility
+ * to keep track of delta time, even for ignored events).
+ *
+ * Returns event length (including status byte) on success, 0 if event was
+ * skipped (eg a meta event), or -1 on EOF (or end of track).
+ *
+ * If @a buf is not large enough to hold the event, 0 will be returned, but ev_size
+ * set to the actual size of the event.
+ */
+int
+SMFReader::read_event(size_t buf_len,
+ uint8_t* buf,
+ uint32_t* ev_size,
+ uint32_t* delta_time)
+ throw (std::logic_error, PrematureEOF, CorruptFile)
+{
+ if (_track == 0)
+ throw logic_error("Attempt to read from unopened SMF file");
+
+ if (!_fd || feof(_fd)) {
+ return -1;
+ }
+
+ assert(buf_len > 0);
+ assert(buf);
+ assert(ev_size);
+ assert(delta_time);
+
+ //cerr.flags(ios::hex);
+ //cerr << "SMF - Reading event at offset 0x" << ftell(_fd) << endl;
+ //cerr.flags(ios::dec);
+
+ // Running status state
+ static uint8_t last_status = 0;
+ static uint32_t last_size = 0;
+
+ *delta_time = read_var_len();
+ int status = fgetc(_fd);
+ if (status == EOF)
+ throw PrematureEOF();
+ else if (status > 0xFF)
+ throw CorruptFile();
+
+ if (status < 0x80) {
+ if (last_status == 0)
+ throw CorruptFile();
+ status = last_status;
+ *ev_size = last_size;
+ fseek(_fd, -1, SEEK_CUR);
+ //cerr << "RUNNING STATUS, size = " << *ev_size << endl;
+ } else {
+ last_status = status;
+ *ev_size = midi_event_size(status) + 1;
+ last_size = *ev_size;
+ //cerr << "NORMAL STATUS, size = " << *ev_size << endl;
+ }
+
+ buf[0] = (uint8_t)status;
+
+ if (status == 0xFF) {
+ *ev_size = 0;
+ if (feof(_fd))
+ throw PrematureEOF();
+ uint8_t type = fgetc(_fd);
+ const uint32_t size = read_var_len();
+ /*cerr.flags(ios::hex);
+ cerr << "SMF - meta 0x" << (int)type << ", size = ";
+ cerr.flags(ios::dec);
+ cerr << size << endl;*/
+
+ if ((uint8_t)type == 0x2F) {
+ //cerr << "SMF - hit EOT" << endl;
+ return -1; // we hit the logical EOF anyway...
+ } else {
+ fseek(_fd, size, SEEK_CUR);
+ return 0;
+ }
+ }
+
+ if (*ev_size > buf_len || *ev_size == 0 || feof(_fd)) {
+ //cerr << "Skipping event" << endl;
+ // Skip event, return 0
+ fseek(_fd, *ev_size - 1, SEEK_CUR);
+ return 0;
+ } else {
+ // Read event, return size
+ if (ferror(_fd))
+ throw CorruptFile();
+ fread(buf+1, 1, *ev_size - 1, _fd);
+
+ if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) {
+ buf[0] = (0x80 | (buf[0] & 0x0F));
+ buf[2] = 0x40;
+ }
+
+ return *ev_size;
+ }
+}
+
+
+void
+SMFReader::close()
+{
+ if (_fd)
+ fclose(_fd);
+
+ _fd = NULL;
+}
+
+
+uint32_t
+SMFReader::read_var_len() const throw(PrematureEOF)
+{
+ if (feof(_fd))
+ throw PrematureEOF();
+
+ uint32_t value;
+ uint8_t c;
+
+ if ( (value = getc(_fd)) & 0x80 ) {
+ value &= 0x7F;
+ do {
+ if (feof(_fd))
+ throw PrematureEOF();
+ value = (value << 7) + ((c = getc(_fd)) & 0x7F);
+ } while (c & 0x80);
+ }
+
+ return value;
+}
+
+
+} // namespace ARDOUR
+
diff --git a/libs/ardour/smf_source.cc b/libs/ardour/smf_source.cc
index 29f36cc2cd..59ad7269db 100644
--- a/libs/ardour/smf_source.cc
+++ b/libs/ardour/smf_source.cc
@@ -70,7 +70,7 @@ SMFSource::SMFSource (Session& s, std::string path, Flag flags)
throw failed_constructor ();
}
- //cerr << "SMF Source path: " << path << endl;
+ cerr << "SMF Source path: " << path << endl;
assert(_name.find("/") == string::npos);
}
@@ -99,7 +99,7 @@ SMFSource::SMFSource (Session& s, const XMLNode& node)
throw failed_constructor ();
}
- //cerr << "SMF Source name: " << _name << endl;
+ cerr << "SMF Source name: " << _name << endl;
assert(_name.find("/") == string::npos);
}
@@ -184,7 +184,7 @@ SMFSource::flush_header ()
const uint16_t type = GUINT16_TO_BE(0); // SMF Type 0 (single track)
const uint16_t ntracks = GUINT16_TO_BE(1); // Number of tracks (always 1 for Type 0)
- const uint16_t division = GUINT16_TO_BE(_ppqn); // Pulses per beat
+ const uint16_t division = GUINT16_TO_BE(_ppqn); // Pulses per quarter note (beat)
char data[6];
memcpy(data, &type, 2);
@@ -195,9 +195,7 @@ SMFSource::flush_header ()
assert(_fd);
fseek(_fd, 0, 0);
write_chunk("MThd", 6, data);
- //if (_track_size > 0) {
- write_chunk_header("MTrk", _track_size);
- //}
+ write_chunk_header("MTrk", _track_size);
fflush(_fd);
@@ -446,7 +444,7 @@ SMFSource::write_unlocked (MidiRingBuffer& src, nframes_t cnt)
void
SMFSource::append_event_unlocked(const MidiEvent& ev)
{
- /*printf("SMF - writing event, time = %lf, size = %u, data = ", ev.time(), ev.size());
+ /*printf("SMF %s - writing event, time = %lf, size = %u, data = ", _path.c_str(), ev.time(), ev.size());
for (size_t i=0; i < ev.size(); ++i) {
printf("%X ", ev.buffer()[i]);
}
@@ -636,6 +634,12 @@ SMFSource::move_to_trash (const string trash_dir_name)
return 0;
}
+bool
+SMFSource::safe_file_extension(const Glib::ustring& file)
+{
+ return (file.rfind(".mid") != Glib::ustring::npos);
+}
+
// FIXME: Merge this with audiofilesource somehow (make a generic filesource?)
bool
SMFSource::find (string pathstr, bool must_exist, bool& isnew)