[beast/wip/timj/soundfont: 1/13] BSE: SF2: Merge and squash branch 'soundfont-support'



commit c0dd83c05aacb5c12e3210b438131f1a9e69e060
Author: Stefan Westerfeld <stefan space twc de>
Date:   Wed Feb 27 01:38:58 2013 +0100

    BSE: SF2: Merge and squash branch 'soundfont-support'
    
    [Originally pulled as several changes from Stefan Westerfeld, squashed,
    rebased, resolved conflicts and fixed messages. -- Tim Janik]
    
      PO: add bstsoundfontview and bsesoundfontosc to translations
      BST: remove sound font related debugging code
      BUILD: include the right sound font header file into beast_headers
      BST: Add necessary gui support for browsing sound fonts
      BST: add new sound font view radget
      BST: add sound font view as widget
      BST: implement browsing and selection of available sound fonts
        Selecting sound font presets in now supported as an alternative to synth
        and wave selection.
      BST: add code for sound font repository super class
      BST: add file dialog support for loading sound fonts
      BUILD: add beast-gtk files for sound font support
      BSE: implement BseSoundFontOsc, which wraps fluid synth computations.
      BSE: add sound font support for bse projects wrapping fluid synths structures.
      BSE: add zintern snet for sound font preset replay
      BSE: allow sound font presets as instruments for a track
      BSE: implement new concept: BseStorageBlob
        A blob is a binary large object, that is some binary data which is stored along
        with the other data. It was designed to store sound font files within .bse
        files, while still allowing fluid synth to access the file directly. Temp files
        are used to give fluid synth direct access to a file.
      BSE: add global sound font repository to each project
        This repo is similar to the wave repo; it contains sound fonts used by a
        project.
      BSE: add code which allows modules to get midi events from receiver
        This is implemented by registering a midi event handler function. Sound font
        support via fluid synth needs this possibility.
      BSE: add BseSoundFont* typedefs
      BSE: remove unnecessary code in constant module
      BUILD: add new files for sound font support
      BUILD: add configure check for libfluidsynth, required for SoundFonts

 beast-gtk/Makefile.am               |    4 +-
 beast-gtk/bstfiledialog.cc          |   47 +++
 beast-gtk/bstfiledialog.hh          |   35 ++-
 beast-gtk/bstsoundfontpresetview.c  |   47 +++
 beast-gtk/bstsoundfontpresetview.h  |   52 +++
 beast-gtk/bstsoundfontview.c        |  155 +++++++++
 beast-gtk/bstsoundfontview.h        |   55 +++
 beast-gtk/bstsupershell.cc          |   17 +
 beast-gtk/bsttracksynthdialog.cc    |   31 ++-
 beast-gtk/bsttracksynthdialog.hh    |    9 +-
 beast-gtk/bsttrackview.cc           |   30 ++-
 beast-gtk/bstutils.cc               |    3 +
 beast-gtk/dialogs/radgets-beast.xml |   19 +
 bse/Makefile.am                     |    4 +
 bse/bsedefs.hh                      |    6 +
 bse/bsemidireceiver.cc              |  272 ++++++++++++----
 bse/bsemidireceiver.hh              |   14 +
 bse/bseproject.cc                   |   27 ++-
 bse/bseproject.hh                   |    1 +
 bse/bseproject.proc                 |   30 ++-
 bse/bsesoundfont.c                  |  394 ++++++++++++++++++++++
 bse/bsesoundfont.h                  |   59 ++++
 bse/bsesoundfontosc.c               |  617 +++++++++++++++++++++++++++++++++++
 bse/bsesoundfontosc.h               |   78 +++++
 bse/bsesoundfontpreset.c            |  195 +++++++++++
 bse/bsesoundfontpreset.h            |   54 +++
 bse/bsesoundfontrepo.c              |  393 ++++++++++++++++++++++
 bse/bsesoundfontrepo.h              |   94 ++++++
 bse/bsesoundfontrepo.proc           |  155 +++++++++
 bse/bsestorage.cc                   |  372 ++++++++++++++++++++-
 bse/bsestorage.hh                   |   16 +
 bse/bsetrack.cc                     |  120 ++++++-
 bse/bsetrack.hh                     |    5 +
 bse/zintern/Makefile.am             |    1 +
 bse/zintern/sound-font-snet.bse     |   17 +
 configure.ac                        |    5 +
 po/POTSCAN                          |    2 +
 37 files changed, 3305 insertions(+), 130 deletions(-)
---
diff --git a/beast-gtk/Makefile.am b/beast-gtk/Makefile.am
index 60dcdbe..d57072f 100644
--- a/beast-gtk/Makefile.am
+++ b/beast-gtk/Makefile.am
@@ -36,7 +36,7 @@ beast_headers = $(strip \
        bstusermessage.hh       bstdial.hh      bsttracksynthdialog.hh  bstwaveeditor.hh            \
        bstzoomedwindow.hh      bstskinconfig.hh        bstmsgabsorb.hh         bstsampleeditor.hh   \
        bstrackview.hh          bsttreestores.hh        bstbseutils.hh          bstutils.hh         \
-       bstdefs.hh \
+       bstdefs.hh              bstsoundfontview.h      bstsoundfontpresetview.h  \
 )
 EXTRA_DIST += $(beast_headers)
 # BEAST sources to build the program from
@@ -58,7 +58,7 @@ beast_sources = $(strip \
        bstusermessage.cc       bstdial.cc      bsttracksynthdialog.cc  bstwaveeditor.cc    \
        bstzoomedwindow.cc     bstskinconfig.cc bstmsgabsorb.cc         bstsampleeditor.cc  \
        bstrackview.cc         bsttreestores.cc bstbseutils.cc          bstutils.cc         \
-       $(PROFILE_SOURCE) \
+       bstsoundfontview.c      bstsoundfontpresetview.c                $(PROFILE_SOURCE)   \
 )
 # BEAST sources that get included (don't have own .lo rules)
 beast_extra_files = $(strip                                            \
diff --git a/beast-gtk/bstfiledialog.cc b/beast-gtk/bstfiledialog.cc
index 231a4a9..0e0b6ee 100644
--- a/beast-gtk/bstfiledialog.cc
+++ b/beast-gtk/bstfiledialog.cc
@@ -228,6 +228,16 @@ bst_file_dialog_global_wave (void)
 }
 
 static BstFileDialog*
+bst_file_dialog_global_sound_font (void)
+{
+  static BstFileDialog *singleton = NULL;
+  if (!singleton)
+    singleton = g_object_new (BST_TYPE_FILE_DIALOG, NULL);
+  return singleton;
+}
+
+
+static BstFileDialog*
 bst_file_dialog_global_effect (void)
 {
   static BstFileDialog *singleton = NULL;
@@ -317,6 +327,7 @@ bst_file_dialog_set_mode (BstFileDialog    *self,
   /* handle tree visibility */
   switch (mode & BST_FILE_DIALOG_MODE_MASK)
     {
+    case BST_FILE_DIALOG_LOAD_SOUND_FONT:
     case BST_FILE_DIALOG_LOAD_WAVE:
       g_free (self->search_path);
       self->search_path = g_strdup (bse_server_get_sample_path (BSE_SERVER));
@@ -325,6 +336,7 @@ bst_file_dialog_set_mode (BstFileDialog    *self,
       gxk_notebook_set_current_page_widget (GTK_NOTEBOOK (self->notebook), self->fpage);
       g_object_set (self->notebook, "show_border", TRUE, "show_tabs", TRUE, NULL);
       break;
+    case BST_FILE_DIALOG_LOAD_SOUND_FONT_LIB:
     case BST_FILE_DIALOG_LOAD_WAVE_LIB:
       g_free (self->search_path);
       self->search_path = g_strdup (bse_server_get_sample_path (BSE_SERVER));
@@ -768,6 +780,37 @@ bst_file_dialog_load_wave (BstFileDialog *self,
 }
 
 GtkWidget*
+bst_file_dialog_popup_load_sound_font (gpointer parent_widget,
+                                      SfiProxy sound_font_repo,
+                                      gboolean show_lib)
+{
+  BstFileDialog *self = bst_file_dialog_global_sound_font ();
+  GtkWidget *widget = GTK_WIDGET (self);
+
+  bst_file_dialog_set_mode (self, parent_widget,
+                           show_lib ? BST_FILE_DIALOG_LOAD_SOUND_FONT_LIB : BST_FILE_DIALOG_LOAD_SOUND_FONT,
+                           _("Load Sound Font"), sound_font_repo);
+  gxk_widget_showraise (widget);
+
+  return widget;
+}
+
+static gboolean
+bst_file_dialog_load_sound_font (BstFileDialog *self,
+                                const gchar   *file_name)
+{
+  BseErrorType error;
+
+  gxk_status_printf (0, NULL, _("Loading sound font `%s'"), file_name);
+  error = bse_sound_font_repo_load_file (self->proxy, file_name);
+  bst_status_eprintf (error, _("Loading sound font `%s'"), file_name);
+  if (error)
+    sfi_error (_("Failed to load sound font \"%s\": %s"), file_name, bse_error_blurb (error));
+
+  return TRUE;
+}
+
+GtkWidget*
 bst_file_dialog_create (void)
 {
   BstFileDialog *self = (BstFileDialog*) g_object_new (BST_TYPE_FILE_DIALOG, NULL);
@@ -905,6 +948,10 @@ bst_file_dialog_activate (BstFileDialog *self)
     case BST_FILE_DIALOG_LOAD_WAVE_LIB:
       popdown = bst_file_dialog_load_wave (self, file_name);
       break;
+    case BST_FILE_DIALOG_LOAD_SOUND_FONT:
+    case BST_FILE_DIALOG_LOAD_SOUND_FONT_LIB:
+      popdown = bst_file_dialog_load_sound_font (self, file_name);
+      break;
     default: ;
     }
   if (swin)
diff --git a/beast-gtk/bstfiledialog.hh b/beast-gtk/bstfiledialog.hh
index 93fc348..9290588 100644
--- a/beast-gtk/bstfiledialog.hh
+++ b/beast-gtk/bstfiledialog.hh
@@ -24,21 +24,23 @@ typedef struct  _BstFileDialogClass BstFileDialogClass;
 
 /* --- structures --- */
 typedef enum {
-  BST_FILE_DIALOG_OPEN_PROJECT    = 0x0001,
-  BST_FILE_DIALOG_MERGE_PROJECT           = 0x0002,
-  BST_FILE_DIALOG_SAVE_PROJECT    = 0x0003,
-  BST_FILE_DIALOG_IMPORT_MIDI     = 0x0004,
-  BST_FILE_DIALOG_SELECT_FILE     = 0x0008,
-  BST_FILE_DIALOG_SELECT_DIR      = 0x0009,
-  BST_FILE_DIALOG_LOAD_WAVE       = 0x0011,
-  BST_FILE_DIALOG_LOAD_WAVE_LIB           = 0x0012,
-  BST_FILE_DIALOG_MERGE_EFFECT     = 0x0021,
-  BST_FILE_DIALOG_MERGE_INSTRUMENT = 0x0022,
-  BST_FILE_DIALOG_SAVE_EFFECT      = 0x0023,
-  BST_FILE_DIALOG_SAVE_INSTRUMENT  = 0x0024,
-  BST_FILE_DIALOG_MODE_MASK       = 0x00ff,
-  BST_FILE_DIALOG_ALLOW_DIRS      = 0x1000,
-  BST_FILE_DIALOG_FLAG_MASK       = 0xff00
+  BST_FILE_DIALOG_OPEN_PROJECT       = 0x0001,
+  BST_FILE_DIALOG_MERGE_PROJECT              = 0x0002,
+  BST_FILE_DIALOG_SAVE_PROJECT       = 0x0003,
+  BST_FILE_DIALOG_IMPORT_MIDI        = 0x0004,
+  BST_FILE_DIALOG_SELECT_FILE        = 0x0008,
+  BST_FILE_DIALOG_SELECT_DIR         = 0x0009,
+  BST_FILE_DIALOG_LOAD_WAVE          = 0x0011,
+  BST_FILE_DIALOG_LOAD_WAVE_LIB              = 0x0012,
+  BST_FILE_DIALOG_LOAD_SOUND_FONT     = 0x0013,
+  BST_FILE_DIALOG_LOAD_SOUND_FONT_LIB = 0x0014,
+  BST_FILE_DIALOG_MERGE_EFFECT       = 0x0021,
+  BST_FILE_DIALOG_MERGE_INSTRUMENT    = 0x0022,
+  BST_FILE_DIALOG_SAVE_EFFECT        = 0x0023,
+  BST_FILE_DIALOG_SAVE_INSTRUMENT     = 0x0024,
+  BST_FILE_DIALOG_MODE_MASK          = 0x00ff,
+  BST_FILE_DIALOG_ALLOW_DIRS         = 0x1000,
+  BST_FILE_DIALOG_FLAG_MASK          = 0xff00
 } BstFileDialogMode;
 struct _BstFileDialog
 {
@@ -94,6 +96,9 @@ GtkWidget*    bst_file_dialog_popup_select_dir        (gpointer          parent_widget);
 GtkWidget*     bst_file_dialog_popup_load_wave         (gpointer          parent_widget,
                                                         SfiProxy          wave_repo,
                                                         gboolean          show_lib);
+GtkWidget*     bst_file_dialog_popup_load_sound_font   (gpointer          parent_widget,
+                                                         SfiProxy          sound_font_repo,
+                                                         gboolean          show_lib);
 void           bst_file_dialog_set_mode                (BstFileDialog    *self,
                                                         gpointer          parent_widget,
                                                         BstFileDialogMode mode,
diff --git a/beast-gtk/bstsoundfontpresetview.c b/beast-gtk/bstsoundfontpresetview.c
new file mode 100644
index 0000000..60ac02e
--- /dev/null
+++ b/beast-gtk/bstsoundfontpresetview.c
@@ -0,0 +1,47 @@
+/* BEAST - Bedevilled Audio System
+ * Copyright (C) 1998-2003 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#include "bstsoundfontpresetview.h"
+
+/* --- functions --- */
+
+G_DEFINE_TYPE (BstSoundFontPresetView, bst_sound_font_preset_view, BST_TYPE_ITEM_VIEW);
+
+
+static void
+bst_sound_font_preset_view_class_init (BstSoundFontPresetViewClass *class)
+{
+  BstItemViewClass *item_view_class = BST_ITEM_VIEW_CLASS (class);
+
+  item_view_class->item_type = "BseSoundFontPreset";
+}
+
+static void
+bst_sound_font_preset_view_init (BstSoundFontPresetView *self)
+{
+  BstItemView *iview = BST_ITEM_VIEW (self);
+}
+
+GtkWidget*
+bst_sound_font_preset_view_new()
+{
+  GtkWidget *sound_font_preset_view;
+
+  sound_font_preset_view = gtk_widget_new (BST_TYPE_SOUND_FONT_PRESET_VIEW, NULL);
+
+  return sound_font_preset_view;
+}
diff --git a/beast-gtk/bstsoundfontpresetview.h b/beast-gtk/bstsoundfontpresetview.h
new file mode 100644
index 0000000..0afb027
--- /dev/null
+++ b/beast-gtk/bstsoundfontpresetview.h
@@ -0,0 +1,52 @@
+/* BEAST - Bedevilled Audio System
+ * Copyright (C) 1998-2003 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+
+#ifndef __BST_SOUND_FONT_PRESET_VIEW_H__
+#define __BST_SOUND_FONT_PRESET_VIEW_H__
+
+#include "bstitemview.h"
+
+G_BEGIN_DECLS
+
+/* --- Gtk+ type macros --- */
+#define        BST_TYPE_SOUND_FONT_PRESET_VIEW              (bst_sound_font_preset_view_get_type ())
+#define        BST_SOUND_FONT_PRESET_VIEW(object)           (GTK_CHECK_CAST ((object), 
BST_TYPE_SOUND_FONT_PRESET_VIEW, BstSoundFontPresetView))
+#define        BST_SOUND_FONT_PRESET_VIEW_CLASS(klass)      (GTK_CHECK_CLASS_CAST ((klass), 
BST_TYPE_SOUND_FONT_PRESET_VIEW, BstSoundFontPresetViewClass))
+#define        BST_IS_SOUND_FONT_PRESET_VIEW(object)        (GTK_CHECK_TYPE ((object), 
BST_TYPE_SOUND_FONT_PRESET_VIEW))
+#define        BST_IS_SOUND_FONT_PRESET_VIEW_CLASS(klass)   (GTK_CHECK_CLASS_TYPE ((klass), 
BST_TYPE_SOUND_FONT_PRESET_VIEW))
+#define BST_SOUND_FONT_PRESET_VIEW_GET_CLASS(obj)    (GTK_CHECK_GET_CLASS ((obj), 
BST_TYPE_SOUND_FONT_PRESET_VIEW, BstSoundFontPresetViewClass))
+
+
+typedef        struct  _BstSoundFontPresetView       BstSoundFontPresetView;
+typedef        struct  _BstSoundFontPresetViewClass  BstSoundFontPresetViewClass;
+struct _BstSoundFontPresetView
+{
+  BstItemView   parent_object;
+};
+
+struct _BstSoundFontPresetViewClass
+{
+  BstItemViewClass parent_class;
+};
+
+/* --- prototypes --- */
+
+GType          bst_sound_font_preset_view_get_type          (void);
+GtkWidget*     bst_sound_font_preset_view_new               (void);
+
+#endif /* __BST_SOUND_FONT_PRESET_VIEW_H__ */
diff --git a/beast-gtk/bstsoundfontview.c b/beast-gtk/bstsoundfontview.c
new file mode 100644
index 0000000..f2929d9
--- /dev/null
+++ b/beast-gtk/bstsoundfontview.c
@@ -0,0 +1,155 @@
+/* BEAST - Bedevilled Audio System
+ * Copyright (C) 1998-2003 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#include "bstsoundfontview.h"
+#include "bstsoundfontpresetview.h"
+#include "bstfiledialog.h"
+
+/* --- prototypes --- */
+
+static void     sound_font_view_action_exec           (gpointer                data,
+                                                       gulong                  action);
+static gboolean sound_font_view_action_check          (gpointer                data,
+                                                       gulong                  action,
+                                                       guint64                 action_stamp);
+
+
+/* --- sound font actions --- */
+
+enum {
+  ACTION_LOAD_SOUND_FONT,
+  ACTION_LOAD_SOUND_FONT_LIB,
+  ACTION_DELETE_SOUND_FONT,
+  ACTION_SOUND_FONT_LAST
+};
+static const GxkStockAction sound_font_view_actions[] = {
+  { N_("Load..."),  NULL,       N_("Load a new sound font file from disk"),
+    ACTION_LOAD_SOUND_FONT,     BST_STOCK_LOAD,        },
+  { N_("Lib..."),   NULL,       N_("Load a sound font file from library paths"),
+    ACTION_LOAD_SOUND_FONT_LIB,        BST_STOCK_LOAD_LIB, },
+  { N_("Delete"),   NULL,       N_("Delete the currently selected sound font from project"),
+    ACTION_DELETE_SOUND_FONT,   BST_STOCK_TRASHCAN, },
+};
+
+
+/* --- functions --- */
+
+G_DEFINE_TYPE (BstSoundFontView, bst_sound_font_view, BST_TYPE_ITEM_VIEW);
+
+static void
+bst_sound_font_view_class_init (BstSoundFontViewClass *class)
+{
+  BstItemViewClass *item_view_class = BST_ITEM_VIEW_CLASS (class);
+
+  item_view_class->item_type = "BseSoundFont";
+}
+
+static void
+sound_font_selection_changed (BstSoundFontView *self)
+{
+  BstItemView *iview = BST_ITEM_VIEW (self);
+  bst_item_view_set_container (BST_ITEM_VIEW (self->preset_view), bst_item_view_get_current (iview));
+}
+
+static void
+bst_sound_font_view_init (BstSoundFontView *self)
+{
+  BstItemView *iview = BST_ITEM_VIEW (self);
+  /* complete GUI */
+  GxkRadget *radget = gxk_radget_complete (GTK_WIDGET (self), "beast", "sound-font-view", NULL);
+  gxk_widget_publish_actions (self, "sound-font-view-actions",
+                              G_N_ELEMENTS (sound_font_view_actions), sound_font_view_actions,
+                              NULL, sound_font_view_action_check, sound_font_view_action_exec);
+  /* setup tree view */
+  GtkTreeView *tview = gxk_radget_find (radget, "tree-view");
+  bst_item_view_complete_tree (iview, tview);
+
+  g_object_connect (gtk_tree_view_get_selection (tview),
+                    "swapped_object_signal::changed", sound_font_selection_changed, self,
+                    NULL);
+
+
+  /* setup preset view */
+  GtkTreeView *pview = gxk_radget_find (radget, "preset-view");
+  self->preset_view = BST_SOUND_FONT_PRESET_VIEW (bst_sound_font_preset_view_new());
+  bst_item_view_complete_tree (BST_ITEM_VIEW (self->preset_view), pview);
+}
+
+GtkWidget*
+bst_sound_font_view_new (SfiProxy sfrepo)
+{
+  GtkWidget *sound_font_view;
+
+  g_return_val_if_fail (BSE_IS_SOUND_FONT_REPO (sfrepo), NULL);
+
+  sound_font_view = gtk_widget_new (BST_TYPE_SOUND_FONT_VIEW, NULL);
+  bst_item_view_set_container (BST_ITEM_VIEW (sound_font_view), sfrepo);
+
+  return sound_font_view;
+}
+
+SfiProxy
+bst_sound_font_view_get_preset (BstSoundFontView *self)
+{
+  return bst_item_view_get_current (BST_ITEM_VIEW (self->preset_view));
+}
+
+static void
+sound_font_view_action_exec (gpointer                data,
+                             gulong                  action)
+{
+  BstSoundFontView *self = BST_SOUND_FONT_VIEW (data);
+  BstItemView *item_view = BST_ITEM_VIEW (self);
+  SfiProxy sfrepo = item_view->container;
+  switch (action)
+    {
+      SfiProxy item;
+    case ACTION_LOAD_SOUND_FONT:
+      bst_file_dialog_popup_load_sound_font (item_view, BST_ITEM_VIEW (self)->container, FALSE);
+      break;
+    case ACTION_LOAD_SOUND_FONT_LIB:
+      bst_file_dialog_popup_load_sound_font (item_view, BST_ITEM_VIEW (self)->container, TRUE);
+      break;
+    case ACTION_DELETE_SOUND_FONT:
+      item = bst_item_view_get_current (BST_ITEM_VIEW (self));
+      bse_sound_font_repo_remove_sound_font (sfrepo, item);
+      break;
+    default:
+      break;
+    }
+  gxk_widget_update_actions_downwards (self);
+}
+
+static gboolean
+sound_font_view_action_check (gpointer                data,
+                              gulong                  action,
+                              guint64                 action_stamp)
+{
+  BstSoundFontView *self = BST_SOUND_FONT_VIEW (data);
+  BstItemView *item_view = BST_ITEM_VIEW (self);
+
+  switch (action)
+    {
+    case ACTION_LOAD_SOUND_FONT:
+    case ACTION_LOAD_SOUND_FONT_LIB:
+      return TRUE;
+    case ACTION_DELETE_SOUND_FONT:
+      return bst_item_view_get_current (item_view) != 0;
+    default:
+      return FALSE;
+    }
+}
diff --git a/beast-gtk/bstsoundfontview.h b/beast-gtk/bstsoundfontview.h
new file mode 100644
index 0000000..bf0e27f
--- /dev/null
+++ b/beast-gtk/bstsoundfontview.h
@@ -0,0 +1,55 @@
+/* BEAST - Bedevilled Audio System
+ * Copyright (C) 1998-2003 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+
+#ifndef __BST_SOUND_FONT_VIEW_H__
+#define __BST_SOUND_FONT_VIEW_H__
+
+#include "bstitemview.h"
+#include "bstsoundfontpresetview.h"
+
+G_BEGIN_DECLS
+
+/* --- Gtk+ type macros --- */
+#define        BST_TYPE_SOUND_FONT_VIEW              (bst_sound_font_view_get_type ())
+#define        BST_SOUND_FONT_VIEW(object)           (GTK_CHECK_CAST ((object), BST_TYPE_SOUND_FONT_VIEW, 
BstSoundFontView))
+#define        BST_SOUND_FONT_VIEW_CLASS(klass)      (GTK_CHECK_CLASS_CAST ((klass), 
BST_TYPE_SOUND_FONT_VIEW, BstSoundFontViewClass))
+#define        BST_IS_SOUND_FONT_VIEW(object)        (GTK_CHECK_TYPE ((object), BST_TYPE_SOUND_FONT_VIEW))
+#define        BST_IS_SOUND_FONT_VIEW_CLASS(klass)   (GTK_CHECK_CLASS_TYPE ((klass), 
BST_TYPE_SOUND_FONT_VIEW))
+#define BST_SOUND_FONT_VIEW_GET_CLASS(obj)    (GTK_CHECK_GET_CLASS ((obj), BST_TYPE_SOUND_FONT_VIEW, 
BstSoundFontViewClass))
+
+
+typedef        struct  _BstSoundFontView             BstSoundFontView;
+typedef        struct  _BstSoundFontViewClass        BstSoundFontViewClass;
+struct _BstSoundFontView
+{
+  BstItemView              parent_object;
+  BstSoundFontPresetView   *preset_view;
+};
+
+struct _BstSoundFontViewClass
+{
+  BstItemViewClass parent_class;
+};
+
+/* --- prototypes --- */
+
+GType          bst_sound_font_view_get_type          (void);
+GtkWidget*     bst_sound_font_view_new               (SfiProxy            sfont_repo);
+SfiProxy       bst_sound_font_view_get_preset        (BstSoundFontView   *self);
+
+#endif /* __BST_SOUND_FONT_VIEW_H__ */
diff --git a/beast-gtk/bstsupershell.cc b/beast-gtk/bstsupershell.cc
index 5f06742..224c4df 100644
--- a/beast-gtk/bstsupershell.cc
+++ b/beast-gtk/bstsupershell.cc
@@ -7,6 +7,7 @@
 #include "bstbusview.hh"
 #include "bstwaveview.hh"
 #include "bstrackview.hh"
