musescore_2.3+dfsg1.orig.tar.xz 2.3+dfsg1
authormirabilos <tg@debian.org>
Sun, 1 Jul 2018 19:39:42 +0000 (21:39 +0200)
committermirabilos <mirabilos@evolvis.org>
Sun, 1 Jul 2018 19:39:42 +0000 (21:39 +0200)
301 files changed:
.appveyor.yml
CMakeLists.txt
Makefile
Makefile.mingw
Makefile.osx
all.h
audiofile/audiofile.cpp
audiofile/audiofile.h
build/Linux+BSD/mscore.1.in
build/Packaging.cmake
build/package_mac
build/travis/job2_AppImage/osuosl.sh
build/travis/job_macos/script.sh
fluid/fluid.cpp
fluid/sfont3.cpp
libmscore/beam.cpp
libmscore/chord.cpp
libmscore/chordline.cpp
libmscore/clef.cpp
libmscore/clef.h
libmscore/cmd.cpp
libmscore/drumset.cpp
libmscore/drumset.h
libmscore/durationtype.cpp
libmscore/edit.cpp
libmscore/instrtemplate.cpp
libmscore/instrtemplate.h
libmscore/instrument.cpp
libmscore/keysig.cpp
libmscore/layout.cpp
libmscore/line.cpp
libmscore/measure.cpp
libmscore/note.cpp
libmscore/note.h
libmscore/rendermidi.cpp
libmscore/scorefile.cpp
libmscore/slur.cpp
libmscore/staff.cpp
libmscore/stafftype.cpp
libmscore/stafftype.h
libmscore/stem.cpp
libmscore/stemslash.cpp
libmscore/stringdata.cpp
libmscore/stringdata.h
libmscore/sym.cpp
libmscore/sym.h
libmscore/tempotext.cpp
libmscore/textline.cpp
libmscore/tremolo.cpp
libmscore/tremolo.h
libmscore/tuplet.cpp
libmscore/tuplet.h
libmscore/undo.cpp
libmscore/utils.cpp
libmscore/utils.h
libmscore/xml.cpp
mscore/CMakeLists.txt
mscore/album.cpp
mscore/album.h
mscore/albummanager.cpp
mscore/albummanager.h
mscore/albummanager.ui
mscore/data/splash.png
mscore/data/splash.svg
mscore/downloadUtils.cpp
mscore/downloadUtils.h
mscore/drumtools.cpp
mscore/drumtools.h
mscore/editdrumset.cpp
mscore/editdrumset.h
mscore/editdrumset.ui
mscore/editstafftype.cpp
mscore/exportxml.cpp
mscore/extension.cpp [new file with mode: 0644]
mscore/extension.h [new file with mode: 0644]
mscore/file.cpp
mscore/fotomode.cpp
mscore/importgtp.cpp
mscore/importmxmlpass2.cpp
mscore/inspector/inspector.cpp
mscore/inspector/inspectorNote.cpp
mscore/instrdialog.cpp
mscore/instrdialog.h
mscore/instrwidget.cpp
mscore/keyb.cpp
mscore/menus.cpp
mscore/mixer.cpp
mscore/musescore.cpp
mscore/musescore.h
mscore/newwizard.cpp
mscore/newwizard.h
mscore/palette.h
mscore/palettebox.cpp
mscore/palettebox.h
mscore/pianotools.cpp
mscore/pianotools.h
mscore/pluginManager.cpp
mscore/preferences.cpp
mscore/preferences.h
mscore/prefsdialog.h
mscore/prefsdialog.ui
mscore/propertymenu.cpp
mscore/resourceManager.cpp
mscore/resourceManager.h
mscore/resourceManager.ui
mscore/revision.h
mscore/scoreBrowser.cpp
mscore/scoreview.cpp
mscore/seq.cpp
mscore/shortcut.cpp
mscore/shortcutcapturedialog.cpp
mscore/stringutils.cpp
mscore/stringutils.h
mscore/synthcontrol.cpp
mscore/synthcontrol.h
mscore/updatechecker.cpp
mscore/updatechecker.h
mscore/workspace.cpp
mscore/workspace.h
mtest/CMakeLists.txt
mtest/importmidi/instrument_channels.mscx
mtest/importmidi/perc_drums.mscx
mtest/importmidi/perc_no_grand_staff.mscx
mtest/importmidi/perc_remove_ties.mscx
mtest/importmidi/perc_respect_beat.mscx
mtest/importmidi/perc_short_notes.mscx
mtest/importmidi/perc_triplet.mscx
mtest/importmidi/perc_tuplet_simplify.mscx
mtest/importmidi/perc_tuplet_simplify2.mscx
mtest/importmidi/perc_tuplet_voice.mscx
mtest/importmidi/updateReference
mtest/libmscore/CMakeLists.txt
mtest/libmscore/note/tst_note.cpp
mtest/libmscore/utils/CMakeLists.txt [new file with mode: 0644]
mtest/libmscore/utils/tst_utils.cpp [new file with mode: 0644]
mtest/mtest.cpp
mtest/zerberus/comments/CMakeLists.txt
mtest/zerberus/envelopes/CMakeLists.txt
mtest/zerberus/envelopes/tst_sfzenvelopes.cpp
mtest/zerberus/global/CMakeLists.txt
mtest/zerberus/includes/CMakeLists.txt
mtest/zerberus/inputControls/CMakeLists.txt
mtest/zerberus/loop/CMakeLists.txt
mtest/zerberus/loop/tst_sfzloop.cpp
mtest/zerberus/opcodeparse/CMakeLists.txt
mtest/zerberus/opcodeparse/opcodeTest.sfz
mtest/zerberus/opcodeparse/tst_sfzopcodes.cpp
share/instruments/generateTs.py
share/instruments/instruments.xml
share/instruments/instrumentsxml.h
share/locale/instruments_af.ts
share/locale/instruments_ar.ts
share/locale/instruments_ar_DZ.ts
share/locale/instruments_ar_EG.ts
share/locale/instruments_ar_SD.ts
share/locale/instruments_ast.ts
share/locale/instruments_be.ts
share/locale/instruments_bg.ts
share/locale/instruments_ca.ts
share/locale/instruments_ca@valencia.ts
share/locale/instruments_cs.ts
share/locale/instruments_cy.ts
share/locale/instruments_da.ts
share/locale/instruments_de.ts
share/locale/instruments_el.ts
share/locale/instruments_en_GB.ts
share/locale/instruments_eo.ts
share/locale/instruments_es.ts
share/locale/instruments_et.ts
share/locale/instruments_eu.ts
share/locale/instruments_fa.ts
share/locale/instruments_fi.ts
share/locale/instruments_fo.ts
share/locale/instruments_fr.ts
share/locale/instruments_ga.ts
share/locale/instruments_gd.ts
share/locale/instruments_gl.ts
share/locale/instruments_he.ts
share/locale/instruments_hi_IN.ts
share/locale/instruments_hr.ts
share/locale/instruments_hu.ts
share/locale/instruments_hy.ts
share/locale/instruments_id.ts
share/locale/instruments_ig.ts
share/locale/instruments_it.ts
share/locale/instruments_ja.ts
share/locale/instruments_ka.ts
share/locale/instruments_kab.ts
share/locale/instruments_ko.ts
share/locale/instruments_lt.ts
share/locale/instruments_lv.ts
share/locale/instruments_ml.ts
share/locale/instruments_mn_MN.ts
share/locale/instruments_nb.ts
share/locale/instruments_nl.ts
share/locale/instruments_nn.ts
share/locale/instruments_pl.ts
share/locale/instruments_pt.ts
share/locale/instruments_pt_BR.ts
share/locale/instruments_ro.ts
share/locale/instruments_ru.ts
share/locale/instruments_sk.ts
share/locale/instruments_sl.ts
share/locale/instruments_sr.ts
share/locale/instruments_sr_RS.ts
share/locale/instruments_sv.ts
share/locale/instruments_th.ts
share/locale/instruments_tr.ts
share/locale/instruments_uk.ts
share/locale/instruments_uz@Latn.ts
share/locale/instruments_vi.ts
share/locale/instruments_zh_CN.ts
share/locale/instruments_zh_TW.ts
share/locale/mscore_af.ts
share/locale/mscore_ar.ts
share/locale/mscore_ar_DZ.ts
share/locale/mscore_ar_EG.ts
share/locale/mscore_ar_SD.ts
share/locale/mscore_ast.ts
share/locale/mscore_be.ts
share/locale/mscore_bg.ts
share/locale/mscore_ca.ts
share/locale/mscore_ca@valencia.ts
share/locale/mscore_cs.ts
share/locale/mscore_cy.ts
share/locale/mscore_da.ts
share/locale/mscore_de.ts
share/locale/mscore_el.ts
share/locale/mscore_en_GB.ts
share/locale/mscore_en_US.ts
share/locale/mscore_eo.ts
share/locale/mscore_es.ts
share/locale/mscore_et.ts
share/locale/mscore_eu.ts
share/locale/mscore_fa.ts
share/locale/mscore_fi.ts
share/locale/mscore_fo.ts
share/locale/mscore_fr.ts
share/locale/mscore_ga.ts
share/locale/mscore_gd.ts
share/locale/mscore_gl.ts
share/locale/mscore_he.ts
share/locale/mscore_hi_IN.ts
share/locale/mscore_hr.ts
share/locale/mscore_hu.ts
share/locale/mscore_hy.ts
share/locale/mscore_id.ts
share/locale/mscore_ig.ts
share/locale/mscore_it.ts
share/locale/mscore_ja.ts
share/locale/mscore_ka.ts
share/locale/mscore_kab.ts
share/locale/mscore_ko.ts
share/locale/mscore_lt.ts
share/locale/mscore_lv.ts
share/locale/mscore_ml.ts
share/locale/mscore_mn_MN.ts
share/locale/mscore_nb.ts
share/locale/mscore_nl.ts
share/locale/mscore_nn.ts
share/locale/mscore_pl.ts
share/locale/mscore_pt.ts
share/locale/mscore_pt_BR.ts
share/locale/mscore_ro.ts
share/locale/mscore_ru.ts
share/locale/mscore_sk.ts
share/locale/mscore_sl.ts
share/locale/mscore_sr.ts
share/locale/mscore_sr_RS.ts
share/locale/mscore_sv.ts
share/locale/mscore_th.ts
share/locale/mscore_tr.ts
share/locale/mscore_uk.ts
share/locale/mscore_uz@Latn.ts
share/locale/mscore_vi.ts
share/locale/mscore_zh_CN.ts
share/locale/mscore_zh_TW.ts
synthesizer/msynthesizer.cpp
synthesizer/msynthesizer.h
thirdparty/qzip/qzip.cpp
thirdparty/qzip/qzipreader_p.h
vtest/accidental-mirror-ref.png [new file with mode: 0644]
vtest/accidental-mirror.mscz [new file with mode: 0644]
vtest/beams-16.mscz
vtest/gen
vtest/gen.bat
vtest/hairpins-1.mscz
vtest/system-3.mscz
zerberus/CMakeLists.txt
zerberus/README
zerberus/filter.cpp [new file with mode: 0644]
zerberus/filter.h [new file with mode: 0644]
zerberus/instrument.cpp
zerberus/sample.h
zerberus/sfz.cpp
zerberus/voice.cpp
zerberus/voice.h
zerberus/zerberus.cpp
zerberus/zerberus.h
zerberus/zerberusgui.cpp
zerberus/zone.h

index 66c09b6..9679595 100644 (file)
@@ -4,6 +4,8 @@ clone_folder: C:\MuseScore
 # set clone depth
 clone_depth: 3                      # clone entire repository history if not defined
 
+os: Visual Studio 2013
+
 # build cache to preserve files/folders between builds
 cache:
   - dependencies.7z
index 856f994..c8e1e94 100644 (file)
@@ -159,13 +159,13 @@ if (APPLE)
 endif (APPLE)
 
 #
-#     Check for gcc compiler >= 4.7
+#     Check for gcc compiler >= 4.8
 #
 if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
       if (${CMAKE_CXX_COMPILER_MAJOR} LESS 4
-        OR ((${CMAKE_CXX_COMPILER_MAJOR} EQUAL 4) AND (${CMAKE_CXX_COMPILER_MINOR} LESS 7)))
+        OR ((${CMAKE_CXX_COMPILER_MAJOR} EQUAL 4) AND (${CMAKE_CXX_COMPILER_MINOR} LESS 8)))
             message(FATAL_ERROR "bad gcc compiler version " ${CMAKE_CXX_COMPILER_VERSION}
-                  " >= 4.7 required")
+                  " >= 4.8 required")
       endif()
 endif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
 
@@ -193,8 +193,8 @@ set(CMAKE_SKIP_RULE_DEPENDENCY TRUE)
 # The Mscore version number.
 SET(MUSESCORE_NAME "MuseScore")
 SET(MUSESCORE_VERSION_MAJOR  "2")
-SET(MUSESCORE_VERSION_MINOR  "2")
-SET(MUSESCORE_VERSION_PATCH  "1")
+SET(MUSESCORE_VERSION_MINOR  "3")
+SET(MUSESCORE_VERSION_PATCH  "0")
 SET(MUSESCORE_VERSION       "${MUSESCORE_VERSION_MAJOR}.${MUSESCORE_VERSION_MINOR}")
 SET(MUSESCORE_NAME_VERSION "${MUSESCORE_NAME} ${MUSESCORE_VERSION_MAJOR}")
 #
@@ -651,7 +651,7 @@ subdirs(
       effects thirdparty/rtf2html thirdparty/diff thirdparty/beatroot
       thirdparty/xmlstream thirdparty/qzip thirdparty/kQOAuth
       )
-string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE)
+string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE)
 if (APPLE AND CMAKE_BUILD_TYPE MATCHES "DEBUG")
 #with xcode, we need to have all the targets in the same project
 add_subdirectory(mtest)
index 1c517d3..0de4449 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -22,8 +22,8 @@ REVISION  := `cat mscore/revision.h`
 CPUS      := $(shell getconf _NPROCESSORS_ONLN 2>/dev/null || getconf NPROCESSORS_ONLN 2>/dev/null || echo 1)
 
 PREFIX    = "/usr/local"
-#VERSION   = "2.2b-${REVISION}"
-VERSION = 2.2.1
+#VERSION   = "2.3b-${REVISION}"
+VERSION = 2.3
 
 # Override SUFFIX and LABEL when multiple versions are installed to avoid conflicts.
 SUFFIX=""# E.g.: SUFFIX="dev" --> "mscore" becomes "mscoredev"
index 9f14685..80f85df 100644 (file)
@@ -20,8 +20,8 @@
 
 
 REVISION  = $(shell type mscore\revision.h)
-#VERSION   = 2.2b-${REVISION}
-VERSION = 2.2.1
+#VERSION   = 2.3b-${REVISION}
+VERSION = 2.3
 CPUS = %NUMBER_OF_PROCESSORS%
 BUILD_FOR_WINSTORE="OFF"     # Override with "ON" to enable.
 
index 543ef23..4e111f7 100644 (file)
@@ -19,8 +19,8 @@
 #=============================================================================
 
 REVISION  = `cat mscore/revision.h`
-#VERSION   = "2.2b-${REVISION}"
-VERSION = 2.2.1
+#VERSION   = "2.3b-${REVISION}"
+VERSION = 2.3
 PREFIX=../applebuild
 
 XCODEPROJ = mscore.xcodeproj
diff --git a/all.h b/all.h
index d662e11..18686f0 100644 (file)
--- a/all.h
+++ b/all.h
 #include <QProgressBar>
 #include <QProgressDialog>
 #include <QRadioButton>
+#include <QButtonGroup>
 #include <QSplashScreen>
 #include <QFontComboBox>
 #include <QApplication>
index 28856b0..e434e51 100644 (file)
@@ -63,17 +63,43 @@ bool AudioFile::open(const QByteArray& b)
       idx = 0;
       sf  = sf_open_virtual(&sfio, SFM_READ, &info, this);
       hasInstrument = sf_command(sf, SFC_GET_INSTRUMENT, &inst, sizeof(inst)) == SF_TRUE;
-
+      _type = info.format & SF_FORMAT_OGG ? fltp : s16p;
       return sf != 0;
       }
 
 //---------------------------------------------------------
-//   read
+//   readData
 //---------------------------------------------------------
 
-int AudioFile::read(short* data, int frames)
+sf_count_t AudioFile::readData(short* data, sf_count_t frames)
       {
-      return sf_readf_short(sf, data, frames);
+      //see https://musescore.org/en/node/22086#comment-83671
+      //see https://github.com/erikd/libsndfile/issues/16
+      //this code fixes the bug in libsndfile: float values are not normalized when reading .ogg
+      //this leads to overflowing signed short values, reverting a sign and clicking noise
+      sf_count_t resFrames = 0;
+      if (s16p == _type)
+            resFrames = sf_readf_short(sf, data, frames);
+      else {
+            //read native float values
+            int totalFrames = frames * channels();
+            float* dataF = new float[totalFrames];
+            resFrames = sf_readf_float(sf, dataF, frames);
+            //find the maximum signal value
+            float maxSignal = 0.f;
+            for (int i = 0; i < totalFrames; ++i) {
+                  if (fabs(dataF[i]) > maxSignal)
+                        maxSignal = dataF[i] > 0 ? dataF[i] : -dataF[i];
+                  }
+            //normalize values if and only if sample values range is incorrect
+            //which means having at least one sample value more than 1.0
+            float adjScale = maxSignal > 1.f ? 1.f/maxSignal : 1.f;
+            //convert normalized floats to signed short values
+            for (int i = 0; i < totalFrames; ++i)
+                  data[i] = adjScale * lrintf(dataF[i] * (dataF[i] > 0 ? SHRT_MAX : -SHRT_MIN));
+            }
+
+      return resFrames;
       }
 
 //---------------------------------------------------------
