Initial commit

This commit is contained in:
kevin 2020-07-30 10:26:58 -04:00
commit 63991ae469
16 changed files with 4178 additions and 0 deletions

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# directories to ignore
libcm
.deps
autom4te.cache
build-aux
build/linux/debug/src/
build/linux/debug/bin
build/linux/debug/lib
build/linux/debug/include
build/linux/release/src/
build/linux/release/bin
build/linux/release/lib
build/linux/release/include
#Files to ignore
*~
*.[oa]
Makefile
aclocal.m4
config.h.in
config.h
configure
hold.makefile
Makefile.in
config.log
config.status
libtool
stamp-h1
m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4

80
Makefile.am Normal file
View File

@ -0,0 +1,80 @@
AM_CXXFLAGS =
AM_LDFLAGS =
AM_CPPFLAGS =
AM_CFLAGS =
ACLOCAL_AMFLAGS = -I m4 # use custom macro's in ./m4
# if we are building and linking to a nested copy of libcm
if BUILD_LIBCM
SUBDIRS = src/libcm # causes recursive make into given sub-directories
AM_CPPFLAGS += -I$(srcdir)/src/libcm/src -I$(srcdir)/src/libcm/src/dsp -I$(srcdir)/src/libcm/src/vop -I$(srcdir)/src/libcm/src/app
AM_LDFLAGS += -Lsrc/libcm/src
endif
# To Profile w/ gprof:
# 1) Modify configure: ./configure --disable-shared CFLAGS="-pg"
# 2) Run the program. ./foo
# 3) Run gprof /libtool --mode=execute gprof ./foo
# C compiler flags
# _GNU_SOURCE - turns on GNU specific extensions and gives correct prototype for double log2(double)
# -Wall turn on all warnings
# -Wno-multichar - turns off multi-character constant warnings from cmAudioFile.c
AM_CPPFLAGS += -D _GNU_SOURCE -I..
AM_CFLAGS += -Wno-multichar
# debug/release switches
if DEBUG
AM_CFLAGS += -g
AM_CXXFLAGS += -g
else
AM_CFLAGS += -O3
AM_CXXFLAGS += -O3
endif
MYLIBS = -lpthread -lfftw3f -lfftw3 -lcm
# Linux specific
if OS_LINUX
MYLIBS += -lsatlas -lasound
if OS_64
AM_CFLAGS += -m64
AM_LDFLAGS += -L/usr/lib64/atlas -L/usr/lib64
MYLIBS += -lrt -lm
endif
endif
if OS_OSX
AM_CPPFLAGS += -I/opt/local/include # Search macports directory for fftw headers
AM_LDFLAGS += -L/opt/local/lib # and libraries.
AM_LDFLAGS += -framework Cocoa -framework CoreAudio -framework CoreMIDI -framework Carbon -framework Accelerate
endif
src_cmtools_cmtools_SOURCES = src/cmtools/cmtools.c
src_cmtools_cmtools_LDADD = $(MYLIBS)
bin_PROGRAMS = src/cmtools/cmtools
src_cmtools_mas_SOURCES = src/cmtools/mas.c
src_cmtools_mas_LDADD = $(MYLIBS)
bin_PROGRAMS += src/cmtools/mas
src_cmtools_audiodev_SOURCES = src/cmtools/audiodev.c
src_cmtools_audiodev_LDADD = $(MYLIBS)
bin_PROGRAMS += src/cmtools/audiodev
# See: https://www.gnu.org/savannah-checkouts/gnu/automake/manual/html_node/Clean.html#Clean
# 'make distclean' sets the source tree back to it's pre-configure state
# 'distclean-local' is used by automake 'distclean' to perform customized local actions
# ${exec_prefix} is the install prefix given to 'configure' by the user.
# ${srcdir} is the directory of this Makefile and is set by autoconf.
distclean-local:
rm -rf ${exec_prefix}/src
rm -rf ${exec_prefix}/bin
rm -rf ${exec_prefix}/include
rm -rf ${exec_prefix}/lib

452
README.md Normal file
View File

