Skip to content

Commit

Permalink
version 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroen authored and cran-robot committed Feb 22, 2024
0 parents commit 4323b43
Show file tree
Hide file tree
Showing 22 changed files with 907 additions and 0 deletions.
30 changes: 30 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Package: fluidsynth
Type: Package
Title: Read and Play Digital Music (MIDI)
Version: 1.0.0
Authors@R: c(
person("Jeroen", "Ooms", role = c("aut", "cre"), email = "jeroen@berkeley.edu",
comment = c(ORCID = "0000-0002-4035-0289")),
person("S. Christian Collins", role = "cph", comment = "author of generaluser-gs
soundbank"))
Description: Bindings to 'libfluidsynth' to parse and synthesize MIDI files. It can
read MIDI into a data frame, play it on the local audio device, or convert into
an audio file.
License: MIT + file LICENSE
Encoding: UTF-8
RoxygenNote: 7.3.1
Imports: av, rappdirs
SystemRequirements: fluidsynth: fluidsynth-devel (rpm) or
libfluidsynth-dev (deb). On Linux you also need a soundfont
provided by 'fluid-soundfont-gm' (Fedora) or 'sf3-soundfont-gm'
(Debian/Ubuntu)
URL: https://docs.ropensci.org/fluidsynth/
https://ropensci.r-universe.dev/fluidsynth
BugReports: https://github.com/ropensci/fluidsynth/issues
NeedsCompilation: yes
Packaged: 2024-02-19 23:14:30 UTC; jeroen
Author: Jeroen Ooms [aut, cre] (<https://orcid.org/0000-0002-4035-0289>),
S. Christian Collins [cph] (author of generaluser-gs soundbank)
Maintainer: Jeroen Ooms <jeroen@berkeley.edu>
Repository: CRAN
Date/Publication: 2024-02-21 15:40:02 UTC
2 changes: 2 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
YEAR: 2024
COPYRIGHT HOLDER: Jeroen Ooms
21 changes: 21 additions & 0 deletions MD5
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
4f13dcd6b0771828048e22f50a5f08f3 *DESCRIPTION
66b8d6ae536d10339b4a28d0c792c08c *LICENSE
62b15399b04abb67f3f82b8371ac79a8 *NAMESPACE
0a88f8da8f2f45dade47f867a98da506 *R/init.R
4218fb4c765df88a99385a871148a3d9 *R/midi.R
5c75da8b3f770f79d8cf713ada3a3c43 *R/settings.R
0dfd0e59c5e90f29a7b9e671d270de8e *R/soundfonts.R
332211638884147974048affa6ca853e *configure
7c46d904a1cf40891dd1f1888727ec30 *inst/midi/All_Night_Long.mid
29d3b2e51d8d5c89b57ca1553d44050b *inst/midi/README.txt
c9fbecdaf525751144852fa285681db8 *man/fluidsynth.Rd
0336e0682a115aae65d1cffcd95addd2 *man/fluidsynth_settings.Rd
f38b15ade099ab1ecb8705c4ecb6e68e *man/soundfonts.Rd
c34834960e96714a1edfa0a9b4eb9551 *src/Makevars.in
2077d6fdae6bede2de6ee21c6f5bc79e *src/Makevars.ucrt
34b7f6afc470087befc51974f2a0d85c *src/Makevars.win
004408fb0b8d1e04d3e18a280e013a7f *src/init.c
641e310d83fd1b3b6c2ff4e7266ec3ff *src/playmidi.c
bf39bafc7efb1aaf140b91f86899691a *src/readmidi.c
cf9567ef3d8ab4f30d5667a2ea900376 *src/settings.c
a8b9dcfcc803af566c89d1e01ffa4bc6 *tools/winlibs.R
18 changes: 18 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by roxygen2: do not edit by hand

export(demo_midi)
export(fluidsynth_setting_default)
export(fluidsynth_setting_list)
export(fluidsynth_setting_options)
export(libfluidsynth_version)
export(midi_convert)
export(midi_play)
export(midi_read)
export(soundfont_download)
export(soundfont_path)
useDynLib(fluidsynth,C_fluidsynth_get_default)
useDynLib(fluidsynth,C_fluidsynth_list_options)
useDynLib(fluidsynth,C_fluidsynth_list_settings)
useDynLib(fluidsynth,C_fluidsynth_version)
useDynLib(fluidsynth,C_midi_play)
useDynLib(fluidsynth,C_midi_read)
3 changes: 3 additions & 0 deletions R/init.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.onAttach <- function(libname, pkg){
packageStartupMessage(paste("Using libfluidsynth", libfluidsynth_version()))
}
95 changes: 95 additions & 0 deletions R/midi.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#' Play or convert a midi file
#'
#' Play a midi file to your audio device, render it to a file, or parse the raw data.
#' Additional settings can be specified, see [fluidsynth_setting_list] for available
#' options.
#'
#' The `midi_convert` function internally uses fluidsynth to generate a raw wav file,
#' and then [av::av_audio_convert()] to convert into the requested about format. See
#' [av::av_muxers()] for supported output formats and their corresponding file extension.
#'
#' You need a soundfont to synthesize midi, see the [soundfonts] page. On Linux you may
#' also need to specify an `audio.driver` that works for your hardware, although on
#' recent distributions the defaults generally work.
#'
#' @useDynLib fluidsynth C_midi_play
#' @export
#' @rdname fluidsynth
#' @family fluidsynth
#' @returns midi_read returns data frame with midi events.
#' @param midi path to the midi file
#' @param soundfont path to the soundfont
#' @param settings a named vector with additional settings from [fluidsynth_setting_list()]
#' @param audio.driver which audio driver to use,
#' see [fluidsynth docs](https://www.fluidsynth.org/api/CreatingAudioDriver.html)
#' @examples
#' df <- midi_read(demo_midi())
midi_play <- function(midi = demo_midi(), soundfont = soundfont_path(), audio.driver = NULL,
settings = list(), verbose = interactive()){
midi <- normalizePath(midi, mustWork = TRUE)
soundfont <- normalizePath(soundfont, mustWork = TRUE)
verbose <- as.logical(verbose)
audio.driver <- as.character(audio.driver)
settings <- validate_fluidsynth_settings(settings)
.Call(C_midi_play, midi, soundfont, audio.driver, settings, verbose)
invisible()
}

#' @rdname fluidsynth
#' @export
#' @param output filename of the output. The out
#' @param verbose print some progress status to the terminal
midi_convert <- function(midi = demo_midi(), soundfont = soundfont_path(), output = 'output.mp3',
settings = list(), verbose = interactive()){
midi <- normalizePath(midi, mustWork = TRUE)
soundfont <- normalizePath(soundfont, mustWork = TRUE)
tmp <- structure(tempfile(fileext = '.wav'), class = 'outputfile')
on.exit(unlink(tmp))
verbose <- as.logical(verbose)
settings <- validate_fluidsynth_settings(settings)
.Call(C_midi_play, midi, soundfont, tmp, settings, verbose)
av::av_audio_convert(tmp, output, verbose = verbose)
}

#' @export
#' @rdname fluidsynth
#' @useDynLib fluidsynth C_midi_read
midi_read <- function(midi = demo_midi(), verbose = FALSE){
midi <- normalizePath(midi, mustWork = TRUE)
verbose <- as.logical(verbose)
out <- .Call(C_midi_read, midi, verbose)
names(out) <- c("tick", "channel", "event", "param1", "param2")
out$event <- factor(out$event, levels = c(midi_events), labels = names(midi_events))
data.frame(out)
}

#' @export
#' @rdname fluidsynth
demo_midi <- function(){
list.files(system.file(package = 'fluidsynth', 'midi'), pattern = '\\.mid$', full.names = TRUE)
}

# Values from: https://github.com/FluidSynth/fluidsynth/blob/master/src/midi/fluid_midi.h#L46C1-L71C27
midi_events <- c(
NOTE_OFF = 0x80,
NOTE_ON = 0x90,
KEY_PRESSURE = 0xa0,
CONTROL_CHANGE = 0xb0,
PROGRAM_CHANGE = 0xc0,
CHANNEL_PRESSURE = 0xd0,
PITCH_BEND = 0xe0,
MIDI_SYSEX = 0xf0,
MIDI_TIME_CODE = 0xf1,
MIDI_SONG_POSITION = 0xf2,
MIDI_SONG_SELECT = 0xf3,
MIDI_TUNE_REQUEST = 0xf6,
MIDI_EOX = 0xf7,
MIDI_SYNC = 0xf8,
MIDI_TICK = 0xf9,
MIDI_START = 0xfa,
MIDI_CONTINUE = 0xfb,
MIDI_STOP = 0xfc,
MIDI_ACTIVE_SENSING = 0xfe,
MIDI_SYSTEM_RESET = 0xff,
MIDI_META_EVENT = 0xff
)
69 changes: 69 additions & 0 deletions R/settings.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#' Fluidsynth settings
#'
#' Get available settings and their types.
#' See [fluidsynth docs](https://www.fluidsynth.org/api/fluidsettings.html)
#' for more information on the available options.
#'
#' @export
#' @name fluidsynth_settings
#' @rdname fluidsynth_settings
#' @family fluidsynth
#' @returns a list with available options
#' @useDynLib fluidsynth C_fluidsynth_list_settings
#' @references [FluidSynth Settings Reference](https://www.fluidsynth.org/api/fluidsettings.html)
#' @examples
#' # List available settings:
#' fluidsynth_setting_list()
#' fluidsynth_setting_options('audio.driver')
#' fluidsynth_setting_default('synth.sample-rate')
fluidsynth_setting_list <- function(){
out <- .Call(C_fluidsynth_list_settings)
names(out) <- c('name', 'type')
data.frame(out)
}

#' @export
#' @rdname fluidsynth_settings
#' @useDynLib fluidsynth C_fluidsynth_list_options
#' @param setting string with one of the options listed in [fluidsynth_setting_list()], see examples.
fluidsynth_setting_options <- function(setting){
setting <- as.character(setting)
.Call(C_fluidsynth_list_options, setting)
}

#' @export
#' @rdname fluidsynth_settings
#' @useDynLib fluidsynth C_fluidsynth_get_default
fluidsynth_setting_default <- function(setting){
setting <- as.character(setting)
.Call(C_fluidsynth_get_default, setting)
}

#' @export
#' @rdname fluidsynth_settings
#' @useDynLib fluidsynth C_fluidsynth_version
libfluidsynth_version <- function(){
.Call(C_fluidsynth_version)
}

validate_fluidsynth_settings <- function(opts){
if(length(opts)){
settings <- fluidsynth_setting_list()
for(o in names(opts)){
if(!(o %in% settings$name))
stop("Unsupported fluidsynth option: ", o)
val <- opts[[o]]
if(length(val) != 1)
stop("Option is not of length 1: ", o)
type <- settings[settings$name == o, "type"]
if(type == 'string' && !is.character(val))
stop("Option should be a string: ", o)
if(type != 'string'){
if(!is.numeric(val))
stop("Option should be a number: ", o)
opts[[o]] <- as.numeric(val)
}
}
return(opts)
}
}
55 changes: 55 additions & 0 deletions R/soundfonts.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#' Managing soundfonts
#'
#' FluidSynth requires a soundfont to synthesize a midi. On Linux distributions
#' some soundfonts are often preinstalled, though their quality varies. If your
#' midi sounds very poor, try using another soundfont.
#'
#' [GeneralUser-GS](https://schristiancollins.com/generaluser) by S. Christian Collins
#' is a nice free soundfont. You can use `soundfont_download()` to install a copy
#' of this soundbank for use by this package.
#'
#' @export
#' @name soundfonts
#' @rdname soundfonts
#' @family fluidsynth
#' @returns the path to a local soundfont to synthesize a midi file.
#' @param download automatically download soundfont if none exists.
soundfont_path <- function(download = FALSE){
if(file.exists(generaluser_gs_path())){
return(generaluser_gs_path())
}
default <- fluidsynth_setting_default('synth.default-soundfont')
if(file.exists(default)){
return(default)
}
if(grepl('redhat-linux', R.version$platform)){
stop('No soundfont found. Install one using either "yum install fluid-soundfont-gm" or in R: soundfont_download()')
}
if(isTRUE(download)){
return(soundfont_download())
}
stop('No default soundfont found. You can install using: soundfont_download()')
}

#' @export
#' @rdname soundfonts
soundfont_download <- function(){
path <- generaluser_gs_path()
if(!file.exists(path)){
url <- 'https://github.com/ropensci/fluidsynth/releases/download/generaluser-gs-v1.471/generaluser-gs-v1.471.zip'
if(getOption('timeout') < 300) {
old <- options(timeout = 300)
on.exit(options(old))
}
tmp <- tempfile(fileext = '.zip')
on.exit(unlink(tmp), add = TRUE)
utils::download.file(url, tmp, quiet = TRUE)
dir.create(dirname(path), showWarnings = FALSE)
utils::unzip(tmp, exdir = dirname(path))
}
return(path)
}

generaluser_gs_path <- function(){
file.path(rappdirs::user_data_dir('soundfonts'), 'generaluser-gs/v1.471.sf2')
}
84 changes: 84 additions & 0 deletions configure
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Anticonf (tm) script by Jeroen Ooms (2024)
# This script will query 'pkg-config' for the required cflags and ldflags.
# If pkg-config is unavailable or does not find the library, try setting
# INCLUDE_DIR and LIB_DIR manually via e.g:
# R CMD INSTALL --configure-vars='INCLUDE_DIR=/.../include LIB_DIR=/.../lib'

# Library settings
PKG_CONFIG_NAME="fluidsynth"
PKG_DEB_NAME="libfluidsynth-dev"
PKG_RPM_NAME="fluidsynth-devel"
PKG_BREW_NAME="fluidsynth"
PKG_TEST_HEADER="<fluidsynth.h>"
PKG_LIBS="-lfluidsynth"

# Use pkg-config if available
if [ `command -v pkg-config` ]; then
PKGCONFIG_CFLAGS=`pkg-config --cflags --silence-errors ${PKG_CONFIG_NAME}`
PKGCONFIG_LIBS=`pkg-config --libs ${PKG_CONFIG_NAME}`
fi

# Note that cflags may be empty in case of success
if [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then
echo "Found INCLUDE_DIR and/or LIB_DIR!"
PKG_CFLAGS="-I$INCLUDE_DIR $PKG_CFLAGS"
PKG_LIBS="-L$LIB_DIR $PKG_LIBS"
elif [ "$PKGCONFIG_CFLAGS" ] || [ "$PKGCONFIG_LIBS" ]; then
echo "Found pkg-config cflags and libs!"
PKG_CFLAGS=${PKGCONFIG_CFLAGS}
PKG_LIBS=${PKGCONFIG_LIBS}
elif [ `uname` = "Darwin" ]; then
test ! "$CI" && brew --version 2>/dev/null
if [ $? -eq 0 ]; then
BREWDIR=`brew --prefix`
PKG_CFLAGS="-I$BREWDIR/include"
PKG_LIBS="-L$BREWDIR/lib $PKG_LIBS"
else
curl -sfL "https://autobrew.github.io/scripts/fluid-synth" > autobrew
. ./autobrew
fi
fi


# For debugging
echo "Using PKG_CFLAGS=$PKG_CFLAGS"
echo "Using PKG_LIBS=$PKG_LIBS"

# Find compiler
CC=`${R_HOME}/bin/R CMD config CC`
CFLAGS=`${R_HOME}/bin/R CMD config CFLAGS`
CPPFLAGS=`${R_HOME}/bin/R CMD config CPPFLAGS`

# Test configuration
echo "#include $PKG_TEST_HEADER" | ${CC} ${CPPFLAGS} ${PKG_CFLAGS} ${CFLAGS} -E -xc - >/dev/null 2>configure.log

# Customize the error
if [ $? -ne 0 ]; then
echo "--------------------------- [ANTICONF] --------------------------------"
echo "Configuration failed because $PKG_CONFIG_NAME was not found. Try installing:"
echo " * deb: $PKG_DEB_NAME (Debian, Ubuntu, etc)"
echo " * rpm: $PKG_RPM_NAME (Fedora, EPEL)"
echo " * brew: $PKG_BREW_NAME (OSX)"
echo "If $PKG_CONFIG_NAME is already installed, check that 'pkg-config' is in your"
echo "PATH and PKG_CONFIG_PATH contains a $PKG_CONFIG_NAME.pc file. If pkg-config"
echo "is unavailable you can set INCLUDE_DIR and LIB_DIR manually via:"
echo "R CMD INSTALL --configure-vars='INCLUDE_DIR=... LIB_DIR=...'"
echo "-------------------------- [ERROR MESSAGE] ---------------------------"
cat configure.log
echo "--------------------------------------------------------------------"
exit 1
fi

# On Linux we need to initiate SDL manually
case "$PKG_CFLAGS" in
*SDL2*)
PKG_CFLAGS="$PKG_CFLAGS -DHAS_LIBSDL2"
PKG_LIBS="$PKG_LIBS -lSDL2"
;;
esac

# Write to Makevars
sed -e "s|@cflags@|$PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" src/Makevars.in > src/Makevars

# Success
exit 0
Binary file added inst/midi/All_Night_Long.mid
Binary file not shown.
3 changes: 3 additions & 0 deletions inst/midi/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This demo file is included with the free GeneralUser-GS soundbank.
See https://www.schristiancollins.com/generaluser

0 comments on commit 4323b43

Please sign in to comment.