Extensive changes to improve NoteTrack display and (some) editing, NoteTrack playback via MIDI, and Midi-to-Audio alignment.

This commit is contained in:
rbdannenberg 2010-09-18 21:02:36 +00:00
parent f6327602e8
commit a1f0e5ed5b
96 changed files with 5679 additions and 3566 deletions

View File

@ -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

View File

@ -0,0 +1,21 @@
static const char * StretchCursorXpm[] = {
"16 16 3 1",
". c #FF0000", // mask color = RGB:255,0,0
"# c #000000",
"+ c #FFFFFF",
"................",
"........#.......",
"........#.......",
"........#.......",
"....#########...",
"....#...#...#...",
"....#...#...#...",
"....#...#...#...",
".###############",
"....#...#...#...",
"....#...#...#...",
"....#...#...#...",
"....#########...",
"........#.......",
"........#.......",
"........#......."};

View File

@ -0,0 +1,21 @@
static const char * StretchLeftCursorXpm[] = {
"16 16 3 1",
". c #FF0000", // mask color = RGB:255,0,0
"# c #000000",
"+ c #FFFFFF",
"................",
"........#.......",
"........#.......",
"........#.......",
"........#####...",
"........#...#...",
"........#...#...",
"........#...#...",
"........########",
"........#...#...",
"........#...#...",
"........#...#...",
"........#####...",
"........#.......",
"........#.......",
"........#......."};

View File

@ -0,0 +1,21 @@
static const char * StretchRightCursorXpm[] = {
"16 16 3 1",
". c #FF0000", // mask color = RGB:255,0,0
"# c #000000",
"+ c #FFFFFF",
"................",
"........#.......",
"........#.......",
"........#.......",
"....#####.......",
"....#...#.......",
"....#...#.......",
"....#...#.......",
".########.......",
"....#...#.......",
"....#...#.......",
"....#...#.......",
"....#####.......",
"........#.......",
"........#.......",
"........#......."};

View File

@ -0,0 +1,37 @@
static const char * StretchCursorXpm[] = {
"32 32 3 1",
". c #FF0000", // mask color = RGB:255,0,0
"# c #000000",
"+ c #FFFFFF",
"................................",
"................................",
"................................",
"...............+++..............",
"...............+#+..............",
"...............+#+..............",
"...............+#+..............",
".......+++++++++#+++++++++......",
".......+#################+......",
".......+#+++++++#+++++++#+......",
".......+#+.....+#+.....+#+......",
".......+#+.....+#+.....+#+......",
".......+#+.....+#+.....+#+......",
".......+#+.....+#+.....+#+......",
".......+#+.....+#+.....+#+......",
"...+++++#+++++++#+++++++##++++..",
"...+#########################+..",
"...+++++#+++++++#+++++++#+++++..",
".......+#+.....+#+.....+#+......",
".......+#+.....+#+.....+#+......",
".......+#+.....+#+.....+#+......",
".......+#+.....+#+.....+#+......",
".......+#+.....+#+.....+#+......",
".......+#+++++++#+++++++#+......",
".......+#################+......",
".......+++++++++#+++++++++......",
"...............+#+..............",
"...............+#+..............",
"...............+#+..............",
"...............+++..............",
"................................",
"................................"};

View File

@ -0,0 +1,37 @@
static const char * StretchLeftCursorXpm[] = {
"32 32 3 1",
". c #FF0000", // mask color = RGB:255,0,0
"# c #000000",
"+ c #FFFFFF",
"................................",
"................................",
"................................",
"...............+++..............",
"...............+#+..............",
"...............+#+..............",
"...............+#+..............",
"...............+#+++++++++......",
"...............+#########+......",
"...............+#+++++++#+......",
"...............+#+.....+#+......",
"...............+#+.....+#+......",
"...............+#+.....+#+......",
"...............+#+.....+#+......",
"...............+#+.....+#+......",
"...............+#+++++++##++++..",
"...............+#############+..",
"...............+#+++++++#+++++..",
"...............+#+.....+#+......",
"...............+#+.....+#+......",
"...............+#+.....+#+......",
"...............+#+.....+#+......",
"...............+#+.....+#+......",
"...............+#+++++++#+......",
"...............+#########+......",
"...............+#+++++++++......",
"...............+#+..............",
"...............+#+..............",
"...............+#+..............",
"...............+++..............",
"................................",
"................................"};

View File

@ -0,0 +1,37 @@
static const char * StretchRightCursorXpm[] = {
"32 32 3 1",
". c #FF0000", // mask color = RGB:255,0,0
"# c #000000",
"+ c #FFFFFF",
"................................",
"................................",
"................................",
"...............+++..............",
"...............+#+..............",
"...............+#+..............",
"...............+#+..............",
".......+++++++++#+..............",
".......+#########+..............",
".......+#+++++++#+..............",
".......+#+.....+#+..............",
".......+#+.....+#+..............",
".......+#+.....+#+..............",
".......+#+.....+#+..............",
".......+#+.....+#+..............",
"...+++++#+++++++#+..............",
"...+#############+..............",
"...+++++#+++++++#+..............",
".......+#+.....+#+..............",
".......+#+.....+#+..............",
".......+#+.....+#+..............",
".......+#+.....+#+..............",
".......+#+.....+#+..............",
".......+#+++++++#+..............",
".......+#########+..............",
".......+++++++++#+..............",
"...............+#+..............",
"...............+#+..............",
"...............+#+..............",
"...............+++..............",
"................................",
"................................"};

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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 */);
}

View File

@ -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);
}

View File

@ -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 );

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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]

View File

@ -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:

View File

@ -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);
};

View File

@ -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)))

View File

@ -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 &params)
{
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
}

View File

@ -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 &params);

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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 ----------

View File

@ -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

View File

@ -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);

View File

@ -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)

View File

@ -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
}

View File

@ -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;

View File

@ -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);
/** @} */

View File

@ -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.

View File

@ -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;
}

View File

@ -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++) {

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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...");

View File

@ -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 {

View File

@ -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");

View File

@ -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) {

View File

@ -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;

View File

@ -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]);
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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:

View File

@ -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);

View File

@ -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
}

View File

@ -124,7 +124,7 @@ PtTimestamp Pt_Time()
}
void Pt_Sleep(long duration)
void Pt_Sleep(int32_t duration)
{
usleep(duration * 1000);
}

View File

@ -134,7 +134,7 @@ PtTimestamp Pt_Time()
}
void Pt_Sleep(long duration)
void Pt_Sleep(int32_t duration)
{
usleep(duration * 1000);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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 &note_on)
bool &note_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;
}

View File

@ -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 &note_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 &note_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);

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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();

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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();
}

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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,

View File

@ -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, &centerBeat);
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, &centerBeat);
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());

View File

@ -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;

View File

@ -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,

View File

@ -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;

View File

@ -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 &params)
: 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()

View File

@ -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 &params);
~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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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()

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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
};

View File

@ -1299,6 +1299,10 @@
RelativePath="..\..\..\src\effects\SBSMSEffect.h"
>
</File>
<File
RelativePath="..\..\..\src\effects\ScoreAlignDialog.cpp"
>
</File>
<File
RelativePath="..\..\..\src\effects\Silence.cpp"
>