+#include "bstsoundfontview.h"
 #include "bstsnetrouter.hh"
 #include "bstgconfig.hh"
 #include <string.h>
@@ -220,6 +221,20 @@ super_shell_build_wave_repo (BstSuperShell *self,
                             gxk_notebook_create_tabulator (_("Properties"), BST_STOCK_PROPERTIES, NULL));
 }
 
+static void
+super_shell_build_sound_font_repo (BstSuperShell *self,
+                                   GtkNotebook   *notebook)
+{
+  SfiProxy sfrepo = self->super;
+
+  gtk_notebook_append_page (notebook,
+                            bst_sound_font_view_new (sfrepo),
+                            gxk_notebook_create_tabulator (_("Sound Fonts"), BST_STOCK_MINI_WAVE_REPO, 
NULL));
+  gtk_notebook_append_page (notebook,
+                            bst_param_view_new (sfrepo),
+                            gxk_notebook_create_tabulator (_("Properties"), BST_STOCK_PROPERTIES, NULL));
+}
+
 static GtkNotebook*
 create_notebook (BstSuperShell *self)
 {
@@ -244,6 +259,8 @@ super_shell_add_views (BstSuperShell *self)
     super_shell_build_song (self, create_notebook (self));
   else if (BSE_IS_WAVE_REPO (self->super))
     super_shell_build_wave_repo (self, create_notebook (self));
+  else if (BSE_IS_SOUND_FONT_REPO (self->super))
+    super_shell_build_sound_font_repo (self, create_notebook (self));
   else /* BSE_IS_SNET (self->super) */
     super_shell_build_snet (self, create_notebook (self));
 }
diff --git a/beast-gtk/bsttracksynthdialog.cc b/beast-gtk/bsttracksynthdialog.cc
index dcad9d9..16ec978 100644
--- a/beast-gtk/bsttracksynthdialog.cc
+++ b/beast-gtk/bsttracksynthdialog.cc
@@ -42,7 +42,7 @@ bst_track_synth_dialog_init (BstTrackSynthDialog *self)
                           GXK_DIALOG_POPUP_POS |
                           GXK_DIALOG_MODAL),
                 NULL);
