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