index a5d1a75..d25ff59 100644 (file)
 //---------------------------------------------------------
 
 class AudioFile {
+      enum FormatType
+            {
+            s16p,
+            fltp
+            };
       SF_INFO info;
       SNDFILE* sf;
       SF_INSTRUMENT inst;
       bool hasInstrument;
       QByteArray buf;  // used during read of Sample
       int idx;
+      FormatType _type;
 
    public:
       AudioFile();
@@ -33,10 +39,10 @@ class AudioFile {
 
       bool open(const QByteArray&);
       const char* error() const     { return sf_strerror(sf); }
-      int read(short*, int);
+      sf_count_t readData(short* data, sf_count_t frames);
 
       int channels() const   { return info.channels; }
-      int frames() const     { return info.frames; }
+      sf_count_t frames() const     { return info.frames; }
       int samplerate() const { return info.samplerate; }
 
       sf_count_t getFileLen() const { return buf.size(); }
index a8ba0cc..9db84c2 100644 (file)
@@ -68,9 +68,9 @@ For help with the full MuseScore program see:
 .TP
 These pages cover the topics in this manual and may be more up-to-date:
 
-<https://musescore.org/handbook/command-line-options-0>
+<https://musescore.org/handbook/command\-line\-options\-0>
 .br
-<https://musescore.org/handbook/revert-factory-settings-0>
+<https://musescore.org/handbook/revert\-factory\-settings\-0>
 
 .SH OPTIONS
 A summary of options is included below. Running
@@ -81,7 +81,7 @@ program and opens any specified files.
 @BEGIN_section_to_only_appear_in_portable_builds@
 @MAN_PORTABLE@.SS Special options for the portable version
 @MAN_PORTABLE@.TP
-@MAN_PORTABLE@.B \-h, --help
+@MAN_PORTABLE@.B \-h, \-\-help
 @MAN_PORTABLE@Lists the various command line options and their usage.
 @MAN_PORTABLE@.TP
 @MAN_PORTABLE@.B man, manual, manpage
@@ -89,9 +89,9 @@ program and opens any specified files.
 @MAN_PORTABLE@If MuseScore is installed the manual can be viewed using the familiar `man
 @MAN_PORTABLE@.BR mscore@MSCORE_INSTALL_SUFFIX@ `.
 @MAN_PORTABLE@.TP
-@MAN_PORTABLE@.B install [-i] [PREFIX]
+@MAN_PORTABLE@.B install [\-i] [PREFIX]
 @MAN_PORTABLE@.RS
-@MAN_PORTABLE@Run this without '-i' or 'PREFIX' to fully integrate MuseScore
+@MAN_PORTABLE@Run this without '\-i' or 'PREFIX' to fully integrate MuseScore
 @MAN_PORTABLE@with the desktop environment (GNOME, KDE, etc). This should:
 @MAN_PORTABLE@.RS 2
 @MAN_PORTABLE@.IP \[bu] 2
@@ -109,7 +109,7 @@ program and opens any specified files.
 @MAN_PORTABLE@.RE
 @MAN_PORTABLE@
 @MAN_PORTABLE@Run as root (sudo) to install for all users.
-@MAN_PORTABLE@Advanced users can use '-i' to enter interactive mode
+@MAN_PORTABLE@Advanced users can use '\-i' to enter interactive mode
 @MAN_PORTABLE@and 'PREFIX' to install to a custom location.
 @MAN_PORTABLE@Installation not required to run the program.
 @MAN_PORTABLE@.RE
@@ -123,7 +123,7 @@ program and opens any specified files.
 @MAN_PORTABLE@If you installed for all users then it will be removed for all users.
 @MAN_PORTABLE@
 @MAN_PORTABLE@.TP
-@MAN_PORTABLE@.B check-depends, check-dependencies [exes-only]
+@MAN_PORTABLE@.B check\-depends, check\-dependencies [exes\-only]
 @MAN_PORTABLE@System information for developers.
 @MAN_PORTABLE@This detects which software libraries needed
 @MAN_PORTABLE@by MuseScore are not available from the system
@@ -132,86 +132,86 @@ program and opens any specified files.
 @MAN_PORTABLE@.SS Normal MuseScore options
 @END_section_to_only_appear_in_portable_builds@
 .TP
-.B \-h, --help
+.B \-h, \-\-help
 Displays help.
 .TP
-.B \-v, --version
+.B \-v, \-\-version
 Displays MuseScore's current version in the command line without starting the graphical interface.
 .TP
-.B \--long-version
+.B \-\-long\-version
 Displays MuseScore's current version and revision in the command line without starting the graphical interface.
 .TP
-.B \-d, --debug
+.B \-d, \-\-debug
 Starts MuseScore in debug mode.
 .TP
-.B \-L, --layout-debug
+.B \-L, \-\-layout\-debug
 Starts MuseScore in layout debug mode
 .TP
-.B \-s, --no-synthesizer
+.B \-s, \-\-no\-synthesizer
 Disables the integrated software synthesizer
 .TP
-.B \-m, --no-midi
+.B \-m, \-\-no\-midi
 Disables MIDI input
 .TP
-.B \-a, --use-audio <driver>
+.B \-a, \-\-use\-audio <driver>
 Use audio driver: jack, alsa, pulse, portaudio
 .TP
-.B \-n, --new-score
+.B \-n, \-\-new\-score
 Starts with the new score wizard regardless of preference setting for start mode
 .TP
-.B \-I, --dump-midi-in
+.B \-I, \-\-dump\-midi\-in
 Displays all MIDI input on the console
 .TP
-.B \-O, --dump-midi-out
+.B \-O, \-\-dump\-midi\-out
 Displays all MIDI output on the console
 .TP
-.B \-o, --export-to <filename>
-Exports the currently opened file to the specified <filename>. The file type depends on the filename extension. This option switches to the "converter" mode and avoids any graphical interface. You can also add a filename before the -o if you want to import and export files from the command line. For example mscore@MSCORE_INSTALL_SUFFIX@ -o "My Score.pdf" "My Score.mscz"
+.B \-o, \-\-export\-to <filename>
+Exports the currently opened file to the specified <filename>. The file type depends on the filename extension. This option switches to the "converter" mode and avoids any graphical interface. You can also add a filename before the \-o if you want to import and export files from the command line. For example mscore@MSCORE_INSTALL_SUFFIX@ \-o "My Score.pdf" "My Score.mscz"
 .TP
-.B \-r, --image-resolution <dpi>
+.B \-r, \-\-image\-resolution <dpi>
 Determines the output resolution for the output to "*.png" files in the converter mode. The default resolution is 300 dpi.
 .TP
-.B \-T, --trim-margin <margin>
+.B \-T, \-\-trim\-margin <margin>
 Trims exported PNG and SVG images to remove surrounding whitespace around the score. The specified number of pixels of whitespace will be added as a margin; use 0 for a tightly cropped image. For SVG, this option works only with single-page scores.
 .TP
-.B \-x, --gui-scaling <factor>
+.B \-x, \-\-gui\-scaling <factor>
 Scales the score display and other GUI elements by the specified factor, for use with high resolution displays.
 .TP
-.B \-S, --style <style>
+.B \-S, \-\-style <style>
 Loads a style file; useful when you convert with the \-o option
 .TP
-.B \-p, --plugin <name>
+.B \-p, \-\-plugin <name>
 Execute the named plugin
 .TP
-.B \--template-mode
+.B \-\-template\-mode
 Save template mode, no page size
 .TP
-.B \-F, --factory-settings
-Use only the standard built-in presets or "factory-settings" and delete preferences
+.B \-F, \-\-factory\-settings
+Use only the standard built-in presets or "factory settings" and delete preferences
 .TP
-.B \-R, --revert-settings
-Use only the standard built-in presets or "factory-settings", but do not delete preferences
+.B \-R, \-\-revert\-settings
+Use only the standard built-in presets or "factory settings", but do not delete preferences
 .TP
-.B \-i, --load-icons
+.B \-i, \-\-load\-icons
 Load icons from the file system. Useful if you want to edit the MuseScore icons and preview the changes
 .TP
-.B \-e, --experimental
+.B \-e, \-\-experimental
 Enable experimental features. (E.g. layers).
 .TP
-.B \-c, --config-folder <pathname>
+.B \-c, \-\-config\-folder <pathname>
 Set config path
 .TP
-.B \-t, --test-mode
+.B \-t, \-\-test\-mode
 Enable Test Mode
 .TP
-.B \-M, --midi-operations <file>
+.B \-M, \-\-midi\-operations <file>
 Specify MIDI import operations file
 .TP
-.B \-w, --no-webview
+.B \-w, \-\-no\-webview
 No web view in Start Center
 .TP
-.B \-P, --export-score-parts
-Used with -o .pdf, export score and parts
+.B \-P, \-\-export\-score\-parts
+Used with \-o .pdf, export score and parts
 
 .SH FILES
 Advanced users can find MuseScore's configuration files at:
index cc84961..a8beafe 100644 (file)
@@ -61,7 +61,7 @@ IF(MINGW)
 
   file(TO_CMAKE_PATH $ENV{PROGRAMFILES} PROGRAMFILES)
   SET(CPACK_WIX_ROOT "${PROGRAMFILES}/WiX Toolset v3.8")
-  SET(CPACK_WIX_PRODUCT_GUID "0317B5F7-01A3-4640-A491-456B453CCAB3")
+  SET(CPACK_WIX_PRODUCT_GUID "688E76DF-EA1E-4CFA-AE73-F57B3A174F01")
   SET(CPACK_WIX_UPGRADE_GUID "6CF17D7E-4CF1-42CF-A23A-B52F7E883D51")
   SET(CPACK_WIX_LICENSE_RTF   "${PROJECT_SOURCE_DIR}/LICENSE.rtf")
   SET(CPACK_WIX_PRODUCT_ICON "${PROJECT_SOURCE_DIR}/mscore/data/mscore.ico")
index 37b95ae..87c6571 100755 (executable)
@@ -11,7 +11,7 @@ then
 else
     LONG_NAME=MuseScore
     LONGER_NAME="MuseScore 2"
-    VERSION=2.2.1
+    VERSION=2.3
 fi
 
 WORKING_DIRECTORY=applebuild
index 443cb43..fb9a2b4 100755 (executable)
@@ -63,4 +63,4 @@ esac
 scp -C -i $SSH_INDENTITY $FILE musescore-nightlies@ftp-osl.osuosl.org:ftp/linux/$ARCH_NAME/$FILE_UPLOAD_PATH
 
 # delete old files
-ssh -i $SSH_INDENTITY musescore-nightlies@ftp-osl.osuosl.org "cd ~/ftp/linux/$ARCH_NAME; ls MuseScoreNightly* -t | tail -n +41 | xargs rm"
+ssh -i $SSH_INDENTITY musescore-nightlies@ftp-osl.osuosl.org "cd ~/ftp/linux/$ARCH_NAME; ls MuseScoreNightly* -t | tail -n +41 | xargs -r rm"
index a219fff..4f89546 100755 (executable)
@@ -56,7 +56,7 @@ ssh -i $SSH_INDENTITY musescore-nightlies@ftp-osl.osuosl.org "~/trigger-musescor
 # update translation on transifex
 make -f Makefile.osx lupdate
 
-sudo pip install transifex-client
+#sudo pip install transifex-client
 
 cat > ~/.transifexrc <<EOL
 [https://www.transifex.com]
@@ -66,7 +66,7 @@ token =
 username = $TRANSIFEX_USER
 EOL
 
-tx push -s
+#tx push -s
 
 
 
index dd9d4da..02f7101 100644 (file)
@@ -21,6 +21,7 @@
 #include "synthesizer/event.h"
 #include "synthesizer/msynthesizer.h"
 #include "mscore/preferences.h"
+#include "mscore/extension.h"
 
 #include "fluid.h"
 #include "sfont.h"
@@ -623,9 +624,7 @@ bool Fluid::loadSoundFonts(const QStringList& sl)
       locker.unlock();
       bool ok = true;
 
-
       QFileInfoList l = sfFiles();
-
       for (int i = sl.size() - 1; i >= 0; --i) {
             QString s = sl[i];
             if (s.isEmpty())
@@ -896,6 +895,10 @@ QFileInfoList Fluid::sfFiles()
       QStringList pl = preferences.mySoundfontsPath.split(";");
       pl.prepend(QFileInfo(QString("%1%2").arg(mscoreGlobalShare).arg("sound")).absoluteFilePath());
 
+      // append extensions directory
+      QStringList extensionsDir = Ms::Extension::getDirectoriesByType(Ms::Extension::soundfontsDir);
+      pl.append(extensionsDir);
+
       foreach (const QString& s, pl) {
             QString ss(s);
             if (!s.isEmpty() && s[0] == '~')
index 105ac38..97c32df 100644 (file)
@@ -23,7 +23,7 @@ bool Sample::decompressOggVorbis(char* src, int size)
             }
       int frames = af.frames();
       data = new short[frames * af.channels()];
-      if (frames != af.read(data, frames)) {
+      if (frames != af.readData(data, frames)) {
             qDebug("Sample read failed: %s", af.error());
             delete[] data;
             data = 0;
index 94117e1..eceeb6f 100644 (file)
@@ -1911,7 +1911,7 @@ qDebug("create stem in layout beam, track %d", c->track());
             if (c->hook())
                   score()->undoRemoveElement(c->hook());
 
-            QPointF stemPos(c->stemPos());
+            QPointF stemPos(c->stemPosX() + c->pagePos().x(), c->stemPos().y());
             qreal x2 = stemPos.x() - _pagePos.x();
             qreal y1 = (x2 - x1) * slope + py1 + _pagePos.y();
             qreal y2 = stemPos.y();
index 5b03948..494efbd 100644 (file)
@@ -424,10 +424,7 @@ void Chord::add(Element* e)
                         }
                   if (!found)
                         _notes.append(note);
-                  if (note->tieFor()) {
-                        if (note->tieFor()->endNote())
-                              note->tieFor()->endNote()->setTieBack(note->tieFor());
-                        }
+                  note->connectTiedNotes();
                   if (voice() && measure() && note->visible())
                         measure()->mstaff(staffIdx())->hasVoices = true;
                   }
@@ -504,10 +501,7 @@ void Chord::remove(Element* e)
                   {
                   Note* note = static_cast<Note*>(e);
                   if (_notes.removeOne(note)) {
-                        if (note->tieFor()) {
-                              if (note->tieFor()->endNote())
-                                    note->tieFor()->endNote()->setTieBack(0);
-                              }
+                        note->disconnectTiedNotes();
                         for (Spanner* s : note->spannerBack()) {
                               note->removeSpannerBack(s);
                               }
@@ -734,7 +728,8 @@ void Chord::addLedgerLines()
 
                   // check if note horiz. pos. is outside current range
                   // if more length on the right, increase range
-                  x = note->pos().x();
+                  // ledger lines need the leftmost point of the notehead with a respect of bbox
+                  x = note->pos().x() + note->bboxXShift();
                   if (x-extraLen < minX) {
                         minX  = x - extraLen;
 //                        minXr = minX - extraLen;
@@ -1154,10 +1149,9 @@ qreal Chord::centerX() const
             return staff()->staffType()->chordStemPosX(this) * spatium();
 
       const Note* note = up() ? upNote() : downNote();
-      qreal x = note->pos().x();
-      x += note->headWidth() * .5;
+      qreal x = note->pos().x() + note->noteheadCenterX();
       if (note->mirror()) {
-            x += note->headWidth() * (up() ? -1.0 : 1.0);
+            x += note->headBodyWidth() * (up() ? -1.0 : 1.0);
             }
       return x;
       }
@@ -1817,6 +1811,10 @@ void Chord::layoutPitched()
                   }
             computeUp();
             layoutStem();
+            if (_stem) { //false when dragging notes from drum palette
+                  qreal stemWidth5 = _stem->lineWidth() * .5;
+                  _stem->rxpos()   = up() ? (upNote()->headBodyWidth() - stemWidth5) : stemWidth5;
+                  }
             addLedgerLines();
             for (LedgerLine* ll = _ledgerLines; ll; ll = ll->next())
                   ll->layout();
@@ -1872,10 +1870,10 @@ void Chord::layoutPitched()
                               if (sc->notes().size() > 1) {
                                     // some notes may be further to the right than start note
                                     // allow overlap with those notes to count toward the minimum
-                                    qreal snEnd = sn->x() + sn->headWidth();
+                                    qreal snEnd = sn->x() + sn->bboxRightPos();
                                     qreal scEnd = snEnd;
                                     for (Note* n : sc->notes())
-                                          scEnd = qMax(scEnd, n->x() + n->headWidth());
+                                          scEnd = qMax(scEnd, n->x() + n->bboxRightPos());
                                     overlap += scEnd - snEnd;
                                     }
                               }
@@ -2798,9 +2796,9 @@ QPointF Chord::layoutArticulation(Articulation* a)
                   }
             if (!staff()->isTabStaff() && !alignToStem) {
                   if (up())
-                        pos.rx() -= upNote()->headWidth() * .5;   // move half-a-note-head to left
+                        pos.rx() -= upNote()->headBodyWidth() * .5;   // move half-a-note-head to left
                   else
-                        pos.rx() += upNote()->headWidth() * .5;   // move half-a-note-head to right
+                        pos.rx() += upNote()->headBodyWidth() * .5;   // move half-a-note-head to right
                   }
             a->setPos(pos);
             a->adjustReadPos();
index c027a57..69fd7d5 100644 (file)
@@ -117,12 +117,12 @@ void ChordLine::layout()
             QPointF p(note->pos());
             // chordlines to the right of the note
             if (_chordLineType == ChordLineType::FALL || _chordLineType == ChordLineType::DOIT)
-                  setPos(p.x() + note->headWidth() + _spatium * .2, p.y());
+                  setPos(p.x() + note->bboxRightPos() + _spatium * .2, p.y());
             // chordlines to the left of the note
             if (_chordLineType == ChordLineType::PLOP)
-                  setPos(p.x() + note->headWidth() * .25, p.y() - note->headHeight() * .75);
+                  setPos(p.x() + note->bboxRightPos() * .25, p.y() - note->headHeight() * .75);
             if (_chordLineType == ChordLineType::SCOOP) {
-                  qreal x = p.x() + (chord()->up() ? note->headWidth() * .25 : _spatium * -.2);
+                  qreal x = p.x() + (chord()->up() ? note->bboxRightPos() * .25 : _spatium * -.2);
                   setPos(x, p.y() + note->headHeight() * .75);
                   }
             }
index e7c3acf..fd523bd 100644 (file)
@@ -598,6 +598,37 @@ void Clef::undoSetShowCourtesy(bool v)
       }
 
 //---------------------------------------------------------
+//   otherClef
+//    try to locate the 'other clef' of a courtesy / main pair
+//---------------------------------------------------------
+
+Clef* Clef::otherClef()
+      {
+      // if not in a clef-segment-measure hierarchy, do nothing
+      if (!parent() || parent()->type() != Element::Type::SEGMENT)
+            return nullptr;
+      Segment* segm = static_cast<Segment*>(parent());
+      int segmTick = segm->tick();
+      if (!segm->parent() || !segm->parent()->isMeasure())
+            return nullptr;
+      Measure* meas = static_cast<Measure*>(segm->parent());
+      Measure* otherMeas = nullptr;
+      Segment* otherSegm = nullptr;
+      if (segmTick == meas->tick())                         // if clef segm is measure-initial
+            otherMeas = meas->prevMeasure();                // look for a previous measure
+      else if (segmTick == meas->tick() + meas->ticks())    // if clef segm is measure-final
+            otherMeas = meas->nextMeasure();                // look for a next measure
+      if (!otherMeas)
+            return nullptr;
+      // look for a clef segment in the 'other' measure at the same tick of this clef segment
+      otherSegm = otherMeas->findSegment(Segment::Type::Clef, segmTick);
+      if (!otherSegm)
+            return nullptr;
+      // if any 'other' segment found, look for a clef in the same track as this
+      return static_cast<Clef*>(otherSegm->element(track()));
+      }
+
+//---------------------------------------------------------
 //   getProperty
 //---------------------------------------------------------
 
index 00161fa..a301b07 100644 (file)
@@ -167,6 +167,7 @@ class Clef : public Element {
       bool showCourtesy() const        { return _showCourtesy; }
       void setShowCourtesy(bool v)     { _showCourtesy = v; }
       void undoSetShowCourtesy(bool v);
+      Clef* otherClef();
 
       static ClefType clefType(const QString& s);
       const char* clefTypeName();
index ba48309..4275ca2 100644 (file)
@@ -3122,7 +3122,7 @@ void Score::addRemoveBreaks(int interval, bool lock)
 void Score::cmdToggleLayoutBreak(LayoutBreak::Type type)
       {
       // find measure(s)
-      QList<Measure*> ml;
+      QList<MeasureBase*> mbl;
       if (selection().isRange()) {
             Measure* startMeasure = nullptr;
             Measure* endMeasure = nullptr;
@@ -3132,33 +3132,66 @@ void Score::cmdToggleLayoutBreak(LayoutBreak::Type type)
                   return;
 #if 1
             // toggle break on the last measure of the range
-            ml.append(endMeasure);
+            mbl.append(endMeasure);
             // if more than one measure selected,
             // also toggle break *before* the range (to try to fit selection on a single line)
-            if (startMeasure != endMeasure && startMeasure->prevMeasure())
-                  ml.append(startMeasure->prevMeasure());
+            if (startMeasure != endMeasure && startMeasure->prev())
+                  mbl.append(startMeasure->prev());
 #else
             // toggle breaks throughout the selection
             for (Measure* m = startMeasure; m; m = m->nextMeasure()) {
-                  ml.append(m);
+                  mbl.append(m);
                   if (m == endMeasure)
                         break;
                   }
 #endif
             }
       else {
+            MeasureBase* mb = nullptr;
             for (Element* el : selection().elements()) {
-                  Measure* measure = static_cast<Measure*>(el->findMeasure());
-                  if (measure)
-                        ml.append(measure);
+                  switch (el->type()) {
+                        case Element::Type::HBOX:
+                        case Element::Type::VBOX:
+                        case Element::Type::TBOX:
+                              mb = static_cast<MeasureBase*>(el);
+                              break;
+                        default: {
+                              // find measure
+                              Measure* measure = static_cast<Measure*>(el->findMeasure());
+                              // if measure is mmrest, then propagate to last original measure
+                              if (measure)
+                                    mb = measure->isMMRest() ? measure->mmRestLast() : measure;
+                              }
+                        }
                   }
+                  if (mb)
+                        mbl.append(mb);
             }
       // toggle the breaks
-      for (Measure* measure : ml) {
-            // if measure is mm rest, then propagate to last original measure
-            measure = measure->isMMRest() ? measure->mmRestLast() : measure;
-            if (measure)
-                  measure->undoSetBreak(!measure->lineBreak(), type);
+      for (MeasureBase* mb: mbl) {
+            if (mb) {
+                  bool val = false;
+                  switch (type) {
+                        case LayoutBreak::Type::LINE:
+                              val = !mb->lineBreak();
+                              mb->undoSetBreak(val, type);
+                              // remove page break if appropriate
+                              if (val && mb->pageBreak())
+                                    mb->undoSetBreak(false, LayoutBreak::Type::PAGE);
+                              break;
+                        case LayoutBreak::Type::PAGE:
+                              val = !mb->pageBreak();
+                              mb->undoSetBreak(val, type);
+                              // remove line break if appropriate
+                              if (val && mb->lineBreak())
+                                    mb->undoSetBreak(false, LayoutBreak::Type::LINE);
+                              break;
+                        case LayoutBreak::Type::SECTION:
+                              val = mb->sectionBreak() == nullptr;
+                              mb->undoSetBreak(val, type);
+                              break;
+                        }
+                  }
             }
       }
 
index 9e34fc2..f0771e3 100644 (file)
@@ -20,22 +20,58 @@ Drumset* smDrumset;           // standard midi drumset
 Drumset* gpDrumset;           // guitar pro drumset
 
 //---------------------------------------------------------
+//   Drumset
+//---------------------------------------------------------
+
+Drumset::Drumset()
+      {
+      _drum.resize(DRUM_INSTRUMENTS);
+      for (int i = 0; i < DRUM_INSTRUMENTS; ++i) {
+            _drum[i].notehead = NoteHead::Group::HEAD_INVALID;
+            _drum[i].line     = 0;
+            _drum[i].shortcut = 0;
+            _drum[i].voice    = 0;
+            _drum[i].stemDirection = MScore::Direction::UP;
+            _drum[i].pitch = i;
+            }
+      }
+
+std::vector<DrumInstrument> Drumset::drumsByIndex() const
+      {
+      auto d = _drum;
+      std::sort(d.begin(), d.end());
+      return d;
+      }
+
+//---------------------------------------------------------
 //   save
 //---------------------------------------------------------
 
 void Drumset::save(Xml& xml) const
       {
-      for (int i = 0; i < 128; ++i) {
-            if (!isValid(i))
+      auto d = drumsByIndex();
+      for (int n = 0; n < DRUM_INSTRUMENTS; ++n) {
+            const DrumInstrument& di = d[n];
+            if (!isValid(di.pitch))
                   continue;
-            xml.stag(QString("Drum pitch=\"%1\"").arg(i));
-            xml.tag("head", int(noteHead(i)));
-            xml.tag("line", line(i));
-            xml.tag("voice", voice(i));
-            xml.tag("name", name(i));
-            xml.tag("stem", int(stemDirection(i)));
-            if (shortcut(i)) {
-                  switch (shortcut(i)) {
+            xml.stag(QString("Drum pitch=\"%1\"").arg(di.pitch));
+            const NoteHead::Group nh = di.notehead;
+            //write custom as Normal notehead group + noteheads tag to keep compatibility with 2.X versions
+            int saveValue = (nh == NoteHead::Group::HEAD_CUSTOM) ? int(NoteHead::Group::HEAD_NORMAL) : int(nh);
+            xml.tag("head", saveValue);
+            if (nh == NoteHead::Group::HEAD_CUSTOM) {
+                  xml.stag("noteheads");
+                  for (int j = 0; j < int(NoteHead::Type::HEAD_TYPES); j++) {
+                        xml.tag(NoteHead::type2name(NoteHead::Type(j)), Sym::id2name(di.noteheads[int(NoteHead::Type(j))]));
+                        }
+                  xml.etag();
+                  }
+            xml.tag("line", di.line);
+            xml.tag("voice", di.voice);
+            xml.tag("name", di.name);
+            xml.tag("stem", int(di.stemDirection));
+            if (di.shortcut) {
+                  switch (di.shortcut) {
                         case 'C':
                         case 'D':
                         case 'E':
@@ -45,7 +81,7 @@ void Drumset::save(Xml& xml) const
                         case 'B':
                               {
                               char a[2];
-                              a[0] = shortcut(i);
+                              a[0] = di.shortcut;
                               a[1] = 0;
                               xml.tag("shortcut", a);
                               }
@@ -55,6 +91,19 @@ void Drumset::save(Xml& xml) const
                               break;
                         }
                   }
+            auto vs = di.variants;
+            if (!vs.isEmpty()) {
+                  xml.stag("variants");
+                  for (auto v : vs) {
+                        xml.stag(QString("variant pitch=\"%1\"").arg(v.pitch));
+                        if (!v.articulationName.isEmpty())
+                              xml.tag("articulation", v.articulationName);
+                        if (v.tremolo != TremoloType::INVALID_TREMOLO)
+                              xml.tag("tremolo", Tremolo::type2name(v.tremolo));
+                        xml.etag();
+                        }
+                  xml.etag();
+                  }
             xml.etag();
             }
       }
@@ -63,18 +112,31 @@ void Drumset::save(Xml& xml) const
 //   load
 //---------------------------------------------------------
 
-void Drumset::load(XmlReader& e)
+void Drumset::load(XmlReader& e, int index)
       {
       int pitch = e.intAttribute("pitch", -1);
       if (pitch < 0 || pitch > 127) {
             qDebug("load drumset: invalid pitch %d", pitch);
             return;
             }
+      _drum[pitch].index = index;
+      _drum[pitch].pitch = pitch;
       while (e.readNextStartElement()) {
             const QStringRef& tag(e.name());
 
             if (tag == "head")
                   _drum[pitch].notehead = NoteHead::Group(e.readInt());
+            else if (tag == "noteheads") {
+                  _drum[pitch].notehead = NoteHead::Group::HEAD_CUSTOM;
+                  while (e.readNextStartElement()) {
+                        const QStringRef& nhTag(e.name());
+                        int noteType = int(NoteHead::name2type(nhTag.toString()));
+                        if (noteType > int(NoteHead::Type::HEAD_TYPES) - 1 || noteType < 0)
+                              return;
+
+                        _drum[pitch].noteheads[noteType] = Sym::name2id(e.readElementText());
+                        }
+                  }
             else if (tag == "line")
                   _drum[pitch].line = e.readInt();
             else if (tag == "voice")
@@ -89,6 +151,25 @@ void Drumset::load(XmlReader& e)
                   int i = val.toInt(&isNum);
                   _drum[pitch].shortcut = isNum ? i : toupper(val[0].toLatin1());
                   }
+            else if (tag == "variants") {
+                  while(e.readNextStartElement()) {
+                        const QStringRef& tagv(e.name());
+                        if (tagv == "variant") {
+                              DrumInstrumentVariant div;
+                              div.pitch = e.attribute("pitch").toInt();
+                              while (e.readNextStartElement()) {
+                                    const QStringRef& taga(e.name());
+                                    if (taga == "articulation") {
+                                          div.articulationName = e.readElementText();
+                                          }
+                                    else if (taga == "tremolo") {
+                                          div.tremolo = Tremolo::name2Type(e.readElementText());
+                                          }
+                                    }
+                              _drum[pitch].addVariant(div);
+                              }
+                        }
+                  }
             else
                   e.unknown();
             }
@@ -104,6 +185,7 @@ void Drumset::clear()
             _drum[i].name = "";
             _drum[i].notehead = NoteHead::Group::HEAD_INVALID;
             _drum[i].shortcut = 0;
+            _drum[i].variants.clear();
             }
       }
 
@@ -142,6 +224,31 @@ int Drumset::prevPitch(int ii) const
       }
 
 //---------------------------------------------------------
+//   findVariant
+/// find a variant for the given pitch with matching chord articulation and tremolo
+//---------------------------------------------------------
+
+DrumInstrumentVariant Drumset::findVariant(int p, const QList<Articulation*> articulations, Tremolo* tremolo) const
+      {
+      DrumInstrumentVariant div;
+      auto vs = variants(p);
+      for (auto v : vs) {
+            bool matchTremolo = (!tremolo && v.tremolo == TremoloType::INVALID_TREMOLO) || (tremolo && v.tremolo == tremolo->tremoloType());
+            bool matchArticulation = v.articulationName.isEmpty() && articulations.isEmpty();
+            for (auto a : articulations) {
+                  matchArticulation = a->subtypeName() == v.articulationName;
+                  if (!matchArticulation)
+                        break;
+                  }
+            if (matchArticulation && matchTremolo) {
+                  div = v;
+                  break;
+                  }
+            }
+      return div;
+      }
+
+//---------------------------------------------------------
 //   initDrumset
 //    initialize standard midi drumset
 //---------------------------------------------------------
@@ -149,38 +256,31 @@ int Drumset::prevPitch(int ii) const
 void initDrumset()
       {
       smDrumset = new Drumset;
-      for (int i = 0; i < 128; ++i) {
-            smDrumset->drum(i).notehead = NoteHead::Group::HEAD_INVALID;
-            smDrumset->drum(i).line     = 0;
-            smDrumset->drum(i).shortcut = 0;
-            smDrumset->drum(i).voice    = 0;
-            smDrumset->drum(i).stemDirection = MScore::Direction::UP;
-            }
-      smDrumset->drum(35) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Acoustic Bass Drum"), NoteHead::Group::HEAD_NORMAL,   7, MScore::Direction::DOWN, 1);
-      smDrumset->drum(36) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Bass Drum 1"),        NoteHead::Group::HEAD_NORMAL,   7, MScore::Direction::DOWN, 1, Qt::Key_B);
-      smDrumset->drum(37) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Side Stick"),         NoteHead::Group::HEAD_CROSS,    3, MScore::Direction::UP);
-      smDrumset->drum(38) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Acoustic Snare"),     NoteHead::Group::HEAD_NORMAL,   3, MScore::Direction::UP, 0, Qt::Key_A);
-      smDrumset->drum(40) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Electric Snare"),     NoteHead::Group::HEAD_NORMAL,   3, MScore::Direction::UP);
-      smDrumset->drum(41) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Floor Tom"),      NoteHead::Group::HEAD_NORMAL,   5, MScore::Direction::UP);
-      smDrumset->drum(42) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Closed Hi-Hat"),      NoteHead::Group::HEAD_CROSS,   -1, MScore::Direction::UP, 0, Qt::Key_G);
-      smDrumset->drum(43) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Floor Tom"),     NoteHead::Group::HEAD_NORMAL,   5, MScore::Direction::DOWN, 1);
-      smDrumset->drum(44) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Pedal Hi-Hat"),       NoteHead::Group::HEAD_CROSS,    9, MScore::Direction::DOWN, 1, Qt::Key_F);
-      smDrumset->drum(45) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Tom"),            NoteHead::Group::HEAD_NORMAL,   2, MScore::Direction::UP);
-      smDrumset->drum(46) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Hi-Hat"),        NoteHead::Group::HEAD_CROSS,    1, MScore::Direction::UP);
-      smDrumset->drum(47) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low-Mid Tom"),        NoteHead::Group::HEAD_NORMAL,   1, MScore::Direction::UP);
-      smDrumset->drum(48) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Hi-Mid Tom"),         NoteHead::Group::HEAD_NORMAL,   0, MScore::Direction::UP);
-      smDrumset->drum(49) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Crash Cymbal 1"),     NoteHead::Group::HEAD_CROSS,   -2, MScore::Direction::UP, 0, Qt::Key_C);
-      smDrumset->drum(50) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Tom"),           NoteHead::Group::HEAD_NORMAL,   0, MScore::Direction::UP, 0, Qt::Key_E);
-      smDrumset->drum(51) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Cymbal 1"),      NoteHead::Group::HEAD_CROSS,    0, MScore::Direction::UP, 0, Qt::Key_D);
-      smDrumset->drum(52) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Chinese Cymbal"),     NoteHead::Group::HEAD_CROSS,   -3, MScore::Direction::UP);
-      smDrumset->drum(53) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Bell"),          NoteHead::Group::HEAD_DIAMOND,  0, MScore::Direction::UP);
-      smDrumset->drum(54) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Tambourine"),         NoteHead::Group::HEAD_DIAMOND,  2, MScore::Direction::UP);
-      smDrumset->drum(55) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Splash Cymbal"),      NoteHead::Group::HEAD_CROSS,   -3, MScore::Direction::UP);
-      smDrumset->drum(56) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Cowbell"),            NoteHead::Group::HEAD_TRIANGLE, 1, MScore::Direction::UP);
-      smDrumset->drum(57) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Crash Cymbal 2"),     NoteHead::Group::HEAD_CROSS,   -3, MScore::Direction::UP);
-      smDrumset->drum(59) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Cymbal 2"),      NoteHead::Group::HEAD_CROSS,    2, MScore::Direction::UP);
-      smDrumset->drum(63) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Hi Conga"),    NoteHead::Group::HEAD_CROSS,    4, MScore::Direction::UP);
-      smDrumset->drum(64) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Conga"),          NoteHead::Group::HEAD_CROSS,    6, MScore::Direction::UP);
+      smDrumset->addDrumInstrument(35, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Acoustic Bass Drum"), NoteHead::Group::HEAD_NORMAL,   7, MScore::Direction::DOWN, 1));
+      smDrumset->addDrumInstrument(36, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Bass Drum 1"),        NoteHead::Group::HEAD_NORMAL,   7, MScore::Direction::DOWN, 1, Qt::Key_B));
+      smDrumset->addDrumInstrument(37, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Side Stick"),         NoteHead::Group::HEAD_CROSS,    3, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(38, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Acoustic Snare"),     NoteHead::Group::HEAD_NORMAL,   3, MScore::Direction::UP, 0, Qt::Key_A));
+      smDrumset->addDrumInstrument(40, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Electric Snare"),     NoteHead::Group::HEAD_NORMAL,   3, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(41, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Floor Tom"),      NoteHead::Group::HEAD_NORMAL,   5, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(42, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Closed Hi-Hat"),      NoteHead::Group::HEAD_CROSS,   -1, MScore::Direction::UP, 0, Qt::Key_G));
+      smDrumset->addDrumInstrument(43, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Floor Tom"),     NoteHead::Group::HEAD_NORMAL,   5, MScore::Direction::DOWN, 1));
+      smDrumset->addDrumInstrument(44, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Pedal Hi-Hat"),       NoteHead::Group::HEAD_CROSS,    9, MScore::Direction::DOWN, 1, Qt::Key_F));
+      smDrumset->addDrumInstrument(45, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Tom"),            NoteHead::Group::HEAD_NORMAL,   2, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(46, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Hi-Hat"),        NoteHead::Group::HEAD_CROSS,    1, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(47, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low-Mid Tom"),        NoteHead::Group::HEAD_NORMAL,   1, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(48, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Hi-Mid Tom"),         NoteHead::Group::HEAD_NORMAL,   0, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(49, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Crash Cymbal 1"),     NoteHead::Group::HEAD_CROSS,   -2, MScore::Direction::UP, 0, Qt::Key_C));
+      smDrumset->addDrumInstrument(50, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Tom"),           NoteHead::Group::HEAD_NORMAL,   0, MScore::Direction::UP, 0, Qt::Key_E));
+      smDrumset->addDrumInstrument(51, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Cymbal 1"),      NoteHead::Group::HEAD_CROSS,    0, MScore::Direction::UP, 0, Qt::Key_D));
+      smDrumset->addDrumInstrument(52, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Chinese Cymbal"),     NoteHead::Group::HEAD_CROSS,   -3, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(53, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Bell"),          NoteHead::Group::HEAD_DIAMOND,  0, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(54, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Tambourine"),         NoteHead::Group::HEAD_DIAMOND,  2, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(55, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Splash Cymbal"),      NoteHead::Group::HEAD_CROSS,   -3, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(56, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Cowbell"),            NoteHead::Group::HEAD_TRIANGLE, 1, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(57, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Crash Cymbal 2"),     NoteHead::Group::HEAD_CROSS,   -3, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(59, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Cymbal 2"),      NoteHead::Group::HEAD_CROSS,    2, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(63, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Hi Conga"),    NoteHead::Group::HEAD_CROSS,    4, MScore::Direction::UP));
+      smDrumset->addDrumInstrument(64, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Conga"),          NoteHead::Group::HEAD_CROSS,    6, MScore::Direction::UP));
       }
 
 }
index 994f6ee..51233f6 100644 (file)
 #define __DRUMSET_H__
 
 #include "mscore.h"
+#include "tremolo.h"
 #include "note.h"
+#include "articulation.h"
+#include "sym.h"
 
 namespace Ms {
 
 class Xml;
 
+struct DrumInstrumentVariant {
+      int pitch;
+      QString articulationName;
+      TremoloType tremolo;
+      DrumInstrumentVariant() {
+            pitch = INVALID_PITCH;
+            tremolo = TremoloType::INVALID_TREMOLO;
+      }
+};
+
 //---------------------------------------------------------
 //   DrumInstrument
 //---------------------------------------------------------
 
 struct DrumInstrument {
       QString name;
+      int index = 0;
+      int pitch = -1;
+      
+      // if @notehead == HEAD_CUSTOM, use @noteheads to extract custom noteheads
       NoteHead::Group notehead; ///< notehead symbol set
+      SymId noteheads[int(NoteHead::Type::HEAD_TYPES)] = { SymId::noteheadWhole, SymId::noteheadHalf, SymId::noteheadBlack, SymId::noteheadDoubleWhole  };
+
       int line;                 ///< place notehead onto this line
       MScore::Direction stemDirection;
       int voice;
       char shortcut;            ///< accelerator key (CDEFGAB)
+      QList<DrumInstrumentVariant> variants;
 
       DrumInstrument() {}
       DrumInstrument(const char* s, NoteHead::Group nh, int l, MScore::Direction d,
          int v = 0, char sc = 0)
          : name(s), notehead(nh), line(l), stemDirection(d), voice(v), shortcut(sc) {}
+      void addVariant(DrumInstrumentVariant v) { variants.append(v); }
+      bool operator<(const DrumInstrument& other) const { return (index != other.index ? index < other.index : pitch < other.pitch); };
       };
 
 static const int DRUM_INSTRUMENTS = 128;
@@ -47,24 +69,31 @@ static const int DRUM_INSTRUMENTS = 128;
 //---------------------------------------------------------
 
 class Drumset {
-      DrumInstrument _drum[DRUM_INSTRUMENTS];
-
+      std::vector<DrumInstrument> _drum;
+      
    public:
-      bool isValid(int pitch) const             { return _drum[pitch].notehead != NoteHead::Group::HEAD_INVALID; }
+      Drumset();
+      bool isValid(int pitch) const             { return pitch > -1 && pitch < DRUM_INSTRUMENTS && !_drum[pitch].name.isEmpty(); }
       NoteHead::Group noteHead(int pitch) const { return _drum[pitch].notehead;       }
+      SymId noteHeads(int pitch, NoteHead::Type t) const  { return _drum[pitch].noteheads[int(t)];      }
+      
       int line(int pitch) const                 { return _drum[pitch].line;           }
       int voice(int pitch) const                { return _drum[pitch].voice;          }
       MScore::Direction stemDirection(int pitch) const  { return _drum[pitch].stemDirection;  }
       const QString& name(int pitch) const      { return _drum[pitch].name;           }
       int shortcut(int pitch) const             { return _drum[pitch].shortcut;       }
+      QList<DrumInstrumentVariant> variants(int pitch) const   { return _drum[pitch].variants; }
 
+      std::vector<DrumInstrument> drumsByIndex() const;
       void save(Xml&) const;
-      void load(XmlReader&);
+      void load(XmlReader&, int index);
       void clear();
       int nextPitch(int) const;
       int prevPitch(int) const;
       DrumInstrument& drum(int i) { return _drum[i]; }
       const DrumInstrument& drum(int i) const { return _drum[i]; }
+      void addDrumInstrument(int pitch, DrumInstrument d) { d.pitch = pitch; _drum[pitch] = d; };
+      DrumInstrumentVariant findVariant(int pitch, const QList<Articulation*> articulations, Tremolo* tremolo) const;
       };
 
 extern Drumset* smDrumset;
index d0919be..664fcdd 100644 (file)
@@ -817,7 +817,7 @@ bool TDuration::isValid(Fraction f)
      t.setType(DurationType::V_LONG);
      t.setDots(MAX_DOTS);
      t.truncateToFraction(f, 4);
-     return ((t.fraction() - f).numerator() == 0);
+     return (t.isValid() && (t.fraction() - f).numerator() == 0);
      }
 }
 
index 62e7fc7..3ab16f7 100644 (file)
@@ -1593,7 +1593,7 @@ void Score::regroupNotesAndRests(int startTick, int endTick, int track)
                                           QList<Note*> nl1 = nchord->notes();
                                           QList<Note*> nl2 = nchord2->notes();
                                           if (!firstpart)
-                                                for (unsigned j = 0; j < nl1.size(); ++j) {
+                                                for (int j = 0; j < nl1.size(); ++j) {
                                                       tie = new Tie(this);
                                                       tie->setStartNote(nl1[j]);
                                                       tie->setEndNote(nl2[j]);
index b4f6815..2f7cb25 100644 (file)
@@ -53,6 +53,19 @@ static InstrumentGroup* searchInstrumentGroup(const QString& name)
       }
 
 //---------------------------------------------------------
+//   searchArticulation
+//---------------------------------------------------------
+
+static MidiArticulation searchArticulation(const QString& name)
+      {
+      foreach(MidiArticulation a, articulation) {
+            if (a.name == name)
+                  return a;
+            }
+      return MidiArticulation();
+      }
+
+//---------------------------------------------------------
 //   readStaffIdx
 //---------------------------------------------------------
 
@@ -67,7 +80,7 @@ static int readStaffIdx(XmlReader& e)
       }
 
 //---------------------------------------------------------
-//   readInstrumentGroup
+//   read InstrumentGroup
 //---------------------------------------------------------
 
 void InstrumentGroup::read(XmlReader& e)
@@ -109,6 +122,16 @@ void InstrumentGroup::read(XmlReader& e)
       }
 
 //---------------------------------------------------------
+//   clear InstrumentGroup
+//---------------------------------------------------------
+
+void InstrumentGroup::clear()
+      {
+      qDeleteAll(instrumentTemplates);
+      instrumentTemplates.clear();
+      }
+
+//---------------------------------------------------------
 //   InstrumentTemplate
 //---------------------------------------------------------
 
@@ -309,7 +332,7 @@ void InstrumentTemplate::write1(Xml& xml) const
 void InstrumentTemplate::read(XmlReader& e)
       {
       id = e.attribute("id");
-
+      int drumsetIndex = 0;
       while (e.readNextStartElement()) {
             const QStringRef& tag(e.name());
 
@@ -409,7 +432,8 @@ void InstrumentTemplate::read(XmlReader& e)
                         drumset = new Drumset(*smDrumset);
                         drumset->clear();
                         }
-                  drumset->load(e);
+                  drumset->load(e, drumsetIndex);
+                  drumsetIndex++;
                   }
             else if (tag == "MidiAction") {
                   NamedEventList a;
@@ -502,7 +526,7 @@ void InstrumentTemplate::read(XmlReader& e)
             channel.append(a);
             }
       if (useDrumset) {
-            if (channel[0].bank == 0)
+            if (channel[0].bank == 0 && channel[0].name != "Zerberus")
                   channel[0].bank = 128;
             channel[0].updateInitList();
             }
@@ -603,6 +627,21 @@ bool saveInstrumentTemplates1(const QString& instrTemplates)
       }
 
 //---------------------------------------------------------
+//   clearInstrumentTemplates
+//---------------------------------------------------------
+
+void clearInstrumentTemplates()
+      {
+      for (InstrumentGroup* g : instrumentGroups)
+            g->clear();
+      qDeleteAll(instrumentGroups);
+      instrumentGroups.clear();
+      qDeleteAll(instrumentGenres);
+      instrumentGenres.clear();
+      articulation.clear();
+      }
+
+//---------------------------------------------------------
 //   loadInstrumentTemplates
 //---------------------------------------------------------
 
@@ -620,8 +659,8 @@ bool loadInstrumentTemplates(const QString& instrTemplates)
                   while (e.readNextStartElement()) {
                         const QStringRef& tag(e.name());
                         if (tag == "instrument-group" || tag == "InstrumentGroup") {
-                              QString id(e.attribute("id"));
-                              InstrumentGroup* group = searchInstrumentGroup(id);
+                              QString idGroup(e.attribute("id"));
+                              InstrumentGroup* group = searchInstrumentGroup(idGroup);
                               if (group == 0) {
                                     group = new InstrumentGroup;
                                     instrumentGroups.append(group);
@@ -630,13 +669,14 @@ bool loadInstrumentTemplates(const QString& instrTemplates)
                               }
                         else if (tag == "Articulation") {
                               // read global articulation
-                              MidiArticulation a;
+                              QString name(e.attribute("name"));
+                              MidiArticulation a = searchArticulation(name);
                               a.read(e);
                               articulation.append(a);
                               }
                         else if (tag == "Genre") {
-                              QString id(e.attribute("id"));
-                              InstrumentGenre* genre = searchInstrumentGenre(id);
+                              QString idGenre(e.attribute("id"));
+                              InstrumentGenre* genre = searchInstrumentGenre(idGenre);
                               if (!genre) {
                                     genre = new InstrumentGenre;
                                     instrumentGenres.append(genre);
index 52348e0..fe29f8a 100644 (file)
@@ -111,6 +111,7 @@ struct InstrumentGroup {
       bool extended;          // belongs to extended instruments set if true
       QList<InstrumentTemplate*> instrumentTemplates;
       void read(XmlReader&);
+      void clear();
 
       InstrumentGroup() { extended = false; }
       };
@@ -118,6 +119,7 @@ struct InstrumentGroup {
 extern QList<InstrumentGenre *> instrumentGenres;
 extern QList<MidiArticulation> articulation;
 extern QList<InstrumentGroup*> instrumentGroups;
+extern void clearInstrumentTemplates();
 extern bool loadInstrumentTemplates(const QString& instrTemplates);
 extern bool saveInstrumentTemplates(const QString& instrTemplates);
 extern InstrumentTemplate* searchTemplate(const QString& name);
index a3e4979..49af2fd 100644 (file)
@@ -259,6 +259,7 @@ void Instrument::read(XmlReader& e)
       int volume  = 100;
       int pan     = 60;
       bool customDrumset = false;
+      int drumsetIndex = 0;
 
       _channel.clear();       // remove default channel
       while (e.readNextStartElement()) {
@@ -313,10 +314,11 @@ void Instrument::read(XmlReader& e)
                   if (!_drumset)
                         _drumset = new Drumset(*smDrumset);
                   if (!customDrumset) {
-                        const_cast<Drumset*>(_drumset)->clear();
+                        _drumset->clear();
                         customDrumset = true;
                         }
-                  const_cast<Drumset*>(_drumset)->load(e);
+                  _drumset->load(e, drumsetIndex);
+                  drumsetIndex++;
                   }
             // support tag "Tablature" for a while for compatibility with existent 2.0 scores
             else if (tag == "Tablature" || tag == "StringData")
@@ -380,7 +382,7 @@ void Instrument::read(XmlReader& e)
             _channel.append(a);
             }
       if (_useDrumset) {
-            if (_channel[0]->bank == 0)
+            if (_channel[0]->bank == 0 && _channel[0]->name != "Zerberus")
                   _channel[0]->bank = 128;
             _channel[0]->updateInitList();
             }
@@ -550,6 +552,9 @@ void Channel::read(XmlReader& e)
             else
                   e.unknown();
             }
+      if (128 == bank && "Zerberus" == synti)
+            bank = 0;
+
       updateInitList();
       }
 
index 75c5a9b..345e662 100644 (file)
@@ -105,8 +105,18 @@ void KeySig::layout()
 
       // determine current clef for this staff
       ClefType clef = ClefType::G;
-      if (staff())
-            clef = staff()->clef(segment()->tick());
+      if (staff()) {
+            // Look for a clef before the key signature at the same tick
+            Clef* c = nullptr;
+            for (Segment* seg = segment()->prev1(); !c && seg && seg->tick() == segment()->tick(); seg = seg->prev1())
+                  if (seg->segmentType() == Segment::Type::Clef)
+                        c = static_cast<Clef*>(seg->element(track()));
+            if (c)
+                  clef = c->clefType();
+            else
+                  // no clef found, so get the clef type from the clefs list, using the previous tick
+                  clef = staff()->clef(segment()->tick() - 1);
+            }
 
       int accidentals = 0, naturals = 0;
       int t1 = int(_sig.key());
@@ -130,7 +140,7 @@ void KeySig::layout()
       // OR key sig is CMaj/Amin (in which case they are always shown)
 
       Measure* m           = measure();
-      Measure* prevMeasure = m ? m->prevMeasure() : nullptr;
+      Measure* prevMeasure = m ? m->prevMeasureMM() : nullptr;
 
       // display of naturals defaults according to style
       bool naturalsOn = score()->styleI(StyleIdx::keySigNaturals) != int(KeySigNatural::NONE) || t1 == 0;
index 8c9d3e6..e59b5e7 100644 (file)
@@ -609,7 +609,7 @@ qreal Score::layoutChords2(QList<Note*>& notes, bool up)
 
             // accumulate return value
             if (!mirror)
-                  maxWidth = qMax(maxWidth, note->headWidth());
+                  maxWidth = qMax(maxWidth, note->bboxRightPos());
 
             // prepare for next iteration
             lvisible      = note->visible();
@@ -827,16 +827,13 @@ void Score::layoutChords3(QList<Note*>& notes, Staff* staff, Segment* segment)
                   ++nAcc;
                   }
 
-            qreal hw     = note->headWidth();   // actual head width, including note & chord mag
             Chord* chord = note->chord();
             bool _up     = chord->up();
-            qreal stemX  = chord->stemPosX();   // stem position for nominal notehead, but allowing for mag
-
             qreal overlapMirror;
             if (chord->stem()) {
                   qreal stemWidth = chord->stem()->lineWidth();
                   qreal stemWidth5 = stemWidth * 0.5;
-                  chord->stem()->rxpos() = _up ? stemX - stemWidth5 : stemWidth5;
+                  chord->stem()->rxpos() = _up ? chord->stemPosX() - stemWidth5 : stemWidth5;
                   overlapMirror = stemWidth;
                   }
             else if (chord->durationType().headType() == NoteHead::Type::HEAD_WHOLE)
@@ -844,19 +841,15 @@ void Score::layoutChords3(QList<Note*>& notes, Staff* staff, Segment* segment)
             else
                   overlapMirror = 0.0;
 
-            qreal x;
+            qreal x = 0.0;
             if (note->mirror()) {
                   if (_up)
-                        x = stemX - overlapMirror;
-                  else
-                        x = stemX - hw + overlapMirror;
-                  }
-            else {
-                  if (_up)
-                        x = stemX - hw;
+                        x = chord->stemPosX() - overlapMirror;
                   else
-                        x = 0.0;
+                        x = -note->headBodyWidth() + overlapMirror;
                   }
+            else if (_up)
+                  x = chord->stemPosX() - note->headBodyWidth();
 
             note->rypos()  = (note->line() + stepOffset) * stepDistance;
             note->rxpos()  = x;
@@ -877,13 +870,16 @@ void Score::layoutChords3(QList<Note*>& notes, Staff* staff, Segment* segment)
             //if (chord->stem())
             //      chord->stem()->rxpos() = _up ? x + hw - stemWidth5 : x + stemWidth5;
 
-            qreal xx = x + hw + chord->pos().x();
+            qreal xx = x + chord->stemPosX() + chord->pos().x();
 
             if (chord->dots()) {
                   if (chord->up())
                         upDotPosX = qMax(upDotPosX, xx);
-                  else
-                        downDotPosX = qMax(downDotPosX, xx);
+                  else {
+                        qreal noteheadShift = note->headBodyWidth();
+                        downDotPosX = qMax(downDotPosX, xx + noteheadShift);
+                        }
+
                   MScore::Direction dotPosition = note->userDotPosition();
 
                   if (dotPosition == MScore::Direction::AUTO && nNotes > 1 && note->visible() && !note->dotsHidden()) {
@@ -930,6 +926,11 @@ void Score::layoutChords3(QList<Note*>& notes, Staff* staff, Segment* segment)
                   }
             }
 
+      // if there are no non-mirrored notes in a downstem chord,
+      // then use the stem X position as X origin for accidental layout
+      if (nNotes && leftNotes.size() == nNotes)
+            lx = notes.front()->chord()->stemPosX();
+
       if (segment) {
             // align all dots for segment/staff
             // it would be possible to dots for up & down chords separately
@@ -2166,6 +2167,7 @@ qreal Score::cautionaryWidth(Measure* m, bool& hasCourtesy)
             for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) {
                   int track = staffIdx * VOICES;
 
+                  // the real key signature in the next measure, if present
                   KeySig* nks = static_cast<KeySig*>(ns->element(track));
 
                   if (nks && nks->showCourtesy() && !nks->generated()) {
@@ -2184,14 +2186,23 @@ qreal Score::cautionaryWidth(Measure* m, bool& hasCourtesy)
                                      }
                                }
 
+                        // the courtesy key signature in this measure, if present
                         Segment* s  = m->findSegment(Segment::Type::KeySigAnnounce, tick);
 
                         if (s && s->element(track)) {
+                              // use width of existing courtesy key signature
                               wwMax = qMax(wwMax, s->element(track)->width() + leftMargin);
                               hasCourtesy = true;
                               }
                         else {
+                              // no courtesy key sig present
+                              // use width of real key signature in next measure
+                              // but make sure naturals are generated for us if appropriate
+                              // just as if this were a courtesy key signature
+                              bool saveCourtesy = nks->showCourtesy();
+                              nks->setShowCourtesy(false);
                               nks->layout();
+                              nks->setShowCourtesy(saveCourtesy);
                               wwMax = qMax(wwMax, nks->width() + leftMargin);
                               //hasCourtesy = false;
                               }
index 5d2f0a2..3fa8f5a 100644 (file)
@@ -614,7 +614,7 @@ QPointF SLine::linePos(Grip grip, System** sys) const
                                           // chord bbox() is unreliable, look at notes
                                           // this also allows us to more easily ignore ledger lines
                                           for (Note* n : static_cast<Chord*>(cr)->notes())
-                                                maxRight = qMax(maxRight, cr->x() + n->x() + n->headWidth());
+                                                maxRight = qMax(maxRight, cr->x() + n->x() + n->bboxRightPos());
                                           }
                                     else {
                                           // rest - won't normally happen
index 1bb90f3..70fc830 100644 (file)
@@ -578,7 +578,8 @@ void Measure::layout2()
                         smn = system()->firstMeasure() == this;
                   else {
                         smn = (_no == 0 && score()->styleB(StyleIdx::showMeasureNumberOne)) ||
-                              ( ((_no+1) % score()->style(StyleIdx::measureNumberInterval).toInt()) == 0 );
+                              ( ((_no + 1) % score()->style(StyleIdx::measureNumberInterval).toInt()) == (score()->styleB(StyleIdx::showMeasureNumberOne) ? 1 : 0) ) ||
+                              (score()->style(StyleIdx::measureNumberInterval).toInt() == 1);
                         }
                   }
             }
@@ -975,11 +976,25 @@ void Measure::spatiumChanged(qreal /*oldValue*/, qreal /*newValue*/)
 
 void Measure::moveTicks(int diff)
       {
+      std::set<Tuplet*> tuplets;
       setTick(tick() + diff);
       for (Segment* segment = first(); segment; segment = segment->next()) {
             if (segment->segmentType() & (Segment::Type::EndBarLine | Segment::Type::TimeSigAnnounce))
                   segment->setTick(tick() + ticks());
+            else if (segment->isChordRest())
+                  // Tuplet ticks are stored as absolute ticks, so they must be adjusted.
+                  // But each tuplet must only be adjusted once.
+                  for (Element* e : segment->elist())
+                        if (e && e->isChordRest()) {
+                              ChordRest* cr = static_cast<ChordRest*>(e);
+                              Tuplet* tuplet = cr->tuplet();
+                              if (tuplet && tuplets.count(tuplet) == 0) {
+                                    tuplet->setTick(tuplet->tick() + diff);
+                                    tuplets.insert(tuplet);
+                                    }
+                              }
             }
+      tuplets.clear();
       }
 
 //---------------------------------------------------------
@@ -1862,7 +1877,7 @@ void Measure::read(XmlReader& e, int staffIdx)
                               // be reconstructed from measure flags
                               bool endBarLineGenerated = (blt == BarLineType::NORMAL || blt == BarLineType::END_REPEAT
                                     || blt == BarLineType::END_START_REPEAT || blt == BarLineType::START_REPEAT);
-                              setEndBarLineType(blt, endBarLineGenerated, true);
+                              setEndBarLineType(blt, barLine->el()->empty() && endBarLineGenerated, true);
                               }
                         if (!barLine->customSpan()) {
                               Staff* staff = score()->staff(staffIdx);
@@ -3115,7 +3130,7 @@ qreal Measure::minWidth1() const
                   }
             _minWidth1 = score()->computeMinWidth(s, false);
             if (MScore::debugMode)
-                  qDebug("Measure::minWidth1: %d %f", no(), _minWidth1 / DPMM);
+                  qDebug("Measure::minWidth1: %d %f", no(), _minWidth1);
             }
       return _minWidth1;
       }
