Skip to content

Commit

Permalink
Made LilyPond discoverable from several locations via search paths. F…
Browse files Browse the repository at this point in the history
…ixed a minor warning about notes. Updated documentation.
  • Loading branch information
MarcTheSpark committed Jan 18, 2023
1 parent f66b93d commit ecada83
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 53 deletions.
3 changes: 3 additions & 0 deletions docs/narrative/LilyWarning.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/narrative/OpenLilyPondAnyway.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/narrative/SecuritySettings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 39 additions & 25 deletions docs/narrative/easy_setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,46 +85,60 @@ Choose 3.4, and click install.
Step 3: Install FluidSynth
--------------------------

`FluidSynth <https://www.fluidsynth.org/>`_ is the synthesizer that SCAMP uses behind the scenes to play notes. On Mac
and Windows, it comes bundled with SCAMP, and so does not need to be separately installed.

.. note::

On new Apple Silicon Macs, you will need to install Rosetta 2 in order for the bundled Intel-based copy of
FluidSynth to work. You can do this by opening a terminal and running ``softwareupdate --install-rosetta``. (See
`this link <https://osxdaily.com/2020/12/04/how-install-rosetta-2-apple-silicon-mac/>`_ for more details.)
`FluidSynth <https://www.fluidsynth.org/>`_ is the synthesizer that SCAMP uses behind the scenes to play notes. On
intel-based Macs and Windows, it comes bundled with SCAMP, and so does not need to be separately installed.

On the new Apple Silicon Macs, the bundled intel-based copy of of FluidSynth will not work. I'm working on bundling a
version that will work, but in the meantime, you can install FluidSynth via `homebrew <https://brew.sh/>`_. Simply
install homebrew via the script on the website, and then run ``brew install fluid-synth`` from a terminal.

On Linux, the easiest way to install FluidSynth is through your distro's package manager. On Debian-based distros,
simply open a terminal and type:

.. code::
sudo apt install fluidsynth
simply open a terminal and type ``sudo apt install fluidsynth``.


Step 4: Install LilyPond
------------------------

LilyPond is a program for engraving music notation that SCAMP uses to generate PDFs of the music you create. You can
download and install it `here <http://lilypond.org/download.html>`_. If you're on a Mac, after you download and unpack
the application, be sure to drag it into the Applications folder, since this is where SCAMP expects to find it.
download it from `here <http://lilypond.org/download.html>`_. If you're on a Mac, after unzipping the download, place it
in the Applications folder so that SCAMP can find it.

.. note::
Having done this, however, you may need to reassure your computer that LilyPond is not malicious.

If you are running MacOS 10.15 (Catalina) or later, the current official release of LilyPond will not work
for you, since it is a 32-bit application, and Catalina abandons 32-bit support. However, you can download an
unofficial 64-bit build `here <https://gitlab.com/marnen/lilypond-mac-builder/-/package_files/9872804/download>`__.
On Windows, you may see an unnerving dialog about allowing an "unknown publisher to make changes". Just click yes
and proceed with the installation:

.. image:: WindowsLilypondUnnerving.png
:width: 70%
:align: center

.. note::
On a Mac, when you first try to generate notation using LilyPond, you may see a dialog come up saying that lilypond
cannot be opened because of an unidentified developer (first image below). You have two options: reverse course and
install lilypond via homebrew (``brew install lilypond``, similar to fluidsynth above), or go through an irritating
sequence of steps to convince your computer that LilyPond is okay.

If you choose the latter, click "cancel", and then open up your security and privacy settings (second image).
You should see an option to "allow" lilypond to run. Then, the next time you try to generate notation using LilyPond,
you should get a different dialog with the option of opening it (third image).

+--------+--------+--------+
||pic1c| | |pic2c|| |pic3c||
+--------+--------+--------+

.. |pic1c| image:: LilyWarning.png
:width: 100%

.. |pic2c| image:: SecuritySettings.png
:width: 100%

.. |pic3c| image:: OpenLilyPondAnyway.png
:width: 100%

On Windows, you may see an unnerving dialog about allowing an "unknown publisher to make changes". Just click yes
and proceed with the installation:
You will then probably have to follow this sequence one more time for a program called `gs`, which LilyPond depends on.

