From a1f0e5ed5bb0a0095809c371ea7c4271d91b25be Mon Sep 17 00:00:00 2001 From: rbdannenberg Date: Sat, 18 Sep 2010 21:02:36 +0000 Subject: [PATCH] Extensive changes to improve NoteTrack display and (some) editing, NoteTrack playback via MIDI, and Midi-to-Audio alignment. --- images/Cursors.h | 11 + images/Cursors16/StretchCursor.xpm | 21 + images/Cursors16/StretchLeftCursor.xpm | 21 + images/Cursors16/StretchRightCursor.xpm | 21 + images/Cursors32/StretchCursor.xpm | 37 + images/Cursors32/StretchLeftCursor.xpm | 37 + images/Cursors32/StretchRightCursor.xpm | 37 + lib-src/libnyquist/nyx.c | 23 +- lib-src/libscorealign/README.txt | 22 +- lib-src/libscorealign/ScoreAlignParams.h | 29 + lib-src/libscorealign/audiomixerreader.cpp | 3 +- lib-src/libscorealign/comp_chroma.cpp | 99 +- lib-src/libscorealign/comp_chroma.h | 27 +- lib-src/libscorealign/curvefit.cpp | 90 +- lib-src/libscorealign/fft3/FFT3.cpp | 6 +- lib-src/libscorealign/gen_chroma.cpp | 192 +- lib-src/libscorealign/gen_chroma.h | 9 +- lib-src/libscorealign/hillclimb.cpp | 34 +- lib-src/libscorealign/hillclimb.h | 10 +- lib-src/libscorealign/sautils.h | 1 + lib-src/libscorealign/scorealign-glue.cpp | 38 +- lib-src/libscorealign/scorealign-glue.h | 7 +- lib-src/libscorealign/scorealign.cpp | 499 +++-- lib-src/libscorealign/scorealign.h | 177 +- lib-src/portmidi/CHANGELOG.txt | 24 + lib-src/portmidi/README.txt | 4 + lib-src/portmidi/pm_cl/cffi-portmidi.lisp | 3 - lib-src/portmidi/pm_common/pminternal.h | 16 +- lib-src/portmidi/pm_common/pmutil.c | 69 +- lib-src/portmidi/pm_common/pmutil.h | 16 +- lib-src/portmidi/pm_common/portmidi.c | 79 +- lib-src/portmidi/pm_common/portmidi.h | 110 +- lib-src/portmidi/pm_linux/README_LINUX.txt | 80 +- lib-src/portmidi/pm_linux/pmlinux.c | 23 +- lib-src/portmidi/pm_linux/pmlinuxalsa.c | 9 +- lib-src/portmidi/pm_mac/README_MAC.txt | 149 +- lib-src/portmidi/pm_mac/finddefault.c | 2 + .../pm_mac/pm_mac.xcodeproj/project.pbxproj | 1746 ++--------------- lib-src/portmidi/pm_mac/pmmacosxcm.c | 94 +- lib-src/portmidi/pm_mac/readbinaryplist.c | 68 +- lib-src/portmidi/pm_mac/readbinaryplist.h | 6 +- lib-src/portmidi/pm_test/latency.c | 16 +- lib-src/portmidi/pm_test/midiclock.c | 19 +- lib-src/portmidi/pm_test/midithread.c | 29 +- lib-src/portmidi/pm_test/midithru.c | 6 +- lib-src/portmidi/pm_test/mm.c | 33 +- lib-src/portmidi/pm_test/qtest.c | 2 +- lib-src/portmidi/pm_test/sysex.c | 27 +- lib-src/portmidi/pm_test/test.c | 55 +- lib-src/portmidi/pm_win/README_WIN.txt | 107 +- lib-src/portmidi/pm_win/pmwinmm.c | 23 +- lib-src/portmidi/porttime/porttime.h | 32 +- lib-src/portmidi/porttime/ptlinux.c | 2 +- lib-src/portmidi/porttime/ptmacosx_cf.c | 2 +- lib-src/portmidi/porttime/ptmacosx_mach.c | 5 +- lib-src/portmidi/porttime/ptwinmm.c | 10 +- lib-src/portsmf/algrd_internal.h | 3 +- lib-src/portsmf/allegro.cpp | 670 ++++++- lib-src/portsmf/allegro.h | 72 +- lib-src/portsmf/allegrord.cpp | 65 +- lib-src/portsmf/allegrosmfrd.cpp | 2 +- lib-src/portsmf/allegrosmfwr.cpp | 3 +- lib-src/portsmf/allegrowr.cpp | 26 +- lib-src/portsmf/strparse.cpp | 2 +- src/AudacityApp.cpp | 7 + src/AudioIO.cpp | 1045 +++++----- src/AudioIO.h | 91 +- src/Experimental.h | 4 +- src/Menus.cpp | 229 ++- src/MixerBoard.cpp | 182 +- src/MixerBoard.h | 32 +- src/NoteTrack.cpp | 488 ++++- src/NoteTrack.h | 121 +- src/Project.cpp | 2 + src/Snap.cpp | 9 +- src/Track.cpp | 21 +- src/Track.h | 2 +- src/TrackArtist.cpp | 442 +++-- src/TrackArtist.h | 10 +- src/TrackPanel.cpp | 592 +++++- src/TrackPanel.h | 42 + src/UndoManager.cpp | 3 + src/effects/Effect.cpp | 9 +- src/effects/ScoreAlignDialog.cpp | 264 +++ src/effects/ScoreAlignDialog.h | 104 + src/effects/nyquist/Nyquist.cpp | 10 +- src/import/ImportMIDI.cpp | 34 +- src/prefs/ImportExportPrefs.cpp | 14 + src/prefs/MidiIOPrefs.cpp | 170 +- src/prefs/MidiIOPrefs.h | 2 + src/toolbars/ControlToolBar.cpp | 6 +- src/widgets/ASlider.cpp | 121 +- src/widgets/ASlider.h | 17 +- src/xml/XMLTagHandler.cpp | 7 + src/xml/XMLTagHandler.h | 1 + win/Projects/Audacity/Audacity.vcproj | 4 + 96 files changed, 5679 insertions(+), 3566 deletions(-) create mode 100644 images/Cursors16/StretchCursor.xpm create mode 100644 images/Cursors16/StretchLeftCursor.xpm create mode 100644 images/Cursors16/StretchRightCursor.xpm create mode 100644 images/Cursors32/StretchCursor.xpm create mode 100644 images/Cursors32/StretchLeftCursor.xpm create mode 100644 images/Cursors32/StretchRightCursor.xpm create mode 100644 lib-src/libscorealign/ScoreAlignParams.h create mode 100644 src/effects/ScoreAlignDialog.cpp create mode 100644 src/effects/ScoreAlignDialog.h diff --git a/images/Cursors.h b/images/Cursors.h index f5706b015..89ab54feb 100644 --- a/images/Cursors.h +++ b/images/Cursors.h @@ -26,6 +26,11 @@ #include "Cursors32/ZoomOutCursor.xpm" #include "Cursors32/LabelCursorLeft.xpm" #include "Cursors32/LabelCursorRight.xpm" +#ifdef USE_MIDI +#include "Cursors32/StretchCursor.xpm" +#include "Cursors32/StretchLeftCursor.xpm" +#include "Cursors32/StretchRightCursor.xpm" +#endif #else @@ -38,4 +43,10 @@ #include "Cursors16/ZoomOutCursor.xpm" #include "Cursors16/LabelCursorLeft.xpm" #include "Cursors16/LabelCursorRight.xpm" +#ifdef USE_MIDI +#include "Cursors16/StretchCursor.xpm" +#include "Cursors16/StretchLeftCursor.xpm" +#include "Cursors16/StretchRightCursor.xpm" +#endif + #endif diff --git a/images/Cursors16/StretchCursor.xpm b/images/Cursors16/StretchCursor.xpm new file mode 100644 index 000000000..6800df7a5 --- /dev/null +++ b/images/Cursors16/StretchCursor.xpm @@ -0,0 +1,21 @@ +static const char * StretchCursorXpm[] = { +"16 16 3 1", +". c #FF0000", // mask color = RGB:255,0,0 +"# c #000000", +"+ c #FFFFFF", +"................", +"........#.......", +"........#.......", +"........#.......", +"....#########...", +"....#...#...#...", +"....#...#...#...", +"....#...#...#...", +".###############", +"....#...#...#...", +"....#...#...#...", +"....#...#...#...", +"....#########...", +"........#.......", +"........#.......", +"........#......."}; diff --git a/images/Cursors16/StretchLeftCursor.xpm b/images/Cursors16/StretchLeftCursor.xpm new file mode 100644 index 000000000..fb7383558 --- /dev/null +++ b/images/Cursors16/StretchLeftCursor.xpm @@ -0,0 +1,21 @@ +static const char * StretchLeftCursorXpm[] = { +"16 16 3 1", +". c #FF0000", // mask color = RGB:255,0,0 +"# c #000000", +"+ c #FFFFFF", +"................", +"........#.......", +"........#.......", +"........#.......", +"........#####...", +"........#...#...", +"........#...#...", +"........#...#...", +"........########", +"........#...#...", +"........#...#...", +"........#...#...", +"........#####...", +"........#.......", +"........#.......", +"........#......."}; diff --git a/images/Cursors16/StretchRightCursor.xpm b/images/Cursors16/StretchRightCursor.xpm new file mode 100644 index 000000000..768a03b49 --- /dev/null +++ b/images/Cursors16/StretchRightCursor.xpm @@ -0,0 +1,21 @@ +static const char * StretchRightCursorXpm[] = { +"16 16 3 1", +". c #FF0000", // mask color = RGB:255,0,0 +"# c #000000", +"+ c #FFFFFF", +"................", +"........#.......", +"........#.......", +"........#.......", +"....#####.......", +"....#...#.......", +"....#...#.......", +"....#...#.......", +".########.......", +"....#...#.......", +"....#...#.......", +"....#...#.......", +"....#####.......", +"........#.......", +"........#.......", +"........#......."}; diff --git a/images/Cursors32/StretchCursor.xpm b/images/Cursors32/StretchCursor.xpm new file mode 100644 index 000000000..c811be5cf --- /dev/null +++ b/images/Cursors32/StretchCursor.xpm @@ -0,0 +1,37 @@ +static const char * StretchCursorXpm[] = { +"32 32 3 1", +". c #FF0000", // mask color = RGB:255,0,0 +"# c #000000", +"+ c #FFFFFF", +"................................", +"................................", +"................................", +"...............+++..............", +"...............+#+..............", +"...............+#+..............", +"...............+#+..............", +".......+++++++++#+++++++++......", +".......+#################+......", +".......+#+++++++#+++++++#+......", +".......+#+.....+#+.....+#+......", +".......+#+.....+#+.....+#+......", +".......+#+.....+#+.....+#+......", +".......+#+.....+#+.....+#+......", +".......+#+.....+#+.....+#+......", +"...+++++#+++++++#+++++++##++++..", +"...+#########################+..", +"...+++++#+++++++#+++++++#+++++..", +".......+#+.....+#+.....+#+......", +".......+#+.....+#+.....+#+......", +".......+#+.....+#+.....+#+......", +".......+#+.....+#+.....+#+......", +".......+#+.....+#+.....+#+......", +".......+#+++++++#+++++++#+......", +".......+#################+......", +".......+++++++++#+++++++++......", +"...............+#+..............", +"...............+#+..............", +"...............+#+..............", +"...............+++..............", +"................................", +"................................"}; diff --git a/images/Cursors32/StretchLeftCursor.xpm b/images/Cursors32/StretchLeftCursor.xpm new file mode 100644 index 000000000..462d07a8b --- /dev/null +++ b/images/Cursors32/StretchLeftCursor.xpm @@ -0,0 +1,37 @@ +static const char * StretchLeftCursorXpm[] = { +"32 32 3 1", +". c #FF0000", // mask color = RGB:255,0,0 +"# c #000000", +"+ c #FFFFFF", +"................................", +"................................", +"................................", +"...............+++..............", +"...............+#+..............", +"...............+#+..............", +"...............+#+..............", +"...............+#+++++++++......", +"...............+#########+......", +"...............+#+++++++#+......", +"...............+#+.....+#+......", +"...............+#+.....+#+......", +"...............+#+.....+#+......", +"...............+#+.....+#+......", +"...............+#+.....+#+......", +"...............+#+++++++##++++..", +"...............+#############+..", +"...............+#+++++++#+++++..", +"...............+#+.....+#+......", +"...............+#+.....+#+......", +"...............+#+.....+#+......", +"...............+#+.....+#+......", +"...............+#+.....+#+......", +"...............+#+++++++#+......", +"...............+#########+......", +"...............+#+++++++++......", +"...............+#+..............", +"...............+#+..............", +"...............+#+..............", +"...............+++..............", +"................................", +"................................"}; diff --git a/images/Cursors32/StretchRightCursor.xpm b/images/Cursors32/StretchRightCursor.xpm new file mode 100644 index 000000000..d752ef370 --- /dev/null +++ b/images/Cursors32/StretchRightCursor.xpm @@ -0,0 +1,37 @@ +static const char * StretchRightCursorXpm[] = { +"32 32 3 1", +". c #FF0000", // mask color = RGB:255,0,0 +"# c #000000", +"+ c #FFFFFF", +"................................", +"................................", +"................................", +"...............+++..............", +"...............+#+..............", +"...............+#+..............", +"...............+#+..............", +".......+++++++++#+..............", +".......+#########+..............", +".......+#+++++++#+..............", +".......+#+.....+#+..............", +".......+#+.....+#+..............", +".......+#+.....+#+..............", +".......+#+.....+#+..............", +".......+#+.....+#+..............", +"...+++++#+++++++#+..............", +"...+#############+..............", +"...+++++#+++++++#+..............", +".......+#+.....+#+..............", +".......+#+.....+#+..............", +".......+#+.....+#+..............", +".......+#+.....+#+..............", +".......+#+.....+#+..............", +".......+#+++++++#+..............", +".......+#########+..............", +".......+++++++++#+..............", +"...............+#+..............", +"...............+#+..............", +"...............+#+..............", +"...............+++..............", +"................................", +"................................"}; diff --git a/lib-src/libnyquist/nyx.c b/lib-src/libnyquist/nyx.c index a91007c66..0c8581e49 100644 --- a/lib-src/libnyquist/nyx.c +++ b/lib-src/libnyquist/nyx.c @@ -354,6 +354,7 @@ LOCAL void nyx_save_obarray() LOCAL void nyx_restore_obarray() { LVAL obvec = getvalue(obarray); + LVAL sscratch = xlenter("*SCRATCH*"); // one-time lookup int i; // Scan all obarray vectors @@ -391,15 +392,21 @@ LOCAL void nyx_restore_obarray() } } - // If we didn't find the symbol in the original obarray, then it must've - // been added and must be removed from the current obarray. + // If we didn't find the symbol in the original obarray, then it + // must've been added and must be removed from the current obarray. + // Exception: if the new symbol is a property symbol of *scratch*, + // then allow the symbol to stay; otherwise, property lookups will + // fail. if (scon == NULL) { - if (last) { - rplacd(last, cdr(dcon)); - } - else { - setelement(obvec, i, cdr(dcon)); - } + // check property list of scratch + if (findprop(sscratch, dsym) == NIL) { + if (last) { + rplacd(last, cdr(dcon)); + } + else { + setelement(obvec, i, cdr(dcon)); + } + } // otherwise, keep new property symbol } // Must track the last dcon for symbol removal diff --git a/lib-src/libscorealign/README.txt b/lib-src/libscorealign/README.txt index 45b9d66a7..2ba4c7502 100644 --- a/lib-src/libscorealign/README.txt +++ b/lib-src/libscorealign/README.txt @@ -15,7 +15,12 @@ are estimated directly from pitch data without synthesis. A similarity matrix is constructed and dynamic programming finds the lowest-cost path through the matrix. -(some more details should be added here about handling boundaries) +The alignment can optionally skip the initial silence and final silence +frames in both files. The "best" path matches from the beginning times +(with or without silence) to the end of either sequence but not +necessarily to the end of both. In other words, the match will match +all of the first file to an initial segment of the second, or it will +match all of the second to an initial segment of the first. Output includes a map from one version to the other. If one file is MIDI, output also includes (1) an estimated transcript in ASCII format with time, @@ -32,10 +37,15 @@ For Windows, open score-align.vcproj (probably out of date now -- please Command line parameters: -scorealign [- [ ]] +scorealign [- [ + ]] [] specifying only simply transcribes MIDI in to transcription.txt. Otherwise, align and . + Flags are all listed together, e.g. -hwrstm, followed by filenames + and arguments corresponding to the flags in the order the flags are + given. Do not try something like "-h 0.1 -w 0.25" Instead, use + "-hw 0.1 0.25". The flags are: -h 0.25 indicates a frame period of 0.25 seconds -w 0.25 indicates a window size of 0.25 seconds. -r indicates filename to write raw alignment path to (default path.data) @@ -44,6 +54,8 @@ scorealign [- [ ]] (default is transcription.txt) -m is filename to write the time aligned midi file (default is midi.mid) -b is filename to write the time aligned beat times (default is beatmap.txt) + -i is filename to write an image of the distance matrix + (default is distance.pnm) -o 2.0 indicates a smoothing window of 2.0s -p 3.0 means pre-smooth with a 3s window -x 6.0 indicates 6s line segment approximation @@ -80,9 +92,9 @@ linear regression values. Next, a hill-climbing search is performed to minimize the total distance along the path. This is like dynamic programming except that each line spans many frames, so the resulting path is forced to be fairly straight. Linear interpolation is used to estimate chroma distance -since the lines do always pass through integer frame locations. This approach -is probably good when the audio is known to have a steady tempo or be -performed with tempo changes that match those in the midi file. +since the lines do not always pass through integer frame locations. This +approach is probably good when the audio is known to have a steady tempo or +be performed with tempo changes that match those in the midi file. Some notes on the software architecture of scorealign: diff --git a/lib-src/libscorealign/ScoreAlignParams.h b/lib-src/libscorealign/ScoreAlignParams.h new file mode 100644 index 000000000..c0f64d73a --- /dev/null +++ b/lib-src/libscorealign/ScoreAlignParams.h @@ -0,0 +1,29 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + ScoreAlignParams.h + +**********************************************************************/ + +#ifndef __AUDACITY_SCORE_ALIGN_PARAMS__ +#define __AUDACITY_SCORE_ALIGN_PARAMS__ + +struct ScoreAlignParams { + double mFramePeriod; + double mWindowSize; + double mSilenceThreshold; + double mForceFinalAlignment; + double mIgnoreSilence; + double mPresmoothTime; + double mLineTime; + double mSmoothTime; + // information returned from score alignment: + int mStatus; // wxID_OK or not? + double mAudioStart; + double mAudioEnd; + double mMidiStart; + double mMidiEnd; +}; + +#endif diff --git a/lib-src/libscorealign/audiomixerreader.cpp b/lib-src/libscorealign/audiomixerreader.cpp index 6091df9d8..fd771fb60 100644 --- a/lib-src/libscorealign/audiomixerreader.cpp +++ b/lib-src/libscorealign/audiomixerreader.cpp @@ -6,6 +6,7 @@ #include "stdlib.h" #include "audioreader.h" #include "allegro.h" +#include "scorealign.h" #include "scorealign-glue.h" #include "audiomixerreader.h" @@ -26,7 +27,7 @@ Audio_mixer_reader::Audio_mixer_reader(void *mixer_, index = 0; channels = chans; sample_rate = srate; - total_frames = end_time * srate + 0.5 /* for rounding */; + total_frames = (long) (end_time * srate + 0.5 /* for rounding */); } diff --git a/lib-src/libscorealign/comp_chroma.cpp b/lib-src/libscorealign/comp_chroma.cpp index d084d0180..f5cc62063 100644 --- a/lib-src/libscorealign/comp_chroma.cpp +++ b/lib-src/libscorealign/comp_chroma.cpp @@ -1,64 +1,15 @@ +#include #include +#include #include "allegro.h" #include "audioreader.h" +#include "scorealign.h" #include "gen_chroma.h" #include "comp_chroma.h" - using namespace std; -/* NORM_CHROMA - * - * This function normalizes the chroma for each frame of the - * chrom_energy to mean 0 and std. dev. 1. But if this is a - * "silent frame", set the 13th element to 1. - */ -void norm_chroma( int len, float *chrom_energy ) { - - float avg = 0; - float dev = 0; - float sum = 0; - - for( int i = 0; i < len; i++ ) { - - /* Calculate avg for this frame */ - sum = 0; - for ( int j = 0; j < 12; j++ ) - sum += AREF2(chrom_energy, i, j); - avg = sum / 12.0; - - /* Silence detection: */ - float silence = 0.0F; - if (avg < SILENCE_THRESHOLD) { /* assume silent */ - silence = 1.0F; - } - AREF2(chrom_energy, i, 12) = silence; - - // printf("avg at %g: %g\n", i * 0.25, avg); - - /* Normalize this frame to avg. 0 */ - for ( int j = 0; j < 12; j++ ) - AREF2(chrom_energy, i, j) -= avg; - - /* Calculate std. dev. for this frame */ - sum = 0; - for ( int j = 0; j < 12; j++ ) { - float x = AREF2(chrom_energy, i, j); - sum += x * x; - } - dev = sqrt( sum / 12.0 ); - if (dev == 0.0) dev = 1.0F; /* don't divide by zero */ - - /* Normalize this frame to std. dev. 1*/ - for ( int j = 0; j < 12; j++ ) - AREF2(chrom_energy, i, j) /= dev; - } -} - -/* Returns the minimum of two values */ -double min2( double x, double y ) { - return (x < y ? x : y); -} +#define SILENCE_DISTANCE 16.0 /* GEN_DIST * @@ -66,27 +17,23 @@ double min2( double x, double y ) { * and j in two chroma vectors for use with dynamic time warping of * the chroma vectors. */ -float gen_dist( int i, int j, float *chrom_energy1, - float *chrom_energy2 ) { - - float sum = 0; - float MAX = 12.0; - - if (AREF2(chrom_energy1, i, CHROMA_BIN_COUNT) != - AREF2(chrom_energy2, j, CHROMA_BIN_COUNT)) { - //printf("gd%g ", SILENCE_DISTANCE); // print result - return SILENCE_DISTANCE; - } - /* Determine the distance between these vectors - chroma1[i] and chroma2[j] to return */ - for (int k = 0; k < 12; k++) { - float x = AREF2(chrom_energy1, i, k); - float y = AREF2(chrom_energy2, j, k); - float diff = x - y; - - sum += diff*diff ; - } - sum = min2( sqrt( sum ), MAX ); - //printf("gd%g ", sum); // print the result - return sum; +float Scorealign::gen_dist(int i, int j) +{ + const float MAX = 12.0; + assert(i < file0_frames); + assert(j < file1_frames); + float *cv0 = AREF1(chrom_energy0, i); + float *cv1 = AREF1(chrom_energy1, j); + if (cv0[CHROMA_BIN_COUNT] != cv1[CHROMA_BIN_COUNT]) { + // silent frames are a (large) constant distance from non-silent frames + return SILENCE_DISTANCE; + } + /* calculate the Euclidean distance between these vectors */ + float sum = 0; + for (int k = 0; k < CHROMA_BIN_COUNT; k++) { + float diff = cv0[k] - cv1[k]; + sum += diff * diff ; + } + // place a ceiling (MAX) on distance + return min(sqrt(sum), MAX); } diff --git a/lib-src/libscorealign/comp_chroma.h b/lib-src/libscorealign/comp_chroma.h index 66a0168b8..ffe011cff 100644 --- a/lib-src/libscorealign/comp_chroma.h +++ b/lib-src/libscorealign/comp_chroma.h @@ -1,24 +1,7 @@ -#include -#include -#include -#include -#include +//#include +//#include +//#include +//#include +//#include -#define SILENCE_THRESHOLD 0.001 -#define SILENCE_DISTANCE 16.0 -/* NORM_CHROMA - * - * This function normalizes the chroma for each frame of the - * chrom_energy to mean 0 and std. dev. 1. - */ -void norm_chroma( int len, float *chrom_energy ); - -/* GEN_DIST - * - * This function generates the Euclidean distance for points i - * and j in two chroma vectors for use with dynamic time warping of - * the chroma vectors. - */ -float gen_dist(int i, int j, float *chrom_energy1, - float *chrom_energy2 ); diff --git a/lib-src/libscorealign/curvefit.cpp b/lib-src/libscorealign/curvefit.cpp index 6d0a8d355..47c10750b 100644 --- a/lib-src/libscorealign/curvefit.cpp +++ b/lib-src/libscorealign/curvefit.cpp @@ -8,6 +8,7 @@ */ #include "assert.h" +#include #include "comp_chroma.h" #include "sautils.h" // the following are needed to get Scorealign @@ -48,9 +49,15 @@ void save_path(char *filename); class Curvefit : public Hillclimb { public: - Curvefit(Scorealign *sa_, bool verbose_) { sa = sa_; verbose = verbose_; } + Curvefit(Scorealign *sa_, bool verbose_) { + sa = sa_; + verbose = verbose_; + p1_cache = p2_cache = d_cache = x = NULL; + } + ~Curvefit(); virtual double evaluate(); void setup(int n); + void set_step_size(double ss); double *get_x() { return x; } private: Scorealign *sa; @@ -101,35 +108,41 @@ void Curvefit::setup(int segments) // number of parameters is greater than segments because the left // col of segment i is parameter i, so the right col of // the last segment == parameter[segments]. - n = segments + 1; - parameters = ALLOC(double, n); + Hillclimb::setup(segments + 1); p1_cache = ALLOC(double, n); p2_cache = ALLOC(double, n); d_cache = ALLOC(double, n); x = ALLOC(double, n); - step_size = ALLOC(double, n); - min_param = ALLOC(double, n); - max_param = ALLOC(double, n); int i; // ideal frames per segment - float seg_length = ((float) (sa->file1_frames - 1)) / segments; + float seg_length = ((float) (sa->last_x - sa->first_x)) / segments; for (i = 0; i < n; i++) { // initialize cache keys to garbage p1_cache[i] = p2_cache[i] = -999999.99; // initialize x values - x[i] = ROUND(i * seg_length); + x[i] = ROUND(sa->first_x + i * seg_length); // now initialize parameters based on pathx/pathy/time_map // time_map has y values for each x parameters[i] = sa->time_map[(int) x[i]]; + assert(parameters[i] >= 0); if (verbose) printf("initial x[%d] = %g, parameters[%d] = %g\n", i, x[i], i, parameters[i]); step_size[i] = 0.5; min_param[i] = 0; - max_param[i] = sa->file2_frames - 1; + max_param[i] = sa->last_y; } } +Curvefit::~Curvefit() +{ + if (p1_cache) FREE(p1_cache); + if (p2_cache) FREE(p2_cache); + if (d_cache) FREE(d_cache); + if (x) FREE(x); +} + + // distance_rc -- look up or compute distance between chroma vectors // at row, col in similarity matrix // @@ -142,7 +155,7 @@ void Curvefit::setup(int segments) // Since distance can be computed relatively quickly, a better plan // would be to cache values along the path. Here's a brief design // (for the future, assuming this routine is actually a hot spot): -// Allocate a matrix that is, say, 20 x file1_frames to contain distances +// Allocate a matrix that is, say, 20 x file0_frames to contain distances // that are +/- 10 frames from the path. Initialize cells to -1. // Allocate an array of integer offsets of size file1_frames. // Fill in the integer offsets with the column number (pathy) value of @@ -157,7 +170,10 @@ void Curvefit::setup(int segments) // double Curvefit::distance_rc(int row, int col) { - return gen_dist(row, col, sa->chrom_energy1, sa->chrom_energy2); + double dist = sa->gen_dist(row, col); + if (dist > 20) // DEBUGGING + printf("internal error"); + return dist; } @@ -190,6 +206,7 @@ double Curvefit::compute_dist(int i) double dx = x2 - x1, dy = y2 - y1; double sum = 0; int n; + assert(x1 >= 0 && x2 >= 0 && y1 >= 0 && y2 >= 0); if (dx > dy) { // evauate at each x n = (int) dx; for (int x = (int) x1; x < x2; x++) { @@ -204,14 +221,52 @@ double Curvefit::compute_dist(int i) } } // normalize using line length: sum/n is average distance. Multiply - // avg. distance (cost per unit length) by length to get total cost: + // avg. distance (cost per unit length) by length to get total cost. + // Note: this gives an advantage to direct diagonal paths without bends + // because longer path lengths result in higher total cost. This also + // gives heigher weight to longer segments, although all segments are + // about the same length. double rslt = sqrt(dx*dx + dy*dy) * sum / n; // printf("compute_dist %d: x1 %g y1 %g x2 %g y2 %g sum %g rslt %g\n", // i, x1, y1, x2, y2, sum, rslt); + if (rslt < 0 || rslt > 20 * n) { // DEBUGGING + printf("internal error"); + } return rslt; } - + +void Curvefit::set_step_size(double ss) +{ + for (int i = 0; i < n; i++) { + step_size[i] = ss; + } +} + + +static long curvefit_iterations; + +// This is a callback from Hillclimb::optimize to report progress +// We can't know percentage completion because we don't know how +// many iterations it will take to converge, so we just report +// iterations. The SAProgress class assumes some number based +// on experience. +// +// Normally, the iterations parameter is a good indicator of work +// expended so far, but since we call Hillclimb::optimize twice +// (second time with a finer grid to search), ignore iterations +// and use curvefit_iterations, a global counter, instead. This +// assumes that curvefit_progress is called once for each iteration. +// +void curvefit_progress(void *cookie, int iterations, double best) +{ + Scorealign *sa = (Scorealign *) cookie; + if (sa->progress) { + sa->progress->set_smoothing_progress(++curvefit_iterations); + } +} + + void curve_fitting(Scorealign *sa, bool verbose) { if (verbose) @@ -220,12 +275,17 @@ void curve_fitting(Scorealign *sa, bool verbose) Curvefit curvefit(sa, verbose); double *parameters; double *x; + curvefit_iterations = 0; // how many segments? About total time / line_time: int segments = - (int) (0.5 + (sa->actual_frame_period_1 * sa->file1_frames) / + (int) (0.5 + (sa->actual_frame_period_0 * (sa->last_x - sa->first_x)) / sa->line_time); curvefit.setup(segments); - curvefit.optimize(); + curvefit.optimize(&curvefit_progress, sa); + // further optimization with smaller step sizes: + // this step size will interpolate 0.25s frames down to 10ms + curvefit.set_step_size(0.04); + curvefit.optimize(&curvefit_progress, sa); parameters = curvefit.get_parameters(); x = curvefit.get_x(); // now, rewrite pathx and pathy according to segments diff --git a/lib-src/libscorealign/fft3/FFT3.cpp b/lib-src/libscorealign/fft3/FFT3.cpp index d60cd1b01..10fa53be4 100644 --- a/lib-src/libscorealign/fft3/FFT3.cpp +++ b/lib-src/libscorealign/fft3/FFT3.cpp @@ -111,7 +111,7 @@ void FFT3(int NumSamples, int i, j, k, n; int BlockSize, BlockEnd; - float angle_numerator = 2.0 * M_PI; + float angle_numerator = float(2.0 * M_PI); float tr, ti; /* temp real, temp imaginary */ if (!IsPowerOfTwo(NumSamples)) { @@ -224,7 +224,7 @@ void RealFFT3(int NumSamples, float *RealIn, float *RealOut, float *ImagOut) int Half = NumSamples / 2; int i; - float theta = M_PI / Half; + float theta = float(M_PI / Half); float *tmpReal = (float *) alloca(sizeof(float) * Half); float *tmpImag = (float *) alloca(sizeof(float) * Half); @@ -289,7 +289,7 @@ void PowerSpectrum3(int NumSamples, float *In, float *Out) int Half = NumSamples / 2; int i; - float theta = M_PI / Half; + float theta = float(M_PI / Half); float *tmpReal = (float *) alloca(sizeof(float) * Half);; float *tmpImag = (float *) alloca(sizeof(float) * Half); diff --git a/lib-src/libscorealign/gen_chroma.cpp b/lib-src/libscorealign/gen_chroma.cpp index e7701b43c..842b0283f 100644 --- a/lib-src/libscorealign/gen_chroma.cpp +++ b/lib-src/libscorealign/gen_chroma.cpp @@ -30,7 +30,6 @@ using namespace std; // each row is one chroma vector, // data is stored as an array of chroma vectors: // vector 1, vector 2, ... -#define CHROM(row, column) AREF2((*chrom_energy), row, column) float hz_to_step(float hz) { @@ -40,21 +39,19 @@ float hz_to_step(float hz) /* GEN_MAGNITUDE given the real and imaginary portions of a complex FFT function, compute the magnitude of the fft bin. - given input of 2 arrays (inR and inI) of length n, takes the ith element - from each, squares them, sums them, takes the square root of the sum and - puts the output into the ith position in the array out. NOTE: out should be length n */ -void gen_Magnitude(float* inR,float* inI, int low, int hi, float* out) +void gen_Magnitude(float* inR, float* inI, int low, int hi, float* out) { int i; + for (i = low; i < hi; i++) { float magVal = sqrt(inR[i] * inR[i] + inI[i] * inI[i]); //printf(" %d: sqrt(%g^2+%g^2)=%g\n",i,inR[i],inI[i+1],magVal); out[i]= magVal; #ifdef SA_VERBOSE - if (i == 1000) printf("gen_Magnitude: %d %g\n", i, magVal); + if (i == 1000) fprintf(dbf, "gen_Magnitude: %d %g\n", i, magVal); #endif } } @@ -116,17 +113,12 @@ int min_Bin_Num(float* bins, int numBins){ applies the hamming function to each sample. n specifies the length of in and out. */ -void gen_Hamming(float* in, int n, float* out) +void gen_Hamming(float* h, int n) { - int k = 0; - for(k = 0; k < n; k++) { - float internalValue = 2.0 * M_PI * k * (1.0 / (n - 1)); - float cosValue = cos(internalValue); - float hammingValue = 0.54F + (-0.46F * cosValue); -#ifdef SA_VERBOSE - if (k == 1000) printf("Hamming %g\n", hammingValue); -#endif - out[k] = hammingValue * in[k]; + int k; + for (k = 0; k < n; k++) { + float cos_value = (float) cos(2.0 * M_PI * k * (1.0 / n)); + h[k] = 0.54F + (-0.46F * cos_value); } } @@ -142,6 +134,36 @@ int nextPowerOf2(int n) } +// normalize a chroma vector (from audio or midi) to have +// mean of 0 and std. dev. of 1 +// +static void normalize(float *cv) +{ + float avg = 0; + + for (int i = 0; i < CHROMA_BIN_COUNT; i++) { + avg += cv[i]; + } + avg /= CHROMA_BIN_COUNT; + + /* Normalize this frame to avg. 0 */ + for (int i = 0; i < CHROMA_BIN_COUNT; i++) + cv[i] -= avg; + + /* Calculate std. dev. for this frame */ + float sum = 0; + for (int i = 0; i < CHROMA_BIN_COUNT; i++) { + float x = cv[i]; + sum += x * x; + } + float dev = sqrt(sum / CHROMA_BIN_COUNT); + if (dev == 0.0) dev = 1.0F; /* don't divide by zero */ + + /* Normalize this frame to std. dev. 1*/ + for (int i = 0; i < CHROMA_BIN_COUNT; i++) cv[i] /= dev; +} + + /* GEN_CHROMA_AUDIO -- compute chroma for an audio file */ /* @@ -153,8 +175,8 @@ int nextPowerOf2(int n) (aka the length of the 1st dimention of chrom_energy) */ int Scorealign::gen_chroma_audio(Audio_reader &reader, int hcutoff, - int lcutoff, float **chrom_energy, float *actual_frame_period, - int id, bool verbose) + int lcutoff, float **chrom_energy, double *actual_frame_period, + int id) { int i; double sample_rate = reader.get_sample_rate(); @@ -165,9 +187,12 @@ int Scorealign::gen_chroma_audio(Audio_reader &reader, int hcutoff, printf ("==============FILE %d====================\n", id); reader.print_info(); } - // this seems like a poor way to set actual_frame_period_1 or _2 in +#if DEBUG_LOG + fprintf(dbf, "******** BEGIN AUDIO CHROMA COMPUTATION *********\n"); +#endif + // this seems like a poor way to set actual_frame_period_0 or _1 in // the Scorealign object, but I'm not sure what would be better: - *actual_frame_period = reader.actual_frame_period; + *actual_frame_period = float(reader.actual_frame_period); for (i = 0; i < CHROMA_BIN_COUNT; i++) { reg11[i] = -999; @@ -230,7 +255,7 @@ int Scorealign::gen_chroma_audio(Audio_reader &reader, int hcutoff, // sample_rate / full_data_size); double freq = low_bin * sample_rate / full_data_size; for (i = low_bin; i < high_bin; i++) { - float raw_bin = hz_to_step(freq); + float raw_bin = hz_to_step(float(freq)); int round_bin = (int) (raw_bin + 0.5F); int mod_bin = round_bin % 12; bin_map[i] = mod_bin; @@ -238,24 +263,35 @@ int Scorealign::gen_chroma_audio(Audio_reader &reader, int hcutoff, } // printf("BIN_COUNT is !!!!!!!!!!!!! %d\n",CHROMA_BIN_COUNT); + // create Hamming window data + float *hamming = ALLOC(float, reader.samples_per_frame); + gen_Hamming(hamming, reader.samples_per_frame); + while (reader.read_window(full_data)) { //fill out array with 0's till next power of 2 #ifdef SA_VERBOSE - printf("samples_per_frame %d sample %g\n", reader.samples_per_frame, - full_data[0]); + fprintf(dbf, "samples_per_frame %d sample %g\n", + reader.samples_per_frame, full_data[0]); #endif for (i = reader.samples_per_frame; i < full_data_size; i++) full_data[i] = 0; -#ifdef AS_VERBOSE - printf("preFFT: full_data[1000] %g\n", full_data[1000]); +#ifdef SA_VERBOSE + fprintf(dbf, "preFFT: full_data[1000] %g\n", full_data[1000]); #endif - //the data from the wave file, each point mult by a hamming value - gen_Hamming(full_data, full_data_size, full_data); + // compute the RMS, then apply the Hamming window to the data + float rms = 0.0f; + for (i = 0; i < reader.samples_per_frame; i++) { + float x = full_data[i]; + rms += x * x; + full_data[i] = x * hamming[i]; + } + rms = sqrt(rms / reader.samples_per_frame); #ifdef SA_VERBOSE - printf("preFFT: hammingData[1000] %g\n", full_data[1000]); + fprintf(dbf, "preFFT: hammingData[1000] %g\n", + full_data[1000]); #endif FFT3(full_data_size, 0, full_data, NULL, fft_dataR, fft_dataI); //fft3 @@ -322,19 +358,42 @@ int Scorealign::gen_chroma_audio(Audio_reader &reader, int hcutoff, //put chrom energy into the returned array #ifdef SA_VERBOSE - printf("cv_index %d\n", cv_index); + fprintf(dbf, "cv_index %d\n", cv_index); #endif assert(cv_index < reader.frame_count); - for (i = 0; i < CHROMA_BIN_COUNT; i++) - CHROM(cv_index, i) = binEnergy[i] / binCount[i]; + float *cv = AREF1(*chrom_energy, cv_index); + for (i = 0; i < CHROMA_BIN_COUNT; i++) { + cv[i] = binEnergy[i] / binCount[i]; + } + if (rms < silence_threshold) { + // "silence" flag + cv[CHROMA_BIN_COUNT] = 1.0f; + } else { + cv[CHROMA_BIN_COUNT] = 0.0f; + // normalize the non-silent frames + normalize(cv); + } +#if DEBUG_LOG + fprintf(dbf, "%d@%g) ", cv_index, cv_index * reader.actual_frame_period); + for (int i = 0; i < CHROMA_BIN_COUNT; i++) { + fprintf(dbf, "%d:%g ", i, cv[i]); + } + fprintf(dbf, " sil?:%g\n\n", cv[CHROMA_BIN_COUNT]); +#endif cv_index++; + if (progress && cv_index % 10 == 0 && + !progress->set_feature_progress( + float(cv_index * reader.actual_frame_period))) { + break; + } } // end of while ((readcount = read_mono_floats... + free(hamming); free(fft_dataI); free(fft_dataR); free(full_data); if (verbose) - printf("\nGenerated Chroma. file%d_frames is %i\n", id, file1_frames); + printf("\nGenerated Chroma. file%d_frames is %i\n", id, file0_frames); return cv_index; } @@ -362,7 +421,7 @@ typedef Event_list *Event_list_ptr; The chroma energy is placed in the float *chrom_energy. this 2D is an array of pointers. The function returns the number of frames - (aka the length of the 1st dimention of chrom_energy) + (aka the length of the 1st dimension of chrom_energy) * * Notes: keep a list of notes that are sounding. @@ -374,25 +433,33 @@ typedef Event_list *Event_list_ptr; How many frames? */ -int Scorealign::gen_chroma_midi(Alg_seq &seq, int hcutoff, int lcutoff, - float **chrom_energy, float *actual_frame_period, - int id, bool verbose) +int Scorealign::gen_chroma_midi(Alg_seq &seq, float dur, int nnotes, + int hcutoff, int lcutoff, + float **chrom_energy, double *actual_frame_period, + int id) { + // silence_threshold is compared to the *average* of chroma bins. + // Rather than divide the sum by CHROMA_BIN_COUNT to compute the + // average, just compute the sum and compare to silence_threshold * 12 + float threshold = (float) (silence_threshold * CHROMA_BIN_COUNT); + if (verbose) { printf ("==============FILE %d====================\n", id); SA_V(seq.write(cout, true)); } - /*=============================================================*/ +#if DEBUG_LOG + fprintf(dbf, "******** BEGIN MIDI CHROMA COMPUTATION *********\n"); +#endif /*=============================================================*/ - *actual_frame_period = (frame_period) ; // since we don't quantize to samples + *actual_frame_period = frame_period; // since we don't quantize to samples /*=============================================================*/ seq.convert_to_seconds(); - /* find duration */ - float dur = 0.0F; - int nnotes = 0; - nnotes= find_midi_duration(seq, &dur); + ///* find duration */ + //float dur = 0.0F; + //int nnotes = 0; + //nnotes = find_midi_duration(seq, &dur); /*================================================================*/ @@ -417,13 +484,15 @@ int Scorealign::gen_chroma_midi(Alg_seq &seq, int hcutoff, int lcutoff, /*====================================================*/ - float frame_begin = max((cv_index * (frame_period)) - - window_size/2 , 0.0F); //chooses zero if negative + float frame_begin = (float) max(cv_index * frame_period - + window_size / 2.0, 0.0); + //chooses zero if negative - float frame_end= frame_begin +(window_size/2); + float frame_end = (float) (cv_index * frame_period + window_size / 2.0); /*============================================================*/ + float *cv = AREF1(*chrom_energy, cv_index); /* zero the vector */ - for (int i = 0; i < CHROMA_BIN_COUNT; i++) CHROM(cv_index, i) = 0; + for (int i = 0; i < CHROMA_BIN_COUNT + 1; i++) cv[i] = 0; /* add new notes that are in the frame */ while (event && event->time < frame_end) { if (event->is_note()) { @@ -442,6 +511,7 @@ int Scorealign::gen_chroma_midi(Alg_seq &seq, int hcutoff, int lcutoff, } if (*ptr) ptr = &((*ptr)->next); } + float sum = 0.0; for (Event_list_ptr item = list; item; item = item->next) { /* compute duration of overlap */ float overlap = @@ -450,18 +520,34 @@ int Scorealign::gen_chroma_midi(Alg_seq &seq, int hcutoff, int lcutoff, float velocity = item->note->loud; float weight = overlap * velocity; #if DEBUG_LOG - fprintf(dbf, "%3d pitch %g key %d overlap %g velocity %g\n", - cv_index, item->note->pitch, item->note->get_identifier(), - overlap, velocity); + fprintf(dbf, "%3d pitch %g starting %g key %d overlap %g velocity %g\n", + cv_index, item->note->pitch, item->note->time, + item->note->get_identifier(), overlap, velocity); #endif - CHROM(cv_index, (int)item->note->pitch % 12) += weight; + cv[(int) item->note->pitch % 12] += weight; + sum += weight; } + + + if (sum < threshold) { + cv[CHROMA_BIN_COUNT] = 1.0; + } else { + normalize(cv); + } + + #if DEBUG_LOG + fprintf(dbf, "%d@%g) ", cv_index, frame_begin); for (int i = 0; i < CHROMA_BIN_COUNT; i++) { - fprintf(dbf, "%d:%g ", i, CHROM(cv_index, i)); + fprintf(dbf, "%d:%g ", i, cv[i]); } - fprintf(dbf, "\n\n"); + fprintf(dbf, " sil?:%g\n\n", cv[CHROMA_BIN_COUNT]); #endif + if (cv_index % 10 == 0 && progress && + !progress->set_feature_progress( + float(cv_index * *actual_frame_period))) { + break; + } } while (list) { Event_list_ptr temp = list; @@ -470,6 +556,6 @@ int Scorealign::gen_chroma_midi(Alg_seq &seq, int hcutoff, int lcutoff, } iterator.end(); if (verbose) - printf("\nGenerated Chroma. file%d_frames is %i\n", id, file1_frames); + printf("\nGenerated Chroma. file%d_frames is %i\n", id, file0_frames); return frame_count; } diff --git a/lib-src/libscorealign/gen_chroma.h b/lib-src/libscorealign/gen_chroma.h index 84caba14b..d78819530 100644 --- a/lib-src/libscorealign/gen_chroma.h +++ b/lib-src/libscorealign/gen_chroma.h @@ -2,5 +2,10 @@ bool is_midi_file(char *filename); -#define AREF2(chrom_energy, row, column) \ - (chrom_energy[row * (CHROMA_BIN_COUNT + 1) + column]) +// index into matrix to extract chroma vector +#define AREF1(chrom_energy, row) \ + ((chrom_energy) + (row) * (CHROMA_BIN_COUNT + 1)) + +// index into matrix to extract element of chroma vector +#define AREF2(chrom_energy, row, column) AREF1(chrom_energy, row)[column] + diff --git a/lib-src/libscorealign/hillclimb.cpp b/lib-src/libscorealign/hillclimb.cpp index 8639cc3f6..fd8ba5cb1 100644 --- a/lib-src/libscorealign/hillclimb.cpp +++ b/lib-src/libscorealign/hillclimb.cpp @@ -26,12 +26,33 @@ * maximum. */ -#include "hillclimb.h" + #include "stdio.h" +#include "malloc.h" +#include "sautils.h" +#include "hillclimb.h" #define HC_VERBOSE 0 #define V if (HC_VERBOSE) +Hillclimb::~Hillclimb() +{ + if (parameters) FREE(parameters); + if (step_size) FREE(step_size); + if (min_param) FREE(min_param); + if (max_param) FREE(max_param); +} + + +void Hillclimb::setup(int n_) { + n = n_; + parameters = ALLOC(double, n); + step_size = ALLOC(double, n); + min_param = ALLOC(double, n); + max_param = ALLOC(double, n); +} + + void Hillclimb::set_parameters(double *p, double *ss, double *min_, double *max_, int plen) { @@ -108,17 +129,20 @@ double Hillclimb::optimize() } */ -double Hillclimb::optimize() +double Hillclimb::optimize(Report_fn_ptr report, void *cookie) { double best = evaluate(); + int iterations = 0; while (true) { + (*report)(cookie, iterations, best); V printf("best %g ", best); // eval partial derivatives int i; // variables to search for max partial derivative double max_y = best; // max of evaluate() so far - int max_i; // index where best max was found - double max_parameter; // the good parameter value for max_i + int max_i = 0; // index where best max was found + // the good parameter value for max_i: + double max_parameter = parameters[0]; // now search over all parameters for best improvement for (i = 0; i < n; i++) { V printf("optimize at %d param %g ", i, parameters[i]); @@ -148,8 +172,10 @@ double Hillclimb::optimize() parameters[i] = save_param; V printf("\n"); } + iterations++; // for debugging, reporting if (max_y <= best) { // no improvement, we're done V printf("\nCompleted hillclimbing, best %g\n", best); + (*report)(cookie, iterations, best); return best; } // improvement because max_y higher than best: diff --git a/lib-src/libscorealign/hillclimb.h b/lib-src/libscorealign/hillclimb.h index 55ab6f2c4..e67240270 100644 --- a/lib-src/libscorealign/hillclimb.h +++ b/lib-src/libscorealign/hillclimb.h @@ -15,6 +15,9 @@ * */ +// while optimizing, this function is called to report progress +typedef void (*Report_fn_ptr)(void *cookie, int iteration, double best); + class Hillclimb { protected: double *parameters; // parameters to optimize @@ -24,12 +27,17 @@ protected: double *max_param; // maximum parameter values int n; // number of parameters public: + Hillclimb() { + parameters = step_size = min_param = max_param = NULL; + } + void setup(int n_); + ~Hillclimb(); void set_parameters(double *parameters_, double *step_size_, double *min_, double *max_, int n_); // retrieve parameters after optimization: double *get_parameters() { return parameters; } virtual double evaluate() = 0; - double optimize(); + double optimize(Report_fn_ptr report, void *cookie); }; diff --git a/lib-src/libscorealign/sautils.h b/lib-src/libscorealign/sautils.h index 5350cfe3a..a127cacb4 100644 --- a/lib-src/libscorealign/sautils.h +++ b/lib-src/libscorealign/sautils.h @@ -8,6 +8,7 @@ */ #define ALLOC(t, n) (t *) malloc(sizeof(t) * (n)) +#define FREE(p) free(p) #define ROUND(x) ((int) (0.5 + (x))) diff --git a/lib-src/libscorealign/scorealign-glue.cpp b/lib-src/libscorealign/scorealign-glue.cpp index 25bb66a31..5fdd3bab6 100644 --- a/lib-src/libscorealign/scorealign-glue.cpp +++ b/lib-src/libscorealign/scorealign-glue.cpp @@ -4,21 +4,41 @@ */ #include "allegro.h" -#include "scorealign-glue.h" #include "audioreader.h" -#include "audiomixerreader.h" #include "scorealign.h" +#include "scorealign-glue.h" +#include "audiomixerreader.h" -void scorealign(void *mixer, mixer_process_fn fn_ptr, int chans, double srate, - double end_time, Alg_seq *seq) + +int scorealign(void *mixer, mixer_process_fn fn_ptr, int chans, double srate, + double end_time, Alg_seq *seq, SAProgress *progress, + ScoreAlignParams ¶ms) { Scorealign sa; - sa.frame_period = 0.2; - sa.window_size = 0.2; + sa.frame_period = params.mFramePeriod; + sa.window_size = params.mWindowSize; + sa.silence_threshold = params.mSilenceThreshold; + sa.force_final_alignment = (params.mForceFinalAlignment != 0.0); + sa.ignore_silence = (params.mIgnoreSilence != 0.0); + sa.presmooth_time = params.mPresmoothTime; + sa.line_time = params.mLineTime; + sa.smooth_time = params.mSmoothTime; + Audio_mixer_reader reader(mixer, fn_ptr, chans, srate, end_time); reader.calculate_parameters(sa, false); - sa.align_midi_to_audio(*seq, reader, true); - sa.midi_tempo_align(*seq, false); + sa.progress = progress; + int result = sa.align_midi_to_audio(*seq, reader); + + params.mMidiStart = sa.first_x * sa.actual_frame_period_0; + params.mMidiEnd = (sa.last_x + 1) * sa.actual_frame_period_0; + params.mAudioStart = sa.first_y * sa.actual_frame_period_1; + params.mAudioEnd = (sa.last_y + 1) * sa.actual_frame_period_1; + + if (result != SA_SUCCESS) { + return result; + } + + sa.midi_tempo_align(*seq); // seq has now been modified to conform to audio provided by mixer - seq->set_real_dur(end_time); + return SA_SUCCESS; // success } diff --git a/lib-src/libscorealign/scorealign-glue.h b/lib-src/libscorealign/scorealign-glue.h index 6b8efe32f..a26d89371 100644 --- a/lib-src/libscorealign/scorealign-glue.h +++ b/lib-src/libscorealign/scorealign-glue.h @@ -1,5 +1,8 @@ typedef long (*mixer_process_fn)(void *mix, float **buffer, long n); -void scorealign(void *mixer, mixer_process_fn fn_ptr, +#include "ScoreAlignParams.h" + +int scorealign(void *mixer, mixer_process_fn fn_ptr, int chans, double srate, - double end_time, Alg_seq *seq); + double end_time, Alg_seq *seq, SAProgress *progress, + ScoreAlignParams ¶ms); diff --git a/lib-src/libscorealign/scorealign.cpp b/lib-src/libscorealign/scorealign.cpp index c5741be59..305d58e87 100644 --- a/lib-src/libscorealign/scorealign.cpp +++ b/lib-src/libscorealign/scorealign.cpp @@ -24,7 +24,7 @@ #define LOW_CUTOFF 40 #define HIGH_CUTOFF 2000 -// Note: There are "verbose" flags passed as parameters that +// Note: There is a "verbose" flag in Score_align objects that // enable some printing. The SA_VERBOSE compiler flag causes a // lot more debugging output, so it could be called VERY_VERBOSE // as opposed to the quieter verbose flags. @@ -36,10 +36,10 @@ // for presmoothing, how near does a point have to be to be "on the line" #define NEAR 1.5 -// path is file1_frames by file2_frames array, so first index -// (rows) is in [0 .. file1_frames]. Array is sequence of rows. -// columns (j) ranges from [0 .. file2_frames] -#define PATH(i,j) (path[(i) * file2_frames + (j)]) +// path is file0_frames by file1_frames array, so first index +// (rows) is in [0 .. file0_frames]. Array is sequence of rows. +// columns (j) ranges from [0 .. file1_frames] +#define PATH(i,j) (path[(i) * file1_frames + (j)]) /*===========================================================================*/ @@ -48,21 +48,52 @@ FILE *dbf = NULL; #endif +Scorealign::Scorealign() { + frame_period = SA_DFT_FRAME_PERIOD; + window_size = SA_DFT_WINDOW_SIZE; + force_final_alignment = SA_DFT_FORCE_FINAL_ALIGNMENT; + ignore_silence = SA_DFT_IGNORE_SILENCE; + silence_threshold = SA_DFT_SILENCE_THRESHOLD; + presmooth_time = SA_DFT_PRESMOOTH_TIME; + line_time = SA_DFT_LINE_TIME; + smooth_time = SA_DFT_SMOOTH_TIME; + pathlen = 0; + path_count = 0; + pathx = NULL; + pathy = NULL; + verbose = false; + progress = NULL; +#if DEBUG_LOG + dbf = fopen("debug-log.txt", "w"); + assert(dbf); +#endif +} + + +Scorealign::~Scorealign() { + if (pathx) free(pathx); + if (pathy) free(pathy); +#if DEBUG_LOG + fclose(dbf); +#endif +} + + /* MAP_TIME - lookup time of file1 in smooth_time_map and interpolate - to get time in file2 + lookup time of file0 in smooth_time_map and interpolate + to get time in file1 */ float Scorealign::map_time(float t1) { - t1 /= actual_frame_period_1; // convert from seconds to frames + t1 /= (float) actual_frame_period_0; // convert from seconds to frames int i = (int) t1; // round down if (i < 0) i = 0; - if (i >= file1_frames - 1) i = file1_frames - 2; + if (i >= file0_frames - 1) i = file0_frames - 2; // interpolate to get time - return actual_frame_period_2 * + return float(actual_frame_period_1 * interpolate(i, smooth_time_map[i], i+1, smooth_time_map[i+1], - t1); + t1)); } @@ -86,7 +117,7 @@ int find_midi_duration(Alg_seq &seq, float *dur) Alg_event_ptr e = notes[i]; if (e->is_note()) { Alg_note_ptr n = (Alg_note_ptr) e; - float note_end = n->time + n->dur; + float note_end = float(n->time + n->dur); if (note_end > *dur) *dur = note_end; nnotes++; } @@ -127,9 +158,9 @@ void Scorealign::path_step(int i, int j) { #if DEBUG_LOG fprintf(dbf, "(%i,%i) ", i, j); - if (++path_count % 5 == 0 || - (i == 0 && j == 0)) - fprintf(dbf, "\n"); + if (++path_count % 5 == 0 || + (i == first_x && j == first_y)) + fprintf(dbf, "\n"); #endif pathx[pathlen] = i; pathy[pathlen] = j; @@ -169,8 +200,8 @@ returns the first index in pathy where the element is bigger than sec */ int Scorealign::sec_to_pathy_index(float sec) { - for (int i = 0 ; i < (file1_frames + file2_frames); i++) { - if (smooth_time_map[i] * actual_frame_period_2 >= sec) { + for (int i = 0 ; i < (file0_frames + file1_frames); i++) { + if (smooth_time_map[i] * actual_frame_period_1 >= sec) { return i; } //printf("%i\n" ,pathy[i]); @@ -184,17 +215,21 @@ given a chrom_energy vector, sees how many of the inital frames are designated as silent */ -int frames_of_init_silence( float *chrom_energy, int frame_count) +int frames_of_init_silence(float *chrom_energy, int frame_count) { - bool silence = true; - int frames=0; - while (silence) { - if (silent(frames, chrom_energy)) - frames++; - else - silence=false; + int frames; + for (frames = 0; frames < frame_count; frames++) { + if (!silent(frames, chrom_energy)) break; + } + return frames; +} + +int last_non_silent_frame(float *chrom_energy, int frame_count) +{ + int frames; + for (frames = frame_count - 1; frames > 0; frames--) { + if (!silent(frames, chrom_energy)) break; } - return frames; } @@ -202,95 +237,130 @@ int frames_of_init_silence( float *chrom_energy, int frame_count) /* COMPARE_CHROMA Perform Dynamic Programming to find optimal alignment */ -void Scorealign::compare_chroma(bool verbose) +int Scorealign::compare_chroma() { float *path; - int x = 0; - int y = 0; /* Allocate the distance matrix */ - path = (float *) calloc(file1_frames * file2_frames, sizeof(float)); + path = (float *) calloc(file0_frames * file1_frames, sizeof(float)); - /* Initialize first row and column */ + /* skip over initial silence in signals */ + if (ignore_silence) { + first_x = frames_of_init_silence(chrom_energy0, file0_frames); + last_x = last_non_silent_frame(chrom_energy0, file0_frames); + first_y = frames_of_init_silence(chrom_energy1, file1_frames); + last_y = last_non_silent_frame(chrom_energy1, file1_frames); + } else { + first_x = 0; + last_x = file0_frames - 1; + first_y = 0; + last_y = file1_frames - 1; + } - /* allow free skip over initial silence in either signal, but not both */ - /* silence is indicated by a run of zeros along the first row and or - * column, starting at the origin (0,0). After computing these runs, we - * put the proper value at (0,0) - */ - if (verbose) printf("Performing silent skip DP \n"); - PATH(0, 0) = (silent(0, chrom_energy1) ? 0 : - gen_dist(0, 0, chrom_energy1, chrom_energy2)); - for (int i = 1; i < file1_frames; i++) - PATH(i, 0) = (PATH(i-1, 0) == 0 && silent(i, chrom_energy1) ? 0 : - gen_dist(i, 0, chrom_energy1, chrom_energy2) + - PATH(i-1, 0)); - PATH(0, 0) = (silent(0, chrom_energy2) ? 0 : - gen_dist(0, 0, chrom_energy1, chrom_energy2)); - for (int j = 1; j < file2_frames; j++) - PATH(0, j) = (PATH(0, j-1) == 0 && silent(j, chrom_energy2) ? 0 : - gen_dist(0, j, chrom_energy1, chrom_energy2) + - PATH(0, j-1)); - /* first row and first column are done, put proper value at (0,0) */ - PATH(0, 0) = (!silent(0, chrom_energy1) || !silent(0, chrom_energy2) ? - gen_dist(0, 0, chrom_energy1, chrom_energy2) : 0); - + if (last_x - first_x <= 0 || last_y - first_y <= 0) { + return SA_TOOSHORT; + } + + /* Initialize first row and column */ + if (verbose) printf("Performing DP\n"); + PATH(first_x, first_y) = gen_dist(first_x, first_y); + for (int x = first_x + 1; x <= last_x; x++) + PATH(x, first_y) = gen_dist(x, first_y) + PATH(x - 1, first_y); + for (int y = 1; y <= last_y; y++) + PATH(first_x, y) = gen_dist(first_x, y) + PATH(first_x, y - 1); + +#if DEBUG_LOG + fprintf(dbf, "DISTANCE MATRIX ***************************\n"); +#endif /* Perform DP for the rest of the matrix */ - for (int i = 1; i < file1_frames; i++) - for (int j = 1; j < file2_frames; j++) - PATH(i, j) = gen_dist(i, j, chrom_energy1, chrom_energy2) + - min3(PATH(i-1, j-1), PATH(i-1, j), PATH(i, j-1)); - + for (int x = first_x + 1; x <= last_x; x++) { + for (int y = first_y + 1; y <= last_y; y++) { + PATH(x, y) = gen_dist(x, y) + + float(min3(PATH(x-1, y-1), PATH(x-1, y), PATH(x, y-1))); +#if DEBUG_LOG + fprintf(dbf, "(%d %d %g) ", x, y, gen_dist(x, y), PATH(x, y)); +#endif + } +#if DEBUG_LOG + fprintf(dbf, "\n"); +#endif + // report progress for each file0_frame (column) + // This is not quite right if we are ignoring silence because + // then only a sub-matrix is computed. + if (progress && !progress->set_matrix_progress(file1_frames)) + return SA_CANCEL; + } +#if DEBUG_LOG + fprintf(dbf, "END OF DISTANCE MATRIX ********************\n"); +#endif + if (verbose) printf("Completed Dynamic Programming.\n"); - x = file1_frames - 1; - y = file2_frames - 1; - //x and y are the ending points, it can end at either the end of midi, - // or end of audio but not both - pathx = ALLOC(short, (x + y + 2)); - pathy = ALLOC(short, (x + y + 2)); + // or end of audio or both + pathx = ALLOC(short, (file0_frames + file1_frames)); + pathy = ALLOC(short, (file0_frames + file1_frames)); assert(pathx != NULL); assert(pathy != NULL); - // map from file1 time to file2 time - time_map = ALLOC(float, file1_frames); - smooth_time_map = ALLOC(float, file1_frames); + // map from file0 time to file1 time + time_map = ALLOC(float, file0_frames); + smooth_time_map = ALLOC(float, file0_frames); + int x = last_x; + int y = last_y; + + if (!force_final_alignment) { #if DEBUG_LOG - fprintf(dbf, "\nOptimal Path: "); + fprintf(dbf, "\nOptimal Path: "); #endif - while (1) { - /* Check for stopping */ - if (x == 0 & y == 0) { - path_step(0, 0); - path_reverse(); - break; + // find end point, the lowest cost matrix value at one of the + // sequence endings + float min_cost = 1.0E10; + for (int i = first_x; i <= last_x; i++) { + if (PATH(i, last_y) <= min_cost) { + min_cost = PATH(i, last_y); + x = i; + y = last_y; + } } - - /* Print the current coordinate in the path*/ + for (int j = first_y; j <= last_y; j++) { + if (PATH(last_x, j) <= min_cost) { + min_cost = PATH(last_x, j); + x = last_x; + y = j; + } + } +#if DEBUG_LOG + fprintf(dbf, "Min cost at %d %d\n\nPATH:\n", x, y); +#endif + } + + while ((x != first_x) || (y != first_y)) { path_step(x, y); /* Check for the optimal path backwards*/ - if (x > 0 && y > 0 && PATH(x-1, y-1) <= PATH(x-1, y) && + if (x > first_x && y > first_y && PATH(x-1, y-1) <= PATH(x-1, y) && PATH(x-1, y-1) <= PATH(x, y-1)) { x--; y--; - } else if (x > 0 && y > 0 && PATH(x-1, y) <= PATH(x, y-1)) { + } else if (x > first_x && y > first_y && PATH(x-1, y) <= PATH(x, y-1)) { x--; - } else if (y > 0) { + } else if (y > first_y) { y--; - } else if (x > 0) { + } else if (x > first_x) { x--; } } + path_step(x, y); + path_reverse(); free(path); + return SA_SUCCESS; // success } - void Scorealign::linear_regression(int n, int width, float &a, float &b) { int hw = (width - 1) / 2; // a more convenient form: 1/2 width @@ -316,32 +386,36 @@ void Scorealign::linear_regression(int n, int width, float &a, float &b) } - - - /* COMPUTE_SMOOTH_TIME_MAP compute regression line and estimate point at i Number of points in regression is smooth (an odd number). First index to compute is (smooth-1)/2. Use that line for the first (smooth+1)/2 points. The last index to compute is - (file1_frames - (smooth+1)/2). Use that line for the last + (file0_frames - (smooth+1)/2). Use that line for the last (smooth+1)/2 points. */ void Scorealign::compute_smooth_time_map() { + int i; + int hw = (smooth - 1) / 2; // half width of smoothing window + + // find the first point + for (i = 0; i < first_x; i++) { + smooth_time_map[i] = NOT_MAPPED; + } + // do the first points: float a, b; - linear_regression((smooth - 1) / 2, smooth, a, b); - int i; - for (i = 0; i < (smooth + 1) / 2; i++) { - smooth_time_map[i] = a + b*i; + linear_regression(first_x + hw, smooth, a, b); + for (i = first_x; i <= first_x + hw; i++) { + smooth_time_map[i] = a + b * i; } // do the middle points: - for (i = (smooth + 1) / 2; i < file1_frames - (smooth + 1) / 2; i++) { + for (i = first_x + hw + 1; i < last_x - hw; i++) { linear_regression(i, smooth, a, b); - smooth_time_map[i] = a + b*i; + smooth_time_map[i] = a + b * i; #if DEBUG_LOG fprintf(dbf, "time_map[%d] = %g, smooth_time_map[%d] = %g\n", @@ -349,14 +423,15 @@ void Scorealign::compute_smooth_time_map() #endif } - + // do the last points - linear_regression(file1_frames - (smooth + 1) / 2, smooth, a, b); - for (i = file1_frames - (smooth + 1) / 2; i < file1_frames; i++) { - smooth_time_map[i] = a + b*i; + linear_regression(last_x - hw, smooth, a, b); + for (i = last_x - hw; i <= last_x; i++) { + smooth_time_map[i] = a + b * i; } - - + // finally, fill with NOT_MAPPED + for (i = last_x + 1; i < file0_frames; i++) + smooth_time_map[i] = NOT_MAPPED; } @@ -401,16 +476,17 @@ short *path_copy(short *path, int len) */ void Scorealign::presmooth() { - int n = ROUND(presmooth_time / actual_frame_period_2); + int n = ROUND(presmooth_time / actual_frame_period_1); n = (n + 3) & ~3; // round up to multiple of 4 int i = 0; - while (pathx[i] + n < file2_frames) { + while (i < pathlen - 1 && pathx[i] + n <= last_x) { /* line goes from i to i+n-1 */ int x1 = pathx[i]; int xmid = x1 + n/2; int x2 = x1 + n; int y1 = pathy[i]; - int y2; + int y2 = pathy[i + 1]; // make sure it has a value. y2 should be + // set in the loop below. int j; /* search for y2 = pathy[j] s.t. pathx[j] == x2 */ for (j = i + n; j < pathlen; j++) { @@ -424,7 +500,8 @@ void Scorealign::presmooth() int k = i; int count = 0; while (pathx[k] < xmid) { // search first half - if (near_line(x1, y1, x2, y2, pathx[k], pathy[k])) { + if (near_line(float(x1), float(y1), float(x2), float(y2), + pathx[k], pathy[k])) { count++; regr.point(pathx[k], pathy[k]); } @@ -437,7 +514,8 @@ void Scorealign::presmooth() } /* see if line fits top half of the data */ while (pathx[k] < x2) { - if (near_line(x1, y1, x2, y2, pathx[k], pathy[k])) { + if (near_line(float(x1), float(y1), float(x2), float(y2), + pathx[k], pathy[k])) { count++; regr.point(pathx[k], pathy[k]); } @@ -511,11 +589,6 @@ void Scorealign::presmooth() // make sure new path is no longer than original path // the last point we wrote was k - 1 k = k - 1; // the last point we wrote is now k - // DEBUG - if (k > j) { - printf("oops: k %d, j %d\n", k, j); - SA_V(print_path_range(pathx, pathy, i, k);) - } assert(k <= j); // if new path is shorter than original, then fix up path if (k < j) { @@ -539,19 +612,28 @@ void Scorealign::presmooth() */ void Scorealign::compute_regression_lines() { - // first, compute the y value of the path at + int i; + // fill in time_map with NOT_MAPPED until the first point + // of the path + for (i = 0; i < pathx[0]; i++) { + time_map[i] = NOT_MAPPED; + } + // now, compute the y value of the path at // each x value. If the path has multiple values // on x, take the average. int p = 0; - int i; int upper, lower; - for (i = 0; i < file1_frames; i++) { + for (i = pathx[0]; p < pathlen; i++) { lower = pathy[p]; while (p < pathlen && pathx[p] == i) { upper = pathy[p]; p = p + 1; } - time_map[i] = (lower + upper) * 0.5; + time_map[i] = (lower + upper) * 0.5F; + } + // fill in rest of time_map with NOT_MAPPED + for (i = pathx[pathlen - 1] + 1; i <= last_x; i++) { + time_map[i] = NOT_MAPPED; } // now fit a line to the nearest WINDOW points and record the // line's y value for each x. @@ -559,115 +641,196 @@ void Scorealign::compute_regression_lines() } -void Scorealign::midi_tempo_align(Alg_seq &seq, bool verbose) +void Scorealign::midi_tempo_align(Alg_seq &seq) { // We create a new time map out of the alignment, and replace // the original time map in the Alg_seq sequence Alg_seq new_time_map_seq; /** align at all integer beats **/ - int totalbeats; - float dur_in_sec; - // probably alignment should respect the real_dur encoded into the seq - // rather than computing real_dur based on note off times -- the - // caller should be required to set real_dur to a good value, and - // the find_midi_duration() function should be available to the caller - // if necessary -RBD - find_midi_duration(seq, &dur_in_sec); - // - // totalbeat = lastbeat + 1 and round up the beat - totalbeats = (int) (seq.get_time_map()->time_to_beat(dur_in_sec) + 2); - if (verbose) + // totalbeats = lastbeat + 1 and round up the beat + int totalbeats = (int) seq.get_beat_dur() + 2; + if (verbose) { + double dur_in_sec = seq.get_real_dur(); printf("midi duration = %f, totalbeats=%i \n", dur_in_sec, totalbeats); - + } +#ifdef DEBUG_LOG + fprintf(dbf, "***************** CONSTRUCTING TIME MAP ***************\n"); +#endif + // turn off last tempo flag so last tempo will extrapolate + new_time_map_seq.get_time_map()->last_tempo_flag = false; + int first_beat = -1; for (int i = 0; i < totalbeats; i++) { - double newtime = map_time(seq.get_time_map()->beat_to_time(i)); - if (newtime > 0) + double newtime = map_time(float(seq.get_time_map()->beat_to_time(i))); + if (newtime > 0) { new_time_map_seq.insert_beat(newtime, (double) i); + // remember where the new time map begins + if (first_beat < 0) first_beat = i; +#ifdef DEBUG_LOG + fprintf(dbf, "map beat %d to time %g\n", i, newtime); +#endif + } } seq.convert_to_beats(); - seq.set_time_map(new_time_map_seq.get_time_map()); + double end_beat = seq.get_dur(); + Alg_time_map_ptr map = new_time_map_seq.get_time_map(); + seq.set_time_map(map); + // the new time map begins where the alignment began, but due to + // smoothing and rounding, there may be some edge effects. + // Try to set the tempo before the first_beat to match the tempo + // at the first beat by introducing another time map point at least + // one beat before the first_beat. To do this, we need at least + // 2 beats before first_beat and at least 2 beats in the time map + // (needed to compute initial tempo). Furthermore, the tempo at + // first_beat could be so slow that we do not have enough time + // before first_beat to anticipate the tempo. + if (first_beat >= 2 && totalbeats > first_beat + 1) { + int new_beat = first_beat / 2; + // compute initial tempo from first_beat and first_beat + 1 + int i = map->locate_beat(first_beat); + double t1 = map->beats[i].time; + double t2 = map->beats[i + 1].time; + double spb = (t2 - t1); // seconds per beat, beat period + double new_time = t1 - (first_beat - new_beat) * spb; + if (new_time <= 0.2) { + // not enough time to start at new_time, new_beat + // let's try using half the time rather than half the beats + new_time = t1 / 2.0; + // this will round down, so new_beat < first_beat + new_beat = int(first_beat - (t1 / 2) / spb); + new_time = t1 - (first_beat - new_beat) * spb; + } + // need to check again if new_beat would be too early + if (new_time > 0.2) { + map->insert_beat(new_time, new_beat); + } + } + // Note: final tempo is extrapolated, so no need to insert new + // time map points beyond the last one + seq.set_dur(end_beat); +#ifdef DEBUG_LOG + fprintf(dbf, "\nend_beat %g end time %g\n", + seq.get_beat_dur(), seq.get_real_dur()); +#endif } // this routine performs an alignment by adjusting midi to match audio // -void Scorealign::align_midi_to_audio(Alg_seq &seq, Audio_reader &reader, - bool verbose) +int Scorealign::align_midi_to_audio(Alg_seq &seq, Audio_reader &reader) { - /* Generate the chroma for file 1 + float dur = 0.0F; + int nnotes = find_midi_duration(seq, &dur); + if (progress) { + progress->set_frame_period(frame_period); + progress->set_smoothing(line_time > 0.0); + progress->set_duration(0, false, dur); + progress->set_duration(1, true, float(reader.actual_frame_period * + reader.frame_count)); + progress->set_phase(0); + } + /* Generate the chroma for file 0 * This will always be the MIDI File when aligning midi with audio. */ - file1_frames = gen_chroma_midi(seq, HIGH_CUTOFF, LOW_CUTOFF, - &chrom_energy1, &actual_frame_period_1, 1, verbose); + file0_frames = gen_chroma_midi(seq, dur, nnotes, HIGH_CUTOFF, LOW_CUTOFF, + &chrom_energy0, &actual_frame_period_0, 0); - /* Generate the chroma for file 2 */ - file2_frames = gen_chroma_audio(reader, HIGH_CUTOFF, LOW_CUTOFF, - &chrom_energy2, &actual_frame_period_2, 2, verbose); - - align_chromagrams(verbose); + /* Generate the chroma for file 1 */ + if (progress) progress->set_phase(1); + file1_frames = gen_chroma_audio(reader, HIGH_CUTOFF, LOW_CUTOFF, + &chrom_energy1, &actual_frame_period_1, 1); + return align_chromagrams(); } -void Scorealign::align_audio_to_audio(Audio_reader &reader1, - Audio_reader &reader2, bool verbose) +int Scorealign::align_audio_to_audio(Audio_reader &reader0, + Audio_reader &reader1) { + if (progress) { + progress->set_frame_period(frame_period); + progress->set_duration(0, true, float(reader0.actual_frame_period * + reader0.frame_count)); + progress->set_duration(1, true, float(reader1.actual_frame_period * + reader1.frame_count)); + + progress->set_phase(0); + progress->set_smoothing(line_time > 0.0); + } + file0_frames = gen_chroma_audio(reader0, HIGH_CUTOFF, LOW_CUTOFF, + &chrom_energy0, &actual_frame_period_0, 0); + + if (progress) progress->set_phase(1); file1_frames = gen_chroma_audio(reader1, HIGH_CUTOFF, LOW_CUTOFF, - &chrom_energy1, &actual_frame_period_1, 1, verbose); - file2_frames = gen_chroma_audio(reader2, HIGH_CUTOFF, LOW_CUTOFF, - &chrom_energy2, &actual_frame_period_2, 2, verbose); - align_chromagrams(verbose); + &chrom_energy1, &actual_frame_period_1, 1); + + return align_chromagrams(); } -void Scorealign::align_midi_to_midi(Alg_seq &seq1, Alg_seq &seq2, - bool verbose) +int Scorealign::align_midi_to_midi(Alg_seq &seq0, Alg_seq &seq1) { - file1_frames = gen_chroma_midi(seq1, HIGH_CUTOFF, LOW_CUTOFF, - &chrom_energy1, &actual_frame_period_1, 1, verbose); + float dur0 = 0.0F; + int nnotes0 = find_midi_duration(seq0, &dur0); + float dur1 = 0.0F; + int nnotes1 = find_midi_duration(seq1, &dur1); + if (progress) { + progress->set_frame_period(frame_period); + progress->set_smoothing(line_time > 0.0); + progress->set_duration(0, false, dur0); + progress->set_duration(1, false, dur1); - file2_frames = gen_chroma_midi(seq2, HIGH_CUTOFF, LOW_CUTOFF, - &chrom_energy2, &actual_frame_period_2, 2, verbose); + progress->set_phase(0); + } + file0_frames = gen_chroma_midi(seq0, dur0, nnotes0, + HIGH_CUTOFF, LOW_CUTOFF, + &chrom_energy0, &actual_frame_period_0, 0); - align_chromagrams(verbose); + if (progress) progress->set_phase(1); + file1_frames = gen_chroma_midi(seq1, dur1, nnotes1, + HIGH_CUTOFF, LOW_CUTOFF, + &chrom_energy1, &actual_frame_period_1, 1); + + return align_chromagrams(); } -void Scorealign::align_chromagrams(bool verbose) +int Scorealign::align_chromagrams() { + if (progress) progress->set_phase(2); if (verbose) printf("\nGenerated Chroma.\n"); - /* now that we have actual_frame_period_2, we can compute smooth */ + /* now that we have actual_frame_period_1, we can compute smooth */ // smooth is an odd number of frames that spans about smooth_time - smooth = ROUND(smooth_time / actual_frame_period_2); + smooth = ROUND(smooth_time / actual_frame_period_1); if (smooth < 3) smooth = 3; if (!(smooth & 1)) smooth++; // must be odd if (verbose) { printf("smoothing time is %g\n", smooth_time); printf("smooth count is %d\n", smooth); } - /* Normalize the chroma frames */ - norm_chroma(file1_frames, chrom_energy1); + SA_V(printf("Chromagram data for file 0:\n");) + SA_V(print_chroma_table(chrom_energy0, file0_frames);) SA_V(printf("Chromagram data for file 1:\n");) SA_V(print_chroma_table(chrom_energy1, file1_frames);) - norm_chroma(file2_frames, chrom_energy2); - SA_V(printf("Chromagram data for file 2:\n");) - SA_V(print_chroma_table(chrom_energy2, file2_frames);) - if (verbose) - printf("Normalized Chroma.\n"); /* Compare the chroma frames */ - compare_chroma(verbose); + int result = compare_chroma(); + if (result != SA_SUCCESS) { + return result; + } + if (progress) progress->set_phase(3); /* Compute the smooth time map now for use by curve-fitting */ compute_regression_lines(); - /* if line_time is set, do curve-fitting */ - if (line_time > 0.0) { - curve_fitting(this, verbose); - /* Redo the smooth time map after curve fitting or smoothing */ - compute_regression_lines(); - } /* if presmooth_time is set, do presmoothing */ if (presmooth_time > 0.0) { presmooth(); /* Redo the smooth time map after curve fitting or smoothing */ compute_regression_lines(); } + /* if line_time is set, do curve-fitting */ + if (line_time > 0.0) { + curve_fitting(this, verbose); + /* Redo the smooth time map after curve fitting or smoothing */ + compute_regression_lines(); + } + if (progress) progress->set_phase(4); + return SA_SUCCESS; } diff --git a/lib-src/libscorealign/scorealign.h b/lib-src/libscorealign/scorealign.h index 3a285092c..038fce066 100644 --- a/lib-src/libscorealign/scorealign.h +++ b/lib-src/libscorealign/scorealign.h @@ -12,52 +12,142 @@ #define SA_V(stmt) #endif +// a class to report (optionally) score alignment progress +class SAProgress { + public: + SAProgress() { smoothing = false; } + // we need the frame period to convert seconds to work units + // call this before set_duration() + virtual void set_frame_period(double seconds) { frame_period = seconds; }; + // index = 0 or 1 to tell which file (first or second) + // is_audio = true (audio) or false (midi) + // seconds = duration of audio or midi data + virtual void set_duration(int index, bool audio_flag, double seconds) { + durations[index] = seconds; + is_audio[index] = audio_flag; }; + // if fitting pwl path to path, set smoothing to true + virtual void set_smoothing(bool s) { smoothing = s; } + // which alignment phase are we working on? + // 0 = first file chroma, 1 = second file chroma, 2 = compute matrix, + // 3 = smoothing + // Note: set_phase(0) is REQUIRED and must be called only ONCE. + // This is when we calculate total work + // and initialize any local state needed to handle set_feature_progress() + // and set_matrix_progress(). + virtual void set_phase(int i) { phase = i; }; + // how many seconds have we processed (in phase 1 or 2) + // return value is normally true; false is request to cancel + virtual bool set_feature_progress(float seconds) { return true; }; + // report that some matrix elements have been computed? + // return value is normally true; false is request to cancel + virtual bool set_matrix_progress(int cells) { return true; }; + // report iterations of line smoothing + virtual bool set_smoothing_progress(int i) { return true; }; + protected: + double frame_period; + int phase; + double durations[2]; + bool is_audio[2]; + bool smoothing; +}; + + +enum { + SA_SUCCESS = 0, + SA_TOOSHORT, + SA_CANCEL +}; + + +#define SA_DFT_FRAME_PERIOD 0.2 +#define SA_DFT_FRAME_PERIOD_TEXT wxT("0.20 secs") + +#define SA_DFT_WINDOW_SIZE 0.2 +#define SA_DFT_WINDOW_SIZE_TEXT wxT("0.20 secs") + +#define SA_DFT_FORCE_FINAL_ALIGNMENT true +#define SA_DFT_FORCE_FINAL_ALIGNMENT_STRING wxT("true") + +#define SA_DFT_IGNORE_SILENCE true +#define SA_DFT_IGNORE_SILENCE_STRING wxT("true") + +#define SA_DFT_SILENCE_THRESHOLD 0.1 +#define SA_DFT_SILENCE_THRESHOLD_TEXT wxT("0.100") + +#define SA_DFT_PRESMOOTH_TIME 0 +#define SA_DFT_PRESMOOTH_TIME_TEXT wxT("(off)") + +#define SA_DFT_LINE_TIME 0 +#define SA_DFT_LINE_TIME_TEXT wxT("(off)") + +#define SA_DFT_SMOOTH_TIME 1.75 +#define SA_DFT_SMOOTH_TIME_TEXT wxT("1.75 secs") + class Scorealign { public: - float frame_period; // time in seconds - float window_size; - float presmooth_time; - float line_time; - float smooth_time; // duration of smoothing window + double frame_period; // time in seconds + double window_size; + double silence_threshold; + bool force_final_alignment; + bool ignore_silence; + double presmooth_time; + double line_time; + double smooth_time; // duration of smoothing window int smooth; // number of points used to compute the smooth time map - Scorealign() { - frame_period = 0.25; - window_size = 0.25; - presmooth_time = 0.0; - line_time = 0.0; - smooth_time = 1.75; - pathlen = 0; - path_count = 0; - pathx = NULL; - pathy = NULL; - } + Scorealign(); + ~Scorealign(); - ~Scorealign() { - if (pathx) free(pathx); - if (pathy) free(pathy); - } + SAProgress *progress; + bool verbose; // chromagrams and lengths, path data + float *chrom_energy0; + int file0_frames; // number of frames in file0 float *chrom_energy1; - int file1_frames; // number of frames in file1 - float *chrom_energy2; - int file2_frames; //number of frames in file2 + int file1_frames; //number of frames in file1 + // pathx, pathy, and pathlen describe the shortest path through the + // matrix from first_x, first_y to last_x, last_y (from the first + // non-silent frame to the last non-silent frame). The length varies + // depending upon the amount of silence that is ignored and how many + // path steps are diagonal. short *pathx; //for midi (when aligning midi and audio) short *pathy; //for audio (when aligning midi and audio) int pathlen; + // first_x, first_y, last_x, last_y are the starting and ending + // points of the path. (It's not 0, 0, file0_frames, file1_frames + // because silent frames may be trimmed from beginning and ending. + int first_x; + int first_y; + int last_x; + int last_y; + void set_pathlen(int p) { pathlen = p; } + // time_map is, for each sequence 0 frame, the time of the matching + // frame in sequence 1. If the path associates a frame of sequence 0 + // with multiple frames in sequence 1, the sequence 1 frame times + // are averaged. The frames that are not mapped to sequence 1 are + // marked with a time of -9999 or NOT_MAPPED. + // These will be silent frames of sequence 0. +#define NOT_MAPPED -9999.0F float *time_map; + // smooth_time_map is a smoothed version of time_map. It also has + // non-mapped frames marked with times of -9999 or NOT_MAPPED. + // Because of smoothing, frames in smooth_time_map may map to + // negative times in sequence 1. + // These negative times will not be as negative as -9999, but + // the recommended coding style is to compare for equality with + // NOT_MAPPED to test for that value. float *smooth_time_map; // chroma vectors are calculated from an integer number of samples // that approximates the nominal frame_period. Actual frame period // is calculated and stored here: // time in seconds for midi (when aligning midi and audio) - float actual_frame_period_1; + double actual_frame_period_0; // time in seconds for audio (when aligning midi and audio) - float actual_frame_period_2; + double actual_frame_period_1; /* gen_chroma.cpp stuff: generates the chroma energy for a given file @@ -69,36 +159,43 @@ class Scorealign { (i.e. the length of the 1st dimention of chrom_energy */ int gen_chroma_audio(Audio_reader &reader, int hcutoff, int lcutoff, - float **chrom_energy, float *actual_frame_period, - int id, bool verbose); + float **chrom_energy, double *actual_frame_period, + int id); - int gen_chroma_midi(Alg_seq &seq, int hcutoff, int lcutoff, - float **chrom_energy, float *actual_frame_period, - int id, bool verbose); + int gen_chroma_midi(Alg_seq &seq, float dur, int nnotes, + int hcutoff, int lcutoff, + float **chrom_energy, double *actual_frame_period, + int id); + + /* comp_chroma.cpp stuff */ + /* GEN_DIST + * + * This function generates the Euclidean distance for points i + * and j in two chroma vectors for use with dynamic time warping of + * the chroma vectors. + */ + float gen_dist(int i, int j); /* scorealign.cpp stuff: */ float map_time(float t1); - void midi_tempo_align(Alg_seq &seq , char *midiname, char *beatname); - void align_midi_to_audio(Alg_seq &seq, Audio_reader &reader, - bool verbose); - void align_midi_to_midi(Alg_seq &seq1, Alg_seq &seq2, bool verbose); - void align_audio_to_audio(Audio_reader &reader1, - Audio_reader &reader2, bool verbose); - void align_chromagrams(bool verbose); + int align_midi_to_audio(Alg_seq &seq, Audio_reader &reader); + int align_midi_to_midi(Alg_seq &seq0, Alg_seq &seq2); + int align_audio_to_audio(Audio_reader &reader1, Audio_reader &reader2); + int align_chromagrams(); int path_count; // for debug log formatting void path_step(int i, int j); void path_reverse(); int sec_to_pathy_index(float sec); - void compare_chroma(bool verbose); + int compare_chroma(); void linear_regression(int n, int width, float &a, float &b); void compute_smooth_time_map(); void presmooth(); void compute_regression_lines(); - void midi_tempo_align(Alg_seq &seq, bool verbose); + void midi_tempo_align(Alg_seq &seq); }; -#define DEBUG_LOG 0 +//#define DEBUG_LOG 1 #if DEBUG_LOG extern FILE *dbf; #endif diff --git a/lib-src/portmidi/CHANGELOG.txt b/lib-src/portmidi/CHANGELOG.txt index 986137ea6..c2a083a56 100644 --- a/lib-src/portmidi/CHANGELOG.txt +++ b/lib-src/portmidi/CHANGELOG.txt @@ -1,4 +1,28 @@ /* CHANGELOG FOR PORTMIDI + * + * 19Oct09 Roger Dannenberg + * - Changes dynamic library names from portmidi_d to portmidi to + * be backward-compatible with programs expecting a library by + * the old name. + * + * 04Oct09 Roger Dannenberg + * - Converted to using Cmake. + * - Renamed static and dynamic library files to portmidi_s and portmidi_d + * - Eliminated VC9 and VC8 files (went back to simply test.vcproj, etc., + * use Cmake to switch from the provided VC9 files to VC8 or other) + * - Many small changes to prepare for 64-bit architectures (but only + * tested on 32-bit machines) + * + * 16Jun09 Roger Dannenberg + * - Started using Microsoft Visual C++ Version 9 (Express). Converted + * all *-VC9.vcproj file to *.vcproj and renamed old project files to + * *-VC8.proj. Previously, output from VC9 went to special VC9 files, + * that breaks any program or script looking for output in release or + * debug files, so now both compiler version output to the same folders. + * Now, debug version uses static linking with debug DLL runtime, and + * release version uses static linking with statically linked runtime. + * Converted to Inno Setup and worked on scripts to make things build + * properly, especially pmdefaults. * * 02Jan09 Roger Dannenberg * - Created Java interface and wrote PmDefaults application to set diff --git a/lib-src/portmidi/README.txt b/lib-src/portmidi/README.txt index 96453be52..18d951e5f 100644 --- a/lib-src/portmidi/README.txt +++ b/lib-src/portmidi/README.txt @@ -11,6 +11,10 @@ Additional documentation: - Linux: see pm_linux/README_LINUX.txt - Mac OSX: see pm_mac/README_MAC.txt - Common Lisp: see pm_cl/README_CL.txt + - Eclipse: see portmidi_cdt.zip (this was contributed as is; the dlls here + are now -- Sep 09 -- out of date. What is really needed is a script + to generate this release automatically so we can maintain it.) + - C-Sharp: see pm_csharp.zip (also contributed as is) ---------- some notes on the design of PortMidi ---------- diff --git a/lib-src/portmidi/pm_cl/cffi-portmidi.lisp b/lib-src/portmidi/pm_cl/cffi-portmidi.lisp index 610d273f4..4b3ad9c3e 100644 --- a/lib-src/portmidi/pm_cl/cffi-portmidi.lisp +++ b/lib-src/portmidi/pm_cl/cffi-portmidi.lisp @@ -6,9 +6,6 @@ ;;; See http://www.cliki.net/LLGPL for the text of this agreement. ;;; ********************************************************************** -;;; $Name: not supported by cvs2svn $ -;;; $Revision: 1.1 $ -;;; $Date: 2009-06-24 20:37:25 $ ;;; A CFFI interface to Portmidi. Should run in most Common Lisp ;;; implementations on Linux, OS X and Windows. For information about diff --git a/lib-src/portmidi/pm_common/pminternal.h b/lib-src/portmidi/pm_common/pminternal.h index 9bfa76a98..6b6242026 100644 --- a/lib-src/portmidi/pm_common/pminternal.h +++ b/lib-src/portmidi/pm_common/pminternal.h @@ -95,7 +95,7 @@ extern int pm_descriptor_max; extern descriptor_type descriptors; extern int pm_descriptor_index; -typedef unsigned long (*time_get_proc_type)(void *time_info); +typedef uint32_t (*time_get_proc_type)(void *time_info); typedef struct pm_internal_struct { int device_id; /* which device is open (index to descriptors) */ @@ -103,10 +103,10 @@ typedef struct pm_internal_struct { PmTimeProcPtr time_proc; /* where to get the time */ void *time_info; /* pass this to get_time() */ - long buffer_len; /* how big is the buffer or queue? */ + int32_t buffer_len; /* how big is the buffer or queue? */ PmQueue *queue; - long latency; /* time delay in ms between timestamps and actual output */ + int32_t latency; /* time delay in ms between timestamps and actual output */ /* set to zero to get immediate, simple blocking output */ /* if latency is zero, timestamps will be ignored; */ /* if midi input device, this field ignored */ @@ -122,8 +122,8 @@ typedef struct pm_internal_struct { PmMessage sysex_message; /* buffer for 4 bytes of sysex data */ int sysex_message_count; /* how many bytes in sysex_message so far */ - long filters; /* flags that filter incoming message classes */ - int channel_mask; /* flter incoming messages based on channel */ + int32_t filters; /* flags that filter incoming message classes */ + int32_t channel_mask; /* filter incoming messages based on channel */ PmTimestamp last_msg_time; /* timestamp of last message */ PmTimestamp sync_time; /* time of last synchronization */ PmTimestamp now; /* set by PmWrite to current time */ @@ -137,8 +137,8 @@ typedef struct pm_internal_struct { * important */ unsigned char *fill_base; /* addr of ptr to sysex data */ - unsigned long *fill_offset_ptr; /* offset of next sysex byte */ - int fill_length; /* how many sysex bytes to write */ + uint32_t *fill_offset_ptr; /* offset of next sysex byte */ + int32_t fill_length; /* how many sysex bytes to write */ } PmInternal; @@ -157,7 +157,7 @@ PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp); PmError pm_success_fn(PmInternal *midi); PmError pm_add_device(char *interf, char *name, int input, void *descriptor, pm_fns_type dictionary); -unsigned int pm_read_bytes(PmInternal *midi, unsigned char *data, int len, +uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len, PmTimestamp timestamp); void pm_read_short(PmInternal *midi, PmEvent *event); diff --git a/lib-src/portmidi/pm_common/pmutil.c b/lib-src/portmidi/pm_common/pmutil.c index b83243fa2..a70fe2fa1 100644 --- a/lib-src/portmidi/pm_common/pmutil.c +++ b/lib-src/portmidi/pm_common/pmutil.c @@ -1,7 +1,7 @@ /* pmutil.c -- some helpful utilities for building midi applications that use PortMidi */ -//#include +#include #include #include #include "portmidi.h" @@ -17,42 +17,37 @@ #include "stdio.h" #endif -/* code is based on 4-byte words -- it should work on a 64-bit machine - as long as a "long" has 4 bytes. This code could be generalized to - be independent of the size of "long" */ - -typedef long int32; - typedef struct { long head; long tail; long len; - long msg_size; /* number of int32 in a message including extra word */ long overflow; - long peek_overflow; - int32 *buffer; - int32 *peek; - int peek_flag; + int32_t msg_size; /* number of int32_t in a message including extra word */ + int32_t peek_overflow; + int32_t *buffer; + int32_t *peek; + int32_t peek_flag; } PmQueueRep; -PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg) +PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg) { - int int32s_per_msg = ((bytes_per_msg + sizeof(int32) - 1) & - ~(sizeof(int32) - 1)) / sizeof(int32); + int32_t int32s_per_msg = + (int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) & + ~(sizeof(int32_t) - 1)) / sizeof(int32_t)); PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep)); if (!queue) /* memory allocation failed */ return NULL; /* need extra word per message for non-zero encoding */ queue->len = num_msgs * (int32s_per_msg + 1); - queue->buffer = (int32 *) pm_alloc(queue->len * sizeof(int32)); - bzero(queue->buffer, queue->len * sizeof(int32)); + queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t)); + bzero(queue->buffer, queue->len * sizeof(int32_t)); if (!queue->buffer) { pm_free(queue); return NULL; } else { /* allocate the "peek" buffer */ - queue->peek = (int32 *) pm_alloc(int32s_per_msg * sizeof(int32)); + queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t)); if (!queue->peek) { /* free everything allocated so far and return */ pm_free(queue->buffer); @@ -60,7 +55,7 @@ PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg) return NULL; } } - bzero(queue->buffer, queue->len * sizeof(int32)); + bzero(queue->buffer, queue->len * sizeof(int32_t)); queue->head = 0; queue->tail = 0; /* msg_size is in words */ @@ -72,7 +67,7 @@ PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg) } -PmError Pm_QueueDestroy(PmQueue *q) +PMEXPORT PmError Pm_QueueDestroy(PmQueue *q) { PmQueueRep *queue = (PmQueueRep *) q; @@ -87,12 +82,12 @@ PmError Pm_QueueDestroy(PmQueue *q) } -PmError Pm_Dequeue(PmQueue *q, void *msg) +PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg) { long head; PmQueueRep *queue = (PmQueueRep *) q; int i; - int32 *msg_as_int32 = (int32 *) msg; + int32_t *msg_as_int32 = (int32_t *) msg; /* arg checking */ if (!queue) @@ -106,7 +101,7 @@ PmError Pm_Dequeue(PmQueue *q, void *msg) return pmBufferOverflow; } if (queue->peek_flag) { - memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32)); + memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t)); queue->peek_flag = FALSE; return pmGotData; } @@ -144,18 +139,18 @@ PmError Pm_Dequeue(PmQueue *q, void *msg) } } memcpy(msg, (char *) &queue->buffer[head + 1], - sizeof(int32) * (queue->msg_size - 1)); + sizeof(int32_t) * (queue->msg_size - 1)); /* fix up zeros */ i = queue->buffer[head]; while (i < queue->msg_size) { - int32 j; + int32_t j; i--; /* msg does not have extra word so shift down */ j = msg_as_int32[i]; msg_as_int32[i] = 0; i = j; } /* signal that data has been removed by zeroing: */ - bzero((char *) &queue->buffer[head], sizeof(int32) * queue->msg_size); + bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size); /* update head */ head += queue->msg_size; @@ -166,7 +161,7 @@ PmError Pm_Dequeue(PmQueue *q, void *msg) -PmError Pm_SetOverflow(PmQueue *q) +PMEXPORT PmError Pm_SetOverflow(PmQueue *q) { PmQueueRep *queue = (PmQueueRep *) q; long tail; @@ -181,14 +176,14 @@ PmError Pm_SetOverflow(PmQueue *q) } -PmError Pm_Enqueue(PmQueue *q, void *msg) +PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg) { PmQueueRep *queue = (PmQueueRep *) q; long tail; int i; - int32 *src = (int32 *) msg; - int32 *ptr; - int32 *dest; + int32_t *src = (int32_t *) msg; + int32_t *ptr; + int32_t *dest; int rslt; if (!queue) return pmBadPtr; @@ -206,7 +201,7 @@ PmError Pm_Enqueue(PmQueue *q, void *msg) ptr = &queue->buffer[tail]; dest = ptr + 1; for (i = 1; i < queue->msg_size; i++) { - int32 j = src[i - 1]; + int32_t j = src[i - 1]; if (!j) { *ptr = i; ptr = dest; @@ -223,7 +218,7 @@ PmError Pm_Enqueue(PmQueue *q, void *msg) } -int Pm_QueueEmpty(PmQueue *q) +PMEXPORT int Pm_QueueEmpty(PmQueue *q) { PmQueueRep *queue = (PmQueueRep *) q; return (!queue) || /* null pointer -> return "empty" */ @@ -231,9 +226,9 @@ int Pm_QueueEmpty(PmQueue *q) } -int Pm_QueueFull(PmQueue *q) +PMEXPORT int Pm_QueueFull(PmQueue *q) { - int tail; + long tail; int i; PmQueueRep *queue = (PmQueueRep *) q; /* arg checking */ @@ -250,10 +245,10 @@ int Pm_QueueFull(PmQueue *q) } -void *Pm_QueuePeek(PmQueue *q) +PMEXPORT void *Pm_QueuePeek(PmQueue *q) { PmError rslt; - long temp; + int32_t temp; PmQueueRep *queue = (PmQueueRep *) q; /* arg checking */ if (!queue) diff --git a/lib-src/portmidi/pm_common/pmutil.h b/lib-src/portmidi/pm_common/pmutil.h index bb5c58669..ef5ee4bf8 100644 --- a/lib-src/portmidi/pm_common/pmutil.h +++ b/lib-src/portmidi/pm_common/pmutil.h @@ -44,8 +44,8 @@ typedef void PmQueue; Pm_QueueDestroy() destroys the queue and frees its storage. */ -PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg); -PmError Pm_QueueDestroy(PmQueue *queue); +PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg); +PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue); /* Pm_Dequeue() removes one item from the queue, copying it into msg. @@ -56,7 +56,7 @@ PmError Pm_QueueDestroy(PmQueue *queue); overflow report. This protocol ensures that the reader will be notified when data is lost due to overflow. */ -PmError Pm_Dequeue(PmQueue *queue, void *msg); +PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg); /* @@ -64,7 +64,7 @@ PmError Pm_Dequeue(PmQueue *queue, void *msg); Returns pmNoError if successful and pmBufferOverflow if the queue was already full. If pmBufferOverflow is returned, the overflow flag is set. */ -PmError Pm_Enqueue(PmQueue *queue, void *msg); +PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg); /* @@ -82,8 +82,8 @@ PmError Pm_Enqueue(PmQueue *queue, void *msg); Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL. Pm_QueueEmpty() returns FALSE if queue is NULL. */ -int Pm_QueueFull(PmQueue *queue); -int Pm_QueueEmpty(PmQueue *queue); +PMEXPORT int Pm_QueueFull(PmQueue *queue); +PMEXPORT int Pm_QueueEmpty(PmQueue *queue); /* @@ -109,7 +109,7 @@ int Pm_QueueEmpty(PmQueue *queue); Note that Pm_QueuePeek() is not a fast check, so if possible, you might as well just call Pm_Dequeue() and accept the data if it is there. */ -void *Pm_QueuePeek(PmQueue *queue); +PMEXPORT void *Pm_QueuePeek(PmQueue *queue); /* Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow @@ -120,7 +120,7 @@ void *Pm_QueuePeek(PmQueue *queue); is NULL, returns pmBufferOverflow if buffer is already in an overflow state, returns pmNoError if successfully set overflow state. */ -PmError Pm_SetOverflow(PmQueue *queue); +PMEXPORT PmError Pm_SetOverflow(PmQueue *queue); #ifdef __cplusplus } diff --git a/lib-src/portmidi/pm_common/portmidi.c b/lib-src/portmidi/pm_common/portmidi.c index 79329835d..b7161700d 100644 --- a/lib-src/portmidi/pm_common/portmidi.c +++ b/lib-src/portmidi/pm_common/portmidi.c @@ -173,14 +173,14 @@ portmidi implementation ==================================================================== */ -int Pm_CountDevices( void ) { +PMEXPORT int Pm_CountDevices( void ) { Pm_Initialize(); /* no error checking -- Pm_Initialize() does not fail */ return pm_descriptor_index; } -const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) { +PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) { Pm_Initialize(); /* no error check needed */ if (id >= 0 && id < pm_descriptor_index) { return &descriptors[id].pub; @@ -217,7 +217,7 @@ static PmError none_open(PmInternal *midi, void *driverInfo) { return pmBadPtr; } static void none_get_host_error(PmInternal * midi, char * msg, unsigned int len) { - strcpy(msg, ""); + *msg = 0; // empty string } static unsigned int none_has_host_error(PmInternal * midi) { return FALSE; @@ -246,7 +246,7 @@ pm_fns_node pm_none_dictionary = { }; -const char *Pm_GetErrorText( PmError errnum ) { +PMEXPORT const char *Pm_GetErrorText( PmError errnum ) { const char *msg; switch(errnum) @@ -292,7 +292,7 @@ const char *Pm_GetErrorText( PmError errnum ) { /* This can be called whenever you get a pmHostError return value. * The error will always be in the global pm_hosterror_text. */ -void Pm_GetHostErrorText(char * msg, unsigned int len) { +PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len) { assert(msg); assert(len > 0); if (pm_hosterror) { @@ -307,7 +307,7 @@ void Pm_GetHostErrorText(char * msg, unsigned int len) { } -int Pm_HasHostError(PortMidiStream * stream) { +PMEXPORT int Pm_HasHostError(PortMidiStream * stream) { if (pm_hosterror) return TRUE; if (stream) { PmInternal * midi = (PmInternal *) stream; @@ -323,7 +323,7 @@ int Pm_HasHostError(PortMidiStream * stream) { } -PmError Pm_Initialize( void ) { +PMEXPORT PmError Pm_Initialize( void ) { if (!pm_initialized) { pm_hosterror = FALSE; pm_hosterror_text[0] = 0; /* the null string */ @@ -334,7 +334,7 @@ PmError Pm_Initialize( void ) { } -PmError Pm_Terminate( void ) { +PMEXPORT PmError Pm_Terminate( void ) { if (pm_initialized) { pm_term(); // if there are no devices, descriptors might still be NULL @@ -350,11 +350,11 @@ PmError Pm_Terminate( void ) { } -/* Pm_Read -- read up to length longs from source into buffer */ +/* Pm_Read -- read up to length messages from source into buffer */ /* - * returns number of longs actually read, or error code + * returns number of messages actually read, or error code */ -int Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) { +PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length) { PmInternal *midi = (PmInternal *) stream; int n = 0; PmError err = pmNoError; @@ -395,7 +395,7 @@ int Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) { return n; } -PmError Pm_Poll( PortMidiStream *stream ) +PMEXPORT PmError Pm_Poll( PortMidiStream *stream ) { PmInternal *midi = (PmInternal *) stream; PmError err; @@ -445,7 +445,7 @@ static PmError pm_end_sysex(PmInternal *midi) Pm_WriteSysEx all operate a state machine that "outputs" calls to write_short, begin_sysex, write_byte, end_sysex, and write_realtime */ -PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length) +PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t length) { PmInternal *midi = (PmInternal *) stream; PmError err = pmNoError; @@ -489,7 +489,7 @@ PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length) * sysex messages in a partially transmitted state. */ for (i = 0; i < length; i++) { - unsigned long msg = buffer[i].message; + uint32_t msg = buffer[i].message; bits = 0; /* is this a sysex message? */ if (Pm_MessageStatus(msg) == MIDI_SYSEX) { @@ -541,7 +541,7 @@ PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length) unsigned char *ptr = midi->fill_base + *(midi->fill_offset_ptr); ptr[0] = msg; ptr[1] = msg >> 8; - ptr[2] = msg >> 18; ptr[3] = msg >> 24; + ptr[2] = msg >> 16; ptr[3] = msg >> 24; (*midi->fill_offset_ptr) += 4; continue; } @@ -578,7 +578,7 @@ error_exit: } -PmError Pm_WriteShort(PortMidiStream *stream, long when, long msg) +PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when, PmMessage msg) { PmEvent event; @@ -588,12 +588,12 @@ PmError Pm_WriteShort(PortMidiStream *stream, long when, long msg) } -PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, +PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, unsigned char *msg) { /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */ /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */ - #define BUFLEN (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage)) + #define BUFLEN ((int) (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage))) PmEvent buffer[BUFLEN]; int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */ PmInternal *midi = (PmInternal *) stream; @@ -666,10 +666,10 @@ end_of_sysex: -PmError Pm_OpenInput(PortMidiStream** stream, +PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream, PmDeviceID inputDevice, void *inputDriverInfo, - long bufferSize, + int32_t bufferSize, PmTimeProcPtr time_proc, void *time_info) { @@ -706,7 +706,7 @@ PmError Pm_OpenInput(PortMidiStream** stream, system-specific midi_out_open() method. */ if (bufferSize <= 0) bufferSize = 256; /* default buffer size */ - midi->queue = Pm_QueueCreate(bufferSize, sizeof(PmEvent)); + midi->queue = Pm_QueueCreate(bufferSize, (int32_t) sizeof(PmEvent)); if (!midi->queue) { /* free portMidi data */ *stream = NULL; @@ -749,13 +749,13 @@ error_return: } -PmError Pm_OpenOutput(PortMidiStream** stream, +PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream, PmDeviceID outputDevice, void *outputDriverInfo, - long bufferSize, + int32_t bufferSize, PmTimeProcPtr time_proc, void *time_info, - long latency ) + int32_t latency ) { PmInternal *midi; PmError err = pmNoError; @@ -828,7 +828,7 @@ error_return: } -PmError Pm_SetChannelMask(PortMidiStream *stream, int mask) +PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask) { PmInternal *midi = (PmInternal *) stream; PmError err = pmNoError; @@ -842,7 +842,7 @@ PmError Pm_SetChannelMask(PortMidiStream *stream, int mask) } -PmError Pm_SetFilter(PortMidiStream *stream, long filters) { +PMEXPORT PmError Pm_SetFilter(PortMidiStream *stream, int32_t filters) { PmInternal *midi = (PmInternal *) stream; PmError err = pmNoError; @@ -857,7 +857,7 @@ PmError Pm_SetFilter(PortMidiStream *stream, long filters) { } -PmError Pm_Close( PortMidiStream *stream ) { +PMEXPORT PmError Pm_Close( PortMidiStream *stream ) { PmInternal *midi = (PmInternal *) stream; PmError err = pmNoError; @@ -889,8 +889,21 @@ error_return: return pm_errmsg(err); } +PmError Pm_Synchronize( PortMidiStream* stream ) { + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + if (midi == NULL) + err = pmBadPtr; + else if (!descriptors[midi->device_id].pub.output) + err = pmBadPtr; + else if (!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else + midi->first_message = TRUE; + return err; +} -PmError Pm_Abort( PortMidiStream* stream ) { +PMEXPORT PmError Pm_Abort( PortMidiStream* stream ) { PmInternal *midi = (PmInternal *) stream; PmError err; /* arg checking */ @@ -1039,7 +1052,7 @@ void pm_read_short(PmInternal *midi, PmEvent *event) /* * returns how many bytes processed */ -unsigned int pm_read_bytes(PmInternal *midi, unsigned char *data, +unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, int len, PmTimestamp timestamp) { int i = 0; /* index into data, must not be unsigned (!) */ @@ -1084,10 +1097,10 @@ unsigned int pm_read_bytes(PmInternal *midi, unsigned char *data, */ while (i < len && midi->sysex_in_progress) { if (midi->sysex_message_count == 0 && i <= len - 4 && - ((event.message = (((long) data[i]) | - (((long) data[i+1]) << 8) | - (((long) data[i+2]) << 16) | - (((long) data[i+3]) << 24))) & + ((event.message = (((PmMessage) data[i]) | + (((PmMessage) data[i+1]) << 8) | + (((PmMessage) data[i+2]) << 16) | + (((PmMessage) data[i+3]) << 24))) & 0x80808080) == 0) { /* all data, no status */ if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { midi->sysex_in_progress = FALSE; diff --git a/lib-src/portmidi/pm_common/portmidi.h b/lib-src/portmidi/pm_common/portmidi.h index 394a0f075..e07991e0d 100644 --- a/lib-src/portmidi/pm_common/portmidi.h +++ b/lib-src/portmidi/pm_common/portmidi.h @@ -90,7 +90,27 @@ extern "C" { * PM_CHECK_ERRORS more-or-less takes over error checking for return values, * stopping your program and printing error messages when an error * occurs. This also uses stdio for console text I/O. - */ + */ + +#ifndef WIN32 +// Linux and OS X have stdint.h +#include +#else +#ifndef INT32_DEFINED +// rather than having users install a special .h file for windows, +// just put the required definitions inline here. porttime.h uses +// these too, so the definitions are (unfortunately) duplicated there +typedef int int32_t; +typedef unsigned int uint32_t; +#define INT32_DEFINED +#endif +#endif + +#ifdef _WINDLL +#define PMEXPORT __declspec(dllexport) +#else +#define PMEXPORT +#endif #ifndef FALSE #define FALSE 0 @@ -130,13 +150,13 @@ typedef enum { Pm_Initialize() is the library initialisation function - call this before using the library. */ -PmError Pm_Initialize( void ); +PMEXPORT PmError Pm_Initialize( void ); /** Pm_Terminate() is the library termination function - call this after using the library. */ -PmError Pm_Terminate( void ); +PMEXPORT PmError Pm_Terminate( void ); /** A single PortMidiStream is a descriptor for an open MIDI device. */ @@ -157,20 +177,20 @@ typedef void PortMidiStream; the stream, e.g. an input or output operation. Until the error is cleared, no new error codes will be obtained, even for a different stream. */ -int Pm_HasHostError( PortMidiStream * stream ); +PMEXPORT int Pm_HasHostError( PortMidiStream * stream ); /** Translate portmidi error number into human readable message. These strings are constants (set at compile time) so client has no need to allocate storage */ -const char *Pm_GetErrorText( PmError errnum ); +PMEXPORT const char *Pm_GetErrorText( PmError errnum ); /** Translate portmidi host error into human readable message. These strings are computed at run time, so client has to allocate storage. After this routine executes, the host error is cleared. */ -void Pm_GetHostErrorText(char * msg, unsigned int len); +PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len); #define HDRLENGTH 50 #define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less @@ -195,7 +215,7 @@ typedef struct { } PmDeviceInfo; /** Get devices count, ids range from 0 to Pm_CountDevices()-1. */ -int Pm_CountDevices( void ); +PMEXPORT int Pm_CountDevices( void ); /** Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID() @@ -238,15 +258,15 @@ int Pm_CountDevices( void ); On Linux, */ -PmDeviceID Pm_GetDefaultInputDeviceID( void ); +PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID( void ); /** see PmDeviceID Pm_GetDefaultInputDeviceID() */ -PmDeviceID Pm_GetDefaultOutputDeviceID( void ); +PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID( void ); /** PmTimestamp is used to represent a millisecond clock with arbitrary start time. The type is used for all MIDI timestampes and clocks. */ -typedef long PmTimestamp; +typedef int32_t PmTimestamp; typedef PmTimestamp (*PmTimeProcPtr)(void *time_info); /** TRUE if t1 before t2 */ @@ -264,7 +284,7 @@ typedef PmTimestamp (*PmTimeProcPtr)(void *time_info); not be manipulated or freed. The pointer is guaranteed to be valid between calls to Pm_Initialize() and Pm_Terminate(). */ -const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); +PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); /** Pm_OpenInput() and Pm_OpenOutput() open devices. @@ -330,20 +350,20 @@ const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); by calling Pm_Close(). */ -PmError Pm_OpenInput( PortMidiStream** stream, +PMEXPORT PmError Pm_OpenInput( PortMidiStream** stream, PmDeviceID inputDevice, void *inputDriverInfo, - long bufferSize, + int32_t bufferSize, PmTimeProcPtr time_proc, void *time_info ); -PmError Pm_OpenOutput( PortMidiStream** stream, +PMEXPORT PmError Pm_OpenOutput( PortMidiStream** stream, PmDeviceID outputDevice, void *outputDriverInfo, - long bufferSize, + int32_t bufferSize, PmTimeProcPtr time_proc, void *time_info, - long latency ); + int32_t latency ); /** @} */ /** @@ -351,7 +371,7 @@ PmError Pm_OpenOutput( PortMidiStream** stream, @{ */ -/* \function PmError Pm_SetFilter( PortMidiStream* stream, long filters ) +/* \function PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ) Pm_SetFilter() sets filters on an open input stream to drop selected input types. By default, only active sensing messages are filtered. To prohibit, say, active sensing and sysex messages, call @@ -411,21 +431,25 @@ PmError Pm_OpenOutput( PortMidiStream** stream, #define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE) -PmError Pm_SetFilter( PortMidiStream* stream, long filters ); +PMEXPORT PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ); #define Pm_Channel(channel) (1<<(channel)) /** Pm_SetChannelMask() filters incoming messages based on channel. - The mask is a 16-bit bitfield corresponding to appropriate channels + The mask is a 16-bit bitfield corresponding to appropriate channels. The Pm_Channel macro can assist in calling this function. i.e. to set receive only input on channel 1, call with Pm_SetChannelMask(Pm_Channel(1)); Multiple channels should be OR'd together, like Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11)) + Note that channels are numbered 0 to 15 (not 1 to 16). Most + synthesizer and interfaces number channels starting at 1, but + PortMidi numbers channels starting at 0. + All channels are allowed by default */ -PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); +PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); /** Pm_Abort() terminates outgoing messages immediately @@ -435,21 +459,47 @@ PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); ignore messages in the buffer and close an input device at any time. */ -PmError Pm_Abort( PortMidiStream* stream ); +PMEXPORT PmError Pm_Abort( PortMidiStream* stream ); /** Pm_Close() closes a midi stream, flushing any pending buffers. (PortMidi attempts to close open streams when the application exits -- this is particularly difficult under Windows.) */ -PmError Pm_Close( PortMidiStream* stream ); +PMEXPORT PmError Pm_Close( PortMidiStream* stream ); /** - Pm_Message() encodes a short Midi message into a long word. If data1 + Pm_Synchronize() instructs PortMidi to (re)synchronize to the + time_proc passed when the stream was opened. Typically, this + is used when the stream must be opened before the time_proc + reference is actually advancing. In this case, message timing + may be erratic, but since timestamps of zero mean + "send immediately," initialization messages with zero timestamps + can be written without a functioning time reference and without + problems. Before the first MIDI message with a non-zero + timestamp is written to the stream, the time reference must + begin to advance (for example, if the time_proc computes time + based on audio samples, time might begin to advance when an + audio stream becomes active). After time_proc return values + become valid, and BEFORE writing the first non-zero timestamped + MIDI message, call Pm_Synchronize() so that PortMidi can observe + the difference between the current time_proc value and its + MIDI stream time. + + In the more normal case where time_proc + values advance continuously, there is no need to call + Pm_Synchronize. PortMidi will always synchronize at the + first output message and periodically thereafter. +*/ +PmError Pm_Synchronize( PortMidiStream* stream ); + + +/** + Pm_Message() encodes a short Midi message into a 32-bit word. If data1 and/or data2 are not present, use zero. Pm_MessageStatus(), Pm_MessageData1(), and - Pm_MessageData2() extract fields from a long-encoded midi message. + Pm_MessageData2() extract fields from a 32-bit midi message. */ #define Pm_Message(status, data1, data2) \ ((((data2) << 16) & 0xFF0000) | \ @@ -459,7 +509,7 @@ PmError Pm_Close( PortMidiStream* stream ); #define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF) #define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF) -typedef long PmMessage; /**< see PmEvent */ +typedef int32_t PmMessage; /**< see PmEvent */ /** All midi data comes in the form of PmEvent structures. A sysex message is encoded as a sequence of PmEvent structures, with each @@ -560,13 +610,13 @@ typedef struct { message" and will be flushed as well. */ -int Pm_Read( PortMidiStream *stream, PmEvent *buffer, long length ); +PMEXPORT int Pm_Read( PortMidiStream *stream, PmEvent *buffer, int32_t length ); /** Pm_Poll() tests whether input is available, returning TRUE, FALSE, or an error value. */ -PmError Pm_Poll( PortMidiStream *stream); +PMEXPORT PmError Pm_Poll( PortMidiStream *stream); /** Pm_Write() writes midi data from a buffer. This may contain: @@ -581,7 +631,7 @@ PmError Pm_Poll( PortMidiStream *stream); Sysex data may contain embedded real-time messages. */ -PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length ); +PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t length ); /** Pm_WriteShort() writes a timestamped non-system-exclusive midi message. @@ -589,12 +639,12 @@ PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length ); non-decreasing. (But timestamps are ignored if the stream was opened with latency = 0.) */ -PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, long msg); +PMEXPORT PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, int32_t msg); /** Pm_WriteSysEx() writes a timestamped system-exclusive midi message. */ -PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg); +PMEXPORT PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg); /** @} */ diff --git a/lib-src/portmidi/pm_linux/README_LINUX.txt b/lib-src/portmidi/pm_linux/README_LINUX.txt index e928a1fcd..818793e57 100644 --- a/lib-src/portmidi/pm_linux/README_LINUX.txt +++ b/lib-src/portmidi/pm_linux/README_LINUX.txt @@ -1,28 +1,74 @@ README_LINUX.txt for PortMidi Roger Dannenberg -29 Aug 2006 +14 Oct 2009 -To make PortMidi and PortTime, go back up to the portmidi -directory and type +To make PortMidi, you need cmake and the Java SDK. +Go back up to the portmidi directory and type: -make -f pm_linux/Makefile +ccmake . -(You can also copy pm_linux/Makefile to the portmidi -directory and just type "make".) +Type 'c' (configure) and then 'g' (generate). You may have +to manually set JAVA_INCLUDE_PATH and JAVA_JVM_LIBRARY +by typing 't' (toggle to advanced mode) and using the +editor to change the fields. You can find possible values +for JAVA_INCLUDE_PATH by typing "locate jni.h", and for +JAVA_JVM_LIBRARY by typing locate libjvm". + +You also need JAVA_INCLUDE_PATH2, but this will normally +be set automatically after you set JAVA_INCLUDE_PATH and +run "configure" (type "c" to ccmake). Normally, +JAVA_INCLUDE_PATH2 is the linux subdirectory within +JAVA_INCLUDE_PATH. + +Notice that the CMAKE_BUILD_TYPE can be Debug or Release. +Stick with Release if you are not debugging. + +After successfully generating make files with ccmake, you +can run make: + +make The Makefile will build all test programs and the portmidi -library. You may want to modify the Makefile to remove the -PM_CHECK_ERRORS definition. For experimental software, +library. For experimental software, especially programs running from the command line, we -recommend using PM_CHECK_ERRORS -- it will terminate your +recommend using the Debug version -- it will terminate your program and print a helpful message if any PortMidi -function returns an error code. +function returns an error code. (Released software should +check for error codes and handle them, but for quick, +non-critical projects, the automatic "print and die" +handling can save some work.) -If you do not compile with PM_CHECK_ERRORS, you should -check for errors yourself. +THE pmdefaults PROGRAM + +You should install pmdefaults. It provides a graphical interface +for selecting default MIDI IN and OUT devices so that you don't +have to build device selection interfaces into all your programs +and so users have a single place to set a preference. + +Follow the instructions above to run ccmake, making sure that +CMAKE_BUILD_TYPE is Release. Run make as described above. Then: + +sudo make install + +This will install PortMidi libraries and the pmdefault program. +You must alos have the environment variable LD_LIBRARY_PATH set +to include /usr/local/lib (where libpmjni.so is installed). + +Now, you can run pmdefault. + + +SETTING LD_LIBRARY_PATH + +pmdefaults will not work unless LD_LIBRARY_PATH includes a +directory (normally /usr/local/lib) containing libpmjni.so, +installed as described above. + +To set LD_LIBRARY_PATH, you might want to add this to your +~/.profile (if you use the bash shell): + +LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib +export LD_LIBRARY_PATH -This code has not been carefully tested; however, -all test programs in pm_test seem to run properly. A NOTE ABOUT AMD64: @@ -44,6 +90,12 @@ be "PIC-enabled". CHANGELOG +22-jan-2010 Roger B. Dannenberg + Updated instructions about Java paths + +14-oct-2009 Roger B. Dannenberg + Using CMake now for building and configuration + 29-aug-2006 Roger B. Dannenberg Fixed PortTime to join with time thread for clean exit. diff --git a/lib-src/portmidi/pm_linux/pmlinux.c b/lib-src/portmidi/pm_linux/pmlinux.c index d53e68549..5105ecd3f 100644 --- a/lib-src/portmidi/pm_linux/pmlinux.c +++ b/lib-src/portmidi/pm_linux/pmlinux.c @@ -12,6 +12,9 @@ #include "stdlib.h" #include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" + #ifdef PMALSA #include "pmlinuxalsa.h" #endif @@ -20,7 +23,10 @@ #include "pmlinuxnull.h" #endif -PmError pm_init() +PmDeviceID pm_default_input_device_id = -1; +PmDeviceID pm_default_output_device_id = -1; + +void pm_init() { /* Note: it is not an error for PMALSA to fail to initialize. * It may be a design error that the client cannot query what subsystems @@ -33,7 +39,15 @@ PmError pm_init() #ifdef PMNULL pm_linuxnull_init(); #endif - return pmNoError; + // this is set when we return to Pm_Initialize, but we need it + // now in order to (successfully) call Pm_CountDevices() + pm_initialized = TRUE; + pm_default_input_device_id = find_default_device( + "/PortMidi/PM_RECOMMENDED_INPUT_DEVICE", TRUE, + pm_default_input_device_id); + pm_default_output_device_id = find_default_device( + "/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE", FALSE, + pm_default_output_device_id); } void pm_term(void) @@ -43,14 +57,13 @@ void pm_term(void) #endif } -PmDeviceID pm_default_input_device_id = -1; -PmDeviceID pm_default_output_device_id = -1; - PmDeviceID Pm_GetDefaultInputDeviceID() { + Pm_Initialize(); return pm_default_input_device_id; } PmDeviceID Pm_GetDefaultOutputDeviceID() { + Pm_Initialize(); return pm_default_output_device_id; } diff --git a/lib-src/portmidi/pm_linux/pmlinuxalsa.c b/lib-src/portmidi/pm_linux/pmlinuxalsa.c index 2bee83dd0..8e85cfe86 100644 --- a/lib-src/portmidi/pm_linux/pmlinuxalsa.c +++ b/lib-src/portmidi/pm_linux/pmlinuxalsa.c @@ -37,7 +37,6 @@ #define GET_DESCRIPTOR_PORT(info) (((int)(info)) & 0xff) #define BYTE unsigned char -#define UINT unsigned long extern pm_fns_node pm_linuxalsa_in_dictionary; extern pm_fns_node pm_linuxalsa_out_dictionary; @@ -383,12 +382,12 @@ static PmError alsa_abort(PmInternal *midi) #ifdef GARBAGE This is old code here temporarily for reference -static PmError alsa_write(PmInternal *midi, PmEvent *buffer, long length) +static PmError alsa_write(PmInternal *midi, PmEvent *buffer, int32_t length) { alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; int i, bytes; unsigned char byte; - long msg; + PmMessage msg; desc->error = 0; for (; length > 0; length--, buffer++) { @@ -421,7 +420,7 @@ static PmError alsa_write(PmInternal *midi, PmEvent *buffer, long length) } if (desc->error < 0) return pmHostError; - VERBOSE printf("snd_seq_drain_output: 0x%x\n", seq); + VERBOSE printf("snd_seq_drain_output: 0x%x\n", (unsigned int) seq); desc->error = snd_seq_drain_output(seq); if (desc->error < 0) return pmHostError; @@ -446,7 +445,7 @@ static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp) static PmError alsa_write_short(PmInternal *midi, PmEvent *event) { int bytes = midi_message_length(event->message); - long msg = event->message; + PmMessage msg = event->message; int i; alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; for (i = 0; i < bytes; i++) { diff --git a/lib-src/portmidi/pm_mac/README_MAC.txt b/lib-src/portmidi/pm_mac/README_MAC.txt index fe8e404a6..25748807f 100644 --- a/lib-src/portmidi/pm_mac/README_MAC.txt +++ b/lib-src/portmidi/pm_mac/README_MAC.txt @@ -1,46 +1,147 @@ README_MAC.txt for PortMidi Roger Dannenberg -17 jan 2007 +20 nov 2009 -To build PortMidi for Mac OS X: +To build PortMidi for Mac OS X, you must install Xcode, and +if you want to build from the command line, you should install +CMake. -==== USING MAKE ==== +CMake can build either Makefiles or Xcode projects, or you +can use the pre-built Xcode project. These approaches are +described in separate sections below. -go back up to the portmidi -directory and type +==== CLEANING UP ==== +You can remove previously built apps, object code, and libraries by +running "cd pm_mac; sh cleanslate.sh" + +==== USING CMAKE (AND COMMAND LINE TOOLS) ==== + +Start in the portmedia/portmidi directory. make -f pm_mac/Makefile.osx -(You can also copy pm_mac/Makefile.osx to Makfile in the -portmidi directory and just type "make".) +(Begin note: make will invoke cmake to build a Makefile and then make to +build portmidi. This extra level allows you to correctly build +both Release and Debug versions. Release is the default, so to get +the Debug version, use: -The Makefile.osx will build all test programs and the portmidi -library. You may want to modify the Makefile.osx to remove the -PM_CHECK_ERRORS definition. For experimental software, -especially programs running from the command line, we -recommend using PM_CHECK_ERRORS -- it will terminate your -program and print a helpful message if any PortMidi -function returns an error code. +make -f pm_mac/Makefile.osx configuration=Debug +) -If you do not compile with PM_CHECK_ERRORS, you should -check for errors yourself. +Release version executables and libraries are now in + portmedia/portmidi/Release -The make file will also build an OS X Universal (ppc and i386) -dynamic link library using xcode. For instructions about this -and other options, type +Debug version executables and libraries are created in + portmedia/portmidi/Debug +The Debug versions are compiled with PM_CHECK_ERRORS which +prints an error message and aborts when an error code is returned +by PortMidi functions. This is useful for small command line +applications. Otherwise, you should check and handle error returns +in your program. + +You can install portmidi as follows: + +cd Release; sudo make install + +This will install /usr/local/include/{portmidi.h, porttime.h} +and /usr/local/lib/{libportmidi.dylib, libportmidi_s.a, libpmjni.dylib} + +You should now make the pmdefaults.app: + +make -f pm_mac/Makefile.osx pmdefaults + +NOTES: xcode is likely to crash after building pmdefaults, but + pmdefaults should be OK (it will be in Release) + Once you get started, you can run make directly in the + Debug or Release directories -make -f pm_mac/Makefile.osx help ==== USING XCODE ==== -Open portmidi/pm_mac/pm_mac.xcode with Xcode and -build what you need: if you are just exploring, start with -the lib+test suite. +(1) Open portmidi/portmidi.xcodeproj with Xcode and +build what you need. The simplest thing is to build the +ALL_BUILD target. The default will be to build the Debug +version, but you may want to change this to Release. + +The Debug version is compiled with PM_CHECK_ERRORS, and the +Release version is not. PM_CHECK_ERRORS will print an error +message and exit your program if any error is returned from +a call into PortMidi. + +CMake (currently) also creates MinSizRel and RelWithDebInfo +versions, but only because I cannot figure out how to disable +them. + +You will probably want the application PmDefaults, which sets +default MIDI In and Out devices for PortMidi. You may also +want to build a Java application using PortMidi. Since I have +not figured out how to use CMake to make an OS X Java application, +use pm_mac/pm_mac.xcodeproj as follows: + +(2) open pm_mac/pm_mac.xcodeproj + +(3) pm_java/pmjni/portmidi_JportmidiApi.h is needed +by libpmjni.jnilib, the Java native interface library. Since +portmidi_JportmidiApi.h is included with PortMidi, you can skip +to step 4, but if you really want to rebuild everything from +scratch, build the JPortMidiHeaders project. + +(4) If you did not build libpmjni.dylib using portmidi.xcodeproj, +do it now. (It depends on portmidi_JportmidiApi.h, and the +PmDefaults project depends on libpmjni.dylib.) + +(5) Returning to pm_mac.xcodeproj, build the PmDefaults program. + +(6) If you wish, copy pm_mac/build/Deployment/PmDefaults.app to +your applications folder. + +(7) If you want to install libportmidi.dylib, first make it with +Xcode, then + sudo make -f pm_mac/Makefile.osx install +This command will install /usr/local/include/{porttime.h, portmidi.h} +and /usr/local/lib/libportmidi.dylib +Note that the "install" function of xcode creates portmidi/Release +and does not install the library to /usr/local/lib, so please use +the command line installer. + +==== USING CMAKE TO BUILD Xcode PROJECT ==== + +(1) Install CMake if you do not have it already. + +(2) Open portmedia/portmidi/CMakeLists.txt with CMake + +(3) Use Configure and Generate buttons + +(4) This creates portmedia/portmidi/portmidi.xcodeproj. + +(5) Follow the directions above using Xcode to compile +PortMidi. + +Notes: + (1) You will also use pm_mac/pm_mac.xcodeproj, which +is not generated by CMake. + (2) The portmidi.xcodeproj created by CMake will have absolute +paths and depend on CMake, so it will not be very portable to other +machines or even directories. You can cd to pm_mac and run +clean_up_project.sh to convert pm_mac.xcodeproj to use relative +paths and to remove the scripts that run CMake. You'll first have +to modify pm_mac/clean_up_project.awk to contain the particular +absolute path or your portmidi project. Also, this is a pretty simple +and probably fragile hack to make a stand-alone xcode project. I +don't recommend it. Instead, either use CMake all the time, or use +the portmidi.xcodeproj you get with the distribution. -[pm_mac.xcodeproj courtesy of Leigh Smith] CHANGELOG +20-Nov-2009 Roger B. Dannenberg + Added some install instructions +26-Sep-2009 Roger B. Dannenberg + More changes for using CMake, Makefiles, XCode +20-Sep-2009 Roger B. Dannenberg + Modifications for using CMake +14-Sep-2009 Roger B. Dannenberg + Modifications for using CMake 17-Jan-2007 Roger B. Dannenberg Explicit instructions for Xcode 15-Jan-2007 Roger B. Dannenberg diff --git a/lib-src/portmidi/pm_mac/finddefault.c b/lib-src/portmidi/pm_mac/finddefault.c index a155c9e41..59e02a10b 100644 --- a/lib-src/portmidi/pm_mac/finddefault.c +++ b/lib-src/portmidi/pm_mac/finddefault.c @@ -5,6 +5,8 @@ #include #include #include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" #include "pmmacosxcm.h" #include "readbinaryplist.h" diff --git a/lib-src/portmidi/pm_mac/pm_mac.xcodeproj/project.pbxproj b/lib-src/portmidi/pm_mac/pm_mac.xcodeproj/project.pbxproj index 20b9a0a63..bb2f284cc 100644 --- a/lib-src/portmidi/pm_mac/pm_mac.xcodeproj/project.pbxproj +++ b/lib-src/portmidi/pm_mac/pm_mac.xcodeproj/project.pbxproj @@ -14,16 +14,7 @@ ); comments = "Builds the Library and all the test shell tools."; dependencies = ( - 3D8DABA50D57B68A000FEBA8 /* PBXTargetDependency */, - 3D4799D40F0E87350006C842 /* PBXTargetDependency */, - D8684B980ACBFB9D009F6478 /* PBXTargetDependency */, - D8684B9A0ACBFB9D009F6478 /* PBXTargetDependency */, - D8684B9C0ACBFB9D009F6478 /* PBXTargetDependency */, - D8684B9E0ACBFB9D009F6478 /* PBXTargetDependency */, - D8684BA00ACBFB9D009F6478 /* PBXTargetDependency */, - D8684BA20ACBFB9D009F6478 /* PBXTargetDependency */, - 3D4799F50F0EE7D10006C842 /* PBXTargetDependency */, - D8E738E20ACDA3BD0030B95B /* PBXTargetDependency */, + 3D3B2826104D8B740043A026 /* PBXTargetDependency */, 3D3534900F0D2DB30059CD05 /* PBXTargetDependency */, ); name = "everything suite"; @@ -40,17 +31,17 @@ 3D9D57CA0E12E22800C4C68D /* Resources */, 3D9D57CB0E12E22800C4C68D /* JavaArchive */, 3D9D57CC0E12E22800C4C68D /* Frameworks */, + 3D1A6BA30FC8A92D008E9959 /* CopyFiles */, ); comments = "Application to set PortMidi default input and output devices."; dependencies = ( - 3D4799F80F0EE7FB0006C842 /* PBXTargetDependency */, ); name = PmDefaults; productInstallPath = "$(USER_APPS_DIR)"; productName = pmdefaults; productReference = 3D9D57CE0E12E22800C4C68D /* pmdefaults.app */; productSettingsXML = " - + CFBundleDevelopmentRegion @@ -79,6 +70,10 @@ $JAVAROOT/pmdefaults.jar + JVMArchs + + i386 + JVMVersion 1.5 MainClass @@ -105,102 +100,23 @@ 3D17A16D0E13D70E00930576 /* JPortMidiApi.java in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57E20E12E39B00C4C68D /* JPortMidiApi.java */; }; 3D17A16E0E13D70E00930576 /* PmDefaults.java in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57EF0E12E3DE00C4C68D /* PmDefaults.java */; }; 3D17A16F0E13D70E00930576 /* PmDefaultsFrame.java in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57F50E12E3DE00C4C68D /* PmDefaultsFrame.java */; }; - 3D17A1A20E13E30100930576 /* pmjni.c in Sources */ = {isa = PBXBuildFile; fileRef = 3D17A1A10E13E30100930576 /* pmjni.c */; }; - 3D17A1A90E13E3B300930576 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774480663EAE60002CE69 /* CoreMIDI.framework */; }; - 3D17A1AF0E13E3BC00930576 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */; }; - 3D17A1DF0E13E3D800930576 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774500663EBBF0002CE69 /* CoreAudio.framework */; }; - 3D17A1E80E13E57700930576 /* JavaVM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D17A1E70E13E57700930576 /* JavaVM.framework */; }; - 3D17A20A0E13E7E900930576 /* readbinaryplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57C50E12E1E500C4C68D /* readbinaryplist.c */; }; - 3D17A20B0E13E7E900930576 /* finddefault.c in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57C30E12E1CF00C4C68D /* finddefault.c */; }; - 3D17A20D0E13E88100930576 /* pmutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D524047955C800EE98CD /* pmutil.c */; }; - 3D17A20E0E13E88100930576 /* portmidi.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D526047955C800EE98CD /* portmidi.c */; }; - 3D17A20F0E13E88100930576 /* pmmacosxcm.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D5310479560B00EE98CD /* pmmacosxcm.c */; }; - 3D17A2100E13E88100930576 /* pmmac.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D5740479613900EE98CD /* pmmac.c */; }; - 3D17A2110E13E88100930576 /* ptmacosx_mach.c in Sources */ = {isa = PBXBuildFile; fileRef = 58ED5B81047D41DB00B92E62 /* ptmacosx_mach.c */; }; - 3D17A2120E13E88100930576 /* readbinaryplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57C50E12E1E500C4C68D /* readbinaryplist.c */; }; - 3D17A2130E13E88100930576 /* finddefault.c in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57C30E12E1CF00C4C68D /* finddefault.c */; }; - 3D17A2290E13EF8000930576 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D17A2280E13EF8000930576 /* CoreServices.framework */; }; - 3D17A22D0E13EF9300930576 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D17A2280E13EF8000930576 /* CoreServices.framework */; }; 3D17A4C40E14079000930576 /* pmdefaults.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3D17A4C30E14079000930576 /* pmdefaults.icns */; }; - 3D65FEE30ECB7548008083A3 /* CoreServices in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D65FEE20ECB7548008083A3 /* CoreServices */; }; - 3D65FEE40ECB7548008083A3 /* CoreServices in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D65FEE20ECB7548008083A3 /* CoreServices */; }; - 3D65FEE50ECB7548008083A3 /* CoreServices in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D65FEE20ECB7548008083A3 /* CoreServices */; }; - 3D65FEE60ECB7548008083A3 /* CoreServices in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D65FEE20ECB7548008083A3 /* CoreServices */; }; - 3D65FEE70ECB7548008083A3 /* CoreServices in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D65FEE20ECB7548008083A3 /* CoreServices */; }; - 3D65FEE80ECB7548008083A3 /* CoreServices in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D65FEE20ECB7548008083A3 /* CoreServices */; }; - 3D65FEE90ECB7548008083A3 /* CoreServices in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D65FEE20ECB7548008083A3 /* CoreServices */; }; - 3D9D57C40E12E1CF00C4C68D /* finddefault.c in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57C30E12E1CF00C4C68D /* finddefault.c */; }; - 3D9D57C60E12E1E500C4C68D /* readbinaryplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57C50E12E1E500C4C68D /* readbinaryplist.c */; }; + 3D29FEC2105EDCFA009417D8 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D17A2280E13EF8000930576 /* CoreServices.framework */; }; + 3D3B2818104D8AF90043A026 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774480663EAE60002CE69 /* CoreMIDI.framework */; }; + 3D3B2819104D8AF90043A026 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */; }; + 3D3B281A104D8AF90043A026 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774500663EBBF0002CE69 /* CoreAudio.framework */; }; + 3D85B13E10672F59003F6706 /* libpmjni.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3D85B13D10672F59003F6706 /* libpmjni.dylib */; }; 3D9D57E60E12E39B00C4C68D /* JPortMidi.java in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57DE0E12E39B00C4C68D /* JPortMidi.java */; }; 3D9D57EA0E12E39B00C4C68D /* JPortMidiApi.java in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57E20E12E39B00C4C68D /* JPortMidiApi.java */; }; 3D9D57EC0E12E39B00C4C68D /* JPortMidiException.java in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57E40E12E39B00C4C68D /* JPortMidiException.java */; }; 3D9D57F80E12E3DE00C4C68D /* PmDefaults.java in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57EF0E12E3DE00C4C68D /* PmDefaults.java */; }; 3D9D57FE0E12E3DE00C4C68D /* PmDefaultsFrame.java in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D57F50E12E3DE00C4C68D /* PmDefaultsFrame.java */; }; 3D9D57FF0E12E3DE00C4C68D /* portmusic_logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D9D57F60E12E3DE00C4C68D /* portmusic_logo.png */; }; - 3DA60D640D3FBEA40036B654 /* libportmidi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D81440D40ACB36E100515C41 /* libportmidi.a */; }; - 3DA60D650D3FBEA40036B654 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774480663EAE60002CE69 /* CoreMIDI.framework */; }; - 3DA60D660D3FBEA40036B654 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */; }; - 3DA60D670D3FBEA40036B654 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774500663EBBF0002CE69 /* CoreAudio.framework */; }; - 3DA60D720D3FBF160036B654 /* midiclock.c in Sources */ = {isa = PBXBuildFile; fileRef = 3DA60D710D3FBF160036B654 /* midiclock.c */; }; - 3DBF246D0B59686800DAD93B /* mm.c in Sources */ = {isa = PBXBuildFile; fileRef = 3DBF246C0B59686800DAD93B /* mm.c */; }; - 3DBF24720B59691500DAD93B /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774480663EAE60002CE69 /* CoreMIDI.framework */; }; - 3DBF24780B59691800DAD93B /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */; }; - 3DBF24A10B59691B00DAD93B /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774500663EBBF0002CE69 /* CoreAudio.framework */; }; - 3DBF24A90B59692C00DAD93B /* libportmidi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D81440D40ACB36E100515C41 /* libportmidi.a */; }; - D814404E0ACB2C1200515C41 /* pmutil.h in Headers */ = {isa = PBXBuildFile; fileRef = 5851D525047955C800EE98CD /* pmutil.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D814404F0ACB2C1200515C41 /* portmidi.h in Headers */ = {isa = PBXBuildFile; fileRef = 5851D527047955C800EE98CD /* portmidi.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D81440510ACB2C1200515C41 /* porttime.h in Headers */ = {isa = PBXBuildFile; fileRef = 5851D51F047955B800EE98CD /* porttime.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D81440540ACB2C1200515C41 /* pmutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D524047955C800EE98CD /* pmutil.c */; }; - D81440550ACB2C1200515C41 /* portmidi.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D526047955C800EE98CD /* portmidi.c */; }; - D81440560ACB2C1200515C41 /* pmmacosxcm.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D5310479560B00EE98CD /* pmmacosxcm.c */; }; - D81440570ACB2C1200515C41 /* pmmac.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D5740479613900EE98CD /* pmmac.c */; }; - D81440580ACB2C1200515C41 /* ptmacosx_mach.c in Sources */ = {isa = PBXBuildFile; fileRef = 58ED5B81047D41DB00B92E62 /* ptmacosx_mach.c */; }; - D84A40DC0ACD4A19001FC716 /* pmutil.h in Headers */ = {isa = PBXBuildFile; fileRef = 5851D525047955C800EE98CD /* pmutil.h */; }; - D84A40DD0ACD4A19001FC716 /* portmidi.h in Headers */ = {isa = PBXBuildFile; fileRef = 5851D527047955C800EE98CD /* portmidi.h */; }; - D84A40DF0ACD4A19001FC716 /* porttime.h in Headers */ = {isa = PBXBuildFile; fileRef = 5851D51F047955B800EE98CD /* porttime.h */; }; - D84A40E10ACD4A26001FC716 /* pmutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D524047955C800EE98CD /* pmutil.c */; }; - D84A40E20ACD4A26001FC716 /* portmidi.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D526047955C800EE98CD /* portmidi.c */; }; - D84A40E30ACD4A26001FC716 /* pmmacosxcm.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D5310479560B00EE98CD /* pmmacosxcm.c */; }; - D84A40E40ACD4A26001FC716 /* pmmac.c in Sources */ = {isa = PBXBuildFile; fileRef = 5851D5740479613900EE98CD /* pmmac.c */; }; - D84A40E50ACD4A26001FC716 /* ptmacosx_mach.c in Sources */ = {isa = PBXBuildFile; fileRef = 58ED5B81047D41DB00B92E62 /* ptmacosx_mach.c */; }; - D84A41220ACD4A92001FC716 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774480663EAE60002CE69 /* CoreMIDI.framework */; }; - D84A41230ACD4A92001FC716 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */; }; - D84A41240ACD4A92001FC716 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774500663EBBF0002CE69 /* CoreAudio.framework */; }; - D8684B370ACBF9AF009F6478 /* libportmidi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D81440D40ACB36E100515C41 /* libportmidi.a */; }; - D8684B380ACBF9B9009F6478 /* test.c in Sources */ = {isa = PBXBuildFile; fileRef = 3DA774260663E9DA0002CE69 /* test.c */; }; - D8684B460ACBFA24009F6478 /* midithread.c in Sources */ = {isa = PBXBuildFile; fileRef = 3DA774160663E9DA0002CE69 /* midithread.c */; }; - D8684B470ACBFA2D009F6478 /* libportmidi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D81440D40ACB36E100515C41 /* libportmidi.a */; }; - D8684B540ACBFA34009F6478 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774480663EAE60002CE69 /* CoreMIDI.framework */; }; - D8684B550ACBFA34009F6478 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */; }; - D8684B560ACBFA34009F6478 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774500663EBBF0002CE69 /* CoreAudio.framework */; }; - D8684B570ACBFA3E009F6478 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774480663EAE60002CE69 /* CoreMIDI.framework */; }; - D8684B580ACBFA3E009F6478 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */; }; - D8684B590ACBFA3E009F6478 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774500663EBBF0002CE69 /* CoreAudio.framework */; }; - D8684B650ACBFA9D009F6478 /* midithru.c in Sources */ = {isa = PBXBuildFile; fileRef = 3DA7741B0663E9DA0002CE69 /* midithru.c */; }; - D8684B660ACBFAA5009F6478 /* libportmidi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D81440D40ACB36E100515C41 /* libportmidi.a */; }; - D8684B670ACBFAA5009F6478 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774480663EAE60002CE69 /* CoreMIDI.framework */; }; - D8684B680ACBFAA5009F6478 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */; }; - D8684B690ACBFAA5009F6478 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774500663EBBF0002CE69 /* CoreAudio.framework */; }; - D8684B790ACBFAF7009F6478 /* sysex.c in Sources */ = {isa = PBXBuildFile; fileRef = 3DA774210663E9DA0002CE69 /* sysex.c */; }; - D8684B7A0ACBFAFD009F6478 /* libportmidi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D81440D40ACB36E100515C41 /* libportmidi.a */; }; - D8684B7B0ACBFAFD009F6478 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774480663EAE60002CE69 /* CoreMIDI.framework */; }; - D8684B7C0ACBFAFD009F6478 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */; }; - D8684B7D0ACBFAFD009F6478 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774500663EBBF0002CE69 /* CoreAudio.framework */; }; - D8684B850ACBFB2C009F6478 /* latency.c in Sources */ = {isa = PBXBuildFile; fileRef = 3DA774110663E9DA0002CE69 /* latency.c */; }; - D8684B860ACBFB33009F6478 /* libportmidi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D81440D40ACB36E100515C41 /* libportmidi.a */; }; - D8684B870ACBFB33009F6478 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774480663EAE60002CE69 /* CoreMIDI.framework */; }; - D8684B880ACBFB33009F6478 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */; }; - D8684B890ACBFB33009F6478 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DA774500663EBBF0002CE69 /* CoreAudio.framework */; }; + 3DB8DD8810614B7700CD6802 /* test.c in Sources */ = {isa = PBXBuildFile; fileRef = 3DB8DD8710614B7700CD6802 /* test.c */; }; + 3DEEAAE3108CA7B40005BF4F /* libportmidi.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DEEAAE2108CA7B40005BF4F /* libportmidi.dylib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 3D17A1EB0E13E5D100930576 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = 3D17A13C0E13D47F00930576; - remoteInfo = JPortMidiHeaders; - }; 3D35348F0F0D2DB30059CD05 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5851D5180479552300EE98CD /* Project object */; @@ -208,145 +124,36 @@ remoteGlobalIDString = 3D9D57CD0E12E22800C4C68D; remoteInfo = PmDefaults; }; - 3D4799D30F0E87350006C842 /* PBXContainerItemProxy */ = { + 3D3B2825104D8B740043A026 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5851D5180479552300EE98CD /* Project object */; proxyType = 1; - remoteGlobalIDString = 3DBF24630B5967A500DAD93B /* mm */; - remoteInfo = mm; - }; - 3D4799F40F0EE7D10006C842 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = 3D17A1900E13E25E00930576 /* libpmjni.jnilib */; - remoteInfo = libpmjni.jnilib; - }; - 3D4799F70F0EE7FB0006C842 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = 3D17A1900E13E25E00930576 /* libpmjni.jnilib */; - remoteInfo = libpmjni.jnilib; - }; - 3D8DABA40D57B68A000FEBA8 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = 3DA60D5E0D3FBEA40036B654; - remoteInfo = midiclock; - }; - 3DA60D600D3FBEA40036B654 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D814404B0ACB2C1200515C41; - remoteInfo = libportmidi.a; - }; - 3DBF24700B5968BC00DAD93B /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D814404B0ACB2C1200515C41; - remoteInfo = libportmidi.a; - }; - D8684B080ACBF996009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D814404B0ACB2C1200515C41; - remoteInfo = libportmidi.a; - }; - D8684B440ACBFA12009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D814404B0ACB2C1200515C41; - remoteInfo = libportmidi.a; - }; - D8684B630ACBFA8D009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D814404B0ACB2C1200515C41; - remoteInfo = libportmidi.a; - }; - D8684B6F0ACBFAD0009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D814404B0ACB2C1200515C41; - remoteInfo = libportmidi.a; - }; - D8684B830ACBFB13009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D814404B0ACB2C1200515C41; - remoteInfo = libportmidi.a; - }; - D8684B970ACBFB9D009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D814404B0ACB2C1200515C41; - remoteInfo = libportmidi.a; - }; - D8684B990ACBFB9D009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D8684B050ACBF977009F6478; - remoteInfo = test; - }; - D8684B9B0ACBFB9D009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D8684B410ACBFA09009F6478; - remoteInfo = midithread; - }; - D8684B9D0ACBFB9D009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D8684B600ACBFA75009F6478; - remoteInfo = midithru; - }; - D8684B9F0ACBFB9D009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D8684B6C0ACBFAB7009F6478; - remoteInfo = sysex; - }; - D8684BA10ACBFB9D009F6478 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D8684B800ACBFB0B009F6478; - remoteInfo = latency; - }; - D8E738E10ACDA3BD0030B95B /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 5851D5180479552300EE98CD /* Project object */; - proxyType = 1; - remoteGlobalIDString = D84A40D90ACD4A08001FC716; - remoteInfo = portmidi; + remoteGlobalIDString = 3D3B2811104D8AF90043A026; + remoteInfo = "test-dylib"; }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 3D1A6BA30FC8A92D008E9959 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 15; + files = ( + 3D85B13E10672F59003F6706 /* libpmjni.dylib in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - 3D17A13D0E13D47F00930576 /* JPortMidiHeaders.jar */ = {isa = PBXFileReference; explicitFileType = archive.jar; includeInIndex = 0; path = JPortMidiHeaders.jar; sourceTree = BUILT_PRODUCTS_DIR; }; - 3D17A18C0E13E1EF00930576 /* jportmidi_JportMidiApi.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jportmidi_JportMidiApi.h; path = build/Development/jportmidi_JportMidiApi.h; sourceTree = ""; }; - 3D17A1910E13E25E00930576 /* libpmjni.jnilib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libpmjni.jnilib; sourceTree = BUILT_PRODUCTS_DIR; }; + 3D17A18C0E13E1EF00930576 /* jportmidi_JportMidiApi.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = jportmidi_JportMidiApi.h; path = ../pm_java/pmjni/jportmidi_JportMidiApi.h; sourceTree = ""; }; 3D17A1A10E13E30100930576 /* pmjni.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pmjni.c; path = ../pm_java/pmjni/pmjni.c; sourceTree = SOURCE_ROOT; }; 3D17A1E70E13E57700930576 /* JavaVM.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaVM.framework; path = /System/Library/Frameworks/JavaVM.framework; sourceTree = ""; }; 3D17A2280E13EF8000930576 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = /System/Library/Frameworks/CoreServices.framework; sourceTree = ""; }; 3D17A4C30E14079000930576 /* pmdefaults.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = pmdefaults.icns; path = ../pm_java/pmdefaults/pmdefaults.icns; sourceTree = SOURCE_ROOT; }; - 3D65FEE20ECB7548008083A3 /* CoreServices */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = CoreServices; path = /System/Library/Frameworks/CoreServices.framework/CoreServices; sourceTree = ""; }; - 3D9D57C30E12E1CF00C4C68D /* finddefault.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = finddefault.c; sourceTree = ""; }; - 3D9D57C50E12E1E500C4C68D /* readbinaryplist.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = readbinaryplist.c; sourceTree = ""; }; + 3D3B2820104D8AF90043A026 /* test-dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "test-dylib"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3D85B13D10672F59003F6706 /* libpmjni.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpmjni.dylib; path = ../Release/libpmjni.dylib; sourceTree = SOURCE_ROOT; }; 3D9D57CE0E12E22800C4C68D /* pmdefaults.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = pmdefaults.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3D9D57DE0E12E39B00C4C68D /* JPortMidi.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = JPortMidi.java; sourceTree = ""; }; 3D9D57E20E12E39B00C4C68D /* JPortMidiApi.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = JPortMidiApi.java; sourceTree = ""; }; @@ -354,43 +161,12 @@ 3D9D57EF0E12E3DE00C4C68D /* PmDefaults.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PmDefaults.java; sourceTree = ""; }; 3D9D57F50E12E3DE00C4C68D /* PmDefaultsFrame.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = PmDefaultsFrame.java; sourceTree = ""; }; 3D9D57F60E12E3DE00C4C68D /* portmusic_logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = portmusic_logo.png; sourceTree = ""; }; - 3DA60D6C0D3FBEA40036B654 /* midiclock */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = midiclock; sourceTree = BUILT_PRODUCTS_DIR; }; - 3DA60D710D3FBF160036B654 /* midiclock.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = midiclock.c; sourceTree = ""; }; - 3DA774110663E9DA0002CE69 /* latency.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = latency.c; path = ../pm_test/latency.c; sourceTree = SOURCE_ROOT; }; - 3DA774160663E9DA0002CE69 /* midithread.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = midithread.c; path = ../pm_test/midithread.c; sourceTree = SOURCE_ROOT; }; - 3DA7741B0663E9DA0002CE69 /* midithru.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = midithru.c; path = ../pm_test/midithru.c; sourceTree = SOURCE_ROOT; }; - 3DA774210663E9DA0002CE69 /* sysex.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = sysex.c; path = ../pm_test/sysex.c; sourceTree = SOURCE_ROOT; }; - 3DA774260663E9DA0002CE69 /* test.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = test.c; path = ../pm_test/test.c; sourceTree = SOURCE_ROOT; }; - 3DA7742B0663E9DA0002CE69 /* txdata.syx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; name = txdata.syx; path = ../pm_test/txdata.syx; sourceTree = SOURCE_ROOT; }; 3DA774480663EAE60002CE69 /* CoreMIDI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMIDI.framework; path = /System/Library/Frameworks/CoreMIDI.framework; sourceTree = ""; }; 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = ""; }; 3DA774500663EBBF0002CE69 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = /System/Library/Frameworks/CoreAudio.framework; sourceTree = ""; }; - 3DBF24640B5967A500DAD93B /* mm */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mm; sourceTree = BUILT_PRODUCTS_DIR; }; - 3DBF246C0B59686800DAD93B /* mm.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = mm.c; sourceTree = ""; }; - 5851D51E047955B800EE98CD /* porttime.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = porttime.c; path = ../porttime/porttime.c; sourceTree = SOURCE_ROOT; }; - 5851D51F047955B800EE98CD /* porttime.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = porttime.h; path = ../porttime/porttime.h; sourceTree = SOURCE_ROOT; }; - 5851D521047955B800EE98CD /* ptmacosx_cf.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = ptmacosx_cf.c; path = ../porttime/ptmacosx_cf.c; sourceTree = SOURCE_ROOT; }; - 5851D523047955C800EE98CD /* pminternal.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = pminternal.h; path = ../pm_common/pminternal.h; sourceTree = SOURCE_ROOT; }; - 5851D524047955C800EE98CD /* pmutil.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pmutil.c; path = ../pm_common/pmutil.c; sourceTree = SOURCE_ROOT; }; - 5851D525047955C800EE98CD /* pmutil.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = pmutil.h; path = ../pm_common/pmutil.h; sourceTree = SOURCE_ROOT; }; - 5851D526047955C800EE98CD /* portmidi.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = portmidi.c; path = ../pm_common/portmidi.c; sourceTree = SOURCE_ROOT; }; - 5851D527047955C800EE98CD /* portmidi.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = portmidi.h; path = ../pm_common/portmidi.h; sourceTree = SOURCE_ROOT; }; - 5851D528047955D700EE98CD /* pmlinux.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pmlinux.c; path = ../pm_linux/pmlinux.c; sourceTree = SOURCE_ROOT; }; - 5851D529047955D700EE98CD /* pmlinux.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = pmlinux.h; path = ../pm_linux/pmlinux.h; sourceTree = SOURCE_ROOT; }; - 5851D52A047955D700EE98CD /* pmlinuxalsa.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = pmlinuxalsa.c; path = ../pm_linux/pmlinuxalsa.c; sourceTree = SOURCE_ROOT; }; - 5851D52B047955D700EE98CD /* pmlinuxalsa.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = pmlinuxalsa.h; path = ../pm_linux/pmlinuxalsa.h; sourceTree = SOURCE_ROOT; }; - 5851D5310479560B00EE98CD /* pmmacosxcm.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = pmmacosxcm.c; sourceTree = SOURCE_ROOT; }; - 5851D5320479560B00EE98CD /* pmmacosxcm.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = pmmacosxcm.h; sourceTree = SOURCE_ROOT; }; - 5851D5730479613900EE98CD /* pmmac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pmmac.h; sourceTree = SOURCE_ROOT; }; - 5851D5740479613900EE98CD /* pmmac.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pmmac.c; sourceTree = SOURCE_ROOT; }; - 58ED5B81047D41DB00B92E62 /* ptmacosx_mach.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = ptmacosx_mach.c; path = ../porttime/ptmacosx_mach.c; sourceTree = SOURCE_ROOT; }; - D81440D40ACB36E100515C41 /* libportmidi.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libportmidi.a; sourceTree = BUILT_PRODUCTS_DIR; }; - D84A40DA0ACD4A08001FC716 /* libportmidi.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libportmidi.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; - D8684B060ACBF977009F6478 /* test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = test; sourceTree = BUILT_PRODUCTS_DIR; }; - D8684B420ACBFA09009F6478 /* midithread */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = midithread; sourceTree = BUILT_PRODUCTS_DIR; }; - D8684B610ACBFA75009F6478 /* midithru */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = midithru; sourceTree = BUILT_PRODUCTS_DIR; }; - D8684B6D0ACBFAB7009F6478 /* sysex */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = sysex; sourceTree = BUILT_PRODUCTS_DIR; }; - D8684B810ACBFB0B009F6478 /* latency */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = latency; sourceTree = BUILT_PRODUCTS_DIR; }; + 3DB8DD8710614B7700CD6802 /* test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = test.c; path = ../pm_test/test.c; sourceTree = SOURCE_ROOT; }; + 3DB8DDB8106189B200CD6802 /* JPortMidiHeaders.jar */ = {isa = PBXFileReference; explicitFileType = archive.jar; includeInIndex = 0; path = JPortMidiHeaders.jar; sourceTree = BUILT_PRODUCTS_DIR; }; + 3DEEAAE2108CA7B40005BF4F /* libportmidi.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libportmidi.dylib; path = ../Release/libportmidi.dylib; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -401,15 +177,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 3D17A18F0E13E25E00930576 /* Frameworks */ = { + 3D3B2816104D8AF90043A026 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3D17A1DF0E13E3D800930576 /* CoreAudio.framework in Frameworks */, - 3D17A1AF0E13E3BC00930576 /* CoreFoundation.framework in Frameworks */, - 3D17A1A90E13E3B300930576 /* CoreMIDI.framework in Frameworks */, - 3D17A1E80E13E57700930576 /* JavaVM.framework in Frameworks */, - 3D17A2290E13EF8000930576 /* CoreServices.framework in Frameworks */, + 3D3B2818104D8AF90043A026 /* CoreMIDI.framework in Frameworks */, + 3D3B2819104D8AF90043A026 /* CoreFoundation.framework in Frameworks */, + 3D3B281A104D8AF90043A026 /* CoreAudio.framework in Frameworks */, + 3D29FEC2105EDCFA009417D8 /* CoreServices.framework in Frameworks */, + 3DEEAAE3108CA7B40005BF4F /* libportmidi.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -420,108 +196,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 3DA60D630D3FBEA40036B654 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3DA60D640D3FBEA40036B654 /* libportmidi.a in Frameworks */, - 3DA60D650D3FBEA40036B654 /* CoreMIDI.framework in Frameworks */, - 3DA60D660D3FBEA40036B654 /* CoreFoundation.framework in Frameworks */, - 3DA60D670D3FBEA40036B654 /* CoreAudio.framework in Frameworks */, - 3D65FEE90ECB7548008083A3 /* CoreServices in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3DBF24620B5967A500DAD93B /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3DBF24A90B59692C00DAD93B /* libportmidi.a in Frameworks */, - 3DBF24720B59691500DAD93B /* CoreMIDI.framework in Frameworks */, - 3DBF24780B59691800DAD93B /* CoreFoundation.framework in Frameworks */, - 3DBF24A10B59691B00DAD93B /* CoreAudio.framework in Frameworks */, - 3D65FEE80ECB7548008083A3 /* CoreServices in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D81440590ACB2C1200515C41 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D84A40D80ACD4A08001FC716 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3D17A22D0E13EF9300930576 /* CoreServices.framework in Frameworks */, - D84A41220ACD4A92001FC716 /* CoreMIDI.framework in Frameworks */, - D84A41230ACD4A92001FC716 /* CoreFoundation.framework in Frameworks */, - D84A41240ACD4A92001FC716 /* CoreAudio.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D8684B040ACBF977009F6478 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - D8684B370ACBF9AF009F6478 /* libportmidi.a in Frameworks */, - D8684B570ACBFA3E009F6478 /* CoreMIDI.framework in Frameworks */, - D8684B580ACBFA3E009F6478 /* CoreFoundation.framework in Frameworks */, - D8684B590ACBFA3E009F6478 /* CoreAudio.framework in Frameworks */, - 3D65FEE30ECB7548008083A3 /* CoreServices in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D8684B400ACBFA09009F6478 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - D8684B470ACBFA2D009F6478 /* libportmidi.a in Frameworks */, - D8684B540ACBFA34009F6478 /* CoreMIDI.framework in Frameworks */, - D8684B550ACBFA34009F6478 /* CoreFoundation.framework in Frameworks */, - D8684B560ACBFA34009F6478 /* CoreAudio.framework in Frameworks */, - 3D65FEE40ECB7548008083A3 /* CoreServices in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D8684B5F0ACBFA75009F6478 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - D8684B660ACBFAA5009F6478 /* libportmidi.a in Frameworks */, - D8684B670ACBFAA5009F6478 /* CoreMIDI.framework in Frameworks */, - D8684B680ACBFAA5009F6478 /* CoreFoundation.framework in Frameworks */, - D8684B690ACBFAA5009F6478 /* CoreAudio.framework in Frameworks */, - 3D65FEE50ECB7548008083A3 /* CoreServices in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D8684B6B0ACBFAB7009F6478 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - D8684B7A0ACBFAFD009F6478 /* libportmidi.a in Frameworks */, - D8684B7B0ACBFAFD009F6478 /* CoreMIDI.framework in Frameworks */, - D8684B7C0ACBFAFD009F6478 /* CoreFoundation.framework in Frameworks */, - D8684B7D0ACBFAFD009F6478 /* CoreAudio.framework in Frameworks */, - 3D65FEE60ECB7548008083A3 /* CoreServices in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D8684B7F0ACBFB0B009F6478 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - D8684B860ACBFB33009F6478 /* libportmidi.a in Frameworks */, - D8684B870ACBFB33009F6478 /* CoreMIDI.framework in Frameworks */, - D8684B880ACBFB33009F6478 /* CoreFoundation.framework in Frameworks */, - D8684B890ACBFB33009F6478 /* CoreAudio.framework in Frameworks */, - 3D65FEE70ECB7548008083A3 /* CoreServices in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -556,36 +230,17 @@ path = ../pm_java/pmdefaults; sourceTree = SOURCE_ROOT; }; - 3DA774100663E9DA0002CE69 /* pm_test */ = { - isa = PBXGroup; - children = ( - 3DA774110663E9DA0002CE69 /* latency.c */, - 3DA60D710D3FBF160036B654 /* midiclock.c */, - 3DA774160663E9DA0002CE69 /* midithread.c */, - 3DA7741B0663E9DA0002CE69 /* midithru.c */, - 3DBF246C0B59686800DAD93B /* mm.c */, - 3DA774210663E9DA0002CE69 /* sysex.c */, - 3DA774260663E9DA0002CE69 /* test.c */, - 3DA7742B0663E9DA0002CE69 /* txdata.syx */, - ); - name = pm_test; - path = ../pm_test; - sourceTree = SOURCE_ROOT; - }; 5851D5140479552300EE98CD = { isa = PBXGroup; children = ( - 3D65FEE20ECB7548008083A3 /* CoreServices */, + 3DEEAAE2108CA7B40005BF4F /* libportmidi.dylib */, + 3D85B13D10672F59003F6706 /* libpmjni.dylib */, + 3DB8DD8710614B7700CD6802 /* test.c */, 3D17A4C30E14079000930576 /* pmdefaults.icns */, 3D17A2280E13EF8000930576 /* CoreServices.framework */, 3D17A1E70E13E57700930576 /* JavaVM.framework */, 3D17A1A10E13E30100930576 /* pmjni.c */, 3D9D57D90E12E35700C4C68D /* pm_java */, - 5851D51D047955A100EE98CD /* porttime */, - 5851D51C0479553600EE98CD /* pm_common */, - 5851D51B0479553500EE98CD /* pm_linux */, - 5851D5190479552E00EE98CD /* pm_mac */, - 3DA774100663E9DA0002CE69 /* pm_test */, 5851D5390479562300EE98CD /* Products */, 3DA774480663EAE60002CE69 /* CoreMIDI.framework */, 3DA7744E0663EBB20002CE69 /* CoreFoundation.framework */, @@ -593,105 +248,19 @@ ); sourceTree = ""; }; - 5851D5190479552E00EE98CD /* pm_mac */ = { - isa = PBXGroup; - children = ( - 5851D5310479560B00EE98CD /* pmmacosxcm.c */, - 5851D5320479560B00EE98CD /* pmmacosxcm.h */, - 3D9D57C50E12E1E500C4C68D /* readbinaryplist.c */, - 3D9D57C30E12E1CF00C4C68D /* finddefault.c */, - 5851D5730479613900EE98CD /* pmmac.h */, - 5851D5740479613900EE98CD /* pmmac.c */, - ); - name = pm_mac; - sourceTree = ""; - }; - 5851D51B0479553500EE98CD /* pm_linux */ = { - isa = PBXGroup; - children = ( - 5851D528047955D700EE98CD /* pmlinux.c */, - 5851D529047955D700EE98CD /* pmlinux.h */, - 5851D52A047955D700EE98CD /* pmlinuxalsa.c */, - 5851D52B047955D700EE98CD /* pmlinuxalsa.h */, - ); - name = pm_linux; - sourceTree = ""; - }; - 5851D51C0479553600EE98CD /* pm_common */ = { - isa = PBXGroup; - children = ( - 5851D523047955C800EE98CD /* pminternal.h */, - 5851D524047955C800EE98CD /* pmutil.c */, - 5851D525047955C800EE98CD /* pmutil.h */, - 5851D526047955C800EE98CD /* portmidi.c */, - 5851D527047955C800EE98CD /* portmidi.h */, - ); - name = pm_common; - sourceTree = SOURCE_ROOT; - }; - 5851D51D047955A100EE98CD /* porttime */ = { - isa = PBXGroup; - children = ( - 5851D51E047955B800EE98CD /* porttime.c */, - 5851D51F047955B800EE98CD /* porttime.h */, - 5851D521047955B800EE98CD /* ptmacosx_cf.c */, - 58ED5B81047D41DB00B92E62 /* ptmacosx_mach.c */, - ); - name = porttime; - sourceTree = ""; - }; 5851D5390479562300EE98CD /* Products */ = { isa = PBXGroup; children = ( - D81440D40ACB36E100515C41 /* libportmidi.a */, - D84A40DA0ACD4A08001FC716 /* libportmidi.dylib */, - D8684B060ACBF977009F6478 /* test */, - D8684B420ACBFA09009F6478 /* midithread */, - D8684B610ACBFA75009F6478 /* midithru */, - D8684B6D0ACBFAB7009F6478 /* sysex */, - D8684B810ACBFB0B009F6478 /* latency */, - 3DBF24640B5967A500DAD93B /* mm */, - 3DA60D6C0D3FBEA40036B654 /* midiclock */, 3D9D57CE0E12E22800C4C68D /* pmdefaults.app */, - 3D17A13D0E13D47F00930576 /* JPortMidiHeaders.jar */, 3D17A18C0E13E1EF00930576 /* jportmidi_JportMidiApi.h */, - 3D17A1910E13E25E00930576 /* libpmjni.jnilib */, + 3D3B2820104D8AF90043A026 /* test-dylib */, + 3DB8DDB8106189B200CD6802 /* JPortMidiHeaders.jar */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ -/* Begin PBXHeadersBuildPhase section */ - 3D17A18D0E13E25E00930576 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D814404C0ACB2C1200515C41 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - D814404E0ACB2C1200515C41 /* pmutil.h in Headers */, - D814404F0ACB2C1200515C41 /* portmidi.h in Headers */, - D81440510ACB2C1200515C41 /* porttime.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D84A40D60ACD4A08001FC716 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - D84A40DC0ACD4A19001FC716 /* pmutil.h in Headers */, - D84A40DD0ACD4A19001FC716 /* portmidi.h in Headers */, - D84A40DF0ACD4A19001FC716 /* porttime.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - /* Begin PBXJavaArchiveBuildPhase section */ 3D17A13A0E13D47F00930576 /* JavaArchive */ = { isa = PBXJavaArchiveBuildPhase; @@ -724,188 +293,26 @@ name = JPortMidiHeaders; productInstallPath = /usr/local/lib; productName = JPortMidiHeaders; - productReference = 3D17A13D0E13D47F00930576 /* JPortMidiHeaders.jar */; + productReference = 3DB8DDB8106189B200CD6802 /* JPortMidiHeaders.jar */; }; /* End PBXLibraryTarget section */ /* Begin PBXNativeTarget section */ - 3D17A1900E13E25E00930576 /* libpmjni.jnilib */ = { + 3D3B2811104D8AF90043A026 /* test-dylib */ = { isa = PBXNativeTarget; - buildConfigurationList = 3D17A19B0E13E29800930576 /* Build configuration list for PBXNativeTarget "libpmjni.jnilib" */; + buildConfigurationList = 3D3B281C104D8AF90043A026 /* Build configuration list for PBXNativeTarget "test-dylib" */; buildPhases = ( - 3D17A18D0E13E25E00930576 /* Headers */, - 3D17A18E0E13E25E00930576 /* Sources */, - 3D17A18F0E13E25E00930576 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 3D17A1EC0E13E5D100930576 /* PBXTargetDependency */, - ); - name = libpmjni.jnilib; - productName = libpmjni.jnilib; - productReference = 3D17A1910E13E25E00930576 /* libpmjni.jnilib */; - productType = "com.apple.product-type.library.dynamic"; - }; - 3DA60D5E0D3FBEA40036B654 /* midiclock */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3DA60D680D3FBEA40036B654 /* Build configuration list for PBXNativeTarget "midiclock" */; - buildPhases = ( - 3DA60D610D3FBEA40036B654 /* Sources */, - 3DA60D630D3FBEA40036B654 /* Frameworks */, - ); - buildRules = ( - ); - comments = "a MIDI clock and MIDI time code generator for testing"; - dependencies = ( - 3DA60D5F0D3FBEA40036B654 /* PBXTargetDependency */, - ); - name = midiclock; - productName = mm; - productReference = 3DA60D6C0D3FBEA40036B654 /* midiclock */; - productType = "com.apple.product-type.tool"; - }; - 3DBF24630B5967A500DAD93B /* mm */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3DBF24680B59681900DAD93B /* Build configuration list for PBXNativeTarget "mm" */; - buildPhases = ( - 3DBF24610B5967A500DAD93B /* Sources */, - 3DBF24620B5967A500DAD93B /* Frameworks */, - ); - buildRules = ( - ); - comments = "a midi monitor program"; - dependencies = ( - 3DBF24710B5968BC00DAD93B /* PBXTargetDependency */, - ); - name = mm; - productName = mm; - productReference = 3DBF24640B5967A500DAD93B /* mm */; - productType = "com.apple.product-type.tool"; - }; - D814404B0ACB2C1200515C41 /* libportmidi.a */ = { - isa = PBXNativeTarget; - buildConfigurationList = D814405B0ACB2C1200515C41 /* Build configuration list for PBXNativeTarget "libportmidi.a" */; - buildPhases = ( - D814404C0ACB2C1200515C41 /* Headers */, - D81440530ACB2C1200515C41 /* Sources */, - D81440590ACB2C1200515C41 /* Frameworks */, + 3D3B2814104D8AF90043A026 /* Sources */, + 3D3B2816104D8AF90043A026 /* Frameworks */, ); buildRules = ( ); + comments = "A PortMidi test program -- this build is here to demonstrate and test an application that uses dynamic linking to include PortMidi\n"; dependencies = ( ); - name = libportmidi.a; - productInstallPath = /usr/local/lib; - productName = libportmidi.a; - productReference = D81440D40ACB36E100515C41 /* libportmidi.a */; - productType = "com.apple.product-type.library.static"; - }; - D84A40D90ACD4A08001FC716 /* portmidi.dylib */ = { - isa = PBXNativeTarget; - buildConfigurationList = D84A40E70ACD4A56001FC716 /* Build configuration list for PBXNativeTarget "portmidi.dylib" */; - buildPhases = ( - D84A40D60ACD4A08001FC716 /* Headers */, - D84A40D70ACD4A08001FC716 /* Sources */, - D84A40D80ACD4A08001FC716 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = portmidi.dylib; - productName = portmidi; - productReference = D84A40DA0ACD4A08001FC716 /* libportmidi.dylib */; - productType = "com.apple.product-type.library.dynamic"; - }; - D8684B050ACBF977009F6478 /* test */ = { - isa = PBXNativeTarget; - buildConfigurationList = D8684B390ACBF9CC009F6478 /* Build configuration list for PBXNativeTarget "test" */; - buildPhases = ( - D8684B030ACBF977009F6478 /* Sources */, - D8684B040ACBF977009F6478 /* Frameworks */, - ); - buildRules = ( - ); - comments = "A PortMidi test program"; - dependencies = ( - D8684B090ACBF996009F6478 /* PBXTargetDependency */, - ); - name = test; + name = "test-dylib"; productName = test; - productReference = D8684B060ACBF977009F6478 /* test */; - productType = "com.apple.product-type.tool"; - }; - D8684B410ACBFA09009F6478 /* midithread */ = { - isa = PBXNativeTarget; - buildConfigurationList = D8684B5A0ACBFA51009F6478 /* Build configuration list for PBXNativeTarget "midithread" */; - buildPhases = ( - D8684B3F0ACBFA09009F6478 /* Sources */, - D8684B400ACBFA09009F6478 /* Frameworks */, - ); - buildRules = ( - ); - comments = "Demonstrates the use of a low-latency thread for midi processing"; - dependencies = ( - D8684B450ACBFA12009F6478 /* PBXTargetDependency */, - ); - name = midithread; - productName = midithread; - productReference = D8684B420ACBFA09009F6478 /* midithread */; - productType = "com.apple.product-type.tool"; - }; - D8684B600ACBFA75009F6478 /* midithru */ = { - isa = PBXNativeTarget; - buildConfigurationList = D8684B710ACBFAE9009F6478 /* Build configuration list for PBXNativeTarget "midithru" */; - buildPhases = ( - D8684B5E0ACBFA75009F6478 /* Sources */, - D8684B5F0ACBFA75009F6478 /* Frameworks */, - ); - buildRules = ( - ); - comments = "Demonstrates midi thru processing in a low-priority thread"; - dependencies = ( - D8684B640ACBFA8D009F6478 /* PBXTargetDependency */, - ); - name = midithru; - productName = midithru; - productReference = D8684B610ACBFA75009F6478 /* midithru */; - productType = "com.apple.product-type.tool"; - }; - D8684B6C0ACBFAB7009F6478 /* sysex */ = { - isa = PBXNativeTarget; - buildConfigurationList = D8684B750ACBFAE9009F6478 /* Build configuration list for PBXNativeTarget "sysex" */; - buildPhases = ( - D8684B6A0ACBFAB7009F6478 /* Sources */, - D8684B6B0ACBFAB7009F6478 /* Frameworks */, - ); - buildRules = ( - ); - comments = "System exclusive input and output test"; - dependencies = ( - D8684B700ACBFAD0009F6478 /* PBXTargetDependency */, - ); - name = sysex; - productName = sysex; - productReference = D8684B6D0ACBFAB7009F6478 /* sysex */; - productType = "com.apple.product-type.tool"; - }; - D8684B800ACBFB0B009F6478 /* latency */ = { - isa = PBXNativeTarget; - buildConfigurationList = D8684B8A0ACBFB3D009F6478 /* Build configuration list for PBXNativeTarget "latency" */; - buildPhases = ( - D8684B7E0ACBFB0B009F6478 /* Sources */, - D8684B7F0ACBFB0B009F6478 /* Frameworks */, - ); - buildRules = ( - ); - comments = "a test program to measure OS latency"; - dependencies = ( - D8684B840ACBFB13009F6478 /* PBXTargetDependency */, - ); - name = latency; - productName = latency; - productReference = D8684B810ACBFB0B009F6478 /* latency */; + productReference = 3D3B2820104D8AF90043A026 /* test-dylib */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ @@ -914,24 +321,17 @@ 5851D5180479552300EE98CD /* Project object */ = { isa = PBXProject; buildConfigurationList = D814401B0ACB28B600515C41 /* Build configuration list for PBXProject "pm_mac" */; + compatibilityVersion = "Xcode 2.4"; hasScannedForEncodings = 1; mainGroup = 5851D5140479552300EE98CD; productRefGroup = 5851D5390479562300EE98CD /* Products */; projectDirPath = ""; + projectRoot = ..; targets = ( - D84A40D90ACD4A08001FC716 /* portmidi.dylib */, - D814404B0ACB2C1200515C41 /* libportmidi.a */, - 3D17A1900E13E25E00930576 /* libpmjni.jnilib */, - D8684B050ACBF977009F6478 /* test */, - D8684B410ACBFA09009F6478 /* midithread */, - D8684B600ACBFA75009F6478 /* midithru */, - D8684B6C0ACBFAB7009F6478 /* sysex */, - D8684B800ACBFB0B009F6478 /* latency */, - 3DBF24630B5967A500DAD93B /* mm */, - 3DA60D5E0D3FBEA40036B654 /* midiclock */, D8684B960ACBFB93009F6478 /* everything suite */, 3D9D57CD0E12E22800C4C68D /* PmDefaults */, 3D17A13C0E13D47F00930576 /* JPortMidiHeaders */, + 3D3B2811104D8AF90043A026 /* test-dylib */, ); }; /* End PBXProject section */ @@ -961,7 +361,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "echo BUILT_PRODUCTS_DIR is ${BUILT_PRODUCTS_DIR}\njavah -classpath \"${BUILT_PRODUCTS_DIR}/JPortMidiHeaders.jar\" -force -o \"${BUILT_PRODUCTS_DIR}/jportmidi_JportMidiApi.h\" \"jportmidi.JPortMidiApi\""; + shellScript = "echo BUILT_PRODUCTS_DIR is ${BUILT_PRODUCTS_DIR}\njavah -classpath \"${BUILT_PRODUCTS_DIR}/JPortMidiHeaders.jar\" -force -o \"${BUILT_PRODUCTS_DIR}/jportmidi_JportMidiApi.h\" \"jportmidi.JPortMidiApi\"\nmv \"${BUILT_PRODUCTS_DIR}/jportmidi_JportMidiApi.h\" ../pm_java/pmjni/\necho \"Created ../pm_java/pmjni/jportmidi_JportMidiApi.h\"\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -978,18 +378,11 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 3D17A18E0E13E25E00930576 /* Sources */ = { + 3D3B2814104D8AF90043A026 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3D17A20D0E13E88100930576 /* pmutil.c in Sources */, - 3D17A20E0E13E88100930576 /* portmidi.c in Sources */, - 3D17A20F0E13E88100930576 /* pmmacosxcm.c in Sources */, - 3D17A2100E13E88100930576 /* pmmac.c in Sources */, - 3D17A2110E13E88100930576 /* ptmacosx_mach.c in Sources */, - 3D17A2120E13E88100930576 /* readbinaryplist.c in Sources */, - 3D17A2130E13E88100930576 /* finddefault.c in Sources */, - 3D17A1A20E13E30100930576 /* pmjni.c in Sources */, + 3DB8DD8810614B7700CD6802 /* test.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1005,197 +398,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 3DA60D610D3FBEA40036B654 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3DA60D720D3FBF160036B654 /* midiclock.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3DBF24610B5967A500DAD93B /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3DBF246D0B59686800DAD93B /* mm.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D81440530ACB2C1200515C41 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D81440540ACB2C1200515C41 /* pmutil.c in Sources */, - D81440550ACB2C1200515C41 /* portmidi.c in Sources */, - D81440560ACB2C1200515C41 /* pmmacosxcm.c in Sources */, - D81440570ACB2C1200515C41 /* pmmac.c in Sources */, - D81440580ACB2C1200515C41 /* ptmacosx_mach.c in Sources */, - 3D17A20A0E13E7E900930576 /* readbinaryplist.c in Sources */, - 3D17A20B0E13E7E900930576 /* finddefault.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D84A40D70ACD4A08001FC716 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D84A40E10ACD4A26001FC716 /* pmutil.c in Sources */, - D84A40E20ACD4A26001FC716 /* portmidi.c in Sources */, - D84A40E30ACD4A26001FC716 /* pmmacosxcm.c in Sources */, - D84A40E40ACD4A26001FC716 /* pmmac.c in Sources */, - D84A40E50ACD4A26001FC716 /* ptmacosx_mach.c in Sources */, - 3D9D57C40E12E1CF00C4C68D /* finddefault.c in Sources */, - 3D9D57C60E12E1E500C4C68D /* readbinaryplist.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D8684B030ACBF977009F6478 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D8684B380ACBF9B9009F6478 /* test.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D8684B3F0ACBFA09009F6478 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D8684B460ACBFA24009F6478 /* midithread.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D8684B5E0ACBFA75009F6478 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D8684B650ACBFA9D009F6478 /* midithru.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D8684B6A0ACBFAB7009F6478 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D8684B790ACBFAF7009F6478 /* sysex.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D8684B7E0ACBFB0B009F6478 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D8684B850ACBFB2C009F6478 /* latency.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 3D17A1EC0E13E5D100930576 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 3D17A13C0E13D47F00930576 /* JPortMidiHeaders */; - targetProxy = 3D17A1EB0E13E5D100930576 /* PBXContainerItemProxy */; - }; 3D3534900F0D2DB30059CD05 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3D9D57CD0E12E22800C4C68D /* PmDefaults */; targetProxy = 3D35348F0F0D2DB30059CD05 /* PBXContainerItemProxy */; }; - 3D4799D40F0E87350006C842 /* PBXTargetDependency */ = { + 3D3B2826104D8B740043A026 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 3DBF24630B5967A500DAD93B /* mm */; - targetProxy = 3D4799D30F0E87350006C842 /* PBXContainerItemProxy */; - }; - 3D4799F50F0EE7D10006C842 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 3D17A1900E13E25E00930576 /* libpmjni.jnilib */; - targetProxy = 3D4799F40F0EE7D10006C842 /* PBXContainerItemProxy */; - }; - 3D4799F80F0EE7FB0006C842 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 3D17A1900E13E25E00930576 /* libpmjni.jnilib */; - targetProxy = 3D4799F70F0EE7FB0006C842 /* PBXContainerItemProxy */; - }; - 3D8DABA50D57B68A000FEBA8 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 3DA60D5E0D3FBEA40036B654 /* midiclock */; - targetProxy = 3D8DABA40D57B68A000FEBA8 /* PBXContainerItemProxy */; - }; - 3DA60D5F0D3FBEA40036B654 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D814404B0ACB2C1200515C41 /* libportmidi.a */; - targetProxy = 3DA60D600D3FBEA40036B654 /* PBXContainerItemProxy */; - }; - 3DBF24710B5968BC00DAD93B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D814404B0ACB2C1200515C41 /* libportmidi.a */; - targetProxy = 3DBF24700B5968BC00DAD93B /* PBXContainerItemProxy */; - }; - D8684B090ACBF996009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D814404B0ACB2C1200515C41 /* libportmidi.a */; - targetProxy = D8684B080ACBF996009F6478 /* PBXContainerItemProxy */; - }; - D8684B450ACBFA12009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D814404B0ACB2C1200515C41 /* libportmidi.a */; - targetProxy = D8684B440ACBFA12009F6478 /* PBXContainerItemProxy */; - }; - D8684B640ACBFA8D009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D814404B0ACB2C1200515C41 /* libportmidi.a */; - targetProxy = D8684B630ACBFA8D009F6478 /* PBXContainerItemProxy */; - }; - D8684B700ACBFAD0009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D814404B0ACB2C1200515C41 /* libportmidi.a */; - targetProxy = D8684B6F0ACBFAD0009F6478 /* PBXContainerItemProxy */; - }; - D8684B840ACBFB13009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D814404B0ACB2C1200515C41 /* libportmidi.a */; - targetProxy = D8684B830ACBFB13009F6478 /* PBXContainerItemProxy */; - }; - D8684B980ACBFB9D009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D814404B0ACB2C1200515C41 /* libportmidi.a */; - targetProxy = D8684B970ACBFB9D009F6478 /* PBXContainerItemProxy */; - }; - D8684B9A0ACBFB9D009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D8684B050ACBF977009F6478 /* test */; - targetProxy = D8684B990ACBFB9D009F6478 /* PBXContainerItemProxy */; - }; - D8684B9C0ACBFB9D009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D8684B410ACBFA09009F6478 /* midithread */; - targetProxy = D8684B9B0ACBFB9D009F6478 /* PBXContainerItemProxy */; - }; - D8684B9E0ACBFB9D009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D8684B600ACBFA75009F6478 /* midithru */; - targetProxy = D8684B9D0ACBFB9D009F6478 /* PBXContainerItemProxy */; - }; - D8684BA00ACBFB9D009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D8684B6C0ACBFAB7009F6478 /* sysex */; - targetProxy = D8684B9F0ACBFB9D009F6478 /* PBXContainerItemProxy */; - }; - D8684BA20ACBFB9D009F6478 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D8684B800ACBFB0B009F6478 /* latency */; - targetProxy = D8684BA10ACBFB9D009F6478 /* PBXContainerItemProxy */; - }; - D8E738E20ACDA3BD0030B95B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D84A40D90ACD4A08001FC716 /* portmidi.dylib */; - targetProxy = D8E738E10ACDA3BD0030B95B /* PBXContainerItemProxy */; + target = 3D3B2811104D8AF90043A026 /* test-dylib */; + targetProxy = 3D3B2825104D8B740043A026 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 3D17A1480E13D48800930576 /* Development */ = { + 3D17A1480E13D48800930576 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; @@ -1209,7 +428,6 @@ JAVA_COMPILER = /usr/bin/javac; JAVA_SOURCE_SUBDIR = .; LIBRARY_STYLE = STATIC; - OPTIMIZATION_CFLAGS = "-O0"; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ""; OTHER_LIBTOOL_FLAGS = ""; @@ -1224,9 +442,9 @@ "-Wno-unknown-pragmas", ); }; - name = Development; + name = Debug; }; - 3D17A1490E13D48800930576 /* Deployment */ = { + 3D17A1490E13D48800930576 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; @@ -1234,12 +452,12 @@ DYLIB_CURRENT_VERSION = 1; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_OPTIMIZATION_LEVEL = 0; JAVA_ARCHIVE_CLASSES = YES; JAVA_ARCHIVE_TYPE = JAR; JAVA_COMPILER = /usr/bin/javac; JAVA_SOURCE_SUBDIR = .; LIBRARY_STYLE = STATIC; - OPTIMIZATION_CFLAGS = "-O0"; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ""; OTHER_LIBTOOL_FLAGS = ""; @@ -1255,19 +473,19 @@ ); ZERO_LINK = NO; }; - name = Deployment; + name = Release; }; 3D17A14A0E13D48800930576 /* Default */ = { isa = XCBuildConfiguration; buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; + GCC_OPTIMIZATION_LEVEL = 0; JAVA_ARCHIVE_CLASSES = YES; JAVA_ARCHIVE_TYPE = JAR; JAVA_COMPILER = /usr/bin/javac; JAVA_SOURCE_SUBDIR = .; LIBRARY_STYLE = STATIC; - OPTIMIZATION_CFLAGS = "-O0"; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ""; OTHER_LIBTOOL_FLAGS = ""; @@ -1284,67 +502,94 @@ }; name = Default; }; - 3D17A19C0E13E29800930576 /* Development */ = { + 3D3B281D104D8AF90043A026 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; - EXECUTABLE_EXTENSION = jnilib; - EXECUTABLE_PREFIX = lib; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ""; HEADER_SEARCH_PATHS = ( - "$(SDKROOT)/System/Library/Frameworks/JavaVM.framework/Headers", - /Developer/Headers/FlatCarbon, + ../pm_common, + ../porttime, ); - INSTALL_PATH = /usr/local/lib; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = ( + ../Debug, + "$(inherited)", + "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", + "$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_2)", + ); + LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; + LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)/../pm_common/Release\""; + LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_2 = "\"$(SRCROOT)/../Release\""; PREBINDING = NO; - PRODUCT_NAME = pmjni; + PRODUCT_NAME = "test-dylib"; ZERO_LINK = YES; }; - name = Development; + name = Debug; }; - 3D17A19D0E13E29800930576 /* Deployment */ = { + 3D3B281E104D8AF90043A026 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; - EXECUTABLE_EXTENSION = jnilib; - EXECUTABLE_PREFIX = lib; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; + GCC_PREPROCESSOR_DEFINITIONS = ""; HEADER_SEARCH_PATHS = ( - "$(SDKROOT)/System/Library/Frameworks/JavaVM.framework/Headers", - /Developer/Headers/FlatCarbon, + ../pm_common, + ../porttime, ); - INSTALL_PATH = /usr/local/lib; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", + ../Release/, + "$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_2)", + ); + LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; + LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)/../pm_common/Release\""; + LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_2 = "\"$(SRCROOT)/../Release\""; PREBINDING = NO; - PRODUCT_NAME = pmjni; + PRODUCT_NAME = "test-dylib"; ZERO_LINK = NO; }; - name = Deployment; + name = Release; }; - 3D17A19E0E13E29800930576 /* Default */ = { + 3D3B281F104D8AF90043A026 /* Default */ = { isa = XCBuildConfiguration; buildSettings = { - EXECUTABLE_EXTENSION = jnilib; - EXECUTABLE_PREFIX = lib; + ARCHS = "$(ARCHS_STANDARD_32_64_BIT_PRE_XCODE_3_1)"; + ARCHS_STANDARD_32_64_BIT_PRE_XCODE_3_1 = "ppc i386 ppc64 x86_64"; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_MODEL_TUNING = G5; + GCC_PREPROCESSOR_DEFINITIONS = ""; HEADER_SEARCH_PATHS = ( - "$(SDKROOT)/System/Library/Frameworks/JavaVM.framework/Headers", - /Developer/Headers/FlatCarbon, + ../pm_common, + ../porttime, ); - INSTALL_PATH = /usr/local/lib; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", + "$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1)", + "$(LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_2)", + ); + LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; + LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1 = "\"$(SRCROOT)/../pm_common/Release\""; + LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_2 = "\"$(SRCROOT)/../Release\""; PREBINDING = NO; - PRODUCT_NAME = pmjni; + PRODUCT_NAME = "test-dylib"; + SDKROOT = /Developer/SDKs/MacOSX10.5.sdk; ZERO_LINK = YES; }; name = Default; }; - 3D9D57D10E12E24600C4C68D /* Development */ = { + 3D9D57D10E12E24600C4C68D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; @@ -1353,7 +598,7 @@ GCC_OPTIMIZATION_LEVEL = 0; HEADER_SEARCH_PATHS = ""; INSTALL_PATH = Applications; - JAVA_CLASS_SEARCH_PATHS = /Users/rbd/portmedia/portmidi/pm_java/pmdefaults; + JAVA_CLASS_SEARCH_PATHS = ../pm_java/pmdefaults; JAVA_COMPILER = /usr/bin/javac; JAVA_COMPILER_SOURCE_VERSION = 1.5; JAVA_COMPILER_TARGET_VM_VERSION = 1.5; @@ -1362,8 +607,9 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../pm_java/pmjni", + "$(SRCROOT)/../pm_common/Release", + "$(SRCROOT)/../Release", ); - OPTIMIZATION_CFLAGS = "-O0"; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ""; OTHER_REZFLAGS = ""; @@ -1376,22 +622,24 @@ ); WRAPPER_EXTENSION = app; }; - name = Development; + name = Debug; }; - 3D9D57D20E12E24600C4C68D /* Deployment */ = { + 3D9D57D20E12E24600C4C68D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_OPTIMIZATION_LEVEL = 0; JAVA_COMPILER = /usr/bin/javac; JAVA_ONLY = YES; JAVA_SOURCE_SUBDIR = .; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../pm_java/pmjni", + "$(SRCROOT)/../pm_common/Release", + "$(SRCROOT)/../Release", ); - OPTIMIZATION_CFLAGS = "-O0"; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ""; OTHER_REZFLAGS = ""; @@ -1405,19 +653,21 @@ WRAPPER_EXTENSION = app; ZERO_LINK = NO; }; - name = Deployment; + name = Release; }; 3D9D57D30E12E24600C4C68D /* Default */ = { isa = XCBuildConfiguration; buildSettings = { + GCC_OPTIMIZATION_LEVEL = 0; JAVA_COMPILER = /usr/bin/javac; JAVA_ONLY = YES; JAVA_SOURCE_SUBDIR = .; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../pm_java/pmjni", + "$(SRCROOT)/../pm_common/Release", + "$(SRCROOT)/../Release", ); - OPTIMIZATION_CFLAGS = "-O0"; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ""; OTHER_REZFLAGS = ""; @@ -1432,608 +682,46 @@ }; name = Default; }; - 3DA60D690D3FBEA40036B654 /* Development */ = { + D814401C0ACB28B600515C41 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_2)", + ARCHS = "$(NATIVE_ARCH)"; + SDKROOT = /Developer/SDKs/MacOSX10.5.sdk; + SYMROOT = /Users/rbd/portmedia/portmidi; + WARNING_CFLAGS = ( + "-Wshorten-64-to-32", + "-Wformat", ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SRCROOT)\""; - LIBRARY_SEARCH_PATHS_QUOTED_2 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = midiclock; - ZERO_LINK = YES; }; - name = Development; + name = Debug; }; - 3DA60D6A0D3FBEA40036B654 /* Deployment */ = { + D814401D0ACB28B600515C41 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_2)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SRCROOT)\""; - LIBRARY_SEARCH_PATHS_QUOTED_2 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = midiclock; - ZERO_LINK = NO; - }; - name = Deployment; - }; - 3DA60D6B0D3FBEA40036B654 /* Default */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_MODEL_TUNING = G5; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_2)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SRCROOT)\""; - LIBRARY_SEARCH_PATHS_QUOTED_2 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = midiclock; - ZERO_LINK = YES; - }; - name = Default; - }; - 3DBF24690B59681900DAD93B /* Development */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_2)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SRCROOT)\""; - LIBRARY_SEARCH_PATHS_QUOTED_2 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = mm; - ZERO_LINK = YES; - }; - name = Development; - }; - 3DBF246A0B59681900DAD93B /* Deployment */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_2)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SRCROOT)\""; - LIBRARY_SEARCH_PATHS_QUOTED_2 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = mm; - ZERO_LINK = NO; - }; - name = Deployment; - }; - 3DBF246B0B59681900DAD93B /* Default */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_MODEL_TUNING = G5; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_2)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SRCROOT)\""; - LIBRARY_SEARCH_PATHS_QUOTED_2 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = mm; - ZERO_LINK = YES; - }; - name = Default; - }; - D814401C0ACB28B600515C41 /* Development */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = ( - ppc, - i386, - ); - SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; - }; - name = Development; - }; - D814401D0ACB28B600515C41 /* Deployment */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = ( - ppc, - i386, - ); + ARCHS = "$(ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1)"; + ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1 = "ppc i386"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; INSTALL_PATH = /usr/local/lib; - SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + OTHER_CFLAGS = ""; + SDKROOT = /Developer/SDKs/MacOSX10.5.sdk; + WARNING_CFLAGS = ( + "-Wshorten-64-to-32", + "-Wformat", + ); }; - name = Deployment; + name = Release; }; D814401E0ACB28B600515C41 /* Default */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = ( - ppc, - i386, - ); - SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + ARCHS = "$(ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1)"; + ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1 = "ppc i386"; + ONLY_ACTIVE_ARCH = NO; + SDKROOT = /Developer/SDKs/MacOSX10.5.sdk; }; name = Default; }; - D814405C0ACB2C1200515C41 /* Development */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - PM_CHECK_ERRORS, - DEBUG, - NEWBUFFER, - ); - HEADER_SEARCH_PATHS = /Developer/Headers/FlatCarbon; - LIBRARY_STYLE = STATIC; - OTHER_CFLAGS = ""; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOL_FLAGS = ""; - OTHER_REZFLAGS = ""; - PRODUCT_NAME = portmidi; - REZ_EXECUTABLE = YES; - SECTORDER_FLAGS = ""; - WARNING_CFLAGS = ( - "-Wmost", - "-Wno-four-char-constants", - "-Wno-unknown-pragmas", - ); - ZERO_LINK = YES; - }; - name = Development; - }; - D814405D0ACB2C1200515C41 /* Deployment */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - GCC_PREPROCESSOR_DEFINITIONS = ( - PM_CHECK_ERRORS, - DEBUG, - NEWBUFFER, - ); - HEADER_SEARCH_PATHS = /Developer/Headers/FlatCarbon; - LIBRARY_STYLE = STATIC; - OTHER_CFLAGS = ""; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOL_FLAGS = ""; - OTHER_REZFLAGS = ""; - PRODUCT_NAME = portmidi; - REZ_EXECUTABLE = YES; - SECTORDER_FLAGS = ""; - SKIP_INSTALL = NO; - WARNING_CFLAGS = ( - "-Wmost", - "-Wno-four-char-constants", - "-Wno-unknown-pragmas", - ); - ZERO_LINK = NO; - }; - name = Deployment; - }; - D814405E0ACB2C1200515C41 /* Default */ = { - isa = XCBuildConfiguration; - buildSettings = { - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - GCC_PREPROCESSOR_DEFINITIONS = ( - PM_CHECK_ERRORS, - DEBUG, - NEWBUFFER, - ); - HEADER_SEARCH_PATHS = /Developer/Headers/FlatCarbon; - LIBRARY_STYLE = STATIC; - OTHER_CFLAGS = ""; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOL_FLAGS = ""; - OTHER_REZFLAGS = ""; - PRODUCT_NAME = portmidi; - REZ_EXECUTABLE = YES; - SECTORDER_FLAGS = ""; - WARNING_CFLAGS = ( - "-Wmost", - "-Wno-four-char-constants", - "-Wno-unknown-pragmas", - ); - }; - name = Default; - }; - D84A40E80ACD4A56001FC716 /* Development */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - EXECUTABLE_PREFIX = lib; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - HEADER_SEARCH_PATHS = /Developer/Headers/FlatCarbon; - INSTALL_PATH = /usr/local/lib; - PREBINDING = NO; - PRODUCT_NAME = portmidi; - ZERO_LINK = YES; - }; - name = Development; - }; - D84A40E90ACD4A56001FC716 /* Deployment */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - EXECUTABLE_PREFIX = lib; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - HEADER_SEARCH_PATHS = /Developer/Headers/FlatCarbon; - INSTALL_PATH = /usr/local/lib; - PREBINDING = NO; - PRODUCT_NAME = portmidi; - ZERO_LINK = NO; - }; - name = Deployment; - }; - D84A40EA0ACD4A56001FC716 /* Default */ = { - isa = XCBuildConfiguration; - buildSettings = { - EXECUTABLE_PREFIX = lib; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - HEADER_SEARCH_PATHS = /Developer/Headers/FlatCarbon; - INSTALL_PATH = /usr/local/lib; - PREBINDING = NO; - PRODUCT_NAME = portmidi; - ZERO_LINK = YES; - }; - name = Default; - }; - D8684B3A0ACBF9CC009F6478 /* Development */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = test; - ZERO_LINK = YES; - }; - name = Development; - }; - D8684B3B0ACBF9CC009F6478 /* Deployment */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = test; - ZERO_LINK = NO; - }; - name = Deployment; - }; - D8684B3C0ACBF9CC009F6478 /* Default */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = test; - ZERO_LINK = YES; - }; - name = Default; - }; - D8684B5B0ACBFA51009F6478 /* Development */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = midithread; - ZERO_LINK = YES; - }; - name = Development; - }; - D8684B5C0ACBFA51009F6478 /* Deployment */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = midithread; - ZERO_LINK = NO; - }; - name = Deployment; - }; - D8684B5D0ACBFA51009F6478 /* Default */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = midithread; - ZERO_LINK = YES; - }; - name = Default; - }; - D8684B720ACBFAE9009F6478 /* Development */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = midithru; - ZERO_LINK = YES; - }; - name = Development; - }; - D8684B730ACBFAE9009F6478 /* Deployment */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = midithru; - ZERO_LINK = NO; - }; - name = Deployment; - }; - D8684B740ACBFAE9009F6478 /* Default */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = midithru; - ZERO_LINK = YES; - }; - name = Default; - }; - D8684B760ACBFAE9009F6478 /* Development */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = sysex; - ZERO_LINK = YES; - }; - name = Development; - }; - D8684B770ACBFAE9009F6478 /* Deployment */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_MODEL_TUNING = G5; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = sysex; - ZERO_LINK = NO; - }; - name = Deployment; - }; - D8684B780ACBFAE9009F6478 /* Default */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_MODEL_TUNING = G5; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = sysex; - ZERO_LINK = YES; - }; - name = Default; - }; - D8684B8B0ACBFB3D009F6478 /* Development */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = latency; - ZERO_LINK = YES; - }; - name = Development; - }; - D8684B8C0ACBFB3D009F6478 /* Deployment */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = latency; - ZERO_LINK = NO; - }; - name = Deployment; - }; - D8684B8D0ACBFB3D009F6478 /* Default */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_MODEL_TUNING = G5; - GCC_PREPROCESSOR_DEFINITIONS = NEWBUFFER; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(LIBRARY_SEARCH_PATHS_QUOTED_1)", - ); - LIBRARY_SEARCH_PATHS_QUOTED_1 = "\"$(SYSTEM_LIBRARY_DIR)/Frameworks/CoreServices.framework\""; - PREBINDING = NO; - PRODUCT_NAME = latency; - ZERO_LINK = YES; - }; - name = Default; - }; - D8684BAB0ACBFBDD009F6478 /* Development */ = { + D8684BAB0ACBFBDD009F6478 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; @@ -2042,9 +730,9 @@ GCC_OPTIMIZATION_LEVEL = 0; PRODUCT_NAME = "lib+test suite"; }; - name = Development; + name = Debug; }; - D8684BAC0ACBFBDD009F6478 /* Deployment */ = { + D8684BAC0ACBFBDD009F6478 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; @@ -2053,7 +741,7 @@ PRODUCT_NAME = "lib+test suite"; ZERO_LINK = NO; }; - name = Deployment; + name = Release; }; D8684BAD0ACBFBDD009F6478 /* Default */ = { isa = XCBuildConfiguration; @@ -2068,19 +756,19 @@ 3D17A1470E13D48800930576 /* Build configuration list for PBXLibraryTarget "JPortMidiHeaders" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3D17A1480E13D48800930576 /* Development */, - 3D17A1490E13D48800930576 /* Deployment */, + 3D17A1480E13D48800930576 /* Debug */, + 3D17A1490E13D48800930576 /* Release */, 3D17A14A0E13D48800930576 /* Default */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Default; }; - 3D17A19B0E13E29800930576 /* Build configuration list for PBXNativeTarget "libpmjni.jnilib" */ = { + 3D3B281C104D8AF90043A026 /* Build configuration list for PBXNativeTarget "test-dylib" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3D17A19C0E13E29800930576 /* Development */, - 3D17A19D0E13E29800930576 /* Deployment */, - 3D17A19E0E13E29800930576 /* Default */, + 3D3B281D104D8AF90043A026 /* Debug */, + 3D3B281E104D8AF90043A026 /* Release */, + 3D3B281F104D8AF90043A026 /* Default */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Default; @@ -2088,118 +776,28 @@ 3D9D57D00E12E24600C4C68D /* Build configuration list for PBXApplicationTarget "PmDefaults" */ = { isa = XCConfigurationList; buildConfigurations = ( - 3D9D57D10E12E24600C4C68D /* Development */, - 3D9D57D20E12E24600C4C68D /* Deployment */, + 3D9D57D10E12E24600C4C68D /* Debug */, + 3D9D57D20E12E24600C4C68D /* Release */, 3D9D57D30E12E24600C4C68D /* Default */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Default; }; - 3DA60D680D3FBEA40036B654 /* Build configuration list for PBXNativeTarget "midiclock" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3DA60D690D3FBEA40036B654 /* Development */, - 3DA60D6A0D3FBEA40036B654 /* Deployment */, - 3DA60D6B0D3FBEA40036B654 /* Default */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Default; - }; - 3DBF24680B59681900DAD93B /* Build configuration list for PBXNativeTarget "mm" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3DBF24690B59681900DAD93B /* Development */, - 3DBF246A0B59681900DAD93B /* Deployment */, - 3DBF246B0B59681900DAD93B /* Default */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Default; - }; D814401B0ACB28B600515C41 /* Build configuration list for PBXProject "pm_mac" */ = { isa = XCConfigurationList; buildConfigurations = ( - D814401C0ACB28B600515C41 /* Development */, - D814401D0ACB28B600515C41 /* Deployment */, + D814401C0ACB28B600515C41 /* Debug */, + D814401D0ACB28B600515C41 /* Release */, D814401E0ACB28B600515C41 /* Default */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Default; }; - D814405B0ACB2C1200515C41 /* Build configuration list for PBXNativeTarget "libportmidi.a" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D814405C0ACB2C1200515C41 /* Development */, - D814405D0ACB2C1200515C41 /* Deployment */, - D814405E0ACB2C1200515C41 /* Default */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Default; - }; - D84A40E70ACD4A56001FC716 /* Build configuration list for PBXNativeTarget "portmidi.dylib" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D84A40E80ACD4A56001FC716 /* Development */, - D84A40E90ACD4A56001FC716 /* Deployment */, - D84A40EA0ACD4A56001FC716 /* Default */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Default; - }; - D8684B390ACBF9CC009F6478 /* Build configuration list for PBXNativeTarget "test" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D8684B3A0ACBF9CC009F6478 /* Development */, - D8684B3B0ACBF9CC009F6478 /* Deployment */, - D8684B3C0ACBF9CC009F6478 /* Default */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Default; - }; - D8684B5A0ACBFA51009F6478 /* Build configuration list for PBXNativeTarget "midithread" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D8684B5B0ACBFA51009F6478 /* Development */, - D8684B5C0ACBFA51009F6478 /* Deployment */, - D8684B5D0ACBFA51009F6478 /* Default */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Default; - }; - D8684B710ACBFAE9009F6478 /* Build configuration list for PBXNativeTarget "midithru" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D8684B720ACBFAE9009F6478 /* Development */, - D8684B730ACBFAE9009F6478 /* Deployment */, - D8684B740ACBFAE9009F6478 /* Default */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Default; - }; - D8684B750ACBFAE9009F6478 /* Build configuration list for PBXNativeTarget "sysex" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D8684B760ACBFAE9009F6478 /* Development */, - D8684B770ACBFAE9009F6478 /* Deployment */, - D8684B780ACBFAE9009F6478 /* Default */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Default; - }; - D8684B8A0ACBFB3D009F6478 /* Build configuration list for PBXNativeTarget "latency" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D8684B8B0ACBFB3D009F6478 /* Development */, - D8684B8C0ACBFB3D009F6478 /* Deployment */, - D8684B8D0ACBFB3D009F6478 /* Default */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Default; - }; D8684BAA0ACBFBDD009F6478 /* Build configuration list for PBXAggregateTarget "everything suite" */ = { isa = XCConfigurationList; buildConfigurations = ( - D8684BAB0ACBFBDD009F6478 /* Development */, - D8684BAC0ACBFBDD009F6478 /* Deployment */, + D8684BAB0ACBFBDD009F6478 /* Debug */, + D8684BAC0ACBFBDD009F6478 /* Release */, D8684BAD0ACBFBDD009F6478 /* Default */, ); defaultConfigurationIsVisible = 0; diff --git a/lib-src/portmidi/pm_mac/pmmacosxcm.c b/lib-src/portmidi/pm_mac/pmmacosxcm.c index 57fe9c413..71ceb40e5 100644 --- a/lib-src/portmidi/pm_mac/pmmacosxcm.c +++ b/lib-src/portmidi/pm_mac/pmmacosxcm.c @@ -57,21 +57,29 @@ #define MIDI_EOX 0xf7 #define MIDI_STATUS_MASK 0x80 -static MIDIClientRef client = NULL; /* Client handle to the MIDI server */ -static MIDIPortRef portIn = NULL; /* Input port handle */ -static MIDIPortRef portOut = NULL; /* Output port handle */ +// "Ref"s are pointers on 32-bit machines and ints on 64 bit machines +// NULL_REF is our representation of either 0 or NULL +#ifdef __LP64__ +#define NULL_REF 0 +#else +#define NULL_REF NULL +#endif + +static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */ +static MIDIPortRef portIn = NULL_REF; /* Input port handle */ +static MIDIPortRef portOut = NULL_REF; /* Output port handle */ extern pm_fns_node pm_macosx_in_dictionary; extern pm_fns_node pm_macosx_out_dictionary; typedef struct midi_macosxcm_struct { - unsigned long sync_time; /* when did we last determine delta? */ + PmTimestamp sync_time; /* when did we last determine delta? */ UInt64 delta; /* difference between stream time and real time in ns */ UInt64 last_time; /* last output time */ int first_message; /* tells midi_write to sychronize timestamps */ int sysex_mode; /* middle of sending sysex */ - unsigned long sysex_word; /* accumulate data when receiving sysex */ - unsigned int sysex_byte_count; /* count how many received */ + uint32_t sysex_word; /* accumulate data when receiving sysex */ + uint32_t sysex_byte_count; /* count how many received */ char error[PM_HOST_ERROR_MSG_LEN]; char callback_error[PM_HOST_ERROR_MSG_LEN]; Byte packetBuffer[PACKET_BUFFER_SIZE]; @@ -81,7 +89,7 @@ typedef struct midi_macosxcm_struct { MIDITimeStamp sysex_timestamp; /* timestamp to use with sysex data */ /* allow for running status (is running status possible here? -rbd): -cpr */ unsigned char last_command; - long last_msg_length; + int32_t last_msg_length; } midi_macosxcm_node, *midi_macosxcm_type; /* private function declarations */ @@ -92,7 +100,7 @@ char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint); static int -midi_length(long msg) +midi_length(int32_t msg) { int status, high, low; static int high_lengths[] = { @@ -235,7 +243,7 @@ readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon) PmEvent event; MIDIPacket *packet; unsigned int packetIndex; - unsigned long now; + uint32_t now; unsigned int status; #ifdef CM_DEBUG @@ -260,9 +268,9 @@ readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon) packet->length); */ for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) { /* Set the timestamp and dispatch this message */ - event.timestamp = + event.timestamp = (PmTimestamp) /* explicit conversion */ ( (AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) / - (UInt64) 1000000; + (UInt64) 1000000); status = packet->data[0]; /* process packet as sysex data if it begins with MIDI_SYSEX, or MIDI_EOX or non-status byte with no running status */ @@ -303,9 +311,8 @@ midi_in_open(PmInternal *midi, void *driverInfo) /* time_get does not take a parameter, so coerce */ midi->time_proc = (PmTimeProcPtr) Pt_Time; } - - endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor; - if (endpoint == NULL) { + endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; + if (endpoint == NULL_REF) { return pmInvalidDeviceId; } @@ -333,7 +340,7 @@ midi_in_open(PmInternal *midi, void *driverInfo) pm_hosterror = macHostError; sprintf(pm_hosterror_text, "Host error %ld: MIDIPortConnectSource() in midi_in_open()", - macHostError); + (long) macHostError); midi->descriptor = NULL; pm_free(m); return pmHostError; @@ -353,8 +360,8 @@ midi_in_close(PmInternal *midi) if (!m) return pmBadPtr; - endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor; - if (endpoint == NULL) { + endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; + if (endpoint == NULL_REF) { pm_hosterror = pmBadPtr; } @@ -364,7 +371,7 @@ midi_in_close(PmInternal *midi) pm_hosterror = macHostError; sprintf(pm_hosterror_text, "Host error %ld: MIDIPortDisconnectSource() in midi_in_close()", - macHostError); + (long) macHostError); err = pmHostError; } @@ -421,12 +428,12 @@ midi_abort(PmInternal *midi) PmError err = pmNoError; OSStatus macHostError; MIDIEndpointRef endpoint = - (MIDIEndpointRef) descriptors[midi->device_id].descriptor; + (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; macHostError = MIDIFlushOutput(endpoint); if (macHostError != noErr) { pm_hosterror = macHostError; sprintf(pm_hosterror_text, - "Host error %ld: MIDIFlushOutput()", macHostError); + "Host error %ld: MIDIFlushOutput()", (long) macHostError); err = pmHostError; } return err; @@ -439,7 +446,7 @@ midi_write_flush(PmInternal *midi, PmTimestamp timestamp) OSStatus macHostError; midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; MIDIEndpointRef endpoint = - (MIDIEndpointRef) descriptors[midi->device_id].descriptor; + (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; assert(m); assert(endpoint); if (m->packet != NULL) { @@ -454,7 +461,7 @@ send_packet_error: pm_hosterror = macHostError; sprintf(pm_hosterror_text, "Host error %ld: MIDISend() in midi_write()", - macHostError); + (long) macHostError); return pmHostError; } @@ -476,7 +483,12 @@ send_packet(PmInternal *midi, Byte *message, unsigned int messageLength, /* out of space, send the buffer and start refilling it */ /* make midi->packet non-null to fool midi_write_flush into sending */ m->packet = (MIDIPacket *) 4; - if ((err = midi_write_flush(midi, timestamp)) != pmNoError) return err; + /* timestamp is 0 because midi_write_flush ignores timestamp since + * timestamps are already in packets. The timestamp parameter is here + * because other API's need it. midi_write_flush can be called + * from system-independent code that must be cross-API. + */ + if ((err = midi_write_flush(midi, 0)) != pmNoError) return err; m->packet = MIDIPacketListInit(m->packetList); assert(m->packet); /* if this fails, it's a programming error */ m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer), @@ -491,8 +503,8 @@ send_packet(PmInternal *midi, Byte *message, unsigned int messageLength, static PmError midi_write_short(PmInternal *midi, PmEvent *event) { - long when = event->timestamp; - long what = event->message; + PmTimestamp when = event->timestamp; + PmMessage what = event->message; MIDITimeStamp timestamp; UInt64 when_ns; midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; @@ -659,9 +671,9 @@ CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) CFRelease(str); } - MIDIEntityRef entity = NULL; + MIDIEntityRef entity = NULL_REF; MIDIEndpointGetEntity(endpoint, &entity); - if (entity == NULL) + if (entity == NULL_REF) // probably virtual return result; @@ -675,9 +687,9 @@ CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) } } // now consider the device's name - MIDIDeviceRef device = NULL; + MIDIDeviceRef device = NULL_REF; MIDIEntityGetDevice(entity, &device); - if (device == NULL) + if (device == NULL_REF) return result; str = NULL; @@ -722,17 +734,17 @@ static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint) CFMutableStringRef result = CFStringCreateMutable(NULL, 0); CFStringRef str; OSStatus err; - int i; + long i; // Does the endpoint have connections? CFDataRef connections = NULL; - int nConnected = 0; + long nConnected = 0; bool anyStrings = false; err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections); if (connections != NULL) { // It has connections, follow them // Concatenate the names of all connected devices - nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID); + nConnected = CFDataGetLength(connections) / (int32_t) sizeof(MIDIUniqueID); if (nConnected) { const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); for (i = 0; i < nConnected; ++i, ++pid) { @@ -901,7 +913,7 @@ PmError pm_macosxcm_init(void) /* Iterate over the MIDI input devices */ for (i = 0; i < numInputs; i++) { endpoint = MIDIGetSource(i); - if (endpoint == NULL) { + if (endpoint == NULL_REF) { continue; } @@ -911,13 +923,13 @@ PmError pm_macosxcm_init(void) /* Register this device with PortMidi */ pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint), - TRUE, (void*)endpoint, &pm_macosx_in_dictionary); + TRUE, (void *) (long) endpoint, &pm_macosx_in_dictionary); } /* Iterate over the MIDI output devices */ for (i = 0; i < numOutputs; i++) { endpoint = MIDIGetDestination(i); - if (endpoint == NULL) { + if (endpoint == NULL_REF) { continue; } @@ -927,20 +939,22 @@ PmError pm_macosxcm_init(void) /* Register this device with PortMidi */ pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint), - FALSE, (void*)endpoint, &pm_macosx_out_dictionary); + FALSE, (void *) (long) endpoint, + &pm_macosx_out_dictionary); } return pmNoError; error_return: pm_hosterror = macHostError; - sprintf(pm_hosterror_text, "Host error %ld: %s\n", macHostError, error_text); + sprintf(pm_hosterror_text, "Host error %ld: %s\n", (long) macHostError, + error_text); pm_macosxcm_term(); /* clear out any opened ports */ return pmHostError; } void pm_macosxcm_term(void) { - if (client != NULL) MIDIClientDispose(client); - if (portIn != NULL) MIDIPortDispose(portIn); - if (portOut != NULL) MIDIPortDispose(portOut); + if (client != NULL_REF) MIDIClientDispose(client); + if (portIn != NULL_REF) MIDIPortDispose(portIn); + if (portOut != NULL_REF) MIDIPortDispose(portOut); } diff --git a/lib-src/portmidi/pm_mac/readbinaryplist.c b/lib-src/portmidi/pm_mac/readbinaryplist.c index d696497b7..f33d01fb8 100644 --- a/lib-src/portmidi/pm_mac/readbinaryplist.c +++ b/lib-src/portmidi/pm_mac/readbinaryplist.c @@ -3,6 +3,10 @@ readbinaryplist.c -- Roger B. Dannenberg, Jun 2008 Based on ReadBinaryPList.m by Jens Ayton, 2007 +Note that this code is intended to read preference files and has an upper +bound on file size (currently 100MB) and assumes in some places that 32 bit +offsets are sufficient. + Here are his comments: Reader for binary property list files (version 00). @@ -74,13 +78,25 @@ memory requested or calls longjmp, so callers don't have to check. #include #include #include "readbinaryplist.h" -#include -#define BPLIST_LOG_VERBOSE 1 +#include "Folders.h" + #define NO 0 #define YES 1 #define BOOL int #define MAXPATHLEN 256 + +/* there are 2 levels of error logging/printing: + * BPLIST_LOG and BPLIST_LOG_VERBOSE + * either or both can be set to non-zero to turn on + * If BPLIST_LOG_VERBOSE is true, then BPLIST_LOG + * is also true. + * + * In the code, logging is done by calling either + * bplist_log() or bplist_log_verbose(), which take + * parameters like printf but might be a no-op. + */ + /* #define BPLIST_LOG_VERBOSE 1 */ #if BPLIST_LOG_VERBOSE @@ -245,7 +261,7 @@ void value_set_uid(value_ptr v, uint64_t uid) v->tag = kTAG_UID; v->uinteger = uid; } -// value->data points to a pldata that points to the actual bytes +// v->data points to a pldata that points to the actual bytes // the bytes are copied, so caller must free byte source (*data) void value_set_data(value_ptr v, const uint8_t *data, size_t len) { v->tag = kTAG_DATA; @@ -324,18 +340,26 @@ value_ptr bplist_read_file(char *filename) value_ptr value; int rslt = stat(filename, &stbuf); if (rslt) { - perror("in stat: "); + #if BPLIST_LOG + perror("in stat"); + #endif bplist_log("Could not stat %s, error %d\n", filename, rslt); return NULL; } - pldata.len = stbuf.st_size; + // if file is >100MB, assume it is not a preferences file and give up + if (stbuf.st_size > 100000000) { + bplist_log("Large file %s encountered (%llu bytes) -- not read\n", + filename, stbuf.st_size); + return NULL; + } + pldata.len = (size_t) stbuf.st_size; // note: this is supposed to be malloc, not allocate. It is separate // from the graph structure, large, and easy to free right after // parsing. pldata.data = (uint8_t *) malloc(pldata.len); if (!pldata.data) { - bplist_log("Could not allocate %d bytes for %s\n", - (long) pldata.len, filename); + bplist_log("Could not allocate %lu bytes for %s\n", + (unsigned long) pldata.len, filename); return NULL; } file = fopen(filename, "rb"); @@ -664,7 +688,8 @@ static value_ptr extract_real(bplist_info_ptr bplist, uint64_t offset) } if (size == sizeof (float)) { - uint32_t i = read_sized_int(bplist, offset + 1, size); + // cast is ok because we know size is 4 bytes + uint32_t i = (uint32_t) read_sized_int(bplist, offset + 1, size); // Note that this handles byte swapping. value_set_real(value, *(float *)&i); return value; @@ -755,7 +780,8 @@ static value_ptr extract_data(bplist_info_ptr bplist, uint64_t offset) return NULL; value = value_create(); - value_set_data(value, bplist->data_bytes + offset, size); + // cast is ok because we only allow files up to 100MB: + value_set_data(value, bplist->data_bytes + (size_t) offset, (size_t) size); return value; } @@ -772,7 +798,9 @@ static value_ptr extract_ascii_string(bplist_info_ptr bplist, uint64_t offset) return NULL; value = value_create(); - value_set_ascii_string(value, bplist->data_bytes + offset, size); + // cast is ok because we only allow 100MB files + value_set_ascii_string(value, bplist->data_bytes + (size_t) offset, + (size_t) size); return value; } @@ -789,7 +817,9 @@ static value_ptr extract_unicode_string(bplist_info_ptr bplist, uint64_t offset) return NULL; value = value_create(); - value_set_unicode_string(value, bplist->data_bytes + offset, size); + // cast is ok because we only allow 100MB files + value_set_unicode_string(value, bplist->data_bytes + (size_t) offset, + (size_t) size); return value; } @@ -814,15 +844,20 @@ static value_ptr extract_uid(bplist_info_ptr bplist, uint64_t offset) return NULL; } - assert(NO); // original code suggests using a string for a key + // assert(NO); // original code suggests using a string for a key // but our dictionaries all use big ints for keys, so I don't know // what to do here + + // In practice, I believe this code is never executed by PortMidi. + // I changed it to do something and not raise compiler warnings, but + // not sure what the code should do. value = value_create(); value_set_uid(value, uid); // return [NSDictionary dictionaryWithObject: // [NSNumber numberWithUnsignedLongLong:value] // forKey:"CF$UID"]; + return value; } @@ -861,11 +896,12 @@ static value_ptr extract_array(bplist_info_ptr bplist, uint64_t offset) assert(value); if (count == 0) { - value_set_array(value, array, count); + // count must be size_t or smaller because max file size is 100MB + value_set_array(value, array, (size_t) count); return value; } - array = allocate(sizeof(value_ptr) * count); + array = allocate(sizeof(value_ptr) * (size_t) count); for (i = 0; i != count; ++i) { bplist_log_verbose("[%u]\n", i); @@ -879,8 +915,8 @@ static value_ptr extract_array(bplist_info_ptr bplist, uint64_t offset) break; } } - if (ok) { - value_set_array(value, array, count); + if (ok) { // count is smaller than size_t max because of 100MB file limit + value_set_array(value, array, (size_t) count); } return value; diff --git a/lib-src/portmidi/pm_mac/readbinaryplist.h b/lib-src/portmidi/pm_mac/readbinaryplist.h index c875920a3..577865996 100644 --- a/lib-src/portmidi/pm_mac/readbinaryplist.h +++ b/lib-src/portmidi/pm_mac/readbinaryplist.h @@ -3,6 +3,8 @@ Roger B. Dannenberg, Jun 2008 */ +#include /* for uint8_t ... */ + #ifndef TRUE #define TRUE 1 #define FALSE 0 @@ -34,13 +36,13 @@ enum }; -typedef struct { +typedef struct pldata_struct { uint8_t *data; size_t len; } pldata_node, *pldata_ptr; -typedef struct { +typedef struct array_struct { struct value_struct **array; uint64_t length; } array_node, *array_ptr; diff --git a/lib-src/portmidi/pm_test/latency.c b/lib-src/portmidi/pm_test/latency.c index 8a7e005bd..bfcdc0ca0 100644 --- a/lib-src/portmidi/pm_test/latency.c +++ b/lib-src/portmidi/pm_test/latency.c @@ -93,9 +93,9 @@ PtTimestamp previous_callback_time = 0; int period; /* milliseconds per callback */ -long histogram[HIST_LEN]; -long max_latency = 0; /* worst latency observed */ -long out_of_range = 0; /* how many points outside of HIST_LEN? */ +int histogram[HIST_LEN]; +int max_latency = 0; /* worst latency observed */ +int out_of_range = 0; /* how many points outside of HIST_LEN? */ int test_in, test_out; /* test MIDI in and/or out? */ int output_period; /* output MIDI every __ iterations if test_out true */ @@ -199,7 +199,7 @@ int main() i, NULL, INPUT_BUFFER_SIZE, - (long (*)(void *)) Pt_Time, + (PmTimestamp (*)(void *)) Pt_Time, NULL); /* turn on filtering; otherwise, input might overflow in the 5-second period before timer callback starts reading midi */ @@ -212,7 +212,7 @@ int main() i, NULL, OUTPUT_BUFFER_SIZE, - (long (*)(void *)) Pt_Time, + (PmTimestamp (*)(void *)) Pt_Time, NULL, 0); /* no latency scheduling */ @@ -252,11 +252,11 @@ int main() /* avoid printing beyond last non-zero histogram entry */ len = min(HIST_LEN, max_latency + 1); for (i = 0; i < len; i++) { - printf("%2d %10ld\n", i, histogram[i]); + printf("%2d %10d\n", i, histogram[i]); } - printf("Number of points greater than %dms: %ld\n", + printf("Number of points greater than %dms: %d\n", HIST_LEN - 1, out_of_range); - printf("Maximum latency: %ld milliseconds\n", max_latency); + printf("Maximum latency: %d milliseconds\n", max_latency); printf("\nNote that due to rounding, actual latency can be 1ms higher\n"); printf("than the numbers reported here.\n"); printf("Type return to exit..."); diff --git a/lib-src/portmidi/pm_test/midiclock.c b/lib-src/portmidi/pm_test/midiclock.c index 43b16e166..60fcf7a9a 100644 --- a/lib-src/portmidi/pm_test/midiclock.c +++ b/lib-src/portmidi/pm_test/midiclock.c @@ -2,10 +2,11 @@ #include "portmidi.h" #include "porttime.h" -#include "stdlib.h" -#include "stdio.h" -#include "string.h" -#include "assert.h" +#include +#include +#include +#include +#include #ifndef false #define false 0 @@ -23,7 +24,7 @@ typedef int boolean; #define OUTPUT_BUFFER_SIZE 0 #define DRIVER_INFO NULL -#define TIME_PROC ((long (*)(void *)) Pt_Time) +#define TIME_PROC ((int32_t (*)(void *)) Pt_Time) #define TIME_INFO NULL #define LATENCY 0 #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ @@ -63,7 +64,7 @@ float tempo = 60.0F; void timer_poll(PtTimestamp timestamp, void *userData) { static int callback_owns_portmidi = false; - static long clock_start_time = 0; + static PmTimestamp clock_start_time = 0; static double next_clock_time = 0; /* SMPTE time */ static int frames = 0; @@ -103,7 +104,7 @@ void timer_poll(PtTimestamp timestamp, void *userData) } } if (time_code_running) { - int data; + int data = 0; // initialization avoids compiler warning if ((timestamp - smpte_start_time) < next_smpte_time) return; switch (mtc_count) { @@ -212,9 +213,9 @@ private void doascii(char c) int input_tempo = get_number("Enter new tempo (bpm): "); if (input_tempo >= 1 && input_tempo <= 300) { printf("Changing tempo to %d\n", input_tempo); - tempo = input_tempo; + tempo = (float) input_tempo; } else { - printf("Tempo range is 1 to 300, current tempo is %d bpm\n", + printf("Tempo range is 1 to 300, current tempo is %g bpm\n", tempo); } } else { diff --git a/lib-src/portmidi/pm_test/midithread.c b/lib-src/portmidi/pm_test/midithread.c index 1e0a8ccce..fab9794f2 100644 --- a/lib-src/portmidi/pm_test/midithread.c +++ b/lib-src/portmidi/pm_test/midithread.c @@ -73,7 +73,7 @@ int active = FALSE; int monitor = FALSE; int midi_thru = TRUE; -long transpose; +int transpose; PmStream *midi_in; PmStream *midi_out; @@ -94,7 +94,7 @@ void process_midi(PtTimestamp timestamp, void *userData) { PmError result; PmEvent buffer; /* just one message at a time */ - long msg; + int32_t msg; /* do nothing until initialization completes */ if (!active) @@ -127,7 +127,7 @@ void process_midi(PtTimestamp timestamp, void *userData) do { result = Pm_Poll(midi_in); if (result) { - long status, data1, data2; + int status, data1, data2; if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow) continue; if (midi_thru) @@ -173,7 +173,7 @@ void exit_with_message(char *msg) int main() { int id; - long n; + int32_t n; const PmDeviceInfo *info; char line[STRING_MAX]; int spin; @@ -190,12 +190,12 @@ int main() /* make the message queues */ /* messages can be of any size and any type, but all messages in - * a given queue must have the same size. We'll just use long's + * a given queue must have the same size. We'll just use int32_t's * for our messages in this simple example */ - midi_to_main = Pm_QueueCreate(32, sizeof(long)); + midi_to_main = Pm_QueueCreate(32, sizeof(int32_t)); assert(midi_to_main != NULL); - main_to_midi = Pm_QueueCreate(32, sizeof(long)); + main_to_midi = Pm_QueueCreate(32, sizeof(int32_t)); assert(main_to_midi != NULL); /* a little test of enqueue and dequeue operations. Ordinarily, @@ -263,7 +263,8 @@ int main() "Must terminate with [ENTER]"); while (!done) { - long msg; + int32_t msg; + int input; int len; fgets(line, STRING_MAX, stdin); /* remove the newline: */ @@ -284,7 +285,8 @@ int main() do { spin = Pm_Dequeue(midi_to_main, &msg); } while (spin == 0); /* spin */ ; - printf("... pitch is %ld\n", msg); + // convert int32_t to long for safe printing + printf("... pitch is %ld\n", (long) msg); } else if (strcmp(line, "t") == 0) { /* reading midi_thru asynchronously could give incorrect results, e.g. if you type "t" twice before the midi thread responds to @@ -294,10 +296,11 @@ int main() printf("Setting THRU %s\n", (midi_thru ? "off" : "on")); msg = THRU_MSG; Pm_Enqueue(main_to_midi, &msg); - } else if (sscanf(line, "%ld", &msg) == 1) { - if (msg >= -127 && msg <= 127) { - /* send transposition value */ - printf("Transposing by %ld\n", msg); + } else if (sscanf(line, "%d", &input) == 1) { + if (input >= -127 && input <= 127) { + /* send transposition value, make sur */ + printf("Transposing by %d\n", input); + msg = (int32_t) input; Pm_Enqueue(main_to_midi, &msg); } else { printf("Transposition must be within -127...127\n"); diff --git a/lib-src/portmidi/pm_test/midithru.c b/lib-src/portmidi/pm_test/midithru.c index ffab64ed4..8ad723138 100644 --- a/lib-src/portmidi/pm_test/midithru.c +++ b/lib-src/portmidi/pm_test/midithru.c @@ -99,7 +99,7 @@ PmTimestamp last_timestamp = 0; /* time proc parameter for Pm_MidiOpen */ -long midithru_time_proc(void *info) +PmTimestamp midithru_time_proc(void *info) { return current_timestamp; } @@ -132,7 +132,7 @@ void process_midi(PtTimestamp timestamp, void *userData) do { result = Pm_Poll(midi_in); if (result) { - long status; + int status; PmError rslt = Pm_Read(midi_in, &buffer, 1); if (rslt == pmBufferOverflow) continue; @@ -189,7 +189,7 @@ void process_midi(PtTimestamp timestamp, void *userData) assert(next); /* must be non-null because queue is not empty */ if (next->timestamp <= current_timestamp) { /* time to send a message, first make sure it's not blocked */ - long status = Pm_MessageStatus(next->message); + int status = Pm_MessageStatus(next->message); if ((status & 0xF8) == 0xF8) { ; /* real-time messages are not blocked */ } else if (thru_sysex_in_progress) { diff --git a/lib-src/portmidi/pm_test/mm.c b/lib-src/portmidi/pm_test/mm.c index bbe69f6bf..380561445 100644 --- a/lib-src/portmidi/pm_test/mm.c +++ b/lib-src/portmidi/pm_test/mm.c @@ -83,12 +83,12 @@ boolean chmode = true; /* show channel mode messages */ boolean pgchanges = true; /* show program changes */ boolean flush = false; /* flush all pending MIDI data */ -long filter = 0; /* remember state of midi filter */ +uint32_t filter = 0; /* remember state of midi filter */ -long clockcount = 0; /* count of clocks */ -long actsensecount = 0; /* cout of active sensing bytes */ -long notescount = 0; /* #notes since last request */ -long notestotal = 0; /* total #notes */ +uint32_t clockcount = 0; /* count of clocks */ +uint32_t actsensecount = 0; /* cout of active sensing bytes */ +uint32_t notescount = 0; /* #notes since last request */ +uint32_t notestotal = 0; /* total #notes */ char val_format[] = " Val %d\n"; @@ -102,11 +102,11 @@ extern int abort_flag; * Routines local to this module *****************************************************************************/ -private void mmexit(); +private void mmexit(int code); private void output(PmMessage data); private int put_pitch(int p); private void showhelp(); -private void showbytes(long data, int len, boolean newline); +private void showbytes(PmMessage data, int len, boolean newline); private void showstatus(boolean flag); private void doascii(char c); private int get_number(char *prompt); @@ -133,7 +133,7 @@ void receive_poll(PtTimestamp timestamp, void *userData) PmEvent event; int count; if (!active) return; - while (count = Pm_Read(midi_in, &event, 1)) { + while ((count = Pm_Read(midi_in, &event, 1))) { if (count == 1) output(event.message); else printf(Pm_GetErrorText(count)); } @@ -169,7 +169,7 @@ int main(int argc, char **argv) if (err) { printf(Pm_GetErrorText(err)); Pt_Stop(); - exit(1); + mmexit(1); } Pm_SetFilter(midi_in, filter); inited = true; /* now can document changes, set filter */ @@ -185,7 +185,8 @@ int main(int argc, char **argv) Pm_Close(midi_in); Pt_Stop(); Pm_Terminate(); - exit(0); + mmexit(0); + return 0; // make the compiler happy be returning a value } @@ -240,7 +241,7 @@ private void doascii(char c) if (clksencnt) { if (inited) printf("Clock Count %ld\nActive Sense Count %ld\n", - clockcount, actsensecount); + (long) clockcount, (long) actsensecount); } else if (inited) { printf("Clock Counting not on\n"); } @@ -248,7 +249,7 @@ private void doascii(char c) notestotal+=notescount; if (inited) printf("This Note Count %ld\nTotal Note Count %ld\n", - notescount, notestotal); + (long) notescount, (long) notestotal); notescount=0; } else if (c == 'v') { verbose = !verbose; @@ -272,12 +273,12 @@ private void doascii(char c) -private void mmexit() +private void mmexit(int code) { /* if this is not being run from a console, maybe we should wait for * the user to read error messages before exiting */ - exit(1); + exit(code); } @@ -304,7 +305,7 @@ private void output(PmMessage data) if (in_sysex || Pm_MessageStatus(data) == MIDI_SYSEX) { #define sysex_max 16 int i; - long data_copy = data; + PmMessage data_copy = data; in_sysex = true; /* look for MIDI_EOX in first 3 bytes * if realtime messages are embedded in sysex message, they will @@ -491,7 +492,7 @@ private int put_pitch(int p) char nib_to_hex[] = "0123456789ABCDEF"; -private void showbytes(long data, int len, boolean newline) +private void showbytes(PmMessage data, int len, boolean newline) { int count = 0; int i; diff --git a/lib-src/portmidi/pm_test/qtest.c b/lib-src/portmidi/pm_test/qtest.c index c34913378..14d803e87 100644 --- a/lib-src/portmidi/pm_test/qtest.c +++ b/lib-src/portmidi/pm_test/qtest.c @@ -22,7 +22,7 @@ void print_msg(long msg[], int n) { int i; for (i = 0; i < n; i++) { - printf(" %d", msg[i]); + printf(" %li", msg[i]); } } diff --git a/lib-src/portmidi/pm_test/sysex.c b/lib-src/portmidi/pm_test/sysex.c index 3138a6a55..c1409802f 100644 --- a/lib-src/portmidi/pm_test/sysex.c +++ b/lib-src/portmidi/pm_test/sysex.c @@ -17,6 +17,7 @@ // need to get declaration for Sleep() #include "windows.h" #else +#include #define Sleep(n) usleep(n * 1000) #endif @@ -58,7 +59,7 @@ void loopback_test() PmStream *midi_out; unsigned char msg[1024]; char line[80]; - long len; + int32_t len; int i; int data; PmEvent event; @@ -88,10 +89,10 @@ void loopback_test() while (1) { PmError count; - long start_time; - long error_position = -1; /* 0; -1; -1 for continuous */ - long expected = 0; - long actual = 0; + int32_t start_time; + int error_position = -1; /* 0; -1; -1 for continuous */ + int expected = 0; + int actual = 0; /* this modification will run until an error is detected */ /* set error_position above to 0 for interactive, -1 for */ /* continuous */ @@ -124,7 +125,7 @@ void loopback_test() } /* send the message */ - printf("Sending %ld byte sysex message.\n", len + 2); + printf("Sending %d byte sysex message.\n", len + 2); Pm_WriteSysEx(midi_out, 0, msg); /* receive the message and compare to msg[] */ @@ -156,7 +157,7 @@ void loopback_test() } } if (error_position >= 0) { - printf("Error at byte %ld: sent %lx recd %lx\n", error_position, + printf("Error at byte %d: sent %x recd %x\n", error_position, expected, actual); } else if (i != len + 2) { printf("Error: byte %d not received\n", i); @@ -228,11 +229,11 @@ void send_multiple_test() #define MAX_MSG_LEN 1024 static unsigned char receive_msg[MAX_MSG_LEN]; -static long receive_msg_index; -static long receive_msg_length; -static long receive_msg_count; -static long receive_msg_error; -static long receive_msg_messages; +static int receive_msg_index; +static int receive_msg_length; +static int receive_msg_count; +static int receive_msg_error; +static int receive_msg_messages; static PmStream *receive_msg_midi_in; static int receive_poll_running; @@ -316,7 +317,7 @@ void receive_multiple_test() /* Important: start PortTime first -- if it is not started first, it will be started by PortMidi, and then our attempt to open again will fail */ receive_poll_running = false; - if (err = Pt_Start(1, receive_poll, 0)) { + if ((err = Pt_Start(1, receive_poll, 0))) { printf("PortTime error code: %d\n", err); goto cleanup; } diff --git a/lib-src/portmidi/pm_test/test.c b/lib-src/portmidi/pm_test/test.c index 0cd731a91..03d6331ad 100644 --- a/lib-src/portmidi/pm_test/test.c +++ b/lib-src/portmidi/pm_test/test.c @@ -8,13 +8,13 @@ #define INPUT_BUFFER_SIZE 100 #define OUTPUT_BUFFER_SIZE 0 #define DRIVER_INFO NULL -#define TIME_PROC ((long (*)(void *)) Pt_Time) +#define TIME_PROC ((int32_t (*)(void *)) Pt_Time) #define TIME_INFO NULL #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ #define STRING_MAX 80 /* used for console input */ -long latency = 0; +int32_t latency = 0; /* crash the program to test whether midi ports are closed */ /**/ @@ -87,10 +87,10 @@ void main_test_input(unsigned int somethingStupid) { if (length > 0) { printf("Got message %d: time %ld, %2lx %2lx %2lx\n", i, - buffer[0].timestamp, - Pm_MessageStatus(buffer[0].message), - Pm_MessageData1(buffer[0].message), - Pm_MessageData2(buffer[0].message)); + (long) buffer[0].timestamp, + (long) Pm_MessageStatus(buffer[0].message), + (long) Pm_MessageData1(buffer[0].message), + (long) Pm_MessageData2(buffer[0].message)); i++; } else { assert(0); @@ -116,7 +116,7 @@ void main_test_input(unsigned int somethingStupid) { void main_test_output() { PmStream * midi; char line[80]; - long off_time; + int32_t off_time; int chord[] = { 60, 67, 76, 83, 90 }; #define chord_size 5 PmEvent buffer[chord_size]; @@ -139,7 +139,7 @@ void main_test_output() { (latency == 0 ? NULL : TIME_PROC), (latency == 0 ? NULL : TIME_INFO), latency); - printf("Midi Output opened with %ld ms latency.\n", latency); + printf("Midi Output opened with %ld ms latency.\n", (long) latency); /* output note on/off w/latency offset; hold until user prompts */ printf("ready to send program 1 change... (type RETURN):"); @@ -230,7 +230,7 @@ void main_test_both() TIME_PROC, TIME_INFO, latency); - printf("Midi Output opened with %ld ms latency.\n", latency); + printf("Midi Output opened with %ld ms latency.\n", (long) latency); /* open input device */ Pm_OpenInput(&midi, in, @@ -253,11 +253,11 @@ void main_test_both() if (length > 0) { Pm_Write(midiOut, buffer, 1); printf("Got message %d: time %ld, %2lx %2lx %2lx\n", - i, - buffer[0].timestamp, - Pm_MessageStatus(buffer[0].message), - Pm_MessageData1(buffer[0].message), - Pm_MessageData2(buffer[0].message)); + i, + (long) buffer[0].timestamp, + (long) Pm_MessageStatus(buffer[0].message), + (long) Pm_MessageData1(buffer[0].message), + (long) Pm_MessageData2(buffer[0].message)); i++; } else { assert(0); @@ -301,7 +301,7 @@ void main_test_stream() { TIME_PROC, TIME_INFO, latency); - printf("Midi Output opened with %ld ms latency.\n", latency); + printf("Midi Output opened with %ld ms latency.\n", (long) latency); /* output note on/off w/latency offset; hold until user prompts */ printf("ready to send output... (type RETURN):"); @@ -386,25 +386,32 @@ int main(int argc, char *argv[]) int stream_test = 0; int latency_valid = FALSE; + if (sizeof(void *) == 8) + printf("Apparently this is a 64-bit machine.\n"); + else if (sizeof(void *) == 4) + printf ("Apparently this is a 32-bit machine.\n"); + for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-h") == 0) { show_usage(); } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { i = i + 1; latency = atoi(argv[i]); - printf("Latency will be %ld\n", latency); - latency_valid = TRUE; + printf("Latency will be %ld\n", (long) latency); + latency_valid = TRUE; } else { show_usage(); } } - while (!latency_valid) { - printf("Latency in ms: "); - if (scanf("%ld", &latency) == 1) { - latency_valid = TRUE; - } - } + while (!latency_valid) { + int lat; // declared int to match "%d" + printf("Latency in ms: "); + if (scanf("%d", &lat) == 1) { + latency = (int32_t) lat; // coerce from "%d" to known size + latency_valid = TRUE; + } + } /* determine what type of test to run */ printf("begin portMidi test...\n"); @@ -413,7 +420,7 @@ int main(int argc, char *argv[]) " 2: test input (fail w/assert)\n", " 3: test input (fail w/NULL assign)\n", " 4: test output\n 5: test both\n", - " 6: stream test\n"); + " 6: stream test\n"); while (n != 1) { n = scanf("%d", &i); fgets(line, STRING_MAX, stdin); diff --git a/lib-src/portmidi/pm_win/README_WIN.txt b/lib-src/portmidi/pm_win/README_WIN.txt index 366dfeecc..674765b2c 100644 --- a/lib-src/portmidi/pm_win/README_WIN.txt +++ b/lib-src/portmidi/pm_win/README_WIN.txt @@ -2,6 +2,17 @@ File: PortMidi Win32 Readme Author: Belinda Thom, June 16 2002 Revised by: Roger Dannenberg, June 2002, May 2004, June 2007, Umpei Kurokawa, June 2007 + Roger Dannenberg Sep 2009 + +Contents: + Using Portmidi + To Install Portmidi + To Compile Portmidi + About Cmake + Using other versions of Visual C++ + To Create Your Own Portmidi Client Application + + ============================================================================= USING PORTMIDI: @@ -26,7 +37,7 @@ is both optimized and lacking the debugging printout code of the Debug version. Read the portmidi.h file for PortMidi API details on using the PortMidi API. -See <...>\pm_dll_test\test.c or <...>\multithread\test.c for usage examples. +See <...>\pm_test\test.c and other files in pm_test for usage examples. ============================================================================= TO INSTALL PORTMIDI: @@ -45,8 +56,7 @@ TO COMPILE PORTMIDI: portmidi-VC9.sln for Visual C++ version 9 users). 5) the following projects exist within this workspace: - - portmidi (the PortMidi library) - - porttime (a small portable library implementing timer facilities) + - portmidi-static, portmidi-dynamic (versions of the PortMidi library) - test (simple midi I/O testing) - midithread (an example illustrating low-latency MIDI processing using a dedicated low-latency thread) @@ -57,43 +67,82 @@ TO COMPILE PORTMIDI: - mm (allows monitoring of midi messages) - pmjni (a dll to provide an interface to PortMidi for Java) -6) open the pmjni project properties - - visit Configuration Properties, C/C++, General - - find Additional Include Directories property and open the editor (...) - - at the end of the list, you will find two paths beginning with E:\ - - these are absolute paths to the Java SDK; you'll need to install the - Java SDK (from Sun) and update these directories in order to build - this project. +6) set the Java SDK path using one of two methods: + Method 1: open portmidi/CMakeLists.txt with CMake, configure, and + generate -- this should find the Java SDK path and update your + solution and project files + Method 2: (does not require CMake): + - open the pmjni project properties + - visit Configuration Properties, C/C++, General + - find Additional Include Directories property and open the editor (...) + - at the end of the list, you will find two paths mentioning Java + - these are absolute paths to the Java SDK; you'll need to install the + Java SDK (from Sun) and update these directories in order to build + this project. -6) verify that all project settings are for Win32 Debug release: - - type Alt-F7 - - highlight all three projects in left part of Project Settings window; - - "Settings For" should say "Win32 Debug" +6) use Build->Batch Build ... to build everything in the project - -In Visual C++ 2005 Express Edition, there is a drop down menu in - the top toolbar to select the Win32 and Debug option. - -7) use Build->Batch Build ... to build everything in the project - - -In Visual C++ 2005 Express Edition, use Build->Build Solution - -8) The settings for these projects were distributed in the zip file, so +7) The settings for these projects were distributed in the zip file, so compile should just work. -9) run test project; use the menu that shows up from the command prompt to +8) run test project; use the menu that shows up from the command prompt to test that portMidi works on your system. tests include: - verify midi output works - verify midi input works -10) run other projects if you wish: sysex, latency, midithread, mm, +9) run other projects if you wish: sysex, latency, midithread, mm, qtest, midithru -11) use pm_java/make.bat (run in a cmd window from pm_java) to compile - the java code. +10) compile the java code: + - cd pm_java + - make.bat + + If there is a problem running javac, note that you must have + a path to javac.exe on your PATH environment variable. Edit + your path (in Vista) using Control Panel > User Accounts > + User Accounts > Change my environment variables; then select + Path and click Edit... After changing, you will have to + restart the command window to see any effect. + + In Vista, you may get a warning about running + UpdateRsrcJavaExe.exe. This is called by make.bat, and you + should allow the program to run. + + Note that make.bat does not build pmjni\jportmidi_JPortMidiApi.h + because it is included in the distribution. You can rebuild it + from sources as follows: + cd pm_java + javah jportmidi.JPortMidiApi + move jportmidi_JPortMidiApi pmjni\jportmidi_JPortMidiApi.h + +11) you might wish to move pm_java/win32 to another location; run the + pmdefaults.exe program from the win32 directory to use PmDefaults. + This program let's you select default input/output midi devices + for PortMidi applications. -12) run pm_java/pmdefaults.bat (run in a cmd window from pm_java) to - run the PmDefaults program. This lets you select the default input - and output devices for PortMidi. +============================================================================ +ABOUT CMAKE +============================================================================ + +cmake was used to generate .vcproj files. cmake embeds absolute paths +into .vcproj files, which makes the files non-portable to other systems. +To work around this problem, pm_win\clean_up_vcproj.bat can be used to +replace absolute paths with relative paths. To use it, you will need to +install gawk and set your search path to allow you to execute gawk, e.g. +my path includes "C:\Program Files\GnuWin32\bin;". You will also need to +edit pm_win\clean_up_vcproj.awk, replacing C:\Users\rbd\portmidi with +whatever absolute path cmake uses in your vcproj files. + +This is not a general or robust fix, but it seems to work with the +vcproj files currently created by CMake. + +============================================================================ +USING OTHER VERSIONS OF VISUAL C++ +============================================================================ + +You can use cmake to make Visual Studio solution and project files. +If you do not want to use the provided Version 9 project files, install +cmake, run it, set the "Where is the source code" box to your portmidi +directory, and click on Configure. A menu will allow you to choose the +Visual Studio project version you want. Click Configure once again, then +Generate, and you should be all set to open portmidi.sln. ============================================================================ TO CREATE YOUR OWN PORTMIDI CLIENT APPLICATION: diff --git a/lib-src/portmidi/pm_win/pmwinmm.c b/lib-src/portmidi/pm_win/pmwinmm.c index 6e6354df6..ebfd4e625 100644 --- a/lib-src/portmidi/pm_win/pmwinmm.c +++ b/lib-src/portmidi/pm_win/pmwinmm.c @@ -23,7 +23,6 @@ /* asserts used to verify portMidi code logic is sound; later may want something more graceful */ #include -#define DEBUG 1 #ifdef DEBUG /* this printf stuff really important for debugging client app w/host errors. probably want to do something else besides read/write from/to console @@ -740,19 +739,20 @@ static void FAR PASCAL winmm_in_callback( case, we do not want to send them back to the interface (if we do, the interface will not close, and Windows OS may hang). */ if (lpMidiHdr->dwBytesRecorded > 0) { + MMRESULT rslt; lpMidiHdr->dwBytesRecorded = 0; lpMidiHdr->dwFlags = 0; /* note: no error checking -- can this actually fail? */ - assert(midiInPrepareHeader(hMidiIn, lpMidiHdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); + rslt = midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); + assert(rslt == MMSYSERR_NOERROR); /* note: I don't think this can fail except possibly for * MMSYSERR_NOMEM, but the pain of reporting this * unlikely but probably catastrophic error does not seem * worth it. */ - assert(midiInAddBuffer(hMidiIn, lpMidiHdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); + rslt = midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); + assert(rslt == MMSYSERR_NOERROR); LeaveCriticalSection(&m->lock); } else { midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR)); @@ -1313,9 +1313,11 @@ static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, printf("out_callback: hdr %x, wMsg %x, MOM_DONE %x\n", hdr, wMsg, MOM_DONE); */ - if (wMsg == MOM_DONE) - assert(midiOutUnprepareHeader(m->handle.out, hdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); + if (wMsg == MOM_DONE) { + MMRETURN ret = midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)); + assert(ret == MMSYSERR_NOERROR); + } /* notify waiting sender that a buffer is available */ err = SetEvent(m->buffer_signal); assert(err); /* false -> error */ @@ -1337,8 +1339,9 @@ static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", hdr, wMsg, MOM_DONE); */ if (wMsg == MOM_DONE) { - assert(midiOutUnprepareHeader(m->handle.out, hdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); + MMRESULT ret = midiOutUnprepareHeader(m->handle.out, hdr, + sizeof(MIDIHDR)); + assert(ret == MMSYSERR_NOERROR); } /* signal client in case it is blocked waiting for buffer */ err = SetEvent(m->buffer_signal); diff --git a/lib-src/portmidi/porttime/porttime.h b/lib-src/portmidi/porttime/porttime.h index 1ed12eb77..ff22de9d5 100644 --- a/lib-src/portmidi/porttime/porttime.h +++ b/lib-src/portmidi/porttime/porttime.h @@ -7,10 +7,30 @@ /* Should there be a way to choose the source of time here? */ +#ifdef WIN32 +#ifndef INT32_DEFINED +// rather than having users install a special .h file for windows, +// just put the required definitions inline here. portmidi.h uses +// these too, so the definitions are (unfortunately) duplicated there +typedef int int32_t; +typedef unsigned int uint32_t; +#define INT32_DEFINED +#endif +#else +#include // needed for int32_t +#endif + #ifdef __cplusplus extern "C" { #endif +#ifndef PMEXPORT +#ifdef _WINDLL +#define PMEXPORT __declspec(dllexport) +#else +#define PMEXPORT +#endif +#endif typedef enum { ptNoError = 0, /* success */ @@ -21,7 +41,7 @@ typedef enum { } PtError; -typedef long PtTimestamp; +typedef int32_t PtTimestamp; typedef void (PtCallback)( PtTimestamp timestamp, void *userData ); @@ -38,7 +58,7 @@ typedef void (PtCallback)( PtTimestamp timestamp, void *userData ); return value: Upon success, returns ptNoError. See PtError for other values. */ -PtError Pt_Start(int resolution, PtCallback *callback, void *userData); +PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData); /* Pt_Stop() stops the timer. @@ -46,17 +66,17 @@ PtError Pt_Start(int resolution, PtCallback *callback, void *userData); return value: Upon success, returns ptNoError. See PtError for other values. */ -PtError Pt_Stop(); +PMEXPORT PtError Pt_Stop(); /* Pt_Started() returns true iff the timer is running. */ -int Pt_Started(); +PMEXPORT int Pt_Started(); /* Pt_Time() returns the current time in ms. */ -PtTimestamp Pt_Time(); +PMEXPORT PtTimestamp Pt_Time(); /* Pt_Sleep() pauses, allowing other threads to run. @@ -65,7 +85,7 @@ PtTimestamp Pt_Time(); of the pause may be rounded to the nearest or next clock tick as determined by resolution in Pt_Start(). */ -void Pt_Sleep(long duration); +PMEXPORT void Pt_Sleep(int32_t duration); #ifdef __cplusplus } diff --git a/lib-src/portmidi/porttime/ptlinux.c b/lib-src/portmidi/porttime/ptlinux.c index e2aad0a05..2816c85c3 100644 --- a/lib-src/portmidi/porttime/ptlinux.c +++ b/lib-src/portmidi/porttime/ptlinux.c @@ -124,7 +124,7 @@ PtTimestamp Pt_Time() } -void Pt_Sleep(long duration) +void Pt_Sleep(int32_t duration) { usleep(duration * 1000); } diff --git a/lib-src/portmidi/porttime/ptmacosx_cf.c b/lib-src/portmidi/porttime/ptmacosx_cf.c index 71f11664d..3eb0de12f 100644 --- a/lib-src/portmidi/porttime/ptmacosx_cf.c +++ b/lib-src/portmidi/porttime/ptmacosx_cf.c @@ -134,7 +134,7 @@ PtTimestamp Pt_Time() } -void Pt_Sleep(long duration) +void Pt_Sleep(int32_t duration) { usleep(duration * 1000); } diff --git a/lib-src/portmidi/porttime/ptmacosx_mach.c b/lib-src/portmidi/porttime/ptmacosx_mach.c index 34f83a4ac..c23210e4d 100644 --- a/lib-src/portmidi/porttime/ptmacosx_mach.c +++ b/lib-src/portmidi/porttime/ptmacosx_mach.c @@ -63,7 +63,7 @@ static void *Pt_CallbackProc(void *p) /* wait for a multiple of resolution ms */ UInt64 wait_time; int delay = mytime++ * parameters->resolution - Pt_Time(); - long timestamp; + PtTimestamp timestamp; if (delay < 0) delay = 0; wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC); wait_time += AudioGetCurrentHostTime(); @@ -104,6 +104,7 @@ PtError Pt_Stop() { /* printf("Pt_Stop called\n"); */ pt_callback_proc_id++; + pthread_join(pt_thread_pid, NULL); time_started_flag = FALSE; return ptNoError; } @@ -124,7 +125,7 @@ PtTimestamp Pt_Time() } -void Pt_Sleep(long duration) +void Pt_Sleep(int32_t duration) { usleep(duration * 1000); } diff --git a/lib-src/portmidi/porttime/ptwinmm.c b/lib-src/portmidi/porttime/ptwinmm.c index ce19fa53b..17675bd6c 100644 --- a/lib-src/portmidi/porttime/ptwinmm.c +++ b/lib-src/portmidi/porttime/ptwinmm.c @@ -21,7 +21,7 @@ void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, } -PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData) { if (time_started_flag) return ptAlreadyStarted; timeBeginPeriod(resolution); @@ -38,7 +38,7 @@ PtError Pt_Start(int resolution, PtCallback *callback, void *userData) } -PtError Pt_Stop() +PMEXPORT PtError Pt_Stop() { if (!time_started_flag) return ptAlreadyStopped; if (time_callback && timer_id) { @@ -52,19 +52,19 @@ PtError Pt_Stop() } -int Pt_Started() +PMEXPORT int Pt_Started() { return time_started_flag; } -PtTimestamp Pt_Time() +PMEXPORT PtTimestamp Pt_Time() { return timeGetTime() - time_offset; } -void Pt_Sleep(long duration) +PMEXPORT void Pt_Sleep(int32_t duration) { Sleep(duration); } diff --git a/lib-src/portsmf/algrd_internal.h b/lib-src/portsmf/algrd_internal.h index 3b77adc4c..8b588727a 100644 --- a/lib-src/portsmf/algrd_internal.h +++ b/lib-src/portsmf/algrd_internal.h @@ -1,4 +1,5 @@ /* algread_internal.h -- interface between allegro.cpp and allegrord.cpp */ -Alg_error alg_read(std::istream &file, Alg_seq_ptr new_seq); +Alg_error alg_read(std::istream &file, Alg_seq_ptr new_seq, + double *offset_ptr = NULL); diff --git a/lib-src/portsmf/allegro.cpp b/lib-src/portsmf/allegro.cpp index e22e820c0..9238cfa88 100644 --- a/lib-src/portsmf/allegro.cpp +++ b/lib-src/portsmf/allegro.cpp @@ -25,6 +25,7 @@ using namespace std; #define STREQL(x, y) (strcmp(x, y) == 0) #define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define ROUND(x) ((int) ((x) + 0.5)) // 4311 is type cast ponter to long warning // 4996 is warning against strcpy @@ -122,7 +123,7 @@ void Alg_parameter::show() printf("%s:%s", attr_name(), s); break; case 'i': - printf("%s:%d", attr_name(), i); + printf("%s:%ld", attr_name(), i); break; case 'l': printf("%s:%s", attr_name(), (l ? "t" : "f")); @@ -565,7 +566,7 @@ bool Alg_event::overlap(double t, double len, bool all) return true; if (all && is_note()) { double dur = ((Alg_note_ptr) this)->dur; - // note ends within region + // note overlaps with region if (time < t && time + dur - ALG_EPS > t) return true; } @@ -600,7 +601,7 @@ Alg_note::~Alg_note() void Alg_note::show() { - printf("Alg_note: time %g, chan %d, dur %g, key %d, " + printf("Alg_note: time %g, chan %ld, dur %g, key %ld, " "pitch %g, loud %g, attributes ", time, chan, dur, key, pitch, loud); Alg_parameters_ptr parms = parameters; @@ -669,8 +670,8 @@ Alg_event_ptr Alg_events::uninsert(long index) { assert(0 <= index && index < len); Alg_event_ptr event = events[index]; - printf("memmove: %x from %x (%d)\n", events + index, events + index + 1, - sizeof(Alg_event_ptr) * (len - index - 1)); + //printf("memmove: %x from %x (%d)\n", events + index, events + index + 1, + // sizeof(Alg_event_ptr) * (len - index - 1)); memmove(events + index, events + index + 1, sizeof(Alg_event_ptr) * (len - index - 1)); len--; @@ -848,7 +849,12 @@ double Alg_time_map::beat_to_time(double beat) return beat; } int i = locate_beat(beat); - if (i == beats.len) { + // case 1: beat is between two time/beat pairs + if (0 < i && i < beats.len) { + mbi = &beats[i - 1]; + mbi1 = &beats[i]; + // case 2: beat is beyond last time/beat pair + } else if (i == beats.len) { if (last_tempo_flag) { return beats[i - 1].time + (beat - beats[i - 1].beat) / last_tempo; @@ -859,11 +865,11 @@ double Alg_time_map::beat_to_time(double beat) mbi = &beats[i - 2]; mbi1 = &beats[i - 1]; } - } else { - mbi = &beats[i - 1]; - mbi1 = &beats[i]; + // case 3: beat is at time 0 + } else /* if (i == 0) */ { + return beats[0].time; } - // whether w extrapolate or interpolate, the math is the same + // whether we extrapolate or interpolate, the math is the same double time_dif = mbi1->time - mbi->time; double beat_dif = mbi1->beat - mbi->beat; return mbi->time + (beat - mbi->beat) * time_dif / beat_dif; @@ -946,6 +952,7 @@ bool Alg_time_map::insert_tempo(double tempo, double beat) // compute difference too diff = diff - old_diff; // apply new_diff to score and beats + i++; while (i < beats.len) { beats[i].time = beats[i].time + diff; i++; @@ -955,6 +962,38 @@ bool Alg_time_map::insert_tempo(double tempo, double beat) } +double Alg_time_map::get_tempo(double beat) +{ + Alg_beat_ptr mbi; + Alg_beat_ptr mbi1; + // if beat < 0, there is probably an error; return something nice anyway + if (beat < 0) return ALG_DEFAULT_BPM / 60.0; + long i = locate_beat(beat); + // this code is similar to beat_to_time() so far, but we want to get + // beyond beat if possible because we want the tempo FOLLOWING beat + // (Consider the case beat == 0.0) + if (i < beats.len && beat >= beats[i].beat) i++; + // case 1: beat is between two time/beat pairs + if (i < beats.len) { + mbi = &beats[i - 1]; + mbi1 = &beats[i]; + // case 2: beat is beyond last time/beat pair + } else /* if (i == beats.len) */ { + if (last_tempo_flag) { + return last_tempo; + } else if (i == 1) { + return ALG_DEFAULT_BPM / 60.0; + } else { + mbi = &beats[i - 2]; + mbi1 = &beats[i - 1]; + } + } + double time_dif = mbi1->time - mbi->time; + double beat_dif = mbi1->beat - mbi->beat; + return beat_dif / time_dif; +} + + bool Alg_time_map::set_tempo(double tempo, double start_beat, double end_beat) { if (start_beat >= end_beat) return false; @@ -976,6 +1015,34 @@ bool Alg_time_map::set_tempo(double tempo, double start_beat, double end_beat) } +bool Alg_time_map::stretch_region(double b0, double b1, double dur) +{ + // find current duration + double t0 = beat_to_time(b0); + double t1 = beat_to_time(b1); + double old_dur = t1 - t0; + if (old_dur <= 0 || dur <= 0) return false; + double scale = dur / old_dur; // larger scale => slower + // insert a beat if necessary at b0 and b1 + insert_beat(t0, b0); + insert_beat(t1, b1); + long start_x = locate_beat(b0); + long stop_x = locate_beat(b1); + double orig_time = beats[start_x].time; + double prev_time = orig_time; + for (int i = start_x + 1; i < beats.len; i++) { + double delta = beats[i].time - orig_time; + if (i <= stop_x) { // change tempo to next Alg_beat + delta *= scale; + } + orig_time = beats[i].time; + prev_time += delta; + beats[i].time = prev_time; + } + return true; +} + + void Alg_time_map::trim(double start, double end, bool units_are_seconds) { // extract the time map from start to end and shift to time zero @@ -1920,7 +1987,7 @@ void Alg_time_sigs::expand() } -void Alg_time_sigs::insert(double beat, double num, double den) +void Alg_time_sigs::insert(double beat, double num, double den, bool force) { // find insertion point: for (int i = 0; i < len; i++) { @@ -1940,7 +2007,7 @@ void Alg_time_sigs::insert(double beat, double num, double den) // check if redundant with implied initial 4/4 time sig: (i == 0 && num == 4 && den == 4 && within(fmod(beat, 4), 0, ALG_EPS))) { - return; // redundant inserts are ignored here + if (!force) return; // redundant inserts can be ignored here } // make room for new event if (maxlen <= len) expand(); @@ -1982,55 +2049,225 @@ int Alg_time_sigs::find_beat(double beat) } -void Alg_time_sigs::cut(double start, double end) +double Alg_time_sigs::get_bar_len(double beat) { - // remove time_sig's from start to start+len -- these must be - // in beats (not seconds) - // now rewrite time_sig[]: copy from i_in to i_out (more or less) - int i_in = 0; - int i_out = 0; - // first, figure out where to begin cut region - i_in = find_beat(start); - i_out = i_in; - // scan to end of cut region - while (i_in < len && time_sigs[i_in].beat < end) { - i_in = i_in + 1; + int i = find_beat(beat); + double num = 4.0; + double den = 4.0; + if (i != 0) { + num = time_sigs[i - 1].num; + den = time_sigs[i - 1].den; } - // change time_sig at start if necessary - // there's a time_sig that was skipped if i_in > i_out. - // if that's true and the next time change is at end, we're - // ok because it will be copied, but if the next time change - // is after end, then maybe we should insert a time change - // corresponding to what's in effect at end. We can skip this - // insert if it corresponds to whatever is in effect at start - if (i_in > i_out && i_in < len && - time_sigs[i_in].beat > end + ALG_EPS && - (i_out == 0 || time_sigs[i_out - 1].num != time_sigs[i_in - 1].num || - time_sigs[i_out - 1].den != time_sigs[i_in - 1].den)) { - time_sigs[i_out] = time_sigs[i_in - 1]; - time_sigs[i_out].beat = start; + return 4 * num / den; +} + +void Alg_time_sigs::cut(double start, double end, double dur) +{ + // remove time_sig's from start to end -- these must be + // in beats (not seconds). + // The duration of the whole sequence is dur (beats). + + // If the first bar line after end comes before a time signature + // and does not fall on a bar line, insert a time signature at + // the time of the bar line to retain relative bar line positions + + int i = find_beat(end); + // i is where you would insert a new time sig at beat, + // Case 1: beat coincides with a time sig at i. Time signature + // at beat means that there is a barline at beat, so when beat + // is shifted to start, the relative barline positions are preserved + if (len > 0 && + within(end, time_sigs[i].beat, ALG_EPS)) { + // beat coincides with time signature change, so end is on a barline + /* do nothing */ ; + // Case 2: there is no time signature before end + } else if (i == 0 && (len == 0 || + time_sigs[0].beat > end)) { + // If the next time signature does not fall on a barline, + // then end must not be on a barline, so there is a partial + // measure from end to the next barline. We need + // a time signature there to preserve relative barline + // locations. It may be that the next bar after start is + // due to another time signature, in which case we do not + // need to insert anything. + double measures = end / 4.0; + double imeasures = ROUND(measures); + if (!within(measures, imeasures, ALG_EPS)) { + // start is not on a barline, maybe add one here: + double bar_loc = (int(measures) + 1) * 4.0; + if (bar_loc < dur - ALG_EPS && + (len == 0 || time_sigs[0].beat > bar_loc + ALG_EPS)) { + insert(bar_loc, 4, 4, true); // forced insert + } + } + // This case should never be true because if i == 0, either there + // are no time signatures before beat (Case 2), + // or there is one time signature at beat (Case 1) + } else if (i == 0) { + /* do nothing (might be good to assert(false)) */ ; + // Case 3: i-1 must be the effective time sig position + } else { + // get the time signature in effect at end + Alg_time_sig &tsp = time_sigs[i - 1]; + double beats_per_measure = (tsp.num * 4) / tsp.den; + double measures = (end - tsp.beat) / beats_per_measure; + int imeasures = ROUND(measures); + if (!within(measures, imeasures, ALG_EPS)) { + // end is not on a measure, so we need to insert a time sig + // to force a bar line at the first measure location after + // beat, if any + double bar_loc = tsp.beat + beats_per_measure * (int(measures) + 1); + // insert new time signature at bar_loc + // It will have the same time signature, but the position will + // force a barline to match the barline before the shift + // However, we should not insert a barline if there is a + // time signature earlier than the barline time + if (i < len /* time_sigs[i] is the last one */ && + time_sigs[i].beat < bar_loc - ALG_EPS) { + /* do not insert because there's already a time signature */; + } else if (bar_loc < dur - ALG_EPS) { + insert(bar_loc, tsp.num, tsp.den, true); // forced insert + } + } + // else beat coincides with a barline, so no need for an extra + // time signature to force barline alignment + } + + // Figure out if time signature at start matches + // the time signature at end. If not, we need to insert a + // time signature at end to force the correct time signature + // there. + // Find time signature at start: + double start_num = 4.0; // default if no time signature specified + double start_den = 4.0; + i = find_beat(start); + // A time signature at start would go at index i, so the effective + // time signature prior to start is at i - 1. If i == 0, the default + // time signature is in effect prior to start. + if (i != 0) { + start_num = time_sigs[i - 1].num; + start_den = time_sigs[i - 1].den; + } + // Find the time signature at end: + double end_num = 4.0; // default if no time signature specified + double end_den = 4.0; + int j = find_beat(end); + if (j != 0) { + end_num = time_sigs[j - 1].num; + end_den = time_sigs[j - 1].den; + } + // compare: If meter changes and there is no time signature at end, + // insert a time signature at end + if (end < dur - ALG_EPS && + (start_num != end_num || start_den != end_den) && + (j >= len || !within(time_sigs[j].beat, end, ALG_EPS))) { + insert(end, end_num, end_den, true); + } + + // Remove time signatures from start to end (not including one AT + // end, if there is one there. Be careful with ALG_EPS on that one.) + + // since we may have inserted a time signature, find position again: + int i0 = find_beat(start); + int i1 = i0; + // scan to end of cut region + while (i1 < len && time_sigs[i1].beat < end - ALG_EPS) { + i1++; } // scan from end to len(time_sig) - while (i_in < length()) { - Alg_time_sig &ts = time_sigs[i_in]; - ts.beat = ts.beat - (end - start); - time_sigs[i_out] = ts; - i_in = i_in + 1; - i_out = i_out + 1; + while (i1 < len) { + Alg_time_sig &ts = time_sigs[i1]; + ts.beat -= (end - start); + time_sigs[i0] = ts; + i0++; + i1++; } - len = i_out; + len = i1; } void Alg_time_sigs::trim(double start, double end) { - // remove time_sig's not in [start, start+end) + // remove time_sig's not in [start, end), but retain + // barline positions relative to the notes. This means that + // if the meter (time signature) changes between start and + // end that we need to insert a time signature at start. + // Also, if trim() would cause barlines to move, we need to + // insert a time signature on a barline (timesignatures + // imply the beginning of a bar even if the previous bar + // does not have enough beats. Note that bars do not need + // to have an integer number of beats). + // // units must be in beats (not seconds) - // copy from i_in to i_out as we scan time_sig array - int i_in = 0; - int i_out = 0; + // + // Uses Alg_time_sigs::cut() to avoid writing a special case + double dur = end + 1000; + if (len > 0) { + dur = time_sigs[len - 1].beat + 1000; + } + cut(end, dur, dur); + cut(0, start, dur); + +#ifdef IGNORE_THIS_OLD_CODE // first, skip time signatures up to start - i_in = find_beat(start); + int i = find_beat(start); + // i is where you would insert a new time sig at beat, + // Case 1: beat coincides with a time sig at i. Time signature + // at beat means that there is a barline at beat, so when beat + // is shifted to 0, the relative barline positions are preserved + if (len > 0 && + within(start, time_sigs[i].beat, ALG_EPS)) { + // beat coincides with time signature change, so offset must + // be a multiple of beats + /* do nothing */ ; + // Case 2: there is no time signature before start + } else if (i == 0 && (len == 0 || + time_sigs[0].beat > start)) { + // If the next time signature does not fall on a barline, + // then start must not be on a barline, so there is a partial + // measure from start to the next barline. We need + // a time signature there to preserve relative barline + // locations. It may be that the next bar after start is + // due to another time signature, in which case we do not + // need to insert anything. + double measures = start / 4.0; + double imeasures = ROUND(measures); + if (!within(measures, imeasures, ALG_EPS)) { + // start is not on a barline, maybe add one here: + double bar_loc = (int(measures) + 1) * 4.0; + if (len == 0 || time_sigs[1].beat > bar_loc + ALG_EPS) { + insert(bar_loc, 4, 4, true); + } + } + // This case should never be true because if i == 0, either there + // are no time signatures before beat (Case 2), + // or there is one time signature at beat (Case 1) + } else if (i == 0) { + /* do nothing (might be good to assert(false)) */ ; + // Case 3: i-1 must be the effective time sig position + } else { + i -= 1; // index the time signature in effect at start + Alg_time_sig &tsp = time_sigs[i]; + double beats_per_measure = (tsp.num * 4) / tsp.den; + double measures = (start - tsp.beat) / beats_per_measure; + int imeasures = ROUND(measures); + if (!within(measures, imeasures, ALG_EPS)) { + // beat is not on a measure, so we need to insert a time sig + // to force a bar line at the first measure location after + // beat, if any + double bar_loc = tsp.beat + beats_per_measure * (int(measures) + 1); + // insert new time signature at bar_loc + // It will have the same time signature, but the position will + // force a barline to match the barline before the shift + insert(bar_loc, tsp.num, tsp.den, true); + } + // else beat coincides with a barline, so no need for an extra + // time signature to force barline alignment + } + // since we may have inserted a time signature, find position again: + int i_in = find_beat(start); + int i_out = 0; + // put time_sig at start if necessary // if 0 < i_in < len, then the time sig at i_in is either // at start or after start. @@ -2057,7 +2294,7 @@ void Alg_time_sigs::trim(double start, double end) time_sigs[0].beat = 0.0; i_out = 1; } - // scan to end of cut region + // copy from i_in to i_out as we scan time_sig array to end of cut region while (i_in < len && time_sigs[i_in].beat < end - ALG_EPS) { Alg_time_sig &ts = time_sigs[i_in]; ts.beat = ts.beat - start; @@ -2066,6 +2303,7 @@ void Alg_time_sigs::trim(double start, double end) i_out++; } len = i_out; +#endif } @@ -2084,6 +2322,13 @@ void Alg_time_sigs::paste(double start, Alg_seq *seq) // remember the time signature at the splice point double num_after_splice = 4; double den_after_splice = 4; // default + double num_before_splice = 4; + double den_before_splice = 4; // default + // this is computed for use in aligning beats after the inserted + // time signatures and duration. It is the position of time signature + // in effect immediately after start (the time signature will be + // before start or at start) + double beat_after_splice = 0.0; // three cases: // 1) time sig at splice is at i-1 // for this, we must have len>0 & i>0 @@ -2096,13 +2341,23 @@ void Alg_time_sigs::paste(double start, Alg_seq *seq) if (len > 0 && i > 0 && ((i < len && time_sigs[i].beat > start + ALG_EPS) || (i == len))) { + // no time_signature at i num_after_splice = time_sigs[i-1].num; den_after_splice = time_sigs[i-1].den; + beat_after_splice = time_sigs[i - 1].beat; + num_before_splice = num_after_splice; + den_before_splice = den_after_splice; } else if (i < len && time_sigs[i].beat <= start + ALG_EPS) { + // time_signature at i is at "start" beats num_after_splice = time_sigs[i].num; den_after_splice = time_sigs[i].den; + beat_after_splice = start; + if (i > 0) { // time signature before start is at i - 1 + num_before_splice = time_sigs[i-1].num; + den_before_splice = time_sigs[i-1].den; + } } - // i is where insert will go, time_sig[i].beat > start + // i is where insert will go, time_sig[i].beat >= start // begin by adding duration to time_sig's at i and above // move time signatures forward by duration of seq double dur = seq->get_beat_dur(); @@ -2112,37 +2367,183 @@ void Alg_time_sigs::paste(double start, Alg_seq *seq) } //printf("time_sig::insert after making space\n"); //show(); - // now insert initial time_signature at start. This may create + // If time signature of "from" is not the effective time signature + // at start, insert a time_signature at start. This may create // an extra measure if seq does not begin on a measure boundary - insert(start, 4, 4); // in case seq uses default starting signature + double num_of_insert = 4.0; + double den_of_insert = 4.0; + double beat_of_insert = 0.0; + int first_from_index = 0; // where to start copying from + if (from.length() > 0 && from[0].beat < ALG_EPS) { + // there is an initial time signature in "from" + num_of_insert = from[0].num; + den_of_insert = from[0].den; + // since we are handling the first time signature in from, + // we can start copying at index == 1: + first_from_index = 1; + } + // compare time signatures to see if we need a change at start: + if (num_before_splice != num_of_insert || + den_before_splice != den_of_insert) { + // note that this will overwrite an existing time signature if + // it is within ALG_EPS of start -- this is correct because the + // existing time signature will already be recorded as + // num_after_splice and den_after_splice + insert(start, num_of_insert, den_of_insert); + } //printf("time_sig::insert after 4/4 at start\n"); //show(); // insert time signatures from seq offset by start - for (i = 0; i < from.length(); i++) { - insert(start + from[i].beat, from[i].num, from[i].den); + for (i = 0; i < from.length() && from[i].beat < dur - ALG_EPS; i++) { + num_of_insert = from[i].num; // keep latest time signature info + den_of_insert = from[i].den; + beat_of_insert = from[i].beat; + insert(start + beat_of_insert, num_of_insert, den_of_insert); } //printf("time_sig::insert after pasting in sigs\n"); //show(); - // now insert time signature at end of splice - insert(start + dur, num_after_splice, den_after_splice); + // now insert time signature at end of splice if necessary + // if the time signature changes, we need to insert a time signature + // immediately: + if (num_of_insert != num_after_splice && + den_of_insert != den_after_splice) { + insert(start + dur, num_after_splice, den_after_splice); + num_of_insert = num_after_splice; + den_of_insert = den_after_splice; + beat_of_insert = start + dur; + } + // if the insert had a partial number of measures, we might need an + // additional time signature to realign the barlines after the insert + // To decide, we compare the beat of the first barline on or after + // start before the splice to the beat of the first barline on or + // after start + dur after the splice. In a sense, this is the "same" + // barline, so it should be shifted exactly by dur. + // First, compute the beat of the first barline on or after start: + double beats_per_measure = (num_after_splice * 4) / den_after_splice; + double measures = (start - beat_after_splice) / beats_per_measure; + // Measures might be slightly negative due to rounding. Use max() + // to eliminate any negative rounding error: + int imeasures = int(max(measures, 0.0)); + double old_bar_loc = beat_after_splice + (imeasures * beats_per_measure); + if (old_bar_loc < start) old_bar_loc += beats_per_measure; + // now old_bar_loc is the original first bar position after start + // Do similar calculation for position after end after the insertion: + // beats_per_measure already calculated because signatures match + measures = (start + dur - beat_of_insert) / beats_per_measure; + imeasures = int(max(measures, 0.0)); + double new_bar_loc = beat_of_insert + (imeasures * beats_per_measure); + if (new_bar_loc < start + dur) new_bar_loc += beats_per_measure; + // old_bar_loc should be shifted by dur: + old_bar_loc += dur; + // now the two bar locations should be equal, but due to rounding, + // they could be off by one measure + double diff = (new_bar_loc - old_bar_loc) + beats_per_measure; + double diff_in_measures = diff / beats_per_measure; + // if diff_in_measures is not (approximately) integer, we need to + // force a barline (time signature) after start + dur to maintain + // the relationship between barliness and notes + if (!within(diff_in_measures, ROUND(diff_in_measures), ALG_EPS)) { + // recall that old_bar_loc is shifted by dur + insert(old_bar_loc, num_after_splice, den_after_splice); + } //printf("time_sig::insert after sig at end of splice\n"); //show(); } -void Alg_time_sigs::insert_beats(double beat, double len) +void Alg_time_sigs::insert_beats(double start, double dur) { - int i; - // find the time_sig entry in effect at t - for (i = 0; i < len; i++) { - if (time_sigs[i].beat < beat + ALG_EPS) { - break; + int i = find_beat(start); + + // time_sigs[i] is after beat and needs to shift + // Compute the time of the first bar at or after beat so that + // a bar can be placed at bar_loc + dur + double tsnum = 4.0; + double tsden = 4.0; + double tsbeat = 0.0; // defaults + + // three cases: + // 1) time sig at splice is at i-1 + // for this, we must have len>0 & i>0 + // two sub-cases: + // A) i < len && time_sig[i].beat > start + // B) i == len + // 2) time_sig at splice is at i + // for this, i < len && time_sig[i].beat ~= start + // 3) time_sig at splice is default 4/4 + if (len > 0 && i > 0 && + ((i < len && time_sigs[i].beat > start + ALG_EPS) || + (i == len))) { + // no time_signature at i + tsnum = time_sigs[i-1].num; + tsden = time_sigs[i-1].den; + tsbeat = time_sigs[i-1].beat; + } else if (i < len && time_sigs[i].beat <= start + ALG_EPS) { + // time_signature at i is at "start" beats + tsnum = time_sigs[i].num; + tsden = time_sigs[i].den; + tsbeat = start; + i++; // we want i to be index of next time signature after start + } + // invariant: i is index of next time signature after start + + // increase beat times from i to len - 1 by dur + for (int j = i; j < len; j++) { + time_sigs[j].beat += dur; + } + + // insert a time signature to maintain bar positions if necessary + double beats_per_measure = (tsnum * 4) / tsden; + double measures = dur / beats_per_measure; // shift distance + int imeasures = ROUND(measures); + if (!within(measures, imeasures, ALG_EPS)) { + // shift is not a whole number of measures, so we may need to insert + // time signature after silence + // compute measures from time signature to next bar after time + measures = (start - tsbeat) / beats_per_measure; + // round up and add to tsbeat to get time of next bar + double bar_loc = tsbeat + beats_per_measure * (int(measures) + 1); + // translate bar_loc by len: + bar_loc += dur; // this is where we want a bar to be, but maybe + // there is a time signature change before bar, in which case we + // should not insert a new time signature + // The next time signature after start is at i if i < len + if (i < len && time_sigs[i].beat < bar_loc) { + /* do not insert */; + } else { + insert(bar_loc, tsnum, tsden); } } - // now, increase beat times by len - for (; i < len; i++) { - time_sigs[i].beat += len; +} + + +double Alg_time_sigs::nearest_beat(double beat) +{ + int i = find_beat(beat); + // i is where we would insert time signature at beat + // case 1: there is no time signature + if (i == 0 && len == 0) { + return ROUND(beat); + // case 2: beat falls approximately on time signature + } else if (i < len && within(time_sigs[i].beat, beat, ALG_EPS)) { + return time_sigs[i].beat; + // case 3: beat is after no time signature and before one + } else if (i == 0) { + double trial_beat = ROUND(beat); + // it is possible that we rounded up past a time signature + if (trial_beat > time_sigs[0].beat - ALG_EPS) { + return time_sigs[0].beat; + } + return trial_beat; } + // case 4: beat is after some time signature + double trial_beat = time_sigs[i - 1].beat + + ROUND(beat - time_sigs[i - 1].beat); + // rounding may advance trial_beat past next time signature: + if (i < len && trial_beat > time_sigs[i].beat - ALG_EPS) { + return time_sigs[i].beat; + } + return trial_beat; } @@ -2265,7 +2666,8 @@ void Alg_iterator::show() { for (int i = 0; i < len; i++) { Alg_pending_event_ptr p = &(pending_events[i]); - printf(" %d: %p[%d] on %d\n", i, p->events, p->index, p->note_on); + printf(" %d: %p[%ld]@%g on %d\n", i, p->events, p->index, + p->offset, p->note_on); } } @@ -2275,11 +2677,11 @@ bool Alg_iterator::earlier(int i, int j) { Alg_pending_event_ptr p_i = &(pending_events[i]); Alg_event_ptr e_i = (*(p_i->events))[p_i->index]; - double t_i = (p_i->note_on ? e_i->time : e_i->get_end_time()); + double t_i = (p_i->note_on ? e_i->time : e_i->get_end_time()) + p_i->offset; Alg_pending_event_ptr p_j = &(pending_events[j]); Alg_event_ptr e_j = (*(p_j->events))[p_j->index]; - double t_j = (p_j->note_on ? e_j->time : e_j->get_end_time()); + double t_j = (p_j->note_on ? e_j->time : e_j->get_end_time()) + p_j->offset; if (t_i < t_j) return true; // not sure if this case really exists or this is the best rule, but @@ -2289,12 +2691,15 @@ bool Alg_iterator::earlier(int i, int j) } -void Alg_iterator::insert(Alg_events_ptr events, long index, bool note_on) +void Alg_iterator::insert(Alg_events_ptr events, long index, + bool note_on, void *cookie, double offset) { if (len == maxlen) expand(); pending_events[len].events = events; pending_events[len].index = index; pending_events[len].note_on = note_on; + pending_events[len].cookie = cookie; + pending_events[len].offset = offset; int loc = len; int loc_parent = HEAP_PARENT(loc); len++; @@ -2312,12 +2717,16 @@ void Alg_iterator::insert(Alg_events_ptr events, long index, bool note_on) } bool Alg_iterator::remove_next(Alg_events_ptr &events, long &index, - bool ¬e_on) + bool ¬e_on, void *&cookie, + double &offset) { if (len == 0) return false; // empty! events = pending_events[0].events; index = pending_events[0].index; note_on = pending_events[0].note_on; + offset = pending_events[0].offset; + cookie = pending_events[0].cookie; + offset = pending_events[0].offset; len--; pending_events[0] = pending_events[len]; // sift down @@ -2344,7 +2753,7 @@ bool Alg_iterator::remove_next(Alg_events_ptr &events, long &index, } -Alg_seq::Alg_seq(const char *filename, bool smf) +Alg_seq::Alg_seq(const char *filename, bool smf, double *offset_ptr) { basic_initialization(); ifstream inf(filename, smf ? ios::binary | ios::in : ios::in); @@ -2354,20 +2763,22 @@ Alg_seq::Alg_seq(const char *filename, bool smf) } if (smf) { error = alg_smf_read(inf, this); + if (offset_ptr) *offset_ptr = 0.0; } else { - error = alg_read(inf, this); + error = alg_read(inf, this, offset_ptr); } inf.close(); } -Alg_seq::Alg_seq(istream &file, bool smf) +Alg_seq::Alg_seq(istream &file, bool smf, double *offset_ptr) { basic_initialization(); if (smf) { error = alg_smf_read(file, this); + if (offset_ptr) *offset_ptr = 0.0; } else { - error = alg_read(file, this); + error = alg_read(file, this, offset_ptr); } } @@ -2512,11 +2923,12 @@ Alg_seq_ptr Alg_seq::cut(double start, double len, bool all) // return sequence from start to start+len and modify this // sequence by removing that time-span { + double dur = get_dur(); // fix parameters to fall within existing sequence - if (start > get_dur()) return NULL; // nothing to cut + if (start > dur) return NULL; // nothing to cut if (start < 0) start = 0; // can't start before sequence starts - if (start + len > get_dur()) // can't cut after end: - len = get_dur() - start; + if (start + len > dur) // can't cut after end: + len = dur - start; Alg_seq_ptr result = new Alg_seq(); Alg_time_map_ptr map = new Alg_time_map(get_time_map()); @@ -2542,11 +2954,13 @@ Alg_seq_ptr Alg_seq::cut(double start, double len, bool all) // we use len. double ts_start = start; double ts_end = start + len; + double ts_dur = dur; double ts_last_note_off = start + result->last_note_off; if (units_are_seconds) { ts_start = time_map->time_to_beat(ts_start); ts_end = time_map->time_to_beat(ts_end); ts_last_note_off = time_map->time_to_beat(ts_last_note_off); + ts_dur = time_map->time_to_beat(ts_dur); } // result is shifted from start to 0 and has length len, but // time_sig and time_map are copies from this. Adjust time_sig, @@ -2568,9 +2982,9 @@ Alg_seq_ptr Alg_seq::cut(double start, double len, bool all) // we sliced out a portion of each track, so now we need to // slice out the corresponding sections of time_sig and time_map // as well as to adjust the duration. - time_sig.cut(ts_start, ts_end); + time_sig.cut(ts_start, ts_end, ts_dur); time_map->cut(start, len, units_are_seconds); - set_dur(get_dur() - len); + set_dur(dur - len); return result; } @@ -2599,9 +3013,11 @@ void Alg_seq::insert_silence(double t, double len) } else { time_map->insert_beats(t_beats, len_beats); } - if (time_sig.length() > 0) { - time_sig.insert_beats(t_beats, len_beats); - } + time_sig.insert_beats(t_beats, len_beats); + // Final duration is defined to be t + len + whatever was + // in the sequence after t (if any). This translates to + // t + len + max(dur - t, 0) + set_dur(t + len + max(get_dur() - t, 0.0)); } @@ -2659,9 +3075,9 @@ Alg_seq *Alg_seq::copy(double start, double len, bool all) void Alg_seq::paste(double start, Alg_seq *seq) { - // insert seq at time; open up space for it - // to manipulate time map, we need units as beats - // save original form so we can convert back if necessary + // Insert seq at time, opening up space for it. + // To manipulate time map, we need units as beats. + // Save original form so we can convert back if necessary. bool units_should_be_seconds = units_are_seconds; bool seq_units_should_be_seconds = seq->get_units_are_seconds(); if (units_are_seconds) { @@ -2738,10 +3154,11 @@ void Alg_seq::clear_track(int track_num, double start, double len, bool all) void Alg_seq::clear(double start, double len, bool all) { // Fix parameters to fall within existing sequence - if (start > get_dur()) return; // nothing to cut + double dur = get_dur(); + if (start > dur) return; // nothing to cut if (start < 0) start = 0; // can't start before sequence starts - if (start + len > get_dur()) // can't cut after end: - len = get_dur() - start; + if (start + len > dur) // can't cut after end: + len = dur - start; for (int i = 0; i < tracks(); i++) clear_track(i, start, len, all); @@ -2749,17 +3166,19 @@ void Alg_seq::clear(double start, double len, bool all) // Put units in beats to match time_sig's. double ts_start = start; double ts_end = start + len; + double ts_dur = dur; if (units_are_seconds) { ts_start = time_map->time_to_beat(ts_start); ts_end = time_map->time_to_beat(ts_end); + ts_dur = time_map->time_to_beat(ts_dur); } // we sliced out a portion of each track, so now we need to // slice out the corresponding sections of time_sig and time_map // as well as to adjust the duration. - time_sig.cut(ts_start, ts_end); + time_sig.cut(ts_start, ts_end, ts_dur); time_map->cut(start, len, units_are_seconds); - set_dur(get_dur() - len); + set_dur(dur - len); } @@ -2817,6 +3236,26 @@ bool Alg_seq::insert_beat(double time, double beat) } +// input is time, return value is time +double Alg_seq::nearest_beat_time(double time, double *beat) +{ + double b = time_map->time_to_beat(time); + b = time_sig.nearest_beat(b); + if (beat) *beat = b; + return time_map->beat_to_time(b); +} + + +bool Alg_seq::stretch_region(double b0, double b1, double dur) +{ + bool units_should_be_seconds = units_are_seconds; + convert_to_beats(); + bool result = time_map->stretch_region(b0, b1, dur); + if (units_should_be_seconds) convert_to_seconds(); + return result; +} + + bool Alg_seq::insert_tempo(double bpm, double beat) { double bps = bpm / 60.0; // convert to beats per second @@ -2867,6 +3306,12 @@ void Alg_seq::add_event(Alg_event_ptr event, int track_num) } +double Alg_seq::get_tempo(double beat) +{ + return time_map->get_tempo(beat); +} + + bool Alg_seq::set_tempo(double bpm, double start_beat, double end_beat) // set tempo from start_beat to end_beat { @@ -2874,12 +3319,21 @@ bool Alg_seq::set_tempo(double bpm, double start_beat, double end_beat) if (start_beat >= end_beat) return false; bool units_should_be_seconds = units_are_seconds; convert_to_beats(); + double dur = get_dur(); bool result = time_map->set_tempo(bpm, start_beat, end_beat); + // preserve sequence duration in beats when tempo changes + set_dur(dur); if (units_should_be_seconds) convert_to_seconds(); return result; } +double Alg_seq::get_bar_len(double beat) +{ + return time_sig.get_bar_len(beat); +} + + void Alg_seq::set_time_sig(double beat, double num, double den) { time_sig.insert(beat, num, den); @@ -2942,41 +3396,51 @@ void Alg_seq::set_events(Alg_event_ptr *events, long len, long max) */ -void Alg_iterator::begin(bool note_off_flag) +void Alg_iterator::begin_seq(Alg_seq_ptr s, void *cookie, double offset) { // keep an array of indexes into tracks - printf("new pending\n"); + // printf("new pending\n"); int i; - for (i = 0; i < seq->track_list.length(); i++) { - if (seq->track_list[i].length() > 0) { - insert(&(seq->track_list[i]), 0, true); + for (i = 0; i < s->track_list.length(); i++) { + if (s->track_list[i].length() > 0) { + insert(&(s->track_list[i]), 0, true, cookie, offset); } } } -Alg_event_ptr Alg_iterator::next(bool *note_on) +Alg_event_ptr Alg_iterator::next(bool *note_on, void **cookie_ptr, + double *offset_ptr, double end_time) // return the next event in time from any track { Alg_events_ptr events_ptr; long index; bool on; - if (!remove_next(events_ptr, index, on)) { + void *cookie; + double offset; + if (!remove_next(events_ptr, index, on, cookie, offset)) { return NULL; } if (note_on) *note_on = on; Alg_event_ptr event = (*events_ptr)[index]; if (on) { - if (note_off_flag && event->is_note()) { + if (note_off_flag && event->is_note() && + (end_time == 0 || + (*events_ptr)[index]->get_end_time() + offset < end_time)) { // this was a note-on, so insert pending note-off - insert(events_ptr, index, false); + insert(events_ptr, index, false, cookie, offset); } - // for both notes and updates, insert next event (at index + 1) + // for both note-ons and updates, insert next event (at index + 1) index++; - if (index < events_ptr->length()) { - insert(events_ptr, index, true); + if (index < events_ptr->length() && + (end_time == 0 || // zero means ignore end time + // stop iterating when end time is reached + (*events_ptr)[index]->time + offset < end_time)) { + insert(events_ptr, index, true, cookie, offset); } } + if (cookie_ptr) *cookie_ptr = cookie; + if (offset_ptr) *offset_ptr = offset; return event; } diff --git a/lib-src/portsmf/allegro.h b/lib-src/portsmf/allegro.h index cc3ac096d..f99006373 100644 --- a/lib-src/portsmf/allegro.h +++ b/lib-src/portsmf/allegro.h @@ -114,6 +114,11 @@ extern Alg_atoms symbol_table; // Alg_parameter class typedef class Alg_parameter { public: + // This constructor guarantees that an Alg_parameter can be + // deleted safely without further initialization. It does not + // do anything useful, so it is expected that the creator will + // set attr and store a value in the appropriate union field. + Alg_parameter() { attr = "i"; } ~Alg_parameter(); Alg_attribute attr; union { @@ -481,8 +486,11 @@ public: // you want tracks to be in beat units. void insert_beat(double time, double beat); // add a point to the map bool insert_tempo(double tempo, double beat); // insert a tempo change + // get the tempo starting at beat + double get_tempo(double beat); // set the tempo over a region bool set_tempo(double tempo, double start_beat, double end_beat); + bool stretch_region(double b0, double b1, double dur); void cut(double start, double len, bool units_are_seconds); void trim(double start, double end, bool units_are_seconds); void paste(double start, Alg_track *tr); @@ -581,7 +589,7 @@ typedef class Serial_write_buffer: public Serial_buffer { void set_string(char *s) { char *fence = buffer + len; assert(ptr < fence); - // two lots of brackets surpress a g++ warning, because this is an + // two brackets surpress a g++ warning, because this is an // assignment operator inside a test. while ((*ptr++ = *s++)) assert(ptr < fence); // 4311 is type cast pointer to long warning @@ -847,11 +855,15 @@ public: void show(); long length() { return len; } int find_beat(double beat); - void insert(double beat, double num, double den); - void cut(double start, double end); // remove from start to end + // get the number of beats per measure starting at beat + double get_bar_len(double beat); + void insert(double beat, double num, double den, bool force = false); + void cut(double start, double end, double dur); // remove from start to end void trim(double start, double end); // retain just start to end void paste(double start, Alg_seq *seq); void insert_beats(double beat, double len); // insert len beats at beat + // find the nearest beat (see Alg_seq::nearest_beat) to beat + double nearest_beat(double beat); }; @@ -894,11 +906,14 @@ typedef enum { typedef struct Alg_pending_event { + void *cookie; // client-provided sequence identifier Alg_events *events; // the array of events long index; // offset of this event bool note_on; // is this a note-on or a note-off (if applicable)? + double offset; // time offset for events } *Alg_pending_event_ptr; + typedef class Alg_iterator { private: long maxlen; @@ -910,8 +925,11 @@ private: void show(); bool earlier(int i, int j); - void insert(Alg_events_ptr events, long index, bool note_on); - bool remove_next(Alg_events_ptr &events, long &index, bool ¬e_on); + void insert(Alg_events_ptr events, long index, bool note_on, + void *cookie, double offset); + // returns the info on the next pending event in the priority queue + bool remove_next(Alg_events_ptr &events, long &index, bool ¬e_on, + void *&cookie, double &offset); public: bool note_off_flag; // remembers if we are iterating over note-off // events as well as note-on and update events @@ -922,17 +940,28 @@ public: maxlen = len = 0; pending_events = NULL; } + // Normally, iteration is over the events in the one sequence used + // to instatiate the iterator (see above), but with this method, you + // can add more sequences to the iteration. Events are returned in + // time order, so effectively sequence events are merged. + // The optional offset is added to each event time of sequence s + // before merging/sorting. + void begin_seq(Alg_seq_ptr s, void *cookie = NULL, double offset = 0.0); ~Alg_iterator(); // Prepare to enumerate events in order. If note_off_flag is true, then // iteration_next will merge note-off events into the sequence. - void begin(bool note_off_flag = false); + void begin(void *cookie = NULL) { begin_seq(seq, cookie); } // return next event (or NULL). If iteration_begin was called with // note_off_flag = true, and if note_on is not NULL, then *note_on // is set to true when the result value represents a note-on or update. // (With note_off_flag, each Alg_note event is returned twice, once // at the note-on time, with *note_on == true, and once at the note-off - // time, with *note_on == false - Alg_event_ptr next(bool *note_on = NULL); + // time, with *note_on == false. If a cookie_ptr is passed, then the + // cookie corresponding to the event is stored at that address + // If end_time is 0, iterate through the entire sequence, but if + // end_time is non_zero, stop iterating at the last event before end_time + Alg_event_ptr next(bool *note_on = NULL, void **cookie_ptr = NULL, + double *offset_ptr = NULL, double end_time = 0); void end(); // clean up after enumerating events } *Alg_iterator_ptr; @@ -967,8 +996,10 @@ public: Alg_seq(Alg_track_ref track) { seq_from_track(track); } Alg_seq(Alg_track_ptr track) { seq_from_track(*track); } void seq_from_track(Alg_track_ref tr); - Alg_seq(std::istream &file, bool smf); // create from file - Alg_seq(const char *filename, bool smf); // create from filename + // create from file: + Alg_seq(std::istream &file, bool smf, double *offset_ptr = NULL); + // create from filename + Alg_seq(const char *filename, bool smf, double *offset_ptr = NULL); virtual ~Alg_seq(); int get_read_error() { return error; } void serialize(void **buffer, long *bytes); @@ -981,9 +1012,9 @@ public: void unserialize_seq(); // write an ascii representation to file - void write(std::ostream &file, bool in_secs); + void write(std::ostream &file, bool in_secs, double offset = 0.0); // returns true on success - bool write(const char *filename); + bool write(const char *filename, double offset = 0.0); void smf_write(std::ostream &file); bool smf_write(const char *filename); @@ -1024,15 +1055,28 @@ public: // find index of first score event after time long seek_time(double time, int track_num); bool insert_beat(double time, double beat); + // return the time of the beat nearest to time, also returns beat + // number through beat. This will correspond to an integer number + // of beats from the nearest previous time signature or 0.0, but + // since time signatures need not be on integer beat boundaries + // the beat location may not be on an integer beat (beat locations + // are measured from the beginning which is beat 0. + double nearest_beat_time(double time, double *beat); // warning: insert_tempo may change representation from seconds to beats bool insert_tempo(double bpm, double beat); - + // change the duration from b0 to b1 (beats) to dur (seconds) by + // scaling the intervening tempos + bool stretch_region(double b0, double b1, double dur); // add_event takes a pointer to an event on the heap. The event is not // copied, and this Alg_seq becomes the owner and freer of the event. void add_event(Alg_event_ptr event, int track_num); void add(Alg_event_ptr event) { assert(false); } // call add_event instead - // warning: set_tempo may change representation from seconds to beats + // get the tempo starting at beat + double get_tempo(double beat); bool set_tempo(double bpm, double start_beat, double end_beat); + + // get the bar length in beats starting at beat + double get_bar_len(double beat); void set_time_sig(double beat, double num, double den); void beat_to_measure(double beat, long *measure, double *m_beat, double *num, double *den); diff --git a/lib-src/portsmf/allegrord.cpp b/lib-src/portsmf/allegrord.cpp index 6a80f219c..87e1dcb8f 100644 --- a/lib-src/portsmf/allegrord.cpp +++ b/lib-src/portsmf/allegrord.cpp @@ -27,6 +27,8 @@ public: Alg_seq_ptr seq; double tsnum; double tsden; + double offset; + bool offset_found; Alg_reader(istream *a_file, Alg_seq_ptr new_seq); void readline(); @@ -73,15 +75,20 @@ Alg_reader::Alg_reader(istream *a_file, Alg_seq_ptr new_seq) tsnum = 4; // default time signature tsden = 4; seq = new_seq; + offset = 0.0; + offset_found = false; } -Alg_error alg_read(istream &file, Alg_seq_ptr new_seq) +Alg_error alg_read(istream &file, Alg_seq_ptr new_seq, double *offset_ptr) // read a sequence from allegro file { assert(new_seq); Alg_reader alg_reader(&file, new_seq); bool err = alg_reader.parse(); + if (!err && offset_ptr) { + *offset_ptr = alg_reader.offset; + } return (err ? alg_error_syntax : alg_no_error); } @@ -169,27 +176,38 @@ bool Alg_reader::parse() // parse_int ignores the first character of the argument track_num = parse_int(field); seq->add_track(track_num); - } - // maybe we have a sequence or track name - line_parser.get_remainder(field); - // if there is a non-space character after #track n then - // use it as sequence or track name. Note that because we - // skip over spaces, a sequence or track name cannot begin - // with leading blanks. Another decision is that the name - // must be at time zero - if (field.length() > 0) { - // insert the field as sequence name or track name - Alg_update_ptr update = new Alg_update; - update->chan = -1; - update->time = 0; - update->set_identifier(-1); - // sequence name is whatever is on track 0 - // other tracks have track names - const char *attr = - (track_num == 0 ? "seqnames" : "tracknames"); - update->parameter.set_attr(symbol_table.insert_string(attr)); - update->parameter.s = heapify(field.c_str()); - seq->add_event(update, track_num); + + // maybe we have a sequence or track name + line_parser.get_remainder(field); + // if there is a non-space character after #track n then + // use it as sequence or track name. Note that because we + // skip over spaces, a sequence or track name cannot begin + // with leading blanks. Another decision is that the name + // must be at time zero + if (field.length() > 0) { + // insert the field as sequence name or track name + Alg_update_ptr update = new Alg_update; + update->chan = -1; + update->time = 0; + update->set_identifier(-1); + // sequence name is whatever is on track 0 + // other tracks have track names + const char *attr = + (track_num == 0 ? "seqnames" : "tracknames"); + update->parameter.set_attr( + symbol_table.insert_string(attr)); + update->parameter.s = heapify(field.c_str()); + seq->add_event(update, track_num); + } + } else if (streql(field.c_str(), "#offset")) { + if (offset_found) { + parse_error(field, 0, "#offset specified twice"); + } + offset_found = true; + line_parser.get_nonspace_quoted(field); // number + field.insert(0, " "); // need char at beginning because + // parse_real ignores first character in the argument + offset = parse_real(field); } } else { // we must have a track to insert into @@ -456,6 +474,7 @@ int Alg_reader::find_real_in(string &field, int n) // scans from offset n to the end of a real constant bool decimal = false; int len = field.length(); + if (n < len && field[n] == '-') n += 1; // parse one minus sign for (int i = n; i < len; i++) { char c = field[i]; if (!isdigit(c)) { @@ -466,7 +485,7 @@ int Alg_reader::find_real_in(string &field, int n) } } } - return field.length(); + return len; } diff --git a/lib-src/portsmf/allegrosmfrd.cpp b/lib-src/portsmf/allegrosmfrd.cpp index 38ea76a2a..2010ede42 100644 --- a/lib-src/portsmf/allegrosmfrd.cpp +++ b/lib-src/portsmf/allegrosmfrd.cpp @@ -377,7 +377,7 @@ void Alg_midifile_reader::Mf_smpte(int hours, int mins, int secs, void Alg_midifile_reader::Mf_timesig(int i1, int i2, int i3, int i4) { - seq->set_time_sig(get_currtime() / divisions, i1, 1 << i2); + seq->set_time_sig(double(get_currtime()) / divisions, i1, 1 << i2); } diff --git a/lib-src/portsmf/allegrosmfwr.cpp b/lib-src/portsmf/allegrosmfwr.cpp index 9edfe233d..4bf1edc6e 100644 --- a/lib-src/portsmf/allegrosmfwr.cpp +++ b/lib-src/portsmf/allegrosmfwr.cpp @@ -512,9 +512,8 @@ void Alg_smf_write::write_tempo_change(int i) void Alg_smf_write::write_time_signature(int i) { Alg_time_sigs &ts = seq->time_sig; + write_delta(ts[i].beat); // write the time signature - long divs = ROUND(ts[i].beat * division); - write_varinum(divs - previous_divs); out_file->put('\xFF'); out_file->put('\x58'); // time signature out_file->put('\x04'); // length of message diff --git a/lib-src/portsmf/allegrowr.cpp b/lib-src/portsmf/allegrowr.cpp index 6ef6d547f..54bf601da 100644 --- a/lib-src/portsmf/allegrowr.cpp +++ b/lib-src/portsmf/allegrowr.cpp @@ -56,32 +56,34 @@ Alg_event_ptr Alg_seq::write_track_name(ostream &file, int n, // find a name and write it, return a pointer to it so the track // writer knows what update (if any) to skip { - Alg_event_ptr e = NULL; + Alg_event_ptr e = NULL; // e is the result, default is NULL file << "#track " << n; const char *attr = symbol_table.insert_string( n == 0 ? "seqnames" : "tracknames"); // search for name in events with timestamp of 0 for (int i = 0; i < events.length(); i++) { - e = events[i]; - if (e->time > 0) break; - if (e->is_update()) { - Alg_update_ptr u = (Alg_update_ptr) e; + Alg_event_ptr ue = events[i]; + if (ue->time > 0) break; + if (ue->is_update()) { + Alg_update_ptr u = (Alg_update_ptr) ue; if (u->parameter.attr == attr) { file << " " << u->parameter.s; + e = ue; // return the update event we found break; } } } - file << endl; - return e; + file << endl; // end of line containing #track [] + return e; // return parameter event with name if one was found } -void Alg_seq::write(ostream &file, bool in_secs) +void Alg_seq::write(ostream &file, bool in_secs, double offset) { int i, j; if (in_secs) convert_to_seconds(); else convert_to_beats(); + file << "#offset " << offset << endl; Alg_event_ptr update_to_skip = write_track_name(file, 0, track_list[0]); Alg_beats &beats = time_map->beats; for (i = 0; i < beats.len - 1; i++) { @@ -171,11 +173,11 @@ void Alg_seq::write(ostream &file, bool in_secs) } } -bool Alg_seq::write(const char *filename) +bool Alg_seq::write(const char *filename, double offset) { ofstream file(filename); if (file.fail()) return false; - write(file, units_are_seconds); - file.close(); - return true; + write(file, units_are_seconds, offset); + file.close(); + return true; } diff --git a/lib-src/portsmf/strparse.cpp b/lib-src/portsmf/strparse.cpp index d2f767282..547424ec8 100644 --- a/lib-src/portsmf/strparse.cpp +++ b/lib-src/portsmf/strparse.cpp @@ -78,7 +78,7 @@ void String_parse::get_remainder(std::string &field) field.clear(); skip_space(); int len = str->length() - pos; - if ((*str)[len - 1] == '\n') { // if str ends in newline, + if ((len > 0) && ((*str)[len - 1] == '\n')) { // if str ends in newline, len--; // reduce length to ignore newline } field.insert(0, *str, pos, len); diff --git a/src/AudacityApp.cpp b/src/AudacityApp.cpp index 538fc0b3d..1e43dffa4 100644 --- a/src/AudacityApp.cpp +++ b/src/AudacityApp.cpp @@ -94,6 +94,10 @@ It handles initialization and termination by subclassing wxApp. #include "import/Import.h" +#ifdef EXPERIMENTAL_SCOREALIGN +#include "effects/ScoreAlignDialog.h" +#endif + #ifdef _DEBUG #ifdef _MSC_VER #undef THIS_FILE @@ -279,6 +283,9 @@ void QuitAudacity(bool bForce) gParentFrame = NULL; CloseContrastDialog(); +#ifdef EXPERIMENTAL_SCOREALIGN + CloseScoreAlignDialog(); +#endif CloseScreenshotTools(); //release ODManager Threads diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 59b039b01..b832d42bb 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -23,12 +23,236 @@ Great care and attention to detail are necessary for understanding and modifying this system. The code in this file is run from three different thread contexts: the UI thread, the disk thread (which - this file creates and maintains) and the PortAudio callback thread. + this file creates and maintains; in the code, this is called the + Audio Thread), and the PortAudio callback thread. To highlight this deliniation, the file is divided into three parts based on what thread context each function is intended to run in. + \par EXPERIMENTAL_MIDI_PLAYBACK + If EXPERIMENTAL_MIDI_PLAYBACK is defined, this class also manages + MIDI playback. The reason for putting MIDI here rather than in, say, + class MidiIO, is that there is no high-level synchronization and + transport architecture, so Audio and MIDI must be coupled in order + to start/stop/pause and synchronize them. + + \par MIDI With Audio + When Audio and MIDI play simultaneously, MIDI synchronizes to Audio. + This is necessary because the Audio sample clock is not the same + hardware as the system time used to schedule MIDI messages. MIDI + is synchronized to Audio because it is simple to pause or rush + the dispatch of MIDI messages, but generally impossible to pause + or rush synchronous audio samples (without distortion). + + \par + MIDI output is driven by yet another thread. In principle, we could + output timestamped MIDI data at the same time we fill audio buffers + from disk, but audio buffers are filled far in advance of playback + time, and there is a lower latency thread (PortAudio's callback) that + actually sends samples to the output device. The relatively low + latency to the output device allows Audacity to stop audio output + quickly. We want the same behavior for MIDI, but there is not + periodic callback from PortMidi (because MIDI is asynchronous), so + this function is performed by the MidiThread class. + + \par + When Audio is running, MIDI is synchronized to Audio. Globals are set + in the Audio callback (audacityAudioCallback) for use by a time + function that reports milliseconds to PortMidi. (Details below.) + + \par MIDI Without Audio + When Audio is not running, PortMidi uses its own millisecond timer + since there is no audio to synchronize to. (Details below.) + + \par Implementation Notes and Details for MIDI + When opening devices, successAudio and successMidi indicate errors + if false, so normally both are true. Use playbackChannels, + captureChannels and mMidiPlaybackTracks.IsEmpty() to determine if + Audio or MIDI is actually in use. + + \par Audio Time + Normally, the current time during playback is given by the variable + mTime. mTime normally advances by frames / samplerate each time an + audio buffer is output by the audio callback. However, Audacity has + a speed control that can perform continuously variable time stretching + on audio. This is achieved in two places: the playback "mixer" that + generates the samples for output processes the audio according to + the speed control. In a separate algorithm, the audio callback updates + mTime by (frames / samplerate) * factor, where factor reflects the + speed at mTime. This effectively integrates speed to get position. + + \par Midi Time + MIDI is not warped according to the speed control. This might be + something that should be changed. (Editorial note: Wouldn't it + make more sense to display audio at the correct time and allow + users to stretch audio the way they can stretch MIDI?) For now, + MIDI plays at 1 second per second, so it requires an unwarped clock. + In fact, MIDI time synchronization requires a millisecond clock that + does not pause. Note that mTime will stop progress when the Pause + button is pressed, even though audio samples (zeros) continue to + be output. + + \par + Therefore, we define the following interface for MIDI timing: + \li \c AudioTime() is the time based on all samples written so far, including zeros output during pauses. AudioTime() is based on the start location mT0, not zero. + \li \c PauseTime() is the amount of time spent paused, based on a count of zero samples output. + \li \c MidiTime() is an estimate in milliseconds of the current audio output time + 1s. In other words, what audacity track time corresponds to the audio (including pause insertions) at the output? + + \par AudioTime() and PauseTime() computation + AudioTime() is simply mT0 + mNumFrames / mRate. + mNumFrames is incremented in each audio callback. Similarly, PauseTime() + is mNumPauseFrames / mRate. mNumPauseFrames is also incremented in + each audio callback when a pause is in effect. + + \par MidiTime() computation + MidiTime() is computed based on information from PortAudio's callback, + which estimates the system time at which the current audio buffer will + be output. Consider the (unimplemented) function RealToTrack() that + maps real time to track time. If outputTime is PortAudio's time + estimate for the most recent output buffer, then \n + RealToTrack(outputTime) = AudioTime() - PauseTime() - bufferDuration \n + We want to know RealToTrack of the current time, so we use this + approximation for small d: \n + RealToTrack(t + d) = RealToTrack(t) + d \n + Letting t = outputTime and d = (systemTime - outputTime), we can + substitute to get:\n + RealToTrack(systemTime) = AudioTime() - PauseTime() - bufferduration + (systemTime - outputTime) \n + MidiTime() should include pause time, so add PauseTime() to both sides of + the equation. Also MidiTime() is offset by 1 second to avoid negative + time at startup, so add 1 to both sides: + MidiTime() in seconds = RealToTrack(systemTime) + PauseTime() + 1 = \n + AudioTime() - bufferduration + (systemTime - outputTime) + 1 + + \par + The difference AudioTime() - PauseTime() is the time "cursor" for + MIDI. When the speed control is used, MIDI and Audio will become + unsynchronized. In particular, MIDI will not be synchronized with + the visual cursor, which moves with scaled time reported in mTime. + + \par Midi Synchronization + The goal of MIDI playback is to deliver MIDI messages synchronized to + audio (assuming no speed variation for now). If a midi event has time + tmidi, then the timestamp for that message should be \n + timestamp (in seconds) = tmidi + PauseTime() + 1.0 - latency.\n + (This is actually off by 1ms; see "PortMidi Latency Parameter" below for + more detail.) + Notice the extra 1.0, added because MidiTime() is offset by 1s to avoid + starting at a negative value. Also notice that we subtract latency. + The user must set device latency using preferences. Some software + synthesizers have very high latency (on the order of 100ms), so unless + we lower timestamps and send messages early, the final output will not + be synchronized. + This timestamp is interpreted by PortMidi relative to MidiTime(), which + is synchronized to audio output. So the only thing we need to do is + output Midi messages shortly before they will be played with the correct + timestamp. We will take "shortly before" to mean "at about the same time + as corresponding audio". Based on this, output the event when + AudioTime() - PauseTime() > mtime - latency, + adjusting the event time by adding PauseTime() + 1 - latency. + This gives at least mAudioOutputLatency for + the MIDI output to be generated (we want to generate MIDI output before + the actual output time because events generated early are accurately timed + according to their timestamp). However, the MIDI thread sleeps for + MIDI_SLEEP in its polling loop, so the worst case is really + mAudioOutputLatency + MIDI_SLEEP. In case the audio output latency is + very low, we will output events when + AudioTime() + MIDI_SLEEP - PauseTime() > mtime - latency. + + \par Interaction between MIDI, Audio, and Pause + When Pause is used, PauseTime() will increase at the same rate as + AudioTime(), and no more events will be output. Because of the + time advance of mAudioOutputLatency + MIDI_SLEEP + latency and the + fact that + AudioTime() advances stepwise by mAudioBufferDuration, some extra MIDI + might be output, but the same is true of audio: something like + mAudioOutputLatency audio samples will be in the output buffer + (with up to mAudioBufferDuration additional samples, depending on + when the Pause takes effect). When playback is resumed, there will + be a slight delay corresponding to the extra data previously sent. + Again, the same is true of audio. Audio and MIDI will not pause and + resume at exactly the same times, but their pause and resume times + will be within the low tens of milliseconds, and the streams will + be synchronized in any case. I.e. if audio pauses 10ms earlier than + MIDI, it will resume 10ms earlier as well. + + \par PortMidi Latency Parameter + PortMidi has a "latency" parameter that is added to all timestamps. + This value must be greater than zero to enable timestamp-based timing, + but serves no other function, so we will set it to 1. All timestamps + must then be adjusted down by 1 before messages are sent. This + adjustment is on top of all the calculations described above. It just + seem too complicated to describe everything in complete detail in one + place. + + \par Midi While Recording Only + All of the midi-to-audio synchronization is of course meaningless when + audio is not playing. If only recording, there is the problem that + synchronization is based on output time, but without audio output, + there is no output time. This does not seem like a critical feature, + so MIDI is not synchronized to audio without audio playback. The + user can always play a track of silence while recording to synchronize. + + \par Midi Without Audio Playback + When there is no audio playback, MIDI runs according to its own clock. + The midi timestamp clock starts at approximately the same time as + audio recording (if any). A timestamp of 0 corresponds to mT0, the + starting time in the Midi track(s). Thus the timestamp for an event + at time tmidi should be: \n + timestamp = tmidi - mT0 + PauseTime() - latency - 0.001\n + Where latency is the synthesizer latency, and the extra 0.001 is the + latency (1ms) that PortMidi adds to timestamps automatically. + + \par Midi Output Without Audio Playback + Midi events should be written before their timestamp expires. Since + the loop that checks for events to write pauses for MIDI_SLEEP, the + events should be written at least MIDI_SLEEP early, and due to + other delays and computation, we want some extra time, so let's + allow 2*MIDI_SLEEP. Therefore, the write time should be when:\n + tmidi - mT0 + PauseTime() - latency - 0.001 - 2 * MIDI_SLEEP < Pt_Time()\n, + which can be rearranged to:\n + tmidi < mT0 + Pt_Time() + MIDI_SLEEP + (MIDI_SLEEP + latency) - PauseTime\n + which matches the code in AudioIO::FillMidiBuffers() after converting ms to + s appropriately. (Note also that the 0.001 is dropped here -- it's not + really important). + + \par The code for Midi Without Audio was developed by simply trying + to play Midi alone and fixing everything that did not work. The + "normal" AudioIO execution was full of assumptions about audio, so + there is no systematic design for running without audio, merely a + number of "patches" to make it work. The expression + "mNumPlaybackChannels > 0" is used to detect whether audio playback + is active, and "mNumFrames > 0" is used to indicate that playback + of either Midi or Audio has actually started. (mNumFrames is + normally incremented by the audio callback, but if there is no + audio playback or recording, it is set to 1 at the end of + initialization. + + \par NoteTrack PlayLooped + When mPlayLooped is true, output is supposed to loop from mT0 to mT1. + For NoteTracks, we interpret this to mean that any note-on or control + change in the range mT0 <= t < mT1 is sent (notes that start before + mT0 are not played even if they extend beyond mT0). Then, all notes + are turned off. Events in the range mT0 <= t < mT1 are then repeated, + offset by (mT1 - mT0), etc. We do NOT go back to the beginning and + play all control changes (update events) up to mT0, nor do we "undo" + any state changes between mT0 and mT1. + + \par NoteTrack PlayLooped Implementation + The mIterator object (an Alg_iterator) returns NULL when there are + no more events scheduled before mT1. At mT1, we want to output + all notes off messages, but the FillMidiBuffers() loop will exit + if mNextEvent is NULL, so we create a "fake" mNextEvent for this + special "event" of sending all notes off. After that, we destroy + the iterator and use PrepareMidiIterator() to set up a new one. + At each iteration, time must advance by (mT1 - mT0), so the + accumulated time is held in mMidiLoopOffset. + \todo run through all functions called from audio and portaudio threads - to verify they are thread-safe. + to verify they are thread-safe. Note that synchronization of the style: + "A sets flag to signal B, B clears flag to acknowledge completion" + is not thread safe in a general multiple-CPU context. For example, + B can write to a buffer and set a completion flag. The flag write can + occur before the buffer write due to out-of-order execution. Then A + can see the flag and read the buffer before buffer writes complete. *//****************************************************************//** @@ -73,10 +297,11 @@ writing audio. #include "WaveTrack.h" #ifdef EXPERIMENTAL_MIDI_OUT -#define MIDI_BUFFER_AHEAD 1.0 /* secondds */ +#define MIDI_SLEEP 10 /* milliseconds */ #define ROUND(x) (int) ((x)+0.5) //#include #include "portmidi.h" +#include "../src/common/pa_util.h" #include "NoteTrack.h" #endif @@ -167,7 +392,7 @@ class AudioThread { public: typedef int ExitCode; AudioThread() { mDestroy = false; mThread = NULL; } - ExitCode Entry(); + virtual ExitCode Entry(); void Create() {} void Delete() { mDestroy = true; @@ -205,6 +430,14 @@ class AudioThread : public wxThread { #endif +#ifdef EXPERIMENTAL_MIDI_OUT +class MidiThread : public AudioThread { + public: + virtual ExitCode Entry(); +}; +#endif + + ////////////////////////////////////////////////////////////////////// // // UI Thread Context @@ -215,6 +448,9 @@ void InitAudioIO() { gAudioIO = new AudioIO(); gAudioIO->mThread->Run(); +#ifdef EXPERIMENTAL_MIDI_OUT + gAudioIO->mMidiThread->Run(); +#endif // Make sure device prefs are initialized if (gPrefs->Read(wxT("AudioIO/RecordingDevice"), wxT("")) == wxT("")) { @@ -274,14 +510,14 @@ AudioIO::AudioIO() #ifdef EXPERIMENTAL_MIDI_OUT mMidiStream = NULL; + mMidiThreadFillBuffersLoopRunning = false; + mMidiThreadFillBuffersLoopActive = false; mMidiStreamActive = false; mSendMidiState = false; + mIterator = NULL; - mIterator = new Alg_iterator(mSeq, true); - mIterator->begin(true); - mNextEvent = mIterator->next(); - - mAudioCallbackSampleNumber = 0; + mNumFrames = 0; + mNumPauseFrames = 0; #endif #ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT @@ -339,6 +575,8 @@ AudioIO::AudioIO() // Same logic for PortMidi as described above for PortAudio } + mMidiThread = new MidiThread(); + mMidiThread->Create(); #endif // Start thread @@ -373,6 +611,7 @@ AudioIO::~AudioIO() #ifdef EXPERIMENTAL_MIDI_OUT Pm_Terminate(); + mMidiThread->Delete(); #endif /* Delete is a "graceful" way to stop the thread. @@ -672,6 +911,10 @@ bool AudioIO::StartPortAudioStream(double sampleRate, unsigned int numCaptureChannels, sampleFormat captureFormat) { +#ifdef EXPERIMENTAL_MIDI_OUT + mNumFrames = 0; + mNumPauseFrames = 0; +#endif mLastPaError = paNoError; // pick a rate to do the audio I/O at, from those available. The project // rate is suggested, but we may get something else if it isn't supported @@ -748,7 +991,10 @@ bool AudioIO::StartPortAudioStream(double sampleRate, else captureParameters->suggestedLatency = latencyDuration/1000.0; } - +#ifdef EXPERIMENTAL_MIDI_OUT + if (numPlaybackChannels == 0 && numCaptureChannels == 0) + return true; +#endif mLastPaError = Pa_OpenStream( &mPortStreamV19, captureParameters, playbackParameters, mRate, paFramesPerBufferUnspecified, @@ -809,7 +1055,7 @@ void AudioIO::StartMonitoring(double sampleRate) int AudioIO::StartStream(WaveTrackArray playbackTracks, WaveTrackArray captureTracks, #ifdef EXPERIMENTAL_MIDI_OUT - NoteTrackArray *midiPlaybackTracks, + NoteTrackArray midiPlaybackTracks, #endif TimeTrack *timeTrack, double sampleRate, double t0, double t1, @@ -932,26 +1178,22 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, successAudio = StartPortAudioStream(sampleRate, playbackChannels, captureChannels, captureFormat); - #ifdef EXPERIMENTAL_MIDI_OUT + // TODO: it may be that midi out will not work unless audio in or out is // active -- this would be a bug and may require a change in the // logic here. - bool successMidi; + bool successMidi = true; - if(mMidiPlaybackTracks && !mMidiPlaybackTracks->IsEmpty()){ + if(!mMidiPlaybackTracks.IsEmpty()){ successMidi = StartPortMidiStream(); } // On the other hand, if MIDI cannot be opened, we will not complain #endif - if (!successAudio -#ifdef EXPERIMENTAL_MIDI_OUT - && !successMidi -#endif - ) { + if (!successAudio) { if (mListener && captureChannels > 0) mListener->OnAudioIOStopRecording(); mStreamToken = 0; @@ -959,7 +1201,8 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, } // - // The (audio) stream has been opened successfully. We now proceed to + // The (audio) stream has been opened successfully (assuming we tried + // to open it). We now proceed to // allocate the memory structures the stream will need. // @@ -1017,16 +1260,14 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, wxMilliSleep( 50 ); #ifdef EXPERIMENTAL_MIDI_OUT - // output MIDI control information up to the current cursor location - // MOTODO: output midi control changes and program changes up to cursor - // QUESTION: can we do that from this thread? + // if no playback, reset the midi time to zero to roughly sync + // with recording (or if recording is not going to happen, just + // reset time now so that time stamps increase from zero + Pt_Stop(); + Pt_Start(1, NULL, NULL); #endif - if(mNumPlaybackChannels > 0 || mNumCaptureChannels > 0 -#ifdef EXPERIMENTAL_MIDI_OUT - || !mMidiPlaybackTracks->IsEmpty() -#endif - ) { + if(mNumPlaybackChannels > 0 || mNumCaptureChannels > 0) { // Now start the PortAudio stream! PaError err; @@ -1046,6 +1287,13 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, } mAudioThreadFillBuffersLoopRunning = true; +#ifdef EXPERIMENTAL_MIDI_OUT + // If audio is not running, mNumFrames will not be incremented and + // MIDI will hang waiting for it unless we do it here. + if (mNumPlaybackChannels + mNumCaptureChannels == 0) { + mNumFrames = 1; + } +#endif // // Generate an unique value each time, to be returned to @@ -1059,47 +1307,57 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, #ifdef EXPERIMENTAL_MIDI_OUT -#define TIME_PROC ((long (*)(void *)) Pt_Time) - -bool AudioIO::IsMidiActive() +PmTimestamp MidiTime(void *info) { - if (!mMidiStream) return false; - // find out if we have passed the last midi event - return mLastMidiTime + mMidiLatency <= TIME_PROC(NULL); + return gAudioIO->MidiTime(); } +// Set up state to iterate NoteTrack events in sequence. +// Sends MIDI control changes up to the starting point mT0 +// if send is true. Output is delayed by offset to facilitate +// looping (each iteration is delayed more). +void AudioIO::PrepareMidiIterator(bool send, double offset) +{ + int i; + int nTracks = mMidiPlaybackTracks.GetCount(); + // Set up to play only one track: + mSeq = mMidiPlaybackTracks[0]->GetSequence(); + mIterator = new Alg_iterator(mSeq, true); + // Iterator not yet intialized, must add each track... + for (i = 0; i < nTracks; i++) { + NoteTrack *t = mMidiPlaybackTracks[i]; + mIterator->begin_seq(t->GetSequence(), t, t->GetOffset() + offset); + } + GetNextEvent(); // prime the pump for FillMidiBuffers + + // Start MIDI from current cursor position + mSendMidiState = true; + while (mNextEvent && + mNextEventTime < mT0 + offset) { + if (send) OutputEvent(); + GetNextEvent(); + } + mSendMidiState = false; +} bool AudioIO::StartPortMidiStream() { - int i, latency; - + int i; + int nTracks = mMidiPlaybackTracks.GetCount(); // Only start MIDI stream if there is an open track - if (mMidiPlaybackTracks->GetCount() == 0) + if (nTracks == 0) return false; - /* get latency from PortAudio */ - int framesPerBuffer = 1102; // constant passed to Pa_OpenStream call - // but not defined beforehand -/* HCK MIDI FIX ORG - int numBuffers = Pa_GetMinNumBuffers( framesPerBuffer, mRate ); - - if (numBuffers) - latency = 1000 * numBuffers * framesPerBuffer / mRate; - else - latency = 500; -HCK MIDI PATCH ORG */ - - mMidiLatency = 100; + mMidiLatency = 1; // arbitrary, but small printf("StartPortMidiStream: mT0 %g mTime %g\n", gAudioIO->mT0, gAudioIO->mTime); - if (Pt_Started()) Pt_Stop(); // start from zero - Pt_Start(1, 0, 0); /* timer started w/millisecond accuracy */ /* get midi playback device */ PmDeviceID playbackDevice = Pm_GetDefaultOutputDeviceID(); wxString playbackDeviceName = gPrefs->Read(wxT("/MidiIO/PlaybackDevice"), wxT("")); - + mSynthLatency = gPrefs->Read(wxT("/MidiIO/SynthLatency"), + DEFAULT_SYNTH_LATENCY); if (wxStrcmp(playbackDeviceName, wxT("")) != 0) { for (i = 0; i < Pm_CountDevices(); i++) { const PmDeviceInfo *info = Pm_GetDeviceInfo(i); @@ -1120,7 +1378,7 @@ HCK MIDI PATCH ORG */ playbackDevice, NULL, 0, - TIME_PROC, + &::MidiTime, NULL, mMidiLatency); // DEBUGGING @@ -1128,35 +1386,22 @@ HCK MIDI PATCH ORG */ printf("Pm_OpenOutput on %s, return code %d\n", info->name, mLastPmError); - mCurrentMidiTime = TIME_PROC(NULL); - mLastMidiTime = 0x7FFF0000; // a big number, adjusted when we play last - // midi event - mMidiWait = 0; - fprintf(stderr, "mT0: %f\n", mT0); - fprintf(stderr, "%li %li : STARTING\n", mCurrentMidiTime, mLastMidiTime ); mMidiStreamActive = true; + mPauseTime = 0; + mMidiLoopOffset = 0; + mMidiOutputComplete = false; // mCnt = 0; - // for now, play only one track - mSeq = (*mMidiPlaybackTracks)[0]->GetSequence(); - mVC = (*mMidiPlaybackTracks)[0]->GetVisibleChannels(); - - mIterator = new Alg_iterator(mSeq, true); - mIterator->begin(); - GetNextEvent(); // prime the pump for FillMidiBuffers + PrepareMidiIterator(); - - // Start MIDI from current cursor position - mSendMidiState = true; - while (mNextEvent && - mNextEvent->time < mT0) { - OutputEvent(); - GetNextEvent(); - } - // MOTODO -- fix this to send updates immediately until t0 is reached - mSendMidiState = false; + // It is ok to call this now, but do not send timestamped midi + // until after the first audio callback, which provides necessary + // data for MidiTime(). + Pm_Synchronize(mMidiStream); // start using timestamps + // start midi output flowing (pending first audio callback) + mMidiThreadFillBuffersLoopRunning = true; return (mLastPmError == pmNoError); } @@ -1258,27 +1503,31 @@ void AudioIO::StopStream() #ifdef EXPERIMENTAL_MIDI_OUT /* Stop Midi playback */ - // problem here -- if explicitly stopped, we'll hang here - // while (mLastMidiTime + mMidiLatency + 10 < TIME_PROC(NULL)) { - // wxMilliSleep( 50 ); - // } - if ( mMidiStream ) { mMidiStreamActive = false; - // MOTODO: if output in progress, send all off, etc. + mMidiThreadFillBuffersLoopRunning = false; // stop output to stream + // but output is in another thread. Wait for output to stop... + while (mMidiThreadFillBuffersLoopActive) { + wxMilliSleep(1); + } + // now we can assume "ownership" of the mMidiStream + // if output in progress, send all off, etc. for (int i = 0; i < 16; i++) { Pm_WriteShort(mMidiStream, 0, Pm_Message(0xB0 + i, 0x7B, 0)); } - wxMilliSleep(40); // deliver the all-off messages before closing - Pm_Abort(mMidiStream); // MOTODO: only abort if output in progress + // Note: this code is here for future consideration. It's + // possible sometimes to abort some messages waiting in the + // output buffers, but if you do that, you might leave notes + // hanging. It seems better to deliver all-off messages and + // flush all the messages, even if that delays things by + // the midi latency. + // wxMilliSleep(40); // deliver the all-off messages + // Pm_Abort(mMidiStream); Pm_Close(mMidiStream); - printf("Pm_Close() called\n"); mMidiStream = NULL; - - // Reset MIDI track positions this way for now - // mMidiPlaybackTracks[0]->SetLastMidiPosition(0); - // mLastMidiTime = 0; mIterator->end(); + delete mIterator; + mIterator = NULL; // just in case someone tries to reference it } #endif @@ -1427,9 +1676,10 @@ bool AudioIO::IsStreamActive() isActive = (Pa_IsStreamActive( mPortStreamV19 ) > 0); else isActive = false; -/* REQUIRES PORTMIDI */ -// if( mMidiStreamActive ) -// isActive = true; +#ifdef EXPERIMENTAL_MIDI_OUT + if( mMidiStreamActive && !mMidiOutputComplete ) + isActive = true; +#endif return isActive; } @@ -1775,19 +2025,67 @@ AudioThread::ExitCode AudioThread::Entry() } gAudioIO->mAudioThreadFillBuffersLoopActive = false; -#ifdef EXPERIMENTAL_MIDI_OUT - if( gAudioIO->mMidiStreamActive && - gAudioIO->mAudioThreadFillBuffersLoopRunning) - { - gAudioIO->FillMidiBuffers(); - } -#endif Sleep(10); } return 0; } + +#ifdef EXPERIMENTAL_MIDI_OUT +MidiThread::ExitCode MidiThread::Entry() +{ + bool paused = false; + long pauseStart = 0; + while( !TestDestroy() ) + { + // Set LoopActive outside the tests to avoid race condition + gAudioIO->mMidiThreadFillBuffersLoopActive = true; + if( gAudioIO->mMidiThreadFillBuffersLoopRunning && + // mNumFrames signals at least one callback, needed for MidiTime() + gAudioIO->mNumFrames > 0) + { + // Keep track of time paused. If not paused, fill buffers. + if (gAudioIO->mNumPlaybackChannels == 0 && gAudioIO->IsPaused()) { + if (!paused) { + paused = true; + pauseStart = MidiTime(NULL); + } + } else { + if (paused) { + paused = false; + gAudioIO->mPauseTime += (MidiTime(NULL) - pauseStart); + } + gAudioIO->FillMidiBuffers(); + + // test for end + double real_time = gAudioIO->mT0 + gAudioIO->MidiTime() * 0.001 - + gAudioIO->PauseTime(); + if (gAudioIO->mNumPlaybackChannels != 0) { + real_time -= 1; // with audio, MidiTime() runs ahead 1s + } + // The TrackPanel::OnTimer() method updates the time position + // indicator every 200ms, so it tends to not advance the + // indicator to the end of the selection (mT1) but instead stop + // up to 200ms before the end. At this point, output is shut + // down and the indicator is removed, but for a brief time, the + // indicator is clearly stopped before reaching mT1. To avoid + // this, we do not set mMidiOutputComplete until we are actually + // 0.22s beyond mT1 (even though we stop playing at mT1. This + // gives OnTimer() time to wake up and draw the final time + // position at mT1 before shutting down the stream. + gAudioIO->mMidiOutputComplete = + (!gAudioIO->mPlayLooped && real_time >= gAudioIO->mT1 + 0.220); + // !gAudioIO->mNextEvent); + } + } + gAudioIO->mMidiThreadFillBuffersLoopActive = false; + Sleep(MIDI_SLEEP); + } + return 0; +} +#endif + int AudioIO::GetCommonlyAvailPlayback() { int commonlyAvail = mPlaybackBuffers[0]->AvailForPut(); @@ -2285,9 +2583,6 @@ void AudioIO::FillBuffers() mListener->OnAudioIONewBlockFiles(blockFileLog); } } // end of record buffering - - //if ( mMidiStreamActive && mMidiPlaybackTracks.GetCount() > 0 ) - //FillMidiBuffers(); } void AudioIO::SetListener(AudioIOListener* listener) @@ -2300,6 +2595,9 @@ void AudioIO::SetListener(AudioIOListener* listener) #ifdef EXPERIMENTAL_MIDI_OUT +static Alg_update gAllNotesOff; // special event for loop ending +// the fields of this event are never used, only the address is important + void AudioIO::OutputEvent() { int channel = (mNextEvent->chan) & 0xF; // must be in [0..15] @@ -2307,15 +2605,59 @@ void AudioIO::OutputEvent() int data1 = -1; int data2 = -1; - // printf("OutputEvent: now %d\n", TIME_PROC(NULL)); - - if (mVC & (1 << channel)) { // if mNextEvent's channel is visible, play it + // 0.0005 is for rounding + double time = mNextEventTime + PauseTime() + 0.0005 - + ((mMidiLatency + mSynthLatency) * 0.001); + if (mNumPlaybackChannels > 0) { // is there audio playback? + time += 1; // MidiTime() has a 1s offset + } else { + time -= mT0; // Midi is not synced to audio + } + // state changes have to go out without delay because the + // midi stream time gets reset when playback starts, and + // we don't want to leave any control changes scheduled for later + if (time < 0 || mSendMidiState) time = 0; + PmTimestamp timestamp = (PmTimestamp) (time * 1000); /* s to ms */ + + // The special event gAllNotesOffEvent means "end of playback, send + // all notes off on all channels" + if (mNextEvent == &gAllNotesOff) { + for (channel = 0; channel < 16; channel++) { + Pm_WriteShort(mMidiStream, timestamp, + Pm_Message(0xB0 + channel, 0x7B, 0)); + } + if (mPlayLooped) { + // jump back to beginning of loop + mMidiLoopOffset += (mT1 - mT0); + PrepareMidiIterator(false, mMidiLoopOffset); + } else { + mNextEvent = NULL; + } + return; + } + + // if mNextEvent's channel is visible, play it, visibility can + // be updated while playing. Be careful: if we have a note-off, + // then we must not pay attention to the channel selection + // because we must turn the note off even if the user changed + // the channel selection after the note began + if (((mNextEventTrack->GetVisibleChannels() & (1 << channel)) && + // only play if note is not muted: + !((mHasSolo || mNextEventTrack->GetMute()) && + !mNextEventTrack->GetSolo())) || + // the following allows note-offs even when muted or not selected + (mNextEvent->is_note() && !mNextIsNoteOn)) { // Note event if (mNextEvent->is_note() && !mSendMidiState) { // Pitch and velocity data1 = mNextEvent->get_pitch(); - if (mNextIsNoteOn) data2 = mNextEvent->get_loud(); // get velocity - else data2 = 0; // 0 velocity means "note off" + if (mNextIsNoteOn) { + data2 = mNextEvent->get_loud(); // get velocity + int offset = mNextEventTrack->GetGain(); + data2 += offset; // offset comes from per-track slider + // clip velocity to insure a legal note-on value + data2 = (data2 < 0 ? 1 : (data2 > 127 ? 127 : data2)); + } else data2 = 0; // 0 velocity means "note off" command = 0x90; // MIDI NOTE ON (or OFF when velocity == 0) // Update event } else if (mNextEvent->is_update()) { @@ -2364,393 +2706,123 @@ void AudioIO::OutputEvent() } } if (command != -1) { - // MIDI time is relative to the start of playback, but note times - // are relative to the start of the track. mT0 is the correction - // factor, because it's the time offset where playback started. - double time = mNextEventTime - mT0; - if (time < 0) time = 0; - PmTimestamp timestamp = (PmTimestamp) (time * 1000); /* s to ms */ Pm_WriteShort(mMidiStream, timestamp, Pm_Message((int) (command + channel), (long) data1, (long) data2)); - // printf("midi out: time %d msg %2x %3d %3d\n", - // (PmTimestamp) (mNextEventTime * 1000), - // (command + channel), (long) data1, (long) data2); } } } void AudioIO::GetNextEvent() { - mNextEvent = mIterator->next(&mNextIsNoteOn); - if (mNextEvent) { - if (mNextIsNoteOn) { - mNextEventTime = mNextEvent->time; - } else { - mNextEventTime = mNextEvent->get_end_time(); - } - } else { - mLastMidiTime = (int) (mNextEventTime * 1000); + mNextEventTrack = NULL; // clear it just to be safe + // now get the next event and the track from which it came + double nextOffset; + if (!mIterator) { + mNextEvent = NULL; + return; + } + mNextEvent = mIterator->next(&mNextIsNoteOn, + (void **) &mNextEventTrack, + &nextOffset, mT1 + mMidiLoopOffset); + if (mNextEvent) { + mNextEventTime = (mNextIsNoteOn ? mNextEvent->time : + mNextEvent->get_end_time()) + nextOffset;; + } else { // terminate playback at mT1 + mNextEvent = &gAllNotesOff; + mNextEventTime = mT1 + mMidiLoopOffset - ALG_EPS; + mNextIsNoteOn = true; // do not look at duration + mIterator->end(); + delete mIterator; + mIterator = NULL; // debugging aid } - // printf("after GetNextEvent: time %g, event %p, mLastMidiTime %d\n", - // mNextEventTime, mNextEvent, mLastMidiTime); } + +bool AudioIO::SetHasSolo(bool hasSolo) +{ + mHasSolo = hasSolo; + return mHasSolo; +} + + +// returns estimated current track time void AudioIO::FillMidiBuffers() { -// if (mPlaybackTracks.IsEmpty() && !mSendMidiState) { -// gAudioIO->mTime = (now - mCurrentMidiTime)/1000 + mT0; -// } - - // assume gAudioIO->mTime is the current time - // assume output MIDI data 1s in advance of real time (mTime) - // assume (for now) that midi time and sequence time are based at zero - // then we need to iterate and output Midi until next time > mTime + 1s - - while (mNextEvent && - mNextEvent->time < gAudioIO->mTime + MIDI_BUFFER_AHEAD) { - OutputEvent(); - GetNextEvent(); - } -#ifdef OLD_CODE - if (now < mMidiWait) { - return; - } - int i, j, k, track, visibleChannels; - long channel, key, time; - float command, data1, data2; - double r; - char updateParameter[13]; - bool forcedBreak = false; - - visibleChannels = mVC; - - if (mSeq) { - i = 0; // index of buffer - //testSeq->iteration_begin(); - - Alg_event_ptr currEvent; - - while ( currEvent = mSeq->iteration_next() ) { - // TODO HCK : this loop has Russian painter problem - /* - // In Update mode, events should be delivered immediately - if (mSendMidiState) time = 0; - // Normal playback mode takes the given event times - else - //time = (currEvent->time - mT0) * 1000; - time = mCurrentMidiTime + ( currEvent->time - mT0 ) * 1000; - */ - - if (gAudioIO->mTime >= gAudioIO->mT1 && !gAudioIO->mPlayLooped) { - mMidiStreamActive = false; - // gAudioIO->mInCallbackFinishedState = true; - mStreamToken = 0; - } - - if (currEvent->time < mLastMidiTime / 1000) { - continue; - } - - if (currEvent->time >= mT0) { - time = ( currEvent->time - mT0 ) * 1000 + mCurrentMidiTime; - if (mSendMidiState) { - mSendMidiState = false; - mCurrentMidiTime = TIME_PROC(NULL); - } - } else { - time = 0; - } - - if (mCnt > 0) { - j = 0; - printf( "HCK : sorting...\n" ); - qsort( mMidiQueue, mCnt, sizeof( PmEvent ), compareTime ); - printf( "HCK : sorting...\n" ); - for (int azaa = 0; azaa < mCnt; azaa++) { - printf( "HCK : SORT : %f %f\n", (float)mMidiQueue[azaa].timestamp, (float)time ); - } - while (mMidiQueue[j].timestamp <= time) { - mMidiBuffer[i].timestamp = mMidiQueue[j].timestamp; - memcpy(&mMidiBuffer[i].message, &mMidiQueue[j].message, - sizeof(PmMessage)); - printf( "HCK : QUEUE!!!! : mCnt : %d\n", mCnt ); - printf( "HCK : QUEUE!!!! : j : %d\n", j ); - printf( "HCK : QUEUE!!!! : timestampQ : %f\n", (float)mMidiQueue[j].timestamp ); - printf( "HCK : QUEUE!!!! : timestampB : %f\n", (float)mMidiBuffer[i].timestamp ); - printf( "HCK : QUEUE!!!! : data Q : %f\n", (float)mMidiQueue[j].message ); - printf( "HCK : QUEUE!!!! : data B : %f\n", (float)mMidiBuffer[i].message ); - - i++; - j++; - mCnt--; - - if (mCnt == 0) { - break; - } - } - - if (j > 0 && mCnt > 0) { - memmove(&mMidiQueue[0], &mMidiQueue[j], - sizeof( PmEvent ) * mCnt ); - } - } - - channel = currEvent->chan; - command = data1 = data2 = -1; - - if (visibleChannels & (1 << channel)) { - // Note event - if (currEvent->is_note() && mSendMidiState == false) { - // Pitch and velocity - data1 = currEvent->get_pitch(); - data2 = currEvent->get_loud(); - command = 0x90; - } - // Update event - else if (currEvent->is_update()) { - // Allegro update events are stored as name/value parameters - // where names can also contain important MIDI values and the - // value data type. To make this as easy as possible, we - // only look at the first four characters of each name to - // determine the command. - strcpy(updateParameter, - ((Alg_update_ptr)currEvent)->parameter.attr_name()); - updateParameter[4] = 0; - - if (strcmp(updateParameter, "prog") == 0) { - // Instrument change - - data1 = ((Alg_update_ptr)currEvent)->parameter.i; - data2 = 0; - command = 0xC0; - } else if (strcmp(updateParameter, "cont") == 0 && - mSendMidiState == false) { - // Controller change - - // The number of the controller being changed is embedded - // in the parameter name so we grab the whole name, set the - // index value to the position just after "control" - strcpy(updateParameter, - ((Alg_update_ptr)currEvent)->parameter.attr_name()); - - k = 7; - data1 = 0; - - while (updateParameter[k] != wxT('r')) { - data1 = data1 * 10 + atoi(&updateParameter[k]); - k++; - } - - // Allegro normalizes controller values - data2 = ((Alg_update_ptr)currEvent)->parameter.r * 127; - command = 0xB0; - } else if (strcmp(updateParameter, "bend") == 0 && - mSendMidiState == false) { - // Bend change - - // Reverse Allegro's post-processing of bend values - r = (((Alg_update_ptr)currEvent)->parameter.r + 1) * 8192; - - data1 = ((long)r) >> 7; - data2 = (((long)r) << 7) >> 7; - command = 0xE0; - } else if (strcmp(updateParameter, "pres") == 0 && - mSendMidiState == false) { - // Pressure change - - // Allegro normalizes pressures - r = ((Alg_update_ptr)currEvent)->parameter.r * 127; - key = currEvent->get_identifier(); - - // Channel pressure - if (key == -1) { - data1 = r; - data2 = 0; - command = 0xD0; - } else { - // Key pressure - data1 = key; - data2 = r; - command = 0xA0; - } - } - } - } - - if (command != -1) { - mMidiBuffer[i].timestamp = time; - mMidiBuffer[i].message = Pm_Message((int)(command + channel), - (long)data1, (long)data2); - printf( "HCK[%d]\n", i ); - printf( "Command : %d\n", (int)command ); - printf( "mTime : %f\n", (float)gAudioIO->mTime ); - printf( "TimeStamp : %f\n", (float)mMidiBuffer[i].timestamp ); - printf( "CurMidiTime : %f\n", (float)mCurrentMidiTime ); - printf( "LastMidiTime: %f\n", (float)mLastMidiTime ); - printf( "MidiWait : %f\n", (float)mMidiWait ); - printf( "Time : %f\n", (float)TIME_PROC(NULL) ); - i++; - if (command == 0x90) { - mMidiQueue[mCnt].timestamp = - time + (long)currEvent->get_duration() * 1000; - mMidiQueue[mCnt].message = Pm_Message((int)(0x90 + channel), - (long)data1, 0 ); - printf( "HCK QUEUE[%d]\n", mCnt ); - printf( "Command : OFF\n" ); - printf( "mTime : %f\n", (float)gAudioIO->mTime ); - printf( "TimeStamp : %f\n", (float)mMidiQueue[mCnt].timestamp ); - printf( "CurMidiTime : %f\n", (float)mCurrentMidiTime ); - printf( "LastMidiTime: %f\n", (float)mLastMidiTime ); - printf( "MidiWait : %f\n", (float)mMidiWait ); - printf( "Time : %f\n", (float)TIME_PROC(NULL) ); - mCnt++; - } else - fprintf(stderr, "command: %s\n", updateParameter); - } - - // Turn off updates when we reach the selection beginning - /* - if (mSendMidiState) { - if (i == 0 && i > testSeq->seek_time(mT0, track)) { - // The first 1/10 of the file has been processed - // so just to 5 seconds before cursor to avoid lag - notesOn = true; - i = testSeq->seek_time(mT0, track); - fprintf(stderr, "%li: Stop processing updates\n", TIME_PROC(NULL)); - } - } - */ - // Stop when: - // 1. enough events are buffered - // 2. there are no more events to buffer - //if (i >= endIndex || i == testSeq->length()) - if (i >= MAX_MIDI_BUFFER_SIZE - 1) { - if (!mSendMidiState) { - printf( "HCK : Pm_Write : 111111111\n" ); - qsort( mMidiBuffer, i, sizeof( PmEvent ), compareTime ); - Pm_Write(mMidiStream, mMidiBuffer, i); - mMidiWait = time - 1000; - } - i = 0; - mLastMidiTime = currEvent->time * 1000; - forcedBreak = true; - break; - } else if (currEvent->time * 1000 >= mLastMidiTime + 2000) { - if (!mSendMidiState) { - printf( "HCK : Pm_Write : 222222222\n" ); - qsort( mMidiBuffer, i, sizeof( PmEvent ), compareTime ); - Pm_Write( mMidiStream, mMidiBuffer, i ); - mMidiWait = time - 1000; - } - i = 0; - mLastMidiTime = currEvent->time * 1000; - forcedBreak = true; - break; - } - } // End of While - //testSeq->iteration_end(); - if (!forcedBreak ) { // this means there are no more event in testSeq. - mMidiStreamActive = false; - // gAudioIO->mInCallbackFinishedState = true; + bool hasSolo = false; + int numPlaybackTracks = gAudioIO->mPlaybackTracks.GetCount(); + int t; + for(t = 0; t < numPlaybackTracks; t++ ) + if( gAudioIO->mPlaybackTracks[t]->GetSolo() ) { + hasSolo = true; + break; } - } // End of if( mSeq ) -#endif // OLD_CODE -} - -void AudioIO::AudacityMidiCallback() { - - // instrumentation to see if we are called often - - // This calculation is taken from GetStreamTime, found in pa_asio.cpp - // asio systemTime is supposed to be measured according to the same - // clock as timeGetTime - double currentSystemTime = (double)timeGetTime() * .001; // get current system time - - if(!mLastSystemTime) mLastSystemTime = currentSystemTime; - if(!mLatencyBetweenSystemTimes) mLatencyBetweenSystemTimes = 0; - - mLatencyBetweenSystemTimes = max(mLatencyBetweenSystemTimes, currentSystemTime - mLastSystemTime); - mLastMidiTime = currentSystemTime; - - if(mRequestMidiStop) { - /* TODO: Send all notes off */ - mMidiStreamActive = false; - return; + int numMidiPlaybackTracks = gAudioIO->mMidiPlaybackTracks.GetCount(); + for(t = 0; t < numMidiPlaybackTracks; t++ ) + if( gAudioIO->mMidiPlaybackTracks[t]->GetSolo() ) { + hasSolo = true; + break; + } + SetHasSolo(hasSolo); + // Compute the current track time differently depending upon + // whether audio playback is in effect: + double time; + if (mNumPlaybackChannels > 0) { + time = AudioTime() - PauseTime(); + } else { + time = mT0 + Pt_Time() * 0.001 - PauseTime(); + if (mNumCaptureChannels <= 0) { + // no audio callback, so move the time cursor here: + double track_time = time - mMidiLoopOffset; + // Since loop offset is incremented when we fill the + // buffer, the cursor tends to jump back to mT0 early. + // Therefore, if we are in loop mode, and if mTime < mT0, + // we must not be at the end of the loop yet. + if (mPlayLooped && track_time < mT0) { + track_time += (mT1 - mT0); + } + // mTime is shared with another thread so we stored + // intermediate values in track_time. Do the update + // atomically now that we have the final value: + mTime = track_time; + } + // advance time so that midi messages are written a little early, + // timestamps will insure accurate output timing. This is an "extra" + // MIDI_SLEEP interval; another is added below to compensate for the + // fact that we need to output messages that will become due while + // we are sleeping. + time += MIDI_SLEEP * 0.001; } - if(mPaused || !mMidiStreamActive) { return; } - - //look at next message - //assume next message from iterator is available - //(so create iterator & get/store first event before - //callback is called) - - double t = getCurrentTrackTime(); - while(mNextEvent != NULL && mNextEvent->time < t + 0.2) { - long ts = calculateMidiTimeStamp(mNextEvent->time); - - /* TODO: Where is ts supposed to be used? - * send next message to portmidi with ts; - * mNextEvent = mIterator->next(); - */ + while (mNextEvent && + mNextEventTime < time + + ((MIDI_SLEEP + mSynthLatency) * 0.001)) { OutputEvent(); GetNextEvent(); } } -double AudioIO::getCurrentTrackTime() { - //compare to code that scrolls cursor - - // This calculation is taken from GetStreamTime, found in pa_asio.cpp - // asio systemTime is supposed to be measured according to the same - // clock as timeGetTime - double timeSinceBoot = (double)timeGetTime() * .001; // get time since boot - - double outputTime; - long sampleNumber; - do { - outputTime = mAudioCallbackOutputTime; - sampleNumber = mAudioCallbackSampleNumber; - } while (outputTime != mAudioCallbackOutputTime); - double delta = timeSinceBoot - outputTime; - - mStartFrame = mT0 * mRate; - - // We add startFrame in case we're not starting at the beginning of the track - double trackTime = (sampleNumber + mStartFrame - mNumPauseFrames) / mRate; - - // at outputTime, trackTime will be correct - - trackTime += delta; - //now tracktime is adjusted from acot to now - return trackTime; +double AudioIO::PauseTime() +{ + if (mNumPlaybackChannels > 0) { + return mNumPauseFrames / mRate; + } else { + return mPauseTime * 0.001; + } } -long AudioIO::calculateMidiTimeStamp(double eventTime) { - //assumptions: portmidi uses default time function which is "system time" - start time - double outputTime; - long sampleNumber; - do { - outputTime = mAudioCallbackOutputTime; - sampleNumber = mAudioCallbackSampleNumber; - } while (outputTime != mAudioCallbackOutputTime); - mStartFrame = mT0 * mRate; - - double trackTime = mAudioCallbackSampleNumber + mStartFrame - mNumPauseFrames / mRate; - //at ot, tracktime will be correct - double delta = eventTime - trackTime; - - outputTime += delta; // at outputTime, it will be time for event - outputTime -= Pt_Time(); // get startTime from PortTime library - - // now, outputTime is in PortMidi timestamp coordinates - - long outputTimeMS = (outputTime * 1000) - 100; // convert to ms and subtract PortMidi latency - return outputTimeMS; +PmTimestamp AudioIO::MidiTime() +{ + if (mNumPlaybackChannels > 0) { + // note: the extra 0.0005 is for rounding + return PmTimestamp(1000 * (AudioTime() + 1.0005 - + mAudioFramesPerBuffer / mRate + + PaUtil_GetTime() - mAudioCallbackOutputTime)); + } else { + return Pt_Time(); + } } - #endif // Automated Input Level Adjustment - Automatically tries to find an acceptable input volume @@ -2964,9 +3036,10 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, #ifdef EXPERIMENTAL_MIDI_OUT /* GSW: Save timeInfo in case MidiPlayback needs it */ gAudioIO->mAudioCallbackOutputTime = timeInfo->outputBufferDacTime; - gAudioIO->mAudioCallbackSampleNumber += framesPerBuffer; + gAudioIO->mAudioFramesPerBuffer = framesPerBuffer; if(gAudioIO->IsPaused()) gAudioIO->mNumPauseFrames += framesPerBuffer; + gAudioIO->mNumFrames += framesPerBuffer; #endif unsigned int i; @@ -3109,7 +3182,12 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, for( t = 0; t < numPlaybackTracks; t++ ) if( gAudioIO->mPlaybackTracks[t]->GetSolo() ) numSolo++; - +#ifdef EXPERIMENTAL_MIDI_OUT + int numMidiPlaybackTracks = gAudioIO->mMidiPlaybackTracks.GetCount(); + for( t = 0; t < numMidiPlaybackTracks; t++ ) + if( gAudioIO->mMidiPlaybackTracks[t]->GetSolo() ) + numSolo++; +#endif for( t = 0; t < numPlaybackTracks; t++) { WaveTrack *vt = gAudioIO->mPlaybackTracks[t]; @@ -3130,17 +3208,21 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, linkFlag = vt->GetLinked(); } + // This code was reorganized so that if all audio tracks + // are muted, we still return paComplete when the end of + // a selection is reached. + unsigned int len; if (cut) - { + { + len = (unsigned int) gAudioIO->mPlaybackBuffers[t]->Discard(framesPerBuffer); - continue; - } - - unsigned int len = (unsigned int) - gAudioIO->mPlaybackBuffers[t]->Get((samplePtr)tempFloats, - floatSample, - (int)framesPerBuffer); - + } else + { + len = (unsigned int) + gAudioIO->mPlaybackBuffers[t]->Get((samplePtr)tempFloats, + floatSample, + (int)framesPerBuffer); + } // If our buffer is empty and the time indicator is past // the end, then we've actually finished playing the entire // selection. @@ -3151,6 +3233,9 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, callbackReturn = paComplete; } + if (cut) // no samples to process, they've been discarded + continue; + if (vt->GetChannel() == Track::LeftChannel || vt->GetChannel() == Track::MonoChannel) { diff --git a/src/AudioIO.h b/src/AudioIO.h index 02b94a5b4..2c587d670 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -63,6 +63,7 @@ public: }; #define MAX_MIDI_BUFFER_SIZE 5000 +#define DEFAULT_SYNTH_LATENCY 5 #define DEFAULT_LATENCY_DURATION 100.0 #define DEFAULT_LATENCY_CORRECTION -130.0 @@ -101,7 +102,7 @@ class AUDACITY_DLL_API AudioIO { int StartStream(WaveTrackArray playbackTracks, WaveTrackArray captureTracks, #ifdef EXPERIMENTAL_MIDI_OUT - NoteTrackArray *midiTracks, + NoteTrackArray midiTracks, #endif TimeTrack *timeTrack, double sampleRate, double t0, double t1, @@ -136,9 +137,26 @@ class AUDACITY_DLL_API AudioIO { bool IsStreamActive(int token); #ifdef EXPERIMENTAL_MIDI_OUT - /** \brief Are any Note tracks playing MIDI? + /** \brief Compute the current PortMidi timestamp time. + * + * This is used by PortMidi to synchronize midi time to audio samples */ - bool IsMidiActive(); // + PmTimestamp MidiTime(); + + // Note: audio code solves the problem of soloing/muting tracks by scanning + // all playback tracks on every call to the audio buffer fill routine. + // We do the same for Midi, but it seems wasteful for at least two + // threads to be frequently polling to update status. This could be + // eliminated (also with a reduction in code I think) by updating mHasSolo + // each time a solo button is activated or deactivated. For now, I'm + // going to do this polling in the FillMidiBuffer routine to localize + // changes for midi to the midi code, but I'm declaring the variable + // here so possibly in the future, Audio code can use it too. -RBD + private: + bool mHasSolo; // is any playback solo button pressed? + public: + bool SetHasSolo(bool hasSolo); + bool GetHasSolo() { return mHasSolo; } #endif /** \brief Returns true if the stream is active, or even if audio I/O is @@ -324,11 +342,14 @@ private: void FillBuffers(); #ifdef EXPERIMENTAL_MIDI_OUT + void PrepareMidiIterator(bool send = true, double offset = 0); bool StartPortMidiStream(); void OutputEvent(); void FillMidiBuffers(); void GetNextEvent(); void AudacityMidiCallback(); + double AudioTime() { return mT0 + mNumFrames / mRate; } + double PauseTime(); double getCurrentTrackTime(); long calculateMidiTimeStamp(double time); #endif @@ -378,42 +399,34 @@ private: #ifdef EXPERIMENTAL_MIDI_OUT // MIDI_PLAYBACK: - PmStream *mMidiStream; - // PmEvent mMidiBuffer[MAX_MIDI_BUFFER_SIZE]; - // PmEvent mMidiQueue[MAX_MIDI_BUFFER_SIZE]; - PmError mLastPmError; - long mCurrentMidiTime; - long mMidiLatency; // latency value for PortMidi - long mLastMidiTime; // timestamp of last midi message - double mLastSystemTime; // last system time received - double mLatencyBetweenSystemTimes; - long mStartFrame; - long mNumPauseFrames; + PmStream *mMidiStream; + PmError mLastPmError; + long mMidiLatency; // latency value for PortMidi + long mSynthLatency; // latency of MIDI synthesizer - // TODO: Finish implementation of RequestMidiStop - bool mRequestMidiStop; + // These fields are used to synchronize MIDI with audio + volatile double mAudioCallbackOutputTime; // PortAudio's outTime + volatile long mNumFrames; // includes pauses + volatile long mNumPauseFrames; // how many frames of zeros inserted? + volatile long mPauseTime; // pause in ms if no audio playback + volatile double mMidiLoopOffset; // total of backward jumps + volatile long mAudioFramesPerBuffer; - // These two fields are used to synchronize MIDI with audio - // TODO: Finish implementing these fields - double mAudioCallbackOutputTime; - long mAudioCallbackSampleNumber; - - Alg_seq_ptr mSeq; - Alg_iterator_ptr mIterator; - Alg_event_ptr mNextEvent; // the next event to play (or null) - double mNextEventTime; // the time of the next event + Alg_seq_ptr mSeq; + Alg_iterator_ptr mIterator; + Alg_event_ptr mNextEvent; // the next event to play (or null) + double mNextEventTime; // the time of the next event // (note that this could be a note's time+duration) - bool mNextIsNoteOn; // is the next event a note-off? - int mVC; // Visible Channel mask + NoteTrack *mNextEventTrack; // track of next event + bool mMidiOutputComplete; // true when output reaches mT1 + bool mNextIsNoteOn; // is the next event a note-off? // int mCnt; - long mMidiWait; - bool mMidiStreamActive; + // mMidiStreamActive tells when mMidiStream is open for output + bool mMidiStreamActive; // when true, mSendMidiState means send only updates, not note-on's, // used to send state changes that precede the selected notes - bool mSendMidiState; - NoteTrackArray *mMidiPlaybackTracks; - // NoteTrackArray mMidiCaptureTracks; - + bool mSendMidiState; + NoteTrackArray mMidiPlaybackTracks; #endif #ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT @@ -434,6 +447,9 @@ private: #endif AudioThread *mThread; +#ifdef EXPERIMENTAL_MIDI_OUT + AudioThread *mMidiThread; +#endif Resample **mResample; RingBuffer **mCaptureBuffers; WaveTrackArray mCaptureTracks; @@ -470,10 +486,10 @@ private: volatile bool mAudioThreadFillBuffersLoopRunning; volatile bool mAudioThreadFillBuffersLoopActive; -/* REQUIRES PORTMIDI */ -// volatile bool mMidiThreadShouldCallFillBuffersOnce; -// volatile bool mMidiThreadFillBuffersLoopRunning; -// volatile bool mMidiThreadFillBuffersLoopActive; +#ifdef EXPERIMENTAL_MIDI_OUT + volatile bool mMidiThreadFillBuffersLoopRunning; + volatile bool mMidiThreadFillBuffersLoopActive; +#endif volatile double mLastRecordingOffset; PaError mLastPaError; @@ -509,6 +525,7 @@ private: AudioIOListener* mListener; friend class AudioThread; + friend class MidiThread; friend void InitAudioIO(); friend void DeinitAudioIO(); diff --git a/src/Experimental.h b/src/Experimental.h index 5d602e05f..7b62ddf0a 100644 --- a/src/Experimental.h +++ b/src/Experimental.h @@ -93,12 +93,12 @@ // Enables MIDI Output of NoteTrack (MIDI) data during playback // USE_MIDI must be defined in order for EXPERIMENTAL_MIDI_OUT to work #ifdef USE_MIDI - //#define EXPERIMENTAL_MIDI_OUT +#define EXPERIMENTAL_MIDI_OUT #endif // USE_MIDI must be defined in order for EXPERIMENTAL_SCOREALIGN to work #ifdef USE_MIDI - //#define EXPERIMENTAL_SCOREALIGN +#define EXPERIMENTAL_SCOREALIGN #endif // experimental features diff --git a/src/Menus.cpp b/src/Menus.cpp index 9249cf154..b17aec393 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -111,6 +111,7 @@ simplifies construction of menu items. #include "CaptureEvents.h" #ifdef EXPERIMENTAL_SCOREALIGN +#include "effects/ScoreAlignDialog.h" #include "audioreader.h" #include "scorealign.h" #include "scorealign-glue.h" @@ -3652,9 +3653,23 @@ void AudacityProject::OnTrim() Track *n = iter.First(); while (n) { - if ((n->GetKind() == Track::Wave) && n->GetSelected()) { - //Delete the section before the left selector - ((WaveTrack*)n)->Trim(mViewInfo.sel0, mViewInfo.sel1); + if (n->GetSelected()) { + switch (n->GetKind()) + { +#if defined(USE_MIDI) + case Track::Note: + ((NoteTrack*)n)->Trim(mViewInfo.sel0, mViewInfo.sel1); + break; +#endif + + case Track::Wave: + //Delete the section before the left selector + ((WaveTrack*)n)->Trim(mViewInfo.sel0, mViewInfo.sel1); + break; + + default: + break; + } } n = iter.Next(); } @@ -4933,6 +4948,143 @@ void AudacityProject::OnAlignMoveSel(int index) } #ifdef EXPERIMENTAL_SCOREALIGN +// rough relative amount of time to compute one +// frame of audio or midi, or one cell of matrix, or one iteration +// of smoothing, measured on a 1.9GHz Core 2 Duo in 32-bit mode +// (see COLLECT_TIMING_DATA below) +#define AUDIO_WORK_UNIT 0.004F +#define MIDI_WORK_UNIT 0.0001F +#define MATRIX_WORK_UNIT 0.000002F +#define SMOOTHING_WORK_UNIT 0.000001F + +// Write timing data to a file; useful for calibrating AUDIO_WORK_UNIT, +// MIDI_WORK_UNIT, MATRIX_WORK_UNIT, and SMOOTHING_WORK_UNIT coefficients +// Data is written to timing-data.txt; look in +// audacity-src/win/Release/modules/ +#define COLLECT_TIMING_DATA + +// Audacity Score Align Progress class -- progress reports come here +class ASAProgress : public SAProgress { + private: + float mTotalWork; + float mFrames[2]; + long mTotalCells; // how many matrix cells? + long mCellCount; // how many cells so far? + long mPrevCellCount; // cell_count last reported with Update() + ProgressDialog *mProgress; + #ifdef COLLECT_TIMING_DATA + FILE *mTimeFile; + wxDateTime mStartTime; + long iterations; + #endif + + public: + ASAProgress() { + smoothing = false; + mProgress = NULL; + #ifdef COLLECT_TIMING_DATA + mTimeFile = fopen("timing-data.txt", "w"); + #endif + } + ~ASAProgress() { + delete mProgress; + #ifdef COLLECT_TIMING_DATA + fclose(mTimeFile); + #endif + } + virtual void set_phase(int i) { + float work[2]; // chromagram computation work estimates + float work2, work3 = 0; // matrix and smoothing work estimates + SAProgress::set_phase(i); + #ifdef COLLECT_TIMING_DATA + long ms = 0; + wxDateTime now = wxDateTime::UNow(); + fprintf(mTimeFile, "Phase %d begins at %s\n", + i, now.FormatTime().c_str()); + if (i != 0) + ms = now.Subtract(mStartTime).GetMilliseconds().ToLong(); + mStartTime = now; + #endif + if (i == 0) { + mCellCount = 0; + for (int j = 0; j < 2; j++) { + mFrames[j] = durations[j] / frame_period; + } + mTotalWork = 0; + for (int j = 0; j < 2; j++) { + work[j] = + (is_audio[j] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[j]; + mTotalWork += work[j]; + } + mTotalCells = mFrames[0] * mFrames[1]; + work2 = mTotalCells * MATRIX_WORK_UNIT; + mTotalWork += work2; + // arbitarily assume 60 iterations to fit smooth segments and + // per frame per iteration is SMOOTHING_WORK_UNIT + if (smoothing) { + work3 = + wxMax(mFrames[0], mFrames[1]) * SMOOTHING_WORK_UNIT * 40; + mTotalWork += work3; + } + #ifdef COLLECT_TIMING_DATA + fprintf(mTimeFile, " mTotalWork (an estimate) = %g\n", mTotalWork); + fprintf(mTimeFile, " work0 = %g, frames %g, is_audio %d\n", + work[0], mFrames[0], is_audio[0]); + fprintf(mTimeFile, " work1 = %g, frames %g, is_audio %d\n", + work[1], mFrames[1], is_audio[1]); + fprintf(mTimeFile, "work2 = %g, work3 = %g\n", work2, work3); + #endif + mProgress = new ProgressDialog(_("Synchronize MIDI with Audio"), + _("Synchronizing MIDI and Audio Tracks")); + } else if (i < 3) { + fprintf(mTimeFile, + "Phase %d took %d ms for %g frames, coefficient = %g s/frame\n", + i - 1, ms, mFrames[i - 1], (ms * 0.001) / mFrames[i - 1]); + } else if (i == 3) { + fprintf(mTimeFile, + "Phase 2 took %d ms for %d cells, coefficient = %g s/cell\n", + ms, mCellCount, (ms * 0.001) / mCellCount); + } else if (i == 4) { + fprintf(mTimeFile, "Phase 3 took %d ms for %d iterations on %g frames, coefficient = %g s per frame per iteration\n", + ms, iterations, wxMax(mFrames[0], mFrames[1]), + (ms * 0.001) / (wxMax(mFrames[0], mFrames[1]) * iterations)); + } + } + virtual bool set_feature_progress(float s) { + float work; + if (phase == 0) { + float f = s / frame_period; + work = (is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * f; + } else if (phase == 1) { + float f = s / frame_period; + work = (is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[0] + + (is_audio[1] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * f; + } + int updateResult = mProgress->Update(int(work), int(mTotalWork)); + return (updateResult == eProgressSuccess); + } + virtual bool set_matrix_progress(int cells) { + mCellCount += cells; + float work = + (is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[0] + + (is_audio[1] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[1]; + work += mCellCount * MATRIX_WORK_UNIT; + int updateResult = mProgress->Update(int(work), int(mTotalWork)); + return (updateResult == eProgressSuccess); + } + virtual bool set_smoothing_progress(int i) { + iterations = i; + float work = + (is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[0] + + (is_audio[1] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[1] + + MATRIX_WORK_UNIT * mFrames[0] * mFrames[1]; + work += i * wxMax(mFrames[0], mFrames[1]) * SMOOTHING_WORK_UNIT; + int updateResult = mProgress->Update(int(work), int(mTotalWork)); + return (updateResult == eProgressSuccess); + } +}; + + long mixer_process(void *mixer, float **buffer, long n) { Mixer *mix = (Mixer *) mixer; @@ -4974,6 +5126,27 @@ void AudacityProject::OnScoreAlign() return; } + // Creating the dialog also stores dialog into gScoreAlignDialog so + // that it can be delted by CloseScoreAlignDialog() either here or + // if the program is quit by the user while the dialog is up. + ScoreAlignParams params; + ScoreAlignDialog *dlog = new ScoreAlignDialog(NULL, params); + CloseScoreAlignDialog(); + + if (params.mStatus != wxID_OK) return; + + // We're going to do it. + PushState(_("Sync MIDI with Audio"), _("Sync MIDI with Audio")); + // Remove offset from NoteTrack because audio is + // mixed starting at zero and incorporating clip offsets. + if (nt->GetOffset() < 0) { + // remove the negative offset data before alignment + nt->Clear(nt->GetOffset(), 0); + } else if (nt->GetOffset() > 0) { + nt->Shift(nt->GetOffset()); + } + nt->SetOffset(0); + WaveTrack **waveTracks; mTracks->GetWaveTracks(true /* selectionOnly */, &numWaveTracksSelected, &waveTracks); @@ -4992,24 +5165,48 @@ void AudacityProject::OnScoreAlign() NULL); // MixerSpec *mixerSpec = NULL delete [] waveTracks; - // debugging/testing - //float *buffer; - //long buffer_len = mixer_process((void *) mix, &buffer, 4096); - //while (buffer_len) - // buffer_len = mixer_process((void *) mix, &buffer, 4096); - - scorealign((void *) mix, &mixer_process, - 2 /* channels */, 44100.0 /* srate */, endTime, nt->GetSequence()); + ASAProgress *progress = new ASAProgress; + // There's a lot of adjusting made to incorporate the note track offset into + // the note track while preserving the position of notes within beats and + // measures. For debugging, you can see just the pre-scorealign note track + // manipulation by setting SKIP_ACTUAL_SCORE_ALIGNMENT. You could then, for + // example, save the modified note track in ".gro" form to read the details. + //#define SKIP_ACTUAL_SCORE_ALIGNMENT 1 +#ifndef SKIP_ACTUAL_SCORE_ALIGNMENT + int result = scorealign((void *) mix, &mixer_process, + 2 /* channels */, 44100.0 /* srate */, endTime, + nt->GetSequence(), progress, params); +#else + int result = SA_SUCCESS; +#endif + + delete progress; delete mix; - PushState(_("Sync MIDI with Audio"), _("Sync MIDI with Audio")); - - RedrawProject(); - - wxMessageBox(_("Alignment completed.")); + if (result == SA_SUCCESS) { + + RedrawProject(); + wxMessageBox(wxString::Format( + _("Alignment completed: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."), + params.mMidiStart, params.mMidiEnd, + params.mAudioStart, params.mAudioEnd)); + } else if (result == SA_TOOSHORT) { + wxMessageBox(wxString::Format( + _("Alignment error: input too short: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."), + params.mMidiStart, params.mMidiEnd, + params.mAudioStart, params.mAudioEnd)); + } else if (result == SA_CANCEL) { + GetActiveProject()->OnUndo(); // recover any changes to note track + return; // no message when user cancels alignment + } else { + GetActiveProject()->OnUndo(); // recover any changes to note track + wxMessageBox(_("Internal error reported by alignment process.")); + } } #endif /* EXPERIMENTAL_SCOREALIGN */ + + void AudacityProject::OnNewWaveTrack() { WaveTrack *t = mTrackFactory->NewWaveTrack(mDefaultFormat, mRate); diff --git a/src/MixerBoard.cpp b/src/MixerBoard.cpp index 277fc8179..27bd850da 100644 --- a/src/MixerBoard.cpp +++ b/src/MixerBoard.cpp @@ -9,6 +9,8 @@ **********************************************************************/ +#include "Audacity.h" + #include "Experimental.h" #include @@ -20,6 +22,9 @@ #include "AColor.h" #include "MixerBoard.h" #include "Project.h" +#ifdef USE_MIDI +#include "NoteTrack.h" +#endif #include "../images/MusicalInstruments.h" @@ -113,7 +118,18 @@ MixerTrackCluster::MixerTrackCluster(wxWindow* parent, { mMixerBoard = grandParent; mProject = project; - mLeftTrack = pLeftTrack; +#ifdef USE_MIDI + if (pLeftTrack->GetKind() == Track::Note) { + mLeftTrack = NULL; + mNoteTrack = (NoteTrack*) pLeftTrack; + mTrack = pLeftTrack; + } else { + mTrack = mLeftTrack = pLeftTrack; + mNoteTrack = NULL; + } +#else + mTrack = mLeftTrack = pLeftTrack; +#endif mRightTrack = pRightTrack; this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); @@ -126,8 +142,9 @@ MixerTrackCluster::MixerTrackCluster(wxWindow* parent, // track name wxPoint ctrlPos(kDoubleInset, kDoubleInset); wxSize ctrlSize(size.GetWidth() - kQuadrupleInset, TRACK_NAME_HEIGHT); + wxString name = mTrack->GetName(); mStaticText_TrackName = - new wxStaticText(this, -1, mLeftTrack->GetName(), ctrlPos, ctrlSize, + new wxStaticText(this, -1, name, ctrlPos, ctrlSize, wxALIGN_CENTRE | wxST_NO_AUTORESIZE | wxSUNKEN_BORDER); //v Useful when different tracks are different colors, but not now. // mStaticText_TrackName->SetBackgroundColour(this->GetTrackColor()); @@ -139,6 +156,17 @@ MixerTrackCluster::MixerTrackCluster(wxWindow* parent, const int nGainSliderHeight = size.GetHeight() - ctrlPos.y - kQuadrupleInset; ctrlSize.Set(kLeftSideStackWidth - kQuadrupleInset, nGainSliderHeight); +#ifdef USE_MIDI + if (mNoteTrack) { + mSlider_Gain = + new MixerTrackSlider( + this, ID_SLIDER_GAIN, + /* i18n-hint: Title of the Gain slider, used to adjust the volume */ + _("Velocity"), + ctrlPos, ctrlSize, VEL_SLIDER, true, + true, 0.0, wxVERTICAL); + } else +#endif mSlider_Gain = new MixerTrackSlider( this, ID_SLIDER_GAIN, @@ -154,7 +182,7 @@ MixerTrackCluster::MixerTrackCluster(wxWindow* parent, // musical instrument image ctrlPos.x += kLeftSideStackWidth + kInset; // + kInset to center it in right side stack ctrlSize.Set(MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH, MUSICAL_INSTRUMENT_HEIGHT_AND_WIDTH); - wxBitmap* bitmap = mMixerBoard->GetMusicalInstrumentBitmap(mLeftTrack); + wxBitmap* bitmap = mMixerBoard->GetMusicalInstrumentBitmap(name); wxASSERT(bitmap); mBitmapButton_MusicalInstrument = new wxBitmapButton(this, ID_BITMAPBUTTON_MUSICAL_INSTRUMENT, *bitmap, @@ -181,7 +209,6 @@ MixerTrackCluster::MixerTrackCluster(wxWindow* parent, this->UpdatePan(); - // mute/solo buttons stacked below Pan slider ctrlPos.y += PAN_HEIGHT + kDoubleInset; ctrlSize.Set(mMixerBoard->mMuteSoloWidth, MUTE_SOLO_HEIGHT); @@ -223,7 +250,7 @@ MixerTrackCluster::MixerTrackCluster(wxWindow* parent, Meter::MixerTrackCluster); // Style style = HorizontalStereo, #if wxUSE_TOOLTIPS - mStaticText_TrackName->SetToolTip(mLeftTrack->GetName()); + mStaticText_TrackName->SetToolTip(name); mToggleButton_Mute->SetToolTip(_("Mute")); mToggleButton_Solo->SetToolTip(_("Solo")); mMeter->SetToolTip(_("Signal Level Meter")); @@ -274,12 +301,17 @@ void MixerTrackCluster::HandleResize() // For wxSizeEvents, update gain slider a void MixerTrackCluster::HandleSliderGain(const bool bWantPushState /*= false*/) { float fValue = mSlider_Gain->Get(); - mLeftTrack->SetGain(fValue); + if (mLeftTrack) + mLeftTrack->SetGain(fValue); +#ifdef EXPERIMENTAL_MIDI_OUT + else + mNoteTrack->SetGain(fValue); +#endif if (mRightTrack) mRightTrack->SetGain(fValue); // Update the TrackPanel correspondingly. - mProject->RefreshTPTrack(mLeftTrack); + mProject->RefreshTPTrack(mTrack); if (bWantPushState) mProject->TP_PushState(_("Moved gain slider"), _("Gain"), true /* consolidate */); @@ -288,12 +320,13 @@ void MixerTrackCluster::HandleSliderGain(const bool bWantPushState /*= false*/) void MixerTrackCluster::HandleSliderPan(const bool bWantPushState /*= false*/) { float fValue = mSlider_Pan->Get(); - mLeftTrack->SetPan(fValue); + if (mLeftTrack) // test in case track is a NoteTrack + mLeftTrack->SetPan(fValue); if (mRightTrack) mRightTrack->SetPan(fValue); // Update the TrackPanel correspondingly. - mProject->RefreshTPTrack(mLeftTrack); + mProject->RefreshTPTrack(mTrack); if (bWantPushState) mProject->TP_PushState(_("Moved pan slider"), _("Pan"), true /* consolidate */); @@ -301,7 +334,8 @@ void MixerTrackCluster::HandleSliderPan(const bool bWantPushState /*= false*/) void MixerTrackCluster::ResetMeter() { - mMeter->Reset(mLeftTrack->GetRate(), true); + if (mLeftTrack) + mMeter->Reset(mLeftTrack->GetRate(), true); } @@ -317,19 +351,19 @@ void MixerTrackCluster::UpdateForStateChange() void MixerTrackCluster::UpdateName() { - const wxString newName = mLeftTrack->GetName(); + wxString newName = mTrack->GetName(); mStaticText_TrackName->SetLabel(newName); #if wxUSE_TOOLTIPS mStaticText_TrackName->SetToolTip(newName); #endif mBitmapButton_MusicalInstrument->SetBitmapLabel( - *(mMixerBoard->GetMusicalInstrumentBitmap(mLeftTrack))); + *(mMixerBoard->GetMusicalInstrumentBitmap(newName))); } void MixerTrackCluster::UpdateMute() { - mToggleButton_Mute->SetAlternate(mLeftTrack->GetSolo()); - if (mLeftTrack->GetMute()) + mToggleButton_Mute->SetAlternate(mTrack->GetSolo()); + if (mTrack->GetMute()) mToggleButton_Mute->PushDown(); else mToggleButton_Mute->PopUp(); @@ -337,7 +371,7 @@ void MixerTrackCluster::UpdateMute() void MixerTrackCluster::UpdateSolo() { - bool bIsSolo = mLeftTrack->GetSolo(); + bool bIsSolo = mTrack->GetSolo(); if (bIsSolo) mToggleButton_Solo->PushDown(); else @@ -347,18 +381,32 @@ void MixerTrackCluster::UpdateSolo() void MixerTrackCluster::UpdatePan() { +#ifdef USE_MIDI + if (mNoteTrack) { + mSlider_Pan->Hide(); + return; + } +#endif mSlider_Pan->Set(mLeftTrack->GetPan()); } void MixerTrackCluster::UpdateGain() { +#ifdef EXPERIMENTAL_MIDI_OUT + if (mNoteTrack) { + mSlider_Gain->SetStyle(VEL_SLIDER); + mSlider_Gain->Set(mNoteTrack->GetGain()); + return; + } + mSlider_Gain->SetStyle(DB_SLIDER); +#endif mSlider_Gain->Set(mLeftTrack->GetGain()); } void MixerTrackCluster::UpdateMeter(const double t0, const double t1) { if ((t0 < 0.0) || (t1 < 0.0) || (t0 >= t1) || // bad time value or nothing to show - ((mMixerBoard->HasSolo() || mLeftTrack->GetMute()) && !mLeftTrack->GetSolo())) + ((mMixerBoard->HasSolo() || mTrack->GetMute()) && !mTrack->GetSolo())) { this->ResetMeter(); return; @@ -371,7 +419,7 @@ void MixerTrackCluster::UpdateMeter(const double t0, const double t1) float* maxRight = new float[nFramesPerBuffer]; float* rmsRight = new float[nFramesPerBuffer]; - bool bSuccess = true; + bool bSuccess = (mLeftTrack != NULL); const double dFrameInterval = (t1 - t0) / (double)nFramesPerBuffer; double dFrameT0 = t0; double dFrameT1 = t0 + dFrameInterval; @@ -449,28 +497,28 @@ void MixerTrackCluster::HandleSelect(const bool bShiftDown) if (bShiftDown) { // ShiftDown => Just toggle selection on this track. - bool bSelect = !mLeftTrack->GetSelected(); - mLeftTrack->SetSelected(bSelect); + bool bSelect = !mTrack->GetSelected(); + mTrack->SetSelected(bSelect); if (mRightTrack) mRightTrack->SetSelected(bSelect); // Refresh only this MixerTrackCluster and WaveTrack in TrackPanel. this->Refresh(true); - mProject->RefreshTPTrack(mLeftTrack); + mProject->RefreshTPTrack(mTrack); } else { // exclusive select mProject->SelectNone(); - mLeftTrack->SetSelected(true); + mTrack->SetSelected(true); if (mRightTrack) mRightTrack->SetSelected(true); if (mProject->GetSel0() >= mProject->GetSel1()) { // No range previously selected, so use the range of this track. - mProject->mViewInfo.sel0 = mLeftTrack->GetOffset(); - mProject->mViewInfo.sel1 = mLeftTrack->GetEndTime(); + mProject->mViewInfo.sel0 = mTrack->GetOffset(); + mProject->mViewInfo.sel1 = mTrack->GetEndTime(); } // Exclusive select, so refresh all MixerTrackClusters. @@ -506,7 +554,8 @@ void MixerTrackCluster::OnPaint(wxPaintEvent &evt) wxSize clusterSize = this->GetSize(); wxRect bev(0, 0, clusterSize.GetWidth() - 1, clusterSize.GetHeight() - 1); - if (mLeftTrack->GetSelected()) + + if (mTrack->GetSelected()) { for (unsigned int i = 0; i < 4; i++) // 4 gives a big bevel, but there were complaints about visibility otherwise. { @@ -552,8 +601,8 @@ void MixerTrackCluster::OnSlider_Pan(wxCommandEvent& event) void MixerTrackCluster::OnButton_Mute(wxCommandEvent& event) { - mProject->HandleTrackMute(mLeftTrack, mToggleButton_Mute->WasShiftDown()); - mToggleButton_Mute->SetAlternate(mLeftTrack->GetSolo()); + mProject->HandleTrackMute(mTrack, mToggleButton_Mute->WasShiftDown()); + mToggleButton_Mute->SetAlternate(mTrack->GetSolo()); // Update the TrackPanel correspondingly. if (mProject->IsSoloSimple()) @@ -564,14 +613,14 @@ void MixerTrackCluster::OnButton_Mute(wxCommandEvent& event) } else // Update only the changed track. - mProject->RefreshTPTrack(mLeftTrack); + mProject->RefreshTPTrack(mTrack); } void MixerTrackCluster::OnButton_Solo(wxCommandEvent& event) { - mProject->HandleTrackSolo(mLeftTrack, mToggleButton_Solo->WasShiftDown()); + mProject->HandleTrackSolo(mTrack, mToggleButton_Solo->WasShiftDown()); - bool bIsSolo = mLeftTrack->GetSolo(); + bool bIsSolo = mTrack->GetSolo(); mToggleButton_Mute->SetAlternate(bIsSolo); // Update the TrackPanel correspondingly. @@ -584,7 +633,7 @@ void MixerTrackCluster::OnButton_Solo(wxCommandEvent& event) } else // Update only the changed track. - mProject->RefreshTPTrack(mLeftTrack); + mProject->RefreshTPTrack(mTrack); } @@ -742,6 +791,12 @@ MixerBoard::~MixerBoard() mMusicalInstruments.Clear(); } +// Reassign mixer input strips (MixerTrackClusters) to Track Clusters +// both have the same order. If USE_MIDI, then Note Tracks appear in the +// mixer, and we must be able to convert and reuse a MixerTrackCluster +// from audio to midi or midi to audio. This task is handled by +// UpdateForStateChange(). +// void MixerBoard::UpdateTrackClusters() { if (mImageMuteUp == NULL) @@ -759,7 +814,11 @@ void MixerBoard::UpdateTrackClusters() while (pLeftTrack) { pRightTrack = pLeftTrack->GetLinked() ? iterTracks.Next() : NULL; - if (pLeftTrack->GetKind() == Track::Wave) + if (pLeftTrack->GetKind() == Track::Wave +#ifdef USE_MIDI + || pLeftTrack->GetKind() == Track::Note +#endif + ) { if (nClusterIndex < nClusterCount) { @@ -767,7 +826,17 @@ void MixerBoard::UpdateTrackClusters() // Track clusters are maintained in the same order as the WaveTracks. // Track pointers can change for the "same" track for different states // on the undo stack, so update the pointers and display name. +#ifdef USE_MIDI + if (pLeftTrack->GetKind() == Track::Note) { + mMixerTrackClusters[nClusterIndex]->mNoteTrack = (NoteTrack*)pLeftTrack; + mMixerTrackClusters[nClusterIndex]->mLeftTrack = NULL; + } else { + mMixerTrackClusters[nClusterIndex]->mNoteTrack = NULL; + mMixerTrackClusters[nClusterIndex]->mLeftTrack = (WaveTrack*)pLeftTrack; + } +#else mMixerTrackClusters[nClusterIndex]->mLeftTrack = (WaveTrack*)pLeftTrack; +#endif mMixerTrackClusters[nClusterIndex]->mRightTrack = (WaveTrack*)pRightTrack; mMixerTrackClusters[nClusterIndex]->UpdateForStateChange(); } @@ -806,7 +875,7 @@ void MixerBoard::UpdateTrackClusters() // that don't call RemoveTrackCluster explicitly. // We've already updated the track pointers for the clusters to the left, so just remove these. for (; nClusterIndex < nClusterCount; nClusterIndex++) - this->RemoveTrackCluster(mMixerTrackClusters[nClusterIndex]->mLeftTrack); + this->RemoveTrackCluster(mMixerTrackClusters[nClusterIndex]->mTrack); } } @@ -819,11 +888,11 @@ int MixerBoard::GetTrackClustersWidth() kDoubleInset; // plus final right margin } -void MixerBoard::MoveTrackCluster(const WaveTrack* pLeftTrack, +void MixerBoard::MoveTrackCluster(const Track* pTrack, bool bUp) // Up in TrackPanel is left in MixerBoard. { MixerTrackCluster* pMixerTrackCluster; - int nIndex = this->FindMixerTrackCluster(pLeftTrack, &pMixerTrackCluster); + int nIndex = FindMixerTrackCluster(pTrack, &pMixerTrackCluster); if (pMixerTrackCluster == NULL) return; // Couldn't find it. @@ -854,11 +923,11 @@ void MixerBoard::MoveTrackCluster(const WaveTrack* pLeftTrack, } } -void MixerBoard::RemoveTrackCluster(const WaveTrack* pLeftTrack) +void MixerBoard::RemoveTrackCluster(const Track* pTrack) { // Find and destroy. MixerTrackCluster* pMixerTrackCluster; - int nIndex = this->FindMixerTrackCluster(pLeftTrack, &pMixerTrackCluster); + int nIndex = FindMixerTrackCluster(pTrack, &pMixerTrackCluster); if (pMixerTrackCluster == NULL) return; // Couldn't find it. @@ -879,17 +948,22 @@ void MixerBoard::RemoveTrackCluster(const WaveTrack* pLeftTrack) } this->UpdateWidth(); + + // Sanity check: if there is still a MixerTrackCluster with pTrack, then + // we deleted the first but should have deleted the last: + FindMixerTrackCluster(pTrack, &pMixerTrackCluster); + assert(pMixerTrackCluster == NULL); } -wxBitmap* MixerBoard::GetMusicalInstrumentBitmap(const WaveTrack* pLeftTrack) +wxBitmap* MixerBoard::GetMusicalInstrumentBitmap(wxString name) { if (mMusicalInstruments.IsEmpty()) return NULL; // random choice: return mMusicalInstruments[(int)pLeftTrack % mMusicalInstruments.GetCount()].mBitmap; - const wxString strTrackName(pLeftTrack->GetName().MakeLower()); + const wxString strTrackName(name.MakeLower()); size_t nBestItemIndex = 0; unsigned int nBestScore = 0; unsigned int nInstrIndex = 0; @@ -936,10 +1010,10 @@ bool MixerBoard::HasSolo() return false; } -void MixerBoard::RefreshTrackCluster(const WaveTrack* pLeftTrack, bool bEraseBackground /*= true*/) +void MixerBoard::RefreshTrackCluster(const Track* pTrack, bool bEraseBackground /*= true*/) { MixerTrackCluster* pMixerTrackCluster; - this->FindMixerTrackCluster(pLeftTrack, &pMixerTrackCluster); + FindMixerTrackCluster(pTrack, &pMixerTrackCluster); if (pMixerTrackCluster) pMixerTrackCluster->Refresh(bEraseBackground); } @@ -967,17 +1041,17 @@ void MixerBoard::ResetMeters() mMixerTrackClusters[i]->ResetMeter(); } -void MixerBoard::UpdateName(const WaveTrack* pLeftTrack) +void MixerBoard::UpdateName(const Track* pTrack) { MixerTrackCluster* pMixerTrackCluster; - this->FindMixerTrackCluster(pLeftTrack, &pMixerTrackCluster); + FindMixerTrackCluster(pTrack, &pMixerTrackCluster); if (pMixerTrackCluster) pMixerTrackCluster->UpdateName(); } -void MixerBoard::UpdateMute(const WaveTrack* pLeftTrack /*= NULL*/) // NULL means update for all tracks. +void MixerBoard::UpdateMute(const Track* pTrack /*= NULL*/) // NULL means update for all tracks. { - if (pLeftTrack == NULL) + if (pTrack == NULL) { for (unsigned int i = 0; i < mMixerTrackClusters.GetCount(); i++) mMixerTrackClusters[i]->UpdateMute(); @@ -985,15 +1059,15 @@ void MixerBoard::UpdateMute(const WaveTrack* pLeftTrack /*= NULL*/) // NULL mean else { MixerTrackCluster* pMixerTrackCluster; - this->FindMixerTrackCluster(pLeftTrack, &pMixerTrackCluster); + FindMixerTrackCluster(pTrack, &pMixerTrackCluster); if (pMixerTrackCluster) pMixerTrackCluster->UpdateMute(); } } -void MixerBoard::UpdateSolo(const WaveTrack* pLeftTrack /*= NULL*/) // NULL means update for all tracks. +void MixerBoard::UpdateSolo(const Track* pTrack /*= NULL*/) // NULL means update for all tracks. { - if (pLeftTrack == NULL) + if (pTrack == NULL) { for (unsigned int i = 0; i < mMixerTrackClusters.GetCount(); i++) mMixerTrackClusters[i]->UpdateSolo(); @@ -1001,24 +1075,24 @@ void MixerBoard::UpdateSolo(const WaveTrack* pLeftTrack /*= NULL*/) // NULL mean else { MixerTrackCluster* pMixerTrackCluster; - this->FindMixerTrackCluster(pLeftTrack, &pMixerTrackCluster); + FindMixerTrackCluster(pTrack, &pMixerTrackCluster); if (pMixerTrackCluster) pMixerTrackCluster->UpdateSolo(); } } -void MixerBoard::UpdatePan(const WaveTrack* pLeftTrack) +void MixerBoard::UpdatePan(const Track* pTrack) { MixerTrackCluster* pMixerTrackCluster; - this->FindMixerTrackCluster(pLeftTrack, &pMixerTrackCluster); + FindMixerTrackCluster(pTrack, &pMixerTrackCluster); if (pMixerTrackCluster) pMixerTrackCluster->UpdatePan(); } -void MixerBoard::UpdateGain(const WaveTrack* pLeftTrack) +void MixerBoard::UpdateGain(const Track* pTrack) { MixerTrackCluster* pMixerTrackCluster; - this->FindMixerTrackCluster(pLeftTrack, &pMixerTrackCluster); + FindMixerTrackCluster(pTrack, &pMixerTrackCluster); if (pMixerTrackCluster) pMixerTrackCluster->UpdateGain(); } @@ -1135,13 +1209,13 @@ void MixerBoard::CreateMuteSoloImages() mImageSoloDisabled = new wxImage(mMuteSoloWidth, MUTE_SOLO_HEIGHT); // Leave empty because unused. } -int MixerBoard::FindMixerTrackCluster(const WaveTrack* pLeftTrack, +int MixerBoard::FindMixerTrackCluster(const Track* pTrack, MixerTrackCluster** hMixerTrackCluster) const { *hMixerTrackCluster = NULL; for (unsigned int i = 0; i < mMixerTrackClusters.GetCount(); i++) { - if (mMixerTrackClusters[i]->mLeftTrack == pLeftTrack) + if (mMixerTrackClusters[i]->mTrack == pTrack) { *hMixerTrackCluster = mMixerTrackClusters[i]; return i; diff --git a/src/MixerBoard.h b/src/MixerBoard.h index 1e8bb19c3..3e3307696 100644 --- a/src/MixerBoard.h +++ b/src/MixerBoard.h @@ -59,7 +59,11 @@ public: class AudacityProject; class MixerBoard; +class Track; class WaveTrack; +#ifdef USE_MIDI +class NoteTrack; +#endif class MixerTrackCluster : public wxPanel { @@ -105,8 +109,14 @@ private: //v void OnSliderScroll_Gain(wxScrollEvent& event); public: - WaveTrack* mLeftTrack; + // mTrack is redundant, but simplifies code that operates on either + // mLeftTrack or mNoteTrack. + Track* mTrack; // either mLeftTrack or mNoteTrack, whichever is not NULL + WaveTrack* mLeftTrack; // NULL if Note Track WaveTrack* mRightTrack; // NULL if mono +#ifdef USE_MIDI + NoteTrack* mNoteTrack; // NULL if Wave Track +#endif private: MixerBoard* mMixerBoard; @@ -185,25 +195,25 @@ public: void UpdateTrackClusters(); int GetTrackClustersWidth(); - void MoveTrackCluster(const WaveTrack* pLeftTrack, bool bUp); // Up in TrackPanel is left in MixerBoard. - void RemoveTrackCluster(const WaveTrack* pLeftTrack); + void MoveTrackCluster(const Track* pTrack, bool bUp); // Up in TrackPanel is left in MixerBoard. + void RemoveTrackCluster(const Track* pTrack); - wxBitmap* GetMusicalInstrumentBitmap(const WaveTrack* pLeftTrack); + wxBitmap* GetMusicalInstrumentBitmap(const wxString name); bool HasSolo(); - void RefreshTrackCluster(const WaveTrack* pLeftTrack, bool bEraseBackground = true); + void RefreshTrackCluster(const Track* pTrack, bool bEraseBackground = true); void RefreshTrackClusters(bool bEraseBackground = true); void ResizeTrackClusters(); void ResetMeters(); - void UpdateName(const WaveTrack* pLeftTrack); - void UpdateMute(const WaveTrack* pLeftTrack = NULL); // NULL means update for all tracks. - void UpdateSolo(const WaveTrack* pLeftTrack = NULL); // NULL means update for all tracks. - void UpdatePan(const WaveTrack* pLeftTrack); - void UpdateGain(const WaveTrack* pLeftTrack); + void UpdateName(const Track* pTrack); + void UpdateMute(const Track* pTrack = NULL); // NULL means update for all tracks. + void UpdateSolo(const Track* pTrack = NULL); // NULL means update for all tracks. + void UpdatePan(const Track* pTrack); + void UpdateGain(const Track* pTrack); void UpdateMeters(const double t1, const bool bLoopedPlay); @@ -211,7 +221,7 @@ public: private: void CreateMuteSoloImages(); - int FindMixerTrackCluster(const WaveTrack* pLeftTrack, + int FindMixerTrackCluster(const Track* pTrack, MixerTrackCluster** hMixerTrackCluster) const; void LoadMusicalInstruments(); diff --git a/src/NoteTrack.cpp b/src/NoteTrack.cpp index c9edad6cc..9a0e9e7d7 100644 --- a/src/NoteTrack.cpp +++ b/src/NoteTrack.cpp @@ -21,16 +21,79 @@ #include "Audacity.h" -#if defined(USE_MIDI) +#include -#include "allegro.h" -/* REQUIRES PORTMIDI */ -//#include "portmidi.h" -//#include "porttime.h" +#if defined(USE_MIDI) +#define ROUND(x) ((int) ((x) + 0.5)) #include "AColor.h" #include "NoteTrack.h" #include "DirManager.h" +#include "Internat.h" +#include "Prefs.h" + +#ifdef SONIFY +#include "portmidi.h" + +#define SON_PROGRAM 0 +#define SON_AutoSave 67 +#define SON_ModifyState 60 +#define SON_NoteBackground 72 +#define SON_NoteForeground 74 +#define SON_Measures 76 /* "bar line" */ +#define SON_Serialize 77 +#define SON_Unserialize 79 +#define SON_VEL 100 + + +PmStream *sonMidiStream; +bool sonificationStarted = false; + +void SonifyBeginSonification() +{ + PmError err = Pm_OpenOutput(&sonMidiStream, Pm_GetDefaultOutputDeviceID(), + NULL, 0, NULL, NULL, 0); + if (err) sonMidiStream = NULL; + if (sonMidiStream) + Pm_WriteShort(sonMidiStream, 0, Pm_Message(0xC0, SON_PROGRAM, 0)); + sonificationStarted = true; +} + + +void SonifyEndSonification() +{ + if (sonMidiStream) Pm_Close(sonMidiStream); + sonificationStarted = false; +} + + + + +void SonifyNoteOnOff(int p, int v) +{ + if (!sonificationStarted) + SonifyBeginSonification(); + if (sonMidiStream) + Pm_WriteShort(sonMidiStream, 0, Pm_Message(0x90, p, v)); +} + +#define SONFNS(name) \ + void SonifyBegin ## name() { SonifyNoteOnOff(SON_ ## name, SON_VEL); } \ + void SonifyEnd ## name() { SonifyNoteOnOff(SON_ ## name, 0); } + +SONFNS(NoteBackground) +SONFNS(NoteForeground) +SONFNS(Measures) +SONFNS(Serialize) +SONFNS(Unserialize) +SONFNS(ModifyState) +SONFNS(AutoSave) + +#undef SONFNS + +#endif + + NoteTrack *TrackFactory::NewNoteTrack() { @@ -44,13 +107,15 @@ Track(projDirManager) SetName(GetDefaultName()); mSeq = NULL; - mLen = 0.0; mSerializationBuffer = NULL; mSerializationLength = 0; mDirManager = projDirManager; + mGain = 0; + mBottomNote = 24; + mPitchHeight = 5; mVisibleChannels = 0xFFFF; mLastMidiPosition = 0; @@ -70,48 +135,69 @@ NoteTrack::~NoteTrack() Track *NoteTrack::Duplicate() { NoteTrack *duplicate = new NoteTrack(mDirManager); + duplicate->Init(*this); // Duplicate on NoteTrack moves data from mSeq to mSerializationBuffer // and from mSerializationBuffer to mSeq on alternate calls. Duplicate // to the undo stack and Duplicate back to the project should result // in serialized blobs on the undo stack and traversable data in the // project object. if (mSeq) { + SonifyBeginSerialize(); assert(!mSerializationBuffer); // serialize from this to duplicate's mSerializationBuffer mSeq->serialize((void**)&duplicate->mSerializationBuffer, &duplicate->mSerializationLength); + SonifyEndSerialize(); } else if (mSerializationBuffer) { + SonifyBeginUnserialize(); assert(!mSeq); Alg_track_ptr alg_track = Alg_seq::unserialize(mSerializationBuffer, mSerializationLength); assert(alg_track->get_type() == 's'); duplicate->mSeq = (Alg_seq_ptr) alg_track; + SonifyEndUnserialize(); } else assert(false); // bug if neither mSeq nor mSerializationBuffer // copy some other fields here - duplicate->mBottomNote = mBottomNote; + duplicate->SetBottomNote(mBottomNote); + duplicate->SetPitchHeight(mPitchHeight); duplicate->mLastMidiPosition = mLastMidiPosition; - duplicate->mLen = mLen; duplicate->mVisibleChannels = mVisibleChannels; + duplicate->SetOffset(GetOffset()); + duplicate->SetGain(GetGain()); + return duplicate; } -void NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r) + +double NoteTrack::GetStartTime() +{ + return GetOffset(); +} + + +double NoteTrack::GetEndTime() +{ + return GetStartTime() + (mSeq ? mSeq->get_real_dur() : 0.0); +} + + +int NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r) { int wid = 23; int ht = 16; if (r.height < ht * 4) { - return; + return r.y + 5 + ht * 4; } int x = r.x + (r.width / 2 - wid * 2) + 2; int y = r.y + 5; + wxRect box; for (int row = 0; row < 4; row++) { for (int col = 0; col < 4; col++) { int channel = row * 4 + col + 1; - wxRect box; box.x = x + col * wid; box.y = y + row * ht; box.width = wid; @@ -147,6 +233,7 @@ void NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r) dc.DrawText(t, box.x + (box.width - w) / 2, box.y + (box.height - h) / 2); } } + return box.GetBottom(); } bool NoteTrack::LabelClick(wxRect & r, int mx, int my, bool right) @@ -159,6 +246,8 @@ bool NoteTrack::LabelClick(wxRect & r, int mx, int my, bool right) int x = r.x + (r.width / 2 - wid * 2); int y = r.y + 1; + // after adding Mute and Solo buttons, mapping is broken, so hack in the offset + y += 12; int col = (mx - x) / wid; int row = (my - y) / ht; @@ -185,7 +274,6 @@ void NoteTrack::SetSequence(Alg_seq_ptr seq) delete mSeq; mSeq = seq; - mLen = (seq ? seq->get_real_dur() : 0.0); } Alg_seq* NoteTrack::GetSequence() @@ -268,10 +356,8 @@ bool NoteTrack::Cut(double t0, double t1, Track **dest){ newTrack->Init(*this); mSeq->convert_to_seconds(); - newTrack->mSeq = mSeq->cut(t0, len, false); - - mLen = (mLen <= len ? 0.0 : mLen - len); - newTrack->mLen = len; + newTrack->mSeq = mSeq->cut(t0 - GetOffset(), len, false); + newTrack->SetOffset(GetOffset()); // What should be done with the rest of newTrack's members? //(mBottomNote, mDirManager, mLastMidiPosition, @@ -281,6 +367,7 @@ bool NoteTrack::Cut(double t0, double t1, Track **dest){ return true; } + bool NoteTrack::Copy(double t0, double t1, Track **dest){ //dest goes onto clipboard @@ -294,8 +381,8 @@ bool NoteTrack::Copy(double t0, double t1, Track **dest){ newTrack->Init(*this); mSeq->convert_to_seconds(); - newTrack->mSeq = mSeq->copy(t0, len, false); - newTrack->mLen = len; + newTrack->mSeq = mSeq->copy(t0 - GetOffset(), len, false); + newTrack->SetOffset(GetOffset()); // What should be done with the rest of newTrack's members? //(mBottomNote, mDirManager, mLastMidiPosition, @@ -305,18 +392,43 @@ bool NoteTrack::Copy(double t0, double t1, Track **dest){ return true; } -bool NoteTrack::Clear(double t0, double t1){ +bool NoteTrack::Trim(double t0, double t1) +{ + if (t1 <= t0) + return false; + double len = t1 - t0; + mSeq->convert_to_seconds(); + // delete way beyond duration just in case something is out there: + mSeq->clear(t1 - GetOffset(), mSeq->get_dur() + 10000.0, false); + // Now that stuff beyond selection is cleared, clear before selection: + mSeq->clear(0.0, t0 - GetOffset(), false); + // want starting time to be t0 + SetOffset(t0); + return true; +} + +bool NoteTrack::Clear(double t0, double t1) +{ // If t1 = t0, should Clear return true? if (t1 <= t0) return false; double len = t1-t0; - mSeq->clear(t0, len, false); + mSeq->clear(t0 - GetOffset(), len, false); return true; } -bool NoteTrack::Paste(double t, Track *src){ + +bool NoteTrack::Paste(double t, Track *src) +{ + // Paste inserts src at time t. If src has a positive offset, + // the offset is treated as silence which is also inserted. If + // the offset is negative, the offset is ignored and the ENTIRE + // src is inserted (otherwise, we would either lose data from + // src by not inserting things at negative times, or inserting + // things at negative times could overlap things already in + // the destination track). //Check that src is a non-NULL NoteTrack if (src == NULL || src->GetKind() != Track::Note) @@ -329,27 +441,256 @@ bool NoteTrack::Paste(double t, Track *src){ if(!mSeq) mSeq = new Alg_seq(); - mSeq->paste(t, other->mSeq); - mLen = mSeq->get_real_dur(); + if (other->GetOffset() > 0) { + mSeq->convert_to_seconds(); + mSeq->insert_silence(t - GetOffset(), other->GetOffset()); + t += other->GetOffset(); + } + mSeq->paste(t - GetOffset(), other->mSeq); return true; } +// Call this function to manipulate the underlying sequence data. This is +// NOT the function that handles horizontal dragging. +bool NoteTrack::Shift(double t) // t is always seconds +{ + if (t > 0) { + // insert an even number of measures + mSeq->convert_to_beats(); + // get initial tempo + double tempo = mSeq->get_tempo(0.0); + double beats_per_measure = mSeq->get_bar_len(0.0); + int m = ROUND(t * tempo / beats_per_measure); + // need at least 1 measure, so if we rounded down to zero, fix it + if (m == 0) m = 1; + // compute new tempo so that m measures at new tempo take t seconds + tempo = beats_per_measure * m / t; // in beats per second + mSeq->insert_silence(0.0, beats_per_measure * m); + mSeq->set_tempo(tempo * 60.0 /* bpm */, 0.0, beats_per_measure * m); + mSeq->write("afterShift.gro"); + } else if (t < 0) { + mSeq->convert_to_seconds(); + mSeq->clear(0, t, true); + } else { // offset is zero, no modifications + return false; + } + return true; +} +double NoteTrack::NearestBeatTime(double time, double *beat) +{ + wxASSERT(mSeq); + // Alg_seq knows nothing about offset, so remove offset time + double seq_time = time - GetOffset(); + seq_time = mSeq->nearest_beat_time(seq_time, beat); + // add the offset back in to get "actual" audacity track time + return seq_time + GetOffset(); +} + +bool NoteTrack::StretchRegion(double t0, double t1, double dur) +{ + wxASSERT(mSeq); + // Alg_seq::stretch_region uses beats, so we translate time + // to beats first: + t0 -= GetOffset(); + t1 -= GetOffset(); + double b0 = mSeq->get_time_map()->time_to_beat(t0); + double b1 = mSeq->get_time_map()->time_to_beat(t1); + bool result = mSeq->stretch_region(b0, b1, dur); + if (result) { + mSeq->convert_to_seconds(); + mSeq->set_dur(mSeq->get_dur() + dur - (t1 - t0)); + } + return result; +} + +Alg_seq_ptr NoteTrack::MakeExportableSeq() +{ + double offset = GetOffset(); + if (offset == 0) + return mSeq; + // make a copy, deleting events that are shifted before time 0 + double start = -offset; + if (start < 0) start = 0; + // notes that begin before "start" are not included even if they + // extend past "start" (because "all" parameter is set to false) + Alg_seq_ptr seq = mSeq->copy(start, mSeq->get_dur() - start, false); + if (offset > 0) { + // swap seq and mSeq so that Shift operates on the new copy + Alg_seq_ptr old_seq = mSeq; + mSeq = seq; + Shift(offset); + seq = mSeq; // undo the swap + mSeq = old_seq; +#ifdef OLD_CODE + // now shift events by offset. This must be done with an integer + // number of measures, so first, find the beats-per-measure + double beats_per_measure = 4.0; + Alg_time_sig_ptr tsp = NULL; + if (seq->time_sig.length() > 0 && seq->time_sig[0].beat < ALG_EPS) { + // there is an initial time signature + tsp = &(seq->time_sig[0]); + beats_per_measure = (tsp->num * 4) / tsp->den; + } + // also need the initial tempo + double bps = ALG_DEFAULT_BPM / 60; + Alg_time_map_ptr map = seq->get_time_map(); + Alg_beat_ptr bp = &(map->beats[0]); + if (bp->time < ALG_EPS) { // tempo change at time 0 + if (map->beats.len > 1) { // compute slope to get tempo + bps = (map->beats[1].beat - map->beats[0].beat) / + (map->beats[1].time - map->beats[0].time); + } else if (seq->get_time_map()->last_tempo_flag) { + bps = seq->get_time_map()->last_tempo; + } + } + // find closest number of measures to fit in the gap + // number of measures is offset / measure_time + double measure_time = beats_per_measure / bps; // seconds per measure + int n = ROUND(offset / measure_time); + if (n == 0) n = 1; + // we will insert n measures. Compute the desired duration of each. + measure_time = offset / n; + bps = beats_per_measure / measure_time; + // insert integer multiple of measures at beginning + seq->convert_to_beats(); + seq->insert_silence(0, beats_per_measure * n); + // make sure time signature at 0 is correct + if (tsp) { + seq->set_time_sig(0, tsp->num, tsp->den); + } + // adjust tempo to match offset + seq->set_tempo(bps * 60.0, 0, beats_per_measure * n); +#endif + } else { + // if offset is negative, it might not be a multiple of beats, but + // we want to preserve the relative positions of measures. I.e. we + // should shift barlines and time signatures as well as notes. + // Insert a time signature at the first bar-line if necessary. + + // Translate start from seconds to beats and call it beat: + double beat = mSeq->get_time_map()->time_to_beat(start); + // Find the time signature in mSeq in effect at start (beat): + int i = mSeq->time_sig.find_beat(beat); + // i is where you would insert a new time sig at beat, + // Case 1: beat coincides with a time sig at i. Time signature + // at beat means that there is a barline at beat, so when beat + // is shifted to 0, the relative barline positions are preserved + if (mSeq->time_sig.length() > 0 && + within(beat, mSeq->time_sig[i].beat, ALG_EPS)) { + // beat coincides with time signature change, so offset must + // be a multiple of beats + /* do nothing */ ; + // Case 2: there is no time signature before beat. + } else if (i == 0 && (mSeq->time_sig.length() == 0 || + mSeq->time_sig[i].beat > beat)) { + // If beat does not fall on an implied barline, we need to + // insert a time signature. + double measures = beat / 4.0; + double imeasures = ROUND(measures); + if (!within(measures, imeasures, ALG_EPS)) { + double bar_offset = (int(measures) + 1) * 4.0 - beat; + seq->set_time_sig(bar_offset, 4, 4); + } + // This case should never be true because if i == 0, either there + // are no time signatures before beat (Case 2), + // or there is one time signature at beat (Case 1) + } else if (i == 0) { + /* do nothing (might be good to assert(false)) */ ; + // Case 3: i-1 must be the effective time sig position + } else { + i -= 1; // index the time signature in effect at beat + Alg_time_sig_ptr tsp = &(mSeq->time_sig[i]); + double beats_per_measure = (tsp->num * 4) / tsp->den; + double measures = (beat - tsp->beat) / beats_per_measure; + int imeasures = ROUND(measures); + if (!within(measures, imeasures, ALG_EPS)) { + // beat is not on a measure, so we need to insert a time sig + // to force a bar line at the first measure location after + // beat + double bar = tsp->beat + beats_per_measure * (int(measures) + 1); + double bar_offset = bar - beat; + // insert new time signature at bar_offset in new sequence + // It will have the same time signature, but the position will + // force a barline to match the barlines in mSeq + seq->set_time_sig(bar_offset, tsp->num, tsp->den); + } + // else beat coincides with a barline, so no need for an extra + // time signature to force barline alignment + } + } + return seq; +} + bool NoteTrack::ExportMIDI(wxString f) { - return mSeq->smf_write(f.mb_str()); + Alg_seq_ptr seq = MakeExportableSeq(); + bool rslt = seq->smf_write(f.mb_str()); + if (seq != mSeq) delete seq; + return rslt; } bool NoteTrack::ExportAllegro(wxString f) { - return mSeq->write(f.mb_str()); + double offset = GetOffset(); + bool in_seconds; + gPrefs->Read(wxT("/FileFormats/AllegroStyle"), &in_seconds, true); + if (in_seconds) { + mSeq->convert_to_seconds(); + } else { + mSeq->convert_to_beats(); + } + return mSeq->write(f.mb_str(), offset); } bool NoteTrack::HandleXMLTag(const wxChar *tag, const wxChar **attrs) { + if (!wxStrcmp(tag, wxT("notetrack"))) { + while (*attrs) { + const wxChar *attr = *attrs++; + const wxChar *value = *attrs++; + if (!value) + break; + const wxString strValue = value; + long nValue; + double dblValue; + if (!wxStrcmp(attr, wxT("name")) && XMLValueChecker::IsGoodString(strValue)) + mName = strValue; + else if (!wxStrcmp(attr, wxT("offset")) && + XMLValueChecker::IsGoodString(strValue) && + Internat::CompatibleToDouble(strValue, &dblValue)) + SetOffset(dblValue); + else if (!wxStrcmp(attr, wxT("visiblechannels"))) { + if (!XMLValueChecker::IsGoodInt(strValue) || + !strValue.ToLong(&nValue) || + !XMLValueChecker::IsValidVisibleChannels(nValue)) + return false; + mVisibleChannels = nValue; + } + else if (!wxStrcmp(attr, wxT("height")) && + XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) + mHeight = nValue; + else if (!wxStrcmp(attr, wxT("minimized")) && + XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) + mMinimized = (nValue != 0); + else if (!wxStrcmp(attr, wxT("velocity")) && + XMLValueChecker::IsGoodString(strValue) && + Internat::CompatibleToDouble(strValue, &dblValue)) + mGain = (float) dblValue; + else if (!wxStrcmp(attr, wxT("bottomnote")) && + XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) + SetBottomNote(nValue); + else if (!wxStrcmp(attr, wxT("data"))) { + std::string s(wxString::FromUTF8(strValue.c_str())); + std::istringstream data(s); + mSeq = new Alg_seq(data, false); + } + } // while + return true; + } return false; } @@ -360,6 +701,103 @@ XMLTagHandler *NoteTrack::HandleXMLChild(const wxChar *tag) void NoteTrack::WriteXML(XMLWriter &xmlFile) { + std::ostringstream data; + // Normally, Duplicate is called in pairs -- once to put NoteTrack + // on the Undo stack, and again to move from the Undo stack to an + // "active" editable state. For efficiency, we do not do a "real" + // Duplicate followed by serialization into a binary blob. Instead, + // we combine the Duplicate with serialization or unserialization. + // Serialization and Unserialization happen on alternate calls to + // Duplicate and (usually) produce the right results at the right + // time. + // It turns out that this optimized Duplicate is a little too + // clever. There is at least one case where a track can be duplicated + // and then AutoSave'd. (E.g. do an "Insert Silence" effect on a + // NoteTrack.) In this case, mSeq will be NULL. To avoid a crash + // and perform WriteXML, we may need to restore NoteTracks from binary + // blobs to regular data structures (with an Alg_seq member). + NoteTrack *saveme = this; + if (!mSeq) { // replace saveme with an (unserialized) duplicate + saveme = (NoteTrack *) this->Duplicate(); + assert(saveme->mSeq); + } + saveme->mSeq->write(data, true); + xmlFile.StartTag(wxT("notetrack")); + xmlFile.WriteAttr(wxT("name"), saveme->mName); + xmlFile.WriteAttr(wxT("offset"), saveme->GetOffset()); + xmlFile.WriteAttr(wxT("visiblechannels"), saveme->mVisibleChannels); + xmlFile.WriteAttr(wxT("height"), saveme->GetActualHeight()); + xmlFile.WriteAttr(wxT("minimized"), saveme->GetMinimized()); + xmlFile.WriteAttr(wxT("velocity"), (double) saveme->mGain); + xmlFile.WriteAttr(wxT("bottomnote"), saveme->mBottomNote); + xmlFile.WriteAttr(wxT("data"), data.str()); + xmlFile.EndTag(wxT("notetrack")); + if (this != saveme) { + delete saveme; // delete the duplicate + } +} + +void NoteTrack::StartVScroll() +{ + mStartBottomNote = mBottomNote; +} + +void NoteTrack::VScroll(int start, int end) +{ + int ph = GetPitchHeight(); + int delta = ((end - start) + ph / 2) / ph; + SetBottomNote(mStartBottomNote + delta); +} + +// Zoom the note track, centering the pitch at centerY, +// amount is 1 for zoom in, and -1 for zoom out +void NoteTrack::Zoom(int centerY, int amount) +{ + // Construct track rectangle to map pitch to screen coordinates + // Only y and height are needed: + wxRect trackRect(0, GetY(), 1, GetHeight()); + PrepareIPitchToY(trackRect); + int centerPitch = YToIPitch(centerY); + // zoom out by changing the pitch height -- a small integer + mPitchHeight += amount; + if (mPitchHeight <= 0) mPitchHeight = 1; + PrepareIPitchToY(trackRect); // update because mPitchHeight changed + int newCenterPitch = YToIPitch(GetY() + GetHeight() / 2); + // center the pitch that the user clicked on + SetBottomNote(mBottomNote + (centerPitch - newCenterPitch)); +} + + +void NoteTrack::ZoomTo(int start, int end) +{ + wxRect trackRect(0, GetY(), 1, GetHeight()); + PrepareIPitchToY(trackRect); + int topPitch = YToIPitch(start); + int botPitch = YToIPitch(end); + if (topPitch < botPitch) { // swap + int temp = topPitch; topPitch = botPitch; botPitch = temp; + } + if (topPitch == botPitch) { // can't divide by zero, do something else + Zoom(start, 1); + return; + } + int trialPitchHeight = trackRect.height / (topPitch - botPitch); + if (trialPitchHeight > 25) { // keep mPitchHeight in bounds [1...25] + trialPitchHeight = 25; + } else if (trialPitchHeight == 0) { + trialPitchHeight = 1; + } + Zoom((start + end) / 2, trialPitchHeight - mPitchHeight); +} + +int NoteTrack::YToIPitch(int y) +{ + y = mBottom - y; // pixels above pitch 0 + int octave = (y / GetOctaveHeight()); + y -= octave * GetOctaveHeight(); + // result is approximate because C and G are one pixel taller than + // mPitchHeight. + return (y / mPitchHeight) + octave * 12; } #endif // USE_MIDI diff --git a/src/NoteTrack.h b/src/NoteTrack.h index b7526f248..6034c3cc8 100644 --- a/src/NoteTrack.h +++ b/src/NoteTrack.h @@ -15,9 +15,33 @@ #include "Audacity.h" #include "Experimental.h" #include "Track.h" +#include "allegro.h" #if defined(USE_MIDI) +// define this switch to play MIDI during redisplay to sonify run times +// Note that if SONIFY is defined, the default MIDI device will be opened +// and may block normal MIDI playback. +//#define SONIFY 1 + +#ifdef SONIFY + +#define SONFNS(name) \ + void Begin ## name(); \ + void End ## name(); + +SONFNS(NoteBackground) +SONFNS(NoteForeground) +SONFNS(Measures) +SONFNS(Serialize) +SONFNS(Unserialize) +SONFNS(ModifyState) +SONFNS(AutoSave) + +#undef SONFNS + +#endif + class wxDC; class wxRect; @@ -35,10 +59,10 @@ class AUDACITY_DLL_API NoteTrack:public Track { virtual int GetKind() const { return Note; } - virtual double GetStartTime() { return 0.0; } - virtual double GetEndTime() { return mLen; } + virtual double GetStartTime(); + virtual double GetEndTime(); - void DrawLabelControls(wxDC & dc, wxRect & r); + int DrawLabelControls(wxDC & dc, wxRect & r); bool LabelClick(wxRect & r, int x, int y, bool right); void SetSequence(Alg_seq *seq); @@ -47,6 +71,7 @@ class AUDACITY_DLL_API NoteTrack:public Track { int GetVisibleChannels(); + Alg_seq_ptr MakeExportableSeq(); bool ExportMIDI(wxString f); bool ExportAllegro(wxString f); @@ -60,10 +85,59 @@ class AUDACITY_DLL_API NoteTrack:public Track { // High-level editing virtual bool Cut (double t0, double t1, Track **dest); virtual bool Copy (double t0, double t1, Track **dest); + virtual bool Trim (double t0, double t1); virtual bool Clear(double t0, double t1); virtual bool Paste(double t, Track *src); + virtual bool Shift(double t); + + float GetGain() const { return mGain; } + void SetGain(float gain) { mGain = gain; } + + double NearestBeatTime(double time, double *beat); + bool StretchRegion(double b0, double b1, double dur); int GetBottomNote() const { return mBottomNote; } + int GetPitchHeight() const { return mPitchHeight; } + void SetPitchHeight(int h) { mPitchHeight = h; } + void ZoomOut(int y) { Zoom(y, -1); } + void ZoomIn(int y) { Zoom(y, 1); } + void Zoom(int centerY, int amount); + void ZoomTo(int start, int end); + int GetNoteMargin() const { return (mPitchHeight + 1) / 2; } + int GetOctaveHeight() const { return mPitchHeight * 12 + 2; } + // call this once before a series of calls to IPitchToY(). It + // sets mBottom to offset of octave 0 so that mBottomNote + // is located at r.y + r.height - (GetNoteMargin() + 1 + mPitchHeight) + void PrepareIPitchToY(const wxRect &r) { + mBottom = r.y + r.height - GetNoteMargin() - 1 - mPitchHeight + + (mBottomNote / 12) * GetOctaveHeight() + + GetNotePos(mBottomNote % 12); + } + // IPitchToY returns Y coordinate of top of pitch p + int IPitchToY(int p) const { + return mBottom - (p / 12) * GetOctaveHeight() - GetNotePos(p % 12); + } + // compute the window coordinate of the bottom of an octave: This is + // the bottom of the line separating B and C. + int GetOctaveBottom(int oct) const { + return IPitchToY(oct * 12) + mPitchHeight + 1; + } + // Y coordinate for given floating point pitch (rounded to int) + int PitchToY(double p) const { + return IPitchToY((int) (p + 0.5)); + } + // Integer pitch corresponding to a Y coordinate + int YToIPitch(int y); + // map pitch class number (0-11) to pixel offset from bottom of octave + // (the bottom of the black line between B and C) to the top of the + // note. Note extra pixel separates B(11)/C(0) and E(4)/F(5). + int GetNotePos(int p) const { return 1 + mPitchHeight * (p + 1) + (p > 4); } + // get pixel offset to top of ith black key note + int GetBlackPos(int i) const { return GetNotePos(i * 2 + 1 + (i > 1)); } + // GetWhitePos tells where to draw lines between keys as an offset from + // GetOctaveBottom. GetWhitePos(0) returns 1, which matches the location + // of the line separating B and C + int GetWhitePos(int i) const { return 1 + (i * GetOctaveHeight()) / 7; } void SetBottomNote(int note) { if (note < 0) @@ -73,6 +147,17 @@ class AUDACITY_DLL_API NoteTrack:public Track { mBottomNote = note; } + // Vertical scrolling is performed by dragging the keyboard at + // left of track. Protocol is call StartVScroll, then update by + // calling VScroll with original and final mouse position. + // These functions are not used -- instead, zooming/dragging works like + // audio track zooming/dragging. The vertical scrolling is nice however, + // so I left these functions here for possible use in the future. + void StartVScroll(); + void VScroll(int start, int end); + + wxRect GetGainPlacementRect() const { return mGainPlacementRect; } + void SetGainPlacementRect(const wxRect &r) { mGainPlacementRect = r; } virtual bool HandleXMLTag(const wxChar *tag, const wxChar **attrs); virtual XMLTagHandler *HandleXMLChild(const wxChar *tag); @@ -93,18 +178,44 @@ class AUDACITY_DLL_API NoteTrack:public Track { // even number of times, otherwise mSeq will be NULL). char *mSerializationBuffer; // NULL means no buffer long mSerializationLength; - double mLen; DirManager *mDirManager; - int mBottomNote; + float mGain; // velocity offset + // mBottom is the Y offset of pitch 0 (normally off screen) + int mBottom; + int mBottomNote; + int mStartBottomNote; + int mPitchHeight; int mVisibleChannels; int mLastMidiPosition; + wxRect mGainPlacementRect; }; #endif // USE_MIDI +#ifndef SONIFY +// no-ops: +#define SonifyBeginSonification() +#define SonifyEndSonification() +#define SonifyBeginNoteBackground() +#define SonifyEndNoteBackground() +#define SonifyBeginNoteForeground() +#define SonifyEndNoteForeground() +#define SonifyBeginMeasures() +#define SonifyEndMeasures() +#define SonifyBeginSerialize() +#define SonifyEndSerialize() +#define SonifyBeginUnserialize() +#define SonifyEndUnserialize() +#define SonifyBeginAutoSave() +#define SonifyEndAutoSave() +#define SonifyBeginModifyState() +#define SonifyEndModifyState() +#endif + + #endif // Indentation settings for Vim and Emacs and unique identifier for Arch, a diff --git a/src/Project.cpp b/src/Project.cpp index ecce8fbac..f6b9a1d8e 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -4250,6 +4250,7 @@ void AudacityProject::AutoSave() { // To minimize the possibility of race conditions, we first write to a // file with the extension ".tmp", then rename the file to .autosave + SonifyBeginAutoSave(); wxString projName; if (mFileName.IsEmpty()) @@ -4305,6 +4306,7 @@ void AudacityProject::AutoSave() mAutoSaveFileName += fn + wxT(".autosave"); mLastAutoSaveTime = wxGetLocalTime(); + SonifyEndAutoSave(); } void AudacityProject::DeleteCurrentAutoSaveFile() diff --git a/src/Snap.cpp b/src/Snap.cpp index 71b60ab28..32ffba240 100644 --- a/src/Snap.cpp +++ b/src/Snap.cpp @@ -79,7 +79,7 @@ SnapManager::SnapManager(TrackList *tracks, TrackClipArray *exclusions, } } } - if (track->GetKind() == Track::Wave) { + else if (track->GetKind() == Track::Wave) { WaveTrack *waveTrack = (WaveTrack *)track; WaveClipList::compatibility_iterator it; for (it=waveTrack->GetClipIterator(); it; it=it->GetNext()) { @@ -98,7 +98,12 @@ SnapManager::SnapManager(TrackList *tracks, TrackClipArray *exclusions, CondListAdd(clip->GetEndTime(), waveTrack, ttc); } } - +#ifdef USE_MIDI + else if (track->GetKind() == Track::Note) { + CondListAdd(track->GetStartTime(), track, ttc); + CondListAdd(track->GetEndTime(), track, ttc); + } +#endif track = iter.Next(); } diff --git a/src/Track.cpp b/src/Track.cpp index a1bdea2ca..835db4e4d 100644 --- a/src/Track.cpp +++ b/src/Track.cpp @@ -519,7 +519,11 @@ Track *SyncLockedTracksIterator::First(Track * member) member = l->GetPrev(member); } - while (member && member->GetKind() == Track::Wave) { + while (member && (member->GetKind() == Track::Wave +#ifdef USE_MIDI + || member->GetKind() == Track::Note +#endif + )) { t = member; member = l->GetPrev(member); } @@ -551,13 +555,14 @@ Track *SyncLockedTracksIterator::Next(bool skiplinked) cur = NULL; return NULL; } - +// This code block stops a group when a NoteTrack is encountered +#ifndef USE_MIDI // Encounter a non-wave non-label track if (t->GetKind() != Track::Wave && t->GetKind() != Track::Label) { cur = NULL; return NULL; } - +#endif // Otherwise, check if we're in the label section mInLabelSection = (t->GetKind() == Track::Label); @@ -581,13 +586,13 @@ Track *SyncLockedTracksIterator::Prev(bool skiplinked) cur = NULL; return NULL; } - +#ifndef USE_MIDI // Encounter a non-wave non-label track if (t->GetKind() != Track::Wave && t->GetKind() != Track::Label) { cur = NULL; return NULL; } - +#endif // Otherwise, check if we're in the label section mInLabelSection = (t->GetKind() == Track::Label); @@ -606,7 +611,11 @@ Track *SyncLockedTracksIterator::Last(bool skiplinked) int nextKind = l->GetNext(t)->GetKind(); if (mInLabelSection && nextKind != Track::Label) break; - if (nextKind != Track::Label && nextKind != Track::Wave) + if (nextKind != Track::Label && nextKind != Track::Wave +#ifdef USE_MIDI + && nextKind != Track::Note +#endif + ) break; t = Next(skiplinked); diff --git a/src/Track.h b/src/Track.h index 89fe8a185..e5b947007 100644 --- a/src/Track.h +++ b/src/Track.h @@ -39,7 +39,7 @@ WX_DEFINE_USER_EXPORTED_ARRAY(WaveTrack*, WaveTrackArray, class AUDACITY_DLL_API #if defined(USE_MIDI) class NoteTrack; -WX_DEFINE_ARRAY(NoteTrack*, NoteTrackArray); +WX_DEFINE_USER_EXPORTED_ARRAY(NoteTrack*, NoteTrackArray, class AUDACITY_DLL_API); #endif class TrackList; diff --git a/src/TrackArtist.cpp b/src/TrackArtist.cpp index 9e1da4397..567adf52a 100644 --- a/src/TrackArtist.cpp +++ b/src/TrackArtist.cpp @@ -39,7 +39,6 @@ #include #ifdef USE_MIDI -#include "allegro.h" #include "NoteTrack.h" #endif // USE_MIDI @@ -70,11 +69,33 @@ int gWaveformTimeCount = 0; #endif #ifdef USE_MIDI +/* const int octaveHeight = 62; const int blackPos[5] = { 6, 16, 32, 42, 52 }; const int whitePos[7] = { 0, 9, 17, 26, 35, 44, 53 }; const int notePos[12] = { 1, 6, 11, 16, 21, 27, 32, 37, 42, 47, 52, 57 }; + +// map pitch number to window coordinate of the *top* of the note +// Note the "free" variable bottom, which is assumed to be a local +// variable set to the offset of pitch 0 relative to the window +#define IPITCH_TO_Y(t, p) (bottom - ((p) / 12) * octaveHeight - \ + notePos[(p) % 12] - (t)->GetPitchHeight()) + +// GetBottom is called from a couple of places to compute the hypothetical +// coordinate of the bottom of pitch 0 in window coordinates. See +// IPITCH_TO_Y above, which computes coordinates relative to GetBottom() +// Note the -NOTE_MARGIN, which leaves a little margin to draw notes that +// are out of bounds. I'm not sure why the -2 is necessary. +int TrackArtist::GetBottom(NoteTrack *t, const wxRect &r) +{ + int bottomNote = t->GetBottomNote(); + int bottom = r.y + r.height - 2 - t->GetNoteMargin() + + ((bottomNote / 12) * octaveHeight + notePos[bottomNote % 12]); + return bottom; + +} +*/ #endif // USE_MIDI TrackArtist::TrackArtist() @@ -274,8 +295,11 @@ void TrackArtist::DrawTrack(const Track * t, } #ifdef USE_MIDI case Track::Note: - DrawNoteTrack((NoteTrack *)t, dc, r, viewInfo); + { + bool muted = (hasSolo || t->GetMute()) && !t->GetSolo(); + DrawNoteTrack((NoteTrack *)t, dc, r, viewInfo, muted); break; + } #endif // USE_MIDI case Track::Label: DrawLabelTrack((LabelTrack *)t, dc, r, viewInfo); @@ -331,8 +355,7 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) } #ifdef USE_MIDI - // The note track isn't drawing a ruler at all! - // But it needs to! + // The note track draws a vertical keyboard to label pitches if (kind == Track::Note) { UpdateVRuler(t, r); @@ -348,9 +371,9 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) r.y += 2; r.height -= 2; - int bottomNote = ((NoteTrack *) t)->GetBottomNote(); - int bottom = r.height + - ((bottomNote / 12) * octaveHeight + notePos[bottomNote % 12]); + //int bottom = GetBottom((NoteTrack *) t, r); + NoteTrack *track = (NoteTrack *) t; + track->PrepareIPitchToY(r); wxPen hilitePen; hilitePen.SetColour(120, 120, 120); @@ -367,26 +390,27 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) wxFont labelFont(fontSize, wxSWISS, wxNORMAL, wxNORMAL); dc->SetFont(labelFont); - for (int octave = 0; octave < 50; octave++) { - int obottom = bottom - octave * octaveHeight; - if (obottom < 0) - break; - + int octave = 0; + int obottom = track->GetOctaveBottom(octave); + int marg = track->GetNoteMargin(); + //IPITCH_TO_Y(octave * 12) + PITCH_HEIGHT + 1; + while (obottom >= r.y) { dc->SetPen(*wxBLACK_PEN); - for (int white = 0; white < 7; white++) - if (r.y + obottom - whitePos[white] > r.y && - r.y + obottom - whitePos[white] < r.y + r.height) - AColor::Line(*dc, r.x, r.y + obottom - whitePos[white], - r.x + r.width, - r.y + obottom - whitePos[white]); - + for (int white = 0; white < 7; white++) { + int pos = track->GetWhitePos(white); + if (obottom - pos > r.y + marg + 1 && + // don't draw too close to margin line -- it's annoying + obottom - pos < r.y + r.height - marg - 3) + AColor::Line(*dc, r.x, obottom - pos, + r.x + r.width, obottom - pos); + } wxRect br = r; - br.height = 5; + br.height = track->GetPitchHeight(); br.x++; br.width = 17; for (int black = 0; black < 5; black++) { - br.y = r.y + obottom - blackPos[black] - 4; - if (br.y > r.y && br.y + br.height < r.y + r.height) { + br.y = obottom - track->GetBlackPos(black); + if (br.y > r.y + marg - 2 && br.y + br.height < r.y + r.height - marg) { dc->SetPen(hilitePen); dc->DrawRectangle(br); dc->SetPen(*wxBLACK_PEN); @@ -399,19 +423,33 @@ void TrackArtist::DrawVRuler(Track *t, wxDC * dc, wxRect & r) } } - if (octave >= 2 && octave <= 9) { + if (octave >= 1 && octave <= 10) { wxString s; - s.Printf(wxT("C%d"), octave - 2); + // ISO standard: A440 is in the 4th octave, denoted + // A4 <- the "4" should be a subscript. + s.Printf(wxT("C%d"), octave - 1); long width, height; dc->GetTextExtent(s, &width, &height); - if (r.y + obottom - height + 4 > r.y && - r.y + obottom + 4 < r.y + r.height) { + if (obottom - height + 4 > r.y && + obottom + 4 < r.y + r.height) { dc->SetTextForeground(wxColour(60, 60, 255)); dc->DrawText(s, r.x + r.width - width, - r.y + obottom - height + 2); + obottom - height + 2); } } + obottom = track->GetOctaveBottom(++octave); } + // draw lines delineating the out-of-bounds margins + dc->SetPen(*wxBLACK_PEN); + int m = track->GetNoteMargin(); + // you would think the -1 offset here should be -2 to match the + // adjustment to r.y (see above), but -1 produces correct output + AColor::Line(*dc, r.x, r.y + marg - 1, r.x + r.width, r.y + marg - 1); + // since the margin gives us the bottom of the line, + // the extra -1 gets us to the top + AColor::Line(*dc, r.x, r.y + r.height - marg - 1, + r.x + r.width, r.y + r.height - marg - 1); + } #endif // USE_MIDI @@ -2020,7 +2058,6 @@ static const char *LookupStringAttribute(Alg_note_ptr note, Alg_attribute attr, static char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def); static int PITCH_TO_Y(double p, int bottom); static char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def); -static int PITCH_TO_Y(double p, int bottom); // returns NULL if note is not a shape, // returns atom (string) value of note if note is a shape @@ -2128,19 +2165,129 @@ char *LookupAtomAttribute(Alg_note_ptr note, Alg_attribute attr, char *def) //#define PITCH_TO_Y(p) (r.y + r.height - int(pitchht * ((p) + 0.5 - pitch0) + 0.5)) #ifdef USE_MIDI -int PITCH_TO_Y(double p, int bottom) + +/* +int PitchToY(double p, int bottom) { int octave = (((int) (p + 0.5)) / 12); int n = ((int) (p + 0.5)) % 12; - return bottom - octave * octaveHeight - notePos[n] - 4; + return IPITCH_TO_Y((int) (p + 0.5)); + // was: bottom - octave * octaveHeight - notePos[n] - 4; +} +*/ + +/* DrawNoteBackground is called by DrawNoteTrack twice: once to draw + the unselected background, and once to draw the selected background. + The selected background is the same except for the horizontal range + and the colors. The background rectangle region is given by r; the + selected region is given by sel. The first time this is called, + sel is equal to r, and the entire region is drawn with unselected + background colors. + */ +void TrackArtist::DrawNoteBackground(NoteTrack *track, wxDC &dc, + const wxRect &r, const wxRect &sel, + const ViewInfo *viewInfo, + const wxBrush &wb, const wxPen &wp, + const wxBrush &bb, const wxPen &bp, + const wxPen &mp) +{ + dc.SetBrush(wb); + dc.SetPen(wp); + dc.DrawRectangle(sel); // fill rectangle with white keys background + double h = viewInfo->h; + double pps = viewInfo->zoom; + + int left = TIME_TO_X(track->GetOffset()); + if (left < sel.x) left = sel.x; // clip on left + + int right = TIME_TO_X(track->GetOffset() + track->mSeq->get_real_dur()); + if (right > sel.x + sel.width) right = sel.x + sel.width; // clip on right + + // need overlap between MIDI data and the background region + if (left >= right) return; + + dc.SetBrush(bb); + int octave = 0; + // obottom is the window coordinate of octave divider line + int obottom = track->GetOctaveBottom(octave); + // eOffset is for the line between E and F; there's another line + // between B and C, hence the offset of 2 for two line thicknesses + int eOffset = track->GetPitchHeight() * 5 + 2; + while (obottom > r.y + track->GetNoteMargin() + 3) { + // draw a black line separating octaves if this octave botton is visible + if (obottom < r.y + r.height - track->GetNoteMargin()) { + dc.SetPen(*wxBLACK_PEN); + // obottom - 1 because obottom is at the bottom of the line + AColor::Line(dc, left, obottom - 1, right, obottom - 1); + } + dc.SetPen(bp); + // draw a black-key stripe colored line separating E and F if visible + if (obottom - eOffset > r.y && obottom - eOffset < r.y + r.height) { + AColor::Line(dc, left, obottom - eOffset, + right, obottom - eOffset); + } + + // draw visible black key lines + wxRect br; + br.x = left; + br.width = right - left; + br.height = track->GetPitchHeight(); + for (int black = 0; black < 5; black++) { + br.y = obottom - track->GetBlackPos(black); + if (br.y > r.y && br.y + br.height < r.y + r.height) { + dc.DrawRectangle(br); // draw each black key background stripe + } + } + obottom = track->GetOctaveBottom(++octave); + } + + // draw bar lines + Alg_seq_ptr seq = track->mSeq; + // We assume that sliding a NoteTrack around slides the barlines + // along with the notes. This means that when we write out a track + // as Allegro or MIDI without the offset, we'll need to insert an + // integer number of measures of silence, using tempo change to + // match the duration to the offset. + // Iterate over all time signatures to generate beat positions of + // bar lines, map the beats to times, map the times to position, + // and draw the bar lines that fall within the region of interest (sel) + seq->convert_to_beats(); + dc.SetPen(mp); + Alg_time_sigs &sigs = seq->time_sig; + int i = 0; // index into ts[] + double next_bar_beat = 0.0; + double beats_per_measure = 4.0; + while (true) { + if (i < sigs.length() && sigs[i].beat < next_bar_beat + ALG_EPS) { + // new time signature takes effect + Alg_time_sig &sig = sigs[i++]; + next_bar_beat = sig.beat; + beats_per_measure = (sig.num * 4.0) / sig.den; + } + // map beat to time + double t = seq->get_time_map()->beat_to_time(next_bar_beat); + // map time to position + int x = TIME_TO_X(t + track->GetOffset()); + if (x > right) break; + AColor::Line(dc, x, sel.y, x, sel.y + sel.height); + next_bar_beat += beats_per_measure; + } } +/* DrawNoteTrack: +Draws a piano-roll style display of sequence data with added +graphics. Since there may be notes outside of the display region, +reserve a half-note-height margin at the top and bottom of the +window and draw out-of-bounds notes here instead. +*/ void TrackArtist::DrawNoteTrack(NoteTrack *track, wxDC & dc, const wxRect & r, - const ViewInfo *viewInfo) + const ViewInfo *viewInfo, + bool muted) { + SonifyBeginNoteBackground(); double h = viewInfo->h; double pps = viewInfo->zoom; double sel0 = viewInfo->sel0; @@ -2164,56 +2311,42 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, if (!track->GetSelected()) sel0 = sel1 = 0.0; - int ctrpitch = 60; - int pitch0; - int pitchht = 4; - - int numPitches = r.height / pitchht; - pitch0 = (ctrpitch - numPitches/2); + // reserve 1/2 note height at top and bottom of track for + // out-of-bounds notes + int numPitches = (r.height) / track->GetPitchHeight(); + if (numPitches < 0) numPitches = 0; // cannot be negative + // bottomNote is the pitch of the note at the bottom of the track + // default is 24 (C1) int bottomNote = track->GetBottomNote(); - int bottom = r.height + - ((bottomNote / 12) * octaveHeight + notePos[bottomNote % 12]); - - //214, 214, 214 - dc.SetBrush(blankBrush); - dc.SetPen(blankPen); - dc.DrawRectangle(r); + // bottom is the hypothetical location of the bottom of pitch 0 relative to + // the top of the clipping region r: r.height - PITCH_HEIGHT/2 is where the + // bottomNote is displayed, and to that + // we add the height of bottomNote from the position of pitch 0 + track->PrepareIPitchToY(r); + // Background comes in 6 colors: + // 214, 214,214 -- unselected white keys + // 192,192,192 -- unselected black keys + // 170,170,170 -- unselected bar lines + // 165,165,190 -- selected white keys + // 148,148,170 -- selected black keys + // 131,131,150 -- selected bar lines wxPen blackStripePen; blackStripePen.SetColour(192, 192, 192); wxBrush blackStripeBrush; blackStripeBrush.SetColour(192, 192, 192); + wxPen barLinePen; + barLinePen.SetColour(170, 170, 170); - dc.SetBrush(blackStripeBrush); - - for (int octave = 0; octave < 50; octave++) { - int obottom = r.y + bottom - octave * octaveHeight; - - if (obottom > r.y && obottom < r.y + r.height) { - dc.SetPen(*wxBLACK_PEN); - AColor::Line(dc, r.x, obottom, r.x + r.width, obottom); - } - if (obottom - 26 > r.y && obottom - 26 < r.y + r.height) { - dc.SetPen(blackStripePen); - AColor::Line(dc, r.x, obottom - 26, r.x + r.width, obottom - 26); - } - - wxRect br = r; - br.height = 5; - for (int black = 0; black < 5; black++) { - br.y = obottom - blackPos[black] - 4; - if (br.y > r.y && br.y + br.height < r.y + r.height) { - dc.SetPen(blackStripePen); - dc.DrawRectangle(br); - } - } - } + DrawNoteBackground(track, dc, r, r, viewInfo, blankBrush, blankPen, + blackStripeBrush, blackStripePen, barLinePen); dc.SetClippingRegion(r); // Draw the selection background // First, the white keys, as a single rectangle + // In other words fill the selection area with selectedWhiteKeyPen wxRect selBG; selBG.y = r.y; selBG.height = r.height; @@ -2226,40 +2359,21 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, wxBrush selectedWhiteKeyBrush; selectedWhiteKeyBrush.SetColour(165, 165, 190); - dc.SetBrush(selectedWhiteKeyBrush); - - dc.DrawRectangle(selBG); - // Then, the black keys and octave stripes, as smaller rectangles wxPen selectedBlackKeyPen; selectedBlackKeyPen.SetColour(148, 148, 170); wxBrush selectedBlackKeyBrush; selectedBlackKeyBrush.SetColour(148, 148, 170); + wxPen selectedBarLinePen; + selectedBarLinePen.SetColour(131, 131, 150); - dc.SetBrush(selectedBlackKeyBrush); - - for (int octave = 0; octave < 50; octave++) { - int obottom = selBG.y + bottom - octave * octaveHeight; - - if (obottom > selBG.y && obottom < selBG.y + selBG.height) { - dc.SetPen(*wxBLACK_PEN); - AColor::Line(dc, selBG.x, obottom, selBG.x + selBG.width, obottom); - } - if (obottom - 26 > selBG.y && obottom - 26 < selBG.y + selBG.height) { - dc.SetPen(selectedBlackKeyPen); - AColor::Line(dc, selBG.x, obottom - 26, selBG.x + selBG.width, obottom - 26); - } - - wxRect bselBG = selBG; - bselBG.height = 5; - for (int black = 0; black < 5; black++) { - bselBG.y = obottom - blackPos[black] - 4; - if (bselBG.y > selBG.y && bselBG.y + bselBG.height < selBG.y + selBG.height) { - dc.SetPen(selectedBlackKeyPen); - dc.DrawRectangle(bselBG); - } - } - } + DrawNoteBackground(track, dc, r, selBG, viewInfo, + selectedWhiteKeyBrush, selectedWhiteKeyPen, + selectedBlackKeyBrush, selectedBlackKeyPen, + selectedBarLinePen); + SonifyEndNoteBackground(); + SonifyBeginNoteForeground(); + int marg = track->GetNoteMargin(); // NOTE: it would be better to put this in some global initialization // function rather than do lookups every time. @@ -2295,75 +2409,78 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, //for every event Alg_event_ptr evt; printf ("go time\n"); - while ( (evt = iterator.next()) ) { - - //printf ("one note"); - - //if the event is a note - if (evt->get_type() == 'n') { - + while (evt = iterator.next()) { + if (evt->get_type() == 'n') { // 'n' means a note Alg_note_ptr note = (Alg_note_ptr) evt; - - //if the notes channel is visible + // if the note's channel is visible if (visibleChannels & (1 << (evt->chan & 15))) { - double x = note->time; - double x1 = note->time + note->dur; + double x = note->time + track->GetOffset(); + double x1 = x + note->dur; if (x < h1 && x1 > h) { // omit if outside box char *shape = NULL; if (note->loud > 0.0 || !(shape = IsShape(note))) { + wxRect nr; // "note rectangle" + nr.y = track->PitchToY(note->pitch); + nr.height = track->GetPitchHeight(); - int octave = (((int) (note->pitch + 0.5)) / 12); - int n = ((int) (note->pitch + 0.5)) % 12; + nr.x = r.x + (int) ((x - h) * pps); + nr.width = (int) ((note->dur * pps) + 0.5); - wxRect nr; - nr.y = bottom - octave * octaveHeight - notePos[n] - 4; - nr.height = 5; - - if (nr.y + nr.height >= 0 && nr.y < r.height) { - - if (nr.y + nr.height > r.height) - nr.height = r.height - nr.y; - if (nr.y < 0) { - nr.height += nr.y; - nr.y = 0; + if (nr.x + nr.width >= r.x && nr.x < r.x + r.width) { + if (nr.x < r.x) { + nr.width -= (r.x - nr.x); + nr.x = r.x; } - nr.y += r.y; + if (nr.x + nr.width > r.x + r.width) // clip on right + nr.width = r.x + r.width - nr.x; - nr.x = r.x + (int) ((note->time - h) * pps); - nr.width = (int) (note->dur * pps) + 1; - - if (nr.x + nr.width >= r.x && nr.x < r.x + r.width) { - if (nr.x < r.x) { - nr.width -= (r.x - nr.x); - nr.x = r.x; + if (nr.y + nr.height < r.y + marg + 3) { + // too high for window + nr.y = r.y; + nr.height = marg; + dc.SetBrush(*wxBLACK_BRUSH); + dc.SetPen(*wxBLACK_PEN); + dc.DrawRectangle(nr); + } else if (nr.y >= r.y + r.height - marg - 1) { + // too low for window + nr.y = r.y + r.height - marg; + nr.height = marg; + dc.SetBrush(*wxBLACK_BRUSH); + dc.SetPen(*wxBLACK_PEN); + dc.DrawRectangle(nr); + } else { + if (nr.y + nr.height > r.y + r.height - marg) + nr.height = r.y + r.height - nr.y; + if (nr.y < r.y + marg) { + int offset = r.y + marg - nr.y; + nr.height -= offset; + nr.y += offset; } - if (nr.x + nr.width > r.x + r.width) - nr.width = r.x + r.width - nr.x; - - AColor::MIDIChannel(&dc, note->chan + 1); - -// if (note->time + note->dur >= sel0 && note->time <= sel1) { -// dc.SetBrush(*wxWHITE_BRUSH); -// dc.DrawRectangle(nr); -// } else { + // nr.y += r.y; + if (muted) + AColor::LightMIDIChannel(&dc, note->chan + 1); + else + AColor::MIDIChannel(&dc, note->chan + 1); dc.DrawRectangle(nr); - AColor::LightMIDIChannel(&dc, note->chan + 1); - AColor::Line(dc, nr.x, nr.y, nr.x + nr.width-2, nr.y); - AColor::Line(dc, nr.x, nr.y, nr.x, nr.y + nr.height-2); - AColor::DarkMIDIChannel(&dc, note->chan + 1); - AColor::Line(dc, nr.x+nr.width-1, nr.y, - nr.x+nr.width-1, nr.y+nr.height-1); - AColor::Line(dc, nr.x, nr.y+nr.height-1, - nr.x+nr.width-1, nr.y+nr.height-1); -// } + if (track->GetPitchHeight() > 2) { + AColor::LightMIDIChannel(&dc, note->chan + 1); + AColor::Line(dc, nr.x, nr.y, nr.x + nr.width-2, nr.y); + AColor::Line(dc, nr.x, nr.y, nr.x, nr.y + nr.height-2); + AColor::DarkMIDIChannel(&dc, note->chan + 1); + AColor::Line(dc, nr.x+nr.width-1, nr.y, + nr.x+nr.width-1, nr.y+nr.height-1); + AColor::Line(dc, nr.x, nr.y+nr.height-1, + nr.x+nr.width-1, nr.y+nr.height-1); + } +// } } } - } else if (shape) { // draw a shape according to attributes - // add 0.5 to pitch because pitches are plotted with height = pitchht, - // thus, the center is raised by pitchht * 0.5 - int y = PITCH_TO_Y(note->pitch, bottom); + // add 0.5 to pitch because pitches are plotted with + // height = PITCH_HEIGHT; thus, the center is raised + // by PITCH_HEIGHT * 0.5 + int y = track->PitchToY(note->pitch); long linecolor = LookupIntAttribute(note, linecolori, -1); long linethick = LookupIntAttribute(note, linethicki, 1); long fillcolor = -1; @@ -2389,7 +2506,7 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, wxSOLID)); if (!fillflag) dc.SetBrush(*wxTRANSPARENT_BRUSH); } - int y1 = PITCH_TO_Y(LookupRealAttribute(note, y1r, note->pitch), bottom); + int y1 = track->PitchToY(LookupRealAttribute(note, y1r, note->pitch)); if (shape == line) { // extreme zooms caues problems under windows, so we have to do some // clipping before calling display routine @@ -2418,21 +2535,21 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, note->pitch)); CLIP(points[1].x); points[1].y = y1; - points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, note->time)); + points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, x)); CLIP(points[2].x); - points[2].y = PITCH_TO_Y(LookupRealAttribute(note, y2r, note->pitch), bottom); + points[2].y = track->PitchToY(LookupRealAttribute(note, y2r, note->pitch)); dc.DrawPolygon(3, points); } else if (shape == polygon) { wxPoint points[20]; // upper bound of 20 sides points[0].x = TIME_TO_X(x); CLIP(points[0].x); points[0].y = y; - points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, note->time)); + points[1].x = TIME_TO_X(LookupRealAttribute(note, x1r, x)); CLIP(points[1].x); points[1].y = y1; - points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, note->time)); + points[2].x = TIME_TO_X(LookupRealAttribute(note, x2r, x)); CLIP(points[2].x); - points[2].y = PITCH_TO_Y(LookupRealAttribute(note, y2r, note->pitch), bottom); + points[2].y = track->PitchToY(LookupRealAttribute(note, y2r, note->pitch)); int n = 3; while (n < 20) { char name[8]; @@ -2446,7 +2563,7 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, attr = symbol_table.insert_string(name); double yn = LookupRealAttribute(note, attr, -1000000.0); if (yn == -1000000.0) break; - points[n].y = PITCH_TO_Y(yn, bottom); + points[n].y = track->PitchToY(yn); n++; } dc.DrawPolygon(n, points); @@ -2523,7 +2640,18 @@ void TrackArtist::DrawNoteTrack(NoteTrack *track, } } iterator.end(); + // draw black line between top/bottom margins and the track + dc.SetPen(*wxBLACK_PEN); + AColor::Line(dc, r.x, r.y + marg, r.x + r.width, r.y + marg); + AColor::Line(dc, r.x, r.y + r.height - marg - 1, // subtract 1 to get + r.x + r.width, r.y + r.height - marg - 1); // top of line + + if (h == 0.0 && track->GetOffset() < 0.0) { + DrawNegativeOffsetTrackArrows(dc, r); + } + dc.DestroyClippingRegion(); + SonifyEndNoteForeground(); } #endif // USE_MIDI diff --git a/src/TrackArtist.h b/src/TrackArtist.h index 567a75e17..86a15654f 100644 --- a/src/TrackArtist.h +++ b/src/TrackArtist.h @@ -114,8 +114,16 @@ class AUDACITY_DLL_API TrackArtist { wxDC & dc, const wxRect & r, const ViewInfo *viewInfo, bool autocorrelation, bool logF); #ifdef USE_MIDI + int GetBottom(NoteTrack *t, const wxRect &r); + void DrawNoteBackground(NoteTrack *track, wxDC &dc, + const wxRect &r, const wxRect &sel, + const ViewInfo *viewInfo, + const wxBrush &wb, const wxPen &wp, + const wxBrush &bb, const wxPen &bp, + const wxPen &mp); void DrawNoteTrack(NoteTrack *track, - wxDC & dc, const wxRect & r, const ViewInfo *viewInfo); + wxDC & dc, const wxRect & r, const ViewInfo *viewInfo, + bool muted); #endif // USE_MIDI void DrawLabelTrack(LabelTrack *track, diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index e02585896..4d6950ef9 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -478,6 +478,17 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id, mZoomOutCursor = MakeCursor( wxCURSOR_MAGNIFIER, ZoomOutCursorXpm, 19, 15); mLabelCursorLeft = MakeCursor( wxCURSOR_ARROW, LabelCursorLeftXpm, 19, 15); mLabelCursorRight = MakeCursor( wxCURSOR_ARROW, LabelCursorRightXpm, 16, 16); +#if USE_MIDI + mStretchMode = stretchCenter; + mStretching = false; + mStretched = false; + mStretchStart = 0; + mStretchCursor = MakeCursor( wxCURSOR_BULLSEYE, StretchCursorXpm, 16, 16); + mStretchLeftCursor = MakeCursor( wxCURSOR_BULLSEYE, + StretchLeftCursorXpm, 16, 16); + mStretchRightCursor = MakeCursor( wxCURSOR_BULLSEYE, + StretchRightCursorXpm, 16, 16); +#endif mArrowCursor = new wxCursor(wxCURSOR_ARROW); mSmoothCursor = new wxCursor(wxCURSOR_SPRAYCAN); @@ -883,11 +894,7 @@ void TrackPanel::OnTimer() // Check whether we were playing or recording, but the stream has stopped. if (p->GetAudioIOToken()>0 && - !gAudioIO->IsStreamActive(p->GetAudioIOToken()) - #ifdef EXPERIMENTAL_MIDI_OUT - && !gAudioIO->IsMidiActive() - #endif - ) + !gAudioIO->IsStreamActive(p->GetAudioIOToken())) { //the stream may have been started up after this one finished (by some other project) //in that case reset the buttons don't stop the stream @@ -911,11 +918,7 @@ void TrackPanel::OnTimer() // and flush the tracks once we've completely finished // recording new state. if (p->GetAudioIOToken()>0 && - !gAudioIO->IsAudioTokenActive(p->GetAudioIOToken()) - #ifdef EXPERIMENTAL_MIDI_OUT - && !gAudioIO->IsMidiActive() - #endif - ) + !gAudioIO->IsAudioTokenActive(p->GetAudioIOToken())) { if (gAudioIO->GetNumCaptureChannels() > 0) { // Tracks are buffered during recording. This flushes @@ -944,7 +947,8 @@ void TrackPanel::OnTimer() } // AS: The "indicator" is the little graphical mark shown in the ruler - // that indicates where the current play/record position is. + // that indicates where the current play/record position is. (This also + // draws the moving vertical line.) if (!gAudioIO->IsPaused() && ( mIndicatorShowing || gAudioIO->IsStreamActive(p->GetAudioIOToken()))) { @@ -1396,6 +1400,11 @@ bool TrackPanel::SetCursorByActivity( ) return true; case IsAdjustingLabel: return true; +#ifdef USE_MIDI + case IsStretching: + SetCursor( unsafe ? *mDisabledCursor : *mStretchCursor); + return true; +#endif case IsOverCutLine: SetCursor( unsafe ? *mDisabledCursor : *mArrowCursor); default: @@ -1417,6 +1426,12 @@ void TrackPanel::SetCursorAndTipWhenInLabel( Track * t, *ppTip = _("Click to vertically zoom in. Shift-click to zoom out. Drag to specify a zoom region."); SetCursor(event.ShiftDown()? *mZoomOutCursor : *mZoomInCursor); } +#ifdef USE_MIDI + else if (event.m_x >= GetVRulerOffset() && t->GetKind() == Track::Note) { + *ppTip = _("Click to verticaly zoom in, Shift-click to zoom out, Drag to create a particular zoom region."); + SetCursor(event.ShiftDown() ? *mZoomOutCursor : *mZoomInCursor); + } +#endif else { // Set a status message if over TrackInfo. *ppTip = _("Drag the track vertically to change the order of the tracks."); @@ -1539,14 +1554,34 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t, } // Is the cursor over the left selection boundary? else if (within(event.m_x, leftSel, SELECTION_RESIZE_REGION)) { +#ifdef USE_MIDI + if (HitTestStretch(t, r, event)) { + *ppTip = _("Click and drag to stretch selected region."); + SetCursor(*mStretchLeftCursor); + return; + } +#endif *ppTip = _("Click and drag to move left selection boundary."); SetCursor(*mAdjustLeftSelectionCursor); } // Is the cursor over the right selection boundary? else if (within(event.m_x, rightSel, SELECTION_RESIZE_REGION)) { +#ifdef USE_MIDI + if (HitTestStretch(t, r, event)) { + *ppTip = _("Click and drag to stretch selected region."); + SetCursor(*mStretchRightCursor); + return; + } +#endif *ppTip = _("Click and drag to move right selection boundary."); SetCursor(*mAdjustRightSelectionCursor); } +#ifdef USE_MIDI + else if (HitTestStretch(t, r, event)) { + *ppTip = _("Click and drag to stretch within selected region."); + SetCursor(*mStretchCursor); + } +#endif else { //For OD regions, we need to override and display the percent complete for this task. @@ -1796,7 +1831,16 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event, mSnapLeft = -1; mSnapRight = -1; - if (event.ShiftDown()) { +#ifdef USE_MIDI + mStretching = false; + bool stretch = HitTestStretch(pTrack, r, event); +#endif + + if (event.ShiftDown() +#ifdef USE_MIDI + && !stretch +#endif + ) { // If the shift button is down and no track is selected yet, // at least select the track we clicked into. bool isAtLeastOneTrackSelected = false; @@ -1827,8 +1871,11 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event, // A control-click will set just the indicator to the clicked spot, // and turn playback on. - else if(event.CmdDown()) - { + else if(event.CmdDown() +#ifdef USE_MIDI + && !stretch +#endif + ) { AudacityProject *p = GetActiveProject(); if (p) { @@ -1906,7 +1953,84 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event, return; } } - + +#ifdef USE_MIDI + if (stretch) { + NoteTrack *nt = (NoteTrack *) pTrack; + // find nearest beat to sel0, sel1 + double minPeriod = 0.05; // minimum beat period + double qBeat0, qBeat1; + double centerBeat; + mStretchSel0 = nt->NearestBeatTime(mViewInfo->sel0, &qBeat0); + mStretchSel1 = nt->NearestBeatTime(mViewInfo->sel1, &qBeat1); + + // If there is not (almost) a beat to stretch that is slower + // than 20 beats per second, don't stretch + if (within(qBeat0, qBeat1, 0.9) || + (mStretchSel1 - mStretchSel0) / (qBeat1 - qBeat0) < minPeriod) return; + + if (startNewSelection) { // mouse is not at an edge, but after + // quantization, we could be indicating the selection edge + mSelStart = PositionToTime(event.m_x, r.x); + mStretchStart = nt->NearestBeatTime(mSelStart, ¢erBeat); + if (within(qBeat0, centerBeat, 0.1)) { + mListener->TP_DisplayStatusMessage( + _("Click and drag to stretch selected region.")); + SetCursor(*mStretchLeftCursor); + // mStretchMode = stretchLeft; + mSelStart = mViewInfo->sel1; // condition that implies stretchLeft + startNewSelection = false; + } else if (within(qBeat1, centerBeat, 0.1)) { + mListener->TP_DisplayStatusMessage( + _("Click and drag to stretch selected region.")); + SetCursor(*mStretchRightCursor); + // mStretchMode = stretchRight; + mSelStart = mViewInfo->sel0; // condition that implies stretchRight + startNewSelection = false; + } + } + + if (startNewSelection) { + mStretchMode = stretchCenter; + mStretchLeftBeats = qBeat1 - centerBeat; + mStretchRightBeats = centerBeat - qBeat0; + } else if (mViewInfo->sel1 == mSelStart) { + // note that at this point, mSelStart is at the opposite + // end of the selection from the cursor. If the cursor is + // over sel0, then mSelStart is at sel1. + mStretchMode = stretchLeft; + } else { + mStretchMode = stretchRight; + } + + if (mStretchMode == stretchLeft) { + mStretchLeftBeats = 0; + mStretchRightBeats = qBeat1 - qBeat0; + } else if (mStretchMode == stretchRight) { + mStretchLeftBeats = qBeat1 - qBeat0; + mStretchRightBeats = 0; + } + mViewInfo->sel0 = mStretchSel0; + mViewInfo->sel1 = mStretchSel1; + mStretching = true; + mStretched = false; + + MakeParentPushState(_("Stretch Note Track"), _("Stretch"), false); + + // Full refresh since the label area may need to indicate + // newly selected tracks. (I'm really not sure if the label area + // needs to be refreshed or how to just refresh non-label areas.-RBD) + Refresh(false); + + // Make sure the ruler follows suit. + mRuler->DrawSelection(); + + // As well as the SelectionBar. + DisplaySelection(); + + return; + } +#endif if (startNewSelection) { // If we didn't move a selection boundary, start a new selection SelectNone(); @@ -1945,8 +2069,9 @@ void TrackPanel::StartSelection(int mouseXCoordinate, int trackLeftEdge) mViewInfo->sel0 = s; mViewInfo->sel1 = s; - + SonifyBeginModifyState(); MakeParentModifyState(); + SonifyEndModifyState(); } /// Extend the existing selection @@ -2020,6 +2145,109 @@ void TrackPanel::ExtendSelection(int mouseXCoordinate, int trackLeftEdge, DisplaySelection(); } +#ifdef USE_MIDI +void TrackPanel::Stretch(int mouseXCoordinate, int trackLeftEdge, + Track *pTrack) +{ + if (mStretched) { // Undo stretch and redo it with new mouse coordinates + // Drag handling was not originally implemented with Undo in mind -- + // there are saved pointers to tracks that are not supposed to change. + // Undo will change tracks, so convert pTrack, mCapturedTrack to index + // values, then look them up after the Undo + TrackListIterator iter(mTracks); + int pTrackIndex = pTrack->GetIndex(); + int capturedTrackIndex = + (mCapturedTrack ? mCapturedTrack->GetIndex() : 0); + + GetProject()->OnUndo(); + + // Undo brings us back to the pre-click state, but we want to + // quantize selected region to integer beat boundaries. These + // were saved in mStretchSel[12] variables: + mViewInfo->sel0 = mStretchSel0; + mViewInfo->sel1 = mStretchSel1; + + mStretched = false; + int index = 0; + for (Track *t = iter.First(mTracks); t; t = iter.Next()) { + if (index == pTrackIndex) pTrack = t; + if (mCapturedTrack && index == capturedTrackIndex) mCapturedTrack = t; + index++; + } + } + + if (pTrack == NULL && mCapturedTrack != NULL) + pTrack = mCapturedTrack; + + if (!pTrack || pTrack->GetKind() != Track::Note) { + return; + } + + NoteTrack *pNt = (NoteTrack *) pTrack; + double moveto = PositionToTime(mouseXCoordinate, trackLeftEdge); + + // check to make sure tempo is not higher than 20 beats per second + // (In principle, tempo can be higher, but not infinity.) + double minPeriod = 0.05; // minimum beat period + double qBeat0, qBeat1; + pNt->NearestBeatTime(mViewInfo->sel0, &qBeat0); // get beat + pNt->NearestBeatTime(mViewInfo->sel1, &qBeat1); + + // We could be moving 3 things: left edge, right edge, a point between + switch (mStretchMode) { + case stretchLeft: { + // make sure target duration is not too short + double dur = mViewInfo->sel1 - moveto; + if (dur < mStretchRightBeats * minPeriod) { + dur = mStretchRightBeats * minPeriod; + moveto = mViewInfo->sel1 - dur; + } + if (pNt->StretchRegion(mStretchSel0, mStretchSel1, dur)) { + pNt->SetOffset(pNt->GetOffset() + moveto - mStretchSel0); + mViewInfo->sel0 = moveto; + } + break; + } + case stretchRight: { + // make sure target duration is not too short + double dur = moveto - mViewInfo->sel0; + if (dur < mStretchLeftBeats * minPeriod) { + dur = mStretchLeftBeats * minPeriod; + moveto = mStretchSel0 + dur; + } + if (pNt->StretchRegion(mStretchSel0, mStretchSel1, dur)) { + mViewInfo->sel1 = moveto; + } + break; + } + case stretchCenter: { + // make sure both left and right target durations are not too short + double left_dur = moveto - mViewInfo->sel0; + double right_dur = mViewInfo->sel1 - moveto; + double centerBeat; + pNt->NearestBeatTime(mSelStart, ¢erBeat); + if (left_dur < mStretchLeftBeats * minPeriod) { + left_dur = mStretchLeftBeats * minPeriod; + moveto = mStretchSel0 + left_dur; + } + if (right_dur < mStretchRightBeats * minPeriod) { + right_dur = mStretchRightBeats * minPeriod; + moveto = mStretchSel1 - right_dur; + } + pNt->StretchRegion(mStretchStart, mStretchSel1, right_dur); + pNt->StretchRegion(mStretchSel0, mStretchStart, left_dur); + break; + } + default: + wxASSERT(false); + break; + } + MakeParentPushState(_("Stretch Note Track"), _("Stretch"), true); + mStretched = true; + Refresh(false); +} +#endif + /// AS: If we're dragging to extend a selection (or actually, /// if the screen is scrolling while you're selecting), we /// handle it here. @@ -2056,7 +2284,11 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack) const int minimumSizedSelection = 5; //measured in pixels wxInt64 SelStart=TimeToPosition( mSelStart, r.x); //cvt time to pixels. // Abandon this drag if selecting < 5 pixels. - if (wxLongLong(SelStart-x).Abs() < minimumSizedSelection) + if (wxLongLong(SelStart-x).Abs() < minimumSizedSelection +#ifdef USE_MIDI // limiting selection size is good, and not starting + && !mStretching // stretch unless mouse moves 5 pixels is good, but +#endif // once stretching starts, it's ok to move even 1 pixel + ) return; // Handle which tracks are selected @@ -2079,7 +2311,16 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack) pTrack = iter.Next(); } while (pTrack); } - +#ifdef USE_MIDI + if (mStretching) { + // the following is also in ExtendSelection, called below + // probably a good idea to "hoist" the code to before this "if" stmt + if (clickedTrack == NULL && mCapturedTrack != NULL) + clickedTrack = mCapturedTrack; + Stretch(x, r.x, clickedTrack); + return; + } +#endif ExtendSelection(x, r.x, clickedTrack); } @@ -2314,13 +2555,25 @@ void TrackPanel::StartSlide(wxMouseEvent & event) ToolsToolBar * ttb = mListener->TP_GetToolsToolBar(); bool multiToolModeActive = (ttb && ttb->IsDown(multiTool)); - if (vt->GetKind() == Track::Wave && !event.ShiftDown()) + if ((vt->GetKind() == Track::Wave +#ifdef USE_MIDI + || vt->GetKind() == Track::Note +#endif + ) && !event.ShiftDown()) { - WaveTrack* wt = (WaveTrack*)vt; - mCapturedClip = wt->GetClipAtX(event.m_x); - if (mCapturedClip == NULL) - return; - +#ifdef USE_MIDI + if (vt->GetKind() == Track::Wave) { +#endif + WaveTrack* wt = (WaveTrack*)vt; + mCapturedClip = wt->GetClipAtX(event.m_x); + if (mCapturedClip == NULL) + return; +#ifdef USE_MIDI + } + else { + mCapturedClip = NULL; + } +#endif // The captured clip is the focus, but we need to create a list // of all clips that have to move, also... @@ -2329,7 +2582,7 @@ void TrackPanel::StartSlide(wxMouseEvent & event) double clickTime = PositionToTime(event.m_x, GetLeftOffset()); bool clickedInSelection = - (wt->GetSelected() && + (vt->GetSelected() && clickTime > mViewInfo->sel0 && clickTime < mViewInfo->sel1); @@ -2347,10 +2600,10 @@ void TrackPanel::StartSlide(wxMouseEvent & event) } else { mCapturedClipIsSelection = false; - mCapturedClipArray.Add(TrackClip(wt, mCapturedClip)); + mCapturedClipArray.Add(TrackClip(vt, mCapturedClip)); // Check for stereo partner - Track *partner = mTracks->GetLink(wt); + Track *partner = mTracks->GetLink(vt); if (partner && partner->GetKind() == Track::Wave) { WaveClip *clip = ((WaveTrack *)partner)->GetClipAtX(event.m_x); if (clip) { @@ -2367,7 +2620,7 @@ void TrackPanel::StartSlide(wxMouseEvent & event) // because AddClipsToCapture doesn't add duplicate clips); to remove // this behavior just store the array size beforehand. for (unsigned int i = 0; i < mCapturedClipArray.GetCount(); ++i) { - // Only capture based on tracks that have clips -- that means we + // Capture based on tracks that have clips -- that means we // don't capture based on links to label tracks for now (until // we can treat individual labels as clips) if (mCapturedClipArray[i].clip) { @@ -2381,6 +2634,18 @@ void TrackPanel::StartSlide(wxMouseEvent & event) mCapturedClipArray[i].clip->GetEndTime() ); } } +#ifdef USE_MIDI + // Capture additional clips from NoteTracks + Track *nt = mCapturedClipArray[i].track; + if (nt->GetKind() == Track::Note) { + // Iterate over group tracks + SyncLockedTracksIterator git(mTracks); + for (Track *t = git.First(nt); t; t = git.Next()) + { + AddClipsToCaptured(t, nt->GetStartTime(), nt->GetEndTime()); + } + } +#endif } } @@ -2470,8 +2735,16 @@ void TrackPanel::AddClipsToCaptured(Track *t, double t0, double t1) } } - if (newClip) + if (newClip) { +#ifdef USE_MIDI + // do not add NoteTrack if the data is outside of time bounds + if (t->GetKind() == Track::Note) { + if (t->GetEndTime() < t0 || t->GetStartTime() > t1) + return; + } +#endif mCapturedClipArray.Add(TrackClip(t, NULL)); + } } } @@ -2486,16 +2759,26 @@ void TrackPanel::DoSlide(wxMouseEvent & event) // find which track the mouse is currently in (mouseTrack) - // this may not be the same as the one we started in... +#ifdef USE_MIDI + Track *mouseTrack = FindTrack(event.m_x, event.m_y, false, false, NULL); + if (!mouseTrack || (mouseTrack->GetKind() != Track::Wave && + mouseTrack->GetKind() != Track::Note)) { +#else WaveTrack *mouseTrack = (WaveTrack *)FindTrack(event.m_x, event.m_y, false, false, NULL); if (!mouseTrack || mouseTrack->GetKind() != Track::Wave) { +#endif return; } // Start by undoing the current slide amount; everything // happens relative to the original horizontal position of // each clip... +#ifdef USE_MIDI + if (mCapturedClipArray.GetCount()) { +#else if (mCapturedClip) { +#endif for(i=0; iOffset(-mHSlideAmount); @@ -2518,12 +2801,31 @@ void TrackPanel::DoSlide(wxMouseEvent & event) mHSlideAmount = 0.0; double desiredSlideAmount = (event.m_x - mMouseClickX) / mViewInfo->zoom; - desiredSlideAmount = rint(mouseTrack->GetRate() * desiredSlideAmount) / mouseTrack->GetRate(); // set it to a sample point - +#ifdef USE_MIDI + if (mouseTrack->GetKind() == Track::Wave) { + WaveTrack *mtw = (WaveTrack *) mouseTrack; + desiredSlideAmount = rint(mtw->GetRate() * desiredSlideAmount) / + mtw->GetRate(); // set it to a sample point + } // Adjust desiredSlideAmount using SnapManager + if (mSnapManager && mCapturedClipArray.GetCount()) { + double clipLeft; + double clipRight; + if (mCapturedClip) { + clipLeft = mCapturedClip->GetStartTime() + desiredSlideAmount; + clipRight = mCapturedClip->GetEndTime() + desiredSlideAmount; + } + else { + clipLeft = mCapturedTrack->GetStartTime() + desiredSlideAmount; + clipRight = mCapturedTrack->GetEndTime() + desiredSlideAmount; + } +#else + desiredSlideAmount = rint(mouseTrack->GetRate() * desiredSlideAmount) / + mouseTrack->GetRate(); // set it to a sample point if (mSnapManager && mCapturedClip) { double clipLeft = mCapturedClip->GetStartTime() + desiredSlideAmount; double clipRight = mCapturedClip->GetEndTime() + desiredSlideAmount; +#endif double newClipLeft = clipLeft; double newClipRight = clipRight; @@ -2569,7 +2871,11 @@ void TrackPanel::DoSlide(wxMouseEvent & event) { // Make sure we always have the first linked track of a stereo track if (!mouseTrack->GetLinked() && mTracks->GetLink(mouseTrack)) - mouseTrack = (WaveTrack*)mTracks->GetLink(mouseTrack); + mouseTrack = +#ifndef USE_MIDI + (WaveTrack*) +#endif + mTracks->GetLink(mouseTrack); // Temporary apply the offset because we want to see if the // track fits with the desired offset @@ -2578,7 +2884,8 @@ void TrackPanel::DoSlide(wxMouseEvent & event) mCapturedClipArray[i].clip->Offset(desiredSlideAmount); // See if it can be moved if (MoveClipToTrack(mCapturedClip, - (WaveTrack*)mCapturedTrack, mouseTrack)) { + (WaveTrack*)mCapturedTrack, + (WaveTrack*)mouseTrack)) { mCapturedTrack = mouseTrack; mDidSlideVertically = true; @@ -2609,13 +2916,17 @@ void TrackPanel::DoSlide(wxMouseEvent & event) return; } +#ifdef USE_MIDI + if (mCapturedClipArray.GetCount()) { +#else if (mCapturedClip) { +#endif double allowed; double initialAllowed; double safeBigDistance = 1000 + 2.0 * (mTracks->GetEndTime() - mTracks->GetStartTime()); - do { + do { // loop to compute allowed, does not actually move anything yet initialAllowed = mHSlideAmount; unsigned int i, j; @@ -2623,7 +2934,7 @@ void TrackPanel::DoSlide(wxMouseEvent & event) WaveTrack *track = (WaveTrack *)mCapturedClipArray[i].track; WaveClip *clip = mCapturedClipArray[i].clip; - if (clip) { + if (clip) { // only audio clips are used to compute allowed // Move all other selected clips totally out of the way // temporarily because they're all moving together and // we want to find out if OTHER clips are in the way, @@ -2649,7 +2960,7 @@ void TrackPanel::DoSlide(wxMouseEvent & event) } } while (mHSlideAmount != initialAllowed); - if (mHSlideAmount != 0.0) { + if (mHSlideAmount != 0.0) { // finally, here is where clips are moved unsigned int i; for(i=0; iOffset(mHSlideAmount); Track* link = mTracks->GetLink(mCapturedTrack); if (link) @@ -2836,12 +3147,22 @@ void TrackPanel::HandleVZoomClick( wxMouseEvent & event ) return; // don't do anything if track is not wave or Spectrum/log Spectrum - if (mCapturedTrack->GetKind() != Track::Wave - || ((WaveTrack *) mCapturedTrack)->GetDisplay() > WaveTrack::SpectrumLogDisplay) - return; - mMouseCapture = IsVZooming; - mZoomStart = event.m_y; - mZoomEnd = event.m_y; + if ((mCapturedTrack->GetKind() == Track::Wave + && ((WaveTrack *) mCapturedTrack)->GetDisplay() <= WaveTrack::SpectrumLogDisplay) +#ifdef USE_MIDI + || mCapturedTrack->GetKind() == Track::Note +#endif + ) { + mMouseCapture = IsVZooming; + mZoomStart = event.m_y; + mZoomEnd = event.m_y; + // change note track to zoom like audio track +//#ifdef USE_MIDI +// if (mCapturedTrack->GetKind() == Track::Note) { +// ((NoteTrack *) mCapturedTrack)->StartVScroll(); +// } +//#endif + } } /// VZoom drag @@ -2849,6 +3170,12 @@ void TrackPanel::HandleVZoomDrag( wxMouseEvent & event ) { mZoomEnd = event.m_y; if (IsDragZooming()){ + // changed Note track to work like audio track + //#ifdef USE_MIDI + // if (mCapturedTrack && mCapturedTrack->GetKind() == Track::Note) { + // ((NoteTrack *) mCapturedTrack)->VScroll(mZoomStart, mZoomEnd); + // } + //#endif Refresh(false); } } @@ -2866,10 +3193,30 @@ void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event ) mMouseCapture = IsUncaptured; +#ifdef USE_MIDI + // handle vertical scrolling in Note Track. This is so different from + // zooming in audio tracks that it is handled as a special case from + // which we then return + if (mCapturedTrack->GetKind() == Track::Note) { + NoteTrack *nt = (NoteTrack *) mCapturedTrack; + if (IsDragZooming()) { + nt->ZoomTo(mZoomStart, mZoomEnd); + } else if (event.ShiftDown() || event.RightUp()) { + nt->ZoomOut(mZoomEnd); + } else { + nt->ZoomIn(mZoomEnd); + } + mZoomEnd = mZoomStart = 0; + Refresh(false); + mCapturedTrack = NULL; + MakeParentModifyState(); + return; + } +#endif + // don't do anything if track is not wave if (mCapturedTrack->GetKind() != Track::Wave) return; - WaveTrack *track = (WaveTrack *)mCapturedTrack; WaveTrack *partner = (WaveTrack *)mTracks->GetLink(track); int height = track->GetHeight(); @@ -2889,7 +3236,7 @@ void TrackPanel::HandleVZoomButtonUp( wxMouseEvent & event ) int fftSkipPoints=0; #endif //EXPERIMENTAL_FFT_SKIP_POINTS double rate = ((WaveTrack *)track)->GetRate(); - ((WaveTrack *) track)->GetDisplay() == WaveTrack::SpectrumDisplay ? spectrum = true : spectrum = false; + spectrum = ((WaveTrack *) track)->GetDisplay() == WaveTrack::SpectrumDisplay; spectrumLog=(((WaveTrack *) track)->GetDisplay() == WaveTrack::SpectrumLogDisplay); if(spectrum) { min = mTrackArtist->GetSpectrumMinFreq(0); @@ -3625,8 +3972,11 @@ void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan) mMouseCapture = IsUncaptured; float newValue = slider->Get(); - WaveTrack *link = (WaveTrack *)mTracks->GetLink(mCapturedTrack); MixerBoard* pMixerBoard = this->GetMixerBoard(); // Update mixer board, too. + #ifdef USE_MIDI + if (mCapturedTrack->GetKind() == Track::Wave) { + #endif + WaveTrack *link = (WaveTrack *)mTracks->GetLink(mCapturedTrack); if (pan) { ((WaveTrack *)mCapturedTrack)->SetPan(newValue); @@ -3644,6 +3994,19 @@ void TrackPanel::HandleSliders(wxMouseEvent &event, bool pan) if (pMixerBoard) pMixerBoard->UpdateGain((WaveTrack*)mCapturedTrack); } + #ifdef USE_MIDI + } else { + if (!pan) { + ((NoteTrack *) mCapturedTrack)->SetGain(newValue); + #ifdef EXPERIMENTAL_MIXER_BOARD + if (pMixerBoard) + // probably should modify UpdateGain to take a track that is + // either a WaveTrack or a NoteTrack. + pMixerBoard->UpdateGain((WaveTrack*)mCapturedTrack); + #endif + } + } + #endif VisibleTrackIterator iter(GetProject()); for (Track *t = iter.First(); t; t = iter.Next()) @@ -3761,10 +4124,28 @@ void TrackPanel::HandleLabelClick(wxMouseEvent & event) else if (t->GetKind() == Track::Note) { wxRect midiRect; + #ifdef EXPERIMENTAL_MIDI_OUT + // this is an awful hack: make a new rectangle at an offset because + // MuteSoloFunc thinks buttons are located below some text, e.g. + // "Mono, 44100Hz 32-bit float", but this is not true for a Note track + wxRect muteSoloRect(r); + muteSoloRect.y -= 34; // subtract the height of wave track text + if (MuteSoloFunc(t, muteSoloRect, event.m_x, event.m_y, false) || + MuteSoloFunc(t, muteSoloRect, event.m_x, event.m_y, true)) + return; + + // this is a similar hack: GainFunc expects a Wave track slider, so it's + // looking in the wrong place. We pass it a bogus rectangle created when + // the slider was placed to "fake" GainFunc into finding the slider in + // its actual location. + if (GainFunc(t, ((NoteTrack *) t)->GetGainPlacementRect(), + event, event.m_x, event.m_y)) + return; + #endif mTrackInfo.GetTrackControlsRect(r, midiRect); - if (midiRect.Contains(event.m_x, event.m_y)) { - ((NoteTrack *) t)->LabelClick(midiRect, event.m_x, event.m_y, - event.Button(wxMOUSE_BTN_RIGHT)); + if (midiRect.Contains(event.m_x, event.m_y) && + ((NoteTrack *) t)->LabelClick(midiRect, event.m_x, event.m_y, + event.Button(wxMOUSE_BTN_RIGHT))) { Refresh(false); return; } @@ -4785,7 +5166,32 @@ int TrackPanel::DetermineToolToUse( ToolsToolBar * pTtb, wxMouseEvent & event) return currentTool; } -/// method that tells us if the mouse event landed on a + +#ifdef USE_MIDI +bool TrackPanel::HitTestStretch(Track *track, wxRect &r, wxMouseEvent & event) +{ + // later, we may want a different policy, but for now, stretch is + // selected when the cursor is near the center of the track and + // within the selection + if (!track || !track->GetSelected() || track->GetKind() != Track::Note || + gAudioIO->IsStreamActive( GetProject()->GetAudioIOToken())) { + return false; + } + NoteTrack *nt = (NoteTrack *) track; + int center = r.y + r.height / 2; + int distance = abs(event.m_y - center); + const int yTolerance = 10; + wxInt64 leftSel = TimeToPosition(mViewInfo->sel0, r.x); + wxInt64 rightSel = TimeToPosition(mViewInfo->sel1, r.x); + // Something is wrong if right edge comes before left edge + wxASSERT(!(rightSel < leftSel)); + return (leftSel <= event.m_x && event.m_x <= rightSel && + distance < yTolerance); +} +#endif + + +/// method that tells us if the mouse event landed on an /// envelope boundary. bool TrackPanel::HitTestEnvelope(Track *track, wxRect &r, wxMouseEvent & event) { @@ -5083,7 +5489,12 @@ void TrackPanel::DrawEverythingElse(wxDC * dc, } if ((mMouseCapture == IsZooming || mMouseCapture == IsVZooming) && - IsDragZooming()) { + IsDragZooming() + // note track zooming now works like audio track + //#ifdef USE_MIDI + // && mCapturedTrack && mCapturedTrack->GetKind() != Track::Note + //#endif + ) { DrawZooming(dc, clip); } @@ -5162,7 +5573,11 @@ void TrackPanel::DrawOutside(Track * t, wxDC * dc, const wxRect rec, dc->SetTextForeground(theTheme.Colour(clrTrackPanelText)); bool bIsWave = (t->GetKind() == Track::Wave); - +#ifdef USE_MIDI + bool bIsNote = (t->GetKind() == Track::Note); +#endif + // don't enable bHasMuteSolo for Note track because it will draw in the + // wrong place. mTrackInfo.DrawBackground(dc, r, t->GetSelected(), bIsWave, labelw, vrul); // Vaughan, 2010-08-24: No longer doing this. @@ -5220,10 +5635,50 @@ void TrackPanel::DrawOutside(Track * t, wxDC * dc, const wxRect rec, } #ifdef USE_MIDI - else if (t->GetKind() == Track::Note) { + else if (bIsNote) { + // Note tracks do not have text, e.g. "Mono, 44100Hz, 32-bit float", so + // Mute & Solo button goes higher. To preserve existing AudioTrack code, + // we move the buttons up by pretending track is higher (at lower y) + r.y -= 34; + r.height += 34; wxRect midiRect; mTrackInfo.GetTrackControlsRect(trackRect, midiRect); - ((NoteTrack *) t)->DrawLabelControls(*dc, midiRect); + // Offset by height of Solo/Mute buttons: + midiRect.y += 15; + midiRect.height -= 21; // allow room for minimize button at bottom + + // the offset 2 is just to leave a little space between channel buttons + // and velocity slider (if any) + int h = ((NoteTrack *) t)->DrawLabelControls(*dc, midiRect) + 2; + + #ifdef EXPERIMENTAL_MIDI_OUT + // Draw some lines for MuteSolo buttons: + if (r.height > 84) { + AColor::Line(*dc, r.x+48 , r.y+50, r.x+48, r.y + 66); + // bevel below mute/solo + AColor::Line(*dc, r.x, r.y + 66, mTrackInfo.GetTrackInfoWidth(), r.y + 66); + } + mTrackInfo.DrawMuteSolo(dc, r, t, + (captured && mMouseCapture == IsMuting), false, HasSoloButton()); + mTrackInfo.DrawMuteSolo(dc, r, t, + (captured && mMouseCapture == IsSoloing), true, HasSoloButton()); + + // place a volume control below channel buttons (this will + // control an offset to midi velocity). + // DrawVelocitySlider places slider assuming this is a Wave track + // and using a large offset to leave room for other things, + // so here we make a fake rectangle as if it is for a Wave + // track, but it is offset to place the slider properly in + // a Note track. This whole placement thing should be redesigned + // to lay out different types of tracks and controls + wxRect gr; // gr is gain rectangle where slider is drawn + mTrackInfo.GetGainRect(r, gr); + r.y = r.y + h - gr.y; // ultimately want slider at r.y + h + r.height = r.height - h + gr.y; + // save for mouse hit detect: + ((NoteTrack *) t)->SetGainPlacementRect(r); + mTrackInfo.DrawVelocitySlider(dc, (NoteTrack *) t, r); + #endif } #endif // USE_MIDI } @@ -7036,6 +7491,11 @@ bool TrackPanel::MoveClipToTrack(WaveClip *clip, WaveTrack *src2 = NULL; WaveTrack *dst2 = NULL; +#ifdef USE_MIDI + // dst could be a note track. Can't move clip to a note track. + if (dst->GetKind() != Track::Wave) return false; +#endif + // Make sure we have the first track of two stereo tracks // with both source and destination if (!src->GetLinked() && mTracks->GetLink(src)) { @@ -7299,7 +7759,7 @@ void TrackInfo::DrawBackground(wxDC * dc, const wxRect r, bool bSelected, //{ // fill=wxRect( r.x+1, r.y+17, vrul-6, 32); // AColor::BevelTrackInfo( *dc, true, fill ); - + // // fill=wxRect( r.x+1, r.y+67, fill.width, r.height-87); // AColor::BevelTrackInfo( *dc, true, fill ); //} @@ -7320,10 +7780,11 @@ void TrackInfo::GetTrackControlsRect(const wxRect r, wxRect & dest) const dest.x = r.x; dest.width = kTrackInfoWidth - dest.x; - dest.y = top.GetBottom() + 2; + dest.y = top.GetBottom() + 2; // BUG dest.height = bot.GetTop() - top.GetBottom() - 2; } + void TrackInfo::DrawCloseBox(wxDC * dc, const wxRect r, bool down) { wxRect bev; @@ -7509,6 +7970,22 @@ void TrackInfo::EnsureSufficientSliders(int index) MakeMoreSliders(); } +#ifdef EXPERIMENTAL_MIDI_OUT +void TrackInfo::DrawVelocitySlider(wxDC *dc, NoteTrack *t, wxRect r) +{ + wxRect gainRect; + int index = t->GetIndex(); + EnsureSufficientSliders(index); + GetGainRect(r, gainRect); + if (gainRect.y + gainRect.height < r.y + r.height - 19) { + mGains[index]->SetStyle(VEL_SLIDER); + GainSlider(index)->Move(wxPoint(gainRect.x, gainRect.y)); + GainSlider(index)->Set(t->GetGain()); + GainSlider(index)->OnPaint(*dc, t->GetSelected()); + } +} +#endif + void TrackInfo::DrawSliders(wxDC *dc, WaveTrack *t, wxRect r) { wxRect gainRect; @@ -7521,6 +7998,9 @@ void TrackInfo::DrawSliders(wxDC *dc, WaveTrack *t, wxRect r) GetPanRect(r, panRect); if (gainRect.y + gainRect.height < r.y + r.height - 19) { +#ifdef USE_MIDI + GainSlider(index)->SetStyle(DB_SLIDER); +#endif GainSlider(index)->Move(wxPoint(gainRect.x, gainRect.y)); GainSlider(index)->Set(t->GetGain()); GainSlider(index)->OnPaint(*dc, t->GetSelected()); diff --git a/src/TrackPanel.h b/src/TrackPanel.h index 2fd0a34d4..7e27f3538 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -110,6 +110,9 @@ private: void DrawTitleBar(wxDC * dc, const wxRect r, Track * t, bool down); void DrawMuteSolo(wxDC * dc, const wxRect r, Track * t, bool down, bool solo, bool bHasSoloButton); void DrawVRuler(wxDC * dc, const wxRect r, Track * t); +#ifdef EXPERIMENTAL_MIDI_OUT + void DrawVelocitySlider(wxDC * dc, NoteTrack *t, wxRect r); +#endif void DrawSliders(wxDC * dc, WaveTrack *t, wxRect r); // Draw the minimize button *and* the sync-lock track icon, if necessary. @@ -263,6 +266,33 @@ class TrackPanel:public wxPanel { bool HitTestEnvelope(Track *track, wxRect &r, wxMouseEvent & event); bool HitTestSamples(Track *track, wxRect &r, wxMouseEvent & event); bool HitTestSlide(Track *track, wxRect &r, wxMouseEvent & event); +#ifdef USE_MIDI + // data for NoteTrack interactive stretch operations: + // Stretching applies to a selected region after quantizing the + // region to beat boundaries (subbeat stretching is not supported, + // but maybe it should be enabled with shift or ctrl or something) + // Stretching can drag the left boundary (the right stays fixed), + // the right boundary (the left stays fixed), or the center (splits + // the selection into two parts: when left part grows, the right + // part shrinks, keeping the leftmost and rightmost boundaries + // fixed. + enum StretchEnum { + stretchLeft, + stretchCenter, + stretchRight + }; + StretchEnum mStretchMode; // remembers what to drag + bool mStretching; // true between mouse down and mouse up + bool mStretched; // true after drag has pushed state + double mStretchStart; // time of initial mouse position, quantized + // to the nearest beat + double mStretchSel0; // initial sel0 (left) quantized to nearest beat + double mStretchSel1; // initial sel1 (left) quantized to nearest beat + double mStretchLeftBeats; // how many beats from left to cursor + double mStretchRightBeats; // how many beats from cursor to right + bool HitTestStretch(Track *track, wxRect &r, wxMouseEvent & event); + void Stretch(int mouseXCoordinate, int trackLeftEdge, Track *pTrack); +#endif // AS: Selection handling void HandleSelect(wxMouseEvent & event); @@ -501,6 +531,10 @@ private: // us to undo the slide and then slide it by another amount double mHSlideAmount; +#ifdef USE_MIDI + NoteTrack *mCapturedNoteClip; +#endif + bool mDidSlideVertically; bool mRedrawAfterStop; @@ -564,6 +598,9 @@ private: IsOverCutLine, WasOverCutLine, IsPopping, +#ifdef USE_MIDI + IsStretching, +#endif IsZooming }; @@ -595,6 +632,11 @@ private: wxCursor *mDisabledCursor; wxCursor *mAdjustLeftSelectionCursor; wxCursor *mAdjustRightSelectionCursor; +#if USE_MIDI + wxCursor *mStretchCursor; + wxCursor *mStretchLeftCursor; + wxCursor *mStretchRightCursor; +#endif wxMenu *mWaveTrackMenu; wxMenu *mNoteTrackMenu; diff --git a/src/UndoManager.cpp b/src/UndoManager.cpp index 10bff9e04..64e4c8ff3 100644 --- a/src/UndoManager.cpp +++ b/src/UndoManager.cpp @@ -27,6 +27,7 @@ UndoManager #include "Sequence.h" #include "Track.h" #include "WaveTrack.h" // temp +#include "NoteTrack.h" // for Sonify* function declarations #include #include @@ -185,6 +186,7 @@ bool UndoManager::RedoAvailable() void UndoManager::ModifyState(TrackList * l, double sel0, double sel1) { + SonifyBeginModifyState(); // Delete current stack[current]->tracks->Clear(true); delete stack[current]->tracks; @@ -202,6 +204,7 @@ void UndoManager::ModifyState(TrackList * l, double sel0, double sel1) stack[current]->tracks = tracksCopy; stack[current]->sel0 = sel0; stack[current]->sel1 = sel1; + SonifyEndModifyState(); } void UndoManager::PushState(TrackList * l, double sel0, double sel1, diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index d047deab7..96c8ebd6f 100644 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -487,13 +487,16 @@ void Effect::Preview() if (mixRight) playbackTracks.Add(mixRight); +#ifdef EXPERIMENTAL_MIDI_OUT + NoteTrackArray empty; +#endif // Start audio playing int token = - gAudioIO->StartStream(playbackTracks, recordingTracks, NULL, + gAudioIO->StartStream(playbackTracks, recordingTracks, #ifdef EXPERIMENTAL_MIDI_OUT - NULL, + empty, #endif - rate, t0, t1, NULL); + NULL, rate, t0, t1, NULL); if (token) { int previewing = eProgressSuccess; diff --git a/src/effects/ScoreAlignDialog.cpp b/src/effects/ScoreAlignDialog.cpp new file mode 100644 index 000000000..f4ecacfb8 --- /dev/null +++ b/src/effects/ScoreAlignDialog.cpp @@ -0,0 +1,264 @@ +#include "../Audacity.h" + +// For compilers that support precompilation, includes "wx/wx.h". +#include + +#ifdef __BORLANDC__ +#pragma hdrstop +#endif + +#ifndef WX_PRECOMP +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include "../Prefs.h" +#include "../ShuttleGui.h" +#include "allegro.h" +#include "audioreader.h" +#include "scorealign.h" +#include "scorealign-glue.h" +#include "ScoreAlignDialog.h" + +static ScoreAlignDialog *gScoreAlignDialog = NULL; + +//IMPLEMENT_CLASS(ScoreAlignDialog, wxDialog) + +ScoreAlignDialog::ScoreAlignDialog(wxWindow *parent, ScoreAlignParams ¶ms) + : wxDialog(parent, -1, _("Align MIDI to Audio"), + wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE) +{ + gScoreAlignDialog = this; // Allows anyone to close dialog by calling + // CloseScoreAlignDialog() + gPrefs->Read(wxT("/Tracks/Synchronize/FramePeriod"), &p.mFramePeriod, + float(SA_DFT_FRAME_PERIOD)); + gPrefs->Read(wxT("/Tracks/Synchronize/WindowSize"), &p.mWindowSize, + float(SA_DFT_WINDOW_SIZE)); + gPrefs->Read(wxT("/Tracks/Synchronize/SilenceThreshold"), + &p.mSilenceThreshold, float(SA_DFT_SILENCE_THRESHOLD)); + gPrefs->Read(wxT("/Tracks/Synchronize/ForceFinalAlignment"), + &p.mForceFinalAlignment, float(SA_DFT_FORCE_FINAL_ALIGNMENT)); + gPrefs->Read(wxT("/Tracks/Synchronize/IgnoreSilence"), + &p.mIgnoreSilence, float(SA_DFT_IGNORE_SILENCE)); + gPrefs->Read(wxT("/Tracks/Synchronize/PresmoothTime"), &p.mPresmoothTime, + float(SA_DFT_PRESMOOTH_TIME)); + gPrefs->Read(wxT("/Tracks/Synchronize/LineTime"), &p.mLineTime, + float(SA_DFT_LINE_TIME)); + gPrefs->Read(wxT("/Tracks/Synchronize/SmoothTime"), &p.mSmoothTime, + float(SA_DFT_SMOOTH_TIME)); + + //wxButton *ok = new wxButton(this, wxID_OK, _("OK")); + //wxButton *cancel = new wxButton(this, wxID_CANCEL, _("Cancel")); + //wxSlider *sl = new wxSlider(this, ID_SLIDER, 0, 0, 100, + // wxDefaultPosition, wxSize(20, 124), + // wxSL_HORIZONTAL); + + ShuttleGui S(this, eIsCreating); + //ok->SetDefault(); + + S.SetBorder(5); + S.StartVerticalLay(true); + S.StartStatic(wxT("Align MIDI to Audio")); + S.StartMultiColumn(3, wxEXPAND | wxALIGN_CENTER_VERTICAL); + S.SetStretchyCol(1); + + mFramePeriodLabel = S.AddVariableText(_("Frame Period:"), true, + wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mFramePeriodSlider = S.Id(ID_FRAMEPERIOD).AddSlider(wxT(""), + /*pos*/ (int) (p.mFramePeriod * 100 + 0.5), /*max*/ 50, /*min*/ 5); + S.SetSizeHints(300, -1); + mFramePeriodSlider->SetName(_("Frame Period")); + mFramePeriodText = S.AddVariableText(SA_DFT_FRAME_PERIOD_TEXT, true, + wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + mWindowSizeLabel = S.AddVariableText(_("Window Size:"), true, + wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mWindowSizeSlider = S.Id(ID_WINDOWSIZE).AddSlider(wxT(""), + /*pos*/ (int) (p.mWindowSize * 100 + 0.5), /*max*/ 100, /*min*/ 5); + mWindowSizeSlider->SetName(_("Window Size")); + mWindowSizeText = S.AddVariableText(SA_DFT_WINDOW_SIZE_TEXT, true, + wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + mForceFinalAlignmentCheckBox = S.Id(ID_FORCEFINALALIGNMENT).AddCheckBox( + wxT("Force Final Alignment"), + (p.mForceFinalAlignment ? wxT("true") : wxT("false"))); + mForceFinalAlignmentCheckBox->SetName(_("Force Final Alignment")); + mIgnoreSilenceCheckBox = S.Id(ID_IGNORESILENCE).AddCheckBox( + wxT("Ignore Silence at Beginnings and Endings"), + (p.mIgnoreSilence ? wxT("true") : wxT("false"))); + mIgnoreSilenceCheckBox->SetName( + _("Ignore Silence at Beginnings and Endings")); + // need a third column after checkboxes: + S.AddVariableText(_(""), true, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + + mSilenceThresholdLabel = S.AddVariableText(_("Silence Threshold:"), + true, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mSilenceThresholdSlider = S.Id(ID_SILENCETHRESHOLD).AddSlider(wxT(""), + /*pos*/ (int) (p.mSilenceThreshold * 1000 + 0.5), /*max*/ 500); + mSilenceThresholdSlider->SetName(_("Silence Threshold")); + mSilenceThresholdText = S.AddVariableText(SA_DFT_SILENCE_THRESHOLD_TEXT, + true, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + mPresmoothLabel = S.AddVariableText(_("Presmooth Time:"), true, + wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mPresmoothSlider = S.Id(ID_PRESMOOTH).AddSlider(wxT(""), + /*pos*/ (int) (p.mPresmoothTime * 100 + 0.5), /*max*/ 500); + mPresmoothSlider->SetName(_("Presmooth Time")); + mPresmoothText = S.AddVariableText(SA_DFT_PRESMOOTH_TIME_TEXT, true, + wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + mLineTimeLabel = S.AddVariableText(_("Line Time:"), true, + wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mLineTimeSlider = S.Id(ID_LINETIME).AddSlider(wxT(""), + /*pos*/ (int) (p.mLineTime * 100 + 0.5), /*max*/ 500); + mLineTimeSlider->SetName(_("Line Time")); + mLineTimeText = S.AddVariableText(SA_DFT_LINE_TIME_TEXT, true, + wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + mSmoothTimeLabel = S.AddVariableText(_("Smooth Time:"), true, + wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); + S.SetStyle(wxSL_HORIZONTAL); + mSmoothTimeSlider = S.Id(ID_SMOOTHTIME).AddSlider(wxT(""), + /*pos*/ (int) (p.mSmoothTime * 100 + 0.5), /*max*/ 500); + mSmoothTimeSlider->SetName(_("Smooth Time")); + mSmoothTimeText = S.AddVariableText(SA_DFT_SMOOTH_TIME_TEXT, true, + wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + + S.EndMultiColumn(); + S.EndStatic(); + + mDefaultButton = new wxButton(this, ID_DEFAULT, _("Use Defaults")); + mDefaultButton->SetName(_("Restore Defaults")); + + S.AddStandardButtons(eOkButton | eCancelButton, mDefaultButton); + S.EndVerticalLay(); + Layout(); + Fit(); + Center(); + + TransferDataFromWindow(); // set labels according to actual initial values + + params.mStatus = p.mStatus = ShowModal(); + + if (p.mStatus == wxID_OK) { + // Retain the settings + gPrefs->Write(wxT("/Tracks/Synchronize/FramePeriod"), p.mFramePeriod); + gPrefs->Write(wxT("/Tracks/Synchronize/WindowSize"), p.mWindowSize); + gPrefs->Write(wxT("/Tracks/Synchronize/SilenceThreshold"), + p.mSilenceThreshold); + gPrefs->Write(wxT("/Tracks/Synchronize/ForceFinalAlignment"), + p.mForceFinalAlignment); + gPrefs->Write(wxT("/Tracks/Synchronize/IgnoreSilence"), + p.mIgnoreSilence); + gPrefs->Write(wxT("/Tracks/Synchronize/PresmoothTime"), + p.mPresmoothTime); + gPrefs->Write(wxT("/Tracks/Synchronize/LineTime"), p.mLineTime); + gPrefs->Write(wxT("/Tracks/Synchronize/SmoothTime"), p.mSmoothTime); + params = p; // return all parameters through params + } +} + +ScoreAlignDialog::~ScoreAlignDialog() +{ +} + + +//void ScoreAlignDialog::OnOK(wxCommandEvent & event) +//{ +// EndModal(wxID_OK); +//} + +//void ScoreAlignDialog::OnCancel(wxCommandEvent & event) +//{ +// EndModal(wxID_CANCEL); +//} + +void ScoreAlignDialog::OnSlider(wxCommandEvent & event) +{ + TransferDataFromWindow(); +} + + +void ScoreAlignDialog::OnDefault(wxCommandEvent & event) +{ + mFramePeriodSlider->SetValue((int) (SA_DFT_FRAME_PERIOD * 100 + 0.5)); + mWindowSizeSlider->SetValue((int) (SA_DFT_WINDOW_SIZE * 100 + 0.5)); + mSilenceThresholdSlider->SetValue( + (int) (SA_DFT_SILENCE_THRESHOLD * 1000 + 0.5)); + mForceFinalAlignmentCheckBox->SetValue(SA_DFT_FORCE_FINAL_ALIGNMENT); + mIgnoreSilenceCheckBox->SetValue(SA_DFT_IGNORE_SILENCE); + mPresmoothSlider->SetValue((int) (SA_DFT_PRESMOOTH_TIME * 100 + 0.5)); + mLineTimeSlider->SetValue((int) (SA_DFT_LINE_TIME * 100 + 0.5)); + mSmoothTimeSlider->SetValue((int) (SA_DFT_SMOOTH_TIME * 100 + 0.5)); + + TransferDataFromWindow(); +} + + +bool ScoreAlignDialog::TransferDataFromWindow() +{ + p.mFramePeriod = (double) mFramePeriodSlider->GetValue() / 100.0; + p.mWindowSize = (double) mWindowSizeSlider->GetValue() / 100.0; + p.mSilenceThreshold = (double) mSilenceThresholdSlider->GetValue() / 1000.0; + p.mForceFinalAlignment = (double) mForceFinalAlignmentCheckBox->GetValue(); + p.mIgnoreSilence = (double) mIgnoreSilenceCheckBox->GetValue(); + p.mPresmoothTime = (double) mPresmoothSlider->GetValue() / 100.0; + p.mLineTime = (double) mLineTimeSlider->GetValue() / 100.0; + p.mSmoothTime = (double) mSmoothTimeSlider->GetValue() / 100.0; + + mFramePeriodText->SetLabel(wxString::Format(_("%.2f secs"), + p.mFramePeriod)); + mWindowSizeText->SetLabel(wxString::Format(_("%.2f secs"), p.mWindowSize)); + mSilenceThresholdText->SetLabel(wxString::Format(_("%.3f"), + p.mSilenceThreshold)); + mPresmoothText->SetLabel(p.mPresmoothTime > 0 ? + wxString::Format(_("%.2f secs"), + p.mPresmoothTime) : wxT("(off)")); + mLineTimeText->SetLabel(p.mLineTime > 0 ? + wxString::Format(_("%.2f secs"), p.mLineTime) : + wxT("(off)")); + mSmoothTimeText->SetLabel(wxString::Format(_("%.2f secs"), p.mSmoothTime)); + return true; +} + + +void CloseScoreAlignDialog() +{ + if (gScoreAlignDialog) { + delete gScoreAlignDialog; + gScoreAlignDialog = NULL; + } +} + + +BEGIN_EVENT_TABLE(ScoreAlignDialog, wxDialog) +// EVT_BUTTON(wxID_OK, ScoreAlignDialog::OnOK) +// EVT_BUTTON(wxID_CANCEL, ScoreAlignDialog::OnCancel) + EVT_BUTTON(ID_DEFAULT, ScoreAlignDialog::OnDefault) + EVT_SLIDER(ID_FRAMEPERIOD, ScoreAlignDialog::OnSlider) + EVT_SLIDER(ID_WINDOWSIZE, ScoreAlignDialog::OnSlider) + EVT_SLIDER(ID_SILENCETHRESHOLD, ScoreAlignDialog::OnSlider) + EVT_CHECKBOX(ID_FORCEFINALALIGNMENT, ScoreAlignDialog::OnSlider) + EVT_CHECKBOX(ID_IGNORESILENCE, ScoreAlignDialog::OnSlider) + EVT_SLIDER(ID_PRESMOOTH, ScoreAlignDialog::OnSlider) + EVT_SLIDER(ID_LINETIME, ScoreAlignDialog::OnSlider) + EVT_SLIDER(ID_SMOOTHTIME, ScoreAlignDialog::OnSlider) +END_EVENT_TABLE() diff --git a/src/effects/ScoreAlignDialog.h b/src/effects/ScoreAlignDialog.h new file mode 100644 index 000000000..670749abb --- /dev/null +++ b/src/effects/ScoreAlignDialog.h @@ -0,0 +1,104 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + ScoreAlignDialog.h + +**********************************************************************/ + +#ifndef __AUDACITY_SCORE_ALIGN_DIALOG__ +#define __AUDACITY_SCORE_ALIGN_DIALOG__ + +#include +#include +#include "ScoreAlignParams.h" + +class wxButton; +class wxSizer; +class wxString; + +void CloseScoreAlignDialog(); + +//---------------------------------------------------------------------------- +// ScoreAlignDialog +//---------------------------------------------------------------------------- + +// Declare window functions + +class ScoreAlignDialog : public wxDialog +{ +public: + ScoreAlignParams p; + + wxStaticText *mFramePeriodLabel; + wxSlider *mFramePeriodSlider; + wxStaticText *mFramePeriodText; + + wxStaticText *mWindowSizeLabel; + wxSlider *mWindowSizeSlider; + wxStaticText *mWindowSizeText; + + wxStaticText *mSilenceThresholdLabel; + wxSlider *mSilenceThresholdSlider; + wxStaticText *mSilenceThresholdText; + + wxCheckBox *mForceFinalAlignmentCheckBox; + wxCheckBox *mIgnoreSilenceCheckBox; + + wxStaticText *mPresmoothLabel; + wxSlider *mPresmoothSlider; + wxStaticText *mPresmoothText; + + wxStaticText *mLineTimeLabel; + wxSlider *mLineTimeSlider; + wxStaticText *mLineTimeText; + + wxStaticText *mSmoothTimeLabel; + wxSlider *mSmoothTimeSlider; + wxStaticText *mSmoothTimeText; + + wxButton *mDefaultButton; + + // constructors and destructors + ScoreAlignDialog(wxWindow * parent, ScoreAlignParams ¶ms); + ~ScoreAlignDialog(); + + bool TransferDataFromWindow(); + +private: + enum { + ID_BASE = 10000, + ID_PRESMOOTH, + ID_WINDOWSIZE, + ID_FRAMEPERIOD, + ID_LINETIME, + ID_SMOOTHTIME, + ID_SILENCETHRESHOLD, + ID_FORCEFINALALIGNMENT, + ID_IGNORESILENCE, + ID_DEFAULT + }; + + // handlers + void OnOK(wxCommandEvent & event); + void OnCancel(wxCommandEvent & event); + void OnSlider(wxCommandEvent & event); + void OnDefault(wxCommandEvent & event); + + DECLARE_EVENT_TABLE() + +}; + +#endif + +// Indentation settings for Vim and Emacs and unique identifier for Arch, a +// version control system. Please do not modify past this point. +// +// Local Variables: +// c-basic-offset: 3 +// indent-tabs-mode: nil +// End: +// +// vim: et sts=3 sw=3 + + diff --git a/src/effects/nyquist/Nyquist.cpp b/src/effects/nyquist/Nyquist.cpp index fa214804a..c329e18a3 100644 --- a/src/effects/nyquist/Nyquist.cpp +++ b/src/effects/nyquist/Nyquist.cpp @@ -317,6 +317,8 @@ void EffectNyquist::Parse(wxString line) void EffectNyquist::ParseFile() { + wxLogDebug(wxT("EffectNyquist::ParseFile called")); + wxTextFile f(mFileName.GetFullPath()); if (!f.Open()) return; @@ -326,6 +328,7 @@ void EffectNyquist::ParseFile() mOK = false; mIsSal = false; mControls.Clear(); + mDebug = false; int i; int len = f.GetLineCount(); @@ -421,9 +424,10 @@ bool EffectNyquist::PromptUser() return false; } - if (result == eDebugID) { + /*if (result == eDebugID) { mDebug = true; - } + }*/ + mDebug = (result == eDebugID); mCmd = dlog.GetCommand(); @@ -604,7 +608,7 @@ bool EffectNyquist::Process() this->ReplaceProcessedTracks(success); - mDebug = false; + //mDebug = false; return success; } diff --git a/src/import/ImportMIDI.cpp b/src/import/ImportMIDI.cpp index 711971bed..f42b61617 100644 --- a/src/import/ImportMIDI.cpp +++ b/src/import/ImportMIDI.cpp @@ -17,14 +17,14 @@ #if defined(USE_MIDI) +//#include "allegro.h" +//#include "strparse.h" +//#include "mfmidi.h" + #include "../Internat.h" #include "../NoteTrack.h" #include "ImportMIDI.h" -#include "allegro.h" -#include "strparse.h" -#include "mfmidi.h" - bool ImportMIDI(wxString fName, NoteTrack * dest) { if (fName.Length() <= 4){ @@ -46,7 +46,8 @@ bool ImportMIDI(wxString fName, NoteTrack * dest) return false; } - Alg_seq_ptr new_seq = new Alg_seq(fName.mb_str(), is_midi); + double offset = 0.0; + Alg_seq_ptr new_seq = new Alg_seq(fName.mb_str(), is_midi, &offset); //Should we also check if(seq->tracks() == 0) ? if(new_seq->get_read_error() == alg_error_open){ @@ -56,7 +57,30 @@ bool ImportMIDI(wxString fName, NoteTrack * dest) } dest->SetSequence(new_seq); + dest->SetOffset(offset); + wxString trackNameBase = fName.AfterLast(wxFILE_SEP_PATH).BeforeLast('.'); + dest->SetName(trackNameBase); mf.Close(); + // the mean pitch should be somewhere in the middle of the display + Alg_iterator iterator(new_seq, false); + iterator.begin(); + // for every event + Alg_event_ptr evt; + int note_count = 0; + int pitch_sum = 0; + while (evt = iterator.next()) { + // if the event is a note + if (evt->get_type() == 'n') { + Alg_note_ptr note = (Alg_note_ptr) evt; + pitch_sum += (int) note->pitch; + note_count++; + } + } + int mean_pitch = (note_count > 0 ? pitch_sum / note_count : 60); + // initial track is about 27 half-steps high; if bottom note is C, + // then middle pitch class is D. Round mean_pitch to the nearest D: + int mid_pitch = ((mean_pitch - 2 + 6) / 12) * 12 + 2; + dest->SetBottomNote(mid_pitch - 14); return true; } diff --git a/src/prefs/ImportExportPrefs.cpp b/src/prefs/ImportExportPrefs.cpp index c802bc584..b922eeca5 100644 --- a/src/prefs/ImportExportPrefs.cpp +++ b/src/prefs/ImportExportPrefs.cpp @@ -84,6 +84,20 @@ void ImportExportPrefs::PopulateOrExchange(ShuttleGui & S) S.AddFixedText(_("Note: Export quality options can be chosen by clicking the Options\nbutton in the Export dialog.")); } S.EndStatic(); +#ifdef USE_MIDI + S.StartStatic(_("When exporting track to an Allegro (.gro) file")); + { + S.StartRadioButtonGroup(wxT("/FileFormats/AllegroStyle"), true); + { + S.TieRadioButton(_("Represent times and durations in &seconds"), + true); + S.TieRadioButton(_("Represent times and durations in &beats"), + false); + } + S.EndRadioButtonGroup(); + } + S.EndStatic(); +#endif } bool ImportExportPrefs::Apply() diff --git a/src/prefs/MidiIOPrefs.cpp b/src/prefs/MidiIOPrefs.cpp index ec8f8d78a..c172bb5cb 100644 --- a/src/prefs/MidiIOPrefs.cpp +++ b/src/prefs/MidiIOPrefs.cpp @@ -92,6 +92,8 @@ void MidiIOPrefs::Populate() /// The corresponding labels are what gets stored. void MidiIOPrefs::GetNamesAndLabels() { // Gather list of hosts. Only added hosts that have devices attached. + Pm_Terminate(); // close and open to refresh device lists + Pm_Initialize(); int nDevices = Pm_CountDevices(); for (int i = 0; i < nDevices; i++) { const PmDeviceInfo *info = Pm_GetDeviceInfo(i); @@ -122,8 +124,7 @@ void MidiIOPrefs::PopulateOrExchange( ShuttleGui & S ) { mHostLabels); S.SetSizeHints(mHostNames); - S.AddPrompt(_("Using:")); - S.AddFixedText(wxString(Pa_GetVersionText(), wxConvLocal)); + S.AddPrompt(_("Using: PortMidi")); } S.EndMultiColumn(); } @@ -137,6 +138,11 @@ void MidiIOPrefs::PopulateOrExchange( ShuttleGui & S ) { mPlay = S.AddChoice(_("Device") + wxString(wxT(":")), wxEmptyString, &empty); + int latency = gPrefs->Read(wxT("/MidiIO/OutputLatency"), + DEFAULT_SYNTH_LATENCY); + mLatency = S.TieTextBox(_("MIDI Synthesizer Latency (ms):"), + wxT("/MidiIO/SynthLatency"), + latency, 3); } S.EndMultiColumn(); } @@ -163,88 +169,6 @@ void MidiIOPrefs::PopulateOrExchange( ShuttleGui & S ) { S.EndStatic(); } -// Not sure that these settings are needed right now. -#if 0 - S.StartStatic( _("Playthrough") ); - { - S.TieCheckBox( _("&Play other tracks while recording new one"), - wxT("Duplex"),true); -#ifdef __MACOSX__ - S.TieCheckBox( _("&Hardware Playthrough (Play new track while recording it)"), - wxT("Playthrough"),false); -#endif - S.TieCheckBox( _("&Software Playthrough (Play new track while recording it)"), - wxT("SWPlaythrough"),false); - } - S.EndStatic(); - S.StartHorizontalLay( wxEXPAND, 0 ); - S.StartStatic( _("Cut Preview"),1 ); - { - S.StartThreeColumn(); - S.TieTextBox( _("Play before cut region:"), wxT("CutPreviewBeforeLen"),1.0,9); - S.AddUnits( _("seconds") ); - S.TieTextBox( _("Play after cut region:"),wxT("CutPreviewAfterLen"), 1.0,9); - S.AddUnits( _("seconds") ); - S.EndThreeColumn(); - } - S.EndStatic(); - S.StartStatic( _("Latency"),1 ); - { - S.StartThreeColumn(); - // only show the following controls if we use Portaudio v19, because - // for Portaudio v19 we always use default buffer sizes - S.TieTextBox( _("Audio to buffer:"),wxT("LatencyDuration"),100.0,9); - S.AddUnits( _("milliseconds") ); - S.TieTextBox( _("Latency correction:"),wxT("LatencyCorrection"),0.0,9); - S.AddUnits( _("milliseconds") ); - S.EndThreeColumn(); - } - S.EndStatic(); - S.EndHorizontalLay(); - S.StartHorizontalLay( wxEXPAND, 0 ); - S.StartStatic( _("Seek Time"),1 ); - { - S.StartThreeColumn(); - S.TieTextBox( _("Short period:"), wxT("SeekShortPeriod"),1.0,9); - S.AddUnits( _("seconds") ); - S.TieTextBox( _("Long period:"),wxT("SeekLongPeriod"), 15.0,9); - S.AddUnits( _("seconds") ); - S.EndThreeColumn(); - } - S.EndStatic(); - S.StartStatic( _("Effects Preview"),1 ); - { - S.StartThreeColumn(); - S.TieTextBox( _("Play when previewing:"), wxT("EffectsPreviewLen"), 3.0,9); - S.AddUnits( _("seconds") ); - S.EndThreeColumn(); - } - - gPrefs->SetPath(wxT("/")); -#endif - -// JKC: This is some old code that was sizing control labels all the same, -// even if in different static controls. It made for a nicer layout. -// We might want to do something like that again in future. -#if 0 - - // find out the biggest minimum size of labels - int maxIndex = 0,r; - wxSize maxMinSize = textSizer[0]->GetMinSize(); - for (r = 1; r < 3; r++) { - if (textSizer[r]->GetMinSize().GetWidth() > maxMinSize.GetWidth()) { - maxMinSize = textSizer[r]->GetMinSize(); - maxIndex = r; - } - } - - // set small minimum sizes to max minumum size - for (r = 0; r < 3; r++) { - if (r != maxIndex) - textSizer[r]->SetMinSize( maxMinSize ); - } -#endif - void MidiIOPrefs::OnHost(wxCommandEvent & e) { int index = mHost->GetCurrentSelection(); @@ -315,65 +239,6 @@ void MidiIOPrefs::OnHost(wxCommandEvent & e) // OnDevice(e); } -/* -void MidiIOPrefs::OnDevice(wxCommandEvent & e) -{ - int ndx = mRecord->GetCurrentSelection(); - if (ndx == wxNOT_FOUND) { - ndx = 0; - } - - int sel = mChannels->GetSelection(); - int cnt = 0; - - const PmDeviceInfo *info = (const PmDeviceInfo *) mRecord->GetClientData(ndx); - if (info != NULL) { - cnt = info->input; - } - - if (sel != wxNOT_FOUND) { - mRecordChannels = sel + 1; - } - - mChannels->Clear(); - - // Limit cnt - cnt = cnt <= 0 ? 16 : cnt; - cnt = cnt > 256 ? 256 : cnt; - - wxArrayString channelnames; - - // Channel counts, mono, stereo etc... - for (int i = 0; i < cnt; i++) { - wxString name; - - if (i == 0) { - name = _("1 (Mono)"); - } - else if (i == 1) { - name = _("2 (Stereo)"); - } - else { - name = wxString::Format(wxT("%d"), i + 1); - } - - channelnames.Add(name); - int index = mChannels->Append(name); - if (i == mRecordChannels - 1) { - mChannels->SetSelection(index); - } - } - - if (mChannels->GetCount() && mChannels->GetCurrentSelection() == wxNOT_FOUND) { - mChannels->SetSelection(0); - } - - ShuttleGui S(this, eIsCreating); - S.SetSizeHints(mChannels, channelnames); - Layout(); -} -*/ - bool MidiIOPrefs::Apply() { ShuttleGui S(this, eIsSavingToPrefs); @@ -397,17 +262,16 @@ bool MidiIOPrefs::Apply() wxString(info->name, wxConvLocal).c_str())); } -/* - gPrefs->Write(wxT("/MidiIO/RecordChannels"), - wxString::Format(wxT("%d"), - mChannels->GetSelection() + 1)); -*/ - - #if USE_PORTMIXER - if (gAudioIO) - gAudioIO->HandleDeviceChange(); - #endif // USE_PORTMIXER + return true; +} +bool MidiIOPrefs::Validate() +{ + long latency; + if (!mLatency->GetValue().ToLong(&latency)) { + wxMessageBox(_("The MIDI Synthesizer Latency must be an integer")); + return false; + } return true; } diff --git a/src/prefs/MidiIOPrefs.h b/src/prefs/MidiIOPrefs.h index 5ada5283f..4bd27be74 100644 --- a/src/prefs/MidiIOPrefs.h +++ b/src/prefs/MidiIOPrefs.h @@ -31,6 +31,7 @@ class MidiIOPrefs:public PrefsPanel MidiIOPrefs(wxWindow * parent); virtual ~MidiIOPrefs(); virtual bool Apply(); + virtual bool Validate(); private: void Populate(); @@ -49,6 +50,7 @@ class MidiIOPrefs:public PrefsPanel wxChoice *mHost; wxChoice *mPlay; + wxTextCtrl *mLatency; wxChoice *mRecord; // wxChoice *mChannels; diff --git a/src/toolbars/ControlToolBar.cpp b/src/toolbars/ControlToolBar.cpp index 93808e0de..25e5f4f53 100644 --- a/src/toolbars/ControlToolBar.cpp +++ b/src/toolbars/ControlToolBar.cpp @@ -544,7 +544,7 @@ void ControlToolBar::PlayPlayRegion(double t0, double t1, mCutPreviewTracks->GetWaveTrackArray(false), WaveTrackArray(), #ifdef EXPERIMENTAL_MIDI_OUT - &NoteTrackArray(), + NoteTrackArray(), #endif NULL, p->GetRate(), tcp0, tcp1, p, false, t0, t1-t0); @@ -563,7 +563,7 @@ void ControlToolBar::PlayPlayRegion(double t0, double t1, token = gAudioIO->StartStream(t->GetWaveTrackArray(false), WaveTrackArray(), #ifdef EXPERIMENTAL_MIDI_OUT - &(t->GetNoteTrackArray(false)), + t->GetNoteTrackArray(false), #endif timetrack, p->GetRate(), t0, t1, p, looped); @@ -900,7 +900,7 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt) int token = gAudioIO->StartStream(playbackTracks, newRecordingTracks, #ifdef EXPERIMENTAL_MIDI_OUT - &midiTracks, + midiTracks, #endif t->GetTimeTrack(), p->GetRate(), t0, t1, p); diff --git a/src/widgets/ASlider.cpp b/src/widgets/ASlider.cpp index 5226b3770..098a15c3e 100644 --- a/src/widgets/ASlider.cpp +++ b/src/widgets/ASlider.cpp @@ -360,6 +360,58 @@ LWSlider::LWSlider(wxWindow * parent, stepValue, canUseShift, style, heavyweight, popup, 1.0, orientation); } + +void LWSlider::SetStyle(int style) +{ + mStyle = style; + mSpeed = 1.0; + switch(style) + { + case PAN_SLIDER: + mMinValue = -1.0f; + mMaxValue = +1.0f; + mStepValue = 0.1f; + mOrientation = wxHORIZONTAL; //v Vertical PAN_SLIDER currently not handled, forced to horizontal. + mName = _("Pan"); + break; + case DB_SLIDER: + mMinValue = DB_MIN; + if (mOrientation == wxHORIZONTAL) + mMaxValue = DB_MAX; + else + mMaxValue = DB_MAX; // for MixerBoard //vvv Previously was 6dB for MixerBoard, but identical for now. + mStepValue = 1.0f; + mSpeed = 0.5; + mName = _("Gain"); + break; + case FRAC_SLIDER: + mMinValue = FRAC_MIN; + mMaxValue = FRAC_MAX; + mStepValue = STEP_CONTINUOUS; + break; + case SPEED_SLIDER: + mMinValue = SPEED_MIN; + mMaxValue = SPEED_MAX; + mStepValue = STEP_CONTINUOUS; + break; +#ifdef USE_MIDI + case VEL_SLIDER: + mMinValue = VEL_MIN; + mMaxValue = VEL_MAX; + mStepValue = 1.0f; + mSpeed = 0.5; + mName = _("Velocity"); + break; +#endif + default: + mMinValue = FRAC_MIN; + mMaxValue = FRAC_MAX; + mStepValue = 0.0f; + wxASSERT(false); // undefined style + } +} + + // Construct predefined slider LWSlider::LWSlider(wxWindow *parent, wxString name, @@ -371,46 +423,13 @@ LWSlider::LWSlider(wxWindow *parent, int orientation /* = wxHORIZONTAL */) // wxHORIZONTAL or wxVERTICAL. wxVERTICAL is currently only for DB_SLIDER. { wxString leftLabel, rightLabel; - float minValue, maxValue, stepValue; - float speed = 1.0; + mOrientation = orientation; + mName = name; - switch(style) - { - case PAN_SLIDER: - minValue = -1.0f; - maxValue = +1.0f; - stepValue = 0.1f; - orientation = wxHORIZONTAL; //v Vertical PAN_SLIDER currently not handled, forced to horizontal. - break; - case DB_SLIDER: - minValue = -36.0f; - if (orientation == wxHORIZONTAL) - maxValue = 36.0f; - else - maxValue = 36.0f; // for MixerBoard //vvv Previously was 6dB for MixerBoard, but identical for now. - stepValue = 1.0f; - speed = 0.5; - break; - case FRAC_SLIDER: - minValue = 0.0f; - maxValue = 1.0f; - stepValue = STEP_CONTINUOUS; - break; - case SPEED_SLIDER: - minValue = 0.01f; - maxValue = 3.0f; - stepValue = STEP_CONTINUOUS; - break; + SetStyle(style); - default: - minValue = 0.0f; - maxValue = 1.0f; - stepValue = 0.0f; - wxASSERT(false); // undefined style - } - - Init(parent, name, pos, size, minValue, maxValue, stepValue, - true, style, heavyweight, popup, speed, orientation); + Init(parent, mName, pos, size, mMinValue, mMaxValue, mStepValue, + true, style, heavyweight, popup, mSpeed, mOrientation); } void LWSlider::Init(wxWindow * parent, @@ -520,7 +539,11 @@ void LWSlider::CreatePopWin() wxString maxStr = mName + wxT(": 000000"); - if (mStyle == PAN_SLIDER || mStyle == DB_SLIDER || mStyle == SPEED_SLIDER) + if (mStyle == PAN_SLIDER || mStyle == DB_SLIDER || mStyle == SPEED_SLIDER +#ifdef USE_MIDI + || mStyle == VEL_SLIDER +#endif + ) maxStr += wxT("000"); mPopWin = new TipPanel(GetToolTipParent(), -1, maxStr, wxDefaultPosition); @@ -886,6 +909,12 @@ void LWSlider::FormatPopWin() case SPEED_SLIDER: label.Printf(wxT("%s: %.2fx"), mName.c_str(), mCurrentValue); break; +#ifdef USE_MIDI + case VEL_SLIDER: + label.Printf(wxT("%s: %s%d"), mName.c_str(), + (mCurrentValue > 0.0f ? _("+") : _("")), + (int) mCurrentValue); +#endif } ((TipPanel *)mPopWin)->label = label; @@ -1506,6 +1535,14 @@ void ASlider::Set(float value) mLWSlider->Set(value); } +#ifdef USE_MIDI +void ASlider::SetStyle(int style) +{ + mStyle = style; + mLWSlider->SetStyle(style); +} +#endif + void ASlider::Increase(float steps) { mLWSlider->Increase(steps); @@ -1740,6 +1777,12 @@ wxAccStatus ASliderAx::GetValue(int childId, wxString* strValue) case SPEED_SLIDER: strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue * 100 ); break; +#ifdef USE_MIDI + case VEL_SLIDER: + strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue); + break; +#endif + } return wxACC_OK; diff --git a/src/widgets/ASlider.h b/src/widgets/ASlider.h index 6dd8fd66b..c0dbf72df 100644 --- a/src/widgets/ASlider.h +++ b/src/widgets/ASlider.h @@ -39,6 +39,18 @@ class Ruler; #define DB_SLIDER 2 // -36...36 dB #define PAN_SLIDER 3 // -1.0...1.0 #define SPEED_SLIDER 4 // 0.01 ..3.0 +#ifdef USE_MIDI +#define VEL_SLIDER 5 // -50..50 +#endif + +#define DB_MIN -36.0f +#define DB_MAX 36.0f +#define FRAC_MIN 0.0f +#define FRAC_MAX 1.0f +#define SPEED_MIN 0.01f +#define SPEED_MAX 3.0f +#define VEL_MIN -50.0f +#define VEL_MAX 50.0f // Customizable slider only: If stepValue is STEP_CONTINUOUS, // every value on the slider between minValue and maxValue @@ -111,7 +123,7 @@ class LWSlider float Get(bool convert = true); void Set(float value); - + void SetStyle(int style); void Increase(float steps); void Decrease(float steps); @@ -245,6 +257,9 @@ class ASlider :public wxPanel float Get( bool convert = true ); void Set(float value); +#ifdef USE_MIDI + void SetStyle(int style); +#endif void Increase(float steps); void Decrease(float steps); diff --git a/src/xml/XMLTagHandler.cpp b/src/xml/XMLTagHandler.cpp index 7df2c59f4..593d12b63 100644 --- a/src/xml/XMLTagHandler.cpp +++ b/src/xml/XMLTagHandler.cpp @@ -161,6 +161,13 @@ bool XMLValueChecker::IsValidChannel(const int nValue) return (nValue >= Track::LeftChannel) && (nValue <= Track::MonoChannel); } +#ifdef USE_MIDI +bool XMLValueChecker::IsValidVisibleChannels(const int nValue) +{ + return (nValue >= 0 && nValue < (1 << 16)); +} +#endif + bool XMLValueChecker::IsValidSampleFormat(const int nValue) { return (nValue == int16Sample) || (nValue == int24Sample) || (nValue == floatSample); diff --git a/src/xml/XMLTagHandler.h b/src/xml/XMLTagHandler.h index 9db19e31e..66e18e754 100644 --- a/src/xml/XMLTagHandler.h +++ b/src/xml/XMLTagHandler.h @@ -42,6 +42,7 @@ public: static bool IsGoodInt(const wxString strInt); static bool IsValidChannel(const int nValue); + static bool IsValidVisibleChannels(const int nValue); static bool IsValidSampleFormat(const int nValue); // true if nValue is one sampleFormat enum values }; diff --git a/win/Projects/Audacity/Audacity.vcproj b/win/Projects/Audacity/Audacity.vcproj index ebbf47c2b..4ce9e848f 100644 --- a/win/Projects/Audacity/Audacity.vcproj +++ b/win/Projects/Audacity/Audacity.vcproj @@ -1299,6 +1299,10 @@ RelativePath="..\..\..\src\effects\SBSMSEffect.h" > + +