MuseScore/omr/omrpage.cpp
2013-05-21 09:44:49 +02:00

1177 lines
37 KiB
C++

//=============================================================================
// MusE Reader
// Music Score Reader
// $Id$
//
// Copyright (C) 2010-2011 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "omrpage.h"
#include "image.h"
#include "utils.h"
#include "omr.h"
#ifdef OCR
#include "ocr.h"
#endif
#include "libmscore/score.h"
#include "libmscore/text.h"
#include "libmscore/measurebase.h"
#include "libmscore/box.h"
#include "libmscore/sym.h"
#include "pattern.h"
namespace Ms {
static const double noteTH = 0.8;
static const double timesigTH = 0.7;
static const double clefTH = 0.7;
static const double keysigTH = 0.8;
struct Hv {
int x;
int val;
Hv(int a, int b) : x(a), val(b) {}
bool operator< (const Hv& a) const { return a.val < val; }
};
struct Peak {
int x;
double val;
int sym;
bool operator<(const Peak& p) const { return p.val < val; }
Peak(int _x, double _val) : x(_x), val(_val) {}
Peak(int _x, double _val, int s) : x(_x), val(_val), sym(s) {}
};
//---------------------------------------------------------
// Lv
// line + value
//---------------------------------------------------------
struct Lv {
int line;
double val;
Lv(int a, double b) : line(a), val(b) {}
bool operator< (const Lv& a) const {
return a.val < val;
}
};
//---------------------------------------------------------
// OmrPage
//---------------------------------------------------------
OmrPage::OmrPage(Omr* parent)
{
_omr = parent;
cropL = cropR = cropT = cropB = 0;
}
//---------------------------------------------------------
// dot
//---------------------------------------------------------
bool OmrPage::dot(int x, int y) const
{
const uint* p = scanLine(y) + (x / 32);
return (*p) & (0x1 << (x % 32));
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void OmrPage::read()
{
crop();
slice();
deSkew();
crop();
slice();
getStaffLines();
//--------------------------------------------------
// create systems
//--------------------------------------------------
int numStaves = staves.size();
int stavesSystem = 2;
int systems = numStaves / stavesSystem;
for (int system = 0; system < systems; ++system) {
OmrSystem omrSystem(this);
for (int i = 0; i < stavesSystem; ++i) {
omrSystem.staves().append(staves[system * stavesSystem + i]);
}
_systems.append(omrSystem);
}
if (_systems.isEmpty())
return;
}
//---------------------------------------------------------
// readBarLines
//---------------------------------------------------------
void OmrPage::readBarLines(int pageNo)
{
QFuture<void> bl = QtConcurrent::map(_systems, &OmrSystem::searchBarLines);
bl.waitForFinished();
int numStaves = staves.size();
int stavesSystem = 2;
int systems = numStaves / stavesSystem;
for (int i = 0; i < systems; ++i) {
OmrSystem* system = &_systems[i];
int n = system->barLines.size();
for (int k = 0; k < n-1; ++k) {
const QLine& l1 = system->barLines[k];
const QLine& l2 = system->barLines[k+1];
OmrMeasure m(l1.x1(), l2.x1());
for (int k = 0; k < system->staves().size(); ++k) {
OmrStaff& staff = system->staves()[k];
QList<OmrChord> chords;
int nx = 0;
int nsym = -1;
OmrChord chord;
foreach(OmrNote* n, staff.notes()) {
int x = n->x();
if (x >= m.x2())
break;
if (x >= m.x1() && x < m.x2()) {
if (qAbs(x - nx) > int(_spatium/2) || (nsym != n->sym)) {
if (!chord.notes.isEmpty()) {
int sym = chord.notes.front()->sym;
if (sym == quartheadSym)
chord.duration.setType(TDuration::V_QUARTER);
else if (sym == halfheadSym)
chord.duration.setType(TDuration::V_HALF);
chords.append(chord);
chord.notes.clear();
}
}
nx = x;
nsym = n->sym;
chord.notes.append(n);
}
}
if (!chord.notes.isEmpty()) {
int sym = chord.notes.front()->sym;
if (sym == quartheadSym)
chord.duration.setType(TDuration::V_QUARTER);
else if (sym == halfheadSym)
chord.duration.setType(TDuration::V_HALF);
chords.append(chord);
}
m.chords().append(chords);
}
system->measures().append(m);
}
}
//--------------------------------------------------
// search clef/keysig
//--------------------------------------------------
if (pageNo == 0) {
OmrSystem* s = &_systems[0];
for (int i = 0; i < s->staves().size(); ++i) {
OmrStaff* staff = &s->staves()[i];
OmrClef clef = searchClef(s, staff);
staff->setClef(clef);
searchKeySig(s, staff);
}
//--------------------------------------------------
// search time signature
//--------------------------------------------------
if (!s->staves().isEmpty()) {
OmrTimesig* ts = searchTimeSig(s);
s->measures()[0].setTimesig(ts);
}
}
}
//---------------------------------------------------------
// searchClef
//---------------------------------------------------------
OmrClef OmrPage::searchClef(OmrSystem* system, OmrStaff* staff)
{
std::vector<Pattern*> pl = {
Omr::trebleclefPattern,
Omr::bassclefPattern
};
const OmrMeasure& m = system->measures().front();
printf("search clef %d %d-%d\n", staff->y(), m.x1(), m.x2());
int x1 = m.x1() + 2;
int x2 = x1 + (m.x2() - x1)/2;
OmrPattern p = searchPattern(pl, staff->y(), x1, x2);
OmrClef clef(p);
if (p.sym == trebleclefSym)
clef.type = CLEF_G;
else if (p.sym == bassclefSym)
clef.type = CLEF_F;
else
clef.type = CLEF_G;
return clef;
}
//---------------------------------------------------------
// searchPattern
//---------------------------------------------------------
OmrPattern OmrPage::searchPattern(const std::vector<Pattern*>& pl, int y, int x1, int x2)
{
OmrPattern p;
p.sym = -1;
p.prob = 0.0;
for (Pattern* pattern : pl) {
double val = 0.0;
int xx = 0;
int hw = pattern->w();
for (int x = x1; x < (x2 - hw); ++x) {
double val1 = pattern->match(&image(), x - pattern->base().x(), y - pattern->base().y());
if (val1 > val) {
val = val1;
xx = x;
}
}
if (val > p.prob) {
p.setRect(xx, y, pattern->w(), pattern->h());
p.sym = pattern->id();
p.prob = val;
}
printf("Pattern found %d %f %d\n", pattern->id(), val, xx);
}
return p;
}
//---------------------------------------------------------
// searchTimeSig
//---------------------------------------------------------
OmrTimesig* OmrPage::searchTimeSig(OmrSystem* system)
{
Pattern pl[10];
pl[0] = Pattern(zeroSym, &symbols[0][zeroSym], _spatium);
pl[1] = Pattern(oneSym, &symbols[0][oneSym], _spatium);
pl[2] = Pattern(twoSym, &symbols[0][twoSym], _spatium);
pl[3] = Pattern(threeSym, &symbols[0][threeSym], _spatium);
pl[4] = Pattern(fourSym, &symbols[0][fourSym], _spatium);
pl[5] = Pattern(fiveSym, &symbols[0][fiveSym], _spatium);
pl[6] = Pattern(sixSym, &symbols[0][sixSym], _spatium);
pl[7] = Pattern(sevenSym, &symbols[0][sevenSym], _spatium);
pl[8] = Pattern(eightSym, &symbols[0][eightSym], _spatium);
pl[9] = Pattern(nineSym, &symbols[0][nineSym], _spatium);
int z = -1;
int n = -1;
double zval = 0;
double nval = 0;
QRect rz, rn;
int y = system->staves().front().y();
OmrMeasure* m = &system->measures().front();
int x1 = m->x1();
for (int i = 0; i < 10; ++i) {
Pattern* pattern = &pl[i];
double val = 0.0;
int hh = pattern->h();
int hw = pattern->w();
int x2 = m->x2() - hw;
QRect r;
for (int x = x1; x < x2; ++x) {
double val1 = pattern->match(&image(), x, y);
if (val1 > val) {
val = val1;
r = QRect(x, y, hw, hh);
}
}
if (val > timesigTH && val > zval) {
z = i;
zval = val;
rz = r;
}
// printf(" found %d %f\n", i, val);
}
if (z < 0)
return 0;
y = system->staves().front().y() + lrint(_spatium * 2);
x1 = rz.x();
int x2 = x1 + 1;
OmrTimesig* ts = 0;
for (int i = 0; i < 10; ++i) {
Pattern* pattern = &pl[i];
double val = 0.0;
int hh = pattern->h();
int hw = pattern->w();
QRect r;
for (int x = x1; x < x2; ++x) {
double val1 = pattern->match(&image(), x, y);
if (val1 > val) {
val = val1;
r = QRect(x, y, hw, hh);
}
}
if (val > timesigTH && val > nval) {
n = i;
nval = val;
rn = r;
}
// printf(" found %d %f\n", i, val);
}
if (n > 0) {
ts = new OmrTimesig(rz | rn);
ts->timesig = Fraction(z, n);
printf("timesig %d/%d\n", z, n);
}
return ts;
}
//---------------------------------------------------------
// searchKeySig
//---------------------------------------------------------
void OmrPage::searchKeySig(OmrSystem* system, OmrStaff* staff)
{
Pattern* pl[2];
pl[0] = Omr::sharpPattern;
pl[1] = Omr::flatPattern;
double zval = 0;
int y = system->staves().front().y();
OmrMeasure* m = &system->measures().front();
int x1 = m->x1();
for (int i = 0; i < 2; ++i) {
Pattern* pattern = pl[i];
double val = 0.0;
int hh = pattern->h();
int hw = pattern->w();
int x2 = m->x2() - hw;
QRect r;
for (int x = x1; x < x2; ++x) {
double val1 = pattern->match(&image(), x, y - hh/2);
if (val1 > val) {
val = val1;
r = QRect(x, y, hw, hh);
}
}
if (val > keysigTH && val > zval) {
zval = val;
OmrKeySig key(r);
key.type = i == 0 ? 1 : -1;
staff->setKeySig(key);
}
printf(" key found %d %f\n", i, val);
}
}
//---------------------------------------------------------
// maxP
//---------------------------------------------------------
int maxP(int* projection, int x1, int x2)
{
int xx = x1;
int max = 0;
for (int x = x1; x < x2; ++x) {
if (projection[x] > max) {
max = projection[x];
xx = x;
}
}
return xx;
}
//---------------------------------------------------------
// searchBarLines
//---------------------------------------------------------
void OmrSystem::searchBarLines()
{
OmrStaff& r1 = _staves[0];
OmrStaff& r2 = _staves[1];
int x1 = r1.x();
int x2 = x1 + r1.width();
int y1 = r1.y();
int y2 = r2.y() + r2.height();
int h = y2 - y1 + 1;
int th = h * 4 / 6; // threshold
int vpw = x2-x1;
int vp[vpw];
memset(vp, 0, sizeof(int) * vpw);
//
// compute vertical projections
//
for (int x = x1; x < x2; ++x) {
int dots = 0;
for (int y = y1; y < y2; ++y) {
if (_page->dot(x, y))
++dots;
}
vp[x - x1] = dots;
}
bool firstBarLine = true;
for (int x = 1; x < vpw; ++x) {
if (vp[x-1] < vp[x])
continue;
if (vp[x] < th)
continue;
// if (vp[x-1] > vp[x])
{
barLines.append(QLine(x + x1, y1, x + x1, y2));
int xx = x + x1;
if (firstBarLine) {
firstBarLine = false;
_staves[0].setX(xx);
_staves[1].setX(xx);
}
else {
_staves[0].setWidth(xx - _staves[0].x());
_staves[1].setWidth(xx - _staves[1].x());
}
}
}
searchNotes();
//
// remove false positive barlines:
// - two barlines too narrow (repeat-/end-barlines
// are detected as two barlines
// - barlines which are really note stems
//
QList<QLine> nbl;
double x = -10000.0;
double spatium = _page->spatium();
int nbar = 0;
// int i = 0;
int n = barLines.size();
for (int i = 0; i < n; ++i) {
const QLine& l = barLines[i];
int nx = l.x1();
if ((nx - x) > spatium) {
//
// check for start repeat:
//
if ((nbar == 1)
&& ((nx-x)/spatium < 8.0) // at begin of system?
// && (i < (n-1))
// && ((barLines[i+1].x1() - x) < spatium) // double bar line?
// missing: check fo note heads
// up to here
) {
x = nx;
continue;
}
nbl.append(l);
x = nx;
++nbar;
}
}
barLines = nbl;
}
//---------------------------------------------------------
// noteCompare
//---------------------------------------------------------
static bool noteCompare(OmrNote* n1, OmrNote* n2)
{
return n1->x() < n2->x();
}
static bool intersectFuzz(const QRect& a, const QRect& b, int fuzz)
{
int xfuzz = fuzz;
int yfuzz = fuzz;
if (a.x() > b.x())
xfuzz = -xfuzz;
if (a.y() > b.y())
yfuzz = -yfuzz;
return (a.intersects(b.translated(xfuzz, yfuzz)));
}
//---------------------------------------------------------
// searchNotes
//---------------------------------------------------------
void OmrSystem::searchNotes()
{
for (int i = 0; i < _staves.size(); ++i) {
OmrStaff* r = &_staves[i];
int x1 = r->x();
int x2 = x1 + r->width();
//
// search notes on a range of vertical line position
//
for (int line = -5; line < 14; ++line)
searchNotes(&r->notes(), x1, x2, r->y(), line);
//
// detect collisions
//
int fuzz = int(_page->spatium()) / 2;
foreach(OmrNote* n, r->notes()) {
foreach(OmrNote* m, r->notes()) {
if (m == n)
continue;
if (intersectFuzz(*m, *n, fuzz)) {
if (m->prob > n->prob)
r->notes().removeOne(n);
else
r->notes().removeOne(m);
}
}
}
qSort(r->notes().begin(), r->notes().end(), noteCompare);
}
}
//---------------------------------------------------------
// addText
//---------------------------------------------------------
#ifdef OCR
static void addText(Score* score, int subtype, const QString& s)
{
#if 0 //TODO-1
MeasureBase* measure = score->first();
if (measure == 0 || measure->type() != Element::VBOX) {
measure = new VBox(score);
measure->setNext(score->first());
measure->setTick(0);
score->add(measure);
}
Text* text = new Text(score);
switch(subtype) {
case TEXT_TITLE: text->setTextStyle(TEXT_STYLE_TITLE); break;
case TEXT_SUBTITLE: text->setTextStyle(TEXT_STYLE_SUBTITLE); break;
case TEXT_COMPOSER: text->setTextStyle(TEXT_STYLE_COMPOSER); break;
case TEXT_POET: text->setTextStyle(TEXT_STYLE_POET); break;
}
text->setSubtype(subtype);
text->setText(s);
measure->add(text);
#endif
}
#endif
//---------------------------------------------------------
// readHeader
//---------------------------------------------------------
void OmrPage::readHeader(Score*)
{
if (_slices.isEmpty())
return;
double maxHeight = _spatium * 4 * 2;
int slice = 0;
double maxH = 0.0;
// int maxIdx;
for (;slice < _slices.size(); ++slice) {
double h = _slices[slice].height();
if (h > maxHeight)
break;
if (h > maxH) {
maxH = h;
// maxIdx = slice;
}
}
#ifdef OCR
//
// assume that highest slice contains header text
//
OcrImage img = OcrImage(_image.bits(), _slices[maxIdx], (_image.width() + 31)/32);
QString s = _omr->ocr()->readLine(img).trimmed();
if (!s.isEmpty())
score->addText("title", s);
QString subTitle;
for (int i = maxIdx + 1; i < slice; ++i) {
OcrImage img = OcrImage(_image.bits(), _slices[i], (_image.width() + 31)/32);
QString s = _omr->ocr()->readLine(img).trimmed();
if (!s.isEmpty()) {
if (!subTitle.isEmpty())
subTitle += "\n";
subTitle += s;
}
}
if (!subTitle.isEmpty())
score->addText("subtitle", subTitle);
#endif
#if 0
OcrImage img = OcrImage(_image.bits(), _slices[0], (_image.width() + 31)/32);
QString s = _omr->ocr()->readLine(img).trimmed();
if (!s.isEmpty())
addText(score, TEXT_TITLE, s);
img = OcrImage(_image.bits(), _slices[1], (_image.width() + 31)/32);
s = _omr->ocr()->readLine(img).trimmed();
if (!s.isEmpty())
addText(score, TEXT_SUBTITLE, s);
img = OcrImage(_image.bits(), _slices[2], (_image.width() + 31)/32);
s = _omr->ocr()->readLine(img).trimmed();
if (!s.isEmpty())
addText(score, TEXT_COMPOSER, s);
#endif
}
//---------------------------------------------------------
// crop
//---------------------------------------------------------
void OmrPage::crop()
{
int wl = wordsPerLine();
int cropT = cropB = cropL = cropR = 0;
for (int y = 0; y < height(); ++y) {
const uint* p = scanLine(y);
for (int k = 0; k < wl; ++k) {
if (*p++) {
cropT = y;
break;
}
}
if (cropT)
break;
}
for (int y = height()-1; y >= cropT; --y) {
const uint* p = scanLine(y);
for (int k = 0; k < wl; ++k) {
if (*p++) {
cropB = height() - y - 1;
break;
}
}
if (cropB)
break;
}
int y1 = cropT;
int y2 = height() - cropT - cropB;
for (int x = 0; x < wl; ++x) {
for (int y = y1; y < y2; ++y) {
if (*(scanLine(y) + x)) {
cropL = x;
break;
}
}
if (cropL)
break;
}
for (int x = wl-1; x >= cropL; --x) {
for (int y = y1; y < y2; ++y) {
if (*(scanLine(y) + x)) {
cropR = wl - x - 1;
break;
}
}
if (cropR)
break;
}
// printf("*** crop: T%d B%d L%d R:%d\n", cropT, cropB, cropL, cropR);
}
//---------------------------------------------------------
// slice
//---------------------------------------------------------
void OmrPage::slice()
{
_slices.clear();
int y1 = cropT;
int y2 = height() - cropB;
int x1 = cropL;
int x2 = wordsPerLine() - cropR;
int xbits = (x2 - x1) * 32;
// _slices.append(QRect(cropL*32, y1, xbits, y2-y1));
#if 1
for (int y = y1; y < y2;) {
//
// skip contents
//
int ys = y;
for (; y < y2; ++y) {
const uint* p = scanLine(y) + cropL;
bool bits = false;
for (int x = cropL; x < x2; ++x) {
if (*p) {
bits = true;
break;
}
++p;
}
if (!bits)
break;
}
if (y - ys)
_slices.append(QRect(cropL*32, ys, xbits, y - ys));
//
// skip space
//
for (; y < y2; ++y) {
const uint* p = scanLine(y) + cropL;
bool bits = false;
for (int x = cropL; x < x2; ++x) {
if (*p) {
bits = true;
break;
}
++p;
}
if (bits)
break;
}
}
#endif
}
//---------------------------------------------------------
// deSkew
//---------------------------------------------------------
void OmrPage::deSkew()
{
int wl = wordsPerLine();
int h = height();
uint* db = new uint[wl * h];
memset(db, 0, wl * h * sizeof(uint));
foreach(const QRect& r, _slices) {
double rot = skew(r);
if (qAbs(rot) < 0.1) {
memcpy(db + wl * r.y(), scanLine(r.y()), wl * r.height() * sizeof(uint));
continue;
}
QTransform t;
t.rotate(rot);
QTransform tt = QImage::trueMatrix(t, width(), r.height());
double m11 = tt.m11();
double m12 = tt.m12();
double m21 = tt.m21();
double m22 = tt.m22();
double dx = tt.m31();
double dy = tt.m32();
double m21y = r.y() * m21;
double m22y = r.y() * m22;
int y2 = r.y() + r.height();
for (int y = r.y(); y < y2; ++y) {
const uint* s = scanLine(y);
m21y += m21;
m22y += m22;
for (int x = 0; x < wl; ++x) {
uint c = *s++;
if (c == 0)
continue;
uint mask = 1;
for (int xx = 0; xx < 32; ++xx) {
if (c & mask) {
int xs = x * 32 + xx;
int xd = lrint(m11 * xs + m21y + dx);
int yd = lrint(m22y + m12 * xs + dy);
int wxd = xd / 32;
if ((xd >= 0) && (wxd < wl) && (yd >= 0) && (yd < h)) {
uint* d = db + wl * yd + wxd;
if( d < db + wl * h) //check that we are in the bounds.
*d |= (0x1 << (xd % 32));
}
}
mask <<= 1;
}
}
}
}
memcpy(_image.bits(), db, wl * h * sizeof(uint));
delete[] db;
}
struct ScanLine {
int run;
int x1, x2;
ScanLine() { run = 0; x1 = 100000; x2 = 0; }
};
struct H {
int y;
int bits;
H(int a, int b) : y(a), bits(b) {}
};
//---------------------------------------------------------
// xproject
//---------------------------------------------------------
int OmrPage::xproject(const uint* p, int wl)
{
int run = 0;
int w = wl - cropL - cropR;
int x1 = cropL + w/4; // only look at part of page
int x2 = x1 + w/2;
for (int x = cropL; x < x2; ++x) {
uint v = *p++;
run += Omr::bitsSetTable[v & 0xff]
+ Omr::bitsSetTable[(v >> 8) & 0xff]
+ Omr::bitsSetTable[(v >> 16) & 0xff]
+ Omr::bitsSetTable[v >> 24];
}
return run;
}
//---------------------------------------------------------
// xproject2
//---------------------------------------------------------
double OmrPage::xproject2(int y1)
{
int wl = wordsPerLine();
const uint* db = bits();
double val = 0.0;
int w = wl - cropL - cropR;
int x1 = (cropL + w/4)*32; // only look at part of page
int x2 = x1 + (w/2 * 32);
int ddx = x2 - x1;
for (int dy = -12; dy < 12; ++dy) {
int onRun = 0;
int offRun = 0;
int on = 0;
int off = 0;
bool onFlag = false;
int incy = (dy > 0) ? 1 : (dy < 0) ? -1 : 0;
int ddy = dy < 0 ? -dy : dy;
int y = y1;
if (y < 1)
y = 0;
int err = ddx / 2;
for (int x = x1; x < x2;) {
const uint* d = db + wl * y + (x / 32);
if (d < db + wl)
break;
if (d >= db + (wl-1) * height()) //check that we are in the bounds.
break;
bool bit = ((*d) & (0x1 << (x % 32)));
bit = bit || ((*(d+wl)) & (0x1 << (x % 32)));
bit = bit || ((*(d-wl)) & (0x1 << (x % 32)));
if (bit != onFlag) {
if (!onFlag) {
//
// end of offrun:
//
if (offRun > 20) {
off += offRun * offRun;
on += onRun * onRun;
onRun = 0;
offRun = 0;
}
else {
onRun += offRun;
offRun = 0;
}
}
onFlag = bit;
}
(bit ? onRun : offRun)++;
if (offRun > 100) {
offRun = 0;
off = 1;
on = 0;
onRun = 0;
break;
}
err -= ddy;
if (err < 0) {
err += ddx;
y += incy;
if (y < 1)
y = 1;
else if (y >= height())
y = height()-1;
}
++x;
}
if (offRun > 20)
off += offRun * offRun;
else
onRun += offRun;
on += onRun * onRun;
if (off == 0)
off = 1;
double nval = double(on) / double(off);
if (nval > val)
val = nval;
}
return val;
}
static bool sortLvStaves(const Lv& a, const Lv& b)
{
return a.line < b.line;
}
//---------------------------------------------------------
// getStaffLines
//---------------------------------------------------------
void OmrPage::getStaffLines()
{
int h = height();
int wl = wordsPerLine();
// printf("getStaffLines %d %d crop %d %d\n", h, wl, cropT, cropB);
if (h < 1)
return;
int y1 = cropT;
int y2 = h - cropB;
if (y2 >= h)
--y2;
double projection[h];
for (int y = 0; y < y1; ++y)
projection[y] = 0;
for (int y = y2; y < h; ++y)
projection[y] = 0;
for (int y = y1; y < y2; ++y)
projection[y] = xproject2(y);
int autoTableSize = (wl * 32) / 10; // 1/10 page width
if (autoTableSize > y2-y1)
autoTableSize = y2 - y1;
double autoTable[autoTableSize];
memset(autoTable, 0, sizeof(autoTable));
for (int i = 0; i < autoTableSize; ++i) {
autoTable[i] = covariance(projection+y1, projection+i+y1, y2-y1-i);
}
//
// search for first maximum in covariance starting at 10 to skip
// line width. Staff line distance (spatium) must be at least 10 dots
//
double maxCorrelation = 0;
_spatium = 0;
for (int i = 10; i < autoTableSize; ++i) {
if (autoTable[i] > maxCorrelation) {
maxCorrelation = autoTable[i];
_spatium = i;
}
}
if (_spatium == 0) {
printf("*** no staff lines found\n");
return;
}
//---------------------------------------------------
// look for staves
//---------------------------------------------------
QList<Lv> lv;
int ly = 0;
int lval = -1000.0;
for (int y = y1; y < (y2 - _spatium * 4); ++y) {
double val = 0.0;
for (int i = 0; i < 5; ++i)
val += projection[y + i * int(_spatium)];
if (val < lval) {
lv.append(Lv(ly, lval));
}
lval = val;
ly = y;
}
qSort(lv);
QList<Lv> staveTop;
int staffHeight = _spatium * 6;
foreach(Lv a, lv) {
if (a.val < 500) // MAGIC to avoid false positives
continue;
int line = a.line;
bool ok = true;
foreach(Lv b, staveTop) {
if ((line > (b.line - staffHeight)) && (line < (b.line + staffHeight))) {
ok = false;
break;
}
}
if (ok)
staveTop.append(a);
}
qSort(staveTop.begin(), staveTop.end(), sortLvStaves);
foreach(Lv a, staveTop) {
staves.append(OmrStaff(cropL * 32, a.line, width() - cropR*32, _spatium*4));
}
}
//---------------------------------------------------------
// searchNotes
//---------------------------------------------------------
void OmrSystem::searchNotes(QList<OmrNote*>* noteList, int x1, int x2, int y, int line)
{
double _spatium = _page->spatium();
y += line * _spatium * .5;
Pattern* patternList[2];
patternList[0] = Omr::quartheadPattern;
patternList[1] = Omr::halfheadPattern;
QList<Peak> notePeaks;
for (int k = 0; k < 2; ++k) {
Pattern* pattern = patternList[k];
int hh = pattern->h();
int hw = pattern->w();
bool found = false;
int xx1;
double val;
for (int x = x1; x < (x2 - hw); ++x) {
double val1 = pattern->match(&_page->image(), x, y - hh/2);
if (val1 >= noteTH) {
if (!found || (val1 > val)) {
xx1 = x;
val = val1;
found = true;
}
}
else {
if (found) {
notePeaks.append(Peak(xx1, val, k));
found = false;
}
}
}
}
qSort(notePeaks);
int n = notePeaks.size();
for (int i = 0; i < n; ++i) {
if (notePeaks[i].val < noteTH)
break;
OmrNote* note = new OmrNote;
int sym = notePeaks[i].sym;
int hh = patternList[sym]->h();
int hw = patternList[sym]->w();
note->setRect(notePeaks[i].x, y - hh/2, hw, hh);
note->line = line;
note->sym = patternList[sym]->id();
note->prob = notePeaks[i].val;
noteList->append(note);
}
}
//---------------------------------------------------------
// staffDistance
//---------------------------------------------------------
double OmrPage::staffDistance() const
{
if (staves.size() < 2)
return 5;
return ((staves[1].y() - staves[0].y()) / _spatium) - 4.0;
}
//---------------------------------------------------------
// systemDistance
//---------------------------------------------------------
double OmrPage::systemDistance() const
{
if (staves.size() < 3)
return 6.0;
return ((staves[2].y() - staves[1].y()) / _spatium) - 4.0;
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void OmrPage::write(Xml& xml) const
{
xml.stag("OmrPage");
xml.tag("cropL", cropL);
xml.tag("cropR", cropR);
xml.tag("cropT", cropT);
xml.tag("cropB", cropB);
foreach(const QRect& r, staves)
xml.tag("staff", QRectF(r));
xml.etag();
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void OmrPage::read(XmlReader& e)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "cropL")
cropL = e.readInt();
else if (tag == "cropR")
cropR = e.readInt();
else if (tag == "cropT")
cropT = e.readInt();
else if (tag == "cropB")
cropB = e.readInt();
else if (tag == "staff") {
OmrStaff r(e.readRect().toRect());
staves.append(r);
}
else
e.unknown();
}
}
}