.. image:: WindowsLilypondUnnerving.png
:width: 70%
:align: center
Note that this whole irritating sequence is Apple's fault: in order to become an "identified developer" you have no
choice but to pay Apple money, and the developers of LilyPond are volunteers who understandably don't want to pay
Apple to offer you free software.


Testing it Out
Expand Down
2 changes: 1 addition & 1 deletion docs/narrative/experienced_setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ that SCAMP has been built to be compatible with. You are free to use a newer ver
there will be unexpected errors due to changes in the abjad API.

After installing `abjad`, you will also need to `download and install LilyPond <https://lilypond.org/>`_,
since `abjad` relies upon it.
since `abjad` relies upon it. You may run into security issues, which are further explained in :doc:`easy_setup`.

Installing scamp_extensions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
6 changes: 3 additions & 3 deletions docs/narrative/note_properties.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,9 @@ following:

.. code-block:: python3
inst.play_chord([60, 65], 1, 1, "regular/x")
inst.play_chord([60, 65], 1, 1, "notehead: regular/x")
inst.play_chord([60, 65], 1, 1, {"noteheads": ["regular", "x"]})
inst.play_chord([60, 65], 1, 1, "normal/x")
inst.play_chord([60, 65], 1, 1, "notehead: normal/x")
inst.play_chord([60, 65], 1, 1, {"noteheads": ["normal", "x"]})
Available noteheads (based on the MusicXML standard) are:
"normal", "diamond", "harmonic", "harmonic-black", "harmonic-mixed", "triangle",
Expand Down
1 change: 1 addition & 0 deletions examples/Tutorial/25_MIDI_in_out.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def midi_callback(midi_message):
notes_started[pitch] = piano.start_note(pitch + 7, volume/127)
elif (volume == 0 and 144 <= code <= 159 or 128 <= code <= 143) and pitch in notes_started:
notes_started[pitch].end()
del notes_started[pitch]


s.register_midi_listener(0, midi_callback)
Expand Down
57 changes: 39 additions & 18 deletions scamp/_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
# If not, see <http://www.gnu.org/licenses/>. #
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #

from .settings import playback_settings
from .settings import playback_settings, engraving_settings
from ._package_info import ABJAD_VERSION, ABJAD_MIN_VERSION
import logging
import os
import platform

from pathlib import Path

try:
if playback_settings.try_system_fluidsynth_first:
Expand Down Expand Up @@ -95,22 +95,22 @@
logging.warning("python-rtmidi was not found; streaming midi input / output will not be available.")


# On Mac and Windows, try to add LilyPond to PATH, if it is installed, so that abjad can just work.
# This is hardly fool-proof, but should work if the user just installed LilyPond in the standard way
if platform.system() == "Darwin":
if "/usr/local/bin" not in os.environ["PATH"]:
os.environ["PATH"] += ":/usr/local/bin"
if os.path.exists("/Applications/LilyPond.app/Contents/Resources/bin"):
os.environ["PATH"] += ":/Applications/LilyPond.app/Contents/Resources/bin"
elif platform.system() == "Windows":
if os.path.exists(r"C:\Program Files (x86)\LilyPond\usr\bin"):
if not os.environ["PATH"].endswith(";"):
os.environ["PATH"] += ";"
os.environ["PATH"] += r"C:\Program Files (x86)\LilyPond\usr\bin;"
elif os.path.exists(r"C:\Program Files\LilyPond\usr\bin"):
if not os.environ["PATH"].endswith(";"):
os.environ["PATH"] += ";"
os.environ["PATH"] += r"C:\Program Files\LilyPond\usr\bin;"
def find_lilypond():
# Look for the lilypond binary and return the directory in which it resides
# searches in the platform-specific paths defined in engraving_settings.lilypond_search_paths, and returns
# the first match
if platform.system() not in engraving_settings.lilypond_search_paths:
return None
logging.warning("Searching for LilyPond binary (this may take a while and is normal on first run)")
for lilypond_search_path in engraving_settings.lilypond_search_paths[platform.system()]:
lsp = Path(lilypond_search_path).expanduser()
if not lsp.exists():
continue
for potential_lp_binary in lsp.rglob("lilypond.exe" if platform.system() == "Windows" else "lilypond"):
if not potential_lp_binary.is_file():
continue
logging.warning(f"LilyPond binary found at {str(potential_lp_binary.parent.resolve())}")
return str(potential_lp_binary.parent.resolve())