index 4e65517..d344a88 100644 (file)
@@ -63,7 +63,8 @@ namespace Ms {
 //    notehead groups
 //---------------------------------------------------------
 
-static const SymId noteHeads[2][int(NoteHead::Group::HEAD_GROUPS)][int(NoteHead::Type::HEAD_TYPES)] = {
+//int(NoteHead::Group::HEAD_GROUPS) - 1: "-1" is needed to prevent building CUSTOM_GROUP noteheads set, since it is built by users and keep a specific set of existing noteheads
+static const SymId noteHeads[2][int(NoteHead::Group::HEAD_GROUPS) - 1][int(NoteHead::Type::HEAD_TYPES)] = {
    // previous non-SMUFL data kept in comments for future reference
    {     // down stem
       { SymId::noteheadWhole,       SymId::noteheadHalf,          SymId::noteheadBlack,     SymId::noteheadDoubleWhole  },
@@ -137,6 +138,14 @@ static const char* noteHeadNames[] = {
     QT_TRANSLATE_NOOP("noteheadnames", "Alt. Brevis")
 };
 
+// same order as NoteHead::Type, partially extracted from master code to support custom noteheads in drumset file
+static const char* noteHeadTypeNames[] = {
+      "auto",
+      "whole",
+      "half",
+      "quarter",
+      "breve"
+      };
 //---------------------------------------------------------
 //   noteHead
 //---------------------------------------------------------
@@ -450,6 +459,16 @@ SymId Note::noteHead() const
       if (_headType != NoteHead::Type::HEAD_AUTO)
             ht = _headType;
 
+      if (_headGroup == NoteHead::Group::HEAD_CUSTOM) {
+            if (chord() && chord()->staff()) {
+                  if (chord()->staff()->isDrumStaff())
+                        return chord()->staff()->part()->instrument(chord()->tick())->drumset()->noteHeads(_pitch, ht);
+                  }
+            else {
+                  return _cachedNoteheadSym;
+                  }
+            }
+            
       SymId t = noteHead(up, _headGroup, ht);
       if (t == SymId::noSym) {
             qDebug("invalid notehead %d/%d", int(_headGroup), int(ht));
@@ -459,9 +478,32 @@ SymId Note::noteHead() const
       }
 
 //---------------------------------------------------------
+//   bboxRightPos
+//
+//    returns the x of the symbol bbox. It is different from headWidth() because zero point could be different from leftmost bbox position.
+//---------------------------------------------------------
+
+qreal Note::bboxRightPos() const
+      {
+      const auto& bbox = score()->scoreFont()->bbox(noteHead(), magS());
+      return bbox.right();
+      }
+
+//---------------------------------------------------------
+//   headBodyWidth
+//
+//    returns the width of the notehead "body". It is actual for slashed noteheads like -O-, where O is body.
+//---------------------------------------------------------
+
+qreal Note::headBodyWidth() const
+      {
+      return headWidth() + 2 * bboxXShift();
+      }
+
+//---------------------------------------------------------
 //   headWidth
 //
-//    returns the width of the notehead symbol
+//    returns the width of the symbol bbox
 //    or the width of the string representation of the fret mark
 //---------------------------------------------------------
 
@@ -471,6 +513,27 @@ qreal Note::headWidth() const
       }
 
 //---------------------------------------------------------
+//   bboxXShift
+//
+//    returns the x shift of the notehead bounding box
+//---------------------------------------------------------
+qreal Note::bboxXShift() const
+      {
+      const auto& bbox = score()->scoreFont()->bbox(noteHead(), magS());
+      return bbox.bottomLeft().x();
+      }
+
+//---------------------------------------------------------
+//   noteheadCenterX
+//
+//    returns the x coordinate of the notehead center related to the basepoint of the notehead bbox
+//---------------------------------------------------------
+qreal Note::noteheadCenterX() const
+      {
+      return score()->scoreFont()->width(noteHead(), magS()) / 2 + bboxXShift();
+      }
+
+//---------------------------------------------------------
 //   tabHeadWidth
 //---------------------------------------------------------
 
@@ -755,7 +818,7 @@ void Note::draw(QPainter* painter) const
             // by coloring the notehead
             //
             if (chord() && chord()->segment() && staff() && !selected()
-               && !score()->printing() && MScore::warnPitchRange) {
+               && !score()->printing() && MScore::warnPitchRange && !staff()->isDrumStaff()) {
                   const Instrument* in = part()->instrument(chord()->tick());
                   int i = ppitch();
                   if (i < in->minPitchP() || i > in->maxPitchP())
@@ -2118,7 +2181,19 @@ void Note::setHeadGroup(NoteHead::Group val)
 
 int Note::ppitch() const
       {
-      return _pitch + staff()->pitchOffset(chord()->segment()->tick());
+      Chord* ch = chord();
+      // if staff is drum
+      // match tremolo and articulation between variants and chord
+      if (play() && ch && ch->staff() && ch->staff()->isDrumStaff()) {
+            const Drumset* ds = ch->staff()->part()->instrument(ch->tick())->drumset();
+            if (ds) {
+                  DrumInstrumentVariant div = ds->findVariant(_pitch, ch->articulations(), ch->tremolo());
+                  if (div.pitch != INVALID_PITCH)
+                        return div.pitch;
+                  }
+            }
+      return _pitch + staff()->pitchOffset(ch->segment()->tick());
+
       }
 
 //---------------------------------------------------------
@@ -2679,6 +2754,26 @@ const char* NoteHead::groupToGroupName(NoteHead::Group group)
       }
 
 //---------------------------------------------------------
+//   type2name
+//---------------------------------------------------------
+const char* NoteHead::type2name(Type type)
+      {
+            return noteHeadTypeNames[int(type) + 1];
+      }
+      
+//---------------------------------------------------------
+//   name2type
+//---------------------------------------------------------
+
+NoteHead::Type NoteHead::name2type(const QString& s)
+      {
+      for (int i = 0; i <= int(NoteHead::Type::HEAD_TYPES); ++i) {
+            if (s.compare(QString(noteHeadTypeNames[i])) == 0)
+                  return NoteHead::Type(i - 1);
+      }
+      return NoteHead::Type::HEAD_AUTO;
+      }
+//---------------------------------------------------------
 //   subtypeName
 //---------------------------------------------------------
 
@@ -2781,6 +2876,36 @@ QList<Note*> Note::tiedNotes() const
       }
 
 //---------------------------------------------------------
+//   disconnectTiedNotes
+//---------------------------------------------------------
+
+void Note::disconnectTiedNotes()
+      {
+      if (tieBack() && tieBack()->startNote()) {
+            tieBack()->startNote()->remove(tieBack());
+            }
+      if (tieFor() && tieFor()->endNote()) {
+            tieFor()->endNote()->setTieBack(0);
+            }
+      }
+
+//---------------------------------------------------------
+//   connectTiedNotes
+//---------------------------------------------------------
+
+void Note::connectTiedNotes()
+      {
+      if (tieBack()) {
+            tieBack()->setEndNote(this);
+            if (tieBack()->startNote())
+                  tieBack()->startNote()->add(tieBack());
+            }
+      if (tieFor() && tieFor()->endNote()) {
+            tieFor()->endNote()->setTieBack(tieFor());
+            }
+      }
+
+//---------------------------------------------------------
 //   accidentalType
 //---------------------------------------------------------
 
index 128d25c..2e02867 100644 (file)
@@ -70,6 +70,7 @@ class NoteHead : public Symbol {
             HEAD_TI,
             HEAD_SOL,
             HEAD_BREVIS_ALT,
+            HEAD_CUSTOM,
             HEAD_GROUPS,
             HEAD_INVALID = -1
             };
@@ -92,6 +93,8 @@ class NoteHead : public Symbol {
       Group headGroup() const;
 
       static const char* groupToGroupName(Group group);
+      static const char* type2name(Type type);
+      static Type name2type(const QString&);
       };
 
 //---------------------------------------------------------
@@ -236,7 +239,9 @@ class Note : public Element {
       NoteEventList _playEvents;
       QVector<Spanner*> _spannerFor;
       QVector<Spanner*> _spannerBack;
-
+      
+      SymId _cachedNoteheadSym; // use in draw to avoid recomputing at every update
+      
       virtual QRectF drag(EditData*) override;
       void endDrag();
       void endEdit();
@@ -260,6 +265,8 @@ class Note : public Element {
       QPointF canvasPos() const;    ///< position in page coordinates
       void layout();
       void layout2();
+      //setter is used only in drumset tools to setup the notehead preview in the drumset editor and the palette
+      void setCachedNoteheadSym(SymId i) { _cachedNoteheadSym = i; };
       void scanElements(void* data, void (*func)(void*, Element*), bool all=true);
       void setTrack(int val);
 
@@ -271,6 +278,10 @@ class Note : public Element {
       qreal tabHeadHeight(StaffType* tab = 0) const;
       QPointF stemDownNW() const;
       QPointF stemUpSE() const;
+      qreal bboxXShift() const;
+      qreal noteheadCenterX() const;
+      qreal bboxRightPos() const;
+      qreal headBodyWidth() const;
 
       SymId noteHead() const;
       NoteHead::Group headGroup() const   { return _headGroup; }
@@ -348,6 +359,8 @@ class Note : public Element {
       Note* firstTiedNote() const;
       Note* lastTiedNote() const;
       QList<Note*> tiedNotes() const;
+      void disconnectTiedNotes();
+      void connectTiedNotes();
 
       Chord* chord() const            { return (Chord*)parent(); }
       void setChord(Chord* a)         { setParent((Element*)a);  }
index 77401fa..870c1fc 100644 (file)
@@ -761,17 +761,40 @@ bool Score::isSubdivided(ChordRest* chord, int swingUnit)
             return false;
       }
 
+const Drumset* getDrumset(const Chord* chord)
+      {
+      if (chord->staff() && chord->staff()->isDrumStaff()) {
+            const Drumset* ds = chord->staff()->part()->instrument(chord->tick())->drumset();
+            return ds;
+            }
+      return nullptr;
+      }
+
 //---------------------------------------------------------
 //   renderTremolo
 //---------------------------------------------------------
 
-void renderTremolo(Chord *chord, QList<NoteEventList> & ell)
+void renderTremolo(Chord* chord, QList<NoteEventList>& ell)
       {
       Segment* seg = chord->segment();
       Tremolo* tremolo = chord->tremolo();
       int notes = chord->notes().size();
-      //int n = 1 << tremolo->lines();
-      //int l = 1000 / n;
+
+      // check if tremolo was rendered before for drum staff
+      const Drumset* ds = getDrumset(chord);
+      if (ds) {
+            for (Note* n : chord->notes()) {
+                  DrumInstrumentVariant div = ds->findVariant(n->pitch(), chord->articulations(), chord->tremolo());
+                  if (div.pitch != INVALID_PITCH && div.tremolo == tremolo->tremoloType())
+                        return; // already rendered
+                  }
+            }
+
+      // we cannot render buzz roll with MIDI events only
+      if (tremolo->tremoloType() == TremoloType::BUZZ_ROLL)
+            return;
+
+      // render tremolo with multiple events
       if (chord->tremoloChordType() == TremoloChordType::TremoloFirstNote) {
             int t = MScore::division / (1 << (tremolo->lines() + chord->durationType().hooks()));
             Segment::Type st = Segment::Type::ChordRest;
@@ -1417,7 +1440,16 @@ static QList<NoteEventList> renderChord(Chord* chord, int gateTime, int ontime)
 void Score::createGraceNotesPlayEvents(QList<Chord*> gnb, int tick, Chord* chord, int &ontime)
       {
       int n = gnb.size();
-      if (n) {
+      int graceDuration = 0;
+      bool drumset = (getDrumset(chord) != nullptr);
+      const qreal ticksPerSecond = tempo(tick) * MScore::division;
+      const qreal chordTimeMS = (chord->actualTicks() / ticksPerSecond) * 1000;
+      if (drumset) {
+            int flamDuration = 15; //ms
+            graceDuration = flamDuration / chordTimeMS * 1000; //ratio 1/1000 from the main note length
+            ontime = graceDuration * n;
+            }
+      else if (n) {
             //
             //  render grace notes:
             //  simplified implementation:
@@ -1429,11 +1461,8 @@ void Score::createGraceNotesPlayEvents(QList<Chord*> gnb, int tick, Chord* chord
             //
             Chord* graceChord = gnb[0];
             if (graceChord->noteType() ==  NoteType::ACCIACCATURA) {
-                  qreal ticksPerSecond = tempo(tick) * MScore::division;
                   int graceTimeMS = 65 * n;     // value determined empirically (TODO: make instrument-specific, like articulations)
-                  // 1000 occurs below for two different reasons:
-                  // number of milliseconds per second, also unit for ontime
-                  qreal chordTimeMS = (chord->actualTicks() / ticksPerSecond) * 1000;
+                  // 1000 occurs below as a unit for ontime
                   ontime = qMin(500, static_cast<int>((graceTimeMS / chordTimeMS) * 1000));
                   }
             else if (chord->dots() == 1)
@@ -1443,27 +1472,27 @@ void Score::createGraceNotesPlayEvents(QList<Chord*> gnb, int tick, Chord* chord
             else
                   ontime = 500;
 
-            int graceDuration = ontime / n;
-
-            int on = 0;
-            for (int i = 0; i < n; ++i) {
-                  QList<NoteEventList> el;
-                  Chord* gc = gnb.at(i);
-                  int nn = gc->notes().size();
-                  for (int ii = 0; ii < nn; ++ii) {
-                        NoteEventList nel;
-                        nel.append(NoteEvent(0, on, graceDuration));
-                        el.append(nel);
-                        }
+            graceDuration = ontime / n;
+            }
 
-                  if (gc->playEventType() == PlayEventType::InvalidUser)
-                        gc->score()->undo(new ChangeEventList(gc, el));
-                  else if (gc->playEventType() == PlayEventType::Auto) {
-                        for (int ii = 0; ii < nn; ++ii)
-                              gc->notes()[ii]->setPlayEvents(el[ii]);
-                        }
-                  on += graceDuration;
+      int on = 0;
+      for (int i = 0; i < n; ++i) {
+            QList<NoteEventList> el;
+            Chord* gc = gnb.at(i);
+            int nn = gc->notes().size();
+            for (int ii = 0; ii < nn; ++ii) {
+                  NoteEventList nel;
+                  nel.append(NoteEvent(0, on, graceDuration));
+                  el.append(nel);
+                  }
+
+            if (gc->playEventType() == PlayEventType::InvalidUser)
+                  gc->score()->undo(new ChangeEventList(gc, el));
+            else if (gc->playEventType() == PlayEventType::Auto) {
+                  for (int ii = 0; ii < nn; ++ii)
+                        gc->notes()[ii]->setPlayEvents(el[ii]);
                   }
+            on += graceDuration;
             }
       }
 
index f3f4399..87c1f4b 100644 (file)
@@ -769,7 +769,7 @@ Score::FileError Score::loadCompressedMsc(QIODevice* io, bool ignoreVersionError
       QByteArray dbuf = uz.fileData(rootfile);
       if (dbuf.isEmpty()) {
 //            qDebug("root file <%s> is empty", qPrintable(rootfile));
-            QList<MQZipReader::FileInfo> fil = uz.fileInfoList();
+            QVector<MQZipReader::FileInfo> fil = uz.fileInfoList();
             foreach(const MQZipReader::FileInfo& fi, fil) {
                   if (fi.filePath.endsWith(".mscx")) {
                         dbuf = uz.fileData(fi.filePath);
index 7197ffc..89eda7b 100644 (file)
@@ -765,7 +765,7 @@ void Slur::slurPosChord(SlurPos* sp)
             }
       Note* _startNote = stChord->downNote();
       Note* _endNote   = enChord->downNote();
-      qreal hw         = _startNote->headWidth();
+      qreal hw         = _startNote->bboxRightPos();
       qreal __up       = _up ? -1.0 : 1.0;
       qreal _spatium = spatium();
 
@@ -920,7 +920,7 @@ void Slur::slurPos(SlurPos* sp)
             bool stemPos = false;   // p1 starts at chord stem side
 
             // default positions
-            xo = hw1 * .5;
+            xo = hw1 * .5 + (note1 ? note1->bboxXShift() : 0.0);
             if (note1)
                   yo = note1->pos().y();
             else if (_up)
@@ -1016,7 +1016,7 @@ void Slur::slurPos(SlurPos* sp)
             if (sa2 == SlurAnchor::NONE) {
 
                   // default positions
-                  xo = hw2 * .5;
+                  xo = hw2 * .5 + (note2 ? note2->bboxXShift() : 0.0);
                   if (note2)
                         yo = note2->pos().y();
                   else if (_up)
index e4ae405..c2d4df1 100644 (file)
@@ -265,8 +265,10 @@ void Staff::setClef(Clef* clef)
       if (clef->generated())
             return;
       int tick = clef->segment()->tick();
-      for (Segment* s = clef->segment()->next(); s && s->tick() == tick; s = s->next()) {
-            if (s->segmentType() == Segment::Type::Clef && s->element(clef->track())) {
+      for (Segment* s = clef->segment()->next1(); s && s->tick() == tick; s = s->next1()) {
+            if (s->segmentType() == Segment::Type::Clef
+                && s->element(clef->track())
+                && !s->element(clef->track())->generated()) {
                   // adding this clef has no effect on the clefs list
                   return;
                   }
@@ -284,14 +286,16 @@ void Staff::removeClef(Clef* clef)
       if (clef->generated())
             return;
       int tick = clef->segment()->tick();
-      for (Segment* s = clef->segment()->next(); s && s->tick() == tick; s = s->next()) {
-            if (s->segmentType() == Segment::Type::Clef && s->element(clef->track())) {
+      for (Segment* s = clef->segment()->next1(); s && s->tick() == tick; s = s->next1()) {
+            if (s->segmentType() == Segment::Type::Clef
+                && s->element(clef->track())
+                && !s->element(clef->track())->generated()) {
                   // removal of this clef has no effect on the clefs list
                   return;
                   }
             }
       clefs.erase(clef->segment()->tick());
-      for (Segment* s = clef->segment()->prev(); s && s->tick() == tick; s = s->prev()) {
+      for (Segment* s = clef->segment()->prev1(); s && s->tick() == tick; s = s->prev1()) {
             if (s->segmentType() == Segment::Type::Clef
                && s->element(clef->track())
                && !s->element(clef->track())->generated()) {
index 2195fb5..cc07d44 100644 (file)
@@ -1342,13 +1342,17 @@ void StaffType::initStaffTypes()
       {
       readConfigFile(0);          // get TAB font config, before initStaffTypes()
 
+      // keep in sync with enum class StaffTypes
       _presets = {
-//                       group,              xml-name,  human-readable-name,        lin dst clef  bars stmless time  key  ledger
-         StaffType(StaffGroup::STANDARD,   "stdNormal", QObject::tr("Standard"),      5, 1, true, true, false, true, true,  true),
-         StaffType(StaffGroup::PERCUSSION, "perc1Line", QObject::tr("Perc. 1 line"),  1, 1, true, true, false, true, false, true),
-         StaffType(StaffGroup::PERCUSSION, "perc3Line", QObject::tr("Perc. 3 lines"), 3, 2, true, true, false, true, false, true),
-         StaffType(StaffGroup::PERCUSSION, "perc5Line", QObject::tr("Perc. 5 lines"), 5, 1, true, true, false, true, false, true),
-//                 group               xml-name,         human-readable-name         lin dist  clef   bars stemless time      duration font     size off genDur     fret font          size off  duration symbol repeat        thru  minim style                  onLin  rests  stmDn  stmThr upsDn  sTFing   nums bkTied
+//                       group,              xml-name,  human-readable-name,          lin  dist clef   bars stmless time  key    ledger
+         StaffType(StaffGroup::STANDARD,   "stdNormal", QObject::tr("Standard"),        5, 1,   true,  true, false, true, true,  true),
+         StaffType(StaffGroup::PERCUSSION, "perc1Line", QObject::tr("Perc. 1 line"),    1, 1,   true,  true, false, true, false, true),
+         StaffType(StaffGroup::PERCUSSION, "perc3Line", QObject::tr("Perc. 3 lines"),   3, 2,   true,  true, false, true, false, true),
+         StaffType(StaffGroup::PERCUSSION, "perc5Line", QObject::tr("Perc. 5 lines"),   5, 1,   true,  true, false, true, false, true),
+//                 group            xml-name,     human-readable-name                  lin dist clef   bars stemless time      duration font     size off genDur     fret font          size off  duration symbol repeat      thru       minim style              onLin  rests  stmDn  stmThr upsDn  sTFing nums  bkTied
+//       StaffType(StaffGroup::TAB, "tab6StrSimple", QObject::tr("Tab. 6-str. simple"), 6, 1.5, true,  true, true,  false, "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Sans",    9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::NONE,   true,  false, true,  false, false, false, true, false),
+//       StaffType(StaffGroup::TAB, "tab6StrCommon", QObject::tr("Tab. 6-str. common"), 6, 1.5, true,  true, false, false, "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Serif",   9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::SHORTER,true,  false, true,  false, false, false, true, true),
+//       StaffType(StaffGroup::TAB, "tab6StrFull",   QObject::tr("Tab. 6-str. full"),   6, 1.5, true,  true, false, true,  "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Serif",   9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::SLASHED,true,  true,  true,  true,  false, false, true, true),
          StaffType(StaffGroup::TAB, "tab6StrSimple", QObject::tr("Tab. 6-str. simple"), 6, 1.5, true,  true, true,  false, "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Sans",    9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::NONE,   true,  false, true,  false, false, false, true, false),
          StaffType(StaffGroup::TAB, "tab6StrCommon", QObject::tr("Tab. 6-str. common"), 6, 1.5, true,  true, false, false, "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Serif",   9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::SHORTER,true,  false, true,  false, false, false, true, true),
          StaffType(StaffGroup::TAB, "tab6StrFull",   QObject::tr("Tab. 6-str. full"),   6, 1.5, true,  true, false, true,  "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Serif",   9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::SLASHED,true,  true,  true,  true,  false, false, true, true),
@@ -1360,8 +1364,12 @@ void StaffType::initStaffTypes()
          StaffType(StaffGroup::TAB, "tab5StrFull",   QObject::tr("Tab. 5-str. full"),   5, 1.5, true,  true, false, false, "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Serif",   9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::SLASHED,true,  true,  true,  true,  false, false, true, true),
          StaffType(StaffGroup::TAB, "tabUkulele",    QObject::tr("Tab. ukulele"),       4, 1.5, true,  true, false, false, "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Serif",   9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::SHORTER,true,  true,  true,  false, false, false, true, true),
          StaffType(StaffGroup::TAB, "tabBalajka",    QObject::tr("Tab. balalaika"),     3, 1.5, true,  true, false, false, "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Serif",   9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::SHORTER,true,  true,  true,  false, false, false, true, true),
+//       StaffType(StaffGroup::TAB, "tab6StrItalian",QObject::tr("Tab. 6-str. Italian"),6, 1.5, false, true, true,  true,  "MuseScore Tab Italian",15, 0, true,  "MuseScore Tab Renaiss",10, 0, TablatureSymbolRepeat::NEVER, true,  TablatureMinimStyle::NONE,   true,  true,  false, false, true,  false, true, false),
+//       StaffType(StaffGroup::TAB, "tab6StrFrench", QObject::tr("Tab. 6-str. French"), 6, 1.5, false, true, true,  true,  "MuseScore Tab French", 15, 0, true,  "MuseScore Tab Renaiss",10, 0, TablatureSymbolRepeat::NEVER, true,  TablatureMinimStyle::NONE,   false, false, false, false, false, false, false,false)
          StaffType(StaffGroup::TAB, "tab6StrItalian",QObject::tr("Tab. 6-str. Italian"),6, 1.5, false, true, true,  true,  "MuseScore Tab Italian",15, 0, true,  "MuseScore Tab Renaiss",10, 0, TablatureSymbolRepeat::NEVER, true,  TablatureMinimStyle::NONE,   true,  true,  false, false, true,  false, true, false),
-         StaffType(StaffGroup::TAB, "tab6StrFrench", QObject::tr("Tab. 6-str. French"), 6, 1.5, false, true, true,  true,  "MuseScore Tab French", 15, 0, true,  "MuseScore Tab Renaiss",10, 0, TablatureSymbolRepeat::NEVER, true,  TablatureMinimStyle::NONE,   false, false, false, false, false, false, false,false)
+         StaffType(StaffGroup::TAB, "tab6StrFrench", QObject::tr("Tab. 6-str. French"), 6, 1.5, false, true, true,  true,  "MuseScore Tab French", 15, 0, true,  "MuseScore Tab Renaiss",10, 0, TablatureSymbolRepeat::NEVER, true,  TablatureMinimStyle::NONE,   false, false, false, false, false, false, false,false),
+         StaffType(StaffGroup::TAB, "tab7StrCommon", QObject::tr("Tab. 7-str. common"), 7, 1.5, true,  true, false, false, "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Serif",   9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::SHORTER,true,  false, true,  false, false, false, true, true),
+         StaffType(StaffGroup::TAB, "tab8StrCommon", QObject::tr("Tab. 8-str. common"), 8, 1.5, true,  true, false, false, "MuseScore Tab Modern", 15, 0, false, "MuseScore Tab Serif",   9, 0, TablatureSymbolRepeat::NEVER, false, TablatureMinimStyle::SHORTER,true,  false, true,  false, false, false, true, true),
          };
       }
 }                 // namespace Ms
index b48013f..85f537c 100644 (file)
@@ -147,18 +147,21 @@ struct TablatureDurationFont {
       bool read(XmlReader&);
       };
 
-// ready-made staff types:
+// ready-made staff types
+// keep in sync with the _presets initialization in StaffType::initStaffTypes()
 
 enum class StaffTypes : signed char {
       STANDARD,
       PERC_1LINE, PERC_3LINE, PERC_5LINE,
       TAB_6SIMPLE, TAB_6COMMON, TAB_6FULL,
-            TAB_4SIMPLE, TAB_4COMMON, TAB_4FULL,
-            TAB_UKULELE, TAB_BALALAJKA, TAB_ITALIAN, TAB_FRENCH,
+      TAB_4SIMPLE, TAB_4COMMON, TAB_4FULL,
+      TAB_5SIMPLE, TAB_5COMMON, TAB_5FULL,
+      TAB_UKULELE, TAB_BALALAJKA, TAB_ITALIAN, TAB_FRENCH,
+      TAB_7COMMON, TAB_8COMMON,
       STAFF_TYPES,
-      // some usefull shorthands:
-            PERC_DEFAULT = StaffTypes::PERC_5LINE,
-            TAB_DEFAULT = StaffTypes::TAB_6COMMON
+      // some useful shorthands:
+      PERC_DEFAULT = StaffTypes::PERC_5LINE,
+      TAB_DEFAULT = StaffTypes::TAB_6COMMON,
       };
 
 static const int  STAFF_GROUP_NAME_MAX_LENGTH   = 32;
index b23129e..349d6b4 100644 (file)
@@ -78,8 +78,8 @@ void Stem::layout()
 
       qreal y1 = 0.0;                           // vertical displacement to match note attach point
       Staff* st = staff();
-      if (chord() && st ) {
-            if (st->isTabStaff() ) {            // TAB staves
+      if (chord()) {
+            if (st && st->isTabStaff()) {            // TAB staves
                   if (st->staffType()->stemThrough()) {
                         // if stems through staves, gets Y pos. of stem-side note relative to chord other side
                         qreal lineDist = st->lineDistance() * spatium();
index 31d1c27..9fa06bd 100644 (file)
@@ -60,7 +60,7 @@ void StemSlash::layout()
             y += l * 1.2;
             h2 = l * .4;
             }
-      qreal w  = chord()->upNote()->headWidth() * .7;
+      qreal w  = chord()->upNote()->bboxRightPos() * .7;
       setLine(QLineF(QPointF(x + w, y - h2), QPointF(x - w, y + h2)));
       adjustReadPos();
       }
index 4c8b976..6c45d07 100644 (file)
@@ -244,6 +244,22 @@ void StringData::fretChords(Chord * chord) const
       bFretting = false;
       }
 
+
+//---------------------------------------------------------
+//   frettedStrings
+//    Returns the number of fretted strings.
+//---------------------------------------------------------
+
+int StringData::frettedStrings() const
+      {
+      int num = 0;
+      for (auto s : stringTable)
+            if (!s.open)
+                  num++;
+      return num;
+      }
+
+
 //********************
 // STATIC METHODS
 //********************
index ca173c1..b582444 100644 (file)
@@ -54,8 +54,9 @@ public:
       void        fretChords(Chord * chord) const;
       int         getPitch(int string, int fret, Staff* staff, int tick) const;
       static int  pitchOffsetAt(Staff* staff, int tick);
-      int         strings() const               { return stringTable.size(); }
-      QList<instrString>  stringList() const    { return stringTable; }
+      int         strings() const                   { return stringTable.size(); }
+      int         frettedStrings() const;
+      const QList<instrString>&  stringList() const { return stringTable; }
       QList<instrString>&  stringList()         { return stringTable; }
       int         frets() const                 { return _frets; }
       void        setFrets(int val)             { _frets = val; }
index b9d0d56..2f3fb66 100644 (file)
@@ -2527,6 +2527,7 @@ QVector<const char*> Sym::symNames = {
       "noteLongaSquareUp",
       "noteLongaSquareDown",
       "space"
+
       };
 
 QVector<QString> Sym::symUserNames = {
@@ -5317,7 +5318,11 @@ void ScoreFont::draw(SymId id, QPainter* painter, qreal mag, const QPointF& pos,
             return;
             }
       if (!isValid(id)) {
-            qDebug("ScoreFont::draw: invalid sym %d\n", int(id));
+            if (/*MScore::useFallbackFont &&*/this != ScoreFont::fallbackFont())
+                  fallbackFont()->draw(id, painter, mag, pos, worldScale);
+            else
+                  qDebug("ScoreFont::draw: invalid sym %d\n", int(id));
+
             return;
             }
       int rv = FT_Load_Glyph(face, sym(id).index(), FT_LOAD_DEFAULT);
@@ -5831,6 +5836,9 @@ const char* ScoreFont::fallbackTextFont()
 
 const QRectF ScoreFont::bbox(SymId id, qreal mag) const
       {
+      if (useFallbackFont(id))
+            return fallbackFont()->bbox(id, mag);
+
       QRectF r = sym(id).bbox();
       return QRectF(r.x() * mag, r.y() * mag, r.width() * mag, r.height() * mag);
       }
@@ -5872,6 +5880,15 @@ ScoreFont::~ScoreFont()
       {
       delete cache;
       }
+
+//---------------------------------------------------------
+//   useFallbackFont
+//---------------------------------------------------------
+
+bool ScoreFont::useFallbackFont(SymId id) const
+      {
+      return /*MScore::useFallbackFont &&*/!sym(id).isValid() && this != ScoreFont::fallbackFont();
+      }
 }
 
 
index 4b78d59..1f0a9e5 100644 (file)
@@ -2663,8 +2663,8 @@ class ScoreFont {
       void draw(const QList<SymId>&, QPainter*, qreal mag, const QPointF& pos, qreal scale) const;
       void draw(SymId id, QPainter* painter, qreal mag, const QPointF& pos, int n) const;
 
-      qreal height(SymId id, qreal mag) const         { return sym(id).bbox().height() * mag; }
-      qreal width(SymId id, qreal mag) const          { return sym(id).bbox().width() * mag;  }
+      qreal height(SymId id, qreal mag) const         { return bbox(id, mag).height(); }
+      qreal width(SymId id, qreal mag) const          { return bbox(id, mag).width();  }
       qreal advance(SymId id, qreal mag) const        { return sym(id).advance() * mag;  }
       qreal width(const QList<SymId>&, qreal mag) const;
 
@@ -2678,6 +2678,7 @@ class ScoreFont {
       QPointF cutOutSW(SymId id, qreal mag) const     { return sym(id).cutOutSW() * mag; }
 
       bool isValid(SymId id) const                    { return sym(id).isValid(); }
+      bool useFallbackFont(SymId id) const;
       };
 
 extern void initScoreFonts();
index e54dec7..68ee176 100644 (file)
@@ -265,6 +265,7 @@ bool TempoText::setProperty(P_ID propertyId, const QVariant& v)
                   break;
             case P_ID::TEMPO_FOLLOW_TEXT:
                   _followText = v.toBool();
+                  textChanged();
                   break;
             default:
                   if (!Text::setProperty(propertyId, v))
index dea80d0..20ac3a9 100644 (file)
@@ -296,7 +296,10 @@ void TextLineSegment::layout1()
       bbox().setRect(x1, y1, x2 - x1, y2 - y1);
       // set end text position and extend bbox
       if (_endText) {
-            _endText->setPos(bbox().right(), 0);
+            // Set _endText's position horizontally related to the dimensions of the previously formed rectangle.
+            // Previously, this was performed while 'zeroing' the vertical alignment, hence the bug of
+            // non-effect regarding the user-defined end-text's vertical alignment
+            _endText->setUserXoffset(bbox().right());
             bbox() |= _endText->bbox().translated(_endText->pos());
             }
       }
index e045f0f..1362721 100644 (file)
@@ -17,6 +17,7 @@
 #include "measure.h"
 #include "segment.h"
 #include "stem.h"
+#include "sym.h"
 #include "xml.h"
 
 namespace Ms {
@@ -30,6 +31,7 @@ static const char* tremoloName[] = {
       QT_TRANSLATE_NOOP("Tremolo", "16th through stem"),
       QT_TRANSLATE_NOOP("Tremolo", "32nd through stem"),
       QT_TRANSLATE_NOOP("Tremolo", "64th through stem"),
+      QT_TRANSLATE_NOOP("Tremolo", "Buzz roll"),
       QT_TRANSLATE_NOOP("Tremolo", "Eighth between notes"),
       QT_TRANSLATE_NOOP("Tremolo", "16th between notes"),
       QT_TRANSLATE_NOOP("Tremolo", "32nd between notes"),
@@ -69,9 +71,15 @@ qreal Tremolo::mag() const
 
 void Tremolo::draw(QPainter* painter) const
       {
-      painter->setBrush(QBrush(curColor()));
-      painter->setPen(Qt::NoPen);
-      painter->drawPath(path);
+      if (tremoloType() == TremoloType::BUZZ_ROLL) {
+            painter->setPen(curColor());
+            drawSymbol(SymId::buzzRoll, painter);
+            }
+      else {
+            painter->setBrush(QBrush(curColor()));
+            painter->setPen(Qt::NoPen);
+            painter->drawPath(path);
+            }
       if ((parent() == 0) && !twoNotes()) {
             qreal x = 0.0; // bbox().width() * .25;
             QPen pen(curColor(), point(score()->styleS(StyleIdx::stemWidth)));
@@ -377,11 +385,30 @@ void Tremolo::read(XmlReader& e)
 
 QString Tremolo::tremoloTypeName() const
       {
-      switch(tremoloType()) {
+      return type2name(tremoloType());
+      }
+
+//---------------------------------------------------------
+//   setTremoloType
+//---------------------------------------------------------
+
+void Tremolo::setTremoloType(const QString& s)
+      {
+      setTremoloType(name2Type(s));
+      }
+
+//---------------------------------------------------------
+//   type2Name
+//---------------------------------------------------------
+
+QString Tremolo::type2name(TremoloType t)
+      {
+      switch(t) {
             case TremoloType::R8:  return QString("r8");
             case TremoloType::R16: return QString("r16");
             case TremoloType::R32: return QString("r32");
             case TremoloType::R64: return QString("r64");
+            case TremoloType::BUZZ_ROLL: return QString("buzzroll");
             case TremoloType::C8:  return QString("c8");
             case TremoloType::C16: return QString("c16");
             case TremoloType::C32: return QString("c32");
@@ -393,10 +420,10 @@ QString Tremolo::tremoloTypeName() const
       }
 
 //---------------------------------------------------------
-//   setTremoloType
+//   nameToType
 //---------------------------------------------------------
 
-void Tremolo::setTremoloType(const QString& s)
+TremoloType Tremolo::name2Type(const QString& s)
       {
       TremoloType t;
       if (s == "r8")
@@ -415,9 +442,11 @@ void Tremolo::setTremoloType(const QString& s)
             t = TremoloType::C32;
       else if (s == "c64")
             t = TremoloType::C64;
+      else if (s == "buzzroll")
+            t = TremoloType::BUZZ_ROLL;
       else
             t = TremoloType(s.toInt());    // for compatibility with old tremolo type
-      setTremoloType(t);
+      return  t;
       }
 
 //---------------------------------------------------------
index b1d4dcf..65eeb49 100644 (file)
@@ -22,7 +22,8 @@ namespace Ms {
 class Chord;
 
 // Tremolo subtypes:
-enum class TremoloType : char {
+enum class TremoloType : signed char {
+      INVALID_TREMOLO = -1,
       OLD_R8 = 0,
       OLD_R16,
       OLD_R32,
@@ -30,7 +31,7 @@ enum class TremoloType : char {
       OLD_C16,
       OLD_C32,
 
-      R8=6, R16, R32, R64,  // one note tremolo (repeat)
+      R8=6, R16, R32, R64, BUZZ_ROLL,  // one note tremolo (repeat)
       C8, C16, C32, C64     // two note tremolo (change)
       };
 
@@ -62,6 +63,8 @@ class Tremolo : public Element {
 
       void setTremoloType(TremoloType t);
       TremoloType tremoloType() const      { return _tremoloType; }
+      static TremoloType name2Type(const QString& s);
+      static QString type2name(TremoloType t);
 
       virtual qreal mag() const;
       virtual void draw(QPainter*) const;
@@ -77,7 +80,7 @@ class Tremolo : public Element {
             _chord2 = c2;
             }
       Fraction tremoloLen() const;
-      bool twoNotes() const { return tremoloType() > TremoloType::R64; } // is it a two note tremolo?
+      bool twoNotes() const { return tremoloType() >= TremoloType::C8; } // is it a two note tremolo?
       int lines() const { return _lines; }
 
       virtual QString accessibleInfo() override;
index acc9e44..b58d5d2 100644 (file)
@@ -23,6 +23,7 @@
 #include "stem.h"
 #include "beam.h"
 #include "measure.h"
+#include "rest.h"
 
 namespace Ms {
 
@@ -1021,5 +1022,91 @@ void Tuplet::sanitizeTuplet()
             }
       }
 
+//---------------------------------------------------------
+//   addMissingElement
+//     Add a rest with the given start and end ticks.
+//     Should only be called from Tuplet::addMissingElements().
+//     Needed for importing files that saved incomplete tuplets.
+//---------------------------------------------------------
+
+Fraction Tuplet::addMissingElement(int startTick, int endTick)
+      {
+      Fraction f = Fraction::fromTicks(endTick - startTick) * ratio();
+      TDuration d = TDuration(f, true);
+      if (!d.isValid()) {
+            qDebug("Tuplet::addMissingElement(): invalid duration: %d/%d", f.numerator(), f.denominator());
+            return Fraction::fromTicks(0);
+            }
+      f = d.fraction();
+      Rest* rest = new Rest(score());
+      rest->setDurationType(d);
+      rest->setDuration(f);
+      rest->setTrack(track());
+      rest->setVisible(false);
+      Segment* segment = measure()->getSegment(Segment::Type::ChordRest, startTick);
+      segment->add(rest);
+      add(rest);
+      return f;
+      }
+
+//---------------------------------------------------------
+//   addMissingElements
+//     Make this tuplet complete by filling in holes where
+//     there ought to be rests. Needed for importing files
+//     that saved incomplete tuplets.
+//---------------------------------------------------------
+
+void Tuplet::addMissingElements()
+      {
+      if (tuplet())
+            return;     // do not correct nested tuplets
+      if (voice() == 0)
+            return;     // nothing to do for tuplets in voice 1
+      Fraction missingElementsDuration = duration() * ratio() - elementsDuration();
+      if (missingElementsDuration.isZero())
+            return;
+      // first, fill in any holes in the middle of the tuplet
+      int expectedTick = elements().front()->tick();
+      for (DurationElement* de : elements()) {
+            if (de->tick() != expectedTick) {
+                  missingElementsDuration -= addMissingElement(expectedTick, de->tick());
+                  if (missingElementsDuration.isZero())
+                        return;
+                  }
+            expectedTick += de->actualTicks();
+            }
+      // calculate the tick where we would expect a tuplet of this duration to start
+      expectedTick = elements().front()->tick() - elements().front()->tick() % duration().ticks();
+      if (expectedTick != elements().front()->tick()) {
+            // try to fill a hole at the beginning of the tuplet
+            int firstAvailableTick = measure()->tick();
+            Segment* segment = measure()->findSegment(Segment::Type::ChordRest, elements().front()->tick());
+            ChordRest* prevChordRest = segment && segment->prev() ? segment->prev()->nextChordRest(track(), true) : nullptr;
+            if (prevChordRest && prevChordRest->measure() == measure())
+                  firstAvailableTick = prevChordRest->tick() + prevChordRest->actualTicks();
+            if (firstAvailableTick != elements().front()->tick()) {
+                  Fraction f = missingElementsDuration / ratio();
+                  int ticksRequired = f.ticks();
+                  int endTick = elements().front()->tick();
+                  int startTick = max(firstAvailableTick, endTick - ticksRequired);
+                  if (expectedTick > startTick)
+                        startTick = expectedTick;
+                  missingElementsDuration -= addMissingElement(startTick, endTick);
+                  if (missingElementsDuration.isZero())
+                        return;
+                  }
+            }
+      // now fill a hole at the end of the tuplet
+      int startTick = elements().back()->tick() + elements().back()->actualTicks();
+      int endTick = elements().front()->tick() + duration().ticks();
+      // just to be safe, find the next ChordRest in the track, and adjust endTick if necessary
+      Segment* segment = measure()->findSegment(Segment::Type::ChordRest, elements().back()->tick());
+      ChordRest* nextChordRest = segment && segment->next() ? segment->next()->nextChordRest(track(), false) : nullptr;
+      if (nextChordRest && nextChordRest->tick() < endTick)
+            endTick = nextChordRest->tick();
+      missingElementsDuration -= addMissingElement(startTick, endTick);
+      if (!missingElementsDuration.isZero())
+            qDebug("Tuplet::addMissingElements(): still missing duration of %d/%d", missingElementsDuration.numerator(), missingElementsDuration.denominator());
+      }
 }
 
index c9ca6a3..bb47bc2 100644 (file)
@@ -125,6 +125,10 @@ class Tuplet : public DurationElement {
       bool setProperty(P_ID propertyId, const QVariant& v);
       QVariant propertyDefault(P_ID id) const;
       void sanitizeTuplet();
+      void addMissingElements();
+
+   private:
+      Fraction addMissingElement(int startTick, int endTick);
       };
 
 
index 64e3cef..19ea888 100644 (file)
@@ -538,8 +538,9 @@ void Score::undoChangeClef(Staff* ostaff, Segment* seg, ClefType st)
 
             // move measure-initial clef to last segment of prev measure
 
-            if (firstSeg                        // if at start of measure
-               && measure->prevMeasure()        // and there is a previous measure
+            if ((firstSeg                        // if at start of measure
+               && measure->prevMeasure())        // and there is a previous measure
+               || tick == seg->measure()->endTick() 
                ) {
                   measure = measure->prevMeasure();
                   destSeg = measure->findSegment(Segment::Type::Clef, tick);
@@ -1596,6 +1597,26 @@ const char* AddElement::name() const
 #endif
 
 //---------------------------------------------------------
+//   removeNote
+//    Helper function for RemoveElement class
+//---------------------------------------------------------
+
+static void removeNote(const Note* note)
+      {
+      Score* score = note->score();
+      if (note->tieFor() && note->tieFor()->endNote())
+            score->undo(new RemoveElement(note->tieFor()));
+      if (note->tieBack())
+            score->undo(new RemoveElement(note->tieBack()));
+      for (Spanner* s : note->spannerBack()) {
+            score->undo(new RemoveElement(s));
+            }
+      for (Spanner* s : note->spannerFor()) {
+            score->undo(new RemoveElement(s));
+            }
+      }
+
+//---------------------------------------------------------
 //   RemoveElement
 //---------------------------------------------------------
 
@@ -1646,19 +1667,15 @@ RemoveElement::RemoveElement(Element* e)
                               score->undo(new RemoveElement(tremolo));
                         }
                   for (const Note* note : chord->notes()) {
-                        if (note->tieFor() && note->tieFor()->endNote())
-                              score->undo(new RemoveElement(note->tieFor()));
-                        if (note->tieBack())
-                              score->undo(new RemoveElement(note->tieBack()));
-                        for (Spanner* s : note->spannerBack()) {
-                              score->undo(new RemoveElement(s));
-                              }
-                        for (Spanner* s : note->spannerFor()) {
-                              score->undo(new RemoveElement(s));
-                              }
+                        removeNote(note);
                         }
                   }
             }
+      else if (e->type() == Element::Type::NOTE) {
+            // Removing an individual note within a chord
+            const Note* note = static_cast<const Note*>(e);
+            removeNote(note);
+            }
       }
 
 //---------------------------------------------------------
@@ -1686,10 +1703,7 @@ void RemoveElement::undo()
             if (element->type() == Element::Type::CHORD) {
                   Chord* chord = static_cast<Chord*>(element);
                   foreach(Note* note, chord->notes()) {
-                        if (note->tieBack())
-                              note->tieBack()->setEndNote(note);
-                        if (note->tieFor() && note->tieFor()->endNote())
-                              note->tieFor()->endNote()->setTieBack(note->tieFor());
+                        note->connectTiedNotes();
                         }
                   }
             undoAddTuplet(static_cast<ChordRest*>(element));
@@ -1709,9 +1723,7 @@ void RemoveElement::redo()
             if (element->type() == Element::Type::CHORD) {
                   Chord* chord = static_cast<Chord*>(element);
                   foreach(Note* note, chord->notes()) {
-                        if (note->tieFor() && note->tieFor()->endNote()) {
-                              note->tieFor()->endNote()->setTieBack(0);
-                              }
+                        note->disconnectTiedNotes();
                         }
                   }
             }
index 9d1bd18..9336318 100644 (file)
@@ -696,6 +696,34 @@ int updateVersion()
       }
 
 //---------------------------------------------------------
+//   updateVersion
+///  Up to 4 digits X.X.X.X
+///  Each digit can be double XX.XX.XX.XX
+///  return true if v1 < v2
+//---------------------------------------------------------
+
+bool compareVersion(QString v1, QString v2)
+      {
+      auto v1l = v1.split(".");
+      auto v2l = v2.split(".");
+      int ma = qPow(100,qMax(v1l.size(), v2l.size()));
+      int m = ma;
+      int vv1 = 0;
+      for (int i = 0; i < v1l.size(); i++) {
+            vv1 += (m * v1l[i].toInt());
+            m /= 100;
+            }
+      m = ma;
+      int vv2 = 0;
+      for (int i = 0; i < v2l.size(); i++) {
+            vv2 += (m * v2l[i].toInt());
+            m /= 100;
+            }
+
+      return vv1 < vv2;
+      }
+
+//---------------------------------------------------------
 //   diatonicUpDown
 //    used to find the second note of a trill, mordent etc.
 //    key  -7 ... +7
index 56e3c61..116fb34 100644 (file)
@@ -65,6 +65,7 @@ extern int version();
 extern int majorVersion();
 extern int minorVersion();
 extern int updateVersion();
+extern bool compareVersion(QString v1, QString v2);
 
 extern Note* nextChordNote(Note* note);
 extern Note* prevChordNote(Note* note);
index 5fbd9f5..0058410 100644 (file)
@@ -250,6 +250,9 @@ void XmlReader::checkTuplets()
                   tuplet->sanitizeTuplet();
                   }
             }
+      // This requires a separate pass in case of nested tuplets that required sanitizing
+      for (Tuplet* tuplet : tuplets())
+            tuplet->addMissingElements();
       }
 
 //---------------------------------------------------------
index 57f2b11..50379c5 100644 (file)
@@ -290,6 +290,7 @@ add_executable ( ${ExecutableName}
       logindialog.cpp loginmanager.cpp uploadscoredialog.cpp breaksdialog.cpp searchComboBox.cpp
       help.cpp help.h
       toolbuttonmenu.cpp
+      extension.cpp extension.h
 
       ${COCOABRIDGE}
       ${OMR_FILES}
@@ -380,7 +381,7 @@ if (MINGW)
             ${PROJECT_BINARY_DIR}/resfile.o
             PROPERTIES generated true
             )
-      string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE)
+      string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE)
       # Windows: add -mconsole to LINK_FLAGS to get a console window for debug output
       if (CMAKE_BUILD_TYPE MATCHES "DEBUG")
             set_target_properties(mscore
index ed165a8..59c6e35 100644 (file)
@@ -44,6 +44,7 @@ AlbumItem::~AlbumItem()
 
 Album::Album()
       {
+      _relativePath = true;
       _dirty = false;
       }
 
@@ -278,6 +279,7 @@ bool Album::read(const QString& p)
 
 void Album::load(XmlReader& e)
       {
+      QDir albumDir = QDir(QFileInfo(QFile(_path)).path());
       while (e.readNextStartElement()) {
             const QStringRef& tag(e.name());
             if (tag == "Score") {
@@ -287,7 +289,7 @@ void Album::load(XmlReader& e)
                         if (tag == "name")
                               i->name = e.readElementText();
                         else if (tag == "path")
-                              i->path = e.readElementText();
+                              i->path = albumDir.filePath(e.readElementText());
                         else
                               e.unknown();
                         }
@@ -328,12 +330,13 @@ void Album::loadScores()
 
 void Album::save(Xml& xml)
       {
+      QDir albumDir = QDir(QFileInfo(QFile(_path)).path());
       xml.stag("Album");
       xml.tag("name", _name);
       for (AlbumItem* item : _scores) {
             xml.stag("Score");
             xml.tag("name", item->name);
-            xml.tag("path", item->path);
+            xml.tag("path", _relativePath ? albumDir.relativeFilePath(item->path) : item->path);
             xml.etag();
             }
       xml.etag();
@@ -405,5 +408,18 @@ void Album::setPath(const QString& s)
             _dirty = true;
             }
       }
+
+//---------------------------------------------------------
+//   setRelativePath
+//---------------------------------------------------------
+
+void Album::setRelativePath(bool relativePath)
+      {
+      if (_relativePath != relativePath)
+            {
+            _relativePath = relativePath;
+            _dirty = true;
+            }
+      }
 }
 
index 80c9c16..c1e4090 100644 (file)
@@ -47,6 +47,7 @@ struct AlbumItem {
 
 class Album {
       bool _dirty;
+      bool _relativePath;
       QString _path;
       QString _name;
       QList<AlbumItem*> _scores;
@@ -66,8 +67,10 @@ class Album {
       bool dirty() const             { return _dirty; }
       QString name() const           { return _name;  }
       QString path() const           { return _path;  }
+      bool relativePath()            { return _relativePath; }
       void setName(const QString&);
       void setPath(const QString&);
+      void setRelativePath(bool);
       QList<AlbumItem*>& scores()    { return _scores; }
       void append(AlbumItem* item);
       void remove(int);
index a52d131..50984f5 100644 (file)
@@ -54,6 +54,7 @@ AlbumManager::AlbumManager(QWidget* parent)
       connect(albumName,   SIGNAL(textChanged(const QString&)), SLOT(albumNameChanged(const QString&)));
       connect(scoreList,   SIGNAL(currentRowChanged(int)), SLOT(currentScoreChanged(int)));
       connect(scoreList,   SIGNAL(itemChanged(QListWidgetItem*)), SLOT(itemChanged(QListWidgetItem*)));
+      connect(checkBoxRelativePath,   SIGNAL(toggled(bool)), SLOT(relativePathChanged()));
       connect(buttonBox,   SIGNAL(clicked(QAbstractButton*)), SLOT(buttonBoxClicked(QAbstractButton*)));
       currentScoreChanged(-1);
       add->setEnabled(false);
@@ -77,7 +78,7 @@ void AlbumManager::addClicked()
          );
       if (files.isEmpty())
             return;
-      foreach(QString fn, files) {
+      for (QString fn : files) {
             if (fn.isEmpty())
                   continue;
             if(fn.endsWith (".mscz") || fn.endsWith (".mscx")) {
@@ -121,7 +122,7 @@ void AlbumManager::printClicked()
       }
 
 //---------------------------------------------------------
-//   createScore
+//   createScoreClicked
 //---------------------------------------------------------
 
 void AlbumManager::createScoreClicked()
@@ -207,12 +208,16 @@ void AlbumManager::setAlbum(Album* a)
       album = a;
       scoreList->clear();
       albumName->setText(album->name().isEmpty() ? QWidget::tr("Untitled") : album->name());
-      foreach(AlbumItem* a, album->scores()) {
+      for (AlbumItem* a : album->scores()) {
             QListWidgetItem* li = new QListWidgetItem(a->name, scoreList);
             li->setToolTip(a->path);
             li->setFlags(Qt::ItemFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled));
+
+            if (album->relativePath() && QFileInfo(a->path).isAbsolute())
+                  album->setRelativePath(false);
             }
       albumName->setEnabled(true);
+      checkBoxRelativePath->setChecked(album->relativePath());
       add->setEnabled(true);
       print->setEnabled(true);
       createScore->setEnabled(true);
@@ -293,7 +298,7 @@ void AlbumManager::buttonBoxClicked(QAbstractButton* button)
       }
 
 //---------------------------------------------------------
-//   buttonBoxClicked
+//   writeAlbum
 //---------------------------------------------------------
 
 void AlbumManager::writeAlbum()
@@ -324,6 +329,7 @@ void AlbumManager::writeAlbum()
             return;
             }
       Xml xml(&f);
+      album->setRelativePath(checkBoxRelativePath->isChecked());
       album->write(xml);
       if (f.error() != QFile::NoError) {
             QString s = QWidget::tr("Write Album failed: ") + f.errorString();
@@ -354,5 +360,17 @@ void AlbumManager::hideEvent(QHideEvent* event)
       MuseScore::saveGeometry(this);
       QDialog::hideEvent(event);
       }
+
+//---------------------------------------------------------
+//   relativePathChanged
+//---------------------------------------------------------
+
+void AlbumManager::relativePathChanged()
+      {
+      if (album) {
+            album->setRelativePath(checkBoxRelativePath->isChecked());
+            album->setDirty(true);
+            }
+      }
 }
 
index 6907bdb..e251475 100644 (file)
@@ -50,6 +50,7 @@ class AlbumManager : public QDialog, public Ui::AlbumManager {
       void currentScoreChanged(int);
       void itemChanged(QListWidgetItem*);   // score name in list is edited
       void buttonBoxClicked(QAbstractButton*);
+      void relativePathChanged();
    private:
       void writeAlbum();
 
index 80f1442..777d64a 100644 (file)
     </layout>
    </item>
    <item>
+    <widget class="QCheckBox" name="checkBoxRelativePath">
+     <property name="text">
+      <string>Store relative paths</string>
+     </property>
+     <property name="checked">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
     <widget class="QGroupBox" name="groupBoxJoinScores">
      <property name="minimumSize">
       <size>
index ef1cbeb..e30f269 100644 (file)
Binary files a/mscore/data/splash.png and b/mscore/data/splash.png differ
index 4c338c4..9fd834b 100644 (file)
@@ -25,7 +25,7 @@
     <g id="MuseScore-Software" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="splash-2">
             <text id="version" fill="url(#linearGradient-1)" font-family="HelveticaNeue-Thin, Helvetica Neue" font-size="77.4965973" font-style="condensed" font-weight="normal" letter-spacing="1">
-                <tspan x="665.062233" y="574.757709">2.2</tspan>
+                <tspan x="665.062233" y="574.757709">2.3</tspan>
             </text>
             <text id="version-2" fill="url(#linearGradient-1)" font-family="HelveticaNeue-Thin, Helvetica Neue" font-size="38" font-style="condensed" font-weight="normal" letter-spacing="0.490344107">
                 <tspan x="597.668942" y="832.757709">musescore.org</tspan>
@@ -39,4 +39,4 @@
             <rect id="Rectangle-10" fill="url(#linearGradient-4)" x="373.578231" y="463.929515" width="693.157895" height="2.30754663"></rect>
         </g>
     </g>
-</svg>
\ No newline at end of file
+</svg>
index de26977..61ab894 100644 (file)
@@ -36,7 +36,6 @@ bool DownloadUtils::saveFile()
 void DownloadUtils::downloadFinished(QNetworkReply *data)
       {
       sdata = data->readAll();
-      qDebug() << "size" << sdata.size();
       emit done();
       }
 
@@ -45,16 +44,31 @@ QByteArray DownloadUtils::returnData()
       return sdata;
       }
 
-void DownloadUtils::download()
+void DownloadUtils::download(bool showProgress)
       {
       QUrl url = QUrl::fromEncoded(_target.toLocal8Bit());
       QNetworkRequest request(url);
       QEventLoop loop;
       QNetworkReply* reply = manager.get(request);
+
       QObject::connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64,qint64)));
       QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
       QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
+
+      if (showProgress) {
+            progressDialog = new QProgressDialog(static_cast<QWidget*>(parent()));
+            progressDialog->setWindowFlags(Qt::WindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint));
+            progressDialog->setWindowModality(Qt::ApplicationModal);
+            progressDialog->setCancelButtonText(tr("Cancel"));
+            progressDialog->setLabelText(tr("Downloading..."));
+            progressDialog->setAutoClose(true);
+            progressDialog->setAutoReset(true);
+            QObject::connect(progressDialog, SIGNAL(canceled()), &loop, SLOT(quit()));
+            progressDialog->show();
+            }
+
       loop.exec();
+
       QObject::disconnect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64,qint64)));
       QObject::disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
       QObject::disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
@@ -62,7 +76,9 @@ void DownloadUtils::download()
 
 void DownloadUtils::downloadProgress(qint64 received, qint64 total)
       {
-      qDebug() << (double(received)/total)*100 << "%";
+      double curVal = (double(received)/total)*100;
+      if (progressDialog && progressDialog->isVisible())
+            progressDialog->setValue(curVal);
       }
 
 }
index a197265..d9c92c5 100644 (file)
@@ -24,6 +24,8 @@ class DownloadUtils : public QObject
       QString _target;
       QString _localFile;
 
+      QProgressDialog* progressDialog = nullptr;
+
    public:
       explicit DownloadUtils(QWidget *parent=0);
 
@@ -36,7 +38,7 @@ class DownloadUtils : public QObject
       void done();
 
    public slots:
-      void download();
+      void download(bool showProgress = false);
       void downloadFinished(QNetworkReply* data);
       void downloadProgress(qint64 received, qint64 total);
       };
index e585397..178f2db 100644 (file)
@@ -51,14 +51,25 @@ DrumTools::DrumTools(QWidget* parent)
 
       QWidget* w = new QWidget(this);
       w->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
+      w->setMaximumHeight(100);
       QHBoxLayout* layout = new QHBoxLayout;
       w->setLayout(layout);
 
       QVBoxLayout* layout1 = new QVBoxLayout;
+      layout1->setSpacing(6);
+      pitchName = new QLabel;
+      pitchName->setAlignment(Qt::AlignCenter);
+      pitchName->setWordWrap(true);
+      pitchName->setContentsMargins(25, 10, 25, 10);
+      layout1->addWidget(pitchName);
+      QHBoxLayout* buttonLayout = new QHBoxLayout;
+      buttonLayout->setContentsMargins(25, 10, 25, 10);
       QToolButton* tb = new QToolButton;
       tb->setText(tr("Edit Drumset"));
-      layout1->addWidget(tb);
-      layout1->addStretch();
+      tb->setMinimumWidth(100);
+      tb->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
+      buttonLayout->addWidget(tb);
+      layout1->addLayout(buttonLayout);
       layout->addLayout(layout1);
 
       drumPalette = new Palette;
@@ -71,7 +82,6 @@ DrumTools::DrumTools(QWidget* parent)
       layout->addWidget(sa);
 
       setWidget(w);
-//      setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
 
       w = new QWidget(this);
       setTitleBarWidget(w);
@@ -79,6 +89,8 @@ DrumTools::DrumTools(QWidget* parent)
       connect(tb, SIGNAL(clicked()), SLOT(editDrumset()));
       void boxClicked(int);
       connect(drumPalette, SIGNAL(boxClicked(int)), SLOT(drumNoteSelected(int)));
+
+      drumPalette->setContextMenuPolicy(Qt::PreventContextMenu);
       }
 
 //---------------------------------------------------------
@@ -92,14 +104,16 @@ void DrumTools::updateDrumset(const Drumset* ds)
       if (!drumset)
             return;
       double _spatium = gscore->spatium();
-      for (int pitch = 0; pitch < 128; ++pitch) {
-            if (!drumset->isValid(pitch))
+      const auto d = ds->drumsByIndex();
+      for (int n = 0; n < DRUM_INSTRUMENTS; ++n) {
+            DrumInstrument di = d[n];
+            if (!drumset->isValid(di.pitch))
                   continue;
             bool up;
-            int line      = drumset->line(pitch);
-            NoteHead::Group noteHead  = drumset->noteHead(pitch);
-            int voice     = drumset->voice(pitch);
-            MScore::Direction dir = drumset->stemDirection(pitch);
+            int line      = di.line;
+            NoteHead::Group noteHead  = di.notehead;
+            int voice     = di.voice;
+            MScore::Direction dir = di.stemDirection;
             if (dir == MScore::Direction::UP)
                   up = true;
             else if (dir == MScore::Direction::DOWN)
@@ -115,21 +129,28 @@ void DrumTools::updateDrumset(const Drumset* ds)
             Note* note = new Note(gscore);
             note->setParent(chord);
             note->setTrack(voice);
-            note->setPitch(pitch);
+            note->setPitch(di.pitch);
             note->setTpcFromPitch();
             note->setLine(line);
             note->setPos(0.0, _spatium * .5 * line);
             note->setHeadGroup(noteHead);
+            SymId noteheadSym = SymId::noteheadBlack;
+            if (noteHead == NoteHead::Group::HEAD_CUSTOM)
+                  noteheadSym = drumset->noteHeads(di.pitch, NoteHead::Type::HEAD_QUARTER);
+            else
+                  noteheadSym = note->noteHead(true, di.notehead, NoteHead::Type::HEAD_QUARTER);
+
+            note->setCachedNoteheadSym(noteheadSym); // we use the cached notehead so we don't recompute it at each layout
             chord->add(note);
             Stem* stem = new Stem(gscore);
             stem->setLen((up ? -3.0 : 3.0) * _spatium);
             chord->add(stem);
             stem->setPos(chord->stemPos());
-            int sc = drumset->shortcut(pitch);
+            int sc = di.shortcut;
             QString shortcut;
             if (sc)
                   shortcut = QChar(sc);
-            drumPalette->append(chord, qApp->translate("drumset", drumset->name(pitch).toLatin1().data()), shortcut);
+            drumPalette->append(chord, qApp->translate("drumset", drumset->name(di.pitch).toUtf8().data()), shortcut);
             }
       }
 
@@ -187,6 +208,8 @@ void DrumTools::drumNoteSelected(int val)
             getAction("voice-2")->setChecked(element->voice() == 1);
             getAction("voice-3")->setChecked(element->voice() == 2);
             getAction("voice-4")->setChecked(element->voice() == 3);
+
+            pitchName->setText(drumPalette->getCellName(val));
             }
       }
 
@@ -199,6 +222,7 @@ int DrumTools::selectedDrumNote()
       if (element && element->type() == Element::Type::CHORD) {
             Chord* ch  = static_cast<Chord*>(element);
             Note* note = ch->downNote();
+            pitchName->setText(drumPalette->getCellName(idx));
             return note->pitch();
             }
       else {
index 56b65ed..da87a68 100644 (file)
@@ -39,6 +39,7 @@ class DrumTools : public QDockWidget {
       Staff* staff;
       Palette* drumPalette;
       const Drumset* drumset;
+      QLabel* pitchName;
 
    private slots:
       void drumNoteSelected(int val);
index e603cea..ccd9442 100644 (file)
@@ -37,7 +37,8 @@ enum Column : char { PITCH, NOTE, SHORTCUT, NAME };
 //   as not being useful for drums
 //---------------------------------------------------------
 
-const char* noteHeadNames[int(NoteHead::Group::HEAD_GROUPS)] = {
+static constexpr int noteheadGroupsGUICount = 13;
+const char* noteHeadNames[noteheadGroupsGUICount] = {
       QT_TRANSLATE_NOOP("EditDrumset", "Normal"),
       QT_TRANSLATE_NOOP("EditDrumset", "Cross"),
       QT_TRANSLATE_NOOP("EditDrumset", "Diamond"),
@@ -50,8 +51,24 @@ const char* noteHeadNames[int(NoteHead::Group::HEAD_GROUPS)] = {
       QT_TRANSLATE_NOOP("EditDrumset", "Fa"),
       QT_TRANSLATE_NOOP("EditDrumset", "La"),
       QT_TRANSLATE_NOOP("EditDrumset", "Ti"),
+      QT_TRANSLATE_NOOP("EditDrumset", "Custom")
       };
 
+NoteHead::Group noteHeadGroups[noteheadGroupsGUICount] = {
+            NoteHead::Group::HEAD_NORMAL,
+            NoteHead::Group::HEAD_CROSS,
+            NoteHead::Group::HEAD_DIAMOND,
+            NoteHead::Group::HEAD_TRIANGLE,
+            NoteHead::Group::HEAD_MI,
+            NoteHead::Group::HEAD_SLASH,
+            NoteHead::Group::HEAD_XCIRCLE,
+            NoteHead::Group::HEAD_DO,
+            NoteHead::Group::HEAD_RE,
+            NoteHead::Group::HEAD_FA,
+            NoteHead::Group::HEAD_LA,
+            NoteHead::Group::HEAD_TI,
+            NoteHead::Group::HEAD_CUSTOM
+      };
 //---------------------------------------------------------
 //   operator<
 //---------------------------------------------------------
@@ -68,6 +85,32 @@ bool EditDrumsetTreeWidgetItem::operator<(const QTreeWidgetItem & other) const
 //   EditDrumset
 //---------------------------------------------------------
 
+struct SymbolIcon {
+      SymId id;
+      QIcon icon;
+      SymbolIcon(SymId i, QIcon j)
+            : id(i), icon(j)
+            {}
+
+      static SymbolIcon generateIcon(const SymId& id, double w, double h, double defaultScale)
+            {
+            QIcon icon;
+            QPixmap image(w, h);
+            image.fill(Qt::transparent);
+            QPainter painter(&image);
+            const QRectF& bbox = ScoreFont::fallbackFont()->bbox(id, 1);
+            const qreal actualSymbolScale = std::min(w / bbox.width(), h / bbox.height());
+            qreal mag = std::min(defaultScale, actualSymbolScale);
+            const qreal& xStShift = (w - mag * bbox.width()) / 2 - mag*bbox.left();
+            const qreal& yStShift = (h - mag * bbox.height()) / 2 - mag*bbox.top();
+            const QPointF& stPtPos = QPointF(xStShift, yStShift);
+            ScoreFont::fallbackFont()->draw(id, &painter, mag, stPtPos);
+            painter.end();
+            icon.addPixmap(image);
+            return SymbolIcon(id, icon);
+            }
+};
+
 EditDrumset::EditDrumset(const Drumset* ds, QWidget* parent)
    : QDialog(parent)
       {
@@ -81,11 +124,10 @@ EditDrumset::EditDrumset(const Drumset* ds, QWidget* parent)
       drumNote->setDrawGrid(false);
       drumNote->setReadOnly(true);
 
-      updateList();
+      updatePitchesList();
 
-      noteHead->addItem(tr("invalid"));
-      for (int i = 0; i < int(NoteHead::Group::HEAD_GROUPS) - 2 ; ++i)
-            noteHead->addItem(tr(noteHeadNames[i]));
+      for (int i = 0; i < noteheadGroupsGUICount; ++i)
+            noteHead->addItem(tr(noteHeadNames[i]), int(noteHeadGroups[i]));
 
       connect(pitchList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
          SLOT(itemChanged(QTreeWidgetItem*, QTreeWidgetItem*)));
@@ -102,14 +144,106 @@ EditDrumset::EditDrumset(const Drumset* ds, QWidget* parent)
       pitchList->setColumnWidth(1, 60);
       pitchList->setColumnWidth(2, 30);
 
+      QStringList validNoteheadRanges = { "Noteheads", "Round and square noteheads", "Slash noteheads", "Shape note noteheads" };
+      QSet<QString> excludeSym = { "noteheadParenthesisLeft", "noteheadParenthesisRight", "noteheadParenthesis", "noteheadNull" };
+      QStringList primaryNoteheads = {
+            "noteheadXOrnate",
+            "noteheadXBlack",
+            "noteheadXHalf",
+            "noteheadXWhole",
+            "noteheadXDoubleWhole",
+            "noteheadSlashedBlack1",
+            "noteheadSlashedHalf1",
+            "noteheadSlashedWhole1",
+            "noteheadSlashedDoubleWhole1",
+            "noteheadSlashedBlack2",
+            "noteheadSlashedHalf2",
+            "noteheadSlashedWhole2",
+            "noteheadSlashedDoubleWhole2",
+            "noteheadSquareBlack",
+            "noteheadMoonBlack",
+            "noteheadTriangleUpRightBlack",
+            "noteheadTriangleDownBlack",
+            "noteheadTriangleUpBlack",
+            "noteheadTriangleLeftBlack",
+            "noteheadTriangleRoundDownBlack",
+            "noteheadDiamondBlack",
+            "noteheadDiamondHalf",
+            "noteheadDiamondWhole",
+            "noteheadDiamondDoubleWhole",
+            "noteheadRoundWhiteWithDot",
+            "noteheadVoidWithX",
+            "noteheadHalfWithX",
+            "noteheadWholeWithX",
+            "noteheadDoubleWholeWithX",
+            "noteheadLargeArrowUpBlack",
+            "noteheadLargeArrowUpHalf",
+            "noteheadLargeArrowUpWhole",
+            "noteheadLargeArrowUpDoubleWhole"
+      };
+
+      int w = quarterCmb->iconSize().width()  * qApp->devicePixelRatio();
+      int h = quarterCmb->iconSize().height() * qApp->devicePixelRatio();
+      //default scale is 1.3, will use smaller scale for large noteheads symbols
+      const qreal defaultScale = 1.3 * qApp->devicePixelRatio();
+
+      QList<SymbolIcon> resNoteheads;
+      for (auto symName : primaryNoteheads) {
+             SymId id = Sym::name2id(symName);
+             resNoteheads.append(SymbolIcon::generateIcon(id, w, h, defaultScale));
+             }
+
+      for (QString range : validNoteheadRanges) {
+            for (auto symName : (*MuseScore::bravuraRanges())[range]) {
+                   SymId id = Sym::name2id(symName);
+                   if (!excludeSym.contains(symName) && !primaryNoteheads.contains(symName))
+                         resNoteheads.append(SymbolIcon::generateIcon(id, w, h, defaultScale));
+                   }
+            }
+
+      
+      QComboBox* combos[] = { wholeCmb, halfCmb, quarterCmb, doubleWholeCmb };
+      for (QComboBox* combo : combos) {
+            for (auto si : resNoteheads) {
+                  SymId id = si.id;
+                  QIcon icon = si.icon;
+                  combo->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+                  combo->addItem(icon, Sym::id2userName(id), Sym::id2name(id));
+                  }
+            }
+      wholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(SymId::noteheadWhole)));
+      halfCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(SymId::noteheadHalf)));
+      quarterCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(SymId::noteheadBlack)));
+      doubleWholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(SymId::noteheadDoubleWhole)));
+      
+      connect(customGbox, SIGNAL(toggled(bool)), this, SLOT(customGboxToggled(bool)));
+      connect(quarterCmb, SIGNAL(currentIndexChanged(int)), SLOT(customQuarterChanged(int)));
+            
       MuseScore::restoreGeometry(this);
+
+      Q_ASSERT(pitchList->topLevelItemCount() > 0);
+      pitchList->setCurrentItem(pitchList->topLevelItem(0));
+      pitchList->setFocus();
       }
 
 //---------------------------------------------------------
-//   updateList
+//   customGboxToggled
+//---------------------------------------------------------
+
+void EditDrumset::customGboxToggled(bool checked)
+      {
+      noteHead->setEnabled(!checked);
+      if (checked)
+            noteHead->setCurrentIndex(noteHead->findData(int(NoteHead::Group::HEAD_CUSTOM)));
+      else
+            noteHead->setCurrentIndex(noteHead->findData(int(NoteHead::Group::HEAD_NORMAL)));
+      }
+      
+//---------------------------------------------------------
+//   updatePitchesList
 //---------------------------------------------------------
 
-void EditDrumset::updateList()
+void EditDrumset::updatePitchesList()
       {
       pitchList->clear();
       for (int i = 0; i < 128; ++i) {
@@ -127,8 +261,11 @@ void EditDrumset::updateList()
             }
       pitchList->sortItems(3, Qt::SortOrder::DescendingOrder);
       }
-
-void EditDrumset::updateList2()
+      
+//---------------------------------------------------------
+//   refreshPitchesList
+//---------------------------------------------------------
+void EditDrumset::refreshPitchesList()
       {
       for (int i = 0; i < pitchList->topLevelItemCount(); ++i) {
             QTreeWidgetItem* item = pitchList->topLevelItem(i);
@@ -145,14 +282,46 @@ void EditDrumset::updateList2()
       }
 
 //---------------------------------------------------------
+//   refreshPitchesList
+//---------------------------------------------------------
+
+void EditDrumset::setEnabledPitchControls(bool enable)
+      {
+      customGbox->setEnabled(enable);
+      noteHead->setEnabled(enable);
+      voice->setEnabled(enable);
+      shortcut->setEnabled(enable);
+      staffLine->setEnabled(enable);
+      stemDirection->setEnabled(enable);
+      drumNote->setEnabled(enable);
+      noteHeadGroupLabel->setEnabled(enable);
+      staffLineLabel->setEnabled(enable);
+      stemDirectionLabel->setEnabled(enable);
+      voiceLabel->setEnabled(enable);
+      shortcutLabel->setEnabled(enable);
+      }
+
+//---------------------------------------------------------
 //   nameChanged
 //---------------------------------------------------------
 
 void EditDrumset::nameChanged(const QString& name)
       {
       QTreeWidgetItem* item = pitchList->currentItem();
-      if (item)
+      if (item) {
             item->setText(Column::NAME, name);
+            int pitch = item->data(Column::PITCH, Qt::UserRole).toInt();
+            if (!name.isEmpty()) {
+                  if (!nDrumset.isValid(pitch))
+                        noteHead->setCurrentIndex(0);
+                  }
+            else {
+                  int pitch = item->data(Column::PITCH, Qt::UserRole).toInt();
+                  nDrumset.drum(pitch).name.clear();
+                  }
+            }
+      setEnabledPitchControls(!name.isEmpty());
+      updateExample();
       }
 
 //---------------------------------------------------------
@@ -188,7 +357,7 @@ void EditDrumset::shortcutChanged()
             else
                   item->setText(Column::SHORTCUT, shortcut->currentText());
             }
-      updateList2();
+      refreshPitchesList();
       }
 
 //---------------------------------------------------------
@@ -227,6 +396,43 @@ void EditDrumset::apply()
       }
 
 //---------------------------------------------------------
+//   fillCustomNoteheadsDataFromComboboxes
+//---------------------------------------------------------
+
+void EditDrumset::fillCustomNoteheadsDataFromComboboxes(int pitch)
+      {
+      nDrumset.drum(pitch).notehead = NoteHead::Group::HEAD_CUSTOM;
+      nDrumset.drum(pitch).noteheads[int(NoteHead::Type::HEAD_WHOLE)] = Sym::name2id(wholeCmb->currentData().toString());
+      nDrumset.drum(pitch).noteheads[int(NoteHead::Type::HEAD_QUARTER)] = Sym::name2id(quarterCmb->currentData().toString());
+      nDrumset.drum(pitch).noteheads[int(NoteHead::Type::HEAD_HALF)] = Sym::name2id(halfCmb->currentData().toString());
+      nDrumset.drum(pitch).noteheads[int(NoteHead::Type::HEAD_BREVIS)] = Sym::name2id(doubleWholeCmb->currentData().toString());
+      }
+
+//---------------------------------------------------------
+//   fillNoteheadsComboboxes
+//---------------------------------------------------------
+
+void EditDrumset::fillNoteheadsComboboxes(bool customGroup, int pitch)
+      {
+      if (customGroup) {
+            wholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(nDrumset.noteHeads(pitch, NoteHead::Type::HEAD_WHOLE))));
+            halfCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(nDrumset.noteHeads(pitch, NoteHead::Type::HEAD_HALF))));
+            quarterCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(nDrumset.noteHeads(pitch, NoteHead::Type::HEAD_QUARTER))));
+            doubleWholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(nDrumset.noteHeads(pitch, NoteHead::Type::HEAD_BREVIS))));
+            }
+      else {
+            const auto group = nDrumset.drum(pitch).notehead;
+            if (group == NoteHead::Group::HEAD_INVALID)
+                  return;
+
+            wholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(Note::noteHead(0, group, NoteHead::Type::HEAD_WHOLE))));
+            halfCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(Note::noteHead(0, group, NoteHead::Type::HEAD_HALF))));
+            quarterCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(Note::noteHead(0, group, NoteHead::Type::HEAD_QUARTER))));
+            doubleWholeCmb->setCurrentIndex(quarterCmb->findData(Sym::id2name(Note::noteHead(0, group, NoteHead::Type::HEAD_BREVIS))));
+            }
+      }
+
+//---------------------------------------------------------
 //   itemChanged
 //---------------------------------------------------------
 
