44 files changed, 2696 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..204d437 --- a/dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +configure +Makefile.in +Doxyfile +config.log +depcomp +config.guess +config.h +ltmain.sh +config.sub +INSTALL +NEWS +Makefile +config.status +midillo.pc +stamp-h1 +config.h.in +libtool +autom4te.cache +missing +aclocal.m4 +install-sh +doxydox @@ -0,0 +1,3 @@ +Klever dissected: + Michael 'hacker' Krelin <hacker@klever.net> + Leonid Ivanov <kamel@klever.net> @@ -0,0 +1,19 @@ +Copyright (c) 2006 Klever Group (http://www.klever.net/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- a/dev/null +++ b/ChangeLog diff --git a/Doxyfile.in b/Doxyfile.in new file mode 100644 index 0000000..95552d7 --- a/dev/null +++ b/Doxyfile.in @@ -0,0 +1,247 @@ +# Doxyfile 1.3.9.1 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = @PACKAGE@ +PROJECT_NUMBER = @VERSION@ +OUTPUT_DIRECTORY = @builddir@/doxydox +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +USE_WINDOWS_ENCODING = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = include +STRIP_FROM_INC_PATH = include +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = YES +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_DIRECTORIES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +INPUT = \ + @srcdir@/include/midillo/ +FILE_PATTERNS = *.h +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 2 +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +GENERATE_XML = YES +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = @HAVE_DOT@ +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = @DOT@ +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 0 +GENERATE_LEGEND = YES +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +SEARCHENGINE = NO diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..5a2a2c7 --- a/dev/null +++ b/Makefile.am @@ -0,0 +1,22 @@ +SUBDIRS=include lib tools man +EXTRA_DIST= NEWS NEWS.xml NEWS.xsl + +DISTCHECK_CONFIGURE_FLAGS=--with-pkgconfigdir=$${dc_install_base}/lib/pkgconfig +if HAVE_PKGCONFIG +pkgconfigdir=@PKGCONFIG_DIR@ +pkgconfig_DATA=midillo.pc +endif + +all-local: NEWS +if HAVE_DOXYGEN +clean-local: + rm -rf doxydox +endif + +NEWS: NEWS.xsl NEWS.xml + ${XSLTPROC} -o $@ NEWS.xsl NEWS.xml + +if HAVE_DOXYGEN +dox: + ${DOXYGEN} +endif diff --git a/NEWS.xml b/NEWS.xml new file mode 100644 index 0000000..f30ed3b --- a/dev/null +++ b/NEWS.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="us-ascii"?> +<news> + <version version="0.0" date="August 1st, 2006"> + <ni>Initial release</ni> + </version> +</news> diff --git a/NEWS.xsl b/NEWS.xsl new file mode 100644 index 0000000..7c71307 --- a/dev/null +++ b/NEWS.xsl @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="us-ascii"?> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + > + <xsl:output + method="text" + encoding="us-ascii" + media-type="text/plain" /> + + <xsl:template match="news"> + <xsl:apply-templates/> + </xsl:template> + <xsl:template match="version"> + <xsl:value-of select="concat(@version,' (',@date,')
')"/> + <xsl:apply-templates/> + </xsl:template> + <xsl:template match="ni"> + <xsl:text> - </xsl:text> + <xsl:apply-templates mode="text"/> + <xsl:text>
</xsl:text> + </xsl:template> + <xsl:template match="*|text()"/> + +</xsl:stylesheet> diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..ec2c50f --- a/dev/null +++ b/acinclude.m4 @@ -0,0 +1,80 @@ +dnl AC_WITH_PKGCONFIG([ACTION-IF-FOUND[,ACTION-IF-NOT-FOUND]]) +dnl Outputs: +dnl AC_SUBST: PKGCONFIG_PKGCONFIG PKGCONFIG_DIR +dnl AM_CONDTIONAL: HAVE_PKGCONFIG +AC_DEFUN([AC_WITH_PKGCONFIG],[ + PKGCONFIG_PKGCONFIG="" + PKGCONFIG_DIR="" + HAVE_PKGCONFIG="no" + EXPLICIT_PKGCONFIGDIR="no" + test -z "${WANT_PKGCONFIG}" && WANT_PKGCONFIG="" + AC_PATH_PROG([PKGCONFIG_PKGCONFIG],[pkg-config],[false]) + if test "${PKGCONFIG_PKGCONFIG}" != "false" ; then + AC_ARG_WITH([pkgconfigdir], + AC_HELP_STRING([--with-pkgconfigdir=dir],[Specify pkgconfig directory]), + [ + if test "${withval}" = "no" ; then + WANT_PKGCONFIG="no" + else + PKGCONFIG_DIR="${withval}" + EXPLICIT_PKGCONFIGDIR="yes" + fi + ],[ + AC_MSG_CHECKING([for pkgconfig directory]) + PKGCONFIG_DIR="`${PKGCONFIG_PKGCONFIG} --debug 2>&1 | grep '^Scanning'| head -n 1 | cut -d\' -f2-|cut -d\' -f1`" + AC_MSG_RESULT([${PKGCONFIG_DIR}]) + ] + ) + if test -d "${PKGCONFIG_DIR}" ; then + HAVE_PKGCONFIG=yes + AC_SUBST([PKGCONFIG_PKGCONFIG]) + AC_SUBST([PKGCONFIG_DIR]) + else + AC_MSG_NOTICE([unexistent pkgconfig directory: ${PKGCONFIG_DIR}]) + if test "${EXPLICIT_PKGCONFIGDIR}" = "yes" ; then + HAVE_PKGCONFIG=yes + AC_SUBST([PKGCONFIG_PKGCONFIG]) + AC_SUBST([PKGCONFIG_DIR]) + else + ifelse([$2], , :, [$2]) + fi + fi + fi + AM_CONDITIONAL([HAVE_PKGCONFIG],[test "${HAVE_PKGCONFIG}" = "yes"]) +]) + +dnl AC_WITH_DOXYGEN([ACTION-IF-FOUND[,ACTION-IF-NOT-FOUND]]) +dnl Outputs: +dnl AC_SUBST: DOXYGEN HAVE_DOXYGEN +dnl AM_CONDTIONAL: HAVE_DOXYGEN +AC_DEFUN([AC_WITH_DOXYGEN],[ + HAVE_DOXYGEN="no" + AC_PATH_PROG([DOXYGEN],[doxygen],[false]) + if test "${DOXYGEN}" = "false" ; then + ifelse([$2], , :, [$2]) + else + HAVE_DOXYGEN="yes" + AC_SUBST([DOXYGEN]) + $1 + fi + AC_SUBST([HAVE_DOXYGEN]) + AM_CONDITIONAL([HAVE_DOXYGEN],[test "${HAVE_DOXYGEN}" = "yes"]) +]) + +dnl AC_WITH_DOT([ACTION-IF-FOUND[,ACTION-IF-NOT-FOUND]]) +dnl Outputs: +dnl AC_SUBST: DOT HAVE_DOT +dnl AM_CONDITIONAL: HAVE_DOT +AC_DEFUN([AC_WITH_DOT],[ + HAVE_DOT="no" + AC_PATH_PROG([DOT],[dot],[false]) + if test "${DOT}" = "false" ; then + ifelse([$2], , :, [$2]) + else + HAVE_DOT="yes" + AC_SUBST([DOT]) + $1 + fi +AC_SUBST([HAVE_DOT]) + AM_CONDITIONAL([HAVE_DOT],[test "${HAVE_DOT}" = "yes"]) +]) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..ba27501 --- a/dev/null +++ b/autogen.sh @@ -0,0 +1,9 @@ +#!/bin/sh +WANT_AUTOMAKE=1.8 +export WANT_AUTOMAKE +libtoolize -f \ +&& aclocal \ +&& autoheader \ +&& automake -a \ +&& autoconf \ +&& ./configure "$@" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..040b9cf --- a/dev/null +++ b/configure.ac @@ -0,0 +1,55 @@ +AC_INIT([midillo], [0.0], [midillo-bugs@klever.net]) +AC_CONFIG_SRCDIR([include/midillo/SMF.h]) +AC_CONFIG_HEADERS([config.h]) +AM_INIT_AUTOMAKE([dist-bzip2]) + +AC_PROG_INSTALL +AC_PROG_AWK +AC_PROG_CXX +AC_PROG_CC +AC_PROG_LIBTOOL + +AC_HEADER_STDC +AC_CHECK_HEADERS([sys/types.h sys/stat.h]) +AC_CHECK_DECLS([environ],,,[ + #include <unistd.h> +]) + +AC_C_CONST + +AC_FUNC_MALLOC +AC_FUNC_REALLOC + +AC_PATH_PROG([XSLTPROC],[xsltproc],[true]) + +AC_WITH_PKGCONFIG + +PKG_CHECK_MODULES([KONFORKA],[konforka],,[ + AC_MSG_ERROR([no konforka library found. get one from http://kin.klever.net/konforka/]) +]) + +WANT_DOXYGEN="yes" +AC_ARG_ENABLE([doxygen], + AC_HELP_STRING([--disable-doxygen],[do not generate documentation]), + [ + test "${enableval}" = "no" && WANT_DOXYGEN="no" + ] +) +if test "${WANT_DOXYGEN}" = "yes" ; then + AC_WITH_DOXYGEN + AC_WITH_DOT +else + AM_CONDITIONAL([HAVE_DOXYGEN],[false]) + AM_CONDITIONAL([HAVE_DOT],[false]) +fi + +AC_CONFIG_FILES([ + Makefile + midillo.pc + Doxyfile + include/Makefile + lib/Makefile + tools/Makefile + man/Makefile +]) +AC_OUTPUT diff --git a/include/.gitignore b/include/.gitignore new file mode 100644 index 0000000..282522d --- a/dev/null +++ b/include/.gitignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..edfb186 --- a/dev/null +++ b/include/Makefile.am @@ -0,0 +1,5 @@ +nobase_include_HEADERS = \ + midillo/util.h midillo/exception.h \ + midillo/SMF.h \ + midillo/chunk.h midillo/MTrk.h midillo/MThd.h \ + midillo/event.h midillo/message.h diff --git a/include/midillo/MThd.h b/include/midillo/MThd.h new file mode 100644 index 0000000..20263cd --- a/dev/null +++ b/include/midillo/MThd.h @@ -0,0 +1,74 @@ +#ifndef __MIDILLO_MTHD_H +#define __MIDILLO_MTHD_H + +#include <istream> +#include <ostream> +#include <midillo/chunk.h> + +/** + * @file + * @brief the MThd_t -- MThd header chunk class + */ + +namespace midillo { + using std::istream; + using std::ostream; + + /** + * MThd header chunk container + */ + class MThd_t : public chunk_t { + public: + enum { + fmt_0 = 0, fmt_1 = 1, fmt_2 = 2, + fmt_singletrack = fmt_0, + fmt_multitrack = fmt_1, + fmt_tracksequence = fmt_2 + }; + /** + * SMF format. 0 for single track, 1 for multitrack, 2 for track + * sequence. + */ + int fmt; + /** + * Number of tracks in the file + */ + int ntracks; + /** + * The number of pulses per quarter note + */ + int division; + + /** + * Load MThd chunk from the stream + * @param s input stream + */ + void load(istream& s); + + /** + * Read MThd chunk data from the stream. This function assumes that + * header is already read. + * @param s input stream + */ + void load_data(istream& s); + + /** + * Save MThd chunk to the stream + * @param s output stream + */ + void save(ostream& s) const; + + /** + * Dump textual representation of MThd chunk to stream + * @param s output stream + */ + void dump(ostream& s) const; + }; + + inline ostream& operator<<(ostream& s,const MThd_t& mthd) { + mthd.dump(s); return s; + } + +} + +#endif /* MIDILLO_MTHD_H */ diff --git a/include/midillo/MTrk.h b/include/midillo/MTrk.h new file mode 100644 index 0000000..0884388 --- a/dev/null +++ b/include/midillo/MTrk.h @@ -0,0 +1,54 @@ +#ifndef __MIDILLO_MTRK_H +#define __MIDILLO_MTRK_H + +#include <istream> +#include <ostream> +#include <list> +#include <midillo/chunk.h> +#include <midillo/event.h> + +/** + * @file + * @brief MTrk -- track chunk container + */ + +namespace midillo { + using std::istream; + using std::ostream; + + /** + * MTrk track chunk container class + */ + class MTrk_t : public chunk_t { + public: + /** + * MIDI events contained in the track + */ + events_t events; + + /** + * Load MTrk chunk from the stream + * @param s input stream + */ + void load(istream& s); + + /** + * Save MTrk chunk to the stream + * @param s output stream + */ + void save(ostream& s) const; + + /** + * Dump textual representation of MTrk chunk to stream + * @param s output stream + */ + void dump(ostream& s) const; + }; + + inline ostream& operator<<(ostream& s,const MTrk_t& mtrk) { + mtrk.dump(s); return s; + } + +} + +#endif /* __MIDILLO_MTRK_H */ diff --git a/include/midillo/SMF.h b/include/midillo/SMF.h new file mode 100644 index 0000000..bec9f7a --- a/dev/null +++ b/include/midillo/SMF.h @@ -0,0 +1,87 @@ +#ifndef __MIDILLO_SMF_H +#define __MIDILLO_SMF_H + +#include <istream> +#include <ostream> +#include <list> +#include <midillo/MThd.h> +#include <midillo/MTrk.h> + +/** + * @file + * @brief the SMF_t -- standard midi file + */ + +namespace midillo { + using std::istream; + using std::vector; + using std::ostream; + + /** + * Standard midi file object + */ + class SMF_t { + public: + /** + * MThd header chunk + */ + MThd_t mthd; + /** + * The type for collection of MTrk track chunks + */ + typedef list<MTrk_t> tracks_t; + /** + * MTrk track chunks collection for the file + */ + tracks_t tracks; + + SMF_t() { } + /** + * Construct object from the file + * @see load(const char *f,bool stdinable) + */ + SMF_t(const char *f,bool stdinable=true) { load(f,stdinable); } + /** + * Construct object from the stream + * @see load(istream& s) + */ + SMF_t(istream& s) { load(s); } + + /** + * Load MIDI data from the file + * @param f filename + * @param stdinable true if '-' is treatead as cin input stream + */ + void load(const char *f,bool stdinable=true); + /** + * Load midi data from the stream + * @param s input stream + */ + void load(istream& s); + + /** + * Save MIDI data to the file + * @param f filename + * @param stdoutable true if '-' is treated as cout output stream + */ + void save(const char *f,bool stdoutable=true) const; + /** + * Save MIDI data to the stream + * @param s output stream + */ + void save(ostream& s) const; + + /** + * Dump textual representation of SMF to stream + * @param s output stream + */ + void dump(ostream& s) const; + }; + + inline ostream& operator<<(ostream& s,const SMF_t& smf) { + smf.dump(s); return s; + } + +} + +#endif /* __MIDILLO_SMF_H */ diff --git a/include/midillo/chunk.h b/include/midillo/chunk.h new file mode 100644 index 0000000..8b6c034 --- a/dev/null +++ b/include/midillo/chunk.h @@ -0,0 +1,88 @@ +#ifndef __MIDILLO_CHUNK_H +#define __MIDILLO_CHUNK_H + +#include <istream> +#include <ostream> + +/** + * @file + * @brief Generic SMF chunk manipulation + */ + +namespace midillo { + using std::istream; + using std::ostream; + + enum { + chunk_id_MThd = 0x6468544d, + chunk_id_MTrk = 0x6b72544d + }; + /** + * Chunk header data structure + */ + struct chunk_header_t { + /** + * Track signature data + */ + union { + /** + * ASCII representation + */ + char id_chars[4]; + /** + * long integer representation + */ + unsigned long id_number; + }; + /** + * Chunk length + */ + unsigned long length; + + chunk_header_t() + : id_number(0), length(0) { } + chunk_header_t(const chunk_header_t& s) + : id_number(s.id_number), length(s.length) { }; + + chunk_header_t& operator=(const chunk_header_t& s) { + id_number=s.id_number; length=s.length; + return *this; + } + + /** + * Load chunk header from the stream + * @param s input stream + */ + void load(istream& s); + /** + * Save chunk header to the stream + * @param s output stream + */ + void save(ostream& s) const; + + /** + * Dump textual representation of chunk header to stream + * @param s output stream + */ + void dump(ostream& s) const; + + }; + + inline ostream& operator<<(ostream& s,const chunk_header_t& ch) { + ch.dump(s); return s; + } + + /** + * Base class for specific chunk containers + */ + class chunk_t { + public: + /** + * Chunk header data + */ + chunk_header_t header; + }; + +} + +#endif /* __MIDILLO_CHUNK_H */ diff --git a/include/midillo/event.h b/include/midillo/event.h new file mode 100644 index 0000000..85f6175 --- a/dev/null +++ b/include/midillo/event.h @@ -0,0 +1,99 @@ +#ifndef __MIDILLO_EVENT_H +#define __MIDILLO_EVENT_H + +#include <istream> +#include <ostream> +#include <list> +#include <midillo/message.h> + +/** + * @file + * @brief midi event container + */ + +namespace midillo { + using std::istream; + using std::ostream; + using std::list; + + /** + * MIDI event container class + */ + class event_t { + public: + /** + * delta time since the last event + */ + long deltat; + /** + * MIDI message itself + */ + message_t message; + + /** + * Load MIDI event from the stream + * @param rs reference to the running status + * @param s input stream + */ + void load(int& rs,istream& s); + + /** + * Save MIDI event to the stream + * @param rs reference to the running status + * @param s output stream + */ + void save(int& rs,ostream& s) const; + + /** + * Calculate the amount of data that would be written to stream in + * case of save + * @param rs reference to the running status + * @return the number of bytes + */ + unsigned long calculate_save_size(int& rs) const; + + /** + * Dump textual representation of event to stream + * @param s output stream + */ + void dump(ostream& s) const; + }; + + inline ostream& operator<<(ostream& s,const event_t& e) { + e.dump(s); return s; + } + + /** + * MIDI events list container + */ + class events_t : public list<event_t> { + public: + + /** + * Append empty event to the end of the list + * @return iterator, pointing to the appended event + */ + iterator append_event(); + + /** + * Load MIDI events (track data) from the stream + * @param s input stream + */ + void load(istream& s); + /** + * Save MIDI events (track data) to the stream + * @param s output stream + */ + void save(ostream& s) const; + + /** + * Calculate the size of the track data that would be written to + * the stream by save() + * @return the number of bytes + */ + unsigned long calculate_save_size() const; + }; + +} + +#endif /* __MIDILLO_EVENT_H */ diff --git a/include/midillo/exception.h b/include/midillo/exception.h new file mode 100644 index 0000000..fb6da27 --- a/dev/null +++ b/include/midillo/exception.h @@ -0,0 +1,55 @@ +#ifndef __MIDILLO_EXCEPTION_H +#define __MIDILLO_EXCEPTION_H + +#include <konforka/exception.h> + +/** + * @file + * @brief midillo specific exceptions + */ + +namespace midillo { + using std::string; + + /** + * Base midillo exception class + */ + class exception : public konforka::exception { + public: + explicit exception(const string& fi,const string& fu,int l,const string& w) + : konforka::exception(fi,fu,l,w) { } + }; + + class exception_io_error : public exception { + public: + explicit exception_io_error(const string& fi,const string& fu,int l,const string& w) + : exception(fi,fu,l,w) { } + }; + + class exception_input_error : public exception_io_error { + public: + explicit exception_input_error(const string& fi,const string& fu,int l,const string& w) + : exception_io_error(fi,fu,l,w) { } + }; + + class exception_invalid_input : public exception_input_error { + public: + explicit exception_invalid_input(const string& fi,const string& fu,int l,const string& w) + : exception_input_error(fi,fu,l,w) { } + }; + + class exception_unexpected_input : public exception_invalid_input { + public: + explicit exception_unexpected_input(const string& fi,const string& fu,int l,const string& w) + : exception_invalid_input(fi,fu,l,w) { } + }; + + class exception_output_error : public exception_io_error { + public: + explicit exception_output_error(const string& fi,const string& fu,int l,const string& w) + : exception_io_error(fi,fu,l,w) { } + }; + +}; + +#endif /* __MIDILLO_EXCEPTION_H */ diff --git a/include/midillo/message.h b/include/midillo/message.h new file mode 100644 index 0000000..d6f0f36 --- a/dev/null +++ b/include/midillo/message.h @@ -0,0 +1,200 @@ +#ifndef __MIDILLO_MESSAGE_H +#define __MIDILLO_MESSAGE_H + +#include <istream> +#include <ostream> +#include <vector> + +/** + * @file + * @brief MIDI message + */ + +namespace midillo { + using std::istream; + using std::ostream; + using std::vector; + + enum { + // bits + status_bit = 0x80, + status_event_bits = 0xF0, + status_channel_bits = 0x0F, + status_system_bits = 0xFF, + // channel voice messages + status_note_off = 0x80, + status_note_on = 0x90, + status_polyphonic_key_pressure = 0xA0, + status_aftertouch = status_polyphonic_key_pressure, + status_control_change = 0xB0, + status_program_change = 0xC0, + status_channel_pressure = 0xD0, + status_pitch_wheel_change = 0xE0, + status_system = 0xF0, + // system common messages + status_system_sysex = 0xF0, + status_system_MTC_quarter_frame = 0xF1, + status_system_song_position_pointer = 0xF2, + status_system_song_select = 0xF3, + status_system_tune_request = 0xF6, + status_system_end_of_sysex = 0xF7, + // system real-time messages + status_system_timing_clock = 0xF8, + status_system_midi_clock = status_system_timing_clock, + status_system_midi_tick = 0xF9, + status_system_start = 0xFA, + status_system_midi_start = status_system_start, + status_system_continue = 0xFB, + status_system_midi_continue = status_system_continue, + status_system_stop = 0xFC, + status_system_midi_stop = status_system_stop, + status_system_active_sense = 0xFE, + status_system_rest = 0xFF, + status_system_reset = status_system_rest, + // midi file specific + status_system_meta = 0xFF + }; + enum { + // meta events + meta_sequence_number = 0x00, + meta_text = 0x01, + meta_copyright = 0x02, + meta_seq_track_name = 0x03, + meta_instrument = 0x04, + meta_lyric = 0x05, + meta_marker = 0x06, + meta_cue_point = 0x07, + meta_patch_name = 0x08, + meta_port_name = 0x09, + meta_EOT = 0x2F, + meta_tempo = 0x51, + meta_SMPTE_offset = 0x54, + meta_time_sig = 0x58, + meta_key_sig = 0x59, + meta_proprietary = 0x7F, + // obsolete meta events + meta_midi_channel = 0x20, + meta_midi_port = 0x21 + }; + + /** + * MIDI message container + */ + class message_t { + public: + /** + * MIDI status byte + */ + int status; + /** + * MIDI meta event type + */ + int meta_status; + typedef unsigned char byte_t; + typedef vector<byte_t> bytes_t; + /** + * MIDI message data -- content is message-specific + */ + bytes_t data; + + message_t() + : status(-1) { } + message_t(const message_t& m) + : status(m.status), meta_status(m.meta_status), data(m.data) { } + + message_t& operator=(const message_t& m) { + status = m.status; + meta_status = m.meta_status; + data = m.data; + return *this; + } + + /** + * Load MIDI message from the stream + * @param rs reference to the running status + * @param s input stream + */ + void load(int& rs,istream& s); + /** + * Save MIDI message to the stream + * @param rs reference to the running status + * @param s output stream + */ + void save(int& rs,ostream& s) const; + /** + * Calculate the amount of data that would be written to stream in + * case of save() + * @param rs reference to the running status + * @return the number of bytes + */ + unsigned long calculate_save_size(int& rs) const; + + /** + * Load data so that we have c bytes of data for the event + * @param s input stream + * @param c size of data needed for the event + */ + void load_data(istream& s,int c); + /** + * Load sysex data from the stream + * @param s input stream + */ + void load_sysex(istream& s); + + /** + * Save data to the stream + * @param s output stream + */ + void save_data(ostream& s) const; + /** + * Save data to the stream and verify if the amount of data is + * correct + * @param s output stream + * @param c data bytes count + */ + void save_data(ostream& s,int c) const; + /** + * Save sysex data to the stream + * @param s output stream + */ + void save_sysex(ostream& s) const; + + /** + * See if the event is meta event + * @return true if yes + */ + bool is_meta() const { + return status==status_system_meta; + } + /** + * Check whether the event is a specific meta event + * @param meta meta event type + * @return true if yes + */ + bool is_meta(int meta) const { + return is_meta() && (meta_status==meta); + } + + /** + * See if the event is system event + * @return true if yes + */ + bool is_system() const { + return (status&status_event_bits)==status_system && !is_meta(); + } + + /** + * Dump textual representation of midi message to stream + * @param s output stream + */ + void dump(ostream& s) const; + + }; + + inline ostream& operator<<(ostream& s,const message_t& m) { + m.dump(s); return s; + } + +} + +#endif /* __MIDILLO_MESSAGE_H */ diff --git a/include/midillo/util.h b/include/midillo/util.h new file mode 100644 index 0000000..f9c8430 --- a/dev/null +++ b/include/midillo/util.h @@ -0,0 +1,63 @@ +#ifndef __MIDILLO_UTIL_H +#define __MIDILLO_UTIL_H + +#include <istream> +#include <ostream> + +/** + * @file + * @brief utilities + */ + +namespace midillo { + using std::istream; + using std::ostream; + + /** + * read 32 bits word from the stream + * @param s input stream + * @return the data acquired + */ + unsigned long read32(istream& s); + /** + * read 16 bits word from the stream + * @param s input stream + * @return the data acquired + */ + unsigned int read16(istream& s); + /** + * read the variable length quantity from the stream + * @param s input stream + * @return the data acquired + */ + unsigned long readVL(istream& s); + + /** + * write 32 bits word to the stream + * @param s output stream + * @param d data to write + */ + void write32(ostream& s,unsigned long d); + /** + * write 16 bits word to the stream + * @param s output stream + * @param d data to write + */ + void write16(ostream& s,unsigned int d); + /** + * write the variable length quantity to the stream + * @param s output stream + * @param d data to write + */ + void writeVL(ostream& s,unsigned long d); + + /** + * calculate the amount of data that would be written by writeVL + * @param d data that would be written + * @return the number of bytes + */ + unsigned long calcVLsize(unsigned long d); + +} + +#endif /* __MIDILLO_UTIL_H */ diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..dc5e416 --- a/dev/null +++ b/lib/.gitignore @@ -0,0 +1,7 @@ +*.lo +*.o +Makefile.in +.libs +.deps +Makefile +*.la diff --git a/lib/MThd.cc b/lib/MThd.cc new file mode 100644 index 0000000..110c98a --- a/dev/null +++ b/lib/MThd.cc @@ -0,0 +1,38 @@ +#include <midillo/MThd.h> +#include <midillo/util.h> +#include <midillo/exception.h> + +namespace midillo { + using std::endl; + + void MThd_t::load(istream& s) { + header.load(s); + if(header.id_number!=chunk_id_MThd) + throw exception_unexpected_input(CODEPOINT,"MThd chunk expected"); + if(header.length!=6) + throw exception_invalid_input(CODEPOINT,"MThd chunk is not 6 bytes long"); + load_data(s); + } + + void MThd_t::load_data(istream& s) { + fmt = read16(s); + ntracks = read16(s); + division = read16(s); + } + + void MThd_t::save(ostream& s) const { + header.save(s); + write16(s,fmt); + write16(s,ntracks); + write16(s,division); + } + + void MThd_t::dump(ostream& s) const { + std::ios::fmtflags ff = s.flags(); + s.unsetf(std::ios::hex); s.setf(std::ios::dec); + s << " " << header << endl + << " fmt=" << fmt << ", " << ntracks << " track(s), division=" << division << endl; + s.flags(ff); + } + +} diff --git a/lib/MTrk.cc b/lib/MTrk.cc new file mode 100644 index 0000000..fa1e0f8 --- a/dev/null +++ b/lib/MTrk.cc @@ -0,0 +1,34 @@ +#include <algorithm> +#include <iterator> +#include <midillo/MTrk.h> +#include <midillo/exception.h> + +namespace midillo { + using std::copy; + using std::ostream_iterator; + using std::endl; + + void MTrk_t::load(istream& s) { + header.load(s); + if(header.id_number!=chunk_id_MTrk) + throw exception_unexpected_input(CODEPOINT,"MTrk chunk expected"); + events.load(s); + } + + void MTrk_t::save(ostream& s) const { + chunk_header_t h = header; + h.id_number = chunk_id_MTrk; + h.length = events.calculate_save_size(); + h.save(s); + events.save(s); + } + + void MTrk_t::dump(ostream& s) const { + s << " " << header << endl + << " "; + copy( + events.begin(), events.end(), + ostream_iterator<event_t>(s,"\n ") ); + } + +} diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..de49c97 --- a/dev/null +++ b/lib/Makefile.am @@ -0,0 +1,15 @@ +lib_LTLIBRARIES = libmidillo.la + +INCLUDES = -I${top_srcdir}/include -I${top_srcdir} +AM_CXXFLAGS = ${KONFORKA_CFLAGS} +LDADD = ${KONFORKA_LIBS} + +libmidillo_la_SOURCES = \ + util.cc \ + SMF.cc \ + chunk.cc \ + MThd.cc MTrk.cc \ + event.cc \ + message.cc + +libmidillo_la_LDFLAGS = -version-info 0:0:0 diff --git a/lib/SMF.cc b/lib/SMF.cc new file mode 100644 index 0000000..ba3179d --- a/dev/null +++ b/lib/SMF.cc @@ -0,0 +1,62 @@ +#include <iostream> +#include <fstream> +#include <algorithm> +#include <iterator> +#include <midillo/SMF.h> + +namespace midillo { + using std::ifstream; + using std::ofstream; + using std::cin; + using std::cout; + using std::copy; + using std::ostream_iterator; + using std::endl; + + void SMF_t::load(const char *f,bool stdinable) { + if(stdinable && !strcmp(f,"-")) { + load(cin); + }else{ + ifstream s(f,std::ios::in|std::ios::binary); + load(s); + } + } + + void SMF_t::load(istream& s) { + mthd.load(s); + tracks.resize(mthd.ntracks); + tracks_t::iterator i = tracks.begin(); + for(int t=0;t<mthd.ntracks;++t,++i) { + i->load(s); + } + } + + void SMF_t::save(const char *f,bool stdoutable) const { + if(stdoutable && !strcmp(f,"-")) { + save(cout); + }else{ + ofstream s(f,std::ios::out|std::ios::trunc|std::ios::binary); + save(s); + } + } + + void SMF_t::save(ostream& s) const { + mthd.save(s); + for(tracks_t::const_iterator i=tracks.begin();i!=tracks.end();++i) { + i->save(s); + } + } + + void SMF_t::dump(ostream& s) const { + std::ios::fmtflags ff = s.flags(); + s.unsetf(std::ios::hex); s.setf(std::ios::dec); + s + << "SMF with " << tracks.size() << " track(s)" << endl + << mthd << endl; + copy( + tracks.begin(), tracks.end(), + ostream_iterator<MTrk_t>(s,"\n") ); + s.flags(ff); + } + +} diff --git a/lib/chunk.cc b/lib/chunk.cc new file mode 100644 index 0000000..7cc15ff --- a/dev/null +++ b/lib/chunk.cc @@ -0,0 +1,31 @@ +#include <midillo/chunk.h> +#include <midillo/util.h> +#include <midillo/exception.h> + +namespace midillo { + + void chunk_header_t::load(istream& s) { + s.read((char*)id_chars,sizeof(id_chars)); + if(!s.good()) + throw exception_input_error(CODEPOINT,"Error reading chunk header"); + length = read32(s); + } + + void chunk_header_t::save(ostream& s) const { + s.write((char*)id_chars,sizeof(id_chars)); + if(!s.good()) + throw exception_output_error(CODEPOINT,"Error writing chunk header"); + write32(s,length); + } + + void chunk_header_t::dump(ostream& s) const { + std::ios::fmtflags ff = s.flags(); + s.unsetf(std::ios::hex); s.setf(std::ios::dec); + s + << id_chars[0] << id_chars[1] + << id_chars[2] << id_chars[3] + << " chunk of " << length << " byte(s)"; + s.flags(ff); + } + +} diff --git a/lib/event.cc b/lib/event.cc new file mode 100644 index 0000000..39438fa --- a/dev/null +++ b/lib/event.cc @@ -0,0 +1,58 @@ +#include <midillo/event.h> +#include <midillo/util.h> + +namespace midillo { + + void event_t::load(int& rs,istream& s) { + deltat = readVL(s); + message.load(rs,s); + } + + void event_t::save(int& rs,ostream& s) const { + writeVL(s,deltat); + message.save(rs,s); + } + + unsigned long event_t::calculate_save_size(int& rs) const { + return calcVLsize(deltat) + message.calculate_save_size(rs); + } + + void event_t::dump(ostream& s) const { + std::ios::fmtflags ff = s.flags(); + s.unsetf(std::ios::hex); s.setf(std::ios::dec); + s << "deltat=" << deltat << " [" << message << "]"; + s.flags(ff); + } + + + events_t::iterator events_t::append_event() { + static event_t empty; + return insert(end(),empty); + } + + void events_t::load(istream& s) { + int rs = -1; + for(;;) { + iterator i=append_event(); + i->load(rs,s); + if(i->message.is_meta(meta_EOT)) + break; + } + } + + void events_t::save(ostream& s) const { + int rs = -1; + for(const_iterator i=begin();i!=end();++i) { + i->save(rs,s); + } + } + + unsigned long events_t::calculate_save_size() const { + unsigned long rv = 0; + int rs = -1; + for(const_iterator i=begin();i!=end();++i) { + rv += i->calculate_save_size(rs); + } + return rv; + } +} diff --git a/lib/message.cc b/lib/message.cc new file mode 100644 index 0000000..8f9e68a --- a/dev/null +++ b/lib/message.cc @@ -0,0 +1,247 @@ +#include <algorithm> +#include <iterator> +#include <midillo/message.h> +#include <midillo/util.h> +#include <midillo/exception.h> + +namespace midillo { + using std::copy; + using std::ostream_iterator; + + unsigned long message_t::calculate_save_size(int& rs) const { + unsigned long rv = 0; + if(status!=rs) { + ++rv; + rs = status; + }else if((status&status_event_bits)==status_system) { + rs = -1; + ++rv; // XXX: is it really needed? + } + switch(status&status_event_bits) { + case status_note_off: + case status_note_on: + case status_polyphonic_key_pressure: // aka status_aftertouch + case status_control_change: + case status_pitch_wheel_change: + rv += 2; break; + case status_program_change: + case status_channel_pressure: + ++rv; break; + case status_system: + switch(status&status_system_bits) { + case status_system_sysex: + case status_system_end_of_sysex: + rv += data.size()+1; break; + case status_system_MTC_quarter_frame: + case status_system_song_select: + ++rv; break; + case status_system_song_position_pointer: + rv += 2; break; + case status_system_tune_request: + case status_system_timing_clock: // aka status_system_midi_clock + case status_system_midi_tick: + case status_system_start: // aka status_system_midi_start + case status_system_stop: // aka status_system_midi_stop + case status_system_continue: // aka status_system_midi_continue + case status_system_active_sense: + break; /* XXX: ensure there is no data? */ + case status_system_meta: // also reset, but not for the purpose of midi file + ++rv; + rv += calcVLsize(data.size()); + rv += data.size(); + break; + default: + throw exception(CODEPOINT,"Internal error"); + break; + } + break; + default: + throw exception(CODEPOINT,"Internal error"); + break; + } + return rv; + } + + void message_t::save(int& rs,ostream& s) const { + if(status!=rs) { + s.put(status); + if(!s.good()) + throw exception_output_error(CODEPOINT,"Error writing midi status byte"); + rs = status; + }else if((status&status_event_bits)==status_system) { + rs = -1; + s.put(status); // XXX: is it really needed? + if(!s.good()) + throw exception_output_error(CODEPOINT,"Error writing midi system status byte"); + } + switch(status&status_event_bits) { + case status_note_off: + case status_note_on: + case status_polyphonic_key_pressure: // aka status_aftertouch + case status_control_change: + case status_pitch_wheel_change: + save_data(s,2); break; + case status_program_change: + case status_channel_pressure: + save_data(s,1); break; + case status_system: + switch(status&status_system_bits) { + case status_system_sysex: + case status_system_end_of_sysex: + save_sysex(s); break; + case status_system_MTC_quarter_frame: + case status_system_song_select: + save_data(s,1); break; + case status_system_song_position_pointer: + save_data(s,2); break; + case status_system_tune_request: + case status_system_timing_clock: // aka status_system_midi_clock + case status_system_midi_tick: + case status_system_start: // aka status_system_midi_start + case status_system_stop: // aka status_system_midi_stop + case status_system_continue: // aka status_system_midi_continue + case status_system_active_sense: + break; /* XXX: ensure there is no data? */ + case status_system_meta: // also reset, but not for the purpose of midi file + s.put(meta_status&0xFF); + if(!s.good()) + throw exception_output_error(CODEPOINT,"Error writing meta event"); + writeVL(s,data.size()); + save_data(s); + break; + default: + throw exception(CODEPOINT,"Internal error"); + break; + } + break; + default: + throw exception(CODEPOINT,"Internal error"); + break; + } + } + + void message_t::load(int& rs,istream& s) { + data.clear(); + status = s.get(); + if(!s.good()) + throw exception_input_error(CODEPOINT,"Error reading MIDI event status byte"); + if(status&status_bit) { + if((status&status_event_bits)!=status_system) + rs = status; + }else{ + if(rs<0) + throw exception_invalid_input(CODEPOINT,"Attempt to rely on the absent running status"); + data.push_back(status); + status = rs; + } + switch(status&status_event_bits) { + case status_note_off: + case status_note_on: + case status_polyphonic_key_pressure: // a.k.a. status_aftertouch + case status_control_change: + case status_pitch_wheel_change: + load_data(s,2); break; + case status_program_change: + case status_channel_pressure: + load_data(s,1); break; + case status_system: + switch(status&status_system_bits) { + case status_system_sysex: + case status_system_end_of_sysex: + load_sysex(s); break; + case status_system_MTC_quarter_frame: + case status_system_song_select: + load_data(s,1); break; + case status_system_song_position_pointer: + load_data(s,2); break; + case status_system_tune_request: + case status_system_timing_clock: // a.k.a. status_system_midi_clock + case status_system_midi_tick: + case status_system_start: // a.k.a. status_system_midi_start + case status_system_stop: // a.k.a. status_system_midi_stop + case status_system_continue: // a.k.a. status_system_midi_continue + case status_system_active_sense: + break; + case status_system_meta: // also, reset, but not in midi files + { + meta_status = s.get(); + if(!s.good()) + throw exception_input_error(CODEPOINT,"Error reading meta event type"); + int l = readVL(s); + load_data(s,l); + } + break; + default: + throw exception(CODEPOINT,"Internal error"); + break; + } + break; + default: + throw exception(CODEPOINT,"Internal error"); + break; + } + } + + void message_t::save_data(ostream& s,int c) const { + if(c!=data.size()) + throw exception(CODEPOINT,"Writing corrupt data"); + save_data(s); + } + + void message_t::save_data(ostream& s) const { + for(bytes_t::const_iterator i=data.begin();i!=data.end();++i) + s.put(*i); + if(!s.good()) + throw exception_output_error(CODEPOINT,"Error writing MIDI message data"); + } + + void message_t::load_data(istream& s,int c) { + c -= data.size(); + if(c<0) + throw exception(CODEPOINT,"Internal error"); + while(c-- > 0) { + data.push_back(s.get()); + if(!s.good()) + throw exception_input_error(CODEPOINT,"Error reading MIDI data"); + } + } + + void message_t::save_sysex(ostream& s) const { + save_data(s); + s.put(0xF7); /* XXX: Or is it better to put it into data? */ + if(!s.good()) + throw exception_output_error(CODEPOINT,"Error writing sysex data"); + } + + void message_t::load_sysex(istream& s) { + int b = s.get(); + if(!s.good()) + throw exception_input_error(CODEPOINT,"Error reading sysex data"); + assert(!(b&0x80)); // manufacturer ought to be 7 bit. not sure if it belongs here, it may well be continuation. + do { + data.push_back(b); + b = s.get(); + if(!s.good()) + throw exception_input_error(CODEPOINT,"Error reading sysex data"); + }while(b!=0xF7); + } + + void message_t::dump(ostream& s) const { + std::ios::fmtflags ff = s.flags(); + int w = s.width(2); + s.unsetf(std::ios::dec); s.setf(std::ios::hex); + s << "status=" << status; + if(is_meta()) { + s << ", meta_status=" << meta_status; + } + if(!data.empty()) { + s << ", data: "; + copy( + data.begin(), data.end(), + ostream_iterator<int>(s," ") ); + } + s.width(w); + s.flags(ff); + } + +} diff --git a/lib/util.cc b/lib/util.cc new file mode 100644 index 0000000..4bbe6f3 --- a/dev/null +++ b/lib/util.cc @@ -0,0 +1,96 @@ +#include <midillo/util.h> +#include <midillo/exception.h> + +namespace midillo { + + unsigned long read32(istream& s) { + unsigned long rv = 0; + for(int t=0;t<4;++t) { + rv<<=8; + rv|=0xFF&s.get(); + if(!s.good()) + throw exception_input_error(CODEPOINT,"Input error"); + } + return rv; + } + + unsigned int read16(istream& s) { + unsigned int rv = 0; + for(int t=0;t<2;++t) { + rv<<=8; + rv|=0xFF&s.get(); + if(!s.good()) + throw exception_input_error(CODEPOINT,"Input error"); + } + return rv; + } + + unsigned long readVL(istream& s) { + unsigned long rv=s.get(); + if(!s.good()) + throw exception_input_error(CODEPOINT,"Error reading VLQ"); + int b; + if(rv&0x80) { + rv&=0x7F; + do { + rv = (rv<<7)|( (b=s.get())&0x7F ); + if(!s.good()) + throw exception_input_error(CODEPOINT,"Error reading VLQ"); + }while(b&0x80); + } + return rv; + } + + void write32(ostream& s,unsigned long d) { + for(int t=3;t>=0;--t) { + s.put(((const char*)&d)[t]); + if(!s.good()) + throw exception_output_error(CODEPOINT,"Output error"); + } + } + + void write16(ostream& s,unsigned int d) { + for(int t=1;t>=0;--t) { + s.put(((const char*)&d)[t]); + if(!s.good()) + throw exception_output_error(CODEPOINT,"Output error"); + } + } + + void writeVL(ostream& s,unsigned long d) { + // TODO: I don't think this is perfectly written code + unsigned long tmp = d&0x7F; + while(d>>=7) { + tmp<<=8; + tmp |=((d&0x7F)|0x80); + } + for(;;) { + s.put(tmp&0xFF); + if(!s.good()) + throw exception_output_error(CODEPOINT,"Error writing VLQ"); + if(tmp&0x80) + tmp>>=8; + else + break; + } + } + + unsigned long calcVLsize(unsigned long d) { + unsigned long rv = 0; + // TODO: imperfect code revisited + unsigned long tmp = d&0x7F; + while(d>>=7) { + tmp<<=8; + tmp |=((d&0x7F)|0x80); + } + for(;;) { + ++rv; + if(tmp&0x80) + tmp>>=8; + else + break; + } + return rv; + } + +} diff --git a/man/.gitignore b/man/.gitignore new file mode 100644 index 0000000..3dda729 --- a/dev/null +++ b/man/.gitignore @@ -0,0 +1,2 @@ +Makefile.in +Makefile diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 0000000..13bcb82 --- a/dev/null +++ b/man/Makefile.am @@ -0,0 +1,3 @@ +man_MANS = midi2f0.1 midifilter.1 mididump.1 + +EXTRA_DIST=${man_MANS} diff --git a/man/midi2f0.1 b/man/midi2f0.1 new file mode 100644 index 0000000..ae89ea8 --- a/dev/null +++ b/man/midi2f0.1 @@ -0,0 +1,85 @@ +.TH midi2f0 1 "August 11th, 2006" "midi2f0(1)" "Klever Group (http://www.klever.net/)" +.hla en + +.SH NAME + +midi2f0 \- Standard MIDI File converter + +.SH SYNOPSYS + +\fBmidi2f0\fR +[\fB-h\fR | \fB--help\fR | \fB--usage\fR] +[\fB-V\fR | \fB--version\fR] +[\fB-L\fR | \fB--license\fR] +[<input-file>[ <output-file>]] + +.SH DESCRIPTION + +midi2f0 converts Standard MIDI Files to Standard MIDI Files format 0, which +is single track midi file. You may want to perform such conversion before +putting the file on the memory card for some MIDI keyboard consumption. + +if you want to load file from standard input or dump results to standard output +just omit corresponding file or specify '-'. + +.SH OPTIONS + +.TP +\fB-h\fR, \fB--help\fR, \fB--usage\fR +Display short usage message and exit. +.TP +\fB-V\fR, \fB--version\fR +Report version and exit. +.TP +\fB-L\fR, \fB--license\fR +Show licensing terms. + +.SH EXAMPLE + +.TP +.nf +\fBmidi2f0\fR midifile.mid \\ +| \fBmidifilter\fR \fB-f\fR \fBsysex,meta_unknown,meta_obsolete,meta_texts\fR \\ +>/mnt/card/casio_md/midifile.mid +.fi + +This command converts file to SMF0 file, strips events not understood and not +used by the keyboard and puts it to the memory card. + +.SH EXIT STATUS + +Zero in case of success, non-zero otherwise. + +.SH AUTHOR + +Written by Michael 'hacker' Krelin <hacker@klever.net> + +.SH COPYRIGHT + +Copyright (c) 2006 Klever Group (http://www.klever.net/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +.SH BUGS + +You tell me. Send reports to <midillo-bugs@klever.net> + +.SH SEE ALSO +\fBmidifilter\fR(1) +\fBmididump\fR(1) diff --git a/man/mididump.1 b/man/mididump.1 new file mode 100644 index 0000000..e236ddc --- a/dev/null +++ b/man/mididump.1 @@ -0,0 +1,72 @@ +.TH mididump 1 "August 11th, 2006" "mididump(1)" "Klever Group (http://www.klever.net/)" +.hla en + +.SH NAME + +mididump \- Standard MIDI File dump + +.SH SYNOPSYS + +\fBmididump\fR +[\fB-h\fR | \fB--help\fR | \fB--usage\fR] +[\fB-V\fR | \fB--version\fR] +[\fB-L\fR | \fB--license\fR] +[<input-file>[ <output-file>]] + +.SH DESCRIPTION + +mididump outputs ascii representation of the input SMF file. At this point it +may only be useful for debugging purpose. + +if you want to load file from standard input or dump results to standard output +just omit corresponding file or specify '-'. + +.SH OPTIONS + +.TP +\fB-h\fR, \fB--help\fR, \fB--usage\fR +Display short usage message and exit. +.TP +\fB-V\fR, \fB--version\fR +Report version and exit. +.TP +\fB-L\fR, \fB--license\fR +Show licensing terms. + +.SH EXIT STATUS + +Zero in case of success, non-zero otherwise. + +.SH AUTHOR + +Written by Michael 'hacker' Krelin <hacker@klever.net> + +.SH COPYRIGHT + +Copyright (c) 2006 Klever Group (http://www.klever.net/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +.SH BUGS + +You tell me. Send reports to <midillo-bugs@klever.net> + +.SH SEE ALSO +\fBmidi2f0\fR(1) +\fBmidifilter\fR(1) diff --git a/man/midifilter.1 b/man/midifilter.1 new file mode 100644 index 0000000..eddd66d --- a/dev/null +++ b/man/midifilter.1 @@ -0,0 +1,93 @@ +.TH midifilter 1 "August 11th, 2006" "midifilter(1)" "Klever Group (http://www.klever.net/)" +.hla en + +.SH NAME + +midifilter \- Standard MIDI File filter + +.SH SYNOPSYS + +\fBmidifilter\fR +[\fB-h\fR | \fB--help\fR | \fB--usage\fR] +[\fB-V\fR | \fB--version\fR] +[\fB-L\fR | \fB--license\fR] +[\fB-f\fR \fI<filters>\fR | \fB--filter=\fI<filters>\fR] +[\fB-l\fR | \fB--list-filters\fR] +[<input-file>[ <output-file>]] + +.SH DESCRIPTION + +midifilter simply strips unwanted events from the Standard MIDI File. You may +wish to strip some certain events if you know the events may break whatever +will do any further processing of the file or just to reduce file size. + +if you want to load file from standard input or dump results to standard output +just omit corresponding file or specify '-'. + +.SH OPTIONS + +.TP +\fB-h\fR, \fB--help\fR, \fB--usage\fR +Display short usage message and exit. +.TP +\fB-V\fR, \fB--version\fR +Report version and exit. +.TP +\fB-L\fR, \fB--license\fR +Show licensing terms. +.TP +\fB-f\fR \fI<filters>\fR, \fB--filter=\fI<filters\fR +Specify the list of comma-separated filters to apply. +.TP +\fB-l\fR, \fB--list-filters\fR +List available filters. + +.SH EXAMPLE + +.TP +.nf +\fBmidi2f0\fR midifile.mid \\ +| \fBmidifilter\fR \fB-f\fR \fBsysex,meta_unknown,meta_obsolete,meta_texts\fR \\ +>/mnt/card/casio_md/midifile.mid +.fi + +This command converts file to SMF0 file, strips events not understood and not +used by the keyboard and puts it to the memory card. + +.SH EXIT STATUS + +Zero in case of success, non-zero otherwise. + +.SH AUTHOR + +Written by Michael 'hacker' Krelin <hacker@klever.net> + +.SH COPYRIGHT + +Copyright (c) 2006 Klever Group (http://www.klever.net/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +.SH BUGS + +You tell me. Send reports to <midillo-bugs@klever.net> + +.SH SEE ALSO +\fBmidi2f0\fR(1) +\fBmididump\fR(1) diff --git a/midillo.pc.in b/midillo.pc.in new file mode 100644 index 0000000..dd57130 --- a/dev/null +++ b/midillo.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: midillo +Description: C++ midi manipulation library +Version: @VERSION@ +Requires: konforka +Libs: -L${libdir} -lmidillo +Cflags: -I${includedir} diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000..6d68099 --- a/dev/null +++ b/tools/.gitignore @@ -0,0 +1,11 @@ +Makefile.in +*.o +midifilter +midi2f0 +mididump +.libs +.deps +Makefile +COPYING.cc +filters_enumeration.cc +filters_definition.cc diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 0000000..811c4f8 --- a/dev/null +++ b/tools/Makefile.am @@ -0,0 +1,31 @@ +bin_PROGRAMS = midi2f0 midifilter mididump + +INCLUDES = -I${top_srcdir}/include ${KONFORKA_CFLAGS} +LIBS += ${top_builddir}/lib/libmidillo.la ${KONFORKA_LIBS} + +midi2f0_SOURCES = midi2f0.cc \ + COPYING.cc +midi2f0_DEPENDENCIES = ${top_builddir}/lib/libmidillo.la + +midifilter_SOURCES = midifilter.cc \ + COPYING.cc \ + filters.list \ + enflesh_filters_list.sh +midifilter_DEPENDENCIES = ${top_builddir}/lib/libmidillo.la +${srcdir}/midifilter.cc: filters_enumeration.cc filters_definition.cc +filters_enumeration.cc filters_definition.cc: enflesh_filters_list.sh filters.list + sh $^ \ + filters_enumeration.cc filters_definition.cc + +mididump_SOURCES = mididump.cc \ + COPYING.cc +mididump_DEPENDENCIES = ${top_builddir}/lib/libmidillo.la + +clean-local: + rm -f filters_enumeration.cc filters_definition.cc + + +COPYING.cc: ${top_srcdir}/COPYING + echo "const char * COPYING =" >$@ || (rm $@;exit 1) + sed 's/"/\\"/g' $< | sed 's/^/\"/' | sed 's/$$/\\n\"/' >>$@ || (rm $@;exit 1) + echo ";" >>$@ || (rm $@;exit 1) diff --git a/tools/enflesh_filters_list.sh b/tools/enflesh_filters_list.sh new file mode 100644 index 0000000..f1d8dfc --- a/dev/null +++ b/tools/enflesh_filters_list.sh @@ -0,0 +1,20 @@ +#!/bin/sh +srcfile="${1}" +enumfile="${2}" +deffile="${3}" +IFS=':' +exec 5>${enumfile} +exec 6>${deffile} +SEQN=0 +LN=0 +cat ${srcfile} \ + | while read id ids help ; do + LN="`expr ${LN} + 1`" + test -z "${id}" && continue + test "${id}" != "${id# }" && continue + echo "#line ${LN} \"${srcfile}\"" >&5 + echo " filter_${id} = ${SEQN}," >&5 + echo "#line ${LN} \"${srcfile}\"" >&6 + echo ' { { "'$(echo $ids|sed -e 's-/-", "-g')'", 0 }, "'"${help}"'", false },' >&6 + SEQN="`expr ${SEQN} + 1`" + done diff --git a/tools/filters.list b/tools/filters.list new file mode 100644 index 0000000..c0fb951 --- a/dev/null +++ b/tools/filters.list @@ -0,0 +1,36 @@ +system:system:strip all system messages +meta:meta:strip all meta events (except for EOT) + +sysex:sysex:strip sysex messages +MTC_quarter_frame:MTC_quarter_frame/quarter_frame:strip 'MTC quarter frame' messages +song_position_pointer:song_position_pointer:strip 'song position pointer' messages +song_select:song_select:strip 'song select' messages +tune_request:tune_request:strip 'tune request' messages + +midi_clock:midi_clock/timing_clock:strip 'midi clock' messages +midi_tick:midi_tick:strip 'midi tick' messages +midi_start:midi_start/start:strip 'midi start' messages +midi_continue:midi_continue/continue:strip 'midi continue' messages +midi_stop:midi_stop/stop:strip 'midi stop' messages +active_sense:active_sense:strip 'active sense' messages + +meta_sequence_number:meta_sequence_number:strip 'sequence number' meta events +meta_text:meta_text:strip 'text' meta events +meta_copyright:meta_copyright:strip 'copyright' meta events +meta_seq_track_name:meta_seq_track_name:strip 'sequence track name' meta events +meta_instrument:meta_instrument:strip 'instrument' meta events +meta_lyric:meta_lyric/meta_lyrics:strip 'lyric' meta events +meta_marker:meta_marker:strip 'marker' meta events +meta_cue_point:meta_cue_point:strip 'cue point' meta events +meta_patch_name:meta_patch_name/meta_program_name:strip 'patch name' meta events +meta_port_name:meta_port_name/meta_device_name:strip 'port name' meta events +meta_tempo:meta_temp:strip 'tempo' meta events +meta_SMPTE_offset:meta_SMPTE_offset:strip 'SMPTE offset' meta events +meta_time_sig:meta_time_sig:strip 'time signature' meta events +meta_key_sig:meta_key_sig:strip 'key signature' meta events +meta_proprietary:meta_proprietary:strip 'proprietary' meta events + +meta_midi_channel:meta_midi_channel:strip 'midi channel' meta events +meta_midi_port:meta_midi_port:strip 'midi port' meta events + +meta_unknown:meta_unknown:strip meta events not known by midillo diff --git a/tools/midi2f0.cc b/tools/midi2f0.cc new file mode 100644 index 0000000..dc95be6 --- a/dev/null +++ b/tools/midi2f0.cc @@ -0,0 +1,151 @@ +#include <getopt.h> +#include <iostream> +#include <algorithm> +using namespace std; +#include <konforka/exception.h> +#include <midillo/SMF.h> +using namespace midillo; + +#include "config.h" +#define PHEADER PACKAGE " " VERSION " - midi2f0 - convert to midi format 0" +#define PCOPY "Copyright (c) 2006 Klever Group" + +static void usage(const char *p) { + cerr << PHEADER << endl + << PCOPY << endl << endl + << " " << p << " [options] [<input-file>[ <output-file>]]" << endl << endl + << " -h, --help" << endl + << " --usage display this text" << endl + << " -V, --version display version number" << endl + << " -L, --license show license" << endl; +} + +static bool MTrk_is_empty(const MTrk_t& t) { + return + t.events.empty() + || t.events.front().message.is_meta(meta_EOT); +} + +static bool MTrk_deltat_lt(const MTrk_t& a,const MTrk_t& b) { + // we assume tracks aren't empty + return a.events.front().deltat < b.events.front().deltat; +} + +static bool event_has_nonzero_deltat_or_is_EOT(const event_t& e) { + return e.deltat!=0 || e.message.is_meta(meta_EOT); +} + +main(int argc,char **argv) { + try { + while(true) { + static struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { "usage", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V' }, + { "license", no_argument, 0, 'L' }, + { NULL, 0, 0, 0 } + }; + int c = getopt_long(argc,argv,"hVL",opts,NULL); + if(c==-1) + break; + switch(c) { + case 'h': + usage(*argv); + exit(0); + break; + case 'V': + cerr << VERSION << endl; + exit(0); + break; + case 'L': + extern const char *COPYING; + cerr << COPYING << endl; + exit(0); + break; + default: + cerr << "Huh??" << endl; + break; + } + } + const char *infile = "-"; + if(optind<argc) + infile = argv[optind++]; + const char *oufile = "-"; + if(optind<argc) + oufile = argv[optind++]; + if(optind<argc) { + usage(*argv); + exit(1); + } + SMF_t in(infile); + if(in.mthd.fmt==MThd_t::fmt_0) { + cerr << "File is already SMF 0" << endl; + in.save(oufile); + }else if(in.mthd.fmt==MThd_t::fmt_1) { + cerr << "Converting from SMF 1 to SMF 0" << endl; + SMF_t ou; + ou.mthd = in.mthd; + ou.mthd.fmt = MThd_t::fmt_0; ou.mthd.ntracks = 1; + ou.tracks.resize(1); + MTrk_t& mtrk = ou.tracks.front(); + events_t& evs = mtrk.events; + for(;;) { + // Cleaning up empty tracks + in.tracks.erase( + remove_if( + in.tracks.begin(),in.tracks.end(), + MTrk_is_empty), + in.tracks.end() ); + if(in.tracks.empty()) + break; + // Find the least deltat + unsigned long mindt = min_element( + in.tracks.begin(), in.tracks.end(), + MTrk_deltat_lt )->events.front().deltat; + int t=0; + bool reset = false; + for(SMF_t::tracks_t::iterator i=in.tracks.begin();i!=in.tracks.end();++i) { + if(i->events.front().deltat > mindt) { + i->events.front().deltat-=mindt; + }else{ + do{ + evs.splice( + evs.end(), + i->events,i->events.begin() ); + if(reset) + evs.back().deltat = 0; + else + reset = true; + events_t::iterator lze=find_if( + i->events.begin(),i->events.end(), + event_has_nonzero_deltat_or_is_EOT ); + evs.splice( + evs.end(), + i->events,i->events.begin(),lze ); + }while( (!MTrk_is_empty(*i)) && i->events.front().deltat==0 ); + } + } + } + event_t& eot = *evs.append_event(); + eot.deltat=0; + eot.message.status = status_system_meta; + eot.message.meta_status = meta_EOT; + ou.save(oufile); + }else if(in.mthd.fmt==MThd_t::fmt_2) { + // TODO + cerr << "Not implemented" << endl; + }else{ + cerr << "Unknown MIDI file format" << endl; + } + return 0; + }catch(konforka::exception& e) { + cerr << "Oops... Konforka exception:" << endl + << " what: " << e.what() << endl + << " where: " << e.where() << endl; + return 1; + }catch(exception& e) { + cerr << "Oops... Exception:" << endl + << " what: " << e.what() << endl; + return 1; + } +} diff --git a/tools/mididump.cc b/tools/mididump.cc new file mode 100644 index 0000000..83b7086 --- a/dev/null +++ b/tools/mididump.cc @@ -0,0 +1,84 @@ +#include <getopt.h> +#include <iostream> +#include <fstream> +#include <string> +#include <algorithm> +using namespace std; +#include <konforka/exception.h> +#include <midillo/SMF.h> +using namespace midillo; + +#include "config.h" +#define PHEADER PACKAGE " " VERSION " - mididump - dump midi files" +#define PCOPY "Copyright (c) 2006 Klever Group" + +static void usage(const char *p) { + cerr << PHEADER << endl + << PCOPY << endl << endl + << " " << p << " [options] [<input-file>[ <output-file>]]" << endl << endl + << " -h, --help" << endl + << " --usage display this text" << endl + << " -V, --version display version number" << endl + << " -L, --license show license" << endl; +} + +main(int argc,char **argv) { + try { + while(true) { + static struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { "usage", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V' }, + { "license", no_argument, 0, 'L' }, + { NULL, 0, 0, 0 } + }; + int c = getopt_long(argc,argv,"f:hVLl",opts,NULL); + if(c==-1) + break; + switch(c) { + case 'h': + usage(*argv); + exit(0); + break; + case 'V': + cerr << VERSION << endl; + exit(0); + break; + case 'L': + extern const char *COPYING; + cerr << COPYING << endl; + exit(0); + break; + default: + cerr << "Huh??" << endl; + break; + } + } + const char *infile = "-"; + if(optind<argc) + infile = argv[optind++]; + const char *oufile = "-"; + if(optind<argc) + oufile = argv[optind++]; + if(optind<argc) { + usage(*argv); + exit(1); + } + SMF_t in(infile); + if(strcmp(oufile,"-")) { + ofstream s(oufile); s << in; + }else{ + cout << in; + } + return 0; + }catch(konforka::exception& e) { + cerr << "Oops... Konforka exception:" << endl + << " what: " << e.what() << endl + << " where: " << e.where() << endl; + return 1; + }catch(exception& e) { + cerr << "Oops... Exception:" << endl + << " what: " << e.what() << endl; + return 1; + } +} diff --git a/tools/midifilter.cc b/tools/midifilter.cc new file mode 100644 index 0000000..1c130de --- a/dev/null +++ b/tools/midifilter.cc @@ -0,0 +1,295 @@ +#include <getopt.h> +#include <iostream> +#include <string> +#include <algorithm> +#include <iterator> +using namespace std; +#include <konforka/exception.h> +#include <midillo/SMF.h> +using namespace midillo; + +#include "config.h" +#define PHEADER PACKAGE " " VERSION " - midifilter - midi filter tool" +#define PCOPY "Copyright (c) 2006 Klever Group" + +enum { +# include "filters_enumeration.cc" + total_filters +}; + +struct filter_t { + const char *ids[5]; + const char *help; + bool filter; +} filters[] = { +# include "filters_definition.cc" + {0,0,0} +}; +#define FILTER(f) filters[filter_##f].filter + +inline ostream& operator<<(ostream& s,const filter_t& f) { + ios::fmtflags ff = s.flags(); + int w = s.width(25); + s.unsetf(ios::right); s.setf(ios::left); + s << *f.ids << " " << f.help; + s.width(w); + s.flags(ff); + return s; +} + +static bool message_is_filtered(const message_t& m) { + if(m.is_meta(meta_EOT)) + return false; + + if(FILTER(sysex) && (m.status==status_system_sysex || m.status==status_system_end_of_sysex)) + return true; + if(FILTER(MTC_quarter_frame) && m.status==status_system_MTC_quarter_frame) + return true; + if(FILTER(song_position_pointer) && m.status==status_system_song_position_pointer) + return true; + if(FILTER(song_select) && m.status==status_system_song_select) + return true; + if(FILTER(tune_request) && m.status==status_system_tune_request) + return true; + if(FILTER(midi_clock) && m.status==status_system_midi_clock) + return true; + if(FILTER(midi_tick) && m.status==status_system_midi_tick) + return true; + if(FILTER(midi_start) && m.status==status_system_midi_start) + return true; + if(FILTER(midi_continue) && m.status==status_system_midi_continue) + return true; + if(FILTER(midi_stop) && m.status==status_system_midi_stop) + return true; + if(FILTER(active_sense) && m.status==status_system_active_sense) + return true; + + if(FILTER(meta_sequence_number) && m.is_meta(meta_sequence_number)) + return true; + if(FILTER(meta_text) && m.is_meta(meta_text)) + return true; + if(FILTER(meta_copyright) && m.is_meta(meta_copyright)) + return true; + if(FILTER(meta_seq_track_name) && m.is_meta(meta_seq_track_name)) + return true; + if(FILTER(meta_instrument) && m.is_meta(meta_instrument)) + return true; + if(FILTER(meta_lyric) && m.is_meta(meta_lyric)) + return true; + if(FILTER(meta_marker) && m.is_meta(meta_marker)) + return true; + if(FILTER(meta_cue_point) && m.is_meta(meta_cue_point)) + return true; + if(FILTER(meta_patch_name) && m.is_meta(meta_patch_name)) + return true; + if(FILTER(meta_port_name) && m.is_meta(meta_port_name)) + return true; + if(FILTER(meta_tempo) && m.is_meta(meta_tempo)) + return true; + if(FILTER(meta_SMPTE_offset) && m.is_meta(meta_SMPTE_offset)) + return true; + if(FILTER(meta_time_sig) && m.is_meta(meta_time_sig)) + return true; + if(FILTER(meta_key_sig) && m.is_meta(meta_key_sig)) + return true; + if(FILTER(meta_proprietary) && m.is_meta(meta_proprietary)) + return true; + + if(FILTER(meta_midi_channel) && m.is_meta(meta_midi_channel)) + return true; + if(FILTER(meta_midi_port) && m.is_meta(meta_midi_port)) + return true; + + if(FILTER(meta_unknown) && m.is_meta()) { + const int known_metas[] = { + meta_sequence_number, meta_text, meta_copyright, meta_seq_track_name, + meta_instrument, meta_lyric, meta_marker, meta_cue_point, + meta_patch_name, meta_port_name, meta_EOT, meta_tempo, + meta_SMPTE_offset, meta_time_sig, meta_key_sig, meta_proprietary, + meta_midi_channel, meta_midi_port }; + const int* lastknown = &known_metas[sizeof(known_metas)/sizeof(*known_metas)]; + if( find( + known_metas, lastknown, + m.meta_status ) == lastknown ) { + return true; + } + } + + if(FILTER(meta) && m.is_meta()) + return true; + + return false; +} + +static bool event_is_filtered(const event_t& e) { + return message_is_filtered(e.message); +} + +static bool MTrk_is_empty(const MTrk_t& t) { + return + t.events.empty() + || t.events.front().message.is_meta(meta_EOT); +} + +struct filter_preset_t { + const char *id; + const char *help; + int filters[total_filters]; +} filter_presets[] = { + { "system_common", "strip system common messages", + { filter_sysex, filter_song_position_pointer, filter_song_select, + filter_tune_request, -1 } }, + { "system_runtime", "strip system runtime messages", + { filter_midi_clock, filter_midi_tick, filter_midi_start, + filter_midi_continue, filter_midi_stop, filter_active_sense, -1 } }, + { "meta_obsolete", "strip obsolete meta events", + { filter_meta_midi_channel, filter_meta_midi_port, -1 } }, + { "meta_texts", "strip textual meta events", + { filter_meta_text, filter_meta_copyright, + filter_meta_seq_track_name, filter_meta_instrument, + filter_meta_lyric, filter_meta_marker, filter_meta_cue_point, + filter_meta_patch_name, filter_meta_port_name, -1 } } +}; + +inline ostream& operator<<(ostream& s,const filter_preset_t& fp) { + ios::fmtflags ff = s.flags(); + int w = s.width(25); + s.unsetf(ios::right); s.setf(ios::left); + s << fp.id << " " << fp.help; + s.width(w); + s.flags(ff); + return s; +} + +static void usage(const char *p) { + cerr << PHEADER << endl + << PCOPY << endl << endl + << " " << p << " [options] [<input-file>[ <output-file>]]" << endl << endl + << " -h, --help" << endl + << " --usage display this text" << endl + << " -V, --version display version number" << endl + << " -L, --license show license" << endl + << " -f <filters>, --filter=<filters>" + << " specify the list of events (comma-separated) to" << endl + << " strip" << endl + << " -l, --list-filters" << endl + << " list available filters" << endl; +} + +main(int argc,char **argv) { + try { + while(true) { + static struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { "usage", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V' }, + { "license", no_argument, 0, 'L' }, + { "filter", no_argument, 0, 'f' }, + { "list-filters", no_argument, 0, 'l' }, + { NULL, 0, 0, 0 } + }; + int c = getopt_long(argc,argv,"f:hVLl",opts,NULL); + if(c==-1) + break; + switch(c) { + case 'h': + usage(*argv); + exit(0); + break; + case 'V': + cerr << VERSION << endl; + exit(0); + break; + case 'L': + extern const char *COPYING; + cerr << COPYING << endl; + exit(0); + break; + case 'f': + { + string fs = optarg; + while(!fs.empty()) { + string::size_type ns = fs.find_first_not_of(" :/,;"); + if(ns==string::npos) + break; + if(ns) + fs.erase(ns); + string::size_type s = fs.find_first_of(" :/,;"); + string f; + if(s==string::npos) { + f = fs; fs.clear(); + }else{ + f = fs.substr(0,ns); + fs.erase(0,ns+1); + } + for(int fn=0;fn<total_filters;++fn) { + filter_t& filter = filters[fn]; + for(int fid=0;fid<(sizeof(filter.ids)/sizeof(*filter.ids)) && filter.ids[fid];++fid) { + if(f == filter.ids[fid]) + filter.filter = true; + } + } + } + } + break; + case 'l': + cerr << PHEADER << endl + << PCOPY << endl << endl; + copy( + filters, &filters[total_filters], + ostream_iterator<filter_t>(cerr,"\n") ); + copy( + filter_presets, &filter_presets[sizeof(filter_presets)/sizeof(*filter_presets)], + ostream_iterator<filter_preset_t>(cerr,"\n") ); + exit(0); + break; + default: + cerr << "Huh??" << endl; + break; + } + } + const char *infile = "-"; + if(optind<argc) + infile = argv[optind++]; + const char *oufile = "-"; + if(optind<argc) + oufile = argv[optind++]; + if(optind<argc) { + usage(*argv); + exit(1); + } + SMF_t in(infile); + for(SMF_t::tracks_t::iterator t=in.tracks.begin();t!=in.tracks.end();++t) { + for(events_t::iterator e=t->events.begin();e!=t->events.end();) { + if(event_is_filtered(*e)) { + events_t::iterator i = e++; + // we assume it is not the last event, since the last event + // (meta_EOT) is unfilterable + e->deltat += i->deltat; + t->events.erase(i); + }else + ++e; + } + } + in.tracks.erase( + remove_if( + in.tracks.begin(),in.tracks.end(), + MTrk_is_empty), + in.tracks.end() ); + if(in.tracks.empty()) { + cerr << "We have no MIDI data in the output file"; + } + in.mthd.ntracks = in.tracks.size(); + in.save(oufile); + return 0; + }catch(konforka::exception& e) { + cerr << "Oops... Konforka exception:" << endl + << " what: " << e.what() << endl + << " where: " << e.where() << endl; + return 1; + }catch(exception& e) { + cerr << "Oops... Exception:" << endl + << " what: " << e.what() << endl; + return 1; + } +} |