@ -0,0 +1,452 @@
Prerequisites
=============
fftw fftw-devel atlas atlas-devel alsa-lib alsa-lib-devel fltk fltk-devel
In the 'template generation' mode the program generates a text file that contains information
of interest from the MusicXML file. We refer to the text file as the as the 'decoration' file
because it allows the score to be further decorated by adding additional information to the score.
In the 'escore generation' mode the program outputs a data file in CSV format which contains
the score in a format which is conveniently readable by a score matching program. The program
also generates a MIDI file which allows the clarified score to be rendered with a sampler.
An additional SVG (scalable vector graphics) file is generated which shows the score in
piano roll form along with any problems that the program may have had during the conversion process.
```
cmtool --score_gen -x <xml_file> -d <dec_fn> {-c <csvOutFn>} {-m <midiOutFn>} {-s <svgOutFn>} {-r report} {-b begMeasNumb} {t begTempoBPM}
Enumerated group: Action selector
-S --score_gen
Run the score generation tool.
-T --timeline_gen
Run the time line generation tool.
-M --meas
Generate perfomance measurements.
-x --xml_fn <filename>
Name of the input MusicXML file.
-d --dec_fn <filename>
Name of a score decoration file.
-c --csv_fn <filename>
Name of a CSV score file.
-p --pgm_rsrc_fn <filename>
Name of program resource file.
-m --midi_out_fn <filename>
Name of a MIDI file to generate as output.
-i --midi_in_fn <filename>
Name of a MIDI file to generate as output.
-s --svg_fn <filename>
Name of a HTML/SVG file to generate as output.
-t --timeline_fn <filename>
Name of a timeline to generate as output.
-r --report_fn <filename>
Name of a status file to generate as output.
-f --debug_fl (required)
Print a report of the score following processing.
-b --beg_meas <int>
The first measure the to be written to the output CSV, MIDI and SVG files.
-e --beg_bpm <int>
Set to 0 to use the tempo from the score otherwise set to use the tempo at begMeasNumb.
-n --svg_stand_alone_fl
Write the SVG output with an HTML wrapper.
-z --svg_pan_zoom_fl
Include the SVG pan-zoom control (--svg_stand_alone must also be enabled)
-h --help
Print this usage information.
-v --version
Print version information.
```
Score Following and Timeline Marker Generator
==============================================
Perform score following based tasks.
Generate the time line marker information used by the performance program resource file.
```
cmtool --score_follow -c <csv_score_fn> -i <midi_in_fn> -r <report_fn> -s <svg_out_fn> -m <midi_out_fn> -t <timeline_fn>
```
If `<midi_out_fn>` is given then the a copy of `<midi_in_fn>` will be created with
note velocities and sostenuto pedal events from the score.
Score match report output snippet:
```
Score Score Score MIDI MIDI MIDI
Bar UUID Pitch UUID Ptch Vel.
- ----- ----- ----- ----- ---- ----
m 1 3 B3 19 B3 127
m 1 4 D#4 20 D#4 127
m 1 5 G#5 21 G#5 127
m 1 14 E4 25 E4 29
m 1 15 G#3 27 G#3 36
m 1 16 E4 29 E4 43
m 1 17 G#3 31 G#3 50
```
TODO: Show errors
TODO: Show SVG output
Performance Measurement Generators
==================================
Given a performance program resource file generate performance measurements where the internal MIDI file
is used as a substitute for a real player.
```
cmtool --meas_gen -p <pgm_rsrc_fn> -r <report_fn>
```
Example `<pgm_rsrc_fn>`:
```
{
timeLineFn: "kc/data/round2.js"
tlPrefixPath: "projects/imag_themes/scores/gen"
scoreFn: "score.csv"
// pppp ppp pp p mp mf f ff fff
dynRef: [ 14, 28, 42, 56, 71, 85, 99, 113,128 ]
}
```
Example call with output file snippet:
```
cmtool --meas_gen -g ~/src/cmtools/examples/perf_meas/pgm_rsrc_round2.js -r ~/src/cmtools/examples/perf_meas/perf_meas_out.js
{
meas :
[
[ "sec" "typeLabel" "val" "cost" "loc" "evt" "seq" "mark" "typeId" ]
[ "6002" "tempo" 34.730932 0.000000 59 76 0 "1" 3 ]
[ "6002" "dyn" 0.952381 0.000000 59 76 0 "1" 2 ]
[ "6002" "even" 0.600000 0.000000 59 76 0 "1" 1 ]
[ "6002" "dyn" 1.000000 0.142857 59 76 0 "2" 2 ]
]
}
```
Column Descriptions:
Column | Description
----------|-----------------------------------------------------
sec | Score section to which this measurement will be applied.
typeLabel | Measurement type
value | Measurement value
cost | Score follower error value (0= perfect match)
loc | Score location index where this measurement will be applied
evt | Score event index where this measurement will be applied
seq | Sequence id.
mark | Time line marker label from which the measurements were made
typeId | Measurement type id (numeric id associated with typeLabel)
Note that the event indexes and score locations are best
seen in the score report (as generated by `cmtool --score_report`)
NOT by directly referencing the score CSV file.
Score Report
============
Generate a human readable score report from a score CSV file.
```
cmtool --score_report -c <csvScoreFn> -r <scoreRptFn>"
```
Example report file snippet:
```
evnt CSV bar
index line loctn bar idx type pitch ETD Dynamic
----- ----- ----- --- --- ----- ----- --- -------
0 2 1 bar
1 3 0 1 0 non B3 section:6001
2 4 0 1 1 non D#4
3 5 0 1 2 non G#5
4 6 1 1 3 ped dn
5 7 2 1 3 ped dn
6 8 3 1 3 non E4
7 9 4 1 4 non G#3 td ppp
8 10 5 1 5 non E4 td pp
9 11 6 1 6 non G#3 td pp
10 12 7 1 7 non C#2 td p
11 13 8 1 8 non C4 td mp
12 14 9 1 9 non G#3 td mp
13 15 10 1 10 non C#2 td mf
14 16 11 1 11 non A#2 td f
15 17 12 1 12 non C4 td f
16 18 13 1 13 non A#2 td f
17 19 14 1 14 non C#1
```
MIDI File Reports
=================
Generate a MIDI file report and optional SVG piano roll image."
cmtool --midi_report -i <midiInFn> -r <midiRptFn> {-s <svgOutFn> {--svg_stand_alone_fl} {--svg_pan_zoom_fl} }
Timeline Report
=================
Generate human readable report from a time line setup file.
cmtool --timeline_report -t <timelineInFn> -l <tlPrefix> -r <rptOutFn>
tlPrefix is the folder where data files for this timeline are stored.
Score Follow Report
===================
```
cmtool --score_follow -c round2.csv -i new_round2.mid -r report.txt -s report_svg.html
```
SVG Description
---------------
- Red borders around score events that were not matched.
- Red borders around MIDI events that did not match.
- Line is drawn to MIDI events that matched to multiple score events. The lines
are drawn to all score events after the first match.
Audio Device Test
=================
Real-time audio port test.
This test also excercises the real-time audio buffer which implements most of the global audio control functions.
```
```
```
-s --srate <real> Audio system sample rate.
-z --hz <real> Tone frequency in Hertz.
-x --ch_index <uint> Index of first channel index.
-c --ch_cnt <uint> Count of audio channels.
-b --buf_cnt <uint> Count of audio buffers. (e.g. 2=double buffering, 3=triple buffering)
-f --frames_per_buf <uint> Count of audio channels.
-i --in_dev_index <uint> Input device index as taken from the audio device report.
-o --out_dev_index <uint> Output device index as taken from the audio device report.
-r --report_flag Print a report of the score following processing.
-h --help Print this usage information.
-v --version Print version information.
-p --parms Print the arguments.
```
MIDI Audio Sync (MAS)
=====================
1) Synchronize Audio to MIDI based on onset patterns:
a. Convert MIDI to audio impulse files:
mas -m -i <midi_dir | midi_fn > -o <out_dir> -s <srate>
Notes:
* If <midi_dir> is given then use all files in the directory as input otherwise convert a single file.
* The files written to <out_dir> are audio files with impulses written at the location of note on msg's. The amplitude of the the impulse is velocity/127.
b. Convert the onsets in audio file(s) to audio impulse
file(s).
mas -a -i <audio_dir | audio_fn > -o <out_dir>
-w <wndMs> -f <hopFactor> -u <chIdx> -r <wnd_frm_cnt>
-x <preWndMult> -t <threshold> -z <maxFrqHz> -e <filtCoeff>
1) If <audio_dir> is given then use all files
in the directory as input otherwise convert a
single file.
2) The onset detector uses a spectral flux based
algorithm.
See cmOnset.h/.c for an explanation of the
onset detection parameters.
c) Convolve impulse files created in a) and b) with a
Hann window to widen the impulse width.
mas -c -i <audio_dir | audio_fn > -o <out_dir> -w <wndMs>
1) If <audio_dir> is given then use all files
in the directory as input otherwise convert a
single file.
2) <wndMs> gives the width of the Hann window.
d) Synchronize MIDI and Audio based convolved impulse
files based on their onset patterns.
mas -y -i <sync_cfg_fn.js> -o <sync_out_fn.js>
1) The <sync_cfg_fn.js> file has the following format:
{
ref_dir : "/home/kevin/temp/mas/midi_conv" // location of ref files
key_dir : "/home/kevin/temp/mas/onset_conv" // location of key files
hop_ms : 25 // sliding window increment
sync_array :
[
// ref_fn wnd_beg_secs wnd_dur_secs key_fn key_beg_secs, key_end_secs
[ "1.aif", 678, 113, "Piano 3_01.aif", 239.0, 417.0],
[ "3.aif", 524, 61, "Piano 3_06.aif", 556.0, 619.0],
]
}
Notes:
a. The 'window' is the section of the reference file which is compared
to the key file search area <key_beg_secs> to <key_end_secs> by sliding it
in increments of 'hop_ms' samples.
b. Set 'key_end_secs' to 0 to search to the end of the file.
c. When one key file matches to multiple reference files the
key files sync recd should be listed consecutively. This way
the earlier searches can stop when they reach the beginning
of the next sync records search region. See sync_files().
Note that by setting <key_beg_secs> to a non-zero value
as occurs in the multi-key-file case has a subtle effect of
changing the master-slave relationship between the reference
an key file.
In general the reference file is the master and the key file
is the slave. When a non-zero <key_beg_secs> is given however
this relationship reverses. See masCreateTimeLine() for
how this is used to assign file group id's during the
time line creation.
3) The <sync_out_fn.js> has the following form.
```
{
"sync" :
{
"refDir" : "/home/kevin/temp/mas/midi_conv"
"keyDir" : "/home/kevin/temp/mas/onset_conv"
"hopMs" : 25.000000
"array" :
[
//
// sync results for "1.aif" to "Piano 3_01.aif"
//
{
// The following block of fields were copied from <sync_cfg_fn.js>.
"refFn" : "1.aif"
"refWndBegSecs" : 678.000000
"refWndSecs" : 113.000000
"keyFn" : "Piano 3_01.aif"
"keyBegSecs" : 239.000000
"keyEndSecs" : 417.000000
// Sync. location of the 'window' in the key file.
// Sample index into the key file which matches to the first sample
// in the reference window.
"keySyncIdx" : 25768800 // Offset into the key file of the best match.
"syncDist" : 4184.826108 // Match distance score for the sync location.
"refSmpCnt" : 200112000 // Count of samples in the reference file.
"keySmpCnt" : 161884800 // Count of samples in the key file.
"srate" : 96000.000000 // Sample rate of the reference and key file.
},
]
}
}
```
2) Create a time line from the results of a synchronization. A time line is a data structure
(See cmTimeLine.h/.c) which maintains a time based ordering of Audio files, MIDI files,
and arbitrary markers.
mas -g -i <sync_out_fn.js> -o <time_line_out_fn.js> -R <ref_dir> -K <key_dir> -M <ref_ext> -A <key_ext>
| <sync_out_fn.js> The output file produced as a result of a previous MIDI <-> Audio synchronization.
|
| <ref_dir> Location of the reference files (MIDI) used for the synchronization.
| <ref_ext> File extension used by the reference files.
| <key_dir> Locate of the key files (Audio) used for the synchronization.
| <key_ext> File extension used by the key files.
a. The time line 'trackId' assigned to each time line object is based on the files
'groupId'. A common group id is given to sets of files which are
locked in time relative to one another. For example
if file B and C are synced to master file A and
file D is synced to file E which is synced to master
file F. Then files A,B,C will be given one group
id and files D,E and F will be given another group id.
(See masCreateTimeLine()).
b. The time line object 'offset' values gives the offset in samples where the object
begins relative to other objects in the group. Note that the master object in the
group may not begin at offset 0 if there are slave objects which start before it.
TODO:
=====
* replace round2.csv time with the times in the full fragment MIDI file
* update timeline lite to allow for a synchronized audio file
* change the build setup to default to getting libcm from the system
and allow an option to build it from src/libcm
* for all svg output create a standalone flag that wraps the output in HTML
and another option to load pan-zoom.
* MIDI report output example and description
* Timeline report output example and description