@@ -235,7 +441,13 @@ void EditDrumset::itemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previou
       if (previous) {
             int pitch = previous->data(0, Qt::UserRole).toInt();
             nDrumset.drum(pitch).name          = name->text();
-            nDrumset.drum(pitch).notehead      = NoteHead::Group(noteHead->currentIndex() - 1);
+            if (customGbox->isChecked())
+                  fillCustomNoteheadsDataFromComboboxes(pitch);
+            else {
+                  const QVariant currData = noteHead->currentData();
+                  if (currData.isValid())
+                        nDrumset.drum(pitch).notehead = NoteHead::Group(currData.toInt());
+                  }
             nDrumset.drum(pitch).line          = staffLine->value();
             nDrumset.drum(pitch).voice         = voice->currentIndex();
             if (shortcut->currentIndex() == 7)
@@ -247,7 +459,7 @@ void EditDrumset::itemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previou
             }
       if (current == 0)
             return;
-      name->blockSignals(true);
+
       staffLine->blockSignals(true);
       voice->blockSignals(true);
       stemDirection->blockSignals(true);
@@ -261,13 +473,17 @@ void EditDrumset::itemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previou
       qDebug("AFTER %d", nDrumset.voice(pitch));
       stemDirection->setCurrentIndex(int(nDrumset.stemDirection(pitch)));
       NoteHead::Group nh = nDrumset.noteHead(pitch);
