Extensive changes to improve NoteTrack display and (some) editing, NoteTrack playback via MIDI, and Midi-to-Audio alignment.
This commit is contained in:
parent
f6327602e8
commit
a1f0e5ed5b
|
@ -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
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
static const char * StretchCursorXpm[] = {
|
||||
"16 16 3 1",
|
||||
". c #FF0000", // mask color = RGB:255,0,0
|
||||
"# c #000000",
|
||||
"+ c #FFFFFF",
|
||||
"................",
|
||||
"........#.......",
|
||||
"........#.......",
|
||||
"........#.......",
|
||||
"....#########...",
|
||||
"....#...#...#...",
|
||||
"....#...#...#...",
|
||||
"....#...#...#...",
|
||||
".###############",
|
||||
"....#...#...#...",
|
||||
"....#...#...#...",
|
||||
"....#...#...#...",
|
||||
"....#########...",
|
||||
"........#.......",
|
||||
"........#.......",
|
||||
"........#......."};
|
|
@ -0,0 +1,21 @@
|
|||
static const char * StretchLeftCursorXpm[] = {
|
||||
"16 16 3 1",
|
||||
". c #FF0000", // mask color = RGB:255,0,0
|
||||
"# c #000000",
|
||||
"+ c #FFFFFF",
|
||||
"................",
|
||||
"........#.......",
|
||||
"........#.......",
|
||||
"........#.......",
|
||||
"........#####...",
|
||||
"........#...#...",
|
||||
"........#...#...",
|
||||
"........#...#...",
|
||||
"........########",
|
||||
"........#...#...",
|
||||
"........#...#...",
|
||||
"........#...#...",
|
||||
"........#####...",
|
||||
"........#.......",
|
||||
"........#.......",
|
||||
"........#......."};
|
|
@ -0,0 +1,21 @@
|
|||
static const char * StretchRightCursorXpm[] = {
|
||||
"16 16 3 1",
|
||||
". c #FF0000", // mask color = RGB:255,0,0
|
||||
"# c #000000",
|
||||
"+ c #FFFFFF",
|
||||
"................",
|
||||
"........#.......",
|
||||
"........#.......",
|
||||
"........#.......",
|
||||
"....#####.......",
|
||||
"....#...#.......",
|
||||
"....#...#.......",
|
||||
"....#...#.......",
|
||||
".########.......",
|
||||
"....#...#.......",
|
||||
"....#...#.......",
|
||||
"....#...#.......",
|
||||
"....#####.......",
|
||||
"........#.......",
|
||||
"........#.......",
|
||||
"........#......."};
|
|
@ -0,0 +1,37 @@
|
|||
static const char * StretchCursorXpm[] = {
|
||||
"32 32 3 1",
|
||||
". c #FF0000", // mask color = RGB:255,0,0
|
||||
"# c #000000",
|
||||
"+ c #FFFFFF",
|
||||
"................................",
|
||||
"................................",
|
||||
"................................",
|
||||
"...............+++..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
".......+++++++++#+++++++++......",
|
||||
".......+#################+......",
|
||||
".......+#+++++++#+++++++#+......",
|
||||
".......+#+.....+#+.....+#+......",
|
||||
".......+#+.....+#+.....+#+......",
|
||||
".......+#+.....+#+.....+#+......",
|
||||
".......+#+.....+#+.....+#+......",
|
||||
".......+#+.....+#+.....+#+......",
|
||||
"...+++++#+++++++#+++++++##++++..",
|
||||
"...+#########################+..",
|
||||
"...+++++#+++++++#+++++++#+++++..",
|
||||
".......+#+.....+#+.....+#+......",
|
||||
".......+#+.....+#+.....+#+......",
|
||||
".......+#+.....+#+.....+#+......",
|
||||
".......+#+.....+#+.....+#+......",
|
||||
".......+#+.....+#+.....+#+......",
|
||||
".......+#+++++++#+++++++#+......",
|
||||
".......+#################+......",
|
||||
".......+++++++++#+++++++++......",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+++..............",
|
||||
"................................",
|
||||
"................................"};
|
|
@ -0,0 +1,37 @@
|
|||
static const char * StretchLeftCursorXpm[] = {
|
||||
"32 32 3 1",
|
||||
". c #FF0000", // mask color = RGB:255,0,0
|
||||
"# c #000000",
|
||||
"+ c #FFFFFF",
|
||||
"................................",
|
||||
"................................",
|
||||
"................................",
|
||||
"...............+++..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+++++++++......",
|
||||
"...............+#########+......",
|
||||
"...............+#+++++++#+......",
|
||||
"...............+#+.....+#+......",
|
||||
"...............+#+.....+#+......",
|
||||
"...............+#+.....+#+......",
|
||||
"...............+#+.....+#+......",
|
||||
"...............+#+.....+#+......",
|
||||
"...............+#+++++++##++++..",
|
||||
"...............+#############+..",
|
||||
"...............+#+++++++#+++++..",
|
||||
"...............+#+.....+#+......",
|
||||
"...............+#+.....+#+......",
|
||||
"...............+#+.....+#+......",
|
||||
"...............+#+.....+#+......",
|
||||
"...............+#+.....+#+......",
|
||||
"...............+#+++++++#+......",
|
||||
"...............+#########+......",
|
||||
"...............+#+++++++++......",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+++..............",
|
||||
"................................",
|
||||
"................................"};
|
|
@ -0,0 +1,37 @@
|
|||
static const char * StretchRightCursorXpm[] = {
|
||||
"32 32 3 1",
|
||||
". c #FF0000", // mask color = RGB:255,0,0
|
||||
"# c #000000",
|
||||
"+ c #FFFFFF",
|
||||
"................................",
|
||||
"................................",
|
||||
"................................",
|
||||
"...............+++..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
".......+++++++++#+..............",
|
||||
".......+#########+..............",
|
||||
".......+#+++++++#+..............",
|
||||
".......+#+.....+#+..............",
|
||||
".......+#+.....+#+..............",
|
||||
".......+#+.....+#+..............",
|
||||
".......+#+.....+#+..............",
|
||||
".......+#+.....+#+..............",
|
||||
"...+++++#+++++++#+..............",
|
||||
"...+#############+..............",
|
||||
"...+++++#+++++++#+..............",
|
||||
".......+#+.....+#+..............",
|
||||
".......+#+.....+#+..............",
|
||||
".......+#+.....+#+..............",
|
||||
".......+#+.....+#+..............",
|
||||
".......+#+.....+#+..............",
|
||||
".......+#+++++++#+..............",
|
||||
".......+#########+..............",
|
||||
".......+++++++++#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+#+..............",
|
||||
"...............+++..............",
|
||||
"................................",
|
||||
"................................"};
|
|
@ -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
|
||||
|
|
|
@ -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 [-<flags> [<period><windowsize><path> <smooth><trans> <midi>]]
|
||||
scorealign [-<flags> [<period> <windowsize> <path> <smooth>
|
||||
<trans> <midi> <beatmap> <image>]]
|
||||
<file1> [<file2>]
|
||||
specifying only <file1> simply transcribes MIDI in <file1> to
|
||||
transcription.txt. Otherwise, align <file1> and <file2>.
|
||||
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 [-<flags> [<period><windowsize><path> <smooth><trans> <midi>]]
|
|||
(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:
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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 */);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,64 +1,15 @@
|
|||
|
||||
#include <math.h>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#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);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
//#include <stdio.h>
|
||||
//#include <stdlib.h>
|
||||
//#include <string.h>
|
||||
//#include <ctype.h>
|
||||
//#include <math.h>
|
||||
|
||||
#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 );
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
#include "assert.h"
|
||||
#include <math.h>
|
||||
#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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ----------
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* pmutil.c -- some helpful utilities for building midi
|
||||
applications that use PortMidi
|
||||
*/
|
||||
//#include <stdlib.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 <stdint.h>
|
||||
#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);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "portmidi.h"
|
||||
#include "pmutil.h"
|
||||
#include "pminternal.h"
|
||||
#include "pmmacosxcm.h"
|
||||
#include "readbinaryplist.h"
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include "readbinaryplist.h"
|
||||
#include <Carbon/Carbon.h>
|
||||
#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;
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
Roger B. Dannenberg, Jun 2008
|
||||
*/
|
||||
|
||||
#include <stdint.h> /* 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;
|
||||
|
|
|
@ -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...");
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
#include "portmidi.h"
|
||||
#include "porttime.h"
|
||||
#include "stdlib.h"
|
||||
#include "stdio.h"
|
||||
#include "string.h"
|
||||
#include "assert.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#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 {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
// need to get declaration for Sleep()
|
||||
#include "windows.h"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
/* asserts used to verify portMidi code logic is sound; later may want
|
||||
something more graceful */
|
||||
#include <assert.h>
|
||||
#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);
|
||||
|
|
|
@ -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 <stdint.h> // 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
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ PtTimestamp Pt_Time()
|
|||
}
|
||||
|
||||
|
||||
void Pt_Sleep(long duration)
|
||||
void Pt_Sleep(int32_t duration)
|
||||
{
|
||||
usleep(duration * 1000);
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ PtTimestamp Pt_Time()
|
|||
}
|
||||
|
||||
|
||||
void Pt_Sleep(long duration)
|
||||
void Pt_Sleep(int32_t duration)
|
||||
{
|
||||
usleep(duration * 1000);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 [<name>]
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
1045
src/AudioIO.cpp
1045
src/AudioIO.cpp
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
229
src/Menus.cpp
229
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);
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Audacity.h"
|
||||
|
||||
#include "Experimental.h"
|
||||
|
||||
#include <math.h>
|
||||
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -21,16 +21,79 @@
|
|||
|
||||
#include "Audacity.h"
|
||||
|
||||
#if defined(USE_MIDI)
|
||||
#include <sstream>
|
||||
|
||||
#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
|
||||
|
|
121
src/NoteTrack.h
121
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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
#include <wx/datetime.h>
|
||||
|
||||
#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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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; i<mCapturedClipArray.GetCount(); i++) {
|
||||
if (mCapturedClipArray[i].clip)
|
||||
mCapturedClipArray[i].clip->Offset(-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; i<mCapturedClipArray.GetCount(); i++) {
|
||||
Track *track = mCapturedClipArray[i].track;
|
||||
|
@ -2662,7 +2973,7 @@ void TrackPanel::DoSlide(wxMouseEvent & event)
|
|||
}
|
||||
}
|
||||
else {
|
||||
// For non wavetracks...
|
||||
// For non wavetracks, specifically label tracks ...
|
||||
mCapturedTrack->Offset(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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -27,6 +27,7 @@ UndoManager
|
|||
#include "Sequence.h"
|
||||
#include "Track.h"
|
||||
#include "WaveTrack.h" // temp
|
||||
#include "NoteTrack.h" // for Sonify* function declarations
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
#include "../Audacity.h"
|
||||
|
||||
// For compilers that support precompilation, includes "wx/wx.h".
|
||||
#include <wx/wxprec.h>
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
#pragma hdrstop
|
||||
#endif
|
||||
|
||||
#ifndef WX_PRECOMP
|
||||
#include <wx/brush.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/dcmemory.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/file.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/intl.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/statusbr.h>
|
||||
#endif
|
||||
|
||||
#include <fstream>
|
||||
#include <wx/dialog.h>
|
||||
#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()
|
|
@ -0,0 +1,104 @@
|
|||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
ScoreAlignDialog.h
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_SCORE_ALIGN_DIALOG__
|
||||
#define __AUDACITY_SCORE_ALIGN_DIALOG__
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/slider.h>
|
||||
#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
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -1299,6 +1299,10 @@
|
|||
RelativePath="..\..\..\src\effects\SBSMSEffect.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\..\src\effects\ScoreAlignDialog.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\..\src\effects\Silence.cpp"
|
||||
>
|
||||
|
|
Loading…
Reference in New Issue