483 lines
18 KiB
C++
483 lines
18 KiB
C++
//=============================================================================
|
|
// MuseScore
|
|
// Music Composition & Notation
|
|
//
|
|
// Copyright (C) 2002-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
|
|
// as published by the Free Software Foundation and appearing in
|
|
// the file LICENCE.GPL
|
|
//=============================================================================
|
|
|
|
#include "stringdata.h"
|
|
#include "chord.h"
|
|
#include "note.h"
|
|
#include "part.h"
|
|
#include "score.h"
|
|
#include "staff.h"
|
|
#include "undo.h"
|
|
#include "segment.h"
|
|
|
|
namespace Ms {
|
|
|
|
//---------------------------------------------------------
|
|
// StringData
|
|
//---------------------------------------------------------
|
|
|
|
bool StringData::bFretting = false;
|
|
|
|
StringData::StringData(int numFrets, int numStrings, int strings[])
|
|
{
|
|
instrString strg = { 0, false};
|
|
_frets = numFrets;
|
|
|
|
for (int i = 0; i < numStrings; i++) {
|
|
strg.pitch = strings[i];
|
|
stringTable.append(strg);
|
|
}
|
|
}
|
|
|
|
StringData::StringData(int numFrets, QList<instrString>& strings)
|
|
{
|
|
_frets = numFrets;
|
|
|
|
stringTable.clear();
|
|
foreach(instrString i, strings)
|
|
stringTable.append(i);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// read
|
|
//---------------------------------------------------------
|
|
|
|
void StringData::read(XmlReader& e)
|
|
{
|
|
stringTable.clear();
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "frets")
|
|
_frets = e.readInt();
|
|
else if (tag == "string") {
|
|
instrString strg;
|
|
strg.open = e.intAttribute("open", 0);
|
|
strg.pitch = e.readInt();
|
|
stringTable.append(strg);
|
|
}
|
|
else
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// write
|
|
//---------------------------------------------------------
|
|
|
|
void StringData::write(XmlWriter& xml) const
|
|
{
|
|
xml.stag("StringData");
|
|
xml.tag("frets", _frets);
|
|
foreach(instrString strg, stringTable) {
|
|
if (strg.open)
|
|
xml.tag("string open=\"1\"", strg.pitch);
|
|
else
|
|
xml.tag("string", strg.pitch);
|
|
}
|
|
xml.etag();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// convertPitch
|
|
// Finds string and fret for a note.
|
|
//
|
|
// Fills *string and *fret with suitable values for pitch at given tick of given staff,
|
|
// using the highest possible string.
|
|
// If note cannot be fretted, uses fret 0 on nearest string and returns false
|
|
//
|
|
// Note: Strings are stored internally from lowest (0) to highest (strings()-1),
|
|
// but the returned *string value references strings in reversed, 'visual', order:
|
|
// from highest (0) to lowest (strings()-1)
|
|
//---------------------------------------------------------
|
|
|
|
bool StringData::convertPitch(int pitch, Staff* staff, int tick, int* string, int* fret) const
|
|
{
|
|
return convertPitch(pitch, pitchOffsetAt(staff, tick), string, fret);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getPitch
|
|
// Returns the pitch corresponding to the string / fret combination
|
|
// at given tick of given staff.
|
|
// Returns INVALID_PITCH if not possible
|
|
// Note: frets above max fret are accepted.
|
|
//---------------------------------------------------------
|
|
|
|
int StringData::getPitch(int string, int fret, Staff* staff, int tick) const
|
|
{
|
|
return getPitch(string, fret, pitchOffsetAt(staff, tick));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// fret
|
|
// Returns the fret corresponding to the pitch / string combination
|
|
// at given tick of given staff.
|
|
// Returns FRET_NONE if not possible
|
|
//---------------------------------------------------------
|
|
|
|
int StringData::fret(int pitch, int string, Staff* staff, int tick) const
|
|
{
|
|
return fret(pitch, string, pitchOffsetAt(staff, tick));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// fretChords
|
|
// Assigns fretting to all the notes of each chord in the same segment of chord
|
|
// re-using existing fretting wherever possible
|
|
//
|
|
// Minimizes fret conflicts (multiple notes on the same string)
|
|
// but marks as fretConflict notes which cannot be fretted
|
|
// (outside tablature range) or which cannot be assigned
|
|
// a separate string
|
|
//---------------------------------------------------------
|
|
|
|
void StringData::fretChords(Chord * chord) const
|
|
{
|
|
int nFret, minFret, maxFret, nNewFret, nTempFret;
|
|
int nString, nNewString, nTempString;
|
|
|
|
if(bFretting)
|
|
return;
|
|
bFretting = true;
|
|
|
|
// we need to keep track of string allocation
|
|
int bUsed[strings()]; // initially all strings are available
|
|
for(nString=0; nString<strings(); nString++)
|
|
bUsed[nString] = 0;
|
|
// we also need the notes sorted in order of string (from highest to lowest) and then pitch
|
|
QMap<int, Note *> sortedNotes;
|
|
int count = 0;
|
|
// store staff pitch offset at this tick, to speed up actual note pitch calculations
|
|
// (ottavas not implemented yet)
|
|
int transp = chord->staff() ? chord->part()->instrument()->transpose().chromatic : 0; // TODO: tick?
|
|
int pitchOffset = /*chord->staff()->pitchOffset(chord->segment()->tick())*/ - transp;
|
|
// if chord parent is not a segment, the chord is special (usually a grace chord):
|
|
// fret it by itself, ignoring the segment
|
|
if (chord->parent()->type() != ElementType::SEGMENT)
|
|
sortChordNotes(sortedNotes, chord, pitchOffset, &count);
|
|
else {
|
|
// scan each chord of seg from same staff as 'chord', inserting each of its notes in sortedNotes
|
|
Segment* seg = chord->segment();
|
|
int trk;
|
|
int trkFrom = (chord->track() / VOICES) * VOICES;
|
|
int trkTo = trkFrom + VOICES;
|
|
for(trk = trkFrom; trk < trkTo; ++trk) {
|
|
Element* ch = seg->elist().at(trk);
|
|
if (ch && ch->type() == ElementType::CHORD)
|
|
sortChordNotes(sortedNotes, toChord(ch), pitchOffset, &count);
|
|
}
|
|
}
|
|
// determine used range of frets
|
|
minFret = INT32_MAX;
|
|
maxFret = INT32_MIN;
|
|
foreach(Note* note, sortedNotes) {
|
|
if (note->string() != STRING_NONE)
|
|
bUsed[note->string()]++;
|
|
if (note->fret() != FRET_NONE && note->fret() < minFret)
|
|
minFret = note->fret();
|
|
if (note->fret() != FRET_NONE && note->fret() > maxFret)
|
|
maxFret = note->fret();
|
|
}
|
|
|
|
// scan chord notes from highest, matching with strings from the highest
|
|
foreach(Note * note, sortedNotes) {
|
|
nString = nNewString = note->string();
|
|
nFret = nNewFret = note->fret();
|
|
note->setFretConflict(false); // assume no conflicts on this note
|
|
// if no fretting (any invalid fretting has been erased by sortChordNotes() )
|
|
if (nString == STRING_NONE /*|| nFret == FRET_NONE || getPitch(nString, nFret) != note->pitch()*/) {
|
|
// get a new fretting
|
|
if (!convertPitch(note->pitch(), pitchOffset, &nNewString, &nNewFret) ) {
|
|
// no way to fit this note in this tab:
|
|
// mark as fretting conflict
|
|
note->setFretConflict(true);
|
|
// store fretting change without affecting chord context
|
|
if (nFret != nNewFret)
|
|
note->undoChangeProperty(P_ID::FRET, nNewFret);
|
|
if (nString != nNewString)
|
|
note->undoChangeProperty(P_ID::STRING, nNewString);
|
|
continue;
|
|
}
|
|
// note can be fretted: use string
|
|
else {
|
|
bUsed[nNewString]++;
|
|
}
|
|
}
|
|
|
|
// if the note string (either original or newly assigned) is also used by another note
|
|
if (bUsed[nNewString] > 1) {
|
|
// attempt to find a suitable string, from topmost
|
|
for (nTempString=0; nTempString < strings(); nTempString++) {
|
|
if (bUsed[nTempString] < 1
|
|
&& (nTempFret=fret(note->pitch(), nTempString, pitchOffset)) != FRET_NONE) {
|
|
bUsed[nNewString]--; // free previous string
|
|
bUsed[nTempString]++; // and occupy new string
|
|
nNewFret = nTempFret;
|
|
nNewString = nTempString;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO : try to optimize used fret range, avoiding eccessively open positions
|
|
|
|
// if fretting did change, store as a fret change
|
|
if (nFret != nNewFret)
|
|
note->undoChangeProperty(P_ID::FRET, nNewFret);
|
|
if (nString != nNewString)
|
|
note->undoChangeProperty(P_ID::STRING, nNewString);
|
|
}
|
|
|
|
// check for any remaining fret conflict
|
|
foreach(Note * note, sortedNotes)
|
|
if (bUsed[note->string()] > 1)
|
|
note->setFretConflict(true);
|
|
|
|
bFretting = false;
|
|
}
|
|
|
|
//********************
|
|
// STATIC METHODS
|
|
//********************
|
|
|
|
//---------------------------------------------------------
|
|
// pitchOffsetAt
|
|
// Computes the pitch offset relevant for string data calculation at the given point.
|
|
//
|
|
// For string data calculations, pitch offset may depend on transposition, capos and, possibly, ottavas.
|
|
//---------------------------------------------------------
|
|
|
|
int StringData::pitchOffsetAt(Staff* staff, int /*tick*/)
|
|
{
|
|
int transp = staff ? staff->part()->instrument()->transpose().chromatic : 0; // TODO: tick?
|
|
return (/*staff->pitchOffset(tick)*/ - transp);
|
|
}
|
|
|
|
//********************
|
|
// PRIVATE METHODS
|
|
//********************
|
|
|
|
//---------------------------------------------------------
|
|
// convertPitch
|
|
// Finds string and fret for a note.
|
|
//
|
|
// Fills *string and *fret with suitable values for pitch / pitchOffset,
|
|
// using the highest possible string.
|
|
// If note cannot be fretted, uses fret 0 on nearest string and returns false
|
|
//
|
|
// Note: Strings are stored internally from lowest (0) to highest (strings()-1),
|
|
// but the returned *string value references strings in reversed, 'visual', order:
|
|
// from highest (0) to lowest (strings()-1)
|
|
//---------------------------------------------------------
|
|
|
|
bool StringData::convertPitch(int pitch, int pitchOffset, int* string, int* fret) const
|
|
{
|
|
int strings = stringTable.size();
|
|
if (strings < 1)
|
|
return false;
|
|
|
|
pitch += pitchOffset;
|
|
|
|
// if above max fret on highest string, fret on first string, but return failure
|
|
if(pitch > stringTable.at(strings-1).pitch + _frets) {
|
|
*string = 0;
|
|
*fret = 0;
|
|
return false;
|
|
}
|
|
|
|
// look for a suitable string, starting from the highest
|
|
// NOTE: this assumes there are always enough frets to fill
|
|
// the interval between any fretted string and the next
|
|
for (int i = strings-1; i >=0; i--) {
|
|
instrString strg = stringTable.at(i);
|
|
if(pitch >= strg.pitch) {
|
|
if (pitch == strg.pitch || !strg.open)
|
|
*string = strings - i - 1;
|
|
*fret = pitch - strg.pitch;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// if no string found, pitch is below lowest string:
|
|
// fret on last string, but return failure
|
|
*string = strings-1;
|
|
*fret = 0;
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getPitch
|
|
// Returns the pitch corresponding to the string / fret / pitchOffset combination.
|
|
// Returns INVALID_PITCH if not possible
|
|
// Note: frets above max fret are accepted.
|
|
//---------------------------------------------------------
|
|
|
|
int StringData::getPitch(int string, int fret, int pitchOffset) const
|
|
{
|
|
int strings = stringTable.size();
|
|
if (string < 0 || string >= strings)
|
|
return INVALID_PITCH;
|
|
instrString strg = stringTable.at(strings - string - 1);
|
|
return strg.pitch - pitchOffset + (strg.open ? 0 : fret);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// fret
|
|
// Returns the fret corresponding to the pitch / string / pitchOffset combination.
|
|
// returns FRET_NONE if not possible
|
|
//---------------------------------------------------------
|
|
|
|
int StringData::fret(int pitch, int string, int pitchOffset) const
|
|
{
|
|
int strings = stringTable.size();
|
|
if (strings < 1) // no strings at all!
|
|
return FRET_NONE;
|
|
|
|
if (string < 0 || string >= strings) // no such a string
|
|
return FRET_NONE;
|
|
|
|
pitch += pitchOffset;
|
|
|
|
int fret = pitch - stringTable[strings - string - 1].pitch;
|
|
// fret number is invalid or string cannot be fretted
|
|
if (fret < 0 || fret >= _frets || (fret > 0 && stringTable[strings - string - 1].open))
|
|
return FRET_NONE;
|
|
return fret;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// sortChordNotes
|
|
// Adds to sortedNotes the notes of Chord in string/pitch order
|
|
// Note: notes are sorted first by string (top string being 0),
|
|
// then by negated pitch (higher pitches resulting in lower key),
|
|
// then by order of submission to disambiguate notes with the same pitch.
|
|
// Everything else being equal, this makes notes in higher-numbered voices
|
|
// to be sorted after notes in lower-numbered voices (voice 2 after voice 1 and so on)
|
|
// Notes without a string assigned yet, are sorted according to the lowest string which can accommodate them.
|
|
//---------------------------------------------------------
|
|
|
|
void StringData::sortChordNotes(QMap<int, Note *>& sortedNotes, const Chord *chord, int pitchOffset, int* count) const
|
|
{
|
|
int key, string, fret;
|
|
|
|
foreach(Note * note, chord->notes()) {
|
|
string = note->string();
|
|
fret = note->fret();
|
|
// if note not fretted yet or current fretting no longer valid,
|
|
// use most convenient string as key
|
|
if (string <= STRING_NONE || fret <= FRET_NONE
|
|
|| getPitch(string, fret, pitchOffset) != note->pitch()) {
|
|
note->setString(STRING_NONE);
|
|
note->setFret(FRET_NONE);
|
|
convertPitch(note->pitch(), pitchOffset, &string, &fret);
|
|
}
|
|
key = string * 100000;
|
|
key += -(note->pitch()+pitchOffset) * 100 + *count; // disambiguate notes of equal pitch
|
|
sortedNotes.insert(key, note);
|
|
(*count)++;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
//---------------------------------------------------------
|
|
// MusicXMLStepAltOct2Pitch
|
|
//---------------------------------------------------------
|
|
|
|
/**
|
|
Convert MusicXML \a step / \a alter / \a octave to midi pitch.
|
|
Note: similar to (part of) xmlSetPitch in mscore/importxml.cpp.
|
|
TODO: combine ?
|
|
*/
|
|
|
|
static int MusicXMLStepAltOct2Pitch(char step, int alter, int octave)
|
|
{
|
|
int istep = step - 'A';
|
|
// a b c d e f g
|
|
static int table[7] = { 9, 11, 0, 2, 4, 5, 7 };
|
|
if (istep < 0 || istep > 6) {
|
|
qDebug("MusicXMLStepAltOct2Pitch: illegal step %d, <%c>", istep, step);
|
|
return -1;
|
|
}
|
|
int pitch = table[istep] + alter + (octave+1) * 12;
|
|
|
|
if (pitch < 0)
|
|
pitch = -1;
|
|
if (pitch > 127)
|
|
pitch = -1;
|
|
|
|
return pitch;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Read MusicXML
|
|
//---------------------------------------------------------
|
|
|
|
void StringData::readMusicXML(XmlReader& e)
|
|
{
|
|
_frets = 25;
|
|
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "staff-lines") {
|
|
int val = e.readInt();
|
|
if (val > 0) {
|
|
// resize the string table and init with zeroes
|
|
stringTable = QVector<int>(val).toList();
|
|
}
|
|
else
|
|
qDebug("StringData::readMusicXML: illegal staff-lines %d", val);
|
|
}
|
|
else if (tag == "staff-tuning") {
|
|
int line = e.intAttribute("line");
|
|
QString step;
|
|
int alter = 0;
|
|
int octave = 0;
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "tuning-alter")
|
|
alter = e.readInt();
|
|
else if (tag == "tuning-octave")
|
|
octave = e.readInt();
|
|
else if (tag == "tuning-step")
|
|
step = e.readElementText();
|
|
else
|
|
e.unknown();
|
|
}
|
|
if (0 < line && line <= stringTable.size()) {
|
|
int pitch = MusicXMLStepAltOct2Pitch(step[0].toLatin1(), alter, octave);
|
|
if (pitch >= 0)
|
|
stringTable[line - 1] = pitch;
|
|
else
|
|
qDebug("StringData::readMusicXML invalid string %d tuning step/alter/oct %s/%d/%d",
|
|
line, qPrintable(step), alter, octave);
|
|
}
|
|
}
|
|
else if (tag == "capo") {
|
|
; // not supported: silently ignored
|
|
}
|
|
else {
|
|
; // others silently ignored
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//---------------------------------------------------------
|
|
// Write MusicXML
|
|
//---------------------------------------------------------
|
|
|
|
void StringData::writeMusicXML(XmlWriter& /*xml*/) const
|
|
{
|
|
}
|
|
|
|
}
|
|
|