_abjad_warning_given = False
Expand Down Expand Up @@ -149,6 +149,27 @@ def abjad():
.format(abjad_library.__version__, ABJAD_VERSION, ABJAD_VERSION)
)
_abjad_warning_given = True

# add lilypond to PATH if needed
if engraving_settings.lilypond_dir is None and platform.system() in ("Darwin", "Windows") \
or engraving_settings.lilypond_dir is not None and not (
Path(engraving_settings.lilypond_dir) / "lilypond").exists():
# Search for a lilypond binary if:
# - there's no record of where lilypond should be found and we're on mac or windows (needs to be explicitly
# added, unlike in Linux)
# - there is a record, but the binary doesn't seem to be there
engraving_settings.lilypond_dir = find_lilypond()
engraving_settings.make_persistent()

if engraving_settings.lilypond_dir is not None:
# There's a lilypond binary to add to PATH
if platform.system() == "Windows":
if not os.environ["PATH"].endswith(";"):
os.environ["PATH"] += ";"
os.environ["PATH"] += f"{engraving_settings.lilypond_dir}"
else:
os.environ["PATH"] += f":{engraving_settings.lilypond_dir}"

return abjad_library


Expand Down
2 changes: 1 addition & 1 deletion scamp/_package_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

name = "scamp"

version = "0.9.1.post5"
version = "0.9.1.post8"

author = "Marc Evanstein"

Expand Down
2 changes: 1 addition & 1 deletion scamp/_thirdparty/fluidsynth.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def _try_to_load_system_fl_library():

# first, in case we're using a homebrew install on an arm mac, find_library doesn't work. So let's manually search
# for a libfluidsynth in /opt/homebrew/Cellar, which is where homebrew is installed on the arm macs
for p in pathlib.Path("/opt/homebrew/Cellar").rglob("libfluidsynth*.dylib"):
for p in pathlib.Path("/opt/homebrew/lib").rglob("libfluidsynth*.dylib"):
# just take the first one found; seems like they are aliases anyway
lib = p
break
Expand Down
4 changes: 2 additions & 2 deletions scamp/instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,14 +900,14 @@ def end_note(self, note_id: Union[int, 'NoteHandle'] = None) -> None:
if note_id is not None:
# as specific note_id has been given, so it had better belong to a currently playing note!
if note_id not in self._note_info_by_id:
logging.warning("Tried to end a note that was never started!")
logging.warning("Tried to end a note that was never started (or already ended)!")
return
elif len(self._note_info_by_id) > 0:
# no specific id was given, so end the oldest note
# (note that ids just count up, so the lowest active id is the oldest)
note_id = min(self._note_info_by_id.keys())
else:
logging.warning("Tried to end a note that was never started!")
logging.warning("Tried to end a note that was never started (or already ended)!")
return

note_info = self._note_info_by_id[note_id]
Expand Down
10 changes: 8 additions & 2 deletions scamp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,12 @@ class EngravingSettings(_ScampSettings):
"show_music_xml_command_line": "auto",
"show_microtonal_annotations": False,
"microtonal_annotation_digits": 2,
"export_note_velocities_to_xml": False
"export_note_velocities_to_xml": False,
"lilypond_dir": None,
"lilypond_search_paths": {
"Darwin": ["/Applications", "~/Applications", "/usr/local/bin", "/opt/homebrew/bin"],
"Windows": [r"C:\Program Files (x86)", r"C:\Program Files"]
}
}

_settings_name = "Engraving settings"
Expand All @@ -590,7 +595,8 @@ def __init__(self, settings_dict: dict = None, suppress_warnings: bool = False):
self.default_spelling_policy = self.ignore_empty_parts = self.pad_incomplete_parts = \
self.show_music_xml_command_line = self.show_microtonal_annotations = self.microtonal_annotation_digits = \
self.allow_duple_tuplets_in_compound_time = self.clefs_by_instrument = self.clef_pitch_centers = \
self.clef_selection_policy = self.export_note_velocities_to_xml = None
self.clef_selection_policy = self.export_note_velocities_to_xml = self.lilypond_dir = \
self.lilypond_search_paths = None
self.glissandi: GlissandiSettings = None
self.tempo: TempoSettings = None
super().__init__(settings_dict, suppress_warnings)
Expand Down

0 comments on commit ecada83

Please sign in to comment.