37
build/clean.sh Executable file
View File

@ -0,0 +1,37 @@
#!/bin/bash
#
# Run 'make distclean' to clean many of the temporary make files.
# then use this script run from cm/build to clean the remaining files
#
function clean_dir {
make -C $1 uninstall
make -C $1 distclean
rm -f $1/bin/kc.app/Contents/MacOS/kc
}
clean_dir linux/debug
clean_dir linux/release
clean_dir osx/debug
clean_dir osx/release
rm -rf osx/debug/a.out.dSYM
# delete everything created by 'autoreconf'.
rm -rf ../build-aux
rm -rf ../autom4te.cache
rm -f ../config.h.in ../config.h.in~ ../configure ../libtool.m4
rm -f ../Makefile.in ../aclocal.m4
rm -f ../m4/libtool.m4 ../m4/ltoptions.m4 ../m4/ltsugar.m4 ../m4/ltversion.m4 ../m4/lt~obsolete.m4

25
build/linux/debug/build.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/sh
curdir=`pwd`
cd ../../..
autoreconf --force --install
cd ${curdir}
# To Profile w/ gprof:
# 1) Modify configure: ./configure --disable-shared CFLAGS="-pg"
# 2) Run the program. ./foo
# 3) Run gprof /libtool --mode=execute gprof ./foo
../../../configure --prefix=${curdir} \
--enable-debug --enable-build_libcm \
CFLAGS="-g -Wall" \
CXXFLAGS="-g -Wall" \
LIBS=
# CPPFLAGS="-I/home/kevin/src/libcm/build/linux/debug/include " \
# LDFLAGS="-L/home/kevin/src/libcm/build/linux/debug/lib" \
#make
#make install

24
build/linux/release/build.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
curdir=`pwd`
cd ../../..
autoreconf --force --install
cd ${curdir}
# To Profile w/ gprof:
# 1) Modify configure: ./configure --disable-shared CFLAGS="-pg"
# 2) Run the program. ./foo
# 3) Run gprof /libtool --mode=execute gprof ./foo
../../../configure --prefix=${curdir} \
CFLAGS="-Wall" \
CXXFLAGS="-Wall" \
CPPFLAGS= \
LDFLAGS= \
LIBS=
#make
#make install

20
build/osx/debug/build.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/sh
curdir=`pwd`
cd ../../..
autoreconf --force --install
cd ${curdir}
../../../configure --prefix=${curdir} \
--enable-debug \
CFLAGS="-g -Wall" \
CXXFLAGS="-g -Wall" \
CPPFLAGS= \
LDFLAGS= \
LIBS=
#make
#make install

19
build/osx/release/build.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/sh
curdir=`pwd`
cd ../../..
autoreconf --force --install
cd ${curdir}
../../../configure --prefix=${curdir} \
CFLAGS="-Wall" \
CXXFLAGS="-Wall" \
CPPFLAGS= \
LDFLAGS= \
LIBS=
#make
#make install

152
configure.ac Normal file
View File

@ -0,0 +1,152 @@
#
# Use "autoreconf --force --install" to update depedent files after changing
# this configure.ac or any of the Makefile.am files.
#
AC_INIT([cmtools],[1.0],[kl@currawongproject.org])
AC_CONFIG_AUX_DIR([build-aux]) # put aux files in build-aux
AM_INIT_AUTOMAKE([1.9 -Wall foreign subdir-objects]) # subdir-objects needed for non-recursive make
AC_CONFIG_SRCDIR([src/cmtools/cmtools.c])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIR([m4])
AM_PROG_AR
LT_INIT
# Check for programs
AC_PROG_CC
AC_PROG_CXX
# AC_PROG_RANLIB # required for static librarires
AM_PROG_CC_C_O
# Checks for libraries.
# AC_CHECK_LIB([cairo],[cairo_debug_reset_static_data],[AC_MSG_RESULT([The 'cairo' library was found.])],[AC_MSG_ERROR([The 'cairo' library was not found.])])
#TODO: add more library checks
# Checks for header files.
AC_CHECK_HEADERS([arpa/inet.h fcntl.h float.h limits.h mach/mach.h netinet/in.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h termios.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL
AC_C_INLINE
AC_TYPE_OFF_T
AC_TYPE_SSIZE_T
AC_TYPE_UINT64_T
# Checks for library functions.
AC_FUNC_ERROR_AT_LINE
AC_FUNC_FORK
AC_FUNC_FSEEKO
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_FUNC_STRTOD
AC_CHECK_FUNCS([clock_gettime floor memmove memset mkdir pow rint select socket sqrt strcasecmp strchr strcspn strerror strspn strstr strtol])
# The following is a custom macro in ./m4/os_type.m4
# be sure to also set "ACLOCAL_AMFLAGS = -I m4" in ./Makefile.am
# Defines the config.h variable OS_LINUX or OS_OSX
AX_FUNC_OS_TYPE
AX_FUNC_OS_64
# ac_cv_os_type is set by AX_FUNC_OS_TYPE
AM_CONDITIONAL([OS_LINUX],[test x"${ax_cv_os_type}" = xLinux])
AM_CONDITIONAL([OS_OSX],[test x"${ax_cv_os_type}" = xDarwin])
echo "OS='${ax_cv_os_type}'"
AM_CONDITIONAL([OS_64],[test x"${ax_cv_os_64}" == xx86_64])
echo "ptr width='${ax_cv_os_64}'"
# check if a request has been made to build libcm
AC_ARG_ENABLE([build_libcm],
[ --enable-build_libcm libcm is included in the local source tree],
[case "${enableval}" in
yes) build_libcm=true ;;
no) build_libcm=false ;;
*) AC_MSG_ERROR([bad value ${enableval} for --enable-build_libcm]) ;;
esac],[build_libcm=false])
echo "build_libcm=${build_libcm}"
# check if a nested copy of libcm exists in /src/libcm
AC_CHECK_FILE([${srcdir}/src/libcm/src/cmGlobal.h],[local_libcm=true],[local_libcm=false])
echo "local_libcm=${local_libcm}"
# set BUILD_LIBCM if a libcm build request was set and a nested copy of libcm exists
AM_CONDITIONAL([BUILD_LIBCM], [test x$build_libcm = xtrue -a x$local_libcm = xtrue ])
AC_ARG_ENABLE([debug],
[ --enable-debug Turn on debugging],
[case "${enableval}" in
yes) debug=true ;;
no) debug=false ;;
*) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;;
esac],[debug=false])
echo "debug=${debug}"
AM_CONDITIONAL([DEBUG], [test x$debug = xtrue])
if test x$debug = xfalse; then
AC_DEFINE([NDEBUG], 1,[Debugging off.])
fi
AC_ARG_ENABLE([vectop],
[ --enable-vectop Turn on use of Lapack and Atlas vector/matrix operations. ],
[case "${enableval}" in
yes) vectop=true ;;
no) vectop=false ;;
*) AC_MSG_ERROR([bad value ${enableval} for --enable-vectop]) ;;
esac],[vectop=true])
echo "vectop=${vectop}"
# if --enable-vectop then #define CM_VECTOP = 1 in config.h otherwise CM_VECTOP is undefined.
if test x"$vectop" = xtrue; then
AC_DEFINE([CM_VECTOP], 1,[Use Lapack and Atlas.])
fi
AC_ARG_ENABLE([memalign],
[ --enable-memalign Turn on memory alignment on dynamic memory allocations. ],
[case "${enableval}" in
yes) memalign=true ;;
no) memalign=false ;;
*) AC_MSG_ERROR([bad value ${enableval} for --enable-memalign]) ;;
esac],[memalign=true])
echo "memalign=${memalign}"
# if --enable-vectop then #define CM_MEMALIGN = 1 in config.h otherwise CM_MEMALIGN is undefined.
if test x"$memalign" = xtrue; then
AC_DEFINE([CM_MEMALIGN], 1,[Turn on dynamic memory alignment.])
fi
AC_ARG_ENABLE([sonicart],
[ --enable-sonicart Enable use of Sonic Arts proprietary code. ],
[case "${enableval}" in
yes) sonicart=true ;;
no) sonicart=false ;;
*) AC_MSG_ERROR([bad value ${enableval} for --enable-sonicart]) ;;
esac],[sonicart=false])
echo "sonicart=${sonicart}"
# if --enable-sonicart then #define CM_SONICART = 1 in config.h otherwise CM_SONICART is undefined.
if test x"$sonicart" = xtrue; then
AC_DEFINE([CM_SONICART], 1,[Include Sonic Arts proprietry code.])
fi
AM_CONDITIONAL([INC_SONICART], [test x$sonicart = xtrue])
AC_CONFIG_FILES([ Makefile ])
# if local nested libcm then do recursive configure into subdirs
if test x$build_libcm = xtrue -a x$local_libcm = xtrue; then
AC_CONFIG_SUBDIRS([src/libcm])
fi
AC_OUTPUT