-  gxk_dialog_set_sizes (GXK_DIALOG (self), 550, 300, 600, 320);
+  gxk_dialog_set_sizes (GXK_DIALOG (self), 550, 300, 600, 450);
 
   /* notebook */
   self->notebook = (GtkNotebook*) g_object_new (GXK_TYPE_NOTEBOOK,
@@ -105,6 +105,9 @@ bst_track_synth_dialog_init (BstTrackSynthDialog *self)
   self->wpage = (GtkWidget*) g_object_new (BST_TYPE_WAVE_VIEW, "visible", TRUE, NULL);
   gxk_notebook_append (self->notebook, self->wpage, "wave", TRUE);
   bst_wave_view_set_editable (BST_WAVE_VIEW (self->wpage), FALSE);
+  /* sound font view */
+  self->sfont_page = g_object_new (BST_TYPE_SOUND_FONT_VIEW, "visible", TRUE, NULL);
+  gxk_notebook_append (self->notebook, self->sfont_page, "sound_font", TRUE);
 
   /* provide buttons */
   self->ok = gxk_dialog_default_action_swapped (GXK_DIALOG (self), BST_STOCK_OK, (void*) 
bst_track_synth_dialog_activate, self);
@@ -221,6 +224,9 @@ bst_track_synth_dialog_popup (gpointer     parent_widget,
                               const gchar *wrepo_label,
                               const gchar *wrepo_tooltip,
                               SfiProxy     wrepo,
+                              const gchar *sfrepo_label,
+                              const gchar *sfrepo_tooltip,
+                              SfiProxy     sfrepo,
                               BstTrackSynthDialogSelected  selected_callback,
                               gpointer                     selected_data,
                               GxkFreeFunc                  selected_cleanup)
@@ -231,6 +237,8 @@ bst_track_synth_dialog_popup (gpointer     parent_widget,
     candidate_label = "";
   if (!wrepo_label)
     wrepo_label = "";
+  if (!sfrepo_label)
+    sfrepo_label = "";
 
   bst_track_synth_dialog_setup (self, NULL, NULL, 0);
 
@@ -238,8 +246,10 @@ bst_track_synth_dialog_popup (gpointer     parent_widget,
   gxk_widget_set_tooltip (self->tview, candidate_tooltip);
   g_object_set (gtk_notebook_get_tab_label (self->notebook, self->wpage), "label", wrepo_label, NULL);
   gxk_widget_set_tooltip (BST_ITEM_VIEW (self->wpage)->tree, wrepo_tooltip);
+  g_object_set (gtk_notebook_get_tab_label (self->notebook, self->sfont_page), "label", sfrepo_label, NULL);
+  gxk_widget_set_tooltip (BST_ITEM_VIEW (self->sfont_page)->tree, sfrepo_tooltip);
 
-  bst_track_synth_dialog_set (self, candidates, wrepo);
+  bst_track_synth_dialog_set (self, candidates, wrepo, sfrepo);
   bst_track_synth_dialog_setup (self, parent_widget,
                                 /* TRANSLATORS: this is a dialog title and %s is replaced by an object name 
*/
                                 _("Synthesizer Selection: %s"),
@@ -256,14 +266,17 @@ bst_track_synth_dialog_popup (gpointer     parent_widget,
 void
 bst_track_synth_dialog_set (BstTrackSynthDialog *self,
                             BseItemSeq          *iseq,
-                            SfiProxy             wrepo)
+                            SfiProxy             wrepo,
+                           SfiProxy             sfrepo)
 {
   g_return_if_fail (BST_IS_TRACK_SYNTH_DIALOG (self));
 
   bst_item_view_set_container (BST_ITEM_VIEW (self->wpage), wrepo);
+  bst_item_view_set_container (BST_ITEM_VIEW (self->sfont_page), sfrepo);
   bst_item_seq_store_set (self->pstore, iseq);
-  g_object_set (self->wpage, "visible", wrepo != 0, NULL);
   g_object_set (self->spage, "visible", iseq != NULL, NULL);
+  g_object_set (self->wpage, "visible", wrepo != 0, NULL);
+  g_object_set (self->sfont_page, "visible", sfrepo != 0, NULL);
 }
 
 static void
@@ -288,8 +301,14 @@ bst_track_synth_dialog_activate (BstTrackSynthDialog *self)
           proxy = bst_item_seq_store_get_from_iter (self->pstore, &piter);
         }
     }
-  else if (self->wpage)
-    proxy = bst_item_view_get_current (BST_ITEM_VIEW (self->wpage));
+  else if (self->wpage && gxk_widget_viewable (GTK_WIDGET (self->wpage)))
+    {
+      proxy = bst_item_view_get_current (BST_ITEM_VIEW (self->wpage));
+    }
+  else if (self->sfont_page && gxk_widget_viewable (GTK_WIDGET (self->sfont_page)))
+    {
+      proxy = bst_sound_font_view_get_preset (BST_SOUND_FONT_VIEW (self->sfont_page));
+    }
 
   /* ignore_activate guards against multiple clicks */
   self->ignore_activate = TRUE;
diff --git a/beast-gtk/bsttracksynthdialog.hh b/beast-gtk/bsttracksynthdialog.hh
index df50a4f..90cbe68 100644
--- a/beast-gtk/bsttracksynthdialog.hh
+++ b/beast-gtk/bsttracksynthdialog.hh
@@ -4,6 +4,7 @@
 
 #include "bstutils.hh"
 #include "bstwaveview.hh"
+#include "bstsoundfontview.h"
 
 G_BEGIN_DECLS
 
@@ -29,6 +30,7 @@ struct _BstTrackSynthDialog
   GtkNotebook   *notebook;
   GtkWidget     *wpage;         /* wave repo item view */
   GtkWidget     *spage;         /* synth list */
+  GtkWidget     *sfont_page;    /* sound font patch selection */
   GtkWidget     *ok;            /* ok button */
   GtkWindow     *parent_window;
   guint          ignore_activate : 1;
@@ -54,13 +56,16 @@ GtkWidget* bst_track_synth_dialog_popup    (gpointer                     parent_
                                             const gchar                 *wrepo_label,
                                             const gchar                 *wrepo_tooltip,
                                             SfiProxy                     wrepo,
+                                            const gchar                 *sfrepo_label,
+                                            const gchar                 *sfrepo_tooltip,
+                                            SfiProxy                     sfrepo,
                                             BstTrackSynthDialogSelected  selected_callback,
                                             gpointer                     selected_data,
                                             GxkFreeFunc                  selected_cleanup);
 void       bst_track_synth_dialog_set      (BstTrackSynthDialog         *self,
                                             BseItemSeq                  *iseq,
-                                            SfiProxy                     wrepo);
-
+                                            SfiProxy                     wrepo,
+                                           SfiProxy                     sfrepo);
 
 
 G_END_DECLS
diff --git a/beast-gtk/bsttrackview.cc b/beast-gtk/bsttrackview.cc
index 60bcc5a..fa94ea0 100644
--- a/beast-gtk/bsttrackview.cc
+++ b/beast-gtk/bsttrackview.cc
@@ -120,7 +120,7 @@ track_view_fill_value (BstItemView *iview,
       const gchar *string;
       gboolean vbool;
       SfiInt vint;
-      SfiProxy snet, wave;
+      SfiProxy snet, wave, sound_font_preset;
       BseItemSeq *iseq;
       SfiSeq *seq;
     case COL_SEQID:
@@ -139,8 +139,18 @@ track_view_fill_value (BstItemView *iview,
       break;
     case COL_SYNTH:
       snet = 0;
-      bse_proxy_get (item, "snet", &snet, "wave", &wave, NULL);
-      g_value_set_string (value, snet || wave ? bse_item_get_name (snet ? snet : wave) : "");
+      wave = 0;
+      sound_font_preset = 0;
+      bse_proxy_get (item, "snet", &snet, "wave", &wave, "sound_font_preset", &sound_font_preset, NULL);
+      if (snet)
+       string = bse_item_get_name (snet);
+      else if (wave)
+       string = bse_item_get_name (wave);
+      else if (sound_font_preset)
+       string = bse_item_get_name (sound_font_preset);
+      else
+       string = "";
+      g_value_set_string (value, string);
       break;
     case COL_MIDI_CHANNEL:
       bse_proxy_get (item, "midi-channel", &vint, NULL);
@@ -184,11 +194,13 @@ track_view_synth_edited (BstTrackView *self,
        {
          SfiProxy proxy = 0;
          GSList *slist = NULL;
-         /* list possible snet/wave candidates */
+         /* list possible snet/wave/sound_font_preset candidates */
           BsePropertyCandidates *pc = bse_item_get_property_candidates (item, "snet");
          slist = g_slist_append (slist, pc->items);
           pc = bse_item_get_property_candidates (item, "wave");
          slist = g_slist_append (slist, pc->items);
+         pc = bse_item_get_property_candidates (item, "sound_font_preset");
+          slist = g_slist_append (slist, pc->items);
          /* find best match */
          proxy = bst_item_seq_list_match (slist, text);
          g_slist_free (slist);
@@ -196,11 +208,13 @@ track_view_synth_edited (BstTrackView *self,
            bse_proxy_set (item, "snet", proxy, NULL);
          else if (proxy && BSE_IS_WAVE (proxy))
            bse_proxy_set (item, "wave", proxy, NULL);
+         else if (proxy && BSE_IS_SOUND_FONT_PRESET (proxy))
+           bse_proxy_set (item, "sound_font_preset", proxy, NULL);
          else
-           bse_proxy_set (item, "snet", 0, "wave", 0, NULL);
+           bse_proxy_set (item, "snet", 0, "wave", 0, "sound_font_preset", 0, NULL);
        }
       else
-       bse_proxy_set (item, "snet", 0, "wave", 0, NULL);
+       bse_proxy_set (item, "snet", 0, "wave", 0, "sound_font_preset", 0, NULL);
     }
 }
 
@@ -281,6 +295,9 @@ track_view_synth_popup (BstTrackView         *self,
                                                             _("Available Waves"),
                                                             _("List of available waves to choose a track 
instrument from"),
                                                             bse_project_get_wave_repo (bse_item_get_project 
(item)),
+                                                           _("Available Sound Fonts"),
+                                                           _("List of available sound fonts to choose track 
instrument from"),
+                                                           bse_project_get_sound_font_repo 
(bse_item_get_project (item)),
                                                             track_view_synth_popup_cb, g_memdup (&sdata, 
sizeof (sdata)), track_view_synth_popup_cleanup);
           gxk_cell_renderer_popup_dialog (pcell, dialog);
         }
@@ -308,6 +325,7 @@ track_view_post_synth_popup (BstTrackView         *self,
           GtkWidget *dialog = bst_track_synth_dialog_popup (self, item,
                                                             pc->label, pc->tooltip, pc->items,
                                                             NULL, NULL, 0,
+                                                           NULL, NULL, 0,
                                                             track_view_synth_popup_cb, g_memdup (&sdata, 
sizeof (sdata)), track_view_synth_popup_cleanup);
           gxk_cell_renderer_popup_dialog (pcell, dialog);
         }
diff --git a/beast-gtk/bstutils.cc b/beast-gtk/bstutils.cc
index af7ad9d..90a1afa 100644
--- a/beast-gtk/bstutils.cc
+++ b/beast-gtk/bstutils.cc
@@ -5,6 +5,7 @@
 #include "bstmenus.hh"
 #include "bsttrackview.hh"
 #include "bstwaveview.hh"
+#include "bstsoundfontview.h"
 #include "bstpartview.hh"
 #include "bstbusmixer.hh"
 #include "bstbuseditor.hh"
@@ -15,6 +16,7 @@
 #include "bstgrowbar.hh"
 #include "bstdbmeter.hh"
 #include "bstscrollgraph.hh"
+
 #include <fcntl.h>
 #include <errno.h>
 #include <unistd.h>
@@ -95,6 +97,7 @@ _bst_init_radgets (void)
   gxk_radget_define_widget_type (BST_TYPE_HGROW_BAR);
   gxk_radget_define_widget_type (BST_TYPE_VGROW_BAR);
   gxk_radget_define_widget_type (BST_TYPE_WAVE_VIEW);
+  gxk_radget_define_widget_type (BST_TYPE_SOUND_FONT_VIEW);
   gxk_radget_define_widget_type (BST_TYPE_PART_VIEW);
   gxk_radget_define_widget_type (BST_TYPE_BUS_EDITOR);
   gxk_radget_define_widget_type (BST_TYPE_BUS_MIXER);
diff --git a/beast-gtk/dialogs/radgets-beast.xml b/beast-gtk/dialogs/radgets-beast.xml
index 09e6e3f..5bb5118 100644
--- a/beast-gtk/dialogs/radgets-beast.xml
+++ b/beast-gtk/dialogs/radgets-beast.xml
@@ -209,6 +209,25 @@
       </alignment>
     </hbox>
   </xdef:wave-view>
+
+  <!-- sound font view -->
+  <xdef:sound-font-view inherit="BstSoundFontView" border-width="3" >
+    <vbox spacing="3" >
+      <hbox spacing="3" >
+       <scrolled-window id="tree-area" pack:expand="1" >
+         <tree-view id="tree-view" height="120" width="200" />
+       </scrolled-window>
+       <alignment yscale="0" yalign="0" >
+         <vbox homogeneous="1" spacing="3" >
+           <big-button-factory actions="sound-font-view-actions" />
+         </vbox>
+       </alignment>
+      </hbox>
+      <scrolled-window id="preset-area" pack:expand="1" >
+       <tree-view id="preset-view" height="120" width="200" />
+      </scrolled-window>
+    </vbox>
+  </xdef:sound-font-view>
   
   <!-- part view -->
   <xdef:part-view inherit="BstPartView" border-width="3" >
diff --git a/bse/Makefile.am b/bse/Makefile.am
index cda233c..0d16dbb 100644
--- a/bse/Makefile.am
+++ b/bse/Makefile.am
@@ -46,6 +46,7 @@ bse_public_headers = $(strip \
        bsesnet.hh              bsesnooper.hh           bsesong.hh                      bsesequencer.hh \
        bsesource.hh            bsestandardosc.hh       bsestandardsynths.hh            bsestorage.hh \
        bseinstrumentoutput.hh  bsesubiport.hh          bseinstrumentinput.hh           bsesuboport.hh \
+        bsesoundfont.h          bsesoundfontpreset.h    bsesoundfontosc.h               \
        bsesubsynth.hh          bsesuper.hh             bsetrack.hh                     bsetype.hh \
        bseutils.hh             bsemidivoice.hh         bsewave.hh                      bsewaveosc.hh \
        bsecsynth.hh            bsewaverepo.hh          bseladspamodule.hh              bsepcmwriter.hh \
@@ -78,6 +79,8 @@ bse_sources = $(strip \
        bsepcmdevice.cc         bsepcmdevice-oss.cc     bsepcmdevice-null.cc            bseplugin.cc \
        bseprocedure.cc         bseproject.cc           bsescripthelper.cc              bseserver.cc \
        bsesnet.cc              bsesnooper.cc           bsesong.cc                      bsesequencer.cc \
+        bsesoundfont.c          bsesoundfontrepo.c      bsesoundfontpreset.c            bsesoundfontosc.c \
+       bsesoundfontrepo.c      bsesoundfont.c          bsesoundfontpreset.c            bsesoundfontosc.c \
        bsesource.cc            bsestandardosc.cc       bsestandardsynths.cc            bsestorage.cc \
        bseinstrumentoutput.cc  bsesubiport.cc          bseinstrumentinput.cc           bsesuboport.cc \
        bsesubsynth.cc          bsesuper.cc             bsetrack.cc                     bsetype.cc \
@@ -117,6 +120,7 @@ bse_proc_sources = $(strip \
        bsejanitor.proc         bsepart.proc            bseparasite.proc        bseprocedure.proc       
bseproject.proc bsescripthelper.proc    \
        bseserver.proc          bsesong.proc            bsebus.proc             bsesource.proc          
bsecsynth.proc  bsesnet.proc            \
        bsetrack.proc           bseitem.proc            bsewave.proc            bsewaveosc.proc         
bsewaverepo.proc                        \
+       bsesoundfontrepo.proc   \
 )
 bse_proc_gen_sources = $(bse_proc_sources:.proc=.genprc.cc)
 # non-compile and non-install sources required
diff --git a/bse/bsedefs.hh b/bse/bsedefs.hh
index 634e5df..f1f962e 100644
--- a/bse/bsedefs.hh
+++ b/bse/bsedefs.hh
@@ -65,6 +65,12 @@ struct BseSNetClass;
 struct BseSong;
 struct BseSongClass;
 typedef struct  _BseSongSequencer          BseSongSequencer;
+typedef struct  _BseSoundFont              BseSoundFont;
+typedef struct  _BseSoundFontClass         BseSoundFontClass;
+typedef struct  _BseSoundFontPreset        BseSoundFontPreset;
+typedef struct  _BseSoundFontPresetClass   BseSoundFontPresetClass;
+typedef struct  _BseSoundFontRepo          BseSoundFontRepo;
+typedef struct  _BseSoundFontRepoClass     BseSoundFontRepoClass;
 struct BseSource;
 struct BseSourceClass;
 struct BseStorage;
diff --git a/bse/bsemidireceiver.cc b/bse/bsemidireceiver.cc
index b72aa52..5ce7e47 100644
--- a/bse/bsemidireceiver.cc
+++ b/bse/bsemidireceiver.cc
@@ -11,6 +11,8 @@
 #include <sfi/gbsearcharray.hh>
 #include <map>
 #include <set>
+#include <list>
+
 namespace {
 using namespace Bse;
 using namespace std;
@@ -211,6 +213,31 @@ struct ControlValue {
   }
 };
 
+struct EventHandler
+{
+  guint               midi_channel;
+  BseMidiEventHandler handler_func;
+  gpointer            handler_data;
+  BseModule          *module;
+
+  EventHandler (guint               midi_channel,
+                BseMidiEventHandler handler_func,
+                gpointer            handler_data,
+                BseModule          *module) :
+    midi_channel (midi_channel),
+    handler_func (handler_func),
+    handler_data (handler_data),
+    module (module)
+  {
+  }
+  bool operator == (const EventHandler& other)
+  {
+    return (midi_channel == other.midi_channel &&
+            handler_func == other.handler_func &&
+            handler_data == other.handler_data &&
+            module       == other.module);
+  }
+};
 
 /* --- voice prototypes --- */
 typedef struct VoiceSwitch          VoiceSwitch;
@@ -226,6 +253,7 @@ struct MidiChannel {
   guint           n_voices;
   VoiceSwitch   **voices;
   VoiceInputTable voice_input_table;
+  std::list<EventHandler> event_handlers;
   MidiChannel (guint mc) :
     midi_channel (mc),
     poly_enabled (0)
@@ -245,6 +273,21 @@ struct MidiChannel {
     if (poly_enabled)
       poly_enabled--;
   }
+  void
+  add_event_handler (const EventHandler& handler)
+  {
+    event_handlers.push_back (handler);
+  }
+  void
+  remove_event_handler (const EventHandler& handler)
+  {
+    list<EventHandler>::iterator hi = find (event_handlers.begin(), event_handlers.end(), handler);
+    g_return_if_fail (hi != event_handlers.end());
+    event_handlers.erase (hi);
+  }
+  bool
+  call_event_handlers (BseMidiEvent *event,
+                       BseTrans     *trans);
   ~MidiChannel()
   {
     if (vinput)
@@ -424,6 +467,24 @@ public:
     ControlValue *cv = get_control_value (midi_channel, signal_type);
     cv->remove_handler (handler_func, handler_data, module);
   }
+  void
+  add_event_handler (guint              midi_channel,
+                    BseMidiEventHandler handler_func,
+                    gpointer           handler_data,
+                    BseModule         *module)
+  {
+    MidiChannel *channel = get_channel (midi_channel);
+    channel->add_event_handler (EventHandler (midi_channel, handler_func, handler_data, module));
+  }
+  void
+  remove_event_handler (guint              midi_channel,
+                       BseMidiEventHandler handler_func,
+                       gpointer           handler_data,
+                       BseModule         *module)
+  {
+    MidiChannel *channel = get_channel (midi_channel);
+    channel->remove_event_handler (EventHandler (midi_channel, handler_func, handler_data, module));
+  }
 };
 
 
@@ -1019,6 +1080,35 @@ MidiChannel::no_poly_voice (const gchar *event_name,
             mchannel->midi_channel, event_name, freq);
 }
 
+bool
+MidiChannel::call_event_handlers (BseMidiEvent *event,
+                                  BseTrans     *trans)
+{
+  bool success = false;
+  list<EventHandler>::iterator hi;
+  for (hi = event_handlers.begin(); hi != event_handlers.end(); hi++)
+    {
+      int activated = 0;
+      for (guint i = 0; i < n_voices; i++)
+       {
+         if (voices[i] && voices[i]->n_vinputs)
+           {
+             if (check_voice_switch_available_L (voices[i]))
+               {
+                 activated++;
+                 VoiceSwitch *vswitch = voices[i];
+                 activate_voice_switch_L (vswitch, event->delta_time, trans);
+               }
+           }
+       }
+      if (!(activated <= 1))
+       g_warning (G_STRLOC ": midi event handling: assertion (activated <= 1) failed, activated = %d", 
activated);
+      hi->handler_func (hi->handler_data, hi->module, event, trans);
+      success = true;
+    }
+  return success;
+}
+
 void
 MidiChannel::start_note (guint64         tick_stamp,
                          gfloat          freq,
@@ -1488,6 +1578,40 @@ bse_midi_receiver_remove_control_handler (BseMidiReceiver      *self,
 }
 
 void
+bse_midi_receiver_add_event_handler (BseMidiReceiver   *self,
+                                     guint              midi_channel,
+                                     BseMidiEventHandler handler_func,
+                                     gpointer           handler_data,
+                                     BseModule         *module)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail (midi_channel > 0);
+  g_return_if_fail (handler_func != NULL);
+  g_return_if_fail (module != NULL);
+
+  BSE_MIDI_RECEIVER_LOCK ();
+  self->add_event_handler (midi_channel, handler_func, handler_data, module);
+  BSE_MIDI_RECEIVER_UNLOCK ();
+}
+
+void
+bse_midi_receiver_remove_event_handler (BseMidiReceiver   *self,
+                                        guint              midi_channel,
+                                        BseMidiEventHandler handler_func,
+                                        gpointer           handler_data,
+                                        BseModule         *module)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail (midi_channel > 0);
+  g_return_if_fail (handler_func != NULL);
+  g_return_if_fail (module != NULL);
+
+  BSE_MIDI_RECEIVER_LOCK ();
+  self->remove_event_handler (midi_channel, handler_func, handler_data, module);
+  BSE_MIDI_RECEIVER_UNLOCK ();
+}
+
+void
 bse_midi_receiver_channel_enable_poly (BseMidiReceiver *self,
                                        guint            midi_channel)
 {
@@ -1938,79 +2062,85 @@ midi_receiver_process_event_L (BseMidiReceiver *self,
   if (event->delta_time <= max_tick_stamp)
     {
       BseTrans *trans = bse_trans_open ();
+      MidiChannel *mchannel = self->peek_channel (event->channel);
       self->events = sfi_ring_remove_node (self->events, self->events);
-      switch (event->status)
+
+      bool event_handled = false;
+      if (mchannel)
+       event_handled = mchannel->call_event_handlers (event, trans);
+
+      if (!event_handled)
        {
-          MidiChannel *mchannel;
-        case BSE_MIDI_NOTE_ON:
-          mchannel = self->peek_channel (event->channel);
-         EDEBUG ("MidiChannel[%u]: NoteOn  %fHz Velo=%f (stamp:%llu)", event->channel,
-                        event->data.note.frequency, event->data.note.velocity, event->delta_time);
-         if (mchannel)
-            mchannel->start_note (event->delta_time,
-                                  event->data.note.frequency,
-                                  event->data.note.velocity,
-                                  trans);
-          else
-            sfi_diag ("ignoring note-on (%fHz) for foreign midi channel: %u", event->data.note.frequency, 
event->channel);
-         break;
-       case BSE_MIDI_KEY_PRESSURE:
-       case BSE_MIDI_NOTE_OFF:
-          mchannel = self->peek_channel (event->channel);
-          EDEBUG ("MidiChannel[%u]: %s %fHz (stamp:%llu)", event->channel,
-                        event->status == BSE_MIDI_NOTE_OFF ? "NoteOff" : "NotePressure",
-                        event->data.note.frequency, event->delta_time);
-          if (mchannel)
-            {
-              gboolean sustained_note = event->status == BSE_MIDI_NOTE_OFF &&
-                                        (BSE_GCONFIG (invert_sustain) ^
-                                         (self->get_control (event->channel, BSE_MIDI_SIGNAL_CONTROL_64) >= 
0.5));
-              mchannel->adjust_note (event->delta_time,
-                                     event->data.note.frequency, event->status,
-                                     event->data.note.velocity, sustained_note, trans);
-            }
-         break;
-       case BSE_MIDI_CONTROL_CHANGE:
-         EDEBUG ("MidiChannel[%u]: Control %2u Value=%f (stamp:%llu)", event->channel,
-                        event->data.control.control, event->data.control.value, event->delta_time);
-         process_midi_control_L (self, event->channel, event->delta_time,
-                                 event->data.control.control, event->data.control.value,
-                                 FALSE,
-                                  trans);
-         break;
-       case BSE_MIDI_X_CONTINUOUS_CHANGE:
-         EDEBUG ("MidiChannel[%u]: X Continuous Control %2u Value=%f (stamp:%llu)", event->channel,
-                        event->data.control.control, event->data.control.value, event->delta_time);
-         process_midi_control_L (self, event->channel, event->delta_time,
-                                 event->data.control.control, event->data.control.value,
-                                  TRUE,
-                                 trans);
-         break;
-       case BSE_MIDI_PROGRAM_CHANGE:
-         EDEBUG ("MidiChannel[%u]: Program %u (Value=%f) (stamp:%llu)", event->channel,
-                        event->data.program, event->data.program / (gfloat) 0x7f, event->delta_time);
-         update_midi_signal_L (self, event->channel, event->delta_time,
-                               BSE_MIDI_SIGNAL_PROGRAM, event->data.program / (gfloat) 0x7f,
-                               trans);
-         break;
-       case BSE_MIDI_CHANNEL_PRESSURE:
-         EDEBUG ("MidiChannel[%u]: Channel Pressure Value=%f (stamp:%llu)", event->channel,
-                        event->data.intensity, event->delta_time);
-         update_midi_signal_L (self, event->channel, event->delta_time,
-                               BSE_MIDI_SIGNAL_PRESSURE, event->data.intensity,
-                               trans);
-         break;
-       case BSE_MIDI_PITCH_BEND:
-         EDEBUG ("MidiChannel[%u]: Pitch Bend Value=%f (stamp:%llu)", event->channel,
-                        event->data.pitch_bend, event->delta_time);
-         update_midi_signal_L (self, event->channel, event->delta_time,
-                               BSE_MIDI_SIGNAL_PITCH_BEND, event->data.pitch_bend,
-                               trans);
-         break;
-       default:
-         EDEBUG ("MidiChannel[%u]: Ignoring Event %u (stamp:%llu)", event->channel,
-                        event->status, event->delta_time);
-         break;
+         switch (event->status)
+           {
+           case BSE_MIDI_NOTE_ON:
+             EDEBUG ("MidiChannel[%u]: NoteOn  %fHz Velo=%f (stamp:%llu)", event->channel,
+                      event->data.note.frequency, event->data.note.velocity, event->delta_time);
+             if (mchannel)
+               mchannel->start_note (event->delta_time,
+                                     event->data.note.frequency,
+                                     event->data.note.velocity,
+                                     trans);
+             else
+               sfi_diag ("ignoring note-on (%fHz) for foreign midi channel: %u", event->data.note.frequency, 
event->channel);
+             break;
+           case BSE_MIDI_KEY_PRESSURE:
+           case BSE_MIDI_NOTE_OFF:
+             EDEBUG ("MidiChannel[%u]: %s %fHz (stamp:%llu)", event->channel,
+                      event->status == BSE_MIDI_NOTE_OFF ? "NoteOff" : "NotePressure",
+                      event->data.note.frequency, event->delta_time);
+             if (mchannel)
+               {
+                 gboolean sustained_note = event->status == BSE_MIDI_NOTE_OFF &&
+                                           (BSE_GCONFIG (invert_sustain) ^
+                                            (self->get_control (event->channel, BSE_MIDI_SIGNAL_CONTROL_64) 
= 0.5));
+                 mchannel->adjust_note (event->delta_time,
+                                        event->data.note.frequency, event->status,
+                                        event->data.note.velocity, sustained_note, trans);
+               }
+             break;
+           case BSE_MIDI_CONTROL_CHANGE:
+             EDEBUG ("MidiChannel[%u]: Control %2u Value=%f (stamp:%llu)", event->channel,
+                      event->data.control.control, event->data.control.value, event->delta_time);
+             process_midi_control_L (self, event->channel, event->delta_time,
+                                     event->data.control.control, event->data.control.value,
+                                     FALSE,
+                                     trans);
+             break;
+           case BSE_MIDI_X_CONTINUOUS_CHANGE:
+             EDEBUG ("MidiChannel[%u]: X Continuous Control %2u Value=%f (stamp:%llu)", event->channel,
+                      event->data.control.control, event->data.control.value, event->delta_time);
+             process_midi_control_L (self, event->channel, event->delta_time,
+                                     event->data.control.control, event->data.control.value,
+                                     TRUE,
+                                     trans);
+             break;
+           case BSE_MIDI_PROGRAM_CHANGE:
+             EDEBUG ("MidiChannel[%u]: Program %u (Value=%f) (stamp:%llu)", event->channel,
+                      event->data.program, event->data.program / (gfloat) 0x7f, event->delta_time);
+             update_midi_signal_L (self, event->channel, event->delta_time,
+                                   BSE_MIDI_SIGNAL_PROGRAM, event->data.program / (gfloat) 0x7f,
+                                   trans);
+             break;
+           case BSE_MIDI_CHANNEL_PRESSURE:
+             EDEBUG ("MidiChannel[%u]: Channel Pressure Value=%f (stamp:%llu)", event->channel,
+                      event->data.intensity, event->delta_time);
+             update_midi_signal_L (self, event->channel, event->delta_time,
+                                   BSE_MIDI_SIGNAL_PRESSURE, event->data.intensity,
+                                   trans);
+             break;
+           case BSE_MIDI_PITCH_BEND:
+             EDEBUG ("MidiChannel[%u]: Pitch Bend Value=%f (stamp:%llu)", event->channel,
+                      event->data.pitch_bend, event->delta_time);
+             update_midi_signal_L (self, event->channel, event->delta_time,
+                                   BSE_MIDI_SIGNAL_PITCH_BEND, event->data.pitch_bend,
+                                   trans);
+             break;
+           default:
+             EDEBUG ("MidiChannel[%u]: Ignoring Event %u (stamp:%llu)", event->channel,
+                      event->status, event->delta_time);
+             break;
+           }
        }
       if (self->notifier)
        {
diff --git a/bse/bsemidireceiver.hh b/bse/bsemidireceiver.hh
index 915de2c..d7a1de6 100644
--- a/bse/bsemidireceiver.hh
+++ b/bse/bsemidireceiver.hh
@@ -20,6 +20,10 @@ typedef void   (*BseMidiControlHandler)                    (gpointer           h
                                                             BseModule  *const *modules,
                                                             gpointer           user_data,
                                                             BseTrans          *trans);
+typedef void   (*BseMidiEventHandler)                      (gpointer           handler_data,
+                                                           BseModule         *module,
+                                                            const BseMidiEvent *event,
+                                                            BseTrans          *trans);
 BseMidiReceiver* bse_midi_receiver_new                     (const gchar       *receiver_name);
 BseMidiReceiver* bse_midi_receiver_ref                     (BseMidiReceiver   *self);
 void             bse_midi_receiver_unref                   (BseMidiReceiver   *self);
@@ -53,6 +57,16 @@ void             bse_midi_receiver_remove_control_handler  (BseMidiReceiver   *s
                                                             BseMidiControlHandler handler_func,
                                                             gpointer           handler_data,
                                                             BseModule         *module);
+void             bse_midi_receiver_add_event_handler       (BseMidiReceiver   *self,
+                                                            guint              midi_channel,
+                                                            BseMidiEventHandler handler_func,
+                                                            gpointer           handler_data,
+                                                            BseModule         *module);
+void             bse_midi_receiver_remove_event_handler    (BseMidiReceiver   *self,
+                                                            guint              midi_channel,
+                                                            BseMidiEventHandler handler_func,
+                                                            gpointer           handler_data,
+                                                            BseModule         *module);
 BseModule*       bse_midi_receiver_retrieve_mono_voice     (BseMidiReceiver   *self,
                                                             guint              midi_channel,
                                                             BseTrans          *trans);
diff --git a/bse/bseproject.cc b/bse/bseproject.cc
index e278a35..039c21b 100644
--- a/bse/bseproject.cc
+++ b/bse/bseproject.cc
@@ -16,6 +16,7 @@
 #include "bsemidinotifier.hh"
 #include "gslcommon.hh"
 #include "bseengine.hh"
+#include "bsesoundfontrepo.h"
 #include <string.h>
 #include <stdlib.h>
 #include <fcntl.h>
@@ -181,9 +182,15 @@ bse_project_init (BseProject *self,
   self->midi_receiver = bse_midi_receiver_new ("BseProjectReceiver");
   bse_midi_receiver_enter_farm (self->midi_receiver);
   /* we always have a wave-repo */
-  BseWaveRepo *wrepo = (BseWaveRepo*) bse_container_new_child (BSE_CONTAINER (self), BSE_TYPE_WAVE_REPO, 
"uname", "Wave-Repository", NULL);
+  BseWaveRepo *wrepo = bse_container_new_child (BSE_CONTAINER (self), BSE_TYPE_WAVE_REPO,
+                                                "uname", "Wave-Repository",
+                                                NULL);
+  BseSoundFontRepo *sfrepo = bse_container_new_child (BSE_CONTAINER (self), BSE_TYPE_SOUND_FONT_REPO,
+                                                      "uname", "Sound-Font-Repository",
+                                                      NULL);
   /* with fixed uname */
   BSE_OBJECT_SET_FLAGS (wrepo, BSE_OBJECT_FLAG_FIXED_UNAME);
+  BSE_OBJECT_SET_FLAGS (sfrepo, BSE_OBJECT_FLAG_FIXED_UNAME);
 }
 
 static void
@@ -414,6 +421,13 @@ bse_project_retrieve_child (BseContainer *container,
       g_warning ("%s: no wave-repo found in project\n", G_STRLOC);
       return NULL;     /* shouldn't happen */
     }
+  else if (g_type_is_a (child_type, BSE_TYPE_SOUND_FONT_REPO)) /* and the same sound font repo */
+    {
+      BseSoundFontRepo *sfrepo = bse_project_get_sound_font_repo (self);
+      if (!sfrepo)
+       g_warning ("%s: no sound-font-repo found in project\n", G_STRLOC);
+      return BSE_ITEM (sfrepo);
+    }
   else
     {
       BseItem *item = BSE_CONTAINER_CLASS (parent_class)->retrieve_child (container, child_type, uname);
@@ -633,6 +647,17 @@ bse_project_get_wave_repo (BseProject *self)
   return NULL;
 }
 
+BseSoundFontRepo*
+bse_project_get_sound_font_repo (BseProject *self)
+{
+  g_return_val_if_fail (BSE_IS_PROJECT (self), NULL);
+  GSList *slist;
+  for (slist = self->supers; slist; slist = slist->next)
+    if (BSE_IS_SOUND_FONT_REPO (slist->data))
+      return slist->data;
+  return NULL;
+}
+
 BseSong*
 bse_project_get_song (BseProject *self)
 {
diff --git a/bse/bseproject.hh b/bse/bseproject.hh
index f4ebe16..48877b0 100644
--- a/bse/bseproject.hh
+++ b/bse/bseproject.hh
@@ -66,6 +66,7 @@ BseItem*      bse_project_lookup_typed_item   (BseProject     *project,
                                                 GType           item_type,
                                                 const gchar    *uname);
 BseWaveRepo*   bse_project_get_wave_repo       (BseProject     *project);
+BseSoundFontRepo* bse_project_get_sound_font_repo (BseProject   *project);
 BseSong*       bse_project_get_song            (BseProject     *project);
 BseSNet*       bse_project_create_intern_synth (BseProject     *project,
                                                 const gchar    *synth_name,
diff --git a/bse/bseproject.proc b/bse/bseproject.proc
index ff4a8d5..2dc1d52 100644
--- a/bse/bseproject.proc
+++ b/bse/bseproject.proc
@@ -6,6 +6,7 @@
 #include <bse/bsesong.hh>
 #include <bse/bseundostack.hh>
 #include <bse/bsewaverepo.hh>
+#include <bse/bsesoundfontrepo.h>
 #include <bse/bsecsynth.hh>
 #include <bse/bsemidisynth.hh>
 #include <bse/bsedatapocket.hh>
@@ -15,7 +16,6 @@
 #include <bse/bseengine.hh>
 #include "bsecxxplugin.hh"
 
-
 AUTHORS = "Tim Janik <timj gtk org>";
 LICENSE = "GNU Lesser General Public License";
 
@@ -286,6 +286,34 @@ METHOD (BseProject, get-wave-repo) {
   return BSE_ERROR_NONE;
 }
 
+METHOD (BseProject, get-sound-font-repo) {
+  HELP  = "Get sound font repository for project";
+  IN    = bse_param_spec_object ("project", "Project", "The project",
+                                 BSE_TYPE_PROJECT, SFI_PARAM_STANDARD);
+  OUT   = bse_param_spec_object ("sfrepo", "Sound Font Repo", "The project's unique sound font repo",
+                                 BSE_TYPE_SOUND_FONT_REPO, SFI_PARAM_STANDARD);
+
+} BODY (BseProcedureClass *proc,
+        const GValue      *in_values,
+        GValue            *out_values)
+{
+  /* extract parameter values */
+  BseProject *project = bse_value_get_object (in_values++);
+  BseSoundFontRepo *sfrepo = NULL;
+
+  /* check parameters */
+  if (!BSE_IS_PROJECT (project))
+    return BSE_ERROR_PROC_PARAM_INVAL;
+
+  /* action */
+  sfrepo = bse_project_get_sound_font_repo (project);
+
+  /* set output parameters */
+  bse_value_set_object (out_values++, sfrepo);
+
+  return BSE_ERROR_NONE;
+}
+
 METHOD (BseProject, get-data-pocket) {
   HELP  = "Retrieve a specifically named data pocket for this project";
   IN    = bse_param_spec_object ("project", "Project", "The project",
diff --git a/bse/bsesoundfont.c b/bse/bsesoundfont.c
new file mode 100644
index 0000000..096d749
--- /dev/null
+++ b/bse/bsesoundfont.c
@@ -0,0 +1,394 @@
+/* BSE - Bedevilled Sound Engine
+ * Copyright (C) 1997-1999, 2000-2005 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#include "bsesoundfont.h"
+#include "bsesoundfontrepo.h"
+#include "bsesoundfontpreset.h"
+#include "bsemain.h"
+#include "bsestorage.h"
+#include "bseprocedure.h"
+#include "gsldatahandle.h"
+#include "bseserver.h"
+#include "bseloader.h"
+
+#include <string.h>
+
+#define parse_or_return         bse_storage_scanner_parse_or_return
+
+enum {
+  PARAM_0,
+  PARAM_FILE_NAME,
+};
+
+/* --- prototypes --- */
+
+
+/* --- variables --- */
+static GTypeClass *parent_class = NULL;
+static GQuark      quark_load_sound_font = 0;
+
+
+/* --- functions --- */
+static void
+bse_sound_font_init (BseSoundFont *sound_font)
+{
+  sound_font->blob = NULL;
+  sound_font->sfont_id = -1;
+  sound_font->sfrepo = NULL;
+}
+
+static void
+bse_sound_font_set_property (GObject      *object,
+                            guint         param_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  switch (param_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    }
+}
+
+static void
+bse_sound_font_get_property (GObject    *object,
+                            guint       param_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  BseSoundFont *sound_font = BSE_SOUND_FONT (object);
+  switch (param_id)
+    {
+    case PARAM_FILE_NAME:
+      if (sound_font->blob)
+        sfi_value_set_string (value, bse_storage_blob_file_name (sound_font->blob));
+      else
+        sfi_value_set_string (value, NULL);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    }
+}
+
+static void
+bse_sound_font_dispose (GObject *object)
+{
+  BseSoundFont *sound_font = BSE_SOUND_FONT (object);
+  if (sound_font->sfont_id != -1)
+    bse_sound_font_unload (sound_font);
+  if (sound_font->sfrepo)
+    {
+      g_object_unref (sound_font->sfrepo);
+      sound_font->sfrepo = NULL;
+    }
+  /* chain parent class' handler */
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+bse_sound_font_finalize (GObject *object)
+{
+  BseSoundFont *sound_font = BSE_SOUND_FONT (object);
+
+  /* free blob */
+  if (sound_font->blob)
+    {
+      bse_storage_blob_unref (sound_font->blob);
+      sound_font->blob = NULL;
+    }
+
+  if (sound_font->sfrepo != NULL || sound_font->blob != NULL || sound_font->sfont_id != -1)
+    g_warning (G_STRLOC ": some resources could not be freed.");
+
+  /* chain parent class' handler */
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+BseErrorType
+bse_sound_font_load_blob (BseSoundFont    *self,
+                          BseStorageBlob  *blob,
+                         gboolean         init_presets)
+{
+  if (self->sfrepo == NULL)
+    {
+      self->sfrepo = BSE_SOUND_FONT_REPO (BSE_ITEM (self)->parent);
+      g_object_ref (self->sfrepo);
+    }
+
+  g_return_val_if_fail (blob != NULL, BSE_ERROR_INTERNAL);
+  g_return_val_if_fail (self->sfrepo != NULL, BSE_ERROR_INTERNAL);
+  g_return_val_if_fail (self->sfont_id == -1, BSE_ERROR_INTERNAL);
+
+  bse_storage_blob_ref (blob);
+  if (self->blob)
+    {
+      bse_storage_blob_unref (self->blob);
+      self->blob = NULL;
+    }
+
+  fluid_synth_t *fluid_synth = bse_sound_font_repo_lock_fluid_synth (self->sfrepo);
+  int sfont_id = fluid_synth_sfload (fluid_synth, bse_storage_blob_file_name (blob), 0);
+  BseErrorType error;
+  if (sfont_id != -1)
+    {
+      if (init_presets)
+       {
+         fluid_sfont_t *fluid_sfont = fluid_synth_get_sfont_by_id (fluid_synth, sfont_id);
+         fluid_preset_t fluid_preset;
+
+         fluid_sfont->iteration_start (fluid_sfont);
+         while (fluid_sfont->iteration_next (fluid_sfont, &fluid_preset))
+           {
+             BseSoundFontPreset *sound_font_preset = g_object_new (BSE_TYPE_SOUND_FONT_PRESET,
+                                                                   "uname", fluid_preset.get_name 
(&fluid_preset),
+                                                                   NULL);
+             bse_container_add_item (BSE_CONTAINER (self), BSE_ITEM (sound_font_preset));
+             bse_sound_font_preset_init_preset (sound_font_preset, &fluid_preset);
+           }
+       }
+      self->sfont_id = sfont_id;
+      self->blob = blob;
+      error = BSE_ERROR_NONE;
+    }
+  else
+    {
+      bse_storage_blob_unref (blob);
+      error = BSE_ERROR_WAVE_NOT_FOUND;
+    }
+  bse_sound_font_repo_unlock_fluid_synth (self->sfrepo);
+  return error;
+}
+
+void
+bse_sound_font_unload (BseSoundFont *sound_font)
+{
+  g_return_if_fail (sound_font->sfrepo != NULL);
+
+  if (sound_font->sfont_id != -1)
+    {
+      fluid_synth_t *fluid_synth = bse_sound_font_repo_lock_fluid_synth (sound_font->sfrepo);
+      fluid_synth_sfunload (fluid_synth, sound_font->sfont_id, 1 /* reset presets */);
+      bse_sound_font_repo_unlock_fluid_synth (sound_font->sfrepo);
+    }
+  sound_font->sfont_id = -1;
+}
+
+BseErrorType
+bse_sound_font_reload (BseSoundFont *sound_font)
+{
+  g_return_val_if_fail (sound_font->sfont_id == -1, BSE_ERROR_INTERNAL);
+
+  return bse_sound_font_load_blob (sound_font, sound_font->blob, FALSE);
+}
+
+static void
+bse_sound_font_store_private (BseObject  *object,
+                             BseStorage *storage)
+{
+  BseSoundFont *sound_font = BSE_SOUND_FONT (object);
+  /* chain parent class' handler */
+  BSE_OBJECT_CLASS (parent_class)->store_private (object, storage);
+
+  if (!BSE_STORAGE_SELF_CONTAINED (storage) && !bse_storage_blob_is_temp_file (sound_font->blob))
+    {
+      bse_storage_break (storage);
+      bse_storage_printf (storage, "(load-sound-font \"%s\")", bse_storage_blob_file_name 
(sound_font->blob));
+    }
+  else
+    {
+      bse_storage_break (storage);
+      bse_storage_printf (storage, "(load-sound-font ");
+      bse_storage_put_blob (storage, sound_font->blob);
+      bse_storage_printf (storage, ")");
+    }
+}
+
+static SfiTokenType
+bse_sound_font_restore_private (BseObject  *object,
+                               BseStorage *storage,
+                                GScanner   *scanner)
+{
+  BseSoundFont *sound_font = BSE_SOUND_FONT (object);
+  GTokenType expected_token;
+  GQuark quark;
+
+  /* chain parent class' handler */
+  if (g_scanner_peek_next_token (scanner) != G_TOKEN_IDENTIFIER)
+    return BSE_OBJECT_CLASS (parent_class)->restore_private (object, storage, scanner);
+
+  /* parse storage commands */
+  quark = g_quark_try_string (scanner->next_value.v_identifier);
+  if (quark == quark_load_sound_font)
+    {
+      BseStorageBlob *blob;
+      BseErrorType error;
+
+      g_scanner_get_next_token (scanner); /* eat quark identifier */
+      if (g_scanner_peek_next_token (scanner) == G_TOKEN_STRING)
+       {
+         parse_or_return (scanner, G_TOKEN_STRING);
+         blob = bse_storage_blob_new_from_file (scanner->value.v_string, FALSE);
+       }
+      else
+       {
+          GTokenType token = bse_storage_parse_blob (storage, &blob);
+         if (token != G_TOKEN_NONE)
+           {
+             if (blob)
+               bse_storage_blob_unref (blob);
+              return token;
+           }
+       }
+      if (g_scanner_peek_next_token (scanner) != ')')
+       {
+         bse_storage_blob_unref (blob);
+         return ')';
+       }
+      parse_or_return (scanner, ')');
+      error = bse_sound_font_load_blob (sound_font, blob, FALSE);
+      if (error)
+       bse_storage_warn (storage, "failed to load sound font \"%s\": %s",
+                                   bse_storage_blob_file_name (blob), bse_error_blurb (error));
+      bse_storage_blob_unref (blob);
+      expected_token = G_TOKEN_NONE; /* got ')' */
+    }
+  else /* chain parent class' handler */
+    expected_token = BSE_OBJECT_CLASS (parent_class)->restore_private (object, storage, scanner);
+
+  return expected_token;
+}
+
+
+static void
+bse_sound_font_add_item (BseContainer *container,
+                        BseItem      *item)
+{
+  BseSoundFont *sound_font = BSE_SOUND_FONT (container);
+
+  if (g_type_is_a (BSE_OBJECT_TYPE (item), BSE_TYPE_SOUND_FONT_PRESET))
+    sound_font->presets = g_list_append (sound_font->presets, item);
+  else
+    g_warning ("BseSoundFont: cannot hold non-sound-font-preset item type `%s'",
+              BSE_OBJECT_TYPE_NAME (item));
+
+  /* chain parent class' add_item handler */
+  BSE_CONTAINER_CLASS (parent_class)->add_item (container, item);
+}
+
+static void
+bse_sound_font_forall_items (BseContainer      *container,
+                            BseForallItemsFunc func,
+                            gpointer           data)
+{
+  BseSoundFont *sound_font = BSE_SOUND_FONT (container);
+  GList *list;
+
+  list = sound_font->presets;
+  while (list)
+    {
+      BseItem *item;
+
+      item = list->data;
+      list = list->next;
+      if (!func (item, data))
+       return;
+    }
+}
+
+static void
+bse_sound_font_remove_item (BseContainer *container,
+                           BseItem      *item)
+{
+  BseSoundFont *sound_font = BSE_SOUND_FONT (container);
+
+  if (g_type_is_a (BSE_OBJECT_TYPE (item), BSE_TYPE_SOUND_FONT_PRESET))
+    sound_font->presets = g_list_remove (sound_font->presets, item);
+  else
+    g_warning ("BseSoundFontRepo: cannot hold non-sound-font-preset item type `%s'",
+              BSE_OBJECT_TYPE_NAME (item));
+
+  /* chain parent class' remove_item handler */
+  BSE_CONTAINER_CLASS (parent_class)->remove_item (container, item);
+}
+
+static void
+bse_sound_font_release_children (BseContainer *container)
+{
+  BseSoundFont *self = BSE_SOUND_FONT (container);
+
+  while (self->presets)
+    bse_container_remove_item (container, self->presets->data);
+
+  /* chain parent class' handler */
+  BSE_CONTAINER_CLASS (parent_class)->release_children (container);
+}
+
+
+static void
+bse_sound_font_class_init (BseSoundFontClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+  BseObjectClass *object_class = BSE_OBJECT_CLASS (class);
+  BseContainerClass *container_class = BSE_CONTAINER_CLASS (class);
+
+  parent_class = g_type_class_peek_parent (class);
+
+  gobject_class->set_property = bse_sound_font_set_property;
+  gobject_class->get_property = bse_sound_font_get_property;
+  gobject_class->dispose = bse_sound_font_dispose;
+  gobject_class->finalize = bse_sound_font_finalize;
+
+  container_class->add_item = bse_sound_font_add_item;
+  container_class->remove_item = bse_sound_font_remove_item;
+  container_class->forall_items = bse_sound_font_forall_items;
+  container_class->release_children = bse_sound_font_release_children;
+
+  object_class->store_private = bse_sound_font_store_private;
+  object_class->restore_private = bse_sound_font_restore_private;
+
+  quark_load_sound_font = g_quark_from_static_string ("load-sound-font");
+
+  bse_object_class_add_param (object_class, "Locator",
+                             PARAM_FILE_NAME,
+                             sfi_pspec_string ("file_name", "File Name", NULL,
+                                               NULL, "G:r"));
+}
+
+BSE_BUILTIN_TYPE (BseSoundFont)
+{
+  static const GTypeInfo sound_font_info = {
+    sizeof (BseSoundFontClass),
+
+    (GBaseInitFunc) NULL,
+    (GBaseFinalizeFunc) NULL,
+    (GClassInitFunc) bse_sound_font_class_init,
+    (GClassFinalizeFunc) NULL,
+    NULL /* class_data */,
+
+    sizeof (BseSoundFont),
+    0  /* n_preallocs */,
+    (GInstanceInitFunc) bse_sound_font_init,
+  };
+
+  return bse_type_register_static (BSE_TYPE_CONTAINER,
+                                  "BseSoundFont",
+                                  "BSE sound_font type",
+                                   __FILE__, __LINE__,
+                                   &sound_font_info);
+}
diff --git a/bse/bsesoundfont.h b/bse/bsesoundfont.h
new file mode 100644
index 0000000..6705f8e
--- /dev/null
+++ b/bse/bsesoundfont.h
@@ -0,0 +1,59 @@
+/* BSE - Bedevilled Sound Engine
+ * Copyright (C) 1997-1999, 2000-2005 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#ifndef __BSE_SOUND_FONT_H__
+#define __BSE_SOUND_FONT_H__
+
+#include       <bse/bsecontainer.h>
+#include        <bse/bsestorage.h>
+
+G_BEGIN_DECLS
+
+/* --- BSE type macros --- */
+#define BSE_TYPE_SOUND_FONT              (BSE_TYPE_ID (BseSoundFont))
+#define BSE_SOUND_FONT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), BSE_TYPE_SOUND_FONT, 
BseSoundFont))
+#define BSE_SOUND_FONT_CLASS(class)      (G_TYPE_CHECK_CLASS_CAST ((class), BSE_TYPE_SOUND_FONT, 
BseSoundFontClass))
+#define BSE_IS_SOUND_FONT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), BSE_TYPE_SOUND_FONT))
+#define BSE_IS_SOUND_FONT_CLASS(class)   (G_TYPE_CHECK_CLASS_TYPE ((class), BSE_TYPE_SOUND_FONT))
+#define BSE_SOUND_FONT_GET_CLASS(object)  (G_TYPE_INSTANCE_GET_CLASS ((object), BSE_TYPE_SOUND_FONT, 
BseSoundFontClass))
+
+
+/* --- BseSoundFont --- */
+struct _BseSoundFont
+{
+  BseContainer      parent_object;
+  BseStorageBlob    *blob;
+  int                sfont_id;
+  BseSoundFontRepo  *sfrepo;
+  GList             *presets;
+};
+struct _BseSoundFontClass
+{
+  BseContainerClass  parent_class;
+};
+
+
+/* --- prototypes -- */
+BseErrorType    bse_sound_font_load_blob       (BseSoundFont       *sound_font,
+                                                BseStorageBlob     *blob,
+                                                gboolean            init_presets);
+void           bse_sound_font_unload           (BseSoundFont       *sound_font);
+BseErrorType    bse_sound_font_reload           (BseSoundFont       *sound_font);
+
+G_END_DECLS
+
+#endif /* __BSE_SOUND_FONT_H__ */
diff --git a/bse/bsesoundfontosc.c b/bse/bsesoundfontosc.c
new file mode 100644
index 0000000..1ba2b9e
--- /dev/null
+++ b/bse/bsesoundfontosc.c
@@ -0,0 +1,617 @@
+/* BseSoundFontOsc - BSE Fluid Synth
+ * Copyright (C) 1999-2002 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#include "bsesoundfontosc.h"
+
+#include <bse/bsecategories.h>
+#include <bse/bseengine.h>
+#include <bse/bseproject.h>
+#include <bse/bsesoundfontrepo.h>
+#include <bse/bsesoundfont.h>
+#include <bse/bsesnet.h>
+#include <bse/bsemidireceiver.h>
+#include "gslcommon.h"
+
+#include <string.h>
+
+/* --- parameters --- */
+enum
+{
+  PARAM_0,
+  PARAM_PRESET
+};
+
+
+/* --- prototypes --- */
+static void     bse_sound_font_osc_init          (BseSoundFontOsc       *sound_font_osc);
+static void     bse_sound_font_osc_class_init    (BseSoundFontOscClass  *class);
+static void     bse_sound_font_osc_set_property  (GObject               *object,
+                                                  guint                  param_id,
+                                                  const GValue          *value,
+                                                  GParamSpec            *pspec);
+static void     bse_sound_font_osc_get_property  (GObject               *object,
+                                                  guint                  param_id,
+                                                  GValue                *value,
+                                                  GParamSpec            *pspec);
+static void     bse_sound_font_osc_get_candidates (BseItem              *item,
+                                                   guint                 param_id,
+                                                   BsePropertyCandidates *pc,
+                                                   GParamSpec           *pspec);
+static void     bse_sound_font_osc_context_create (BseSource            *source,
+                                                   guint                 context_handle,
+                                                   BseTrans             *trans);
+static void     bse_sound_font_osc_context_dismiss (BseSource           *source,
+                                                    guint                context_handle,
+                                                    BseTrans            *trans);
+static void     bse_sound_font_osc_update_modules (BseSoundFontOsc      *sound_font_osc,
+                                                   BseTrans             *trans);
+static void      bse_sound_font_osc_dispose        (GObject              *object);
+static void      bse_sound_font_osc_finalize       (GObject              *object);
+
+
+/* --- variables --- */
+static gpointer         parent_class = NULL;
+
+
+/* --- functions --- */
+BSE_BUILTIN_TYPE (BseSoundFontOsc)
+{
+  static const GTypeInfo type_info = {
+    sizeof (BseSoundFontOscClass),
+
+    (GBaseInitFunc) NULL,
+    (GBaseFinalizeFunc) NULL,
+    (GClassInitFunc) bse_sound_font_osc_class_init,
+    (GClassFinalizeFunc) NULL,
+    NULL /* class_data */,
+
+    sizeof (BseSoundFontOsc),
+    0 /* n_preallocs */,
+    (GInstanceInitFunc) bse_sound_font_osc_init,
+  };
+  GType type_id;
+
+  type_id = bse_type_register_static (BSE_TYPE_SOURCE,
+                                     "BseSoundFontOsc",
+                                     "This internal module wraps fluid synth which plays sound font 
contents",
+                                      __FILE__, __LINE__,
+                                      &type_info);
+  return type_id;
+}
+
+static void
+bse_sound_font_osc_class_init (BseSoundFontOscClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+  BseObjectClass *object_class = BSE_OBJECT_CLASS (class);
+  BseSourceClass *source_class = BSE_SOURCE_CLASS (class);
+  BseItemClass *item_class = BSE_ITEM_CLASS (class);
+  guint ochannel;
+
+  parent_class = g_type_class_peek_parent (class);
+
+  gobject_class->set_property = bse_sound_font_osc_set_property;
+  gobject_class->get_property = bse_sound_font_osc_get_property;
+  gobject_class->finalize = bse_sound_font_osc_finalize;
+  gobject_class->dispose = bse_sound_font_osc_dispose;
+
+  item_class->get_candidates = bse_sound_font_osc_get_candidates;
+
+  source_class->context_create = bse_sound_font_osc_context_create;
+  source_class->context_dismiss = bse_sound_font_osc_context_dismiss;
+
+  bse_object_class_add_param (object_class, _("Sound Font Preset"),
+                              PARAM_PRESET,
+                              bse_param_spec_object ("preset", _("Preset"), _("Sound Font Preset to be used 
during replay"),
+                                                     BSE_TYPE_SOUND_FONT_PRESET, SFI_PARAM_STANDARD));
+
+  ochannel = bse_source_class_add_ochannel (source_class, "left-out", _("Left Out"), _("Output of the fluid 
synth soundfont synthesizer"));
+  g_assert (ochannel == BSE_SOUND_FONT_OSC_OCHANNEL_LEFT_OUT);
+  ochannel = bse_source_class_add_ochannel (source_class, "right-out", _("Right Out"), _("Output of the 
fluid synth soundfont synthesizer"));
+  g_assert (ochannel == BSE_SOUND_FONT_OSC_OCHANNEL_RIGHT_OUT);
+  ochannel = bse_source_class_add_ochannel (source_class, "done-out", _("Done Out"), _("Done Output"));
+  g_assert (ochannel == BSE_SOUND_FONT_OSC_OCHANNEL_DONE_OUT);
+}
+
+static void
+bse_sound_font_osc_init (BseSoundFontOsc *self)
+{
+  memset (&self->config, 0, sizeof (self->config));
+  self->preset = NULL;
+}
+
+static void
+bse_sound_font_osc_dispose (GObject *object)
+{
+  BseSoundFontOsc *self = BSE_SOUND_FONT_OSC (object);
+
+  if (self->config.sfrepo)
+    {
+      bse_sound_font_repo_remove_osc (self->config.sfrepo, self->config.osc_id);
+
+      self->config.sfrepo = NULL;
+    }
+
+  /* chain parent class' handler */
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+bse_sound_font_osc_finalize (GObject *object)
+{
+  //BseSoundFontOsc *self = BSE_SOUND_FONT_OSC (object);
+
+  /* chain parent class' handler */
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static BseSoundFontRepo*
+get_sfrepo (BseSoundFontOsc *self)
+{
+  if (!self->config.sfrepo)
+    {
+      BseProject *project = bse_item_get_project (BSE_ITEM (self));
+      if (project)
+       {
+         self->config.sfrepo = bse_project_get_sound_font_repo (project);
+         self->config.osc_id = bse_sound_font_repo_add_osc (self->config.sfrepo, self);
+       }
+      else
+       {
+         g_warning ("BseSoundFontOsc: could not find sfrepo\n");
+         self->config.sfrepo = NULL;
+       }
+    }
+  return self->config.sfrepo;
+}
+
+static void
+bse_sound_font_osc_uncross_preset (BseItem *owner,
+                                  BseItem *ref_item)
+{
+  BseSoundFontOsc *self = BSE_SOUND_FONT_OSC (owner);
+  bse_item_set (self, "preset", NULL, NULL);
+}
+
+
+static void
+bse_sound_font_osc_set_property (GObject      *object,
+                                guint         param_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  BseSoundFontOsc *self = BSE_SOUND_FONT_OSC (object);
+
+  switch (param_id)
+    {
+      BseSoundFontPreset *preset;
+    case PARAM_PRESET:
+      preset = bse_value_get_object (value);
+      if (preset != self->preset)
+        {
+          self->preset = preset;
+          if (self->preset)
+            {
+              bse_item_cross_link (BSE_ITEM (self), BSE_ITEM (self->preset), 
bse_sound_font_osc_uncross_preset);
+              bse_object_proxy_notifies (self->preset, self, "notify::preset");
+             self->config.sfont_id = BSE_SOUND_FONT (BSE_ITEM (self->preset)->parent)->sfont_id;
+             self->config.bank = self->preset->bank;
+             self->config.program = self->preset->program;
+             self->config.update_preset++;
+              bse_sound_font_osc_update_modules (self, NULL);
+            }
+        }
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, param_id, pspec);
+      break;
+    }
+}
+
+static void
+bse_sound_font_osc_get_property (GObject     *object,
+                                guint        param_id,
+                                GValue      *value,
+                                GParamSpec  *pspec)
+{
+  BseSoundFontOsc *self = BSE_SOUND_FONT_OSC (object);
+
+  switch (param_id)
+    {
+    case PARAM_PRESET:
+      bse_value_set_object (value, self->preset);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, param_id, pspec);
+      break;
+    }
+}
+
+
+static void
+bse_sound_font_osc_get_candidates (BseItem               *item,
+                                   guint                  param_id,
+                                   BsePropertyCandidates *pc,
+                                   GParamSpec            *pspec)
+{
+  BseSoundFontOsc *self = BSE_SOUND_FONT_OSC (item);
+  switch (param_id)
+    {
+    case PARAM_PRESET:
+      bse_property_candidate_relabel (pc, _("Available Presets"), _("List of available sound font presets to 
choose as fluid synth preset"));
+      bse_sound_font_repo_list_all_presets (get_sfrepo (self), pc->items);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, param_id, pspec);
+      break;
+    }
+}
+
+
+typedef struct
+{
+  BseSoundFontOscConfig        config;
+  int                  last_update_preset;
+  guint64               n_silence_samples;    // for done detection
+} SoundFontOscModule;
+
+static void
+bse_sound_font_osc_update_modules (BseSoundFontOsc *sound_font_osc,
+                                  BseTrans        *trans)
+{
+  get_sfrepo (sound_font_osc);
+
+  if (BSE_SOURCE_PREPARED (sound_font_osc))
+    {
+      bse_source_update_modules (BSE_SOURCE (sound_font_osc),
+                                G_STRUCT_OFFSET (SoundFontOscModule, config),
+                                &sound_font_osc->config,
+                                sizeof (sound_font_osc->config),
+                                trans);
+    }
+}
+
+static void
+sound_font_osc_reset (BseModule *module)
+{
+  SoundFontOscModule *flmod = module->user_data;
+
+  flmod->last_update_preset = -1;
+}
+
+/* process_fluid is only called once per block, not once per module
+ */
+static void
+process_fluid_L (BseSoundFontRepo   *sfrepo,
+                fluid_synth_t      *fluid_synth,
+                 guint64            now_tick_stamp)
+{
+  float **channel_values_left = g_alloca (sfrepo->n_fluid_channels * sizeof (float *));
+  float **channel_values_right = g_alloca (sfrepo->n_fluid_channels * sizeof (float *));
+  float null_fx[BSE_STREAM_MAX_VALUES];
+  float *channel_fx_null[2] = { null_fx, null_fx };
+  int i;
+
+  g_return_if_fail (now_tick_stamp > sfrepo->channel_values_tick_stamp);
+  sfrepo->channel_values_tick_stamp = now_tick_stamp;
+
+  /* Sample precise timing: If events don't occur at block boundary, the block
+     is partially calculated, then the event is executed, and then the rest of
+     the block (until the next event) is calculated, and so on */
+  for (i = 0; i < sfrepo->n_fluid_channels; i++)
+    {
+      channel_values_left[i] = sfrepo->channel_values_left[i];
+      channel_values_right[i] = sfrepo->channel_values_right[i];
+    }
+  guint values_remaining = bse_engine_block_size();
+  while (values_remaining)
+    {
+      /* get 1st event tick stamp */
+      BseFluidEvent *event = NULL;
+      guint64 event_tick_stamp;
+      if (sfrepo->fluid_events)
+       {
+         event = (BseFluidEvent *) sfrepo->fluid_events->data;
+         event_tick_stamp = event->tick_stamp;
+       }
+      else
+       {
+         /* if no event is present, the earliest event that can occur is after this block */
+         event_tick_stamp = now_tick_stamp + values_remaining;
+       }
+      if (event_tick_stamp <= now_tick_stamp)       /* past or present event -> process it */
+       {
+         switch (event->command)
+           {
+             case BSE_MIDI_NOTE_ON:    fluid_synth_noteon (fluid_synth, event->channel, event->arg1, 
event->arg2);
+                                       sfrepo->n_silence_samples[event->channel] = 0;
+                                       break;
+             case BSE_MIDI_NOTE_OFF:   fluid_synth_noteoff (fluid_synth, event->channel, event->arg1);
+                                       break;
+             case BSE_MIDI_PITCH_BEND: fluid_synth_pitch_bend (fluid_synth, event->channel, event->arg1);
+                                       break;
+             case BSE_FLUID_SYNTH_PROGRAM_SELECT:
+                                       fluid_synth_program_select (fluid_synth, event->channel,
+                                                                   event->sfont_id,
+                                                                   event->arg1, event->arg2);
+                                       break;
+             case BSE_MIDI_X_CONTINUOUS_CHANGE:
+                                       fluid_synth_cc (fluid_synth, event->channel,
+                                                       event->arg1, event->arg2);
+                                       break;
+           }
+         sfrepo->fluid_events = sfi_ring_remove_node (sfrepo->fluid_events, sfrepo->fluid_events);
+         g_free (event);
+       }
+      else                                                  /* future event tick stamp: process audio until 
then */
+       {
+         gint64 values_todo = MIN (values_remaining, event_tick_stamp - now_tick_stamp);
+         fluid_synth_nwrite_float (fluid_synth, values_todo,
+                                   channel_values_left, channel_values_right,
+                                   channel_fx_null, channel_fx_null);
+         values_remaining -= values_todo;
+         now_tick_stamp += values_todo;
+         for (i = 0; i < sfrepo->n_fluid_channels; i++)          /* increment fluid synth output buffer 
pointers */
+           {
+             channel_values_left[i] += values_todo;
+             channel_values_right[i] += values_todo;
+           }
+       }
+    }
+}
+
+static void
+sound_font_osc_process (BseModule *module,
+                       guint      n_values)
+{
+  SoundFontOscModule *flmod = module->user_data;
+  BseSoundFontRepo *sfrepo = flmod->config.sfrepo;
+  fluid_synth_t *fluid_synth = bse_sound_font_repo_lock_fluid_synth (sfrepo);
+  guint i;
+  if (flmod->config.update_preset != flmod->last_update_preset)
+    {
+      fluid_synth_program_select (fluid_synth, flmod->config.sfrepo->channel_map[flmod->config.osc_id],
+                                               flmod->config.sfont_id, flmod->config.bank, 
flmod->config.program);
+      flmod->last_update_preset = flmod->config.update_preset;
+    }
+  gint64 now_tick_stamp = GSL_TICK_STAMP;
+  if (sfrepo->channel_values_tick_stamp != now_tick_stamp)
+    process_fluid_L (sfrepo, fluid_synth, now_tick_stamp);
+
+  float *left_output = sfrepo->channel_values_left[sfrepo->channel_map[flmod->config.osc_id]];
+  float *right_output = sfrepo->channel_values_right[sfrepo->channel_map[flmod->config.osc_id]];
+
+  int delta = bse_module_tick_stamp (module) - now_tick_stamp;
+  if (delta + n_values <= bse_engine_block_size())    /* paranoid check, should always pass */
+    {
+      left_output += delta;
+      right_output += delta;
+      BSE_MODULE_OSTREAM (module, BSE_SOUND_FONT_OSC_OCHANNEL_LEFT_OUT).values = left_output;
+      BSE_MODULE_OSTREAM (module, BSE_SOUND_FONT_OSC_OCHANNEL_RIGHT_OUT).values = right_output;
+    }
+  else
+    {
+      g_warning (G_STRLOC ": access past end of channel_values buffer");
+    }
+  if (BSE_MODULE_OSTREAM (module, BSE_SOUND_FONT_OSC_OCHANNEL_DONE_OUT).connected)
+    {
+      for (i = 0; i < n_values && left_output[i] == 0.0 && right_output[i] == 0.0; i++)
+       ;
+      if (i == n_values)
+       sfrepo->n_silence_samples[sfrepo->channel_map[flmod->config.osc_id]] += n_values;
+      else
+       sfrepo->n_silence_samples[sfrepo->channel_map[flmod->config.osc_id]] = 0;
+      float done = (sfrepo->n_silence_samples[sfrepo->channel_map[flmod->config.osc_id]] > 1024 && 
sfrepo->fluid_events == NULL) ? 1.0 : 0.0;
+      BSE_MODULE_OSTREAM (module, BSE_SOUND_FONT_OSC_OCHANNEL_DONE_OUT).values = bse_engine_const_values 
(done);
+    }
+
+  bse_sound_font_repo_unlock_fluid_synth (sfrepo);
+}
+
+static int
+event_cmp (gconstpointer  a,
+           gconstpointer  b,
+           gpointer       data)
+{
+  const BseFluidEvent *event1 = (const BseFluidEvent *) a;
+  const BseFluidEvent *event2 = (const BseFluidEvent *) b;
+
+  if (event1->tick_stamp < event2->tick_stamp)
+    return -1;
+  return (event1->tick_stamp > event2->tick_stamp);
+}
+
+static void
+sound_font_osc_process_midi (gpointer            null,
+                             BseModule          *module,
+                             const BseMidiEvent *event,
+                             BseTrans           *trans)
+{
+  SoundFontOscModule *flmod = module->user_data;
+  bse_sound_font_repo_lock_fluid_synth (flmod->config.sfrepo);
+  int note = bse_note_from_freq (BSE_MUSICAL_TUNING_12_TET, event->data.note.frequency);
+  BseFluidEvent *fluid_event = NULL;
+  switch (event->status)
+    {
+      case BSE_MIDI_NOTE_ON:
+       fluid_event = g_new0 (BseFluidEvent, 1);
+       fluid_event->command = BSE_MIDI_NOTE_ON;
+       fluid_event->arg1 = note;
+       fluid_event->arg2 = event->data.note.velocity * 127;
+       break;
+      case BSE_MIDI_NOTE_OFF:
+       fluid_event = g_new0 (BseFluidEvent, 1);
+       fluid_event->command = BSE_MIDI_NOTE_OFF;
+       fluid_event->arg1 = note;
+       break;
+      case BSE_MIDI_PITCH_BEND:
+       fluid_event = g_new0 (BseFluidEvent, 1);
+       fluid_event->command = BSE_MIDI_PITCH_BEND;
+       /* since midi uses 14 bits, the range is 0x0000 ... 0x3fff
+        * however, since beast uses -1 ... 0 ...  1, we use the formula
+        * below with an output range of  0x0000 ... 0x4000 (but fluid synth
+        * seems to accept these values without trouble) - its also the
+        * inverse of whats done to the input in bsemididecoder.c
+        */
+       fluid_event->arg1 = (event->data.pitch_bend * 0x2000) + 0x2000;
+       break;
+      case BSE_MIDI_CONTROL_CHANGE:
+      case BSE_MIDI_X_CONTINUOUS_CHANGE:
+       fluid_event = g_new0 (BseFluidEvent, 1);
+       fluid_event->command = BSE_MIDI_X_CONTINUOUS_CHANGE;
+       fluid_event->arg1 = event->data.control.control;
+       /* we do the inverse of what the BEAST midi file reading code does;
+         * this means we should be able to replay midi files without loosing
+         * any information - however, since midi information has no sign,
+         * we truncate negative numbers to zero
+         *
+         * FIXME: it would be possible to do an almost loss free conversion
+         * if the beast representation of controls would be more MIDI like
+         */
+       fluid_event->arg2 = CLAMP (event->data.control.value * 127, 0, 127);
+       break;
+      case BSE_MIDI_PROGRAM_CHANGE:
+       /* programs should be set at track level, and are thus not changeable here */
+       break;
+      default:
+       printf ("BseSoundFontOsc: unhandled status %02x\n", event->status);
+       break;
+    }
+  if (fluid_event)
+    {
+      fluid_event->tick_stamp = event->delta_time;
+      fluid_event->channel = flmod->config.sfrepo->channel_map[flmod->config.osc_id];
+      flmod->config.sfrepo->fluid_events = sfi_ring_insert_sorted (flmod->config.sfrepo->fluid_events, 
fluid_event, event_cmp, NULL);
+    }
+  bse_sound_font_repo_unlock_fluid_synth (flmod->config.sfrepo);
+}
+
+typedef struct
+{
+  BseMidiReceiver  *midi_receiver;
+  guint                    midi_channel;
+  BseModule        *module;
+} EventHandlerSetup;
+
+static void
+event_handler_setup_func (BseModule *module,
+                          void *ehs_data)
+{
+  EventHandlerSetup *ehs = (EventHandlerSetup *)ehs_data;
+  bse_midi_receiver_add_event_handler (ehs->midi_receiver,
+                                       ehs->midi_channel,
+                                       sound_font_osc_process_midi,
+                                       NULL,
+                                       ehs->module);
+
+  /* setup program before first midi event */
+  SoundFontOscModule *flmod = module->user_data;
+
+  BseFluidEvent *fluid_event = g_new0 (BseFluidEvent, 1);
+  fluid_event->command = BSE_FLUID_SYNTH_PROGRAM_SELECT;
+  fluid_event->channel = flmod->config.sfrepo->channel_map[flmod->config.osc_id];
+  fluid_event->arg1 = flmod->config.bank;
+  fluid_event->arg2 = flmod->config.program;
+  fluid_event->sfont_id = flmod->config.sfont_id;
+  fluid_event->tick_stamp = 0; /* now */
+  flmod->config.sfrepo->fluid_events = sfi_ring_insert_sorted (flmod->config.sfrepo->fluid_events, 
fluid_event, event_cmp, NULL);
+}
+
+static void
+bse_sound_font_osc_context_create (BseSource *source,
+                                  guint      context_handle,
+                                  BseTrans  *trans)
+{
+  static const BseModuleClass sound_font_osc_class = {
+    0,                             /* n_istreams */
+    0,                             /* n_jstreams */
+    BSE_SOUND_FONT_OSC_N_OCHANNELS, /* n_ostreams */
+    sound_font_osc_process,        /* process */
+    NULL,                          /* process_defer */
+    sound_font_osc_reset,          /* reset */
+    (BseModuleFreeFunc) g_free,            /* free */
+    BSE_COST_CHEAP,                /* flags */
+  };
+  SoundFontOscModule *sound_font_osc = g_new0 (SoundFontOscModule, 1);
+  BseModule *module;
+
+  module = bse_module_new (&sound_font_osc_class, sound_font_osc);
+
+  /* setup module i/o streams with BseSource i/o channels */
+  bse_source_set_context_module (source, context_handle, module);
+
+  /* commit module to engine */
+  bse_trans_add (trans, bse_job_integrate (module));
+
+  /* chain parent class' handler */
+  BSE_SOURCE_CLASS (parent_class)->context_create (source, context_handle, trans);
+
+  /* update (initialize) module data */
+  bse_sound_font_osc_update_modules (BSE_SOUND_FONT_OSC (source), trans);
+
+  /* setup midi event handler */
+  EventHandlerSetup *ehs = g_new0 (EventHandlerSetup, 1);
+  BseMidiContext mc = bse_snet_get_midi_context (bse_item_get_snet (BSE_ITEM (source)), context_handle);
+  ehs->midi_receiver = mc.midi_receiver;
+  ehs->midi_channel = mc.midi_channel;
+  ehs->module = module;
+  bse_trans_add (trans, bse_job_access (module, event_handler_setup_func, ehs, g_free));
+
+  /* reset fluid synth if necessary */
+  BseSoundFontOsc *self = BSE_SOUND_FONT_OSC (source);
+  fluid_synth_t *fluid_synth = bse_sound_font_repo_lock_fluid_synth (self->config.sfrepo);
+  if (self->config.sfrepo->n_channel_oscs_active == 0)
+    fluid_synth_system_reset (fluid_synth);
+  self->config.sfrepo->n_channel_oscs_active++;
+  bse_sound_font_repo_unlock_fluid_synth (self->config.sfrepo);
+}
+
+static void
+bse_sound_font_osc_context_dismiss (BseSource           *source,
+                                   guint                 context_handle,
+                                   BseTrans             *trans)
+{
+  BseSoundFontOsc *self = BSE_SOUND_FONT_OSC (source);
+  BseModule *module = bse_source_get_context_omodule (source, context_handle);
+  BseMidiContext mc = bse_snet_get_midi_context (bse_item_get_snet (BSE_ITEM (source)), context_handle);
+  bse_midi_receiver_remove_event_handler (mc.midi_receiver,
+                                          mc.midi_channel,
+                                         sound_font_osc_process_midi,
+                                          NULL,
+                                          module);
+  /* remove old events from the event queue */
+  bse_sound_font_repo_lock_fluid_synth (self->config.sfrepo);
+  SfiRing *fluid_events = self->config.sfrepo->fluid_events;
+  SfiRing *node = fluid_events;
+  while (node)
+    {
+      SfiRing *next_node = sfi_ring_walk (node, fluid_events);
+      BseFluidEvent *event = node->data;
+      if (event->channel == self->config.sfrepo->channel_map[self->config.osc_id])
+       {
+         g_free (event);
+         fluid_events = sfi_ring_remove_node (fluid_events, node);
+       }
+      node = next_node;
+    }
+  self->config.sfrepo->n_channel_oscs_active--;
+  self->config.sfrepo->fluid_events = fluid_events;
+  bse_sound_font_repo_unlock_fluid_synth (self->config.sfrepo);
+  /* chain parent class' handler */
+  BSE_SOURCE_CLASS (parent_class)->context_dismiss (source, context_handle, trans);
+}
diff --git a/bse/bsesoundfontosc.h b/bse/bsesoundfontosc.h
new file mode 100644
index 0000000..395e217
--- /dev/null
+++ b/bse/bsesoundfontosc.h
@@ -0,0 +1,78 @@
+/* BseSoundFontOsc - BSE Fluid Synth sound font player
+ * Copyright (C) 1999-2002 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#ifndef __BSE_SOUND_FONT_OSC_H__
+#define __BSE_SOUND_FONT_OSC_H__
+
+#include <bse/bsesource.h>
+#include <bse/bsesoundfontpreset.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+
+/* --- object type macros --- */
+#define BSE_TYPE_SOUND_FONT_OSC                      (BSE_TYPE_ID (BseSoundFontOsc))
+#define BSE_SOUND_FONT_OSC(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), BSE_TYPE_SOUND_FONT_OSC, 
BseSoundFontOsc))
+#define BSE_SOUND_FONT_OSC_CLASS(class)              (G_TYPE_CHECK_CLASS_CAST ((class), 
BSE_TYPE_SOUND_FONT_OSC, BseSoundFontOscClass))
+#define BSE_IS_SOUND_FONT_OSC(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), BSE_TYPE_SOUND_FONT_OSC))
+#define BSE_IS_SOUND_FONT_OSC_CLASS(class)    (G_TYPE_CHECK_CLASS_TYPE ((class), BSE_TYPE_SOUND_FONT_OSC))
+#define BSE_SOUND_FONT_OSC_GET_CLASS(object)  (G_TYPE_INSTANCE_GET_CLASS ((object), BSE_TYPE_SOUND_FONT_OSC, 
BseSoundFontOscClass))
+
+enum
+{
+  BSE_SOUND_FONT_OSC_OCHANNEL_LEFT_OUT,
+  BSE_SOUND_FONT_OSC_OCHANNEL_RIGHT_OUT,
+  BSE_SOUND_FONT_OSC_OCHANNEL_DONE_OUT,
+  BSE_SOUND_FONT_OSC_N_OCHANNELS
+};
+
+/* --- BseSoundFontOsc source --- */
+typedef struct _BseSoundFontOsc              BseSoundFontOsc;
+typedef struct _BseSoundFontOscClass  BseSoundFontOscClass;
+typedef struct _BseSoundFontOscConfig BseSoundFontOscConfig;
+struct _BseSoundFontOscConfig
+{
+  int                  osc_id;
+  int                  sfont_id;
+  int                  bank;
+  int                  program;
+  BseSoundFontRepo     *sfrepo;
+
+  int                   update_preset;  /* preset changed indicator */
+};
+struct _BseSoundFontOsc
+{
+  BseSource            parent_object;
+  BseSoundFontPreset   *preset;
+  BseSoundFontOscConfig        config;
+};
+struct _BseSoundFontOscClass
+{
+  BseSourceClass parent_class;
+};
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __BSE_SOUND_FONT_OSC_H__ */
diff --git a/bse/bsesoundfontpreset.c b/bse/bsesoundfontpreset.c
new file mode 100644
index 0000000..9de08b3
--- /dev/null
+++ b/bse/bsesoundfontpreset.c
@@ -0,0 +1,195 @@
+/* BSE - Bedevilled Sound Engine
+ * Copyright (C) 1997-1999, 2000-2005 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#include "bsesoundfontpreset.h"
+#include "bsemain.h"
+#include "bsestorage.h"
+#include "bseprocedure.h"
+#include "gsldatahandle.h"
+#include "bseserver.h"
+#include "bseloader.h"
+
+#include <string.h>
+#include <fluidsynth.h>
+
+#define parse_or_return         bse_storage_scanner_parse_or_return
+
+/* --- variables --- */
+static GTypeClass *parent_class = NULL;
+static GQuark      quark_program = 0;
+static GQuark      quark_bank = 0;
+
+
+/* --- functions --- */
+static void
+bse_sound_font_preset_init (BseSoundFontPreset *sound_font_preset)
+{
+  // init members here
+}
+
+void
+bse_sound_font_preset_init_preset (BseSoundFontPreset *self,
+                                   fluid_preset_t     *fluid_preset)
+{
+  self->bank = fluid_preset->get_banknum (fluid_preset);
+  self->program = fluid_preset->get_num (fluid_preset);
+}
+
+static void
+bse_sound_font_preset_store_private (BseObject  *object,
+                                    BseStorage *storage)
+{
+  BseSoundFontPreset *self = BSE_SOUND_FONT_PRESET (object);
+  /* chain parent class' handler */
+  BSE_OBJECT_CLASS (parent_class)->store_private (object, storage);
+
+  bse_storage_break (storage);
+  bse_storage_printf (storage, "(bank %d)", self->bank);
+  bse_storage_break (storage);
+  bse_storage_printf (storage, "(program %d)", self->program);
+}
+
+static SfiTokenType
+bse_sound_font_preset_restore_private (BseObject  *object,
+                                      BseStorage *storage,
+                                       GScanner   *scanner)
+{
+  BseSoundFontPreset *sound_font_preset = BSE_SOUND_FONT_PRESET (object);
+  GTokenType expected_token;
+  GQuark quark;
+
+  /* chain parent class' handler */
+  if (g_scanner_peek_next_token (scanner) != G_TOKEN_IDENTIFIER)
+    return BSE_OBJECT_CLASS (parent_class)->restore_private (object, storage, scanner);
+
+  /* parse storage commands */
+  quark = g_quark_try_string (scanner->next_value.v_identifier);
+  if (quark == quark_program)
+    {
+      g_scanner_get_next_token (scanner); /* eat quark identifier */
+      parse_or_return (scanner, G_TOKEN_INT);
+      sound_font_preset->program = scanner->value.v_int;
+      parse_or_return (scanner, ')');
+      expected_token = G_TOKEN_NONE; /* got ')' */
+    }
+  else if (quark == quark_bank)
+    {
+      g_scanner_get_next_token (scanner); /* eat quark identifier */
+      parse_or_return (scanner, G_TOKEN_INT);
+      sound_font_preset->bank = scanner->value.v_int;
+      parse_or_return (scanner, ')');
+      expected_token = G_TOKEN_NONE; /* got ')' */
+    }
+  else /* chain parent class' handler */
+    expected_token = BSE_OBJECT_CLASS (parent_class)->restore_private (object, storage, scanner);
+
+  return expected_token;
+}
+
+
+
+static void
+bse_sound_font_preset_set_property (GObject      *object,
+                                   guint         param_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  switch (param_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    }
+}
+
+static void
+bse_sound_font_preset_get_property (GObject    *object,
+                                   guint       param_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  // BseSoundFontPreset *sound_font_preset = BSE_SOUND_FONT_PRESET (object);
+  switch (param_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    }
+}
+
+static void
+bse_sound_font_preset_dispose (GObject *object)
+{
+  // BseSoundFontPreset *sound_font_preset = BSE_SOUND_FONT_PRESET (object);
+  // bse_sound_font_preset_clear (sound_font_preset);
+
+  /* chain parent class' handler */
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+bse_sound_font_preset_finalize (GObject *object)
+{
+  // BseSoundFontPreset *sound_font_preset = BSE_SOUND_FONT_PRESET (object);
+  // bse_sound_font_preset_clear (sound_font_preset);
+
+  /* chain parent class' handler */
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+bse_sound_font_preset_class_init (BseSoundFontPresetClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+  BseObjectClass *object_class = BSE_OBJECT_CLASS (class);
+
+  parent_class = g_type_class_peek_parent (class);
+
+  gobject_class->set_property = bse_sound_font_preset_set_property;
+  gobject_class->get_property = bse_sound_font_preset_get_property;
+  gobject_class->dispose = bse_sound_font_preset_dispose;
+  gobject_class->finalize = bse_sound_font_preset_finalize;
+
+  object_class->store_private = bse_sound_font_preset_store_private;
+  object_class->restore_private = bse_sound_font_preset_restore_private;
+
+  quark_program = g_quark_from_static_string ("program");
+  quark_bank = g_quark_from_static_string ("bank");
+}
+
+BSE_BUILTIN_TYPE (BseSoundFontPreset)
+{
+  static const GTypeInfo sound_font_preset_info = {
+    sizeof (BseSoundFontPresetClass),
+
+    (GBaseInitFunc) NULL,
+    (GBaseFinalizeFunc) NULL,
+    (GClassInitFunc) bse_sound_font_preset_class_init,
+    (GClassFinalizeFunc) NULL,
+    NULL /* class_data */,
+
+    sizeof (BseSoundFontPreset),
+    0  /* n_preallocs */,
+    (GInstanceInitFunc) bse_sound_font_preset_init,
+  };
+
+  return bse_type_register_static (BSE_TYPE_ITEM,
+                                  "BseSoundFontPreset",
+                                  "BSE sound_font_preset type",
+                                   __FILE__, __LINE__,
+                                   &sound_font_preset_info);
+}
diff --git a/bse/bsesoundfontpreset.h b/bse/bsesoundfontpreset.h
new file mode 100644
index 0000000..a90b931
--- /dev/null
+++ b/bse/bsesoundfontpreset.h
@@ -0,0 +1,54 @@
+/* BSE - Bedevilled Sound Engine
+ * Copyright (C) 1997-1999, 2000-2005 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#ifndef __BSE_SOUND_FONT_PRESET_H__
+#define __BSE_SOUND_FONT_PRESET_H__
+
+#include       <bse/bseitem.h>
+#include        <fluidsynth.h>
+
+G_BEGIN_DECLS
+
+/* --- BSE type macros --- */
+#define BSE_TYPE_SOUND_FONT_PRESET             (BSE_TYPE_ID (BseSoundFontPreset))
+#define BSE_SOUND_FONT_PRESET(object)          (G_TYPE_CHECK_INSTANCE_CAST ((object), 
BSE_TYPE_SOUND_FONT_PRESET, BseSoundFontPreset))
+#define BSE_SOUND_FONT_PRESET_CLASS(class)     (G_TYPE_CHECK_CLASS_CAST ((class), 
BSE_TYPE_SOUND_FONT_PRESET, BseSoundFontPresetClass))
+#define BSE_IS_SOUND_FONT_PRESET(object)       (G_TYPE_CHECK_INSTANCE_TYPE ((object), 
BSE_TYPE_SOUND_FONT_PRESET))
+#define BSE_IS_SOUND_FONT_PRESET_CLASS(class)  (G_TYPE_CHECK_CLASS_TYPE ((class), 
BSE_TYPE_SOUND_FONT_PRESET))
+#define BSE_SOUND_FONT_PRESET_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), 
BSE_TYPE_SOUND_FONT_PRESET, BseSoundFontPresetClass))
+
+
+/* --- BseSoundFontPreset --- */
+struct _BseSoundFontPreset
+{
+  BseItem      parent_object;
+  int           program;
+  int           bank;
+};
+struct _BseSoundFontPresetClass
+{
+  BseItemClass  parent_class;
+};
+
+
+/* --- prototypes -- */
+void   bse_sound_font_preset_init_preset (BseSoundFontPreset *self,
+                                         fluid_preset_t     *fluid_preset);
+
+G_END_DECLS
+
+#endif /* __BSE_SOUND_FONT_PRESET_H__ */
diff --git a/bse/bsesoundfontrepo.c b/bse/bsesoundfontrepo.c
new file mode 100644
index 0000000..00c321b
--- /dev/null
+++ b/bse/bsesoundfontrepo.c
@@ -0,0 +1,393 @@
+/* BSE - Bedevilled Sound Engine
+ * Copyright (C) 1996-1999, 2000-2003 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#include        "bsesoundfontrepo.h"
+#include        "bsesoundfont.h"
+#include        "bsesoundfontpreset.h"
+#include        "bsedefs.h"
+#include        "bseblockutils.hh"
+
+
+/* --- parameters --- */
+enum
+{
+  PARAM_0,
+};
+
+
+/* --- prototypes --- */
+static void    bse_sound_font_repo_class_init          (BseSoundFontRepoClass  *class);
+static void    bse_sound_font_repo_init                (BseSoundFontRepo       *wrepo);
+static void    bse_sound_font_repo_dispose             (GObject                *object);
+static void     bse_sound_font_repo_release_children    (BseContainer          *container);
+static void    bse_sound_font_repo_set_property        (GObject                *object,
+                                                        guint                   param_id,
+                                                        const GValue           *value,
+                                                        GParamSpec             *pspec);
+static void    bse_sound_font_repo_get_property (GObject               *object,
+                                                 guint                  param_id,
+                                                 GValue                *value,
+                                                 GParamSpec            *pspec);
+static void     bse_sound_font_repo_add_item     (BseContainer         *container,
+                                                 BseItem               *item);
+static void     bse_sound_font_repo_forall_items (BseContainer         *container,
+                                                 BseForallItemsFunc     func,
+                                                 gpointer               data);
+static void     bse_sound_font_repo_remove_item         (BseContainer          *container,
+                                                 BseItem               *item);
+static void     bse_sound_font_repo_prepare      (BseSource             *source);
+
+
+/* --- variables --- */
+static GTypeClass     *parent_class = NULL;
+
+
+/* --- functions --- */
+BSE_BUILTIN_TYPE (BseSoundFontRepo)
+{
+  GType sound_font_repo_type;
+
+  static const GTypeInfo sfrepo_info = {
+    sizeof (BseSoundFontRepoClass),
+
+    (GBaseInitFunc) NULL,
+    (GBaseFinalizeFunc) NULL,
+    (GClassInitFunc) bse_sound_font_repo_class_init,
+    (GClassFinalizeFunc) NULL,
+    NULL /* class_data */,
+
+    sizeof (BseSoundFontRepo),
+    0,
+    (GInstanceInitFunc) bse_sound_font_repo_init,
+  };
+
+  sound_font_repo_type = bse_type_register_static (BSE_TYPE_SUPER,
+                                                  "BseSoundFontRepo",
+                                                  "BSE Sound Font Repository",
+                                                   __FILE__, __LINE__,
+                                                   &sfrepo_info);
+  return sound_font_repo_type;
+}
+
+static void
+bse_sound_font_repo_class_init (BseSoundFontRepoClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+  BseContainerClass *container_class = BSE_CONTAINER_CLASS (class);
+  BseSourceClass *source_class = BSE_SOURCE_CLASS (class);
+
+  parent_class = g_type_class_peek_parent (class);
+
+  gobject_class->set_property = bse_sound_font_repo_set_property;
+  gobject_class->get_property = bse_sound_font_repo_get_property;
+  gobject_class->dispose = bse_sound_font_repo_dispose;
+
+  container_class->add_item = bse_sound_font_repo_add_item;
+  container_class->remove_item = bse_sound_font_repo_remove_item;
+  container_class->forall_items = bse_sound_font_repo_forall_items;
+  container_class->release_children = bse_sound_font_repo_release_children;
+
+  source_class->prepare = bse_sound_font_repo_prepare;
+}
+
+static void
+bse_sound_font_repo_init (BseSoundFontRepo *sfrepo)
+{
+  sfi_mutex_init (&sfrepo->fluid_synth_mutex);
+
+  sfrepo->n_oscs = 0;
+  sfrepo->oscs = NULL;
+  sfrepo->channel_map = NULL;
+
+  sfrepo->fluid_settings = new_fluid_settings();
+  sfrepo->fluid_synth = new_fluid_synth (sfrepo->fluid_settings);
+  sfrepo->fluid_events = NULL;
+  sfrepo->sound_fonts = NULL;
+  sfrepo->fluid_mix_freq = 0;
+
+  sfrepo->n_fluid_channels = 0;
+  sfrepo->channel_values_left = NULL;
+  sfrepo->channel_values_right = NULL;
+  sfrepo->n_silence_samples = NULL;
+
+  sfrepo->n_channel_oscs_active = 0;
+  sfrepo->channel_values_tick_stamp = 0;
+}
+
+static gboolean
+reload_sound_font (BseItem *item,
+                   gpointer data)
+{
+  BseSoundFont *sound_font = BSE_SOUND_FONT (item);
+  bse_sound_font_reload (sound_font);
+  return TRUE;
+}
+
+static gboolean
+unload_sound_font (BseItem *item,
+                   gpointer data)
+{
+  BseSoundFont *sound_font = BSE_SOUND_FONT (item);
+  bse_sound_font_unload (sound_font);
+  return TRUE;
+}
+
+static void
+bse_sound_font_repo_prepare (BseSource *source)
+{
+  BseSoundFontRepo *sfrepo = BSE_SOUND_FONT_REPO (source);
+  int i, channels_required = 0;
+  for (i = 0; i < sfrepo->n_oscs; i++)
+    {
+      if (sfrepo->oscs[i])
+       sfrepo->channel_map[i] = channels_required++;
+    }
+  int mix_freq = bse_engine_sample_freq();
+  if (sfrepo->n_fluid_channels != channels_required || sfrepo->fluid_mix_freq != mix_freq)
+    {
+      for (i = channels_required; i < sfrepo->n_fluid_channels; i++) // n_fluid_channels > channels_required
+       {
+         g_free (sfrepo->channel_values_left[i]);
+         g_free (sfrepo->channel_values_right[i]);
+       }
+      sfrepo->channel_values_left = (float **)g_realloc (sfrepo->channel_values_left, sizeof (float *) * 
channels_required);
+      sfrepo->channel_values_right = (float **)g_realloc (sfrepo->channel_values_right, sizeof (float *) * 
channels_required);
+      sfrepo->n_silence_samples = (gint *) g_realloc (sfrepo->n_silence_samples, sizeof (gint) * 
channels_required);
+      for (i = sfrepo->n_fluid_channels; i < channels_required; i++) // n_fluid_channels < channels_required
+       {
+         sfrepo->channel_values_left[i] = g_new0 (float, BSE_STREAM_MAX_VALUES);
+         sfrepo->channel_values_right[i] = g_new0 (float, BSE_STREAM_MAX_VALUES);
+         sfrepo->n_silence_samples[i] = 0;
+       }
+      sfrepo->n_fluid_channels = channels_required;
+      sfrepo->fluid_mix_freq = mix_freq;
+
+      fluid_settings_setnum (sfrepo->fluid_settings, "synth.sample-rate", mix_freq);
+      fluid_settings_setint (sfrepo->fluid_settings, "synth.midi-channels", channels_required);
+      fluid_settings_setint (sfrepo->fluid_settings, "synth.audio-channels", channels_required);
+      fluid_settings_setint (sfrepo->fluid_settings, "synth.audio-groups", channels_required);
+      fluid_settings_setstr (sfrepo->fluid_settings, "synth.reverb.active", "no");
+      fluid_settings_setstr (sfrepo->fluid_settings, "synth.chorus.active", "no");
+
+      bse_sound_font_repo_forall_items (BSE_CONTAINER (sfrepo), unload_sound_font, sfrepo);
+      if (sfrepo->fluid_synth)
+       delete_fluid_synth (sfrepo->fluid_synth);
+
+      sfrepo->fluid_synth = new_fluid_synth (sfrepo->fluid_settings);
+      bse_sound_font_repo_forall_items (BSE_CONTAINER (sfrepo), reload_sound_font, sfrepo);
+    }
+
+  /* chain parent class' handler */
+  BSE_SOURCE_CLASS (parent_class)->prepare (source);
+}
+
+static void
+bse_sound_font_repo_release_children (BseContainer *container)
+{
+  BseSoundFontRepo *sfrepo = BSE_SOUND_FONT_REPO (container);
+
+  while (sfrepo->sound_fonts)
+    bse_container_remove_item (container, sfrepo->sound_fonts->data);
+
+  /* chain parent class' handler */
+  BSE_CONTAINER_CLASS (parent_class)->release_children (container);
+}
+
+static void
+bse_sound_font_repo_dispose (GObject *object)
+{
+  BseSoundFontRepo *sfrepo = BSE_SOUND_FONT_REPO (object);
+  int i;
+
+  bse_sound_font_repo_forall_items (BSE_CONTAINER (sfrepo), unload_sound_font, sfrepo);
+
+  if (sfrepo->fluid_synth)
+    {
+      delete_fluid_synth (sfrepo->fluid_synth);
+      sfrepo->fluid_synth = NULL;
+    }
+  if (sfrepo->fluid_settings)
+    {
+      delete_fluid_settings (sfrepo->fluid_settings);
+      sfrepo->fluid_settings = NULL;
+    }
+  g_free (sfrepo->channel_map);
+  sfrepo->channel_map = NULL;
+  g_free (sfrepo->oscs);
+  sfrepo->oscs = NULL;
+  for (i = 0; i < sfrepo->n_fluid_channels; i++)
+    {
+      g_free (sfrepo->channel_values_left[i]);
+      g_free (sfrepo->channel_values_right[i]);
+    }
+  sfrepo->n_fluid_channels = 0;
+  g_free (sfrepo->channel_values_left);
+  sfrepo->channel_values_left = NULL;
+  g_free (sfrepo->channel_values_right);
+  sfrepo->channel_values_right = NULL;
+  g_free (sfrepo->n_silence_samples);
+  sfrepo->n_silence_samples = NULL;
+
+  if (sfrepo->fluid_events != NULL)
+    g_warning (G_STRLOC ": fluid event queue should be empty in dispose");
+
+  /* chain parent class' handler */
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+bse_sound_font_repo_set_property (GObject      *object,
+                                 guint         param_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  BseSoundFontRepo *sfrepo = BSE_SOUND_FONT_REPO (object);
+  switch (param_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (sfrepo, param_id, pspec);
+      break;
+    }
+}
+
+static void
+bse_sound_font_repo_get_property (GObject      *object,
+                                 guint         param_id,
+                                 GValue       *value,
+                                 GParamSpec   *pspec)
+{
+  BseSoundFontRepo *sfrepo = BSE_SOUND_FONT_REPO (object);
+  switch (param_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (sfrepo, param_id, pspec);
+      break;
+    }
+}
+
+static void
+bse_sound_font_repo_add_item (BseContainer *container,
+                             BseItem      *item)
+{
+  BseSoundFontRepo *sfrepo = BSE_SOUND_FONT_REPO (container);
+
+  if (g_type_is_a (BSE_OBJECT_TYPE (item), BSE_TYPE_SOUND_FONT))
+    sfrepo->sound_fonts = g_list_append (sfrepo->sound_fonts, item);
+  else
+    g_warning ("BseSoundFontRepo: cannot hold non-sound-font item type `%s'",
+              BSE_OBJECT_TYPE_NAME (item));
+
+  /* chain parent class' add_item handler */
+  BSE_CONTAINER_CLASS (parent_class)->add_item (container, item);
+}
+
+static void
+bse_sound_font_repo_forall_items (BseContainer      *container,
+                                 BseForallItemsFunc func,
+                                 gpointer           data)
+{
+  BseSoundFontRepo *sfrepo = BSE_SOUND_FONT_REPO (container);
+  GList *list;
+
+  list = sfrepo->sound_fonts;
+  while (list)
+    {
+      BseItem *item;
+
+      item = list->data;
+      list = list->next;
+      if (!func (item, data))
+       return;
+    }
+}
+
+static void
+bse_sound_font_repo_remove_item (BseContainer *container,
+                                BseItem      *item)
+{
+  BseSoundFontRepo *sfrepo = BSE_SOUND_FONT_REPO (container);
+
+  if (g_type_is_a (BSE_OBJECT_TYPE (item), BSE_TYPE_SOUND_FONT))
+    sfrepo->sound_fonts = g_list_remove (sfrepo->sound_fonts, item);
+  else
+    g_warning ("BseSoundFontRepo: cannot hold non-sound-font item type `%s'",
+              BSE_OBJECT_TYPE_NAME (item));
+
+  /* chain parent class' remove_item handler */
+  BSE_CONTAINER_CLASS (parent_class)->remove_item (container, item);
+}
+
+static gboolean
+gather_presets (BseItem  *item,
+                gpointer  items)
+{
+  if (BSE_IS_SOUND_FONT (item) || BSE_IS_SOUND_FONT_REPO (item))
+    bse_container_forall_items (BSE_CONTAINER (item), gather_presets, items);
+  else if (BSE_IS_SOUND_FONT_PRESET (item))
+    bse_item_seq_append (items, item);
+  else
+    g_warning ("Searching for sound font presets, an unexpected `%s' item was found", BSE_OBJECT_TYPE_NAME 
(item));
+  return TRUE;
+}
+
+void
+bse_sound_font_repo_list_all_presets (BseSoundFontRepo *sfrepo,
+                                      BseItemSeq       *items)
+{
+  gather_presets (BSE_ITEM (sfrepo), items);
+}
+
+fluid_synth_t *
+bse_sound_font_repo_lock_fluid_synth (BseSoundFontRepo *sfrepo)
+{
+  sfi_mutex_lock (&sfrepo->fluid_synth_mutex);
+  return sfrepo->fluid_synth;
+}
+
+void
+bse_sound_font_repo_unlock_fluid_synth (BseSoundFontRepo *sfrepo)
+{
+  sfi_mutex_unlock (&sfrepo->fluid_synth_mutex);
+}
+
+int
+bse_sound_font_repo_add_osc (BseSoundFontRepo *sfrepo,
+                             BseSoundFontOsc  *osc)
+{
+  int i;
+  for (i = 0; i < sfrepo->n_oscs; i++)
+    {
+      if (sfrepo->oscs[i] == 0)
+       {
+         sfrepo->oscs[i] = osc;
+         return i;
+       }
+    }
+  sfrepo->oscs = (BseSoundFontOsc **)g_realloc (sfrepo->oscs, sizeof (BseSoundFontOsc *) * (i + 1));
+  sfrepo->oscs[i] = osc;
+  sfrepo->channel_map = g_realloc (sfrepo->channel_map, sizeof (guint) * (i + 1));
+  return sfrepo->n_oscs++;
+}
+
+void
+bse_sound_font_repo_remove_osc (BseSoundFontRepo *sfrepo,
+                                int               osc_id)
+{
+  g_return_if_fail (osc_id >= 0 && osc_id < sfrepo->n_oscs);
+
+  sfrepo->oscs[osc_id] = 0;
+}
diff --git a/bse/bsesoundfontrepo.h b/bse/bsesoundfontrepo.h
new file mode 100644
index 0000000..312d624
--- /dev/null
+++ b/bse/bsesoundfontrepo.h
@@ -0,0 +1,94 @@
+/* BSE - Bedevilled Sound Engine
+ * Copyright (C) 1996-1999, 2000-2003 Tim Janik
+ * Copyright (C) 2009 Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#ifndef        __BSE_SOUND_FONT_REPO_H__
+#define        __BSE_SOUND_FONT_REPO_H__
+
+#include       <bse/bsesuper.h>
+#include        <fluidsynth.h>
+#include        <bse/bsesoundfontosc.h>
+#include        <bse/bseengine.h>
+
+
+G_BEGIN_DECLS
+
+
+/* --- object type macros --- */
+#define BSE_TYPE_SOUND_FONT_REPO               (BSE_TYPE_ID (BseSoundFontRepo))
+#define BSE_SOUND_FONT_REPO(object)            (G_TYPE_CHECK_INSTANCE_CAST ((object), 
BSE_TYPE_SOUND_FONT_REPO, BseSoundFontRepo))
+#define BSE_SOUND_FONT_REPO_CLASS(class)       (G_TYPE_CHECK_CLASS_CAST ((class), BSE_TYPE_SOUND_FONT_REPO, 
BseSoundFontRepoClass))
+#define BSE_IS_SOUND_FONT_REPO(object)         (G_TYPE_CHECK_INSTANCE_TYPE ((object), 
BSE_TYPE_SOUND_FONT_REPO))
+#define BSE_IS_SOUND_FONT_REPO_CLASS(class)    (G_TYPE_CHECK_CLASS_TYPE ((class), BSE_TYPE_SOUND_FONT_REPO))
+#define BSE_SOUND_FONT_REPO_GET_CLASS(object)  (G_TYPE_INSTANCE_GET_CLASS ((object), 
BSE_TYPE_SOUND_FONT_REPO, BseSoundFontRepoClass))
+
+
+/* --- BseSoundFontRepo object --- */
+#define BSE_FLUID_SYNTH_PROGRAM_SELECT -1
+typedef struct _BseFluidEvent BseFluidEvent;
+struct _BseFluidEvent
+{
+  guint64            tick_stamp;
+  int                channel;
+  int               command;
+  int               arg1;
+  int               arg2;
+  int                sfont_id;   /* required for program selection only */
+};
+struct _BseSoundFontRepo
+{
+  BseSuper          parent_object;
+
+  BirnetMutex       fluid_synth_mutex;
+  fluid_settings_t  *fluid_settings;
+  fluid_synth_t     *fluid_synth;
+  SfiRing           *fluid_events;
+  guint              fluid_mix_freq;
+
+  guint              n_fluid_channels;
+  float                   **channel_values_left;     /* [0..n_fluid_channels-1] */
+  float                   **channel_values_right;    /* [0..n_fluid_channels-1] */
+  guint64           channel_values_tick_stamp;
+  gint              *n_silence_samples;       /* [0..n_fluid_channels-1] */
+
+  guint              n_oscs;
+  BseSoundFontOsc  **oscs;                   /* [0..n_oscs-1] */
+  guint                    *channel_map;             /* [0..n_oscs-1] */
+
+  int               n_channel_oscs_active;       /* SoundFontOscs with an active module in the engine thread 
*/
+
+  GList             *sound_fonts;
+};
+
+struct _BseSoundFontRepoClass
+{
+  BseSuperClass  parent_class;
+};
+
+
+/* --- prototypes --- */
+void          bse_sound_font_repo_list_all_presets   (BseSoundFontRepo *sfrepo,
+                                                      BseItemSeq       *items);
+fluid_synth_t *bse_sound_font_repo_lock_fluid_synth   (BseSoundFontRepo *sfrepo);
+void           bse_sound_font_repo_unlock_fluid_synth (BseSoundFontRepo *sfrepo);
+int           bse_sound_font_repo_add_osc            (BseSoundFontRepo *sfrepo,
+                                                      BseSoundFontOsc  *osc);
+void           bse_sound_font_repo_remove_osc         (BseSoundFontRepo *sfrepo,
+                                                      int               osc_id);
+
+G_END_DECLS
+
+#endif /* __BSE_SOUND_FONT_REPO_H__ */
diff --git a/bse/bsesoundfontrepo.proc b/bse/bsesoundfontrepo.proc
new file mode 100644
index 0000000..8281837
--- /dev/null
+++ b/bse/bsesoundfontrepo.proc
@@ -0,0 +1,155 @@
+/* BSE - Bedevilled Sound Engine       -*-mode: c;-*-
+ * Copyright (C) 2000-2003 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * A copy of the GNU Lesser General Public License should ship along
+ * with this library; if not, see http://www.gnu.org/copyleft/.
+ */
+#include <bse/bseplugin.h>
+#include <bse/bseprocedure.h>
+#include <bse/bsesoundfontrepo.h>
+#include <bse/bsesoundfont.h>
+#include <bse/bseloader.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+
+/* --- auxlillary functions --- */
+static BseErrorType
+load_file (BseSoundFontRepo *sfrepo,
+          const gchar      *file_name,
+           BseSoundFont    **sound_font_p)
+{
+  gchar *fname = g_path_get_basename (file_name);
+  BseSoundFont *sound_font = g_object_new (BSE_TYPE_SOUND_FONT, "uname", fname, NULL);
+  g_free (fname);
+  bse_container_add_item (BSE_CONTAINER (sfrepo), BSE_ITEM (sound_font));
+
+  BseStorageBlob *blob = bse_storage_blob_new_from_file (file_name, FALSE);
+  BseErrorType error = bse_sound_font_load_blob (sound_font, blob, TRUE);
+  bse_storage_blob_unref (blob);
+  if (error == BSE_ERROR_NONE)
+    {
+      *sound_font_p = sound_font;
+    }
+  else
+    {
+      bse_container_remove_item (BSE_CONTAINER (sfrepo), BSE_ITEM (sound_font));
+      *sound_font_p = NULL;
+    }
+  g_object_unref (sound_font);
+  return error;
+}
+
+/* --- procedures --- */
+AUTHORS        = "Tim Janik <timj gtk org>";
+LICENSE = "GNU Lesser General Public License";
+
+
+METHOD (BseSoundFontRepo, load-file) {
+  HELP = "Load sound font from file";
+  IN    = bse_param_spec_object ("sound_font_repo", "Sound Font Repo", NULL,
+                                BSE_TYPE_SOUND_FONT_REPO, SFI_PARAM_STANDARD);
+  IN   = sfi_pspec_string ("file_name", "File Name", "The file to import sound fonts from",
+                           NULL, SFI_PARAM_STANDARD);
+  OUT   = bse_param_spec_genum ("error", "Error", NULL,
+                               BSE_TYPE_ERROR_TYPE, BSE_ERROR_NONE,
+                               SFI_PARAM_STANDARD);
+}
+BODY (BseProcedureClass *proc,
+      const GValue      *in_values,
+      GValue            *out_values)
+{
+  /* extract parameter values */
+  BseSoundFontRepo *self    = (BseSoundFontRepo*) bse_value_get_object (in_values++);
+  gchar *file_name          = sfi_value_get_string (in_values++);
+  BseUndoStack *ustack;
+  BseErrorType error;
+  BseSoundFont *sound_font;
+
+  /* check parameters */
+  if (!BSE_IS_SOUND_FONT_REPO (self) || !file_name)
+    return BSE_ERROR_PROC_PARAM_INVAL;
+
+  if (BSE_SOURCE_PREPARED (self))
+    {
+      /* In theory, its possible to allow loading sound fonts while
+       * the project is playing; in practice, the sound font repo
+       * lock would be locked for a very long time, which would stall
+       * the audio production ...
+       */
+      error = BSE_ERROR_SOURCE_BUSY;
+    }
+  else
+    {
+      ustack = bse_item_undo_open (self, "load-sound-font");
+      error = load_file (self, file_name, &sound_font);
+      if (sound_font)
+       bse_item_push_undo_proc (self, "remove-sound-font", sound_font);
+      bse_item_undo_close (ustack);
+   }
+
+  /* set output parameters */
+  g_value_set_enum (out_values++, error);
+
+  return BSE_ERROR_NONE;
+}
+
+METHOD (BseSoundFontRepo, remove-sound-font) {
+  HELP  = "Remove a sound font from repository";
+  IN    = bse_param_spec_object ("sound_font_repo", "Sound Font Repo", NULL,
+                                BSE_TYPE_SOUND_FONT_REPO, SFI_PARAM_STANDARD);
+  IN    = bse_param_spec_object ("sound_font", "Sound Font", NULL,
+                                BSE_TYPE_SOUND_FONT, SFI_PARAM_STANDARD);
+} BODY (BseProcedureClass *proc,
+       const GValue      *in_values,
+       GValue            *out_values)
+{
+  /* extract parameter values */
+  BseSoundFontRepo *self = (BseSoundFontRepo*) bse_value_get_object (in_values++);
+  BseItem *child         = (BseItem*) bse_value_get_object (in_values++);
+  BseUndoStack *ustack;
+  BseErrorType error;
+
+  /* check parameters */
+  if (!BSE_IS_SOUND_FONT_REPO (self) || !BSE_IS_SOUND_FONT (child) ||
+      child->parent != BSE_ITEM (self))
+    return BSE_ERROR_PROC_PARAM_INVAL;
+
+  if (BSE_SOURCE_PREPARED (self))
+    {
+      /* Don't allow unloading sound fonts which could be required by engine
+       * modules in the DSP thread currently producing audio output.
+       */
+      error = BSE_ERROR_SOURCE_BUSY;
+    }
+  else
+    {
+      /* action */
+      ustack = bse_item_undo_open (self, "remove-sound-font %s", bse_object_debug_name (child));
+      /* remove object references */
+      bse_container_uncross_undoable (BSE_CONTAINER (self), child);
+      /* how to get rid of the item once backed up */
+      bse_item_push_redo_proc (self, "remove-sound-font", child);
+      /* remove (without redo queueing) */
+      bse_container_remove_backedup (BSE_CONTAINER (self), child, ustack);
+      /* done */
+      bse_item_undo_close (ustack);
+
+      error = BSE_ERROR_NONE;
+    }
+
+  return error;
+}
diff --git a/bse/bsestorage.cc b/bse/bsestorage.cc
index 22438e2..e62eb4e 100644
--- a/bse/bsestorage.cc
+++ b/bse/bsestorage.cc
@@ -14,6 +14,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <sys/types.h>
+#include <signal.h>
 
 using Bse::Flac1Handle;
 
