diff --git a/engine/drumkit.c b/engine/drumkit.c
index 599d61cdf60c8355cc135610d986401aa08fb288..6205618fc1403c348705b9d5fd62ca12abae14ff 100644
--- a/engine/drumkit.c
+++ b/engine/drumkit.c
@@ -3,6 +3,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
 
 #include <libxml/parser.h>
 #include <libxml/tree.h>
@@ -450,6 +452,32 @@ static void fix_bad_xml(xmlNodePtr cur, xmlNsPtr ns) {
   }
 }
 
+// Return the final location pointed at by filename if it's a symbolic
+// link. In all cases, the resulting memory must be freed by the caller.
+static char *resolve_symlink(const char *filename) {
+  char buf[1024];
+  struct stat stbuf;
+  char *cur = strdup(filename);
+  int n;
+
+  while (1) {
+    if (lstat(cur, &stbuf) < 0) {
+      return NULL;
+    }
+    if ((stbuf.st_mode & S_IFLNK) != S_IFLNK) {
+      return cur;
+    }
+
+    n = readlink(cur, buf, sizeof(buf)-1);
+    if (n < 0) {
+      return NULL;
+    }
+    buf[n] = '\0';
+    free(cur);
+    cur = strdup(buf);
+  }
+}
+
 int drumkit_load(struct drumkit *kit, const char *filename, int target_samplerate) {
   char *basedir = NULL;
   xmlDocPtr doc = NULL;
@@ -459,6 +487,10 @@ int drumkit_load(struct drumkit *kit, const char *filename, int target_samplerat
   xmlXPathObjectPtr xpathObj = NULL;
   int n, r = 1;
 
+  // Resolve symlinks because sometimes (i.e. in presets) we get sent
+  // a symbolic link to the actual drumkit.xml, which messes up the
+  // relative filenames of the samples.
+  filename = resolve_symlink(filename);
   basedir = path_dirname(filename);
   kit->instruments = NULL;
 
@@ -508,6 +540,7 @@ int drumkit_load(struct drumkit *kit, const char *filename, int target_samplerat
   fix_midi_notes(kit);
 
  cleanup:
+  free(filename);
   free(basedir);
   if (xpathObj) {
     xmlXPathFreeObject(xpathObj);