-      noteHead->setCurrentIndex(int(nh) + 1);
+      bool isCustomGroup = (nh == NoteHead::Group::HEAD_CUSTOM);
+      if (nDrumset.isValid(pitch))
+            setCustomNoteheadsGUIEnabled(isCustomGroup);
+      noteHead->setCurrentIndex(noteHead->findData(int(nh)));
+      fillNoteheadsComboboxes(isCustomGroup, pitch);
+
       if (nDrumset.shortcut(pitch) == 0)
             shortcut->setCurrentIndex(7);
       else
             shortcut->setCurrentIndex(nDrumset.shortcut(pitch) - 'A');
 
-      name->blockSignals(false);
       staffLine->blockSignals(false);
       voice->blockSignals(false);
       stemDirection->blockSignals(false);
@@ -277,6 +493,17 @@ void EditDrumset::itemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previou
       }
 
 //---------------------------------------------------------
+//   setCustomNoteheadsGUIEnabled
+//---------------------------------------------------------
+void EditDrumset::setCustomNoteheadsGUIEnabled(bool enabled)
+      {
+      customGbox->setChecked(enabled);
+      noteHead->setEnabled(!enabled);
+      if (enabled)
+            noteHead->setCurrentIndex(noteHead->findData(int(NoteHead::Group::HEAD_CUSTOM)));
+      }
+
+//---------------------------------------------------------
 //   valueChanged
 //---------------------------------------------------------
 