@@ -41,7 +43,14 @@ struct _BseStorageItemLink
   BseItem              *to_item;
   gchar                *error;
 };
-
+struct _BseStorageBlob
+{
+  SfiMutex    mutex;
+  char       *file_name;
+  int        ref_count;
+  gboolean    is_temp_file;
+  gulong      id;
+};
 
 /* --- prototypes --- */
 static void       bse_storage_init                 (BseStorage       *self);
@@ -68,6 +77,8 @@ static GTokenType compat_parse_data_handle         (BseStorage       *self,
 /* --- variables --- */
 static gpointer parent_class = NULL;
 static GQuark   quark_raw_data_handle = 0;
+static GQuark   quark_blob = 0;
+static GQuark   quark_blob_id = 0;
 static GQuark   quark_vorbis_data_handle = 0;
 static GQuark   quark_flac_data_handle = 0;
 static GQuark   quark_dblock_data_handle = 0;
@@ -109,6 +120,10 @@ bse_storage_class_init (BseStorageClass *klass)
   quark_flac_data_handle = g_quark_from_static_string ("flac-data-handle");
   quark_dblock_data_handle = g_quark_from_static_string ("dblock-data-handle");
   quark_bse_storage_binary_v0 = g_quark_from_static_string ("BseStorageBinaryV0");
+  quark_blob = g_quark_from_string ("blob");
+  quark_blob_id = g_quark_from_string ("blob-id");
+
+  bse_storage_blob_clean_files(); /* FIXME: maybe better placed in bsemain.c */
 
   gobject_class->finalize = bse_storage_finalize;
 }