BIN
doc/score_follow_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

254
doc/xscore_gen.md Normal file
View File

@ -0,0 +1,254 @@
xscore_gen
==========
*xscore_gen* parses MusicXML score files and generates a text file
which allows the score to be clarified and additional information to
be added. This is the first step in creating a 'machine readable
score' based on the 'human readable score'.
This step is necessary because we need a way to efficiently
append additional information to the score which cannot be entered
directly by the score editing program (e.g. Sibelius, Dorico, Finale).
In practice we use Sibelius 6 as our primary score editor.
Likewise there are certain limitations to the generated MusicXML
which need to be worked around. The primary problem being that
dynamic markings are not tied to specific notes. This is important
for purposes of score analysis as well as audio rendering.
The overall approach to adding this addtional information
is as follows:
1. Add as much auxilliary information as possible from within Sibelius.
This entails using colored note heads, and carefully placed
text strings.
2. Generate the MusicXML file using the [Dolet 6 Sibelius
plug-in](https://www.musicxml.com/). The resulting MusicXML file is
run through *xscore_gen* and parsed to find any invalid structures
such as damper up events not preceeded by damper down events, or tied
notes with no ending note. These problems are cleared by careful
re-editing of the score within Sibelius until all the problematic
structures are fixed.
3. As a side effect of step 2 a template 'decoration' file is generated.
This text file has all the relavant 'machine score' information
from the XML score as a time tagged list. In this step *decoration* information is manually
added by entering codes at the end of each line. The codes
are cryptic but they are also succinct and allow for relatively
painless editing.
4. Once the addition information is entered *xscore_gen* is
run again to generate three output files:
- machine score as a CSV file
- MIDI file suitable for audio rendering
- SVG piano roll file
As with step 2 this step may need to be iterated several times
to clear syntactic errors in the decoration data.
5. Generate the time line marker information to be used with the performance program:
Generate the time line marker information, into `temp/time_line_temp.txt` like this:
`cmtest -F`
This calls `cmMidiScoreFollowMain()` in app\cmMidiScoreFollow.c.
Then paste `temp\time_line_temp.txt` into kc/src/kc/data/round1.js.
Preparing the score
-------------------
Note color is used to assign notes to groups.
These groups may later be used to indicate
certain processes which will be performed
on these notes during performance.
There are currently three defined groups
'even','dynamics' and 'tempo'.
### Score Coloring Chart:
Description Number Color
------------------- -------- -------------------------
Even #0000FF blue
Tempo #00FF00 green
Dyn #FF0000 red
Tempo + Even #00FFFF green + blue (turquoise)
Dyn + Even #FF00FF red + blue
Dyn + Tempo #FF7F00 red + green (brown)
Tempo + Even + Dyn #996633 purple
Decrement color by one (i.e. 0xFE) to indicate the last note in a group
of measured notes. Note that a decremented color stops all active measures
not just the measurement associated with the decremented color.
Preparing the Music XML File
----------------------------
*xscore_gen* is know to work with the MusicXML files produced by
the [Dolet 6 Sibelius plug-in]<https://www.musicxml.com/>
After generating the file it is necessary to do some
minor pre-processing before submitting it to *xscore_gen*
iconv -f UTF-16 -t UTF-8 -o score-utf16.xml score-utf8.xml
Create the decoration file
--------------------------
```
cmtools --score_gen -x myscore.xml -d mydec.txt
```
Here's a snippet of a typical 'decoration' file.
```
Part:P1
1 : div:768 beat:4 beat-type:4 (3072)
idx voc loc tick durtn rval flags
--- --- ----- ------- ----- ---- --- ---------------
0 0 2 0 0 0.0 |--------------
1 0 0 0 54 4.0 --------------- 54 bpm
2 1 0 0 3072 1.0 -R-------------
3 5 0 0 2304 2.0 -R-.-----------
4 0 0 996 0 0.0 --------V------
5 0 0 1920 0 0.0 --------^------
6 5 0 2304 341 8.0 -R-------------
7 0 0 2643 0 0.0 --------V------
8 5 0 2645 85 32.0 -R-------------
9 5 3 2730 85 32.0 F 5 --------------*
10 5 4 2815 85 32.0 Ab2 --------------*
11 5 5 2900 85 32.0 C 3 --------------*
12 5 6 2985 87 32.0 F 6 --------------*
13 1 0 3072 768 4.0 -R-------------
14 5 0 3072 768 4.0 -R------------- 3840
```
### Decoration file format
Column | Description
-------|-----------------------------
idx | event index
voc | voice index
tick | MIDI tick
durtn | duration in MIDI ticks
rval | rythmic value
pitch | scientific pitch
flags | event attributes
### Event attribute flags:
Event attribute symbols used in the decoration file:
Desc | Flag |
----------|------|-----------------------------------------
Bar | | | Beginning of a measure
Rest | R | Rest event
Grace | G | Grace note event
Dot | . | note is dotted
Chord | C | note is part of a chord
Even | e | note is part of an 'even' group
Dyn | d | note is part of a 'dynamics' group
Tempo | t | note is part of a 'tempo' group
DampDn | V | damper down event
DampUp | ^ | damper up event
DampUpDn | X | damper up/down event
SostDn | { | sostenuto down event
Section | S | section boundary
SostUp | } | sostenuto up event
Heel | H | heel event
Tie Begin | T | begin of a tied note
Tie End | _ | end of a tied note
Onset | * | note onset
Decoration Sytax:
------------------
!<dyn_mark> Assign dynamics
!(<dyn_mark>) - less uncertain dynamic mark
!<upper-case-dyn-mark> - begin of dynamic fork (See note below regarding dynamic forks)
!!<upper-case-dyn-mark> - end of dynamic fork
~<mark> Insert or remove event (See pedal marks below.)
@<new_tick_value> Move event to a new time position
%<grace_note_flag> Flag note as a grace note
%%<grace_note_flag> -last note in grace note sequence
$<sci_pitch> Assign a note a new pitch
<grace_note_flag>
b (begin grace)
a (add grace and end grace)
s (subtract grace and end grace)
g (grace note)
A (after first)
N (soon after first)
Note: The first non-grace note in a grace note sequence is marked with a %b.
The last non-grace note in a grace note sequence is marked with a %s or %a.
Where: %s = steal time from the note marked with %b.
%a = insert time prior to the note marked with %a.
The last (by row number) note (grace or non-grace) in the the sequence
is marked with %%# where # is replaced with a,b,s,or g.
It is only necessary to mark the tick number of grace notes in order
to give the time sequence of the notes. A single grace note therefore does
not require an explict tick mark notation (i.e. @####)
Insert/delete Event Marks: <mark>
-----------------------------------
d (sostenuto down - just after note onset)
u ( " up - just before this event)
x ( " up just before this event and down just after it)
D (damper pedal down - after this event)
U (damper pedal up - before this event)
_ (set tie end flag)
& (skip this event)
Dynamic Marks: <dyn-mark>
--------------------------
s (silent note)
pppp-
pppp
pppp+
ppp-
ppp
ppp+
pp-
pp
pp+
p-
p
p+
mp-
mp
mp+
mf-
mf
mf+
f-
f
f+
ff
ff+
fff
Note: Dynamic Forks:
--------------------
Use upper case dynamic letters to indicate forks in the dynamics
which should be filled automatically. Note that all notes
in the voice assigned to the first note in the fork will be
included in the dynamic change. To exclude a note from the
fork assign it a lower case mark.

8
m4/os_64.m4 Normal file
View File

@ -0,0 +1,8 @@
AC_DEFUN([AX_FUNC_OS_64],
[AC_CACHE_CHECK([operating system address width],
[ax_cv_os_64],
[ax_cv_os_64=`uname -m`])
if test x"$ax_cv_os_64" = xx86_64; then
AC_DEFINE([OS_64], 1,[Operating system is 64 bits.])
fi
]) # AX_FUNC_OS_TYPE

11
m4/os_type.m4 Normal file
View File

@ -0,0 +1,11 @@
AC_DEFUN([AX_FUNC_OS_TYPE],
[AC_CACHE_CHECK([operating system type],
[ax_cv_os_type],
[ax_cv_os_type=`uname`])
if test x"$ax_cv_os_type" = xLinux; then
AC_DEFINE([OS_LINUX], 1,[Operating system is Linux.])
fi
if test x"$ax_cv_os_type" = xDarwin; then
AC_DEFINE([OS_OSX], 1,[Operating system is Darwin.])
fi]) # AX_FUNC_OS_TYPE

397
src/cmtools/audiodev.c Normal file
View File

@ -0,0 +1,397 @@
#include "cmPrefix.h"
#include "cmGlobal.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmLinkedHeap.h"
#include "cmFileSys.h"
#include "cmText.h"
#include "cmPgmOpts.h"
#include "cmTime.h"
#include "cmAudioPort.h"
#include "cmApBuf.h" // only needed for cmApBufTest().
#include "cmAudioPortFile.h"
#include "cmAudioAggDev.h"
#include "cmAudioNrtDev.h"
#include "cmFloatTypes.h"
#include "cmAudioFile.h"
#include "cmFile.h"
enum
{
kOkAdRC = cmOkRC,
kAudioPortFailAdRC,
kAudioPortFileFailAdRC,
kAudioPortNrtFailAdRC,
kAudioBufFailAdRC
};
const cmChar_t* poBegHelpStr =
"audiodev Test the real-time audio ports"
"\n"
"audiodev -i<in_dev_index> -o <out_dev_index -s <sample_rate>\n"
"\n"
"All arguments are optional. The default input and output device index is 0.\n"
"\n";
const cmChar_t* poEndHelpStr = "";
/// [cmAudioPortExample]
// See cmApPortTest() below for the main point of entry.
// Data structure used to hold the parameters for cpApPortTest()
// and the user defined data record passed to the host from the
// audio port callback functions.
typedef struct
{
unsigned bufCnt; // 2=double buffering 3=triple buffering
unsigned chIdx; // first test channel
unsigned chCnt; // count of channels to test
unsigned framesPerCycle; // DSP frames per cycle
unsigned bufFrmCnt; // count of DSP frames used by the audio buffer (bufCnt * framesPerCycle)
unsigned bufSmpCnt; // count of samples used by the audio buffer (chCnt * bufFrmCnt)
unsigned inDevIdx; // input device index
unsigned outDevIdx; // output device index
double srate; // audio sample rate
unsigned meterMs; // audio meter buffer length
// param's and state for cmApSynthSine()
unsigned phase; // sine synth phase
double frqHz; // sine synth frequency in Hz
// buffer and state for cmApCopyIn/Out()
cmApSample_t* buf; // buf[bufSmpCnt] - circular interleaved audio buffer
unsigned bufInIdx; // next input buffer index
unsigned bufOutIdx; // next output buffer index
unsigned bufFullCnt; // count of full samples
// debugging log data arrays
unsigned logCnt; // count of elements in log[] and ilong[]
char* log; // log[logCnt]
unsigned* ilog; // ilog[logCnt]
unsigned logIdx; // current log index
unsigned cbCnt; // count the callback
} cmApPortTestRecd;
unsigned _cmGlobalInDevIdx = 0;
unsigned _cmGlobalOutDevIdx = 0;
unsigned _cmGlobalCbCnt = 0;
#define aSrate 48000
#define aFrmN aSrate*10
#define aChN 2
#define abufN aFrmN*aChN
cmApSample_t abuf[ abufN ];
unsigned abufi = 0;
void _abuf_copy_in( cmApAudioPacket_t* pktArray, unsigned pktN )
{
unsigned i,j,k;
for(i=0; i<pktN; ++i)
{
cmApSample_t* sp = (cmApSample_t*)(pktArray[i].audioBytesPtr);
unsigned frmN = pktArray[i].audioFramesCnt;
unsigned chN = cmMin(pktArray[i].chCnt,aChN);
for(j=0; abufi<aFrmN && j<frmN; ++j, ++abufi)
for(k=0; k<chN; ++k)
abuf[ (k*aFrmN) + abufi ] = *sp++;
}
}
void _abuf_write_audio_file(cmCtx_t* ctx )
{
cmApSample_t* sigVV[ ] = { abuf, abuf + aFrmN };
cmAudioFileWriteFileFloat( "/home/kevin/temp/temp.wav", aSrate, 16, aFrmN, 2, sigVV, &ctx->rpt );
}
void _abuf_write_csv_file(cmCtx_t* ctx )
{
cmFileH_t fH = cmFileNullHandle;
unsigned i = 0,j;
cmFileOpen( &fH, "/home/kevin/temp/temp.csv", kWriteFileFl, &ctx->rpt );
for(i=0; i<aFrmN; ++i)
{
for(j=0; j<aChN; ++j)
{
char comma = j==aChN-1 ? ' ':',';
cmFilePrintf(fH, "%f%c",abuf[ aFrmN*j + i ], comma );
}
cmFilePrintf( fH, "\n");
}
cmFileClose(&fH);
}
void _cmApPortCb2( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt )
{
cmApBufInputToOutput( _cmGlobalInDevIdx, _cmGlobalOutDevIdx );
cmApBufUpdate( inPktArray, inPktCnt, outPktArray, outPktCnt );
if( outPktArray != NULL )
_abuf_copy_in(outPktArray,outPktCnt);
_cmGlobalCbCnt += 1;
}
cmRC_t audio_port_test( cmCtx_t* ctx, cmApPortTestRecd* r, bool runFl )
{
cmRC_t rc = kOkAdRC;
unsigned i = 0;
int srateMult = 0;
cmRpt_t* rpt = &ctx->rpt;
cmApSample_t buf[r->bufSmpCnt];
char log[r->logCnt];
unsigned ilog[r->logCnt];
r->buf = buf;
r->log = log;
r->ilog = ilog;
r->cbCnt = 0;
_cmGlobalInDevIdx = r->inDevIdx;
_cmGlobalOutDevIdx= r->outDevIdx;
cmRptPrintf(rpt,"%s in:%i out:%i chidx:%i chs:%i bufs=%i frm=%i rate=%f\n",runFl?"exec":"rpt",r->inDevIdx,r->outDevIdx,r->chIdx,r->chCnt,r->bufCnt,r->framesPerCycle,r->srate);
if( cmApFileAllocate(rpt) != kOkApRC )
{
rc = cmErrMsg(&ctx->err, kAudioPortFileFailAdRC,"Audio port file allocation failed.");
goto errLabel;
}
// allocate the non-real-time port
if( cmApNrtAllocate(rpt) != kOkApRC )
{
rc = cmErrMsg(&ctx->err, kAudioPortNrtFailAdRC,"Non-real-time system allocation failed.");
goto errLabel;
}
// initialize the audio device interface
if( cmApInitialize(rpt) != kOkApRC )
{
rc = cmErrMsg(&ctx->err, kAudioPortFailAdRC,"Port initialize failed.\n");
goto errLabel;
}
// report the current audio device configuration
for(i=0; i<cmApDeviceCount(); ++i)
{
cmRptPrintf(rpt,"%i [in: chs=%i frames=%i] [out: chs=%i frames=%i] srate:%f %s\n",i,cmApDeviceChannelCount(i,true),cmApDeviceFramesPerCycle(i,true),cmApDeviceChannelCount(i,false),cmApDeviceFramesPerCycle(i,false),cmApDeviceSampleRate(i),cmApDeviceLabel(i));
}
// report the current audio devices using the audio port interface function
cmApReport(rpt);
if( runFl )
{
// initialize the audio buffer
cmApBufInitialize( cmApDeviceCount(), r->meterMs );
// setup the buffer for the output device
cmApBufSetup( r->outDevIdx, r->srate, r->framesPerCycle, r->bufCnt, cmApDeviceChannelCount(r->outDevIdx,true), r->framesPerCycle, cmApDeviceChannelCount(r->outDevIdx,false), r->framesPerCycle, srateMult );
// setup the buffer for the input device
if( r->inDevIdx != r->outDevIdx )
cmApBufSetup( r->inDevIdx, r->srate, r->framesPerCycle, r->bufCnt, cmApDeviceChannelCount(r->inDevIdx,true), r->framesPerCycle, cmApDeviceChannelCount(r->inDevIdx,false), r->framesPerCycle, srateMult );
// setup an output device
if(cmApDeviceSetup(r->outDevIdx,r->srate,r->framesPerCycle,_cmApPortCb2,&r) != kOkApRC )
rc = cmErrMsg(&ctx->err,kAudioPortFailAdRC,"Out audio device setup failed.\n");
else
// setup an input device
if( cmApDeviceSetup(r->inDevIdx,r->srate,r->framesPerCycle,_cmApPortCb2,&r) != kOkApRC )
rc = cmErrMsg(&ctx->err,kAudioPortFailAdRC,"In audio device setup failed.\n");
else
// start the input device
if( cmApDeviceStart(r->inDevIdx) != kOkApRC )
rc = cmErrMsg(&ctx->err,kAudioPortFailAdRC,"In audio device start failed.\n");
else
// start the output device
if( cmApDeviceStart(r->outDevIdx) != kOkApRC )
rc = cmErrMsg(&ctx->err, kAudioPortFailAdRC,"Out audio device start failed.\n");
else
cmRptPrintf(rpt,"Started...");
cmApBufEnableChannel(r->inDevIdx, -1, kEnableApFl | kInApFl );
cmApBufEnableMeter( r->inDevIdx, -1, kEnableApFl | kInApFl );
cmApBufEnableChannel(r->outDevIdx, -1, kEnableApFl | kOutApFl );
cmApBufEnableMeter( r->outDevIdx, -1, kEnableApFl | kOutApFl );
cmRptPrintf(rpt,"q=quit O/o=output tone, I/i=input tone P/p=pass s=buf report\n");
char c;
while((c=getchar()) != 'q')
{
//cmApAlsaDeviceRtReport(rpt,r->outDevIdx);
switch(c)
{
case 'i':
case 'I':
cmApBufEnableTone(r->inDevIdx,-1,kInApFl | (c=='I'?kEnableApFl:0));
break;
case 'o':
case 'O':
cmApBufEnableTone(r->outDevIdx,-1,kOutApFl | (c=='O'?kEnableApFl:0));
break;
case 'p':
case 'P':
cmApBufEnablePass(r->outDevIdx,-1,kOutApFl | (c=='P'?kEnableApFl:0));
break;
case 's':
cmApBufReport(rpt);
cmRptPrintf(rpt,"CB:%i\n",_cmGlobalCbCnt);
break;
}
}
// stop the input device
if( cmApDeviceIsStarted(r->inDevIdx) )
if( cmApDeviceStop(r->inDevIdx) != kOkApRC )
cmRptPrintf(rpt,"In device stop failed.\n");
// stop the output device
if( cmApDeviceIsStarted(r->outDevIdx) )
if( cmApDeviceStop(r->outDevIdx) != kOkApRC )
cmRptPrintf(rpt,"Out device stop failed.\n");
}
errLabel:
// release any resources held by the audio port interface
if( cmApFinalize() != kOkApRC )
rc = cmErrMsg(&ctx->err,kAudioPortFailAdRC,"Finalize failed.\n");
cmApBufFinalize();
cmApNrtFree();
cmApFileFree();
// report the count of audio buffer callbacks
cmRptPrintf(rpt,"cb count:%i\n", r->cbCnt );
//for(i=0; i<_logCnt; ++i)
// cmRptPrintf(rpt,"%c(%i)",_log[i],_ilog[i]);
//cmRptPrintf(rpt,"\n");
return rc;
}
void print( void* arg, const char* text )
{
printf("%s",text);
}
int main( int argc, char* argv[] )
{
enum
{
kSratePoId = kBasePoId,
kHzPoId,
kChIdxPoId,
kChCntPoId,
kBufCntPoId,
kFrmCntPoId,
kFrmsPerBufPoId,
kInDevIdxPoId,
kOutDevIdxPoId,
kReportFlagPoId
};
cmRC_t rc = cmOkRC;
bool memDebugFl = cmDEBUG_FL;
unsigned memGuardByteCnt = memDebugFl ? 8 : 0;
unsigned memAlignByteCnt = 16;
unsigned memFlags = memDebugFl ? kTrackMmFl | kDeferFreeMmFl | kFillUninitMmFl : 0;
cmPgmOptH_t poH = cmPgmOptNullHandle;
const cmChar_t* appTitle = "audiodev";
unsigned reportFl = 0;
cmCtx_t ctx;
cmApPortTestRecd r;
memset(&r,0,sizeof(r));
r.meterMs = 50;
r.logCnt = 100;
memset(abuf,0,sizeof(cmApSample_t)*abufN);
cmCtxSetup(&ctx,appTitle,print,print,NULL,memGuardByteCnt,memAlignByteCnt,memFlags);
cmMdInitialize( memGuardByteCnt, memAlignByteCnt, memFlags, &ctx.rpt );
cmFsInitialize( &ctx, appTitle );
cmTsInitialize(&ctx );
cmPgmOptInitialize(&ctx, &poH, poBegHelpStr, poEndHelpStr );
cmPgmOptInstallDbl( poH, kSratePoId, 's', "srate", 0, 48000, &r.srate, 1,
"Audio system sample rate." );
cmPgmOptInstallDbl( poH, kHzPoId, 'z', "hz", 0, 1000, &r.frqHz, 1,
"Tone frequency in Hertz." );
cmPgmOptInstallUInt( poH, kChIdxPoId, 'x', "ch_index", 0, 0, &r.chIdx, 1,
"Index of first channel index." );
cmPgmOptInstallUInt( poH, kChCntPoId, 'c', "ch_cnt", 0, 2, &r.chCnt, 1,
"Count of audio channels." );
cmPgmOptInstallUInt( poH, kBufCntPoId, 'b', "buf_cnt", 0, 3, &r.bufCnt, 1,
"Count of audio buffers. (e.g. 2=double buffering, 3=triple buffering)" );
cmPgmOptInstallUInt( poH, kFrmsPerBufPoId, 'f', "frames_per_buf",0, 512, &r.framesPerCycle, 1,
"Count of audio channels." );
cmPgmOptInstallUInt( poH, kInDevIdxPoId, 'i', "in_dev_index",0, 0, &r.inDevIdx, 1,
"Input device index as taken from the audio device report." );
cmPgmOptInstallUInt( poH, kOutDevIdxPoId, 'o', "out_dev_index",0, 0, &r.outDevIdx, 1,
"Output device index as taken from the audio device report." );
cmPgmOptInstallFlag( poH, kReportFlagPoId, 'r', "report_flag", 0, 1, &reportFl, 1,
"Print an audio device report." );
// parse the command line arguments
if( cmPgmOptParse(poH, argc, argv ) == kOkPoRC )
{
// handle the built-in arg's (e.g. -v,-p,-h)
// (returns false if only built-in options were selected)
if( cmPgmOptHandleBuiltInActions(poH, &ctx.rpt ) == false )
goto errLabel;
rc = audio_port_test( &ctx, &r, !reportFl );
}
errLabel:
_abuf_write_audio_file(&ctx);
cmPgmOptFinalize(&poH);
cmTsFinalize();
cmFsFinalize();
cmMdReport( kIgnoreNormalMmFl );
cmMdFinalize();
return rc;
}

431
src/cmtools/cmtools.c Normal file
View File

@ -0,0 +1,431 @@
#include "cmPrefix.h"
#include "cmGlobal.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmLinkedHeap.h"
#include "cmFileSys.h"
#include "cmText.h"
#include "cmPgmOpts.h"
#include "cmXScore.h"
#include "cmMidiScoreFollow.h"
#include "cmScoreProc.h"
#include "cmSymTbl.h"
#include "cmTime.h"
#include "cmMidi.h"
#include "cmScore.h"
#include "cmMidiFile.h"
#include "cmFloatTypes.h"
#include "cmAudioFile.h"
#include "cmTimeLine.h"
enum
{
kOkCtRC = cmOkRC,
kNoActionIdSelectedCtRC,
kMissingRequiredFileNameCtRC,
kScoreGenFailedCtRC,
kScoreFollowFailedCtRC,
kMidiFileRptFailedCtRC,
kTimeLineRptFailedCtRC,
kAudioFileRptFailedCtRC
};
const cmChar_t poEndHelpStr[] = "";
const cmChar_t poBegHelpStr[] =
"xscore_proc Music XML to electronic score generator\n"
"\n"
"USAGE:\n"
"\n"
"Parse an XML score file and decoration file to produce a score file in CSV format.\n"
"\n"
"cmtool --score_gen -x <xml_file> -d <dec_fn> {-c <csvScoreOutFn} {-m <midiOutFn>} {-s <svgOutFn>} {-r report} {-b begMeasNumb} {t begTempoBPM}\n"
"\n"
"Notes:\n"
"1. If <dec_fn> does not exist then a decoration template file will be generated based on the MusicXML file. \n"
"2. Along with the CSV score file MIDI and HTML/SVG files will also be produced based on the contents of the MusicXML and decoration file.\n"
"3. See README.md for a detailed description of the how to edit the decoration file.\n"
"\n"
"\n"
"Use the score follower to generate a timeline configuration file.\n"
"\n"
"cmtool --timeline_gen -c <csvScoreFn> -i <midiInFn> -r <matchRptFn> -s <matchSvgFn> {-m <midiOutFn>} {-t timelineOutFn} \n"
"\n"
"Measure some perforamance attributes:\n"
"\n"
"cmtool --meas_gen -g <pgmRsrcFn> -r <measRptFn>\n"
"\n"
"Generate a score file report\n"
"\n"
"cmtool --score_report -c <csvScoreFn> -r <scoreRptFn>\n"
"\n"
"Generate a MIDI file report and optional SVG piano roll image\n"
"\n"
"cmtool --midi_report -i <midiInFn> -r <midiRptFn> {-s <svgOutFn>}\n"
"\n"
"Generate a timeline report\n"
"\n"
"cmtool --timeline_report -t <timelineFn> -r <timelineRptFn>\n"
"\n"
"Generate an audio file report\n"
"\n"
"cmtool --audiofile_report -a <audioFn> -r <rptFn>\n"
"\n";
void print( void* arg, const char* text )
{
printf("%s",text);
}
bool verify_file_exists( cmCtx_t* ctx, const cmChar_t* fn, const cmChar_t* msg )
{
if( fn == NULL || cmFsIsFile(fn)==false )
return cmErrMsg(&ctx->err,kMissingRequiredFileNameCtRC,"The required file <%s> does not exist.",msg);
return kOkCtRC;
}
bool verify_non_null_filename( cmCtx_t* ctx, const cmChar_t* fn, const cmChar_t* msg )
{
if( fn == NULL )
return cmErrMsg(&ctx->err,kMissingRequiredFileNameCtRC,"The required file name <%s> is blank.",msg);
return kOkCtRC;
}
cmRC_t score_gen( cmCtx_t* ctx, const cmChar_t* xmlFn, const cmChar_t* decFn, const cmChar_t* csvOutFn, const cmChar_t* midiOutFn, const cmChar_t* svgOutFn, unsigned reportFl, int begMeasNumb, int begTempoBPM, bool svgStandAloneFl, bool svgPanZoomFl )
{
cmRC_t rc;
if((rc = verify_file_exists(ctx,xmlFn,"XML file")) != kOkCtRC )
return rc;
if( cmXScoreTest( ctx, xmlFn, decFn, csvOutFn, midiOutFn, svgOutFn, reportFl, begMeasNumb, begTempoBPM, svgStandAloneFl, svgPanZoomFl ) != kOkXsRC )
return cmErrMsg(&ctx->err,kScoreGenFailedCtRC,"score_gen failed.");
return kOkCtRC;
}
cmRC_t score_follow( cmCtx_t* ctx, const cmChar_t* csvScoreFn, const cmChar_t* midiInFn, const cmChar_t* matchRptOutFn, const cmChar_t* matchSvgOutFn, const cmChar_t* midiOutFn, const cmChar_t* timelineFn )
{
cmRC_t rc;
if((rc = verify_file_exists(ctx,csvScoreFn,"Score CSV file")) != kOkCtRC )
return rc;
if((rc = verify_file_exists(ctx,midiInFn,"MIDI input file")) != kOkCtRC )
return rc;
//if((rc = verify_file_exists(ctx,matchRptOutFn,"Match report file")) != kOkCtRC )
// return rc;
//if((rc = verify_file_exists(ctx,matchSvgOutFn,"Match HTML/SVG file")) != kOkCtRC )
// return rc;
if(cmMidiScoreFollowMain(ctx, csvScoreFn, midiInFn, matchRptOutFn, matchSvgOutFn, midiOutFn, timelineFn) != kOkMsfRC )
return cmErrMsg(&ctx->err,kScoreFollowFailedCtRC,"score_follow failed.");
return kOkCtRC;
}
cmRC_t meas_gen( cmCtx_t* ctx, const cmChar_t* pgmRsrcFn, const cmChar_t* outFn )
{
cmRC_t rc;
if((rc = verify_file_exists(ctx,pgmRsrcFn,"Program resource file")) != kOkCtRC )
return rc;
if((rc = verify_non_null_filename( ctx,outFn,"Measurements output file.")) != kOkCtRC )
return rc;
return cmScoreProc(ctx, "meas", pgmRsrcFn, outFn );
}
cmRC_t score_report( cmCtx_t* ctx, const cmChar_t* csvScoreFn, const cmChar_t* rptFn )
{
cmRC_t rc;
if((rc = verify_file_exists(ctx,csvScoreFn,"Score CSV file")) != kOkCtRC )
return rc;
cmScoreReport(ctx,csvScoreFn,rptFn);
return rc;
}
cmRC_t midi_file_report( cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* rptFn, const cmChar_t* svgFn, bool standAloneFl, bool panZoomFl )
{
cmRC_t rc ;
if((rc = verify_file_exists(ctx,midiFn,"MIDI file")) != kOkCtRC )
return rc;
if((rc = cmMidiFileReport(ctx, midiFn, rptFn )) != kOkMfRC )
return cmErrMsg(&ctx->err,kMidiFileRptFailedCtRC,"MIDI file report generation failed.");
if( svgFn != NULL )
if((rc = cmMidiFileGenSvgFile(ctx, midiFn, svgFn, "midi_file_svg.css", standAloneFl, panZoomFl )) != kOkMfRC )
return cmErrMsg(&ctx->err,kMidiFileRptFailedCtRC,"MIDI file SVG output generation failed.");
return kOkCtRC;
}
cmRC_t timeline_report( cmCtx_t* ctx, const cmChar_t* timelineFn, const cmChar_t* tlPrefixPath, const cmChar_t* rptFn )
{
cmRC_t rc ;
if((rc = verify_file_exists(ctx,timelineFn,"Timeline file")) != kOkCtRC )
return rc;
if((rc = cmTimeLineReport( ctx, timelineFn, tlPrefixPath, rptFn )) != kOkTlRC )
return cmErrMsg(&ctx->err,kTimeLineRptFailedCtRC,"The timeline file report failed.");
return rc;
}
cmRC_t audio_file_report( cmCtx_t* ctx, const cmChar_t* audioFn, const cmChar_t* rptFn )
{
cmRC_t rc;
if((rc = verify_file_exists(ctx,audioFn,"Audio file")) != kOkCtRC )
return rc;
if((rc = cmAudioFileReportInfo( ctx, audioFn, rptFn )) != kOkTlRC )
return cmErrMsg(&ctx->err,kAudioFileRptFailedCtRC,"The audio file report failed.");
return rc;
}
cmRC_t midi_trim(cmCtx_t* ctx, const cmChar_t* midiInFn, unsigned begMidiUId, unsigned endMidiUId, const cmChar_t* midiOutFn)
{
cmRC_t rc;
if((rc = verify_file_exists(ctx,midiInFn,"MIDI file")) != kOkCtRC )
return rc;
// kNoteTerminateFl | kPedalTerminateFl
return cmMidiFileTrimFn(ctx, midiInFn, begMidiUId, endMidiUId, 0, midiOutFn );
}
int main( int argc, char* argv[] )
{
cmRC_t rc = cmOkRC;
enum
{
kInvalidPoId = kBasePoId,
kActionPoId,
kXmlFileNamePoId,
kDecorateFileNamePoId,
kCsvOutFileNamePoId,
kPgmRsrcFileNamePoId,
kMidiOutFileNamePoId,
kMidiInFileNamePoId,
kSvgOutFileNamePoId,
kStatusOutFileNamePoId,
kTimelineFileNamePoId,
kTimelinePrefixPoId,
kAudioFileNamePoId,
kReportFlagPoId,
kSvgStandAloneFlPoId,
kSvgPanZoomFlPoId,
kBegMeasPoId,
kBegBpmPoId,
kBegMidiUidPoId,
kEndMidiUidPoId
};
enum {
kNoSelId,
kScoreGenSelId,
kScoreFollowSelId,
kMeasGenSelId,
kScoreReportSelId,
kMidiReportSelId,
kTimelineReportSelId,
kAudioReportSelId,
kMidiTrimSelId
};
// initialize the heap check library
bool memDebugFl = 0; //cmDEBUG_FL;
unsigned memGuardByteCnt = memDebugFl ? 8 : 0;
unsigned memAlignByteCnt = 16;
unsigned memFlags = memDebugFl ? kTrackMmFl | kDeferFreeMmFl | kFillUninitMmFl : 0;
cmPgmOptH_t poH = cmPgmOptNullHandle;
const cmChar_t* appTitle = "cmtools";
cmCtx_t ctx;
const cmChar_t* xmlFn = NULL;
const cmChar_t* decFn = NULL;
const cmChar_t* pgmRsrcFn = NULL;
const cmChar_t* csvScoreFn = NULL;
const cmChar_t* midiOutFn = NULL;
const cmChar_t* midiInFn = NULL;
const cmChar_t* audioFn = NULL;
const cmChar_t* svgOutFn = NULL;
const cmChar_t* timelineFn = NULL;
const cmChar_t* timelinePrefix = NULL;
const cmChar_t* rptFn = NULL;
unsigned reportFl = 0;
unsigned svgStandAloneFl = 1;
unsigned svgPanZoomFl = 1;
int begMeasNumb = 0;
int begTempoBPM = 60;
unsigned begMidiUId = cmInvalidId;
unsigned endMidiUId = cmInvalidId;
unsigned actionSelId = kNoSelId;
cmCtxSetup(&ctx,appTitle,print,print,NULL,memGuardByteCnt,memAlignByteCnt,memFlags);
cmMdInitialize( memGuardByteCnt, memAlignByteCnt, memFlags, &ctx.rpt );
cmFsInitialize( &ctx, appTitle);
cmTsInitialize(&ctx );
cmPgmOptInitialize(&ctx, &poH, poBegHelpStr, poEndHelpStr );
cmPgmOptInstallEnum( poH, kActionPoId, 'S', "score_gen", 0, kScoreGenSelId, kNoSelId, &actionSelId, 1,
"Run the score generation tool.","Action selector");
cmPgmOptInstallEnum( poH, kActionPoId, 'F', "score_follow", 0, kScoreFollowSelId, kNoSelId, &actionSelId, 1,
"Run the time line marker generation tool.",NULL);
cmPgmOptInstallEnum( poH, kActionPoId, 'M', "meas_gen", 0, kMeasGenSelId, kNoSelId, &actionSelId, 1,
"Generate perfomance measurements.",NULL);
cmPgmOptInstallEnum( poH, kActionPoId, 'R', "score_report", 0, kScoreReportSelId, kNoSelId, &actionSelId, 1,
"Generate a score file report.",NULL);
cmPgmOptInstallEnum( poH, kActionPoId, 'I', "midi_report", 0, kMidiReportSelId, kNoSelId, &actionSelId, 1,
"Generate a MIDI file report and optional SVG piano roll output.",NULL);
cmPgmOptInstallEnum( poH, kActionPoId, 'E', "timeline_report", 0, kTimelineReportSelId, kNoSelId, &actionSelId, 1,
"Generate a timeline report.",NULL);
cmPgmOptInstallEnum( poH, kActionPoId, 'A', "audio_report", 0, kAudioReportSelId, kNoSelId, &actionSelId, 1,
"Generate an audio file report.",NULL);
cmPgmOptInstallEnum( poH, kActionPoId, 'T', "midi_trim", 0, kMidiTrimSelId, kNoSelId, &actionSelId, 1,
"Trim a MIDI file to create a shortened version.",NULL);
cmPgmOptInstallStr( poH, kXmlFileNamePoId, 'x', "muisic_xml_fn",0, NULL, &xmlFn, 1,
"Name of the input MusicXML file.");
cmPgmOptInstallStr( poH, kDecorateFileNamePoId, 'd', "dec_fn", 0, NULL, &decFn, 1,
"Name of a score decoration file.");
cmPgmOptInstallStr( poH, kCsvOutFileNamePoId, 'c', "score_csv_fn",0, NULL, &csvScoreFn, 1,
"Name of a CSV score file.");
cmPgmOptInstallStr( poH, kPgmRsrcFileNamePoId, 'g', "pgm_rsrc_fn", 0, NULL, &pgmRsrcFn, 1,
"Name of program resource file.");
cmPgmOptInstallStr( poH, kMidiOutFileNamePoId, 'm', "midi_out_fn", 0, NULL, &midiOutFn, 1,
"Name of a MIDI file to generate as output.");
cmPgmOptInstallStr( poH, kMidiInFileNamePoId, 'i', "midi_in_fn", 0, NULL, &midiInFn, 1,
"Name of a MIDI file to generate as output.");
cmPgmOptInstallStr( poH, kSvgOutFileNamePoId, 's', "svg_fn", 0, NULL, &svgOutFn, 1,
"Name of a HTML/SVG file to generate as output.");
cmPgmOptInstallStr( poH, kTimelineFileNamePoId, 't', "timeline_fn", 0, NULL, &timelineFn, 1,
"Name of a timeline to generate as output.");
cmPgmOptInstallStr( poH, kTimelinePrefixPoId, 'l', "tl_prefix", 0, NULL, &timelinePrefix,1,
"Timeline data path prefix.");
cmPgmOptInstallStr( poH, kAudioFileNamePoId, 'a', "audio_fn", 0, NULL, &audioFn, 1,
"Audio file name.");
cmPgmOptInstallStr( poH, kStatusOutFileNamePoId,'r', "report_fn", 0, NULL, &rptFn, 1,
"Name of a status file to generate as output.");
cmPgmOptInstallFlag( poH, kReportFlagPoId, 'f', "debug_fl", 0, 1, &reportFl, 1,
"Print a report of the score following processing." );
cmPgmOptInstallInt( poH, kBegMeasPoId, 'b', "beg_meas", 0, 1, &begMeasNumb, 1,
"The first measure the to be written to the output CSV, MIDI and SVG files." );
cmPgmOptInstallInt( poH, kBegBpmPoId, 'e', "beg_bpm", 0, 0, &begTempoBPM, 1,
"Set to 0 to use the tempo from the score otherwise set to use the tempo at begMeasNumb." );
cmPgmOptInstallFlag( poH, kSvgStandAloneFlPoId, 'n', "svg_stand_alone_fl",0, 1, &svgStandAloneFl, 1,
"Write the SVG file as a stand alone HTML file. Enabled by default." );
cmPgmOptInstallFlag( poH, kSvgPanZoomFlPoId, 'z', "svg_pan_zoom_fl", 0, 1, &svgPanZoomFl, 1,
"Include the pan-zoom control. Enabled by default." );
cmPgmOptInstallUInt( poH, kBegMidiUidPoId, 'w', "beg_midi_uid", 0, 1, &begMidiUId, 1,
"Begin MIDI msg. uuid." );
cmPgmOptInstallUInt( poH, kEndMidiUidPoId, 'y', "end_midi_uid", 0, 1, &endMidiUId, 1,
"End MIDI msg. uuid." );
// parse the command line arguments
if( cmPgmOptParse(poH, argc, argv ) == kOkPoRC )
{
// handle the built-in arg's (e.g. -v,-p,-h)
// (returns false if only built-in options were selected)
if( cmPgmOptHandleBuiltInActions(poH, &ctx.rpt ) == false )
goto errLabel;
switch( actionSelId )
{
case kScoreGenSelId:
rc = score_gen( &ctx, xmlFn, decFn, csvScoreFn, midiOutFn, svgOutFn, reportFl, begMeasNumb, begTempoBPM, svgStandAloneFl, svgPanZoomFl );
break;
case kScoreFollowSelId:
rc = score_follow( &ctx, csvScoreFn, midiInFn, rptFn, svgOutFn, midiOutFn, timelineFn );
break;
case kMeasGenSelId:
rc = meas_gen(&ctx, pgmRsrcFn, rptFn);
break;
case kScoreReportSelId:
rc = score_report(&ctx, csvScoreFn, rptFn );
break;
case kMidiReportSelId:
rc = midi_file_report(&ctx, midiInFn, rptFn, svgOutFn, svgStandAloneFl, svgPanZoomFl );
break;
case kTimelineReportSelId:
rc = timeline_report(&ctx, timelineFn, timelinePrefix, rptFn );
break;
case kAudioReportSelId:
rc = audio_file_report(&ctx, audioFn, rptFn );
break;
case kMidiTrimSelId:
rc = midi_trim(&ctx, midiInFn, begMidiUId, endMidiUId, midiOutFn);
break;
default:
rc = cmErrMsg(&ctx.err, kNoActionIdSelectedCtRC,"No action selector was selected.");
}
}
errLabel:
cmPgmOptFinalize(&poH);
cmTsFinalize();
cmFsFinalize();
cmMdReport( kIgnoreNormalMmFl );
cmMdFinalize();
return rc;
}

2232
src/cmtools/mas.c Normal file

File diff suppressed because it is too large Load Diff