@@ -286,7 +513,16 @@ void EditDrumset::valueChanged()
             return;
       int pitch = pitchList->currentItem()->data(Column::PITCH, Qt::UserRole).toInt();
       nDrumset.drum(pitch).name          = name->text();
-      nDrumset.drum(pitch).notehead      = NoteHead::Group(noteHead->currentIndex() - 1);
+      if (customGbox->isChecked() || noteHead->currentIndex() == noteHead->findData(int(NoteHead::Group::HEAD_CUSTOM))) {
+            fillCustomNoteheadsDataFromComboboxes(pitch);
+            setCustomNoteheadsGUIEnabled(true);
+            }
+      else {
+            nDrumset.drum(pitch).notehead = NoteHead::Group(noteHead->currentIndex());
+            fillNoteheadsComboboxes(false, pitch);
+            setCustomNoteheadsGUIEnabled(false);
+            }
+      
       nDrumset.drum(pitch).line          = staffLine->value();
       nDrumset.drum(pitch).voice         = voice->currentIndex();
       nDrumset.drum(pitch).stemDirection = MScore::Direction(stemDirection->currentIndex());
@@ -314,13 +550,7 @@ void EditDrumset::updateExample()
       NoteHead::Group noteHead = nDrumset.noteHead(pitch);
       int voice     = nDrumset.voice(pitch);
       MScore::Direction dir = nDrumset.stemDirection(pitch);