@@ -129,6 +144,8 @@ bse_storage_init (BseStorage *self)
   self->dblocks = NULL;
   self->n_dblocks = 0;
   self->free_me = NULL;
+  self->blobs = NULL;
+  self->n_blobs = 0;
 
   bse_storage_reset (self);
 }
@@ -149,9 +166,10 @@ bse_storage_turn_readable (BseStorage  *self,
                            const gchar *storage_name)
 {
   BseStorageDBlock *dblocks;
+  BseStorageBlob **blobs;
   const gchar *cmem;
   gchar *text;
-  guint n_dblocks, l;
+  guint n_dblocks, n_blobs, l;
 
   g_return_if_fail (BSE_IS_STORAGE (self));
   g_return_if_fail (BSE_STORAGE_DBLOCK_CONTAINED (self));
@@ -166,13 +184,19 @@ bse_storage_turn_readable (BseStorage  *self,
   text = (char*) g_memdup (cmem, l + 1);
   dblocks = self->dblocks;
   n_dblocks = self->n_dblocks;
+  blobs = self->blobs;
+  n_blobs = self->n_blobs;
   self->dblocks = NULL;
   self->n_dblocks = 0;
+  self->blobs = NULL;
+  self->n_blobs = 0;
 
   bse_storage_input_text (self, text, storage_name);
   self->free_me = text;
   self->dblocks = dblocks;
   self->n_dblocks = n_dblocks;
+  self->blobs = blobs;
+  self->n_blobs = n_blobs;
   BSE_OBJECT_SET_FLAGS (self, BSE_STORAGE_DBLOCK_CONTAINED);
 }
 
@@ -220,6 +244,12 @@ bse_storage_reset (BseStorage *self)
   self->dblocks = NULL;
   self->n_dblocks = 0;
 
+  for (i = 0; i < self->n_blobs; i++)
+    bse_storage_blob_unref (self->blobs[i]);
+  g_free (self->blobs);
+  self->blobs = NULL;
+  self->n_blobs = 0;
+
   g_free (self->free_me);
   self->free_me = NULL;
 
@@ -248,6 +278,16 @@ bse_storage_add_dblock (BseStorage    *self,
   return self->dblocks[i].id;
 }
 
+static gulong
+bse_storage_add_blob (BseStorage     *self,
+                      BseStorageBlob *blob)
+{
+  guint i = self->n_blobs++;
+  self->blobs = g_renew (BseStorageBlob *, self->blobs, self->n_blobs);
+  self->blobs[i] = bse_storage_blob_ref (blob);
+  return self->blobs[i]->id;
+}
+
 static BseStorageDBlock*
 bse_storage_get_dblock (BseStorage    *self,
                         gulong         id)
@@ -564,21 +604,27 @@ storage_path_table_resolve_upath (BseStorage   *self,
                                   BseContainer *container,
                                   gchar        *upath)
 {
-  gchar *next_uname = strchr (upath, ':');
+  char *next_upath = strchr (upath, ':');
   /* upaths consist of colon seperated unames from the item's ancestry */
-  if (next_uname)
+  if (next_upath) /* A:B[:...] */
     {
+      char *next_next_upath = strchr (next_upath + 1, ':');
       BseItem *item;
-      next_uname[0] = 0;
-      item = storage_path_table_lookup (self, container, upath);
-      next_uname[0] = ':';
+      next_upath[0] = 0;
+      if (next_next_upath)
+       next_next_upath[0] = 0;
+      /* lookup A */
+      item = storage_path_table_resolve_upath (self, container, upath);
+      next_upath[0] = ':';
+      if (next_next_upath)
+       next_next_upath[0] = ':';
+      /* lookup B[:...] in A */
       if (BSE_IS_CONTAINER (item))
-        return storage_path_table_lookup (self, BSE_CONTAINER (item), next_uname + 1);
+       return storage_path_table_resolve_upath (self, BSE_CONTAINER (item), next_upath + 1);
       else
-        return NULL;
+       return NULL;
     }
-  else
-    return storage_path_table_lookup (self, container, upath);
+  return storage_path_table_lookup (self, container, upath);
 }
 
 static void
@@ -1566,6 +1612,310 @@ bse_storage_parse_data_handle_rest (BseStorage     *self,
   return parse_data_handle_trampoline (self, TRUE, data_handle_p, n_channels_p, mix_freq_p, osc_freq_p);
 }
 
+/* blobs */
+
+BseStorageBlob *
+bse_storage_blob_ref (BseStorageBlob *blob)
+{
+  g_return_val_if_fail (blob != NULL, NULL);
+  g_return_val_if_fail (blob->ref_count > 0, NULL);
+
+  GSL_SPIN_LOCK (&blob->mutex);
+  blob->ref_count++;
+  GSL_SPIN_UNLOCK (&blob->mutex);
+
+  return blob;
+}
+
+const gchar *
+bse_storage_blob_file_name (BseStorageBlob *blob)
+{
+  g_return_val_if_fail (blob != NULL, NULL);
+  g_return_val_if_fail (blob->ref_count > 0, NULL);
+
+  GSL_SPIN_LOCK (&blob->mutex);
+  const gchar *file_name = blob->file_name;
+  GSL_SPIN_UNLOCK (&blob->mutex);
+
+  return file_name;
+}
+
+void
+bse_storage_blob_unref (BseStorageBlob *blob)
+{
+  g_return_if_fail (blob != NULL);
+  g_return_if_fail (blob->ref_count > 0);
+
+  GSL_SPIN_LOCK (&blob->mutex);
+  blob->ref_count--;
+  gboolean destroy = blob->ref_count == 0;
+  GSL_SPIN_UNLOCK (&blob->mutex);
+  if (destroy)
+    {
+      if (blob->is_temp_file)
+       {
+         unlink (blob->file_name);
+         /* FIXME: check error code and do what? */
+       }
+      sfi_mutex_destroy (&blob->mutex);
+      g_free (blob->file_name);
+      blob->file_name = NULL;
+      bse_id_free (blob->id);
+      g_free (blob);
+    }
+}
+
+/* search in /tmp for files called "bse-<user>-<pid>*"
+ * delete files if the pid does not exist any longer
+ */
+void
+bse_storage_blob_clean_files()
+{
+  GError *error;
+  const char *tmp_dir = g_get_tmp_dir();
+  GDir *dir = g_dir_open (tmp_dir, 0, &error);
+  if (dir)
+    {
+      char *pattern = g_strdup_printf ("bse-%s-", g_get_user_name());
+      const char *file_name;
+      while ((file_name = g_dir_read_name (dir)))
+       {
+         if (strncmp (pattern, file_name, strlen (pattern)) == 0)
+           {
+             int pid = atoi (file_name + strlen (pattern));
+
+              if (kill (pid, 0) == -1 && errno == ESRCH)
+               {
+                 char *path = g_strdup_printf ("%s/%s", tmp_dir, file_name);
+                 unlink (path);
+                 g_free (path);
+               }
+           }
+       }
+      g_free (pattern);
+      g_dir_close (dir);
+    }
+}
+
+BseStorageBlob *
+bse_storage_blob_new_from_file (const char *file_name,
+                                gboolean    is_temp_file)
+{
+  BseStorageBlob *blob = g_new0 (BseStorageBlob, 1);
+  blob->file_name = g_strdup (file_name);
+  blob->ref_count = 1;
+  blob->is_temp_file = is_temp_file;
+  blob->id = bse_id_alloc();
+  sfi_mutex_init (&blob->mutex);
+  return blob;
+}
+
+gboolean
+bse_storage_blob_is_temp_file (BseStorageBlob *blob)
+{
+  g_return_val_if_fail (blob != NULL, FALSE);
+  g_return_val_if_fail (blob->ref_count > 0, FALSE);
+
+  GSL_SPIN_LOCK (&blob->mutex);
+  gboolean is_temp_file = blob->is_temp_file;
+  GSL_SPIN_UNLOCK (&blob->mutex);
+
+  return is_temp_file;
+}
+
+typedef struct
+{
+  BseStorageBlob *blob;
+  BseStorage     *storage;
+  int             fd;
+} WStoreBlob;
+
+static WStoreBlob *
+wstore_blob_new (BseStorage *storage,
+                 BseStorageBlob *blob)
+{
+  WStoreBlob *wsb = (WStoreBlob *) g_new0 (WStoreBlob, 1);
+  wsb->blob = bse_storage_blob_ref (blob);
+  wsb->storage = storage;
+  wsb->fd = -1;
+  return wsb;
+}
+
+static gint /* -errno || length */
+wstore_blob_reader (gpointer data,
+                   void    *buffer,
+                   guint    blength)
+{
+  WStoreBlob *wsb = data;
+  if (wsb->fd == -1)
+    {
+      do
+       wsb->fd = open (bse_storage_blob_file_name (wsb->blob), O_RDONLY);
+      while (wsb->fd == -1 && errno == EINTR);
+      if (wsb->fd == -1)
+       {
+         bse_storage_error (wsb->storage, "file %s could not be opened: %s", bse_storage_blob_file_name 
(wsb->blob), strerror (errno));
+         return -errno;
+       }
+    }
+  int n;
+  do
+    n = read (wsb->fd, buffer, blength);
+  while (n == -1 && errno == EINTR);
+  if (n < 0)
+    return -errno;
+  else
+    return n;
+}
+
+static void
+wstore_blob_destroy (gpointer data)
+{
+  WStoreBlob *wblob = data;
+  if (wblob->fd >= 0)
+    close (wblob->fd);
+  bse_storage_blob_unref (wblob->blob);
+}
+
+void
+bse_storage_put_blob (BseStorage      *self,
+                      BseStorageBlob  *blob)
+{
+  if (BSE_STORAGE_DBLOCK_CONTAINED (self))
+    {
+      gulong id = bse_storage_add_blob (self, blob);
+      bse_storage_break (self);
+      bse_storage_printf (self, "(%s %lu)", g_quark_to_string (quark_blob_id), id);
+    }
+  else
+    {
+      bse_storage_break (self);
+      bse_storage_printf (self, "(%s ", g_quark_to_string (quark_blob));
+      bse_storage_push_level (self);
+      bse_storage_break (self);
+      sfi_wstore_put_binary (self->wstore, wstore_blob_reader, wstore_blob_new (self, blob), 
wstore_blob_destroy);
+      bse_storage_pop_level (self);
+      bse_storage_putc (self, ')');
+    }
+}
+
+GTokenType
+bse_storage_parse_blob (BseStorage             *self,
+                        BseStorageBlob        **blob)
+{
+  GScanner *scanner = bse_storage_get_scanner (self);
+  int bse_fd = -1;
+  int tmp_fd = -1;
+  char *file_name = g_strdup_printf ("%s/bse-%s-%u-%08x", g_get_tmp_dir(), g_get_user_name(), getpid(), 
g_random_int());
+
+  *blob = NULL; /* on error, the resulting blob should be NULL */
+
+  parse_or_return (scanner, '(');
+  parse_or_return (scanner, G_TOKEN_IDENTIFIER);
+  if (g_quark_try_string (scanner->value.v_identifier) == quark_blob)
+    {
+      SfiNum offset, length;
+      GTokenType token = sfi_rstore_parse_binary (self->rstore, &offset, &length);
+      if (token != G_TOKEN_NONE)
+       return token;
+
+      char buffer[1024];
+      bse_fd = open (self->rstore->fname, O_RDONLY);
+      if (bse_fd < 0)
+       {
+         bse_storage_error (self, "couldn't open file %s for reading: %s\n", self->rstore->fname, strerror 
(errno));
+         goto return_with_error;
+       }
+      tmp_fd = open (file_name, O_CREAT | O_WRONLY, 0600);
+      if (tmp_fd < 0)
+       {
+         bse_storage_error (self, "couldn't open file %s for writing: %s\n", file_name, strerror (errno));
+         goto return_with_error;
+       }
+      int result = lseek (bse_fd, offset, SEEK_SET);
+      if (result != offset)
+       {
+         bse_storage_error (self, "could not seek to position %lld in bse file %s\n", offset, 
self->rstore->fname);
+         goto return_with_error;
+       }
+      int bytes_todo = length;
+      while (bytes_todo > 0)
+       {
+         int rbytes, wbytes;
+
+          do
+            rbytes = read (bse_fd, buffer, MIN (bytes_todo, 1024));
+          while (rbytes == -1 && errno == EINTR);
+
+         if (rbytes == -1)
+           {
+             bse_storage_error (self, "error while reading file %s: %s\n", self->rstore->fname, strerror 
(errno));
+             goto return_with_error;
+           }
+         if (rbytes == 0)
+           {
+             bse_storage_error (self, "end-of-file occured too early in file %s\n", self->rstore->fname);
+             goto return_with_error;
+           }
+
+         int bytes_written = 0;
+         while (bytes_written != rbytes)
+           {
+             do
+               wbytes = write (tmp_fd, &buffer[bytes_written], rbytes - bytes_written);
+             while (wbytes == -1 && errno == EINTR);
+             if (wbytes == -1)
+               {
+                 bse_storage_error (self, "error while writing file %s: %s\n", self->rstore->fname, strerror 
(errno));
+                 goto return_with_error;
+               }
+             bytes_written += wbytes;
+           }
+
+         bytes_todo -= rbytes;
+       }
+      close (bse_fd);
+      close (tmp_fd);
+      *blob = bse_storage_blob_new_from_file (file_name, TRUE);
+      g_free (file_name);
+    }
+  else if (g_quark_try_string (scanner->value.v_identifier) == quark_blob_id)
+    {
+      int i;
+      gulong id;
+      parse_or_return (scanner, G_TOKEN_INT);
+      id = scanner->value.v_int64;
+      *blob = NULL;
+      for (i = 0; i < self->n_blobs; i++)
+       {
+         if (self->blobs[i]->id == id)
+           *blob = bse_storage_blob_ref (self->blobs[i]);
+       }
+      if (!*blob)
+       {
+         g_warning ("failed to lookup storage blob with id=%ld\n", id);
+         goto return_with_error;
+       }
+     }
+  else
+    {
+      goto return_with_error;
+    }
+  parse_or_return (scanner, ')');
+
+  return G_TOKEN_NONE;
+
+return_with_error:
+  if (bse_fd != -1)
+    close (bse_fd);
+  if (tmp_fd != -1)
+    {
+      close (tmp_fd);
+      unlink (file_name);
+    }
+  return G_TOKEN_ERROR;
+}
+
 BseErrorType
 bse_storage_flush_fd (BseStorage *self,
                       gint        fd)
diff --git a/bse/bsestorage.hh b/bse/bsestorage.hh
index 676fae0..c7f8645 100644
--- a/bse/bsestorage.hh
+++ b/bse/bsestorage.hh
@@ -41,6 +41,7 @@ typedef enum    /*< skip >*/
 /* --- BseStorage --- */
 typedef struct _BseStorageDBlock   BseStorageDBlock;
 typedef struct _BseStorageItemLink BseStorageItemLink;
+typedef struct _BseStorageBlob     BseStorageBlob;
 typedef void (*BseStorageRestoreLink)   (gpointer        data,
                                          BseStorage     *storage,
                                          BseItem        *from_item,
@@ -62,6 +63,8 @@ struct BseStorage : BseObject {
   /* internal data */
   guint                  n_dblocks;
   BseStorageDBlock      *dblocks;
+  guint                  n_blobs;
+  BseStorageBlob       **blobs;
   gchar                 *free_me;
   /* compat */ // VERSION-FIXME: needed only for <= 0.5.1
   gfloat                 mix_freq;
@@ -118,6 +121,8 @@ void         bse_storage_put_item_link          (BseStorage             *self,
 void         bse_storage_put_data_handle        (BseStorage             *self,
                                                  guint                   significant_bits,
                                                  GslDataHandle          *dhandle);
+void         bse_storage_put_blob               (BseStorage             *self,
+                                                 BseStorageBlob         *blob);
 void         bse_storage_put_xinfos             (BseStorage             *self,
                                                  gchar                 **xinfos);
 BseErrorType bse_storage_flush_fd               (BseStorage             *self,
@@ -159,8 +164,19 @@ GTokenType   bse_storage_parse_rest             (BseStorage             *self,
                                                  gpointer                context_data,
                                                  BseTryStatement         try_statement,
                                                  gpointer                user_data);
+GTokenType   bse_storage_parse_blob             (BseStorage             *self,
+                                                 BseStorageBlob        **blob);
 gboolean     bse_storage_check_parse_negate     (BseStorage             *self);
 
+/* --- bse storage blob --- */
+
+BseStorageBlob  *bse_storage_blob_ref               (BseStorageBlob         *self);
+const gchar     *bse_storage_blob_file_name         (BseStorageBlob         *self);
+gboolean         bse_storage_blob_is_temp_file      (BseStorageBlob         *self);
+void             bse_storage_blob_unref             (BseStorageBlob         *self);
+BseStorageBlob  *bse_storage_blob_new_from_file     (const gchar            *file_name,
+                                                     gboolean                is_temp_file);
+void             bse_storage_blob_clean_files       (void);
 
 /* --- short-hands --- */
 #define bse_storage_get_scanner(s)      ((s)->rstore->scanner)
diff --git a/bse/bsetrack.cc b/bse/bsetrack.cc
index 84677c6..b1fbba9 100644
--- a/bse/bsetrack.cc
+++ b/bse/bsetrack.cc
@@ -15,7 +15,11 @@
 #include "bsemidivoice.hh"
 #include "bsemidireceiver.hh"
 #include "bsewaverepo.hh"
+#include "bsesoundfontrepo.h"
+#include "bsesoundfontpreset.h"
+#include "bsesoundfont.h"
 #include "bsecxxplugin.hh"
+
 #include <string.h>
 
 #define XREF_DEBUG(...) BSE_KEY_DEBUG ("xref", __VA_ARGS__)
@@ -29,6 +33,7 @@ enum {
   PROP_MUTED,
   PROP_SNET,
   PROP_WAVE,
+  PROP_SOUND_FONT_PRESET,
   PROP_MIDI_CHANNEL,
   PROP_N_VOICES,
   PROP_PNET,
@@ -103,6 +108,9 @@ bse_track_init (BseTrack *self)
   BSE_OBJECT_SET_FLAGS (self, BSE_SOURCE_FLAG_PRIVATE_INPUTS);
   self->snet = NULL;
   self->pnet = NULL;
+  self->sound_font_preset = NULL;
+  self->wave = NULL;
+  self->wnet = NULL;
   self->max_voices = 16;
   self->muted_SL = FALSE;
   self->n_entries_SL = 0;
@@ -269,6 +277,12 @@ bse_track_get_candidates (BseItem               *item,
          bse_item_gather_items_typed (BSE_ITEM (wrepo), pc->items, BSE_TYPE_WAVE, BSE_TYPE_WAVE_REPO, FALSE);
        }
       break;
+    case PROP_SOUND_FONT_PRESET:
+      bse_property_candidate_relabel (pc, _("Sound Fonts"), _("List of available sound font presets to 
choose as track instrument"));
+      project = bse_item_get_project (item);
+      if (project)
+       bse_sound_font_repo_list_all_presets (bse_project_get_sound_font_repo (project), pc->items);
+      break;
     case PROP_SNET:
       bse_property_candidate_relabel (pc, _("Available Synthesizers"), _("List of available synthesis 
networks to choose a track instrument from"));
       bse_item_gather_items_typed (item, pc->items, BSE_TYPE_CSYNTH, BSE_TYPE_PROJECT, FALSE);
@@ -327,6 +341,7 @@ set_amp_master_volume (BseSNet *snet, const char *amp_name, gchar **xinfos)
       g_object_set (amp, "master-volume", volume, NULL);
     }
 }
+
 static void
 set_adsr_params (BseSNet *snet, const char *adsr_name, gchar **xinfos)
 {
@@ -343,6 +358,15 @@ set_adsr_params (BseSNet *snet, const char *adsr_name, gchar **xinfos)
       g_object_set (adsr, "attack_time", g_ascii_strtod (adsr_attack_time, NULL), NULL);
     }
 }
+
+static void
+track_uncross_sound_font_preset (BseItem *owner,
+                                 BseItem *ref_item)
+{
+  BseTrack *self = BSE_TRACK (owner);
+  bse_item_set (self, "sound-font-preset", NULL, NULL);
+}
+
 static void
 create_wnet (BseTrack *self,
             BseWave  *wave)
@@ -403,7 +427,30 @@ create_wnet (BseTrack *self,
 }
 
 static void
-clear_snet_and_wave (BseTrack *self)
+create_sound_font_net (BseTrack           *self,
+                       BseSoundFontPreset *preset)
+{
+  g_return_if_fail (self->sound_font_net == NULL);
+
+  self->sound_font_net =  bse_project_create_intern_synth (bse_item_get_project (BSE_ITEM (self)),
+                                                          "sound-font-snet",
+                                                          BSE_TYPE_SNET);
+
+  bse_item_cross_link (BSE_ITEM (self), BSE_ITEM (self->sound_font_net), track_uncross_sound_font_preset);
+
+  if (self->sub_synth)
+    {
+      g_object_set (self->sub_synth, /* no undo */
+                   "snet", self->sound_font_net,
+                   NULL);
+    }
+
+  g_object_set (bse_container_resolve_upath (BSE_CONTAINER (self->sound_font_net), "sound-font-osc"), /* no 
undo */
+               "preset", preset,
+               NULL);
+}
+static void
+clear_snet_and_wave_and_sfpreset (BseTrack *self)
 {
   g_return_if_fail (!self->sub_synth || !BSE_SOURCE_PREPARED (self->sub_synth));
 
@@ -432,6 +479,20 @@ clear_snet_and_wave (BseTrack *self)
       self->wnet = NULL;
       bse_container_remove_item (BSE_CONTAINER (bse_item_get_project (BSE_ITEM (self))), BSE_ITEM (wnet));
     }
+  if (self->sound_font_preset)
+    {
+      bse_object_unproxy_notifies (self->sound_font_preset, self, "changed");
+      bse_item_cross_unlink (BSE_ITEM (self), BSE_ITEM (self->sound_font_preset), 
track_uncross_sound_font_preset);
+      self->sound_font_preset = NULL;
+      g_object_notify (self, "sound_font_preset");
+    }
+  if (self->sound_font_net)
+    {
+      BseSNet *sound_font_net = self->sound_font_net;
+      bse_item_cross_unlink (BSE_ITEM (self), BSE_ITEM (self->sound_font_net), 
track_uncross_sound_font_preset);
+      self->sound_font_net = NULL;
+      bse_container_remove_item (BSE_CONTAINER (bse_item_get_project (BSE_ITEM (self))), BSE_ITEM 
(sound_font_net));
+    }
 }
 
 static void
@@ -456,7 +517,7 @@ bse_track_set_property (GObject      *object,
          BseSNet *snet = (BseSNet*) bse_value_get_object (value);
          if (snet || self->snet)
            {
-             clear_snet_and_wave (self);
+             clear_snet_and_wave_and_sfpreset (self);
              self->snet = snet;
              if (self->snet)
                {
@@ -476,7 +537,7 @@ bse_track_set_property (GObject      *object,
          BseWave *wave = (BseWave*) bse_value_get_object (value);
          if (wave || self->wave)
            {
-             clear_snet_and_wave (self);
+             clear_snet_and_wave_and_sfpreset (self);
 
              self->wave = wave;
              if (self->wave)
@@ -488,6 +549,24 @@ bse_track_set_property (GObject      *object,
            }
        }
       break;
+    case PROP_SOUND_FONT_PRESET:
+      if (!self->sub_synth || !BSE_SOURCE_PREPARED (self->sub_synth))
+       {
+         BseSoundFontPreset *sound_font_preset = bse_value_get_object (value);
+         if (sound_font_preset || self->sound_font_preset)
+           {
+             clear_snet_and_wave_and_sfpreset (self);
+
+             self->sound_font_preset = sound_font_preset;
+             if (self->sound_font_preset)
+               {
+                 create_sound_font_net (self, sound_font_preset);
+                 bse_item_cross_link (BSE_ITEM (self), BSE_ITEM (self->sound_font_preset), 
track_uncross_sound_font_preset);
+                 bse_object_proxy_notifies (self->sound_font_preset, self, "changed");
+               }
+           }
+       }
+      break;
     case PROP_N_VOICES:
       if (!self->postprocess || !BSE_SOURCE_PREPARED (self->postprocess))
         self->max_voices = sfi_value_get_int (value);
@@ -560,6 +639,9 @@ bse_track_get_property (GObject    *object,
     case PROP_WAVE:
       bse_value_set_object (value, self->wave);
       break;
+    case PROP_SOUND_FONT_PRESET:
+      bse_value_set_object (value, self->sound_font_preset);
+      break;
     case PROP_N_VOICES:
       sfi_value_set_int (value, self->max_voices);
       break;
@@ -850,11 +932,11 @@ static void
 bse_track_update_midi_channel (BseTrack *self)
 {
   if (self->voice_switch)
-    {
-      bse_sub_synth_set_midi_channel (BSE_SUB_SYNTH (self->sub_synth), self->midi_channel_SL);
-      bse_sub_synth_set_midi_channel (BSE_SUB_SYNTH (self->postprocess), self->midi_channel_SL);
-      bse_midi_voice_switch_set_midi_channel (BSE_MIDI_VOICE_SWITCH (self->voice_switch), 
self->midi_channel_SL);
-    }
+  {
+    bse_sub_synth_set_midi_channel (BSE_SUB_SYNTH (self->sub_synth), self->midi_channel_SL);
+    bse_sub_synth_set_midi_channel (BSE_SUB_SYNTH (self->postprocess), self->midi_channel_SL);
+    bse_midi_voice_switch_set_midi_channel (BSE_MIDI_VOICE_SWITCH (self->voice_switch), 
self->midi_channel_SL);
+  }
 }
 
 static void
@@ -885,7 +967,7 @@ bse_track_context_dismiss (BseSource      *source,
 
 void
 bse_track_remove_modules (BseTrack     *self,
-                         BseContainer *container)
+                          BseContainer *container)
 {
   g_return_if_fail (BSE_IS_TRACK (self));
   g_return_if_fail (BSE_IS_CONTAINER (container));
@@ -903,19 +985,21 @@ bse_track_remove_modules (BseTrack     *self,
 
 void
 bse_track_clone_voices (BseTrack       *self,
-                       BseSNet        *snet,
-                       guint           context,
+                        BseSNet        *snet,
+                        guint           context,
                         BseMidiContext  mcontext,
-                       BseTrans       *trans)
+                        BseTrans       *trans)
 {
   guint i;
 
   g_return_if_fail (BSE_IS_TRACK (self));
   g_return_if_fail (BSE_IS_SNET (snet));
   g_return_if_fail (trans != NULL);
-
-  for (i = 0; i < self->max_voices - 1; i++)
-    bse_snet_context_clone_branch (snet, context, BSE_SOURCE (self), mcontext, trans);
+  if (!self->sound_font_preset)
+    {
+      for (i = 0; i < self->max_voices - 1; i++)
+       bse_snet_context_clone_branch (snet, context, BSE_SOURCE (self), mcontext, trans);
+    }
 }
 
 static void
@@ -1035,6 +1119,12 @@ bse_track_class_init (BseTrackClass *klass)
                                                     BSE_TYPE_WAVE,
                                                     SFI_PARAM_STANDARD ":unprepared"));
   bse_object_class_add_param (object_class, _("Synth Input"),
+                             PROP_SOUND_FONT_PRESET,
+                              bse_param_spec_object (("sound_font_preset"), _("Sound Font Preset"),
+                                                     _("Sound font preset to be used as instrument"),
+                                                    BSE_TYPE_SOUND_FONT_PRESET,
+                                                    SFI_PARAM_STANDARD ":unprepared"));
+  bse_object_class_add_param (object_class, _("Synth Input"),
                              PROP_N_VOICES,
                              sfi_pspec_int ("n_voices", _("Max Voices"), _("Maximum number of voices for 
simultaneous playback"),
                                             16, 1, 256, 1,
diff --git a/bse/bsetrack.hh b/bse/bsetrack.hh
index 83a06a7..deb50df 100644
--- a/bse/bsetrack.hh
+++ b/bse/bsetrack.hh
@@ -29,6 +29,11 @@ struct BseTrack : BseContextMerger {
   /* wave synthesis */
   BseWave        *wave;
   BseSNet        *wnet;
+
+  /* sound font synthesis */
+  BseSoundFontPreset  *sound_font_preset;
+  BseSNet         *sound_font_net;
+
   /* playback intergration */
   BseSource       *sub_synth;
   BseSource       *voice_input;
diff --git a/bse/zintern/Makefile.am b/bse/zintern/Makefile.am
index 7cd7251..9bbe5c9 100644
--- a/bse/zintern/Makefile.am
+++ b/bse/zintern/Makefile.am
@@ -11,6 +11,7 @@ ZFILE_DEFS = $(strip \
        adsr-wave-2             $(srcdir)/adsr-wave-2.bse       \
        plain-wave-1            $(srcdir)/plain-wave-1.bse      \
        plain-wave-2            $(srcdir)/plain-wave-2.bse      \
+       sound-font-snet         $(srcdir)/sound-font-snet.bse   \
 )
 
 gen_sources = xgen-bzc xgen-bzh
diff --git a/bse/zintern/sound-font-snet.bse b/bse/zintern/sound-font-snet.bse
new file mode 100644
index 0000000..38c981e
--- /dev/null
+++ b/bse/zintern/sound-font-snet.bse
@@ -0,0 +1,17 @@
+; BseProject
+
+(bse-version "0.7.2")
+
+(container-child "BseCSynth::%bse-intern-sound-font-snet"
+  (modification-time "2009-03-14 14:23:28")
+  (creation-time "2003-04-27 20:45:24")
+  (license "Provided \"as is\", WITHOUT ANY WARRANTY (http://beast.gtk.org/LICENSE-AS-IS)")
+  (author "Tim Janik, Stefan Westerfeld")
+  (container-child "BseInstrumentOutput::instrument-output"
+    (pos-y 1)
+    (pos-x 3)
+    (source-input "left-audio" (link 1 "sound-font-osc") "left-out")
+    (source-input "right-audio" (link 1 "sound-font-osc") "right-out")
+    (source-input "synth-done" (link 1 "sound-font-osc") "done-out"))
+  (container-child "BseSoundFontOsc::sound-font-osc"
+    (pos-y 1)))
diff --git a/configure.ac b/configure.ac
index a7d9f28..1ae3882 100644
--- a/configure.ac
+++ b/configure.ac
@@ -367,9 +367,14 @@ AC_DEFUN([AC_BSE_REQUIREMENTS],
       AC_MSG_ERROR([Ogg/Vorbis is missing, but required])
     fi
 
+    dnl Check for FluidSynth
+    PKG_CHECK_MODULES(FLUID, fluidsynth >= 1.0.6)
+
     dnl # --- complete CFLAGS/LIBS setup ---
     BSE_CFLAGS="$MAD_CFLAGS $FLAC_CFLAGS $SFI_CPPFLAGS"
     BSE_LIBS="$OV_LIBS $MAD_LIBS $FLAC_LIBS $SFI_LIBS"
+    BSE_CFLAGS="$FLUID_CFLAGS $BSE_CFLAGS"
+    BSE_LIBS="$FLUID_LIBS $BSE_LIBS"
     AC_SUBST(BSE_CFLAGS)
     AC_SUBST(BSE_LIBS)
     dnl # --- figure stuff for bse.pc ---
diff --git a/po/POTSCAN b/po/POTSCAN
index aadb347..8a75ee0 100644
--- a/po/POTSCAN
+++ b/po/POTSCAN
@@ -43,6 +43,7 @@ beast-gtk/bstrecords.idl
 beast-gtk/bstsampleeditor.cc
 beast-gtk/bstscrollgraph.cc
 beast-gtk/bstsnetrouter.cc
+beast-gtk/bstsoundfontview.c
 beast-gtk/bstsupershell.cc
 beast-gtk/bsttrackrollctrl.cc
 beast-gtk/bsttracksynthdialog.cc
@@ -91,6 +92,7 @@ bse/bseserver.cc
 bse/bsesnooper.cc
 bse/bsesong.cc
 bse/bsesong.proc
+bse/bsesoundfontosc.c
 bse/bsesource.proc
 bse/bsestandardosc.cc
 bse/bsesubiport.cc


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]