-      bool up;
-      if (dir == MScore::Direction::UP)
-            up = true;
-      else if (dir == MScore::Direction::DOWN)
-            up = false;
-      else
-            up = line > 4;
+      bool up = (MScore::Direction::UP == dir) || (MScore::Direction::AUTO == dir && line > 4);
       Chord* chord = new Chord(gscore);
       chord->setDurationType(TDuration::DurationType::V_QUARTER);
       chord->setStemDirection(dir);
@@ -333,7 +563,9 @@ void EditDrumset::updateExample()
       note->setTpcFromPitch();
       note->setLine(line);
       note->setPos(0.0, gscore->spatium() * .5 * line);
+      note->setHeadType(NoteHead::Type::HEAD_QUARTER);
       note->setHeadGroup(noteHead);
+      note->setCachedNoteheadSym(Sym::name2id(quarterCmb->currentData().toString()));
       chord->add(note);
       Stem* stem = new Stem(gscore);
       stem->setLen((up ? -3.0 : 3.0) * gscore->spatium());
@@ -360,16 +592,19 @@ void EditDrumset::load()
       nDrumset.clear();
       while (e.readNextStartElement()) {
             if (e.name() == "museScore") {
+                  int drumInstrumentIndex = 0;
                   while (e.readNextStartElement()) {
-                        if (e.name() == "Drum")
-                              nDrumset.load(e);
+                        if (e.name() == "Drum") {
+                              nDrumset.load(e, drumInstrumentIndex);
+                              drumInstrumentIndex++;
+                              }
                         else
                               e.unknown();
                         }
                   }
             }
       fp.close();
-      updateList();
+      updatePitchesList();
       }
 
 //---------------------------------------------------------
@@ -410,6 +645,13 @@ void EditDrumset::hideEvent(QHideEvent* event)
       MuseScore::saveGeometry(this);
       QDialog::hideEvent(event);
       }
-
+      
+//---------------------------------------------------------
+//   customQuarterChanged
+//---------------------------------------------------------
+void EditDrumset::customQuarterChanged(int)
+      {
+      updateExample();
+      }
 }
 
index 77df84e..d4d7f53 100644 (file)
@@ -36,12 +36,17 @@ class EditDrumset : public QDialog, private Ui::EditDrumsetBase {
       Drumset  nDrumset;
 
       void apply();
-      void updateList();
-      void updateList2();
+      void updatePitchesList();
+      void refreshPitchesList();
       void updateExample();
 
       virtual void hideEvent(QHideEvent*);
 
+      void fillCustomNoteheadsDataFromComboboxes(int pitch);
+      void setCustomNoteheadsGUIEnabled(bool enabled);
+
+      void setEnabledPitchControls(bool enable);
+      void fillNoteheadsComboboxes(bool customGroup, int pitch);
    private slots:
       void bboxClicked(QAbstractButton* button);
       void itemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
@@ -50,7 +55,9 @@ class EditDrumset : public QDialog, private Ui::EditDrumsetBase {
       void valueChanged();
       void load();
       void save();
-
+      void customGboxToggled(bool);
+      void customQuarterChanged(int);
+      
    public:
       EditDrumset(const Drumset* ds, QWidget* parent = 0);
       const Drumset* drumset() const { return &nDrumset; }
index 478c2b9..fe86846 100644 (file)
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>579</width>
-    <height>332</height>
+    <width>696</width>
+    <height>355</height>
    </rect>
   </property>
   <property name="windowTitle">
      <property name="spacing">
       <number>8</number>
      </property>
-     <item row="0" column="1">
-      <layout class="QGridLayout" name="gridLayout_2">
-       <item row="0" column="0">
-        <widget class="QLabel" name="label">
-         <property name="text">
-          <string>Name:</string>
-         </property>
-         <property name="alignment">
-          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-         </property>
-        </widget>
-       </item>
-       <item row="0" column="1">
-        <widget class="QLineEdit" name="name"/>
-       </item>
-      </layout>
-     </item>
-     <item row="6" column="0" colspan="3">
-      <layout class="QHBoxLayout" name="horizontalLayout">
-       <item>
-        <widget class="QPushButton" name="loadButton">
-         <property name="text">
-          <string>Load...</string>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <widget class="QPushButton" name="saveButton">
-         <property name="text">
-          <string>Save As...</string>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <spacer name="horizontalSpacer">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>40</width>
-           <height>20</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item>
-        <widget class="QDialogButtonBox" name="buttonBox">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-         <property name="standardButtons">
-          <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-         </property>
-        </widget>
-       </item>
-      </layout>
-     </item>
      <item row="0" column="0" rowspan="4">
       <widget class="QTreeWidget" name="pitchList">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
+       <property name="minimumSize">
+        <size>
+         <width>250</width>
+         <height>250</height>
+        </size>
        </property>
        <property name="alternatingRowColors">
         <bool>true</bool>
        <attribute name="headerMinimumSectionSize">
         <number>20</number>
        </attribute>
-       <attribute name="headerStretchLastSection">
-        <bool>true</bool>
-       </attribute>
        <column>
         <property name="text">
          <string>No.</string>
        </item>
       </widget>
      </item>
-     <item row="1" column="1">
-      <layout class="QGridLayout" name="gridLayout">
-       <property name="spacing">
-        <number>1</number>
-       </property>
-       <item row="2" column="0">
-        <widget class="QLabel" name="label_3">
+     <item row="0" column="1">
+      <layout class="QGridLayout" name="gridLayout_2">
+       <item row="0" column="0">
+        <widget class="QLabel" name="label">
          <property name="text">
-          <string>Staff line:</string>
+          <string>Name:</string>
          </property>
          <property name="alignment">
           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
          </property>
         </widget>
        </item>
-       <item row="2" column="1">
-        <widget class="QSpinBox" name="staffLine">
-         <property name="minimum">
-          <number>-12</number>
+       <item row="0" column="1">
+        <widget class="QLineEdit" name="name">
+         <property name="minimumSize">
+          <size>
+           <width>250</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="placeholderText">
+          <string>Enter a name to enable pitch editing</string>
          </property>
         </widget>
        </item>
-       <item row="3" column="0">
-        <widget class="QLabel" name="label_4">
+      </layout>
+     </item>
+     <item row="6" column="0" colspan="3">
+      <layout class="QHBoxLayout" name="horizontalLayout">
+       <item>
+        <widget class="QPushButton" name="loadButton">
          <property name="text">
-          <string>Stem direction:</string>
-         </property>
-         <property name="alignment">
-          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+          <string>Load...</string>
          </property>
         </widget>
        </item>
-       <item row="3" column="1">
-        <widget class="QComboBox" name="stemDirection">
-         <item>
-          <property name="text">
-           <string>Auto</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string>Up</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string>Down</string>
-          </property>
-         </item>
-        </widget>
-       </item>
-       <item row="4" column="0">
-        <widget class="QLabel" name="label_5">
+       <item>
+        <widget class="QPushButton" name="saveButton">
          <property name="text">
-          <string>Default voice:</string>
-         </property>
-         <property name="alignment">
-          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+          <string>Save As...</string>
          </property>
         </widget>
        </item>
-       <item row="4" column="1">
-        <widget class="QComboBox" name="voice">
-         <item>
-          <property name="text">
-           <string>1</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string>2</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string>3</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string>4</string>
-          </property>
-         </item>
-        </widget>
+       <item>
+        <spacer name="horizontalSpacer">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
        </item>
-       <item row="5" column="0">
-        <widget class="QLabel" name="label_6">
-         <property name="text">
-          <string>Shortcut:</string>
+       <item>
+        <widget class="QDialogButtonBox" name="buttonBox">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
          </property>
-         <property name="alignment">
-          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         <property name="standardButtons">
+          <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
          </property>
         </widget>
        </item>
-       <item row="5" column="1">
-        <widget class="QComboBox" name="shortcut">
-         <item>
-          <property name="text">
-           <string notr="true">A</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string notr="true">B</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string notr="true">C</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string notr="true">D</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string notr="true">E</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string notr="true">F</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string notr="true">G</string>
-          </property>
-         </item>
-         <item>
-          <property name="text">
-           <string notr="true">--</string>
-          </property>
-         </item>
+      </layout>
+     </item>
+     <item row="1" column="1">
+      <layout class="QGridLayout" name="gridLayout">
+       <property name="horizontalSpacing">
+        <number>8</number>
+       </property>
+       <property name="verticalSpacing">
+        <number>1</number>
+       </property>
+       <item row="1" column="1">
+        <widget class="QComboBox" name="noteHead">
+         <property name="minimumSize">
+          <size>
+           <width>200</width>
+           <height>0</height>
+          </size>
+         </property>
         </widget>
        </item>
        <item row="1" column="0">
-        <widget class="QLabel" name="label_2">
+        <widget class="QLabel" name="noteHeadGroupLabel">
          <property name="text">
-          <string>Notehead:</string>
+          <string>Noteheads group:</string>
          </property>
          <property name="alignment">
           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
          </property>
         </widget>
        </item>
-       <item row="1" column="1">
-        <widget class="QComboBox" name="noteHead"/>
+       <item row="2" column="0" colspan="2">
+        <widget class="QGroupBox" name="customGbox">
+         <property name="title">
+          <string>Edit noteheads</string>
+         </property>
+         <property name="checkable">
+          <bool>true</bool>
+         </property>
+         <property name="checked">
+          <bool>false</bool>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_5">
+          <item row="2" column="1">
+           <widget class="QComboBox" name="wholeCmb">
+            <property name="minimumSize">
+             <size>
+              <width>250</width>
+              <height>0</height>
+             </size>
+            </property>
+           </widget>
+          </item>
+          <item row="5" column="1">
+           <widget class="QComboBox" name="doubleWholeCmb">
+            <property name="minimumSize">
+             <size>
+              <width>250</width>
+              <height>0</height>
+             </size>
+            </property>
+           </widget>
+          </item>
+          <item row="5" column="0">
+           <widget class="QLabel" name="doubleWholeLbl">
+            <property name="text">
+             <string>Double Whole note</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="0">
+           <widget class="QLabel" name="wholeLbl">
+            <property name="text">
+             <string>Whole note</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="QComboBox" name="quarterCmb">
+            <property name="minimumSize">
+             <size>
+              <width>250</width>
+              <height>0</height>
+             </size>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="0">
+           <widget class="QLabel" name="quarterLbl">
+            <property name="text">
+             <string>Quarter note</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="halfLbl">
+            <property name="text">
+             <string>Half note</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QComboBox" name="halfCmb">
+            <property name="minimumSize">
+             <size>
+              <width>250</width>
+              <height>0</height>
+             </size>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
        </item>
       </layout>
      </item>
          </property>
         </spacer>
        </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_2">
+         <property name="spacing">
+          <number>6</number>
+         </property>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_5">
+           <item>
+            <widget class="QLabel" name="voiceLabel">
+             <property name="text">
+              <string>Default voice:</string>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QComboBox" name="voice">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="minimumSize">
+              <size>
+               <width>50</width>
+               <height>0</height>
+              </size>
+             </property>
+             <item>
+              <property name="text">
+               <string>1</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>2</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>3</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>4</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_3">
+           <property name="spacing">
+            <number>6</number>
+           </property>
+           <item>
+            <widget class="QLabel" name="staffLineLabel">
+             <property name="text">
+              <string>Staff line:</string>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QSpinBox" name="staffLine">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="minimumSize">
+              <size>
+               <width>50</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="minimum">
+              <number>-12</number>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_6">
+           <item>
+            <widget class="QLabel" name="shortcutLabel">
+             <property name="text">
+              <string>Shortcut:</string>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QComboBox" name="shortcut">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="minimumSize">
+              <size>
+               <width>50</width>
+               <height>0</height>
+              </size>
+             </property>
+             <item>
+              <property name="text">
+               <string notr="true">A</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">B</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">C</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">D</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">E</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">F</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">G</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">--</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_4">
+           <item>
+            <widget class="QLabel" name="stemDirectionLabel">
+             <property name="text">
+              <string>Stem direction:</string>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QComboBox" name="stemDirection">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="minimumSize">
+              <size>
+               <width>50</width>
+               <height>0</height>
+              </size>
+             </property>
+             <item>
+              <property name="text">
+               <string>Auto</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>Up</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>Down</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
       </layout>
      </item>
     </layout>
   </customwidget>
  </customwidgets>
  <tabstops>
+  <tabstop>pitchList</tabstop>
   <tabstop>name</tabstop>
   <tabstop>noteHead</tabstop>
-  <tabstop>staffLine</tabstop>
-  <tabstop>stemDirection</tabstop>
-  <tabstop>voice</tabstop>
-  <tabstop>shortcut</tabstop>
+  <tabstop>customGbox</tabstop>
+  <tabstop>quarterCmb</tabstop>
+  <tabstop>halfCmb</tabstop>
+  <tabstop>wholeCmb</tabstop>
+  <tabstop>doubleWholeCmb</tabstop>
   <tabstop>loadButton</tabstop>
   <tabstop>saveButton</tabstop>
-  <tabstop>buttonBox</tabstop>
-  <tabstop>pitchList</tabstop>
  </tabstops>
  <resources/>
  <connections>
index db1ddb8..a9ab0e4 100644 (file)
@@ -47,15 +47,15 @@ EditStaffType::EditStaffType(QWidget* parent, Staff* st)
       // template combo
 
       templateCombo->clear();
-      // statdard group also as fall-back (but excluded by percussion)
+      // standard group also as fall-back (but excluded by percussion)
       bool bStandard    = !(instr != nullptr && instr->drumset() != nullptr);
       bool bPerc        = (instr != nullptr && instr->drumset() != nullptr);
-      bool bTab         = (instr != nullptr && instr->stringData() != nullptr && instr->stringData()->strings() > 0);
+      bool bTab         = (instr != nullptr && instr->stringData() != nullptr && instr->stringData()->frettedStrings() > 0);
       int idx           = 0;
       for (const StaffType& t : StaffType::presets()) {
             if ( (t.group() == StaffGroup::STANDARD && bStandard)
                         || (t.group() == StaffGroup::PERCUSSION && bPerc)
-                        || (t.group() == StaffGroup::TAB && bTab))
+                        || (t.group() == StaffGroup::TAB && bTab && t.lines() <= instr->stringData()->frettedStrings()))
                   templateCombo->addItem(t.name(), idx);
             idx++;
             }
index e8c2723..5b34ad7 100644 (file)
@@ -351,7 +351,11 @@ static QString addPositioningAttributes(Element const* const el, bool isSpanStar
       float relativeY = 0;
       float spatium = el->spatium();
 
-      if (SLine const* const span = dynamic_cast<SLine const* const>(el)) {
+      const SLine* span = nullptr;
+      if (el->isSLine())
+            span = static_cast<const SLine*>(el);
+
+      if (span && !span->spannerSegments().isEmpty()) {
             if (isSpanStart) {
                   const auto seg = span->spannerSegments().first();
                   const auto userOff = seg->userOff();
diff --git a/mscore/extension.cpp b/mscore/extension.cpp
new file mode 100644 (file)
index 0000000..f8532f0
--- /dev/null
@@ -0,0 +1,71 @@
+//=============================================================================
+//  MusE Score
+//  Linux Music Score Editor
+//  $Id:$
+//
+//  Copyright (C) 2018 Werner Schweer and others
+//
+//  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 "extension.h"
+#include "preferences.h"
+#include "libmscore/utils.h"
+
+namespace Ms {
+
+//---------------------------------------------------------
+//   getDirectoriesByType
+//---------------------------------------------------------
+
+QStringList Extension::getDirectoriesByType(const char* type)
+      {
+      QStringList result;
+      QDir d(preferences.myExtensionsPath);
+      for (auto dd : d.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot| QDir::Readable | QDir::NoSymLinks)) {
+            QDir extensionsDir(dd.absoluteFilePath());
+            auto extDir = extensionsDir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot| QDir::Readable | QDir::NoSymLinks, QDir::Name);
+            // take the most recent version only
+            if (!extDir.isEmpty()) {
+                  QString typeDir = QString("%1/%2").arg(extDir.last().absoluteFilePath()).arg(type);
+                  if (QFileInfo(typeDir).exists())
+                        result.append(typeDir);
+                  }
+            }
+      return result;
+      }
+
+//---------------------------------------------------------
+//   isInstalled
+//---------------------------------------------------------
+
+bool Extension::isInstalled(QString extensionId)
+      {
+      QDir extensionDir(QString("%1/%2").arg(preferences.myExtensionsPath).arg(extensionId));
+      return extensionDir.exists();
+      }
+
+//---------------------------------------------------------
+//   getLatestVersion
+//---------------------------------------------------------
+
+QString Extension::getLatestVersion(QString extensionId)
+      {
+      QString result = "0.0";
+      QDir extensionDir(QString("%1/%2").arg(preferences.myExtensionsPath).arg(extensionId));
+      auto extDir = extensionDir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot| QDir::Readable | QDir::NoSymLinks, QDir::Name);
+      if (!extDir.isEmpty())
+            result = extDir.last().fileName();
+      return result;
+      }
+}
diff --git a/mscore/extension.h b/mscore/extension.h
new file mode 100644 (file)
index 0000000..0361b5d
--- /dev/null
@@ -0,0 +1,47 @@
+//=============================================================================
+//  MusE Score
+//  Linux Music Score Editor
+//  $Id:$
+//
+//  Copyright (C) 2018 Werner Schweer and others
+//
+//  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.
+//=============================================================================
+
+#ifndef __EXTENSION_H__
+#define __EXTENSION_H__
+
+namespace Ms {
+
+//---------------------------------------------------------
+//   Extension
+//---------------------------------------------------------
+
+class Extension {
+   public:
+      Extension() {}
+      static constexpr const char* workspacesDir = "workspaces";
+      static constexpr const char* sfzsDir       = "sfzs";
+      static constexpr const char* soundfontsDir = "soundfonts";
+      static constexpr const char* templatesDir  = "templates";
+      static constexpr const char* instrumentsDir = "instruments";
+
+      static QStringList getDirectoriesByType(const char* type);
+      static bool isInstalled(QString extensionId);
+      static QString getLatestVersion(QString extensionId);
+      };
+
+
+} // namespace Ms
+#endif
+
index c90b378..add6aef 100644 (file)
@@ -73,6 +73,7 @@
 #include "synthesizer/msynthesizer.h"
 #include "svggenerator.h"
 #include "scorePreview.h"
+#include "extension.h"
 
 #ifdef OMR
 #include "omr/omr.h"
@@ -432,7 +433,7 @@ bool MuseScore::saveFile(Score* score)
             return false;
             }
       score->setCreated(false);
-      setWindowTitle(QString(MUSESCORE_NAME_VERSION) + ": " + score->name());
+      updateWindowTitle(score);
       int idx = scoreList.indexOf(score);
       tab1->setTabText(idx, score->name());
       if (tab2)
@@ -480,8 +481,9 @@ QString MuseScore::createDefaultName() const
 
 void MuseScore::updateNewWizard()
       {
-      if (newWizard != 0)
+      if (newWizard == nullptr)
             newWizard = new NewWizard(this);
+      newWizard->updateValues();
       }
 
 //---------------------------------------------------------
@@ -491,8 +493,7 @@ void MuseScore::updateNewWizard()
 
 void MuseScore::newFile()
       {
-      if (newWizard == 0)
-            newWizard = new NewWizard(this);
+      updateNewWizard();
       newWizard->restart();
       if (newWizard->exec() != QDialog::Accepted)
             return;
@@ -1852,7 +1853,7 @@ bool MuseScore::saveAs(Score* cs, bool saveCopy, const QString& path, const QStr
 
             if (rv && !saveCopy) {
                   cs->fileInfo()->setFile(fn);
-                  setWindowTitle(QString(MUSESCORE_NAME_VERSION) + ": " + cs->name());
+                  updateWindowTitle(cs);
                   cs->undo()->setClean();
                   dirtyChanged(cs);
                   cs->setCreated(false);
@@ -2088,6 +2089,15 @@ void importSoundfont(QString name)
       }
 
 //---------------------------------------------------------
+//   importExtension
+//---------------------------------------------------------
+
+void importExtension(QString name)
+      {
+      mscore->importExtension(name);
+      }
+
+//---------------------------------------------------------
 //   readScore
 ///   Import file \a name
 //---------------------------------------------------------
@@ -2110,6 +2120,10 @@ Score::FileError readScore(Score* score, QString name, bool ignoreVersionError)
             importSoundfont(name);
             return Score::FileError::FILE_IGNORE_ERROR;
             }
+      else if (suffix == "muxt") {
+           importExtension(name);
+           return Score::FileError::FILE_IGNORE_ERROR;
+           }
       else {
             // typedef Score::FileError (*ImportFunction)(Score*, const QString&);
             struct ImportDef {
index 63bcce1..5f5992d 100644 (file)
@@ -683,7 +683,14 @@ void ScoreView::fotoContextPopup(QContextMenuEvent* ev)
 
 void ScoreView::fotoModeCopy()
       {
+#if defined(Q_OS_WIN)
+      // See https://bugreports.qt.io/browse/QTBUG-11463
+      // while transparent copy/paste works fine inside musescore,
+      // it does not paste into other programs in Windows though
+      bool transparent = false;
+#else
       bool transparent = preferences.pngTransparent;
+#endif
       double convDpi   = preferences.pngResolution;
       double mag       = convDpi / DPI;
 
index f5a6fe9..7cefeae 100644 (file)
@@ -241,75 +241,68 @@ int GuitarPro::readInt()
 void GuitarPro::initGuitarProDrumset()
       {
       gpDrumset = new Drumset;
-      for (int i = 0; i < 128; ++i) {
-            gpDrumset->drum(i).notehead = NoteHead::Group::HEAD_INVALID;
-            gpDrumset->drum(i).line     = 0;
-            gpDrumset->drum(i).shortcut = 0;
-            gpDrumset->drum(i).voice    = 0;
-            gpDrumset->drum(i).stemDirection = MScore::Direction::UP;
-            }
       // new drumset determined via guitar pro (third argument specifies position on staff, 10 = C3, 9 = D3, 8 = E3,...)
-      gpDrumset->drum(27) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Q"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(28) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Slap"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(29) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Scratch Push"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(30) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Scratch Pull"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(31) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Sticks"), NoteHead::Group::HEAD_CROSS, 3, MScore::Direction::UP);
-      gpDrumset->drum(32) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Square Click"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(33) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Metronome Click"), NoteHead::Group::HEAD_CROSS, 3, MScore::Direction::UP);
-      gpDrumset->drum(34) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Metronome Bell"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(35) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Acoustic Bass Drum"), NoteHead::Group::HEAD_NORMAL, 7, MScore::Direction::UP);
-      gpDrumset->drum(36) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Bass Drum 1"), NoteHead::Group::HEAD_NORMAL, 7, MScore::Direction::UP);
-      gpDrumset->drum(37) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Side Stick"), NoteHead::Group::HEAD_CROSS, 3, MScore::Direction::UP);
-      gpDrumset->drum(38) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Acoustic Snare"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(39) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Hand Clap"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(40) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Electric Snare"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(41) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Floor Tom"), NoteHead::Group::HEAD_NORMAL, 6, MScore::Direction::UP);
-      gpDrumset->drum(42) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Closed Hi-Hat"), NoteHead::Group::HEAD_CROSS, -1, MScore::Direction::UP);
-      gpDrumset->drum(43) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Floor Tom"), NoteHead::Group::HEAD_NORMAL, 5, MScore::Direction::UP);
-      gpDrumset->drum(44) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Pedal Hi-Hat"), NoteHead::Group::HEAD_CROSS, 7, MScore::Direction::UP);
-      gpDrumset->drum(45) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Tom"), NoteHead::Group::HEAD_NORMAL, 4, MScore::Direction::UP);
-      gpDrumset->drum(46) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Hi-Hat"), NoteHead::Group::HEAD_CROSS, -1, MScore::Direction::UP);
-      gpDrumset->drum(47) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low-Mid Tom"), NoteHead::Group::HEAD_NORMAL, 2, MScore::Direction::UP);
-      gpDrumset->drum(48) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Hi-Mid Tom"), NoteHead::Group::HEAD_NORMAL, 1, MScore::Direction::UP);
-      gpDrumset->drum(49) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Crash Cymbal 1"), NoteHead::Group::HEAD_DIAMOND, -1, MScore::Direction::UP);
-      gpDrumset->drum(50) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Tom"), NoteHead::Group::HEAD_NORMAL, 0, MScore::Direction::UP);
-      gpDrumset->drum(51) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Cymbal 1"), NoteHead::Group::HEAD_DIAMOND, -1, MScore::Direction::UP);
-      gpDrumset->drum(52) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Chinese Cymbal"), NoteHead::Group::HEAD_CROSS, -1, MScore::Direction::UP);
-      gpDrumset->drum(53) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Bell"), NoteHead::Group::HEAD_DIAMOND, -1, MScore::Direction::UP);
-      gpDrumset->drum(54) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Tambourine"), NoteHead::Group::HEAD_CROSS, 2, MScore::Direction::UP);
-      gpDrumset->drum(55) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Splash Cymbal"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(56) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Cowbell"), NoteHead::Group::HEAD_NORMAL, 1, MScore::Direction::UP);
-      gpDrumset->drum(57) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Crash Cymbal 2"), NoteHead::Group::HEAD_DIAMOND, -1, MScore::Direction::UP);
-      gpDrumset->drum(58) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Vibraslap"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(59) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Cymbal 2"), NoteHead::Group::HEAD_DIAMOND, -1, MScore::Direction::UP);
-      gpDrumset->drum(60) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Hi Bongo"), NoteHead::Group::HEAD_NORMAL, 8, MScore::Direction::UP);
-      gpDrumset->drum(61) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Bongo"), NoteHead::Group::HEAD_NORMAL, 9, MScore::Direction::UP);
-      gpDrumset->drum(62) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Mute Hi Conga"), NoteHead::Group::HEAD_CROSS, 5, MScore::Direction::UP);
-      gpDrumset->drum(63) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Hi Conga"), NoteHead::Group::HEAD_CROSS, 4, MScore::Direction::UP);
-      gpDrumset->drum(64) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Conga"), NoteHead::Group::HEAD_CROSS, 6, MScore::Direction::UP);
-      gpDrumset->drum(65) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Timbale"), NoteHead::Group::HEAD_CROSS, 8, MScore::Direction::UP);
-      gpDrumset->drum(66) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Timbale"), NoteHead::Group::HEAD_CROSS, 9, MScore::Direction::UP);
-      gpDrumset->drum(67) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Agogo"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(68) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Agogo"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(69) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Cabasa"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(70) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Maracas"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(71) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Short Whistle"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(72) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Long Whistle"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(73) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Short Güiro"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(74) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Long Güiro"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(75) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Claves"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(76) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Hi Wood Block"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(77) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Wood Block"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(78) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Mute Cuica"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(79) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Cuica"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(80) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Mute Triangle"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(81) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Triangle"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(82) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Shaker"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(83) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Sleigh Bell"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(84) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Bell Tree"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(85) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Castanets"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(86) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Mute Surdo"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
-      gpDrumset->drum(87) = DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Surdo"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP);
+      gpDrumset->addDrumInstrument(27, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Q"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(28, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Slap"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(29, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Scratch Push"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(30, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Scratch Pull"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(31, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Sticks"), NoteHead::Group::HEAD_CROSS, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(32, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Square Click"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(33, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Metronome Click"), NoteHead::Group::HEAD_CROSS, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(34, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Metronome Bell"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(35, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Acoustic Bass Drum"), NoteHead::Group::HEAD_NORMAL, 7, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(36, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Bass Drum 1"), NoteHead::Group::HEAD_NORMAL, 7, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(37, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Side Stick"), NoteHead::Group::HEAD_CROSS, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(38, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Acoustic Snare"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(39, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Hand Clap"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(40, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Electric Snare"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(41, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Floor Tom"), NoteHead::Group::HEAD_NORMAL, 6, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(42, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Closed Hi-Hat"), NoteHead::Group::HEAD_CROSS, -1, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(43, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Floor Tom"), NoteHead::Group::HEAD_NORMAL, 5, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(44, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Pedal Hi-Hat"), NoteHead::Group::HEAD_CROSS, 7, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(45, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Tom"), NoteHead::Group::HEAD_NORMAL, 4, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(46, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Hi-Hat"), NoteHead::Group::HEAD_CROSS, -1, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(47, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low-Mid Tom"), NoteHead::Group::HEAD_NORMAL, 2, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(48, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Hi-Mid Tom"), NoteHead::Group::HEAD_NORMAL, 1, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(49, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Crash Cymbal 1"), NoteHead::Group::HEAD_DIAMOND, -1, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(50, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Tom"), NoteHead::Group::HEAD_NORMAL, 0, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(51, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Cymbal 1"), NoteHead::Group::HEAD_DIAMOND, -1, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(52, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Chinese Cymbal"), NoteHead::Group::HEAD_CROSS, -1, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(53, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Bell"), NoteHead::Group::HEAD_DIAMOND, -1, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(54, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Tambourine"), NoteHead::Group::HEAD_CROSS, 2, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(55, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Splash Cymbal"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(56, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Cowbell"), NoteHead::Group::HEAD_NORMAL, 1, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(57, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Crash Cymbal 2"), NoteHead::Group::HEAD_DIAMOND, -1, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(58, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Vibraslap"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(59, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Ride Cymbal 2"), NoteHead::Group::HEAD_DIAMOND, -1, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(60, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Hi Bongo"), NoteHead::Group::HEAD_NORMAL, 8, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(61, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Bongo"), NoteHead::Group::HEAD_NORMAL, 9, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(62, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Mute Hi Conga"), NoteHead::Group::HEAD_CROSS, 5, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(63, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Open Hi Conga"), NoteHead::Group::HEAD_CROSS, 4, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(64, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Conga"), NoteHead::Group::HEAD_CROSS, 6, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(65, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Timbale"), NoteHead::Group::HEAD_CROSS, 8, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(66, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Timbale"), NoteHead::Group::HEAD_CROSS, 9, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(67, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "High Agogo"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(68, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Agogo"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(69, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Cabasa"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(70, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Maracas"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(71, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Short Whistle"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(72, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Long Whistle"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(73, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Short Güiro"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(74, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Long Güiro"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(75, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Claves"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(76, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Hi Wood Block"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+      gpDrumset->addDrumInstrument(77, DrumInstrument(QT_TRANSLATE_NOOP("drumset", "Low Wood Block"), NoteHead::Group::HEAD_NORMAL, 3, MScore::Direction::UP));
+    &nbs