From 3c0ed6b66b6e99c68fc01c86b5796a55d74a5977 Mon Sep 17 00:00:00 2001
From: David Robillard <d@drobilla.net>
Date: Sat, 9 Mar 2019 17:44:36 +0100
Subject: WIP: Port to serd1

---
 doc/style.css                 | 724 ++++++++++++------------------------------
 ingen/AtomForge.hpp           |  41 +--
 ingen/AtomWriter.hpp          |   2 +-
 ingen/Configuration.hpp       |   9 +-
 ingen/FilePath.hpp            |   6 +-
 ingen/Forge.hpp               |  15 +-
 ingen/Parser.hpp              |   7 +-
 ingen/Serialiser.hpp          |   7 +-
 ingen/SocketReader.hpp        |  22 +-
 ingen/SocketWriter.hpp        |   3 +-
 ingen/StreamWriter.hpp        |   3 +-
 ingen/TurtleWriter.hpp        |  19 +-
 ingen/URI.hpp                 | 114 +------
 ingen/URIMap.hpp              |   2 +
 ingen/URIs.hpp                |  11 +-
 ingen/World.hpp               |   7 +-
 ingen/client/PluginModel.hpp  |   9 +-
 ingen/client/SocketClient.hpp |  12 +-
 ingen/filesystem.hpp          |   2 +
 ingen/paths.hpp               |   6 +-
 src/AtomWriter.cpp            |  15 +-
 src/Configuration.cpp         | 106 +++----
 src/FilePath.cpp              |   2 +-
 src/Forge.cpp                 |   2 +-
 src/Parser.cpp                | 355 ++++++++++-----------
 src/Serialiser.cpp            | 358 ++++++++++-----------
 src/SocketReader.cpp          | 119 +++----
 src/SocketWriter.cpp          |   7 +-
 src/StreamWriter.cpp          |   9 +-
 src/TurtleWriter.cpp          |  93 ++----
 src/URI.cpp                   |  91 +-----
 src/URIs.cpp                  |  13 -
 src/World.cpp                 |  32 +-
 src/client/ClientStore.cpp    |   1 +
 src/client/PluginModel.cpp    |  13 +-
 src/client/PluginUI.cpp       |   6 +-
 src/client/wscript            |   2 +-
 src/gui/App.cpp               |  13 +-
 src/gui/ConnectWindow.cpp     |   2 +-
 src/gui/GraphCanvas.cpp       |   4 +-
 src/gui/LoadPluginWindow.cpp  |   2 +-
 src/gui/NodeMenu.cpp          |   4 +-
 src/gui/NodeMenu.hpp          |   2 +-
 src/gui/Port.cpp              |   4 +-
 src/gui/PropertiesWindow.cpp  |   3 +-
 src/gui/ingen_gui_lv2.cpp     |   1 +
 src/gui/wscript               |   7 +-
 src/ingen/ingen.cpp           |  29 +-
 src/server/Engine.cpp         |  59 ++--
 src/server/Event.hpp          |   2 +-
 src/server/LV2Plugin.cpp      |   2 +-
 src/server/PreProcessor.cpp   |   2 +
 src/server/SocketListener.cpp |   4 +-
 src/server/SocketServer.hpp   |   6 +-
 src/server/UndoStack.cpp      | 153 +++++----
 src/server/UndoStack.hpp      |  22 +-
 src/server/events/Copy.cpp    |   5 +-
 src/server/events/Delta.cpp   |   8 +-
 src/server/ingen_lv2.cpp      |  24 +-
 src/server/wscript            |   2 +-
 src/wscript                   |   2 +-
 tests/empty.ingen/main.ttl    |   1 -
 tests/ingen_test.cpp          |  66 ++--
 tests/tst_FilePath.cpp        |   5 +-
 wscript                       |  12 +-
 65 files changed, 1034 insertions(+), 1657 deletions(-)

diff --git a/doc/style.css b/doc/style.css
index 172be516..5079e86c 100644
--- a/doc/style.css
+++ b/doc/style.css
@@ -1,54 +1,108 @@
 body {
-	max-width: 80em;
-	margin: 0;
+	background: #FFF;
+	color: #222;
+	font-style: normal;
+	line-height: 1.6em;
 	margin-left: auto;
 	margin-right: auto;
-	background: #FFF;
-	color: #000;
+	max-width: 60em;
+	/* padding: 0; */
+    font-family: "DejaVu Serif",Palatino,serif;
+    text-rendering: optimizeLegibility;
 }
 
-#titlearea {
-	display: none;
+/* #titlearea { */
+/* 	display: none; */
+/* } */
+
+h1, .title, #projectname, h2, h3, h4, h5, h6 {
+	line-height: 1.0125em;
+    color: #222;
+    font-family: "DejaVu Sans","Helvetica","Arial";
+    margin: 1em 0 0.5em 0;
+}
+
+h1, .title, #projectname {
+	font-size: 320%;
+	font-weight: 400;
+}
+
+.title {
+    margin-bottom: 0.25em;
+    margin-top: 0;
+}
+
+#titlebox, #metabox {
+	display: inline-block;
+}
+#titlebox{
+	display: inline-block;
+	width: 75%;
+	left: 0;
+	top: 0;
+}
+#title {
+	margin-top: 0;
+    margin-bottom: 0.25em;
+}
+
+#shortdesc {
+	margin: 0 0 0.5em 0;
+    color: #666;
+    display: inline-block;
+    font-style: italic;
+    padding: 0;
 }
 
-h1 {
-	font-size: 180%;
-	font-weight: 900;
+#titlearea {
+	margin: 2em auto 1em auto;
+	padding: 0 0.5em 0 0.5em;
+	position: relative;
+    clear: both;
+    line-height: 1.0em;
 }
 
 h2 {
-	font-size: 140%;
-	font-weight: 700;
+	font-size: 160%;
+	font-weight: 400;
 }
 
 h3 {
-	font-size: 120%;
-	font-weight: 700;
+	font-size: 140%;
+	font-weight: 400;
 }
 
 h4 {
-	font-size: 110%;
-	font-weight: 700;
+	font-size: 120%;
+	font-weight: 500;
 }
 
-h5 {
-	font-size: 100%;
-	font-weight: 700;
+h5, h6 {
+	font-size: 110%;
+	font-weight: 600;
 }
 
-h6 {
-	font-size: 100%;
-	font-weight: 600;
+h1 a, h1 a:link, h1 a:visited ,
+h2 a, h2 a:link, h2 a:visited ,
+h3 a, h3 a:link, h3 a:visited ,
+h4 a, h4 a:link, h4 a:visited ,
+h5 a, h5 a:link, h5 a:visited ,
+h6 a, h6 a:link, h6 a:visited {
+    color: #444;
 }
 
 p {
-	margin: 0 0 1em 0;
+	margin: 0.5em 0 0.5em 0;
 }
 
 dt {
 	font-weight: 700;
 }
 
+dd {
+    margin-left: 2em;
+}
+
 p.startli,p.startdd,p.starttd {
 	margin-top: 2px;
 }
@@ -70,12 +124,12 @@ caption {
 }
 
 span.legend {
-	font-size: 70%;
+	font-size: small;
 	text-align: center;
 }
 
 h3.version {
-	font-size: 90%;
+	font-size: small;
 	text-align: center;
 }
 
@@ -130,8 +184,6 @@ dl.el {
 
 .fragment {
 	font-family: monospace, fixed;
-	font-size: 105%;
-	padding-bottom: 1em;
 }
 
 pre.fragment {
@@ -140,7 +192,6 @@ pre.fragment {
 	padding: 4px 6px;
 	margin: 4px 8px 4px 2px;
 	overflow: auto;
-	font-size: 9pt;
 	line-height: 125%;
 }
 
@@ -170,10 +221,11 @@ div.groupText {
 	font-style: italic;
 }
 
-div.contents {
-	margin-top: 10px;
-	margin-left: 10px;
-	margin-right: 10px;
+div.contents, #content {
+    padding: 0 0.5em 0 0.5em;
+    max-width: 60em;
+	margin-left: auto;
+	margin-right: auto;
 }
 
 td.indexkey {
@@ -191,6 +243,10 @@ td.indexvalue {
 	margin: 2px 0;
 }
 
+table.memname {
+    font-family: "DejaVu Sans Mono",monospace;
+}
+
 tr.memlist {
 	background-color: #EEF1F7;
 }
@@ -267,7 +323,7 @@ span.vhdllogic {
 
 /* @end */
 td.tiny {
-	font-size: 75%;
+	font-size: x-small;
 }
 
 .dirtab {
@@ -295,11 +351,12 @@ hr.footer {
 /* @group Member Descriptions */
 table.memberdecls {
 	border-spacing: 0.125em;
+    line-height: 1.3em;
 }
 
-h2.groupheader {
-	margin: 1em 0 0.5em 0;
-}
+/* h2.groupheader { */
+/* 	margin: 0.5em 0 0.25em 0; */
+/* } */
 
 .mdescLeft,.mdescRight,.memItemLeft,.memItemRight,.memTemplItemLeft,.memTemplItemRight,.memTemplParams {
 	margin: 0;
@@ -313,7 +370,6 @@ h2.groupheader {
 .memItemLeft,.memItemRight,.memTemplParams {
 	border: 0;
 	font-family: monospace, fixed;
-	font-size: 90%;
 }
 
 .memItemLeft,.memTemplItemLeft {
@@ -349,7 +405,6 @@ td.mlabels-right {
 /* @group Member Details */
 /* Styles for detailed member documentation */
 .memtemplate {
-	font-size: 80%;
 	color: #4665A2;
 	font-weight: bold;
 }
@@ -364,24 +419,32 @@ td.mlabels-right {
 }
 
 .memitem {
-	padding: 0;
-	margin: 1em 0 1em 0;
+	padding: 0.25em 0.5em 0.25em 0.5em;
+	margin: 0 0 1em 0;
+	border-radius: 6px;
+    border: 1px solid #DDD;
 }
 
 .memproto {
-	padding: 0;
 	font-size: 110%;
-	font-weight: bold;
-	color: #000;
+	font-weight: 400;
+    line-height: 1em;
+	/* line-height: 1.0125em; */
+    color: #000;
 }
 
 .memproto .paramname {
-	color: #444;
 	font-style: normal;
 }
 
 .memdoc {
-	padding: 0 0 0.5em 2em;
+	padding: 0 0.25em 0 0.25em;
+}
+
+.memdoc p:first-of-type {
+    /* color: #666; */
+    /* font-style: italic; */
+    /* margin-top: 0.25em; */
 }
 
 .paramkey {
@@ -389,36 +452,57 @@ td.mlabels-right {
 }
 
 .paramtype {
-	color: #3E873E;
+	color: #666;
+    padding-right: 0.5em;
 	white-space: nowrap;
 }
 
 .paramname {
-	color: #444;
+	color: #111;
 	white-space: nowrap;
-	font-weight: bold;
-}
-
-td.paramname {
-	vertical-align: top;
+	/* font-weight: bold; */
+    font-family: monospace;
+    font-style: italic;
+    padding-right: 0.5em;
+    /* padding-left: 0.5em; */
 }
 
 .fieldname {
 	color: #000;
 }
 
+.fieldtable {
+    padding-top: 0.25em;
+    border-top: 1px dashed #DDD;
+}
+
+.fieldtable tbody tr:first-child {
+    display: none;
+}
+
 td.fieldname {
-	padding-right: 1em;
+	padding: 0 0.5em 0 0.25em;
+    /* padding-left: 2em; */
 	vertical-align: top;
+    font-family: monospace;
 }
 
 td.fieldtype {
+	color: #666;
+	padding: 0 0.5em 0 0;
 	vertical-align: top;
-	color: #444;
+    font-family: monospace;
 }
 
 td.fielddoc p {
 	margin: 0;
+	vertical-align: top;
+	padding: 0 0.5em 0 0;
+}
+
+p.reference {
+    font-size: x-small;
+    font-style: italic;
 }
 
 /* @end */
@@ -558,11 +642,10 @@ div.navpath {
 }
 
 div.summary {
-	float: right;
-	font-size: x-small;
-	padding: 0.25em 0.5em 0 0;
-	width: 50%;
-	text-align: right;
+	font-size: small;
+    font-family: "DejaVu Sans","Helvetica","Arial";
+    margin: 0.5em 0 0.5em 0;
+    color: #FFF; /* Hide separator bars */
 }
 
 div.summary a {
@@ -570,23 +653,62 @@ div.summary a {
 }
 
 div.header {
-	background-color: #F3F3F3;
-	margin: 0;
-	border: 0;
+	/* background-color: #F3F3F3; */
+	padding: 0;
+    margin: 0 0.5em 1em 0.5em;
+	/* border: 0; */
+    border-top: 1px solid #DDD;
+    border-bottom: 1px solid #DDD;
 }
 
 div.headertitle {
-	font-size: 180%;
-	font-weight: bold;
-	color: #FFF;
-	padding: 0.125em 0.25em 0.125em 0.25em;
-	background-color: #333;
-	background: linear-gradient(to bottom, #333 0%, #111 100%);
-	border: solid 1px #444;
-	border-top: 0;
-	border-radius: 0 0 6px 6px;
+    display: none;
+	/* font-size: 180%; */
+	/* font-weight: bold; */
+	/* color: #FFF; */
+	/* padding: 0.125em 0.25em 0.125em 0.25em; */
+	/* background-color: #333; */
+	/* background: linear-gradient(to bottom, #333 0%, #111 100%); */
+	/* border: solid 1px #444; */
+	/* border-top: 0; */
+	/* border-radius: 0 0 6px 6px; */
+}
+
+/* Metadata box (right aligned next to title) */
+
+#metabox {
+	bottom: 0;
+	display: inline-block;
+	font-size: x-small;
+    font-style: italic;
+	margin: 0 0 0.5em 0;
+	position: absolute;
+	right: 0;
+    color: #666;
+    padding: 0;    
+}
+
+#meta {
+	border-style: hidden;
+    margin-right: 0.25em;
+}
+
+#meta tr, #meta th, #meta td {
+	background-color: transparent;
+	border: 0;
+	font-weight: normal;
+	/* padding: 0.125em 0.25em 0.125em 0.25em; */
+}
+
+#meta th {
+	text-align: right;
+}
+
+#meta th:after {
+	content: ":";
 }
 
+
 div.line {
 	font-family: monospace, fixed;
 	font-size: 13px;
@@ -637,6 +759,9 @@ span.lineno a:hover {
 
 th {
 	text-align: left;
+    font-family: "DejaVu Sans","Helvetica","Arial";
+    font-size: 110%;
+    font-weight: 500;
 }
 
 .mlabel {
@@ -672,9 +797,9 @@ th {
 	outline: none;
 }
 
-.header a {
-	color: #859900;
-}
+/* .header a { */
+/* 	color: #859900; */
+/* } */
 
 .tabs3 .tablist a {
 	padding: 0 10px;
@@ -682,464 +807,15 @@ th {
 
 .tablist a:hover {
 	color: #fff;
+	text-shadow: 0 1px 1px rgba(0, 0, 0, 1.0);
 	text-decoration: none;
 }
 
 .tablist li.current a {
 	color: #fff;
+	text-shadow: 0 1px 1px rgba(0, 0, 0, 1.0);
 }
 
 span.icon {
 	display: none;
 }
-
-/* nav bar */
-
-.sm {
-    position: relative;
-    z-index: 9999;
-}
-
-.sm,.sm ul,.sm li {
-    display: block;
-    list-style: none;
-    margin: 0;
-    padding: 0;
-    line-height: normal;
-    direction: ltr;
-    text-align: left;
-    -webkit-tap-highlight-color: rgba(0,0,0,0);
-}
-
-.sm-rtl,.sm-rtl ul,.sm-rtl li {
-    direction: rtl;
-    text-align: right;
-}
-
-.sm>li>h1,.sm>li>h2,.sm>li>h3,.sm>li>h4,.sm>li>h5,.sm>li>h6 {
-    margin: 0;
-    padding: 0;
-}
-
-.sm ul {
-    display: none;
-}
-
-.sm li,.sm a {
-    position: relative;
-}
-
-.sm a {
-    display: block;
-}
-
-.sm a.disabled {
-    cursor: not-allowed;
-}
-
-.sm:after {
-    content: "\00a0";
-    display: block;
-    height: 0;
-    font: 0/0 serif;
-    clear: both;
-    visibility: hidden;
-    overflow: hidden;
-}
-
-.sm,.sm *,.sm :before,.sm :after {
-    -moz-box-sizing: border-box;
-    -webkit-box-sizing: border-box;
-    box-sizing: border-box;
-}
-
-#doc-content {
-    overflow: auto;
-    display: block;
-    padding: 0;
-    margin: 0;
-    -webkit-overflow-scrolling: touch;
-}
-
-.sm-dox {
-    background-image: none;
-	background-color: #333;
-	background: linear-gradient(to bottom, #333 0%, #111 100%);
-    color: #fff;
-}
-
-.sm-dox a,.sm-dox a:focus,.sm-dox a:hover,.sm-dox a:active {
-    padding: 0 12px;
-    padding-right: 43px;
-    font-size: small;
-    font-weight: 600;
-    line-height: auto;
-    text-decoration: none;
-    text-shadow: none;
-    color: inherit;
-    outline: 0;
-}
-
-.sm-dox a:hover {
-    background-image: none;
-    background-repeat: repeat-x;
-    color: inherit;
-    text-shadow: 0 1px 1px #000;
-}
-
-.sm-dox a.current {
-    color: #d23600;
-}
-
-.sm-dox a.disabled {
-    color: #bbb;
-}
-
-.sm-dox a span.sub-arrow {
-    position: absolute;
-    top: 50%;
-    margin-top: -14px;
-    left: auto;
-    right: 3px;
-    width: 28px;
-    height: 28px;
-    overflow: hidden;
-    font: bold 12px/28px monospace !important;
-    text-align: center;
-    text-shadow: none;
-    background: rgba(255,255,255,0.5);
-    -moz-border-radius: 5px;
-    -webkit-border-radius: 5px;
-    border-radius: 5px;
-}
-
-.sm-dox a.highlighted span.sub-arrow:before {
-    display: block;
-    content: '-';
-}
-
-.sm-dox>li:first-child>a,.sm-dox>li:first-child>:not(ul) a {
-    -moz-border-radius: 5px 5px 0 0;
-    -webkit-border-radius: 5px;
-    border-radius: 5px 5px 0 0;
-}
-
-.sm-dox>li:last-child>a,.sm-dox>li:last-child>:not(ul) a,.sm-dox>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul {
-    -moz-border-radius: 0 0 5px 5px;
-    -webkit-border-radius: 0;
-    border-radius: 0 0 5px 5px;
-}
-
-.sm-dox>li:last-child>a.highlighted,.sm-dox>li:last-child>:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>:not(ul) a.highlighted {
-    -moz-border-radius: 0;
-    -webkit-border-radius: 0;
-    border-radius: 0;
-}
-
-.sm-dox ul {
-    background: rgba(162,162,162,0.1);
-}
-
-.sm-dox ul a,.sm-dox ul a:focus,.sm-dox ul a:hover,.sm-dox ul a:active {
-    font-size: 12px;
-    border-left: 8px solid transparent;
-    line-height: auto;
-    text-shadow: none;
-    background-color: #fff;
-    background-image: none;
-}
-
-.sm-dox ul a:hover {
-    background-image: none;
-    background-repeat: repeat-x;
-    color: inherit;
-    text-shadow: 0 1px 1px #000;
-}
-
-.sm-dox ul ul a,.sm-dox ul ul a:hover,.sm-dox ul ul a:focus,.sm-dox ul ul a:active {
-    border-left: 16px solid transparent;
-}
-
-.sm-dox ul ul ul a,.sm-dox ul ul ul a:hover,.sm-dox ul ul ul a:focus,.sm-dox ul ul ul a:active {
-    border-left: 24px solid transparent;
-}
-
-.sm-dox ul ul ul ul a,.sm-dox ul ul ul ul a:hover,.sm-dox ul ul ul ul a:focus,.sm-dox ul ul ul ul a:active {
-    border-left: 32px solid transparent;
-}
-
-.sm-dox ul ul ul ul ul a,.sm-dox ul ul ul ul ul a:hover,.sm-dox ul ul ul ul ul a:focus,.sm-dox ul ul ul ul ul a:active {
-    border-left: 40px solid transparent;
-}
-
-@media(min-width:768px) {
-    .sm-dox ul {
-        position: absolute;
-        width: 12em;
-    }
-
-    .sm-dox li {
-        float: left;
-    }
-
-    .sm-dox.sm-rtl li {
-        float: right;
-    }
-
-    .sm-dox ul li,.sm-dox.sm-rtl ul li,.sm-dox.sm-vertical li {
-        float: none;
-    }
-
-    .sm-dox a {
-        white-space: nowrap;
-    }
-
-    .sm-dox ul a,.sm-dox.sm-vertical a {
-        white-space: normal;
-    }
-
-    .sm-dox .sm-nowrap>li>a,.sm-dox .sm-nowrap>li>:not(ul) a {
-        white-space: nowrap;
-    }
-
-    .sm-dox {
-        padding: 0 10px;
-        background-image: none;
-        background-color: #000;
-        line-height: normal;
-        background-image: none;
-	    background-color: #333;
-	    background: linear-gradient(to bottom, #333 0%, #111 100%);
-        color: #ddd;
-    }
-
-    .sm-dox a span.sub-arrow {
-        top: 50%;
-        margin-top: -2px;
-        right: 12px;
-        width: 0;
-        height: 0;
-        border-width: 4px;
-        border-style: solid dashed dashed;
-        border-color: #ddd transparent transparent;
-        background: transparent;
-        -moz-border-radius: 0;
-        -webkit-border-radius: 0;
-        border-radius: 0;
-    }
-
-    .sm-dox a,.sm-dox a:focus,.sm-dox a:active,.sm-dox a:hover,.sm-dox a.highlighted {
-        padding: 0 1em 0 0;
-        background-image: none;
-        background-repeat: no-repeat;
-        background-position: right;
-        -moz-border-radius: 0 !important;
-        -webkit-border-radius: 0;
-        border-radius: 0 !important;
-    }
-
-    .sm-dox a:hover {
-        background-image: none;
-        background-repeat: repeat-x;
-        color: #fff;
-        text-shadow: 0 1px 1px #000;
-    }
-
-    .sm-dox a:hover span.sub-arrow {
-        border-color: #fff transparent transparent;
-    }
-
-    .sm-dox a.has-submenu {
-        padding-right: 24px;
-    }
-
-    .sm-dox li {
-        border-top: 0;
-    }
-
-    .sm-dox>li>ul:before,.sm-dox>li>ul:after {
-        content: '';
-        position: absolute;
-        top: -18px;
-        left: 30px;
-        width: 0;
-        height: 0;
-        overflow: hidden;
-        border-width: 9px;
-        border-style: dashed dashed solid;
-        border-color: transparent transparent #bbb;
-    }
-
-    .sm-dox>li>ul:after {
-        top: -16px;
-        left: 31px;
-        border-width: 8px;
-        border-color: transparent transparent #fff;
-    }
-
-    .sm-dox ul {
-        border: 1px solid #bbb;
-        padding: 5px 0;
-        background: initial;
-        -moz-border-radius: 5px !important;
-        -webkit-border-radius: 5px;
-        border-radius: 5px !important;
-        -moz-box-shadow: 0 5px 9px rgba(0,0,0,0.2);
-        -webkit-box-shadow: 0 5px 9px rgba(0,0,0,0.2);
-        box-shadow: 0 5px 9px rgba(0,0,0,0.2);
-    }
-
-    .sm-dox ul a span.sub-arrow {
-        right: 8px;
-        top: 50%;
-        margin-top: -5px;
-        border-width: 5px;
-        border-color: transparent transparent transparent #555;
-        border-style: dashed dashed dashed solid;
-    }
-
-    .sm-dox ul a,.sm-dox ul a:hover,.sm-dox ul a:focus,.sm-dox ul a:active,.sm-dox ul a.highlighted {
-        color: #555;
-        background-image: none;
-        border: 0 !important;
-        color: #555;
-        background-image: none;
-    }
-
-    .sm-dox ul a:hover {
-        background-image: none;
-        background-repeat: repeat-x;
-        color: #fff;
-        text-shadow: 0 1px 1px #000;
-    }
-
-    .sm-dox ul a:hover span.sub-arrow {
-        border-color: transparent transparent transparent #fff;
-    }
-
-    .sm-dox span.scroll-up,.sm-dox span.scroll-down {
-        position: absolute;
-        display: none;
-        visibility: hidden;
-        overflow: hidden;
-        background: initial;
-        height: 36px;
-    }
-
-    .sm-dox span.scroll-up:hover,.sm-dox span.scroll-down:hover {
-        background: #eee;
-    }
-
-    .sm-dox span.scroll-up:hover span.scroll-up-arrow,.sm-dox span.scroll-up:hover span.scroll-down-arrow {
-        border-color: transparent transparent #d23600;
-    }
-
-    .sm-dox span.scroll-down:hover span.scroll-down-arrow {
-        border-color: #d23600 transparent transparent;
-    }
-
-    .sm-dox span.scroll-up-arrow,.sm-dox span.scroll-down-arrow {
-        position: absolute;
-        top: 0;
-        left: 50%;
-        margin-left: -6px;
-        width: 0;
-        height: 0;
-        overflow: hidden;
-        border-width: 6px;
-        border-style: dashed dashed solid;
-        border-color: transparent transparent #555;
-    }
-
-    .sm-dox span.scroll-down-arrow {
-        top: 8px;
-        border-style: solid dashed dashed;
-        border-color: #555 transparent transparent;
-    }
-
-    .sm-dox.sm-rtl a.has-submenu {
-        padding-right: 12px;
-        padding-left: 24px;
-    }
-
-    .sm-dox.sm-rtl a span.sub-arrow {
-        right: auto;
-        left: 12px;
-    }
-
-    .sm-dox.sm-rtl.sm-vertical a.has-submenu {
-        padding: 10px 20px;
-    }
-
-    .sm-dox.sm-rtl.sm-vertical a span.sub-arrow {
-        right: auto;
-        left: 8px;
-        border-style: dashed solid dashed dashed;
-        border-color: transparent #555 transparent transparent;
-    }
-
-    .sm-dox.sm-rtl>li>ul:before {
-        left: auto;
-        right: 30px;
-    }
-
-    .sm-dox.sm-rtl>li>ul:after {
-        left: auto;
-        right: 31px;
-    }
-
-    .sm-dox.sm-rtl ul a.has-submenu {
-        padding: 10px 20px !important;
-    }
-
-    .sm-dox.sm-rtl ul a span.sub-arrow {
-        right: auto;
-        left: 8px;
-        border-style: dashed solid dashed dashed;
-        border-color: transparent #555 transparent transparent;
-    }
-
-    .sm-dox.sm-vertical {
-        padding: 10px 0;
-        -moz-border-radius: 5px;
-        -webkit-border-radius: 5px;
-        border-radius: 5px;
-    }
-
-    .sm-dox.sm-vertical a {
-        padding: 10px 20px;
-    }
-
-    .sm-dox.sm-vertical a:hover,.sm-dox.sm-vertical a:focus,.sm-dox.sm-vertical a:active,.sm-dox.sm-vertical a.highlighted {
-        background: initial;
-    }
-
-    .sm-dox.sm-vertical a.disabled {
-        background-image: none;
-    }
-
-    .sm-dox.sm-vertical a span.sub-arrow {
-        right: 8px;
-        top: 50%;
-        margin-top: -5px;
-        border-width: 5px;
-        border-style: dashed dashed dashed solid;
-        border-color: transparent transparent transparent #555;
-    }
-
-    .sm-dox.sm-vertical>li>ul:before,.sm-dox.sm-vertical>li>ul:after {
-        display: none;
-    }
-
-    .sm-dox.sm-vertical ul a {
-        padding: 10px 20px;
-    }
-
-    .sm-dox.sm-vertical ul a:hover,.sm-dox.sm-vertical ul a:focus,.sm-dox.sm-vertical ul a:active,.sm-dox.sm-vertical ul a.highlighted {
-        background: #eee;
-    }
-
-    .sm-dox.sm-vertical ul a.disabled {
-        background: initial;
-    }
-}
diff --git a/ingen/AtomForge.hpp b/ingen/AtomForge.hpp
index acb24fac..241233ea 100644
--- a/ingen/AtomForge.hpp
+++ b/ingen/AtomForge.hpp
@@ -21,8 +21,8 @@
 #include "lv2/atom/atom.h"
 #include "lv2/atom/forge.h"
 #include "lv2/atom/util.h"
-#include "sord/sordmm.hpp"
-#include "sratom/sratom.h"
+#include "serd/serd.hpp"
+#include "sratom/sratom.hpp"
 
 #include <cassert>
 #include <cstdint>
@@ -31,14 +31,19 @@
 
 namespace ingen {
 
-/// An atom forge that writes to an automatically-resized memory buffer
+/** An atom forge that writes to an automatically-resized memory buffer.
+ *
+ * Can be used to easily forge a complete atom from a node using read(), or
+ * manually to forge more complex atoms using clear(), atom(), and the
+ * LV2_Atom_Forge interface.
+ */
 class AtomForge : public LV2_Atom_Forge
 {
 public:
-	explicit AtomForge(LV2_URID_Map& map)
+	explicit AtomForge(serd::World& world, LV2_URID_Map& map)
 	    : _size{0}
 	    , _capacity{8 * sizeof(LV2_Atom)}
-	    , _sratom{sratom_new(&map)}
+	    , _forger{world, map}
 	    , _buf{(LV2_Atom*)calloc(8, sizeof(LV2_Atom))}
 	{
 		lv2_atom_forge_init(this, &map);
@@ -46,14 +51,15 @@ public:
 	}
 
 	/// Forge an atom from `node` in `model`
-	void read(Sord::World& world, SordModel* model, const SordNode* node)
+	const LV2_Atom* read(serd::Model&        model,
+	                     const serd::Node&   node,
+	                     const sratom::Flags flags = {})
 	{
-		sratom_read(_sratom.get(), this, world.c_obj(), model, node);
+		clear();
+		_forger.read(serd::make_uri("file:///"), *this, model, node, flags);
+		return atom();
 	}
 
-	/// Return the top-level atom that has been forged
-	const LV2_Atom* atom() const { return _buf.get(); }
-
 	/// Clear the atom buffer and reset the forge
 	void clear()
 	{
@@ -62,14 +68,11 @@ public:
 		*_buf = {0U, 0U};
 	}
 
-	/// Return the internal atom serialiser
-	Sratom& sratom() { return *_sratom; }
+	/// Return the top-level atom that has been forged
+	const LV2_Atom* atom() const { return _buf.get(); }
 
 private:
-	struct SratomDeleter { void operator()(Sratom* s) { sratom_free(s); } };
-
 	using AtomPtr = UPtr<LV2_Atom, FreeDeleter<LV2_Atom>>;
-	using SratomPtr = UPtr<Sratom, SratomDeleter>;
 
 	/// Append some data and return a reference to its start
 	intptr_t append(const void* buf, uint32_t len) {
@@ -110,10 +113,10 @@ private:
 		return ((AtomForge*)handle)->deref(ref);
 	}
 
-	size_t    _size;     ///< Current atom size
-	size_t    _capacity; ///< Allocated size of atom buffer
-	SratomPtr _sratom;   ///< Atom serialiser
-	AtomPtr   _buf;      ///< Atom buffer
+	size_t         _size;     ///< Current atom size
+	size_t         _capacity; ///< Allocated size of atom buffer
+	sratom::Forger _forger;   ///< Atom forger
+	AtomPtr        _buf;      ///< Atom buffer
 };
 
 }  // namespace ingen
diff --git a/ingen/AtomWriter.hpp b/ingen/AtomWriter.hpp
index f9052d93..565fbdba 100644
--- a/ingen/AtomWriter.hpp
+++ b/ingen/AtomWriter.hpp
@@ -43,7 +43,7 @@ class INGEN_API AtomWriter : public Interface
 public:
 	using result_type = void; ///< For boost::apply_visitor
 
-	AtomWriter(URIMap& map, URIs& uris, AtomSink& sink);
+	AtomWriter(serd::World& world, URIMap& map, URIs& uris, AtomSink& sink);
 
 	URI uri() const override { return URI("ingen:/clients/atom_writer"); }
 
diff --git a/ingen/Configuration.hpp b/ingen/Configuration.hpp
index 6c0343b1..20fc79f5 100644
--- a/ingen/Configuration.hpp
+++ b/ingen/Configuration.hpp
@@ -22,6 +22,7 @@
 #include "ingen/ingen.h"
 #include "lv2/urid/urid.h"
 #include "raul/Exception.hpp"
+#include "serd/serd.hpp"
 
 #include <cstdio>
 #include <list>
@@ -86,7 +87,7 @@ public:
 	void parse(int argc, char **argv);
 
 	/** Load a specific file. */
-	bool load(const FilePath& path);
+	bool load(serd::World& world, const FilePath& path);
 
 	/** Save configuration to a file.
 	 *
@@ -103,7 +104,8 @@ public:
 	 *
 	 * @return The absolute path of the saved configuration file.
 	 */
-	FilePath save(URIMap&            uri_map,
+	FilePath save(serd::World&       world,
+	              URIMap&            uri_map,
 	              const std::string& app,
 	              const FilePath&    filename,
 	              unsigned           scopes);
@@ -114,7 +116,8 @@ public:
 	 * will be loaded before the user's, e.g. ~/.config/appname/filename,
 	 * so the user options will override the system options.
 	 */
-	std::list<FilePath> load_default(const std::string& app,
+	std::list<FilePath> load_default(serd::World&       world,
+	                                 const std::string& app,
 	                                 const FilePath&    filename);
 
 	const Atom& option(const std::string& long_name) const;
diff --git a/ingen/FilePath.hpp b/ingen/FilePath.hpp
index 98893dfc..8d5c6882 100644
--- a/ingen/FilePath.hpp
+++ b/ingen/FilePath.hpp
@@ -19,7 +19,7 @@
 
 #include "ingen/ingen.h"
 
-#include <boost/utility/string_view.hpp>
+#include "serd/serd.hpp"
 
 #include <ostream>
 #include <string>
@@ -57,7 +57,7 @@ public:
 	FilePath(string_type&& str) : _str(std::move(str)) {}
 	FilePath(const string_type& str) : _str(str) {}
 	FilePath(const value_type* str) : _str(str) {}
-	FilePath(const boost::basic_string_view<value_type>& sv)
+	FilePath(const serd::StringView& sv)
 		: _str(sv.data(), sv.length())
 	{}
 
@@ -73,7 +73,7 @@ public:
 	FilePath& operator+=(const string_type& str);
 	FilePath& operator+=(const value_type* str);
 	FilePath& operator+=(value_type chr);
-	FilePath& operator+=(boost::basic_string_view<value_type> sv);
+	FilePath& operator+=(serd::StringView sv);
 
 	void clear() noexcept { _str.clear(); }
 
diff --git a/ingen/Forge.hpp b/ingen/Forge.hpp
index b414a6ab..53cae153 100644
--- a/ingen/Forge.hpp
+++ b/ingen/Forge.hpp
@@ -19,7 +19,9 @@
 
 #include "ingen/Atom.hpp"
 #include "ingen/ingen.h"
+#include "lv2/atom/atom.h"
 #include "lv2/atom/forge.h"
+#include "serd/serd.hpp"
 
 #include <cstdint>
 #include <cstring>
@@ -59,8 +61,12 @@ public:
 		return Atom(size, type, val);
 	}
 
+	Atom alloc(const LV2_Atom* atom) {
+		return Atom(atom->size, atom->type, LV2_ATOM_BODY_CONST(atom));
+	}
+
 	Atom alloc(const char* v) {
-		const size_t len = strlen(v);
+		const auto len = strlen(v);
 		return Atom(len + 1, String, v);
 	}
 
@@ -68,12 +74,11 @@ public:
 		return Atom(v.length() + 1, String, v.c_str());
 	}
 
-	Atom alloc_uri(const char* v) {
-		const size_t len = strlen(v);
-		return Atom(len + 1, URI, v);
+	Atom alloc(serd::StringView v) {
+		return Atom(v.length() + 1, String, v.c_str());
 	}
 
-	Atom alloc_uri(const std::string& v) {
+	Atom alloc_uri(serd::StringView v) {
 		return Atom(v.length() + 1, URI, v.c_str());
 	}
 
diff --git a/ingen/Parser.hpp b/ingen/Parser.hpp
index 45d087cd..697e96c8 100644
--- a/ingen/Parser.hpp
+++ b/ingen/Parser.hpp
@@ -30,8 +30,6 @@
 #include <string>
 #include <utility>
 
-namespace Sord { class World; }
-
 namespace ingen {
 
 class Interface;
@@ -63,9 +61,8 @@ public:
 	};
 
 	/** Find all resources of a given type listed in a manifest file. */
-	virtual std::set<ResourceRecord> find_resources(Sord::World& world,
-	                                                const URI&   manifest_uri,
-	                                                const URI&   type_uri);
+	virtual std::set<ResourceRecord> find_resources(const URI& manifest_uri,
+	                                                const URI& type_uri);
 
 	/** Parse a graph from RDF into a Interface (engine or client).
 	 *
diff --git a/ingen/Serialiser.hpp b/ingen/Serialiser.hpp
index 1ac5c151..522d0bdb 100644
--- a/ingen/Serialiser.hpp
+++ b/ingen/Serialiser.hpp
@@ -21,7 +21,7 @@
 #include "ingen/Properties.hpp"
 #include "ingen/ingen.h"
 #include "ingen/types.hpp"
-#include "sord/sordmm.hpp"
+#include "serd/serd.hpp"
 
 #include <string>
 
@@ -30,6 +30,7 @@ namespace Raul { class Path; }
 namespace ingen {
 
 class Arc;
+class FilePath;
 class Node;
 class URI;
 class World;
@@ -84,8 +85,8 @@ public:
 	 *
 	 * @throw std::logic_error
 	 */
-	virtual void serialise_arc(const Sord::Node&      parent,
-	                           const SPtr<const Arc>& arc);
+	virtual void serialise_arc(const serd::Optional<serd::Node>& parent,
+	                           const SPtr<const Arc>&            arc);
 
 	/** Finish serialization.
 	 *
diff --git a/ingen/SocketReader.hpp b/ingen/SocketReader.hpp
index 3c3c5f3c..8afcafb7 100644
--- a/ingen/SocketReader.hpp
+++ b/ingen/SocketReader.hpp
@@ -19,8 +19,6 @@
 
 #include "ingen/ingen.h"
 #include "ingen/types.hpp"
-#include "serd/serd.h"
-#include "sord/sord.h"
 
 #include <thread>
 
@@ -47,29 +45,11 @@ protected:
 private:
 	void run();
 
-	static SerdStatus set_base_uri(SocketReader*   iface,
-	                               const SerdNode* uri_node);
-
-	static SerdStatus set_prefix(SocketReader*   iface,
-	                             const SerdNode* name,
-	                             const SerdNode* uri_node);
-
-	static SerdStatus write_statement(SocketReader*      iface,
-	                                  SerdStatementFlags flags,
-	                                  const SerdNode*    graph,
-	                                  const SerdNode*    subject,
-	                                  const SerdNode*    predicate,
-	                                  const SerdNode*    object,
-	                                  const SerdNode*    object_datatype,
-	                                  const SerdNode*    object_lang);
-
 	World&             _world;
 	Interface&         _iface;
-	SerdEnv*           _env;
-	SordInserter*      _inserter;
-	SordNode*          _msg_node;
 	SPtr<Raul::Socket> _socket;
 	bool               _exit_flag;
+
 	std::thread        _thread;
 };
 
diff --git a/ingen/SocketWriter.hpp b/ingen/SocketWriter.hpp
index 2424fe24..b3852940 100644
--- a/ingen/SocketWriter.hpp
+++ b/ingen/SocketWriter.hpp
@@ -39,7 +39,8 @@ class URIs;
 class INGEN_API SocketWriter : public TurtleWriter
 {
 public:
-	SocketWriter(URIMap&            map,
+	SocketWriter(serd::World&       world,
+	             URIMap&            map,
 	             URIs&              uris,
 	             const URI&         uri,
 	             SPtr<Raul::Socket> sock);
diff --git a/ingen/StreamWriter.hpp b/ingen/StreamWriter.hpp
index 56603b92..15ef42c1 100644
--- a/ingen/StreamWriter.hpp
+++ b/ingen/StreamWriter.hpp
@@ -34,7 +34,8 @@ class URIs;
 class INGEN_API StreamWriter : public TurtleWriter
 {
 public:
-	StreamWriter(URIMap&             map,
+	StreamWriter(serd::World&        world,
+	             URIMap&             map,
 	             URIs&               uris,
 	             const URI&          uri,
 	             FILE*               stream,
diff --git a/ingen/TurtleWriter.hpp b/ingen/TurtleWriter.hpp
index 9c88be2e..09c01d26 100644
--- a/ingen/TurtleWriter.hpp
+++ b/ingen/TurtleWriter.hpp
@@ -23,7 +23,7 @@
 #include "ingen/ingen.h"
 #include "lv2/atom/atom.h"
 #include "serd/serd.h"
-#include "sratom/sratom.h"
+#include "sratom/sratom.hpp"
 
 #include <cstddef>
 #include <cstdint>
@@ -41,7 +41,7 @@ class URIs;
 class INGEN_API TurtleWriter : public AtomWriter, public AtomSink
 {
 public:
-	TurtleWriter(URIMap& map, URIs& uris, URI uri);
+	TurtleWriter(serd::World& world, URIMap& map, URIs& uris, const URI& uri);
 
 	~TurtleWriter() override;
 
@@ -54,14 +54,13 @@ public:
 	URI uri() const override { return _uri; }
 
 protected:
-	URIMap&     _map;
-	Sratom*     _sratom;
-	SerdNode    _base;
-	SerdURI     _base_uri;
-	SerdEnv*    _env;
-	SerdWriter* _writer;
-	URI         _uri;
-	bool        _wrote_prefixes;
+	URIMap&          _map;
+	sratom::Streamer _streamer;
+	serd::Node       _base;
+	serd::Env        _env;
+	serd::Writer     _writer;
+	URI              _uri;
+	bool             _wrote_prefixes;
 };
 
 }  // namespace ingen
diff --git a/ingen/URI.hpp b/ingen/URI.hpp
index cbbfd46a..489b6219 100644
--- a/ingen/URI.hpp
+++ b/ingen/URI.hpp
@@ -19,8 +19,7 @@
 
 #include "ingen/FilePath.hpp"
 #include "ingen/ingen.h"
-#include "serd/serd.h"
-#include "sord/sordmm.hpp"
+#include "serd/serd.hpp"
 
 #include <boost/utility/string_view.hpp>
 
@@ -31,130 +30,45 @@
 
 namespace ingen {
 
-class INGEN_API URI
+class INGEN_API URI : public serd::Node
 {
 public:
-	using Chunk = boost::string_view;
+	using Slice = serd::StringView;
 
-	URI();
 	explicit URI(const std::string& str);
 	explicit URI(const char* str);
-	URI(const std::string& str, const URI& base);
-	URI(const Sord::Node& node);
-	URI(SerdNode node);
+	URI(const serd::NodeView& node);
+	URI(const serd::Node& node);
 	explicit URI(const FilePath& path);
 
-	URI(const URI& uri);
-	URI& operator=(const URI& uri);
-
-	URI(URI&& uri) noexcept;
-	URI& operator=(URI&& uri) noexcept;
-
-	~URI();
-
 	URI make_relative(const URI& base) const;
 
-	bool empty() const { return !_node.buf; }
-
-	std::string string() const { return std::string(c_str(), _node.n_bytes); }
-	size_t      length() const { return _node.n_bytes; }
-	const char* c_str()  const { return (const char*)_node.buf; }
+	std::string string() const { return std::string(*this); }
+	std::string str() const { return std::string(*this); }
 
 	FilePath file_path() const {
 		return scheme() == "file" ? FilePath(path()) : FilePath();
 	}
 
-	operator std::string() const { return string(); }
-
-	const char* begin() const { return (const char*)_node.buf; }
-	const char* end()   const { return (const char*)_node.buf + _node.n_bytes; }
-
-	Chunk scheme()    const { return make_chunk(_uri.scheme); }
-	Chunk authority() const { return make_chunk(_uri.authority); }
-	Chunk path()      const { return make_chunk(_uri.path); }
-	Chunk query()     const { return make_chunk(_uri.query); }
-	Chunk fragment()  const { return make_chunk(_uri.fragment); }
+	Slice scheme()    const { return serd::URI{*this}.scheme(); }
+	Slice authority() const { return serd::URI{*this}.authority(); }
+	Slice path()      const { return serd::URI{*this}.path(); }
+	Slice query()     const { return serd::URI{*this}.query(); }
+	Slice fragment()  const { return serd::URI{*this}.fragment(); }
 
 	static bool is_valid(const char* str) {
-		return serd_uri_string_has_scheme((const uint8_t*)str);
+		return serd_uri_string_has_scheme(str);
 	}
 
 	static bool is_valid(const std::string& str)
 	{
 		return is_valid(str.c_str());
 	}
-
-private:
-	URI(SerdNode node, SerdURI uri);
-
-	static Chunk make_chunk(const SerdChunk& chunk) {
-		return Chunk((const char*)chunk.buf, chunk.len);
-	}
-
-	SerdURI  _uri;
-	SerdNode _node;
 };
 
-inline bool operator==(const URI& lhs, const URI& rhs)
-{
-	return lhs.string() == rhs.string();
-}
-
 inline bool operator==(const URI& lhs, const std::string& rhs)
 {
-	return lhs.string() == rhs;
-}
-
-inline bool operator==(const URI& lhs, const char* rhs)
-{
-	return lhs.string() == rhs;
-}
-
-inline bool operator==(const URI& lhs, const Sord::Node& rhs)
-{
-	return rhs.type() == Sord::Node::URI && lhs.string() == rhs.to_string();
-}
-
-inline bool operator==(const Sord::Node& lhs, const URI& rhs)
-{
-	return rhs == lhs;
-}
-
-inline bool operator!=(const URI& lhs, const URI& rhs)
-{
-	return lhs.string() != rhs.string();
-}
-
-inline bool operator!=(const URI& lhs, const std::string& rhs)
-{
-	return lhs.string() != rhs;
-}
-
-inline bool operator!=(const URI& lhs, const char* rhs)
-{
-	return lhs.string() != rhs;
-}
-
-inline bool operator!=(const URI& lhs, const Sord::Node& rhs)
-{
-	return !(lhs == rhs);
-}
-
-inline bool operator!=(const Sord::Node& lhs, const URI& rhs)
-{
-	return !(lhs == rhs);
-}
-
-inline bool operator<(const URI& lhs, const URI& rhs)
-{
-	return lhs.string() < rhs.string();
-}
-
-template <typename Char, typename Traits>
-inline std::basic_ostream<Char, Traits>&
-operator<<(std::basic_ostream<Char, Traits>& os, const URI& uri)
-{
-	return os << uri.string();
+	return lhs.c_str() == rhs;
 }
 
 } // namespace ingen
diff --git a/ingen/URIMap.hpp b/ingen/URIMap.hpp
index a3b9f219..3ca22557 100644
--- a/ingen/URIMap.hpp
+++ b/ingen/URIMap.hpp
@@ -18,6 +18,7 @@
 #define INGEN_URIMAP_HPP
 
 #include "ingen/LV2Features.hpp"
+#include "ingen/URI.hpp"
 #include "ingen/ingen.h"
 #include "ingen/types.hpp"
 #include "lv2/core/lv2.h"
@@ -45,6 +46,7 @@ public:
 
 	uint32_t    map_uri(const char* uri);
 	uint32_t    map_uri(const std::string& uri) { return map_uri(uri.c_str()); }
+	uint32_t    map_uri(const URI& uri) { return map_uri(uri.c_str()); }
 	const char* unmap_uri(uint32_t urid) const;
 
 	class Feature : public LV2Features::Feature {
diff --git a/ingen/URIs.hpp b/ingen/URIs.hpp
index eb657473..f8bf65a9 100644
--- a/ingen/URIs.hpp
+++ b/ingen/URIs.hpp
@@ -47,17 +47,12 @@ public:
 		      LilvWorld*    lworld,
 		      const char*   str);
 
-		Quark(const Quark& copy);
-
-		~Quark();
-
 		operator LV2_URID()        const { return urid.get<LV2_URID>(); }
 		explicit operator Atom()   const { return urid; }
-		operator const LilvNode*() const { return lnode; }
+		operator const LilvNode*() const { return cobj(); }
 
-		Atom      urid;
-		Atom      uri;
-		LilvNode* lnode;
+		Atom urid;
+		Atom uri;
 	};
 
 	ingen::Forge& forge;
diff --git a/ingen/World.hpp b/ingen/World.hpp
index a2906525..62d160b1 100644
--- a/ingen/World.hpp
+++ b/ingen/World.hpp
@@ -28,7 +28,7 @@
 
 typedef struct LilvWorldImpl LilvWorld;
 
-namespace Sord { class World; }
+namespace serd { class World; class Env; }
 
 namespace ingen {
 
@@ -49,7 +49,7 @@ class URIs;
  *
  * This is the root to which all components of Ingen are connected.  It
  * contains all necessary shared data (including the world for libraries like
- * Sord and Lilv) and holds references to components.
+ * Serd and Lilv) and holds references to components.
  *
  * Some functionality in Ingen is implemented in dynamically loaded modules,
  * which are loaded using this interface.  When loaded, those modules add
@@ -126,7 +126,8 @@ public:
 	/** Lock for rdf_world() or lilv_world(). */
 	virtual std::mutex& rdf_mutex();
 
-	virtual Sord::World* rdf_world();
+	virtual serd::World& rdf_world();
+	virtual serd::Env&   env();
 	virtual LilvWorld*   lilv_world();
 
 	virtual LV2Features&  lv2_features();
diff --git a/ingen/client/PluginModel.hpp b/ingen/client/PluginModel.hpp
index 5f43b3c4..c2d291f0 100644
--- a/ingen/client/PluginModel.hpp
+++ b/ingen/client/PluginModel.hpp
@@ -25,7 +25,7 @@
 #include "ingen/types.hpp"
 #include "lilv/lilv.h"
 #include "raul/Symbol.hpp"
-#include "sord/sordmm.hpp"
+#include "serd/serd.hpp"
 
 #include <cstdint>
 #include <map>
@@ -88,12 +88,6 @@ public:
 	std::string documentation(bool html) const;
 	std::string port_documentation(uint32_t index, bool html) const;
 
-	static void set_rdf_world(Sord::World& world) {
-		_rdf_world = &world;
-	}
-
-	static Sord::World* rdf_world() { return _rdf_world; }
-
 	// Signals
 	INGEN_SIGNAL(changed, void);
 	INGEN_SIGNAL(property, void, const URI&, const Atom&);
@@ -111,7 +105,6 @@ protected:
 private:
 	std::string get_documentation(const LilvNode* subject, bool html) const;
 
-	static Sord::World*       _rdf_world;
 	static LilvWorld*         _lilv_world;
 	static const LilvPlugins* _lilv_plugins;
 
diff --git a/ingen/client/SocketClient.hpp b/ingen/client/SocketClient.hpp
index 092ef9d2..b3656d92 100644
--- a/ingen/client/SocketClient.hpp
+++ b/ingen/client/SocketClient.hpp
@@ -33,9 +33,13 @@ public:
 	             const URI&         uri,
 	             SPtr<Raul::Socket> sock,
 	             SPtr<Interface>    respondee)
-		: SocketWriter(world.uri_map(), world.uris(), uri, sock)
-		, _respondee(respondee)
-		, _reader(world, *respondee.get(), sock)
+	    : SocketWriter(world.rdf_world(),
+	                   world.uri_map(),
+	                   world.uris(),
+	                   uri,
+	                   sock)
+	    , _respondee(respondee)
+	    , _reader(world, *respondee.get(), sock)
 	{}
 
 	SPtr<Interface> respondee() const override {
@@ -56,7 +60,7 @@ public:
 		                                 : Raul::Socket::Type::TCP);
 
 		SPtr<Raul::Socket> sock(new Raul::Socket(type));
-		if (!sock->connect(uri)) {
+		if (!sock->connect(uri.str())) {
 			world.log().error("Failed to connect <%1%> (%2%)\n",
 			                  sock->uri(), strerror(errno));
 			return SPtr<Interface>();
diff --git a/ingen/filesystem.hpp b/ingen/filesystem.hpp
index 5156eb79..8be6b1f4 100644
--- a/ingen/filesystem.hpp
+++ b/ingen/filesystem.hpp
@@ -27,6 +27,8 @@
 #    include <io.h>
 #    define F_OK 0
 #    define mkdir(path, flags) _mkdir(path)
+#else
+#    include <unistd.h>
 #endif
 
 #include <cerrno>
diff --git a/ingen/paths.hpp b/ingen/paths.hpp
index 05496114..fa6c9aff 100644
--- a/ingen/paths.hpp
+++ b/ingen/paths.hpp
@@ -29,12 +29,12 @@ inline URI main_uri() { return URI("ingen:/main"); }
 
 inline bool uri_is_path(const URI& uri)
 {
-	const size_t root_len = main_uri().string().length();
+	const size_t root_len = main_uri().length();
 	if (uri == main_uri()) {
 		return true;
 	} else {
-		return uri.string().substr(0, root_len + 1) ==
-		       main_uri().string() + "/";
+		return uri.str().substr(0, root_len + 1) ==
+		       main_uri().str() + "/";
 	}
 }
 
diff --git a/src/AtomWriter.cpp b/src/AtomWriter.cpp
index 27f224fc..e2c0f252 100644
--- a/src/AtomWriter.cpp
+++ b/src/AtomWriter.cpp
@@ -74,11 +74,14 @@
 
 namespace ingen {
 
-AtomWriter::AtomWriter(URIMap& map, URIs& uris, AtomSink& sink)
-	: _map(map)
-	, _uris(uris)
-	, _sink(sink)
-	, _forge(map.urid_map_feature()->urid_map)
+AtomWriter::AtomWriter(serd::World& world,
+                       URIMap&      map,
+                       URIs&        uris,
+                       AtomSink&    sink)
+    : _map(map)
+    , _uris(uris)
+    , _sink(sink)
+    , _forge(world, map.urid_map_feature()->urid_map)
 {
 }
 
@@ -135,7 +138,7 @@ AtomWriter::operator()(const BundleEnd& message)
 void
 AtomWriter::forge_uri(const URI& uri)
 {
-	if (serd_uri_string_has_scheme((const uint8_t*)uri.c_str())) {
+	if (serd_uri_string_has_scheme(uri.c_str())) {
 		lv2_atom_forge_urid(&_forge, _map.map_uri(uri.c_str()));
 	} else {
 		lv2_atom_forge_uri(&_forge, uri.c_str(), uri.length());
diff --git a/src/Configuration.cpp b/src/Configuration.cpp
index 5178e97a..611d949b 100644
--- a/src/Configuration.cpp
+++ b/src/Configuration.cpp
@@ -22,10 +22,8 @@
 #include "ingen/ingen.h"
 #include "ingen/runtime_paths.hpp"
 #include "lv2/urid/urid.h"
-#include "serd/serd.h"
-#include "sord/sord.h"
-#include "sord/sordmm.hpp"
-#include "sratom/sratom.h"
+#include "serd/serd.hpp"
+#include "sratom/sratom.hpp"
 
 #include <algorithm>
 #include <cassert>
@@ -33,6 +31,7 @@
 #include <cstdint>
 #include <cstdlib>
 #include <cstring>
+#include <fstream>
 #include <memory>
 #include <thread>
 #include <utility>
@@ -226,43 +225,40 @@ Configuration::parse(int argc, char** argv)
 }
 
 bool
-Configuration::load(const FilePath& path)
+Configuration::load(serd::World& world, const FilePath& path)
 {
 	if (!filesystem::exists(path)) {
 		return false;
 	}
 
-	SerdNode node = serd_node_new_file_uri(
-		(const uint8_t*)path.c_str(), nullptr, nullptr, true);
-	const std::string uri((const char*)node.buf);
-
-	Sord::World world;
-	Sord::Model model(world, uri, SORD_SPO, false);
-	SerdEnv*    env = serd_env_new(&node);
-	model.load_file(env, SERD_TURTLE, uri, uri);
-
-	Sord::Node nodemm(world, Sord::Node::URI, (const char*)node.buf);
-	Sord::Node nil;
-	for (Sord::Iter i = model.find(nodemm, nil, nil); !i.end(); ++i) {
-		const Sord::Node& pred = i.get_predicate();
-		const Sord::Node& obj  = i.get_object();
-		if (pred.to_string().substr(0, sizeof(INGEN_NS) - 1) == INGEN_NS) {
-			const std::string key = pred.to_string().substr(sizeof(INGEN_NS) - 1);
-			const Keys::iterator k = _keys.find(key);
-			if (k != _keys.end() && obj.type() == Sord::Node::LITERAL) {
+	const serd::Node uri(serd::make_file_uri(path.c_str(), nullptr));
+	serd::Env        env(uri);
+	serd::Model      model(world, serd::ModelFlag::index_SPO);
+	serd::Inserter   inserter(model, env);
+	serd::Reader     reader(world, serd::Syntax::Turtle, {}, inserter.sink(), 4096);
+	reader.start_file(uri);
+	reader.read_document();
+	reader.finish();
+
+	for (const auto& s : model.range(uri, {}, {})) {
+		const auto& pred = s.predicate();
+		const auto& obj  = s.object();
+		if (pred.str().substr(0, sizeof(INGEN_NS) - 1) == INGEN_NS) {
+			const auto key = pred.str().substr(sizeof(INGEN_NS) - 1);
+			const Keys::iterator k = _keys.find(key.str());
+			if (k != _keys.end() && obj.type() == serd::NodeType::literal) {
 				set_value_from_string(_options.find(k->second)->second,
-				                      obj.to_string());
+				                      std::string(obj));
 			}
 		}
 	}
 
-	serd_node_free(&node);
-	serd_env_free(env);
 	return true;
 }
 
 FilePath
-Configuration::save(URIMap&            uri_map,
+Configuration::save(serd::World&       world,
+                    URIMap&            uri_map,
                     const std::string& app,
                     const FilePath&    filename,
                     unsigned           scopes)
@@ -281,41 +277,33 @@ Configuration::save(URIMap&            uri_map,
 	}
 
 	// Attempt to open file for writing
-	std::unique_ptr<FILE, decltype(&fclose)> file{fopen(path.c_str(), "w"),
-	                                              &fclose};
-	if (!file) {
+	std::ofstream file(path);
+	if (!file.good()) {
 		throw FileError(fmt("Failed to open file %1% (%2%)",
 		                    path, strerror(errno)));
 	}
 
 	// Use the file's URI as the base URI
-	SerdURI  base_uri;
-	SerdNode base = serd_node_new_file_uri(
-		(const uint8_t*)path.c_str(), nullptr, &base_uri, true);
+	serd::Node base = serd::make_file_uri(path.c_str());
 
 	// Create environment with ingen prefix
-	SerdEnv* env = serd_env_new(&base);
-	serd_env_set_prefix_from_strings(
-		env, (const uint8_t*)"ingen", (const uint8_t*)INGEN_NS);
+	serd::Env env(base);
+	env.set_prefix("ingen", INGEN_NS);
 
 	// Create Turtle writer
-	SerdWriter* writer = serd_writer_new(
-		SERD_TURTLE,
-		(SerdStyle)(SERD_STYLE_RESOLVED|SERD_STYLE_ABBREVIATED),
-		env,
-		&base_uri,
-		serd_file_sink,
-		file.get());
+	serd::Writer writer(world,
+	                    serd::Syntax::Turtle,
+	                    {},
+	                    env,
+	                    file);
 
 	// Write a prefix directive for each prefix in the environment
-	serd_env_foreach(env, (SerdPrefixSink)serd_writer_set_prefix, writer);
+	env.write_prefixes(writer.sink());
 
 	// Create an atom serialiser and connect it to the Turtle writer
-	Sratom* sratom = sratom_new(&uri_map.urid_map_feature()->urid_map);
-	sratom_set_pretty_numbers(sratom, true);
-	sratom_set_sink(sratom, (const char*)base.buf,
-	                (SerdStatementSink)serd_writer_write_statement, nullptr,
-	                writer);
+	sratom::Streamer streamer{world,
+	                          uri_map.urid_map_feature()->urid_map,
+	                          uri_map.urid_unmap_feature()->urid_unmap};
 
 	// Write a statement for each valid option
 	for (const auto& o : _options) {
@@ -327,35 +315,31 @@ Configuration::save(URIMap&            uri_map,
 		}
 
 		const std::string key(std::string("ingen:") + o.second.key);
-		SerdNode pred = serd_node_from_string(
-			SERD_CURIE, (const uint8_t*)key.c_str());
-		sratom_write(sratom, &uri_map.urid_unmap_feature()->urid_unmap, 0,
-		             &base, &pred, value.type(), value.size(), value.get_body());
+		const serd::Node  pred = serd::make_curie(key);
+		streamer.write(
+			writer.sink(), base, pred, *value.atom(), sratom::Flag::pretty_numbers);
 	}
 
-	sratom_free(sratom);
-	serd_writer_free(writer);
-	serd_env_free(env);
-	serd_node_free(&base);
-
 	return path;
 }
 
 std::list<FilePath>
-Configuration::load_default(const std::string& app, const FilePath& filename)
+Configuration::load_default(serd::World&       world,
+                            const std::string& app,
+                            const FilePath&    filename)
 {
 	std::list<FilePath> loaded;
 
 	const std::vector<FilePath> dirs = system_config_dirs();
 	for (const auto& d : dirs) {
 		const FilePath path = d / app / filename;
-		if (load(path)) {
+		if (load(world, path)) {
 			loaded.push_back(path);
 		}
 	}
 
 	const FilePath path = user_config_dir() / app / filename;
-	if (load(path)) {
+	if (load(world, path)) {
 		loaded.push_back(path);
 	}
 
diff --git a/src/FilePath.cpp b/src/FilePath.cpp
index d16c133c..33b1b937 100644
--- a/src/FilePath.cpp
+++ b/src/FilePath.cpp
@@ -89,7 +89,7 @@ FilePath::operator+=(value_type chr)
 }
 
 FilePath&
-FilePath::operator+=(boost::basic_string_view<value_type> sv)
+FilePath::operator+=(serd::StringView sv)
 {
 	_str.append(sv.data(), sv.size());
 	return *this;
diff --git a/src/Forge.cpp b/src/Forge.cpp
index cc1b12c8..d9390e08 100644
--- a/src/Forge.cpp
+++ b/src/Forge.cpp
@@ -35,7 +35,7 @@ Forge::Forge(URIMap& map)
 Atom
 Forge::make_urid(const ingen::URI& u)
 {
-	const LV2_URID urid = _map.map_uri(u.string());
+	const LV2_URID urid = _map.map_uri(u.c_str());
 	return Atom(sizeof(int32_t), URID, &urid);
 }
 
diff --git a/src/Parser.cpp b/src/Parser.cpp
index 5cc1dedd..936f1b9f 100644
--- a/src/Parser.cpp
+++ b/src/Parser.cpp
@@ -16,6 +16,9 @@
 
 #include "ingen/Parser.hpp"
 
+#include <boost/optional/optional.hpp>
+#include <boost/optional/optional_io.hpp>
+
 #include "ingen/Atom.hpp"
 #include "ingen/AtomForge.hpp"
 #include "ingen/Forge.hpp"
@@ -35,9 +38,7 @@
 #include "lv2/urid/urid.h"
 #include "raul/Path.hpp"
 #include "raul/Symbol.hpp"
-#include "serd/serd.h"
-#include "sord/sord.h"
-#include "sord/sordmm.hpp"
+#include "serd/serd.hpp"
 
 #include <cassert>
 #include <cstdint>
@@ -52,36 +53,43 @@
 
 namespace ingen {
 
+static void
+load_file(serd::World& world,
+          serd::Model& model,
+          serd::Env&   env,
+          const URI&   file_uri)
+{
+	serd::Inserter inserter(model, env);
+	serd::Reader reader(world, serd::Syntax::Turtle, {}, inserter.sink(), 4096);
+
+	reader.start_file(file_uri.str());
+	reader.read_document();
+	reader.finish();
+}
+
 std::set<Parser::ResourceRecord>
-Parser::find_resources(Sord::World& world,
-                       const URI&   manifest_uri,
-                       const URI&   type_uri)
+Parser::find_resources(const URI& manifest_uri, const URI& type_uri)
 {
-	const Sord::URI  base        (world, manifest_uri.string());
-	const Sord::URI  type        (world, type_uri.string());
-	const Sord::URI  rdf_type    (world, NS_RDF "type");
-	const Sord::URI  rdfs_seeAlso(world, NS_RDFS "seeAlso");
-	const Sord::Node nil;
+	const auto rdf_type     = serd::make_uri(NS_RDF "type");
+	const auto rdfs_seeAlso = serd::make_uri(NS_RDFS "seeAlso");
+
+	serd::World world;
+	serd::Env   env{serd::Node(manifest_uri)};
+	serd::Model model{world,
+	                  serd::ModelFlag::index_SPO | serd::ModelFlag::index_OPS};
 
-	SerdEnv* env = serd_env_new(sord_node_to_serd_node(base.c_obj()));
-	Sord::Model model(world, manifest_uri.string());
-	model.load_file(env, SERD_TURTLE, manifest_uri.string());
+	load_file(world, model, env, manifest_uri);
 
 	std::set<ResourceRecord> resources;
-	for (Sord::Iter i = model.find(nil, rdf_type, type); !i.end(); ++i) {
-		const Sord::Node  resource     = i.get_subject();
-		const std::string resource_uri = resource.to_c_string();
-		Sord::Iter        f            = model.find(resource, rdfs_seeAlso, nil);
-		std::string       file_path;
-		if (!f.end()) {
-			uint8_t* p = serd_file_uri_parse(f.get_object().to_u_string(), nullptr);
-			file_path = (const char*)p;
-			serd_free(p);
-		}
+	for (const auto& s : model.range({}, rdf_type, type_uri)) {
+		const auto&    resource = s.subject();
+		const auto     f        = model.find(resource, rdfs_seeAlso, {});
+		const FilePath file_path =
+		        ((f != model.end() ? serd::file_uri_parse((*f).object().str())
+		                           : ""));
 		resources.insert(ResourceRecord(resource, file_path));
 	}
 
-	serd_env_free(env);
 	return resources;
 }
 
@@ -89,13 +97,14 @@ static boost::optional<Raul::Path>
 get_path(const URI& base, const URI& uri)
 {
 	const URI         relative = uri.make_relative(base);
-	const std::string uri_str  = "/" + relative.string();
+	const std::string uri_str  = relative.string();
+	// assert(Raul::Path::is_valid(uri_str));
 	return Raul::Path::is_valid(uri_str) ? Raul::Path(uri_str)
 	                                     : boost::optional<Raul::Path>();
 }
 
 static bool
-skip_property(ingen::URIs& uris, const Sord::Node& predicate)
+skip_property(ingen::URIs& uris, const serd::Node& predicate)
 {
 	return (predicate == INGEN__file ||
 	        predicate == uris.ingen_arc ||
@@ -105,25 +114,20 @@ skip_property(ingen::URIs& uris, const Sord::Node& predicate)
 
 static Properties
 get_properties(ingen::World&                      world,
-               Sord::Model&                       model,
-               const Sord::Node&                  subject,
+               serd::Model&                       model,
+               const serd::Node&                  subject,
                Resource::Graph                    ctx,
                const boost::optional<Properties>& data = {})
 {
-	AtomForge forge(world.uri_map().urid_map_feature()->urid_map);
-
-	const Sord::Node nil;
-	Properties       props;
-	for (Sord::Iter i = model.find(subject, nil, nil); !i.end(); ++i) {
-		if (!skip_property(world.uris(), i.get_predicate())) {
-			forge.clear();
-			forge.read(
-				*world.rdf_world(), model.c_obj(), i.get_object().c_obj());
-			const LV2_Atom* atom = forge.atom();
-			Atom            atomm;
-			atomm = world.forge().alloc(
-				atom->size, atom->type, LV2_ATOM_BODY_CONST(atom));
-			props.emplace(i.get_predicate(), Property(atomm, ctx));
+	AtomForge forge(world.rdf_world(),
+	                world.uri_map().urid_map_feature()->urid_map);
+
+	Properties props;
+	for (const auto& s : model.range(subject, {}, {})) {
+		if (!skip_property(world.uris(), s.predicate())) {
+			auto atom = forge.read(model, s.object());
+			props.emplace(s.predicate(),
+			              Property(world.forge().alloc(atom), ctx));
 		}
 	}
 
@@ -151,8 +155,8 @@ using PortRecord = std::pair<Raul::Path, Properties>;
 
 static boost::optional<PortRecord>
 get_port(ingen::World&     world,
-         Sord::Model&      model,
-         const Sord::Node& subject,
+         serd::Model&      model,
+         const serd::Node& subject,
          Resource::Graph   ctx,
          const Raul::Path& parent,
          uint32_t*         index)
@@ -180,8 +184,8 @@ get_port(ingen::World&     world,
 	if (s != props.end() && s->second.type() == world.forge().String) {
 		sym = s->second.ptr<char>();
 	} else {
-		const std::string subject_str = subject.to_string();
-		const size_t      last_slash  = subject_str.find_last_of('/');
+		const std::string subject_str{subject.str()};
+		const size_t      last_slash{subject_str.find_last_of('/')};
 
 		sym = ((last_slash == std::string::npos)
 		       ? subject_str
@@ -204,9 +208,8 @@ static boost::optional<Raul::Path>
 parse(
 	World&                               world,
 	Interface&                           target,
-	Sord::Model&                         model,
+	serd::Model&                         model,
 	const URI&                           base_uri,
-	Sord::Node&                          subject,
 	const boost::optional<Raul::Path>&   parent = boost::optional<Raul::Path>(),
 	const boost::optional<Raul::Symbol>& symbol = boost::optional<Raul::Symbol>(),
 	const boost::optional<Properties>&   data   = boost::optional<Properties>());
@@ -215,9 +218,9 @@ static boost::optional<Raul::Path>
 parse_graph(
 	World&                               world,
 	Interface&                           target,
-	Sord::Model&                         model,
+	serd::Model&                         model,
 	const URI&                           base_uri,
-	const Sord::Node&                    subject,
+	const serd::Node&                    subject,
 	Resource::Graph                      ctx,
 	const boost::optional<Raul::Path>&   parent = boost::optional<Raul::Path>(),
 	const boost::optional<Raul::Symbol>& symbol = boost::optional<Raul::Symbol>(),
@@ -227,9 +230,9 @@ static boost::optional<Raul::Path>
 parse_block(
 	World&                             world,
 	Interface&                         target,
-	Sord::Model&                       model,
+	serd::Model&                       model,
 	const URI&                         base_uri,
-	const Sord::Node&                  subject,
+	const serd::Node&                  subject,
 	const Raul::Path&                  path,
 	const boost::optional<Properties>& data = boost::optional<Properties>());
 
@@ -237,70 +240,58 @@ static bool
 parse_arcs(
 	World&             world,
 	Interface&         target,
-	Sord::Model&       model,
+	serd::Model&       model,
 	const URI&         base_uri,
-	const Sord::Node&  subject,
+	const serd::Node&  subject,
 	const Raul::Path&  graph);
 
 static boost::optional<Raul::Path>
 parse_block(ingen::World&                      world,
             ingen::Interface&                  target,
-            Sord::Model&                       model,
+            serd::Model&                       model,
             const URI&                         base_uri,
-            const Sord::Node&                  subject,
+            const serd::Node&                  subject,
             const Raul::Path&                  path,
             const boost::optional<Properties>& data)
 {
 	const URIs& uris = world.uris();
 
 	// Try lv2:prototype and old ingen:prototype for backwards compatibility
-	const Sord::URI prototype_predicates[] = {
-		Sord::URI(*world.rdf_world(), uris.lv2_prototype),
-		Sord::URI(*world.rdf_world(), uris.ingen_prototype)
-	};
+	const serd::Node prototype_predicates[] = {uris.lv2_prototype,
+	                                           uris.ingen_prototype};
 
 	// Get prototype
-	Sord::Node prototype;
-	for (const Sord::URI& pred : prototype_predicates) {
-		prototype = model.get(subject, pred, Sord::Node());
-		if (prototype.is_valid()) {
+	serd::Optional<serd::NodeView> prototype;
+	for (const auto& pred : prototype_predicates) {
+		prototype = model.get(subject, pred, {});
+		if (prototype) {
 			break;
 		}
 	}
 
-	if (!prototype.is_valid()) {
-		world.log().error("Block %1% (%2%) missing mandatory lv2:prototype\n",
-		                  subject, path);
+	if (!prototype) {
+		world.log().error(
+			fmt("Block %1% (%2%) missing mandatory lv2:prototype\n",
+			    subject, path));
 		return boost::optional<Raul::Path>();
 	}
 
-	const auto* type_uri = (const uint8_t*)prototype.to_c_string();
-	if (!serd_uri_string_has_scheme(type_uri) ||
-	    !strncmp((const char*)type_uri, "file:", 5)) {
+	const char* type_uri = prototype->c_str();
+	if (!serd_uri_string_has_scheme(type_uri) || !strncmp(type_uri, "file:", 5)) {
 		// Prototype is a file, subgraph
-		SerdURI base_uri_parts;
-		serd_uri_parse((const uint8_t*)base_uri.c_str(), &base_uri_parts);
-
-		SerdURI  ignored;
-		SerdNode sub_uri = serd_node_new_uri_from_string(
-			type_uri,
-			&base_uri_parts,
-			&ignored);
-
-		const std::string sub_uri_str = (const char*)sub_uri.buf;
-		const std::string sub_file    = sub_uri_str + "/main.ttl";
+		serd::Node sub_uri = serd::make_relative_uri(type_uri, base_uri);
 
-		const SerdNode sub_base = serd_node_from_string(
-			SERD_URI, (const uint8_t*)sub_file.c_str());
+		const URI        sub_file{sub_uri.str().str() + "/main.ttl"};
+		const serd::Node sub_base = serd::make_uri(sub_file.c_str());
 
-		Sord::Model sub_model(*world.rdf_world(), sub_file);
-		SerdEnv* env = serd_env_new(&sub_base);
-		sub_model.load_file(env, SERD_TURTLE, sub_file);
-		serd_env_free(env);
+		serd::Model sub_model(world.rdf_world(),
+		                      serd::ModelFlag::index_SPO |
+		                              serd::ModelFlag::index_OPS);
+		serd::Env env(sub_base);
+		load_file(world.rdf_world(), sub_model, env, sub_file);
 
-		Sord::URI sub_node(*world.rdf_world(), sub_file);
 		parse_graph(world, target, sub_model, sub_base,
-		            sub_node, Resource::Graph::INTERNAL,
+		            sub_uri, Resource::Graph::INTERNAL,
 		            path.parent(), Raul::Symbol(path.symbol()), data);
 
 		parse_graph(world, target, model, base_uri,
@@ -319,9 +310,9 @@ parse_block(ingen::World&                      world,
 static boost::optional<Raul::Path>
 parse_graph(ingen::World&                        world,
             ingen::Interface&                    target,
-            Sord::Model&                         model,
+            serd::Model&                         model,
             const URI&                           base_uri,
-            const Sord::Node&                    subject,
+            const serd::Node&                    subject,
             Resource::Graph                      ctx,
             const boost::optional<Raul::Path>&   parent,
             const boost::optional<Raul::Symbol>& symbol,
@@ -329,11 +320,9 @@ parse_graph(ingen::World&                        world,
 {
 	const URIs& uris = world.uris();
 
-	const Sord::URI ingen_block(*world.rdf_world(), uris.ingen_block);
-	const Sord::URI lv2_port(*world.rdf_world(),    LV2_CORE__port);
+	const serd::Node lv2_port = serd::make_uri(LV2_CORE__port);
 
-	const Sord::Node& graph = subject;
-	const Sord::Node  nil;
+	const serd::Node& graph = subject;
 
 	// Build graph path and symbol
 	Raul::Path graph_path;
@@ -352,8 +341,8 @@ parse_graph(ingen::World&                        world,
 	// For each port on this graph
 	using PortRecords = std::map<uint32_t, PortRecord>;
 	PortRecords ports;
-	for (Sord::Iter p = model.find(graph, lv2_port, nil); !p.end(); ++p) {
-		Sord::Node port = p.get_object();
+	for (const auto& s : model.range(graph, lv2_port, {})) {
+		const auto& port = s.object();
 
 		// Get all properties
 		uint32_t index = 0;
@@ -385,9 +374,9 @@ parse_graph(ingen::World&                        world,
 	}
 
 	// For each block in this graph
-	for (Sord::Iter n = model.find(subject, ingen_block, nil); !n.end(); ++n) {
-		Sord::Node node     = n.get_object();
-		URI        node_uri = node;
+	for (const auto& b : model.range(subject, uris.ingen_block, {})) {
+		const auto& node     = b.object();
+		URI         node_uri = node;
 		assert(!node_uri.path().empty() && node_uri.path() != "/");
 		const Raul::Path block_path = graph_path.child(
 			Raul::Symbol(FilePath(node_uri.path()).stem().string()));
@@ -397,13 +386,12 @@ parse_graph(ingen::World&                        world,
 		            boost::optional<Properties>());
 
 		// For each port on this block
-		for (Sord::Iter p = model.find(node, lv2_port, nil); !p.end(); ++p) {
-			Sord::Node port = p.get_object();
+		for (const auto& p : model.range(node, lv2_port, {})) {
+			const auto& port = p.object();
 
 			Resource::Graph subctx = Resource::Graph::DEFAULT;
-			if (!model.find(node,
-			                Sord::URI(*world.rdf_world(), uris.rdf_type),
-			                Sord::URI(*world.rdf_world(), uris.ingen_Graph)).end()) {
+			if (model.find(node, uris.rdf_type, uris.ingen_Graph) ==
+			    model.end()) {
 				subctx = Resource::Graph::EXTERNAL;
 			}
 
@@ -431,50 +419,38 @@ parse_graph(ingen::World&                        world,
 static bool
 parse_arc(ingen::World&      world,
           ingen::Interface&  target,
-          Sord::Model&       model,
+          serd::Model&       model,
           const URI&         base_uri,
-          const Sord::Node&  subject,
+          const serd::Node&  subject,
           const Raul::Path&  graph)
 {
 	const URIs& uris = world.uris();
 
-	const Sord::URI  ingen_tail(*world.rdf_world(), uris.ingen_tail);
-	const Sord::URI  ingen_head(*world.rdf_world(), uris.ingen_head);
-	const Sord::Node nil;
+	const auto& tail = model.get(subject, uris.ingen_tail, {});
+	const auto& head = model.get(subject, uris.ingen_head, {});
 
-	Sord::Iter t = model.find(subject, ingen_tail, nil);
-	Sord::Iter h = model.find(subject, ingen_head, nil);
-
-	if (t.end()) {
+	if (!tail) {
 		world.log().error("Arc has no tail\n");
 		return false;
-	} else if (h.end()) {
+	} else if (!head) {
 		world.log().error("Arc has no head\n");
 		return false;
 	}
 
 	const boost::optional<Raul::Path> tail_path = get_path(
-		base_uri, t.get_object());
+		base_uri, *tail);
 	if (!tail_path) {
 		world.log().error("Arc tail has invalid URI\n");
 		return false;
 	}
 
 	const boost::optional<Raul::Path> head_path = get_path(
-		base_uri, h.get_object());
+		base_uri, *head);
 	if (!head_path) {
 		world.log().error("Arc head has invalid URI\n");
 		return false;
 	}
 
-	if (!(++t).end()) {
-		world.log().error("Arc has multiple tails\n");
-		return false;
-	} else if (!(++h).end()) {
-		world.log().error("Arc has multiple heads\n");
-		return false;
-	}
-
 	target.connect(graph.child(*tail_path), graph.child(*head_path));
 
 	return true;
@@ -483,16 +459,13 @@ parse_arc(ingen::World&      world,
 static bool
 parse_arcs(ingen::World&      world,
            ingen::Interface&  target,
-           Sord::Model&       model,
+           serd::Model&       model,
            const URI&         base_uri,
-           const Sord::Node&  subject,
+           const serd::Node&  subject,
            const Raul::Path&  graph)
 {
-	const Sord::URI  ingen_arc(*world.rdf_world(), world.uris().ingen_arc);
-	const Sord::Node nil;
-
-	for (Sord::Iter i = model.find(subject, ingen_arc, nil); !i.end(); ++i) {
-		parse_arc(world, target, model, base_uri, i.get_object(), graph);
+	for (const auto& s : model.range(subject, world.uris().ingen_arc, {})) {
+		parse_arc(world, target, model, base_uri, s.object(), graph);
 	}
 
 	return true;
@@ -501,72 +474,56 @@ parse_arcs(ingen::World&      world,
 static boost::optional<Raul::Path>
 parse(ingen::World&                        world,
       ingen::Interface&                    target,
-      Sord::Model&                         model,
+      serd::Model&                         model,
       const URI&                           base_uri,
-      Sord::Node&                          subject,
       const boost::optional<Raul::Path>&   parent,
       const boost::optional<Raul::Symbol>& symbol,
       const boost::optional<Properties>&   data)
 {
-	const URIs& uris = world.uris();
+	using Subjects = std::map<serd::Node, std::set<serd::Node>>;
 
-	const Sord::URI  graph_class   (*world.rdf_world(), uris.ingen_Graph);
-	const Sord::URI  block_class   (*world.rdf_world(), uris.ingen_Block);
-	const Sord::URI  arc_class     (*world.rdf_world(), uris.ingen_Arc);
-	const Sord::URI  internal_class(*world.rdf_world(), uris.ingen_Internal);
-	const Sord::URI  in_port_class (*world.rdf_world(), LV2_CORE__InputPort);
-	const Sord::URI  out_port_class(*world.rdf_world(), LV2_CORE__OutputPort);
-	const Sord::URI  lv2_class     (*world.rdf_world(), LV2_CORE__Plugin);
-	const Sord::URI  rdf_type      (*world.rdf_world(), uris.rdf_type);
-	const Sord::Node nil;
-
-	// Parse explicit subject graph
-	if (subject.is_valid()) {
-		return parse_graph(world, target, model, base_uri,
-		                   subject, Resource::Graph::INTERNAL,
-		                   parent, symbol, data);
-	}
+	const URIs& uris = world.uris();
 
 	// Get all subjects and their types (?subject a ?type)
-	using Subjects = std::map< Sord::Node, std::set<Sord::Node> >;
 	Subjects subjects;
-	for (Sord::Iter i = model.find(subject, rdf_type, nil); !i.end(); ++i) {
-		const Sord::Node& subject   = i.get_subject();
-		const Sord::Node& rdf_class = i.get_object();
-
-		assert(rdf_class.is_uri());
-		auto s = subjects.find(subject);
-		if (s == subjects.end()) {
-			std::set<Sord::Node> types;
+	for (const auto& s : model.range({}, uris.rdf_type, {})) {
+		const auto& subject   = s.subject();
+		const auto& rdf_class = s.object();
+
+		assert(rdf_class.type() == serd::NodeType::URI);
+		auto t = subjects.find(subject);
+		if (t == subjects.end()) {
+			std::set<serd::Node> types;
 			types.insert(rdf_class);
 			subjects.emplace(subject, types);
 		} else {
-			s->second.insert(rdf_class);
+			t->second.insert(rdf_class);
 		}
 	}
 
 	// Parse and create each subject
 	for (const auto& i : subjects) {
-		const Sord::Node&           s     = i.first;
-		const std::set<Sord::Node>& types = i.second;
+		const auto& s     = i.first;
+		const auto& types = i.second;
+
 		boost::optional<Raul::Path> ret;
-		if (types.find(graph_class) != types.end()) {
+		if (types.find(uris.ingen_Graph) != types.end()) {
 			ret = parse_graph(world, target, model, base_uri,
 			                  s, Resource::Graph::INTERNAL,
 			                  parent, symbol, data);
-		} else if (types.find(block_class) != types.end()) {
+		} else if (types.find(uris.ingen_Block) != types.end()) {
 			const Raul::Path rel_path(*get_path(base_uri, s));
 			const Raul::Path path = parent ? parent->child(rel_path) : rel_path;
 			ret = parse_block(world, target, model, base_uri, s, path, data);
-		} else if (types.find(in_port_class) != types.end() ||
-		           types.find(out_port_class) != types.end()) {
+		} else if (types.find(uris.lv2_InputPort) != types.end() ||
+		           types.find(uris.lv2_OutputPort) != types.end()) {
 			const Raul::Path rel_path(*get_path(base_uri, s));
 			const Raul::Path path = parent ? parent->child(rel_path) : rel_path;
 			const Properties properties = get_properties(
 				world, model, s, Resource::Graph::DEFAULT, data);
 			target.put(path_to_uri(path), properties);
 			ret = path;
-		} else if (types.find(arc_class) != types.end()) {
+		} else if (types.find(uris.ingen_Arc) != types.end()) {
 			Raul::Path parent_path(parent ? parent.get() : Raul::Path("/"));
 			parse_arc(world, target, model, base_uri, s, parent_path);
 		} else {
@@ -599,8 +556,8 @@ Parser::parse_file(ingen::World&                        world,
 	URI manifest_uri(manifest_path);
 
 	// Find graphs in manifest
-	const std::set<ResourceRecord> resources = find_resources(
-		*world.rdf_world(), manifest_uri, URI(INGEN__Graph));
+	const std::set<ResourceRecord> resources =
+		find_resources(manifest_uri, URI(INGEN__Graph));
 
 	if (resources.empty()) {
 		world.log().error("No graphs found in %1%\n", path);
@@ -610,7 +567,7 @@ Parser::parse_file(ingen::World&                        world,
 	/* Choose the graph to load.  If this is a manifest, then there should only be
 	   one, but if this is a graph file, subgraphs will be returned as well.
 	   In this case, choose the one with the file URI. */
-	URI uri;
+	boost::optional<URI> uri;
 	for (const ResourceRecord& r : resources) {
 		if (r.uri == URI(manifest_path)) {
 			uri = r.uri;
@@ -619,7 +576,7 @@ Parser::parse_file(ingen::World&                        world,
 		}
 	}
 
-	if (uri.empty()) {
+	if (!uri) {
 		// Didn't find a graph with the same URI as the file, use the first
 		uri       = (*resources.begin()).uri;
 		file_path = (*resources.begin()).filename;
@@ -631,15 +588,13 @@ Parser::parse_file(ingen::World&                        world,
 	}
 
 	// Initialise parsing environment
-	const URI   file_uri  = URI(file_path);
-	const auto* uri_c_str = (const uint8_t*)uri.c_str();
-	SerdNode    base_node = serd_node_from_string(SERD_URI, uri_c_str);
-	SerdEnv*    env       = serd_env_new(&base_node);
+	const URI file_uri(file_path);
+	serd::Env env(*uri);
 
 	// Load graph into model
-	Sord::Model model(*world.rdf_world(), uri.string(), SORD_SPO|SORD_PSO, false);
-	model.load_file(env, SERD_TURTLE, file_uri);
-	serd_env_free(env);
+	serd::Model model(world.rdf_world(),
+	                  serd::ModelFlag::index_SPO | serd::ModelFlag::index_OPS);
+	load_file(world.rdf_world(), model, env, file_uri);
 
 	world.log().info("Loading %1% from %2%\n", uri, file_path);
 	if (parent) {
@@ -649,15 +604,21 @@ Parser::parse_file(ingen::World&                        world,
 		world.log().info("Symbol: %1%\n", symbol->c_str());
 	}
 
-	Sord::Node subject(*world.rdf_world(), Sord::Node::URI, uri.string());
-	boost::optional<Raul::Path> parsed_path
-		= parse(world, target, model, model.base_uri(),
-		        subject, parent, symbol, data);
+	boost::optional<Raul::Path> parsed_path =
+	        parse_graph(world,
+	                    target,
+	                    model,
+	                    *uri,
+	                    *uri,
+	                    Resource::Graph::INTERNAL,
+	                    parent,
+	                    symbol,
+	                    data);
 
 	if (parsed_path) {
 		target.set_property(path_to_uri(*parsed_path),
 		                    URI(INGEN__file),
-		                    world.forge().alloc_uri(uri.string()));
+		                    world.forge().alloc_uri(uri->string()));
 		return true;
 	} else {
 		world.log().warn("Document URI lost\n");
@@ -675,23 +636,27 @@ Parser::parse_string(ingen::World&                        world,
                      const boost::optional<Properties>&   data)
 {
 	// Load string into model
-	Sord::Model model(*world.rdf_world(), base_uri, SORD_SPO|SORD_PSO, false);
+	serd::Model model(world.rdf_world(),
+	                  serd::ModelFlag::index_SPO | serd::ModelFlag::index_OPS);
 
-	SerdEnv* env = serd_env_new(nullptr);
+	serd::Env env;
 	if (!base_uri.empty()) {
-		const SerdNode base = serd_node_from_string(
-			SERD_URI, (const uint8_t*)base_uri.c_str());
-		serd_env_set_base_uri(env, &base);
+		env.set_base_uri(base_uri);
 	}
-	model.load_string(env, SERD_TURTLE, str.c_str(), str.length(), base_uri);
 
-	URI actual_base((const char*)serd_env_get_base_uri(env, nullptr)->buf);
-	serd_env_free(env);
+	serd::Inserter inserter(model, env);
+	serd::Reader   reader(
+		world.rdf_world(), serd::Syntax::Turtle, {}, inserter.sink(), 4096);
+
+	reader.start_string(str);
+	reader.read_document();
+	reader.finish();
+
+	URI actual_base(env.base_uri());
 
 	world.log().info("Parsing string (base %1%)\n", base_uri);
 
-	Sord::Node subject;
-	parse(world, target, model, actual_base, subject, parent, symbol, data);
+	parse(world, target, model, actual_base, parent, symbol, data);
 	return actual_base;
 }
 
diff --git a/src/Serialiser.cpp b/src/Serialiser.cpp
index f0b5009e..3ac18fe2 100644
--- a/src/Serialiser.cpp
+++ b/src/Serialiser.cpp
@@ -38,15 +38,16 @@
 #include "raul/Path.hpp"
 #include "raul/Symbol.hpp"
 #include "serd/serd.h"
-#include "sord/sord.h"
-#include "sord/sordmm.hpp"
-#include "sratom/sratom.h"
+#include "serd/serd.hpp"
+#include "sratom/sratom.hpp"
 
 #include <cassert>
 #include <cstdint>
 #include <cstring>
+#include <fstream>
 #include <map>
 #include <set>
+#include <sstream>
 #include <stdexcept>
 #include <string>
 #include <utility>
@@ -55,15 +56,14 @@ namespace ingen {
 
 struct Serialiser::Impl {
 	explicit Impl(World& world)
-		: _root_path("/")
-		, _mode(Mode::TO_FILE)
-		, _world(world)
-		, _model(nullptr)
-		, _sratom(sratom_new(&_world.uri_map().urid_map_feature()->urid_map))
-	{}
-
-	~Impl() {
-		sratom_free(_sratom);
+	    : _root_path("/")
+	    , _mode(Mode::TO_STRING)
+	    , _base_uri("ingen:")
+	    , _world(world)
+	    , _streamer(_world.rdf_world(),
+	                _world.uri_map().urid_map_feature()->urid_map,
+	                _world.uri_map().urid_unmap_feature()->urid_unmap)
+	{
 	}
 
 	Impl(const Impl&) = delete;
@@ -77,22 +77,22 @@ struct Serialiser::Impl {
 	                   const FilePath&   filename);
 
 	std::set<const Resource*> serialise_graph(const SPtr<const Node>& graph,
-	                                          const Sord::Node&       graph_id);
+	                                          const serd::Node&       graph_id);
 
 	void serialise_block(const SPtr<const Node>& block,
-	                     const Sord::Node&       class_id,
-	                     const Sord::Node&       block_id);
+	                     const serd::Node&       class_id,
+	                     const serd::Node&       block_id);
 
 	void serialise_port(const Node*       port,
 	                    Resource::Graph   context,
-	                    const Sord::Node& port_id);
+	                    const serd::Node& port_id);
 
-	void serialise_properties(Sord::Node        id,
+	void serialise_properties(serd::Node        id,
 	                          const Properties& props);
 
 	void write_bundle(const SPtr<const Node>& graph, const URI& uri);
 
-	Sord::Node path_rdf_node(const Raul::Path& path);
+	serd::Node path_rdf_node(const Raul::Path& path);
 
 	void write_manifest(const FilePath&         bundle_path,
 	                    const SPtr<const Node>& graph);
@@ -100,18 +100,18 @@ struct Serialiser::Impl {
 	void write_plugins(const FilePath&                  bundle_path,
 	                   const std::set<const Resource*>& plugins);
 
-	void serialise_arc(const Sord::Node&      parent,
-	                   const SPtr<const Arc>& arc);
+	void serialise_arc(const serd::Optional<serd::Node>& parent,
+	                   const SPtr<const Arc>&            arc);
 
 	std::string finish();
 
-	Raul::Path   _root_path;
-	Mode         _mode;
-	URI          _base_uri;
-	FilePath     _basename;
-	World&       _world;
-	Sord::Model* _model;
-	Sratom*      _sratom;
+	Raul::Path        _root_path;
+	Mode              _mode;
+	URI               _base_uri;
+	FilePath          _basename;
+	World&            _world;
+	UPtr<serd::Model> _model;
+	sratom::Streamer  _streamer;
 };
 
 Serialiser::Serialiser(World& world)
@@ -129,24 +129,17 @@ Serialiser::Impl::write_manifest(const FilePath& bundle_path,
 
 	start_to_file(Raul::Path("/"), manifest_path);
 
-	Sord::World& world = _model->world();
-	const URIs&  uris  = _world.uris();
+	const URIs& uris  = _world.uris();
 
 	const std::string filename("main.ttl");
-	const Sord::URI   subject(world, filename, _base_uri);
-
-	_model->add_statement(subject,
-	                      Sord::URI(world, uris.rdf_type),
-	                      Sord::URI(world, uris.ingen_Graph));
-	_model->add_statement(subject,
-	                      Sord::URI(world, uris.rdf_type),
-	                      Sord::URI(world, uris.lv2_Plugin));
-	_model->add_statement(subject,
-	                      Sord::URI(world, uris.rdfs_seeAlso),
-	                      Sord::URI(world, filename, _base_uri));
-	_model->add_statement(subject,
-	                      Sord::URI(world, uris.lv2_prototype),
-	                      Sord::URI(world, uris.ingen_GraphPrototype));
+	const serd::Node  subject = serd::make_resolved_uri(filename, _base_uri);
+
+	_model->insert(subject, uris.rdf_type, uris.ingen_Graph);
+	_model->insert(subject, uris.rdf_type, uris.lv2_Plugin);
+	_model->insert(subject, uris.lv2_prototype, uris.ingen_GraphPrototype);
+	_model->insert(subject,
+	               uris.rdfs_seeAlso,
+	               serd::make_resolved_uri(filename, _base_uri));
 
 	finish();
 }
@@ -159,24 +152,21 @@ Serialiser::Impl::write_plugins(const FilePath&                  bundle_path,
 
 	start_to_file(Raul::Path("/"), plugins_path);
 
-	Sord::World& world = _model->world();
-	const URIs&  uris  = _world.uris();
+	const URIs& uris  = _world.uris();
 
 	for (const auto& p : plugins) {
 		const Atom& minor = p->get_property(uris.lv2_minorVersion);
 		const Atom& micro = p->get_property(uris.lv2_microVersion);
 
-		_model->add_statement(Sord::URI(world, p->uri()),
-		                      Sord::URI(world, uris.rdf_type),
-		                      Sord::URI(world, uris.lv2_Plugin));
+		_model->insert(p->uri(), uris.rdf_type, uris.lv2_Plugin);
 
 		if (minor.is_valid() && micro.is_valid()) {
-			_model->add_statement(Sord::URI(world, p->uri()),
-			                      Sord::URI(world, uris.lv2_minorVersion),
-			                      Sord::Literal::integer(world, minor.get<int32_t>()));
-			_model->add_statement(Sord::URI(world, p->uri()),
-			                      Sord::URI(world, uris.lv2_microVersion),
-			                      Sord::Literal::integer(world, micro.get<int32_t>()));
+			_model->insert(p->uri(),
+			               uris.lv2_minorVersion,
+			               serd::make_integer(minor.get<int32_t>()));
+			_model->insert(p->uri(),
+			               uris.lv2_microVersion,
+			               serd::make_integer(micro.get<int32_t>()));
 		}
 	}
 
@@ -207,7 +197,7 @@ Serialiser::Impl::write_bundle(const SPtr<const Node>& graph, const URI& uri)
 
 	std::set<const Resource*> plugins = serialise_graph(
 		graph,
-		Sord::URI(_model->world(), main_file, _base_uri));
+		serd::make_resolved_uri(main_file.c_str(), _base_uri));
 
 	finish();
 	write_manifest(path, graph);
@@ -230,9 +220,11 @@ Serialiser::Impl::start_to_file(const Raul::Path& root,
 		_basename = filename.parent_path().stem();
 	}
 
-	_model     = new Sord::Model(*_world.rdf_world(), _base_uri);
-	_mode      = Mode::TO_FILE;
 	_root_path = root;
+	_mode      = Mode::TO_FILE;
+	_model     = make_unique<serd::Model>(_world.rdf_world(),
+	                                      serd::ModelFlag::index_SPO |
+	                                      serd::ModelFlag::index_OPS);
 }
 
 void
@@ -240,8 +232,10 @@ Serialiser::start_to_string(const Raul::Path& root, const URI& base_uri)
 {
 	me->_root_path = root;
 	me->_base_uri  = base_uri;
-	me->_model     = new Sord::Model(*me->_world.rdf_world(), base_uri);
 	me->_mode      = Impl::Mode::TO_STRING;
+	me->_model     = make_unique<serd::Model>(me->_world.rdf_world(),
+	                                          serd::ModelFlag::index_SPO |
+	                                          serd::ModelFlag::index_OPS);
 }
 
 void
@@ -259,32 +253,54 @@ Serialiser::finish()
 std::string
 Serialiser::Impl::finish()
 {
-	std::string ret;
+	std::string ret{};
+	serd::Env   env{_world.env()};
+
+	env.set_base_uri(_base_uri);
+
 	if (_mode == Mode::TO_FILE) {
-		SerdStatus st = _model->write_to_file(_base_uri, SERD_TURTLE);
-		if (st) {
-			_world.log().error("Error writing file %1% (%2%)\n",
-			                   _base_uri, serd_strerror(st));
+		const std::string path = serd::file_uri_parse(_base_uri.string());
+		std::ofstream     file(path);
+		serd::Writer      writer(
+			_world.rdf_world(), serd::Syntax::Turtle, {}, env, file);
+
+		env.write_prefixes(writer.sink());
+		const serd::Status st = _model->all().serialise(writer.sink());
+
+		if (st != serd::Status::success) {
+			_world.log().error(fmt("Error writing file %1% (%2%)\n",
+			                       _base_uri, serd::strerror(st)));
 		}
 	} else {
-		ret = _model->write_to_string(_base_uri, SERD_TURTLE);
+		std::stringstream ss;
+		serd::Writer      writer(
+                _world.rdf_world(), serd::Syntax::Turtle, {}, env, ss);
+
+		env.write_prefixes(writer.sink());
+		const serd::Status st = _model->all().serialise(writer.sink());
+
+		writer.finish();
+		ret = ss.str();
+
+		if (st != serd::Status::success) {
+			_world.log().error(fmt("Error writing string (%2%)\n",
+			                       serd::strerror(st)));
+		}
 	}
 
-	delete _model;
-	_model    = nullptr;
-	_base_uri = URI();
+	_model.reset();
+	_base_uri = URI("ingen:");
 
 	return ret;
 }
 
-Sord::Node
+serd::Node
 Serialiser::Impl::path_rdf_node(const Raul::Path& path)
 {
 	assert(_model);
 	assert(path == _root_path || path.is_child_of(_root_path));
-	return Sord::URI(_model->world(),
-	                 path.substr(_root_path.base().length()),
-	                 _base_uri);
+	return serd::make_resolved_uri(path.substr(_root_path.base().length()),
+	                               _base_uri);
 }
 
 void
@@ -297,8 +313,9 @@ Serialiser::serialise(const SPtr<const Node>& object, Resource::Graph context)
 	if (object->graph_type() == Node::GraphType::GRAPH) {
 		me->serialise_graph(object, me->path_rdf_node(object->path()));
 	} else if (object->graph_type() == Node::GraphType::BLOCK) {
-		const Sord::URI plugin_id(me->_model->world(), object->plugin()->uri());
-		me->serialise_block(object, plugin_id, me->path_rdf_node(object->path()));
+		me->serialise_block(object,
+		                    object->plugin()->uri(),
+		                    me->path_rdf_node(object->path()));
 	} else if (object->graph_type() == Node::GraphType::PORT) {
 		me->serialise_port(
 			object.get(), context, me->path_rdf_node(object->path()));
@@ -310,32 +327,27 @@ Serialiser::serialise(const SPtr<const Node>& object, Resource::Graph context)
 
 std::set<const Resource*>
 Serialiser::Impl::serialise_graph(const SPtr<const Node>& graph,
-                                  const Sord::Node&       graph_id)
+                                  const serd::Node&       graph_id)
 {
-	Sord::World& world = _model->world();
-	const URIs&  uris  = _world.uris();
+	const URIs& uris = _world.uris();
 
-	_model->add_statement(graph_id,
-	                      Sord::URI(world, uris.rdf_type),
-	                      Sord::URI(world, uris.ingen_Graph));
+	_model->insert(graph_id, uris.rdf_type, uris.ingen_Graph);
 
-	_model->add_statement(graph_id,
-	                      Sord::URI(world, uris.rdf_type),
-	                      Sord::URI(world, uris.lv2_Plugin));
+	_model->insert(graph_id, uris.rdf_type, uris.lv2_Plugin);
 
-	_model->add_statement(graph_id,
-	                      Sord::URI(world, uris.lv2_extensionData),
-	                      Sord::URI(world, LV2_STATE__interface));
+	_model->insert(graph_id,
+	               uris.lv2_extensionData,
+	               serd::make_uri(LV2_STATE__interface));
 
-	_model->add_statement(graph_id,
-	                      Sord::URI(world, LV2_UI__ui),
-	                      Sord::URI(world, "http://drobilla.net/ns/ingen#GraphUIGtk2"));
+	_model->insert(graph_id,
+	               serd::make_uri(LV2_UI__ui),
+	               serd::make_uri("http://drobilla.net/ns/ingen#GraphUIGtk2"));
 
 	// If the graph has no doap:name (required by LV2), use the basename
 	if (graph->properties().find(uris.doap_name) == graph->properties().end()) {
-		_model->add_statement(graph_id,
-		                      Sord::URI(world, uris.doap_name),
-		                      Sord::Literal(world, _basename));
+		_model->insert(graph_id,
+		               uris.doap_name,
+		               serd::make_string((std::string)_basename));
 	}
 
 	const Properties props = graph->properties(Resource::Graph::INTERNAL);
@@ -352,45 +364,33 @@ Serialiser::Impl::serialise_graph(const SPtr<const Node>& graph,
 		if (n->second->graph_type() == Node::GraphType::GRAPH) {
 			SPtr<Node> subgraph = n->second;
 
-			SerdURI base_uri;
-			serd_uri_parse((const uint8_t*)_base_uri.c_str(), &base_uri);
-
 			const std::string sub_bundle_path = subgraph->path().substr(1) + ".ingen";
 
-			SerdURI  subgraph_uri;
-			SerdNode subgraph_node = serd_node_new_uri_from_string(
-				(const uint8_t*)sub_bundle_path.c_str(),
-				&base_uri,
-				&subgraph_uri);
-
-			const Sord::URI subgraph_id(world, (const char*)subgraph_node.buf);
+			serd::Node subgraph_node = serd::make_resolved_uri(
+				sub_bundle_path,
+				_base_uri);
 
 			// Save our state
-			URI          my_base_uri = _base_uri;
-			Sord::Model* my_model    = _model;
+			URI  my_base_uri = _base_uri;
+			auto my_model    = std::move(_model);
 
 			// Write child bundle within this bundle
-			write_bundle(subgraph, subgraph_id);
+			write_bundle(subgraph, subgraph_node);
 
 			// Restore our state
 			_base_uri = my_base_uri;
-			_model    = my_model;
+			_model    = std::move(my_model);
 
 			// Serialise reference to graph block
-			const Sord::Node block_id(path_rdf_node(subgraph->path()));
-			_model->add_statement(graph_id,
-			                      Sord::URI(world, uris.ingen_block),
-			                      block_id);
-			serialise_block(subgraph, subgraph_id, block_id);
+			const serd::Node block_id(path_rdf_node(subgraph->path()));
+			_model->insert(graph_id, uris.ingen_block, block_id);
+			serialise_block(subgraph, subgraph_node, block_id);
 		} else if (n->second->graph_type() == Node::GraphType::BLOCK) {
 			SPtr<const Node> block = n->second;
 
-			const Sord::URI  class_id(world, block->plugin()->uri());
-			const Sord::Node block_id(path_rdf_node(n->second->path()));
-			_model->add_statement(graph_id,
-			                      Sord::URI(world, uris.ingen_block),
-			                      block_id);
-			serialise_block(block, class_id, block_id);
+			const serd::Node block_id(path_rdf_node(n->second->path()));
+			_model->insert(graph_id, uris.ingen_block, block_id);
+			serialise_block(block, block->plugin()->uri(), block_id);
 
 			plugins.insert(block->plugin());
 		}
@@ -398,7 +398,7 @@ Serialiser::Impl::serialise_graph(const SPtr<const Node>& graph,
 
 	for (uint32_t i = 0; i < graph->num_ports(); ++i) {
 		Node* p = graph->port(i);
-		const Sord::Node port_id = path_rdf_node(p->path());
+		const serd::Node port_id = path_rdf_node(p->path());
 
 		// Ensure lv2:name always exists so Graph is a valid LV2 plugin
 		if (p->properties().find(uris.lv2_name) == p->properties().end()) {
@@ -406,9 +406,7 @@ Serialiser::Impl::serialise_graph(const SPtr<const Node>& graph,
 			                _world.forge().alloc(p->symbol().c_str()));
 		}
 
-		_model->add_statement(graph_id,
-		                      Sord::URI(world, LV2_CORE__port),
-		                      port_id);
+		_model->insert(graph_id, serd::make_uri(LV2_CORE__port), port_id);
 		serialise_port(p, Resource::Graph::DEFAULT, port_id);
 		serialise_port(p, Resource::Graph::INTERNAL, port_id);
 	}
@@ -422,17 +420,13 @@ Serialiser::Impl::serialise_graph(const SPtr<const Node>& graph,
 
 void
 Serialiser::Impl::serialise_block(const SPtr<const Node>& block,
-                                  const Sord::Node&       class_id,
-                                  const Sord::Node&       block_id)
+                                  const serd::Node&       class_id,
+                                  const serd::Node&       block_id)
 {
 	const URIs& uris = _world.uris();
 
-	_model->add_statement(block_id,
-	                      Sord::URI(_model->world(), uris.rdf_type),
-	                      Sord::URI(_model->world(), uris.ingen_Block));
-	_model->add_statement(block_id,
-	                      Sord::URI(_model->world(), uris.lv2_prototype),
-	                      class_id);
+	_model->insert(block_id, uris.rdf_type, uris.ingen_Block);
+	_model->insert(block_id, uris.lv2_prototype, class_id);
 
 	// Serialise properties, but remove possibly stale state:state (set again below)
 	Properties props = block->properties();
@@ -445,36 +439,33 @@ Serialiser::Impl::serialise_block(const SPtr<const Node>& block,
 		const FilePath state_dir  = graph_dir / block->symbol();
 		const FilePath state_file = state_dir / "state.ttl";
 		if (block->save_state(state_dir)) {
-			_model->add_statement(block_id,
-			                      Sord::URI(_model->world(), uris.state_state),
-			                      Sord::URI(_model->world(), URI(state_file)));
+			_model->insert(block_id,
+			               uris.state_state,
+			               serd::make_uri((std::string)state_file));
 		}
 	}
 
 	for (uint32_t i = 0; i < block->num_ports(); ++i) {
 		Node* const      p       = block->port(i);
-		const Sord::Node port_id = path_rdf_node(p->path());
+		const serd::Node port_id = path_rdf_node(p->path());
 		serialise_port(p, Resource::Graph::DEFAULT, port_id);
-		_model->add_statement(block_id,
-		                      Sord::URI(_model->world(), uris.lv2_port),
-		                      port_id);
+		_model->insert(block_id, uris.lv2_port, port_id);
 	}
 }
 
 void
 Serialiser::Impl::serialise_port(const Node*       port,
                                  Resource::Graph   context,
-                                 const Sord::Node& port_id)
+                                 const serd::Node& port_id)
 {
-	URIs&            uris  = _world.uris();
-	Sord::World&     world = _model->world();
+	URIs&      uris  = _world.uris();
 	Properties props = port->properties(context);
 
 	if (context == Resource::Graph::INTERNAL) {
 		// Always write lv2:symbol for Graph ports (required for lv2:Plugin)
-		_model->add_statement(port_id,
-		                      Sord::URI(world, uris.lv2_symbol),
-		                      Sord::Literal(world, port->path().symbol()));
+		_model->insert(port_id,
+		               uris.lv2_symbol,
+		               serd::make_string(port->path().symbol()));
 	} else if (context == Resource::Graph::EXTERNAL) {
 		// Never write lv2:index for plugin instances (not persistent/stable)
 		props.erase(uris.lv2_index);
@@ -500,47 +491,38 @@ Serialiser::Impl::serialise_port(const Node*       port,
 }
 
 void
-Serialiser::serialise_arc(const Sord::Node&      parent,
-                          const SPtr<const Arc>& arc)
+Serialiser::serialise_arc(const serd::Optional<serd::Node>& parent,
+                          const SPtr<const Arc>&            arc)
 {
 	return me->serialise_arc(parent, arc);
 }
 
 void
-Serialiser::Impl::serialise_arc(const Sord::Node&      parent,
-                                const SPtr<const Arc>& arc)
+Serialiser::Impl::serialise_arc(const serd::Optional<serd::Node>& parent,
+                                const SPtr<const Arc>&            arc)
 {
 	if (!_model) {
 		throw std::logic_error(
 			"serialise_arc called without serialisation in progress");
 	}
 
-	Sord::World& world = _model->world();
-	const URIs&  uris  = _world.uris();
-
-	const Sord::Node src    = path_rdf_node(arc->tail_path());
-	const Sord::Node dst    = path_rdf_node(arc->head_path());
-	const Sord::Node arc_id = Sord::Node::blank_id(*_world.rdf_world());
-	_model->add_statement(arc_id,
-	                      Sord::URI(world, uris.ingen_tail),
-	                      src);
-	_model->add_statement(arc_id,
-	                      Sord::URI(world, uris.ingen_head),
-	                      dst);
-
-	if (parent.is_valid()) {
-		_model->add_statement(parent,
-		                      Sord::URI(world, uris.ingen_arc),
-		                      arc_id);
+	const URIs& uris = _world.uris();
+
+	const serd::Node src    = path_rdf_node(arc->tail_path());
+	const serd::Node dst    = path_rdf_node(arc->head_path());
+	const serd::Node arc_id = _world.rdf_world().get_blank();
+	_model->insert(arc_id, uris.ingen_tail, src);
+	_model->insert(arc_id, uris.ingen_head, dst);
+
+	if (parent) {
+		_model->insert(*parent, uris.ingen_arc, arc_id);
 	} else {
-		_model->add_statement(arc_id,
-		                      Sord::URI(world, uris.rdf_type),
-		                      Sord::URI(world, uris.ingen_Arc));
+		_model->insert(arc_id, uris.rdf_type, uris.ingen_Arc);
 	}
 }
 
 static bool
-skip_property(ingen::URIs& uris, const Sord::Node& predicate)
+skip_property(ingen::URIs& uris, const serd::Node& predicate)
 {
 	return (predicate == INGEN__file ||
 	        predicate == uris.ingen_arc ||
@@ -549,45 +531,37 @@ skip_property(ingen::URIs& uris, const Sord::Node& predicate)
 }
 
 void
-Serialiser::Impl::serialise_properties(Sord::Node        id,
+Serialiser::Impl::serialise_properties(serd::Node        id,
                                        const Properties& props)
 {
-	LV2_URID_Unmap* unmap    = &_world.uri_map().urid_unmap_feature()->urid_unmap;
-	SerdNode        base     = serd_node_from_string(SERD_URI,
-	                                                 (const uint8_t*)_base_uri.c_str());
-	SerdEnv*        env      = serd_env_new(&base);
-	SordInserter*   inserter = sord_inserter_new(_model->c_obj(), env);
-
-	sratom_set_sink(_sratom, _base_uri.c_str(),
-	                (SerdStatementSink)sord_inserter_write_statement, nullptr,
-	                inserter);
-
-	sratom_set_pretty_numbers(_sratom, true);
+	serd::Env      env{_base_uri};
+	serd::Inserter inserter{*_model, env, {}};
 
 	for (const auto& p : props) {
-		const Sord::URI key(_model->world(), p.first);
+		const serd::Node key = serd::make_uri(p.first.c_str());
 		if (!skip_property(_world.uris(), key)) {
 			if (p.second.type() == _world.uris().atom_URI &&
-			    !strncmp((const char*)p.second.get_body(), "ingen:/main/", 13)) {
+			    !strncmp(
+			            (const char*)p.second.get_body(), "ingen:/main/", 13)) {
 				/* Value is a graph URI relative to the running engine.
 				   Chop the prefix and save the path relative to the graph file.
 				   This allows saving references to bundle resources. */
-				sratom_write(_sratom, unmap, 0,
-				             sord_node_to_serd_node(id.c_obj()),
-				             sord_node_to_serd_node(key.c_obj()),
-				             p.second.type(), p.second.size(),
-				             (const char*)p.second.get_body() + 13);
+				_streamer.write(inserter.sink(),
+				                id,
+				                key,
+				                p.second.type(),
+				                p.second.size(),
+				                (const char*)p.second.get_body() + 13,
+				                sratom::Flag::pretty_numbers);
 			} else {
-				sratom_write(_sratom, unmap, 0,
-				             sord_node_to_serd_node(id.c_obj()),
-				             sord_node_to_serd_node(key.c_obj()),
-				             p.second.type(), p.second.size(), p.second.get_body());
+				_streamer.write(inserter.sink(),
+				                id,
+				                key,
+				                *p.second.atom(),
+				                sratom::Flag::pretty_numbers);
 			}
 		}
 	}
-
-	sord_inserter_free(inserter);
-	serd_env_free(env);
 }
 
 } // namespace ingen
diff --git a/src/SocketReader.cpp b/src/SocketReader.cpp
index 443c418f..273c9e49 100644
--- a/src/SocketReader.cpp
+++ b/src/SocketReader.cpp
@@ -21,10 +21,11 @@
 #include "ingen/Log.hpp"
 #include "ingen/URIMap.hpp"
 #include "ingen/World.hpp"
+#include "ingen/types.hpp"
 #include "lv2/atom/forge.h"
 #include "lv2/urid/urid.h"
 #include "raul/Socket.hpp"
-#include "sord/sordmm.hpp"
+#include "serd/serd.hpp"
 
 #include <cerrno>
 #include <cstdint>
@@ -42,9 +43,6 @@ SocketReader::SocketReader(ingen::World&      world,
                            SPtr<Raul::Socket> sock)
 	: _world(world)
 	, _iface(iface)
-	, _env()
-	, _inserter(nullptr)
-	, _msg_node(nullptr)
 	, _socket(std::move(sock))
 	, _exit_flag(false)
 	, _thread(&SocketReader::run, this)
@@ -57,46 +55,10 @@ SocketReader::~SocketReader()
 	_thread.join();
 }
 
-SerdStatus
-SocketReader::set_base_uri(SocketReader*   iface,
-                           const SerdNode* uri_node)
-{
-	return sord_inserter_set_base_uri(iface->_inserter, uri_node);
-}
-
-SerdStatus
-SocketReader::set_prefix(SocketReader*   iface,
-                         const SerdNode* name,
-                         const SerdNode* uri_node)
-{
-	return sord_inserter_set_prefix(iface->_inserter, name, uri_node);
-}
-
-SerdStatus
-SocketReader::write_statement(SocketReader*      iface,
-                              SerdStatementFlags flags,
-                              const SerdNode*    graph,
-                              const SerdNode*    subject,
-                              const SerdNode*    predicate,
-                              const SerdNode*    object,
-                              const SerdNode*    object_datatype,
-                              const SerdNode*    object_lang)
-{
-	if (!iface->_msg_node) {
-		iface->_msg_node = sord_node_from_serd_node(
-			iface->_world.rdf_world()->c_obj(), iface->_env, subject, nullptr, nullptr);
-	}
-
-	return sord_inserter_write_statement(
-		iface->_inserter, flags, graph,
-		subject, predicate, object,
-		object_datatype, object_lang);
-}
-
 void
 SocketReader::run()
 {
-	Sord::World*  world = _world.rdf_world();
+	serd::World&  world = _world.rdf_world();
 	LV2_URID_Map& map   = _world.uri_map().urid_map_feature()->urid_map;
 
 	// Open socket as a FILE for reading directly with serd
@@ -111,33 +73,51 @@ SocketReader::run()
 	}
 
 	// Set up a forge to build LV2 atoms from model
-	SordNode*  base_uri = nullptr;
-	SordModel* model    = nullptr;
-	AtomForge  forge(map);
+	AtomForge                   forge(world, map);
+	serd::Optional<serd::Node>  base_uri;
+	serd::Optional<serd::Model> model;
+	serd::Env                   env;
+	UPtr<serd::Inserter>        inserter;
+	serd::Optional<serd::Node>  msg_node;
 	{
 		// Lock RDF world
 		std::lock_guard<std::mutex> lock(_world.rdf_mutex());
 
 		// Use <ingen:/> as base URI, so relative URIs are like bundle paths
-		base_uri = sord_new_uri(world->c_obj(), (const uint8_t*)"ingen:/");
+		base_uri = serd::make_uri("ingen:/");
 
 		// Make a model and reader to parse the next Turtle message
-		_env = world->prefixes().c_obj();
-		model = sord_new(world->c_obj(), SORD_SPO, false);
+		env   = _world.env();
+		model = serd::Model(world, serd::ModelFlag::index_SPO);
 
 		// Create an inserter for writing incoming triples to model
-		_inserter = sord_inserter_new(model, _env);
+		inserter = UPtr<serd::Inserter>{new serd::Inserter(*model, env)};
 	}
 
-	SerdReader* reader = serd_reader_new(
-		SERD_TURTLE, this, nullptr,
-		(SerdBaseSink)set_base_uri,
-		(SerdPrefixSink)set_prefix,
-		(SerdStatementSink)write_statement,
-		nullptr);
+	serd::Sink sink;
+
+	sink.set_base_func([&](const serd::Node& uri) {
+		return inserter->sink().base(uri);
+	});
+
+	sink.set_prefix_func([&](const serd::Node& name, const serd::Node& uri) {
+		return inserter->sink().prefix(name, uri);
+	});
+
+	sink.set_statement_func([&](const serd::StatementFlags flags,
+	                            const serd::Statement&     statement) {
+		if (!msg_node) {
+			msg_node = statement.subject();
+		}
+
+		return inserter->sink().statement(flags, statement);
+	});
+
+	serd::Reader reader(world, serd::Syntax::Turtle, {}, sink, 4096);
 
-	serd_env_set_base_uri(_env, sord_node_to_serd_node(base_uri));
-	serd_reader_start_stream(reader, f.get(), (const uint8_t*)"(socket)", false);
+	serd::Node name = serd::make_string("(socket)");
+	env.set_base_uri(*base_uri);
+	reader.start_stream(f.get(), name, 1);
 
 	// Make an AtomReader to call Ingen Interface methods based on Atom
 	AtomReader ar(_world.uri_map(), _world.uris(), _world.log(), _iface);
@@ -165,24 +145,22 @@ SocketReader::run()
 		std::lock_guard<std::mutex> lock(_world.rdf_mutex());
 
 		// Read until the next '.'
-		SerdStatus st = serd_reader_read_chunk(reader);
-		if (st == SERD_FAILURE || !_msg_node) {
-			continue;  // Read nothing, e.g. just whitespace
-		} else if (st) {
-			_world.log().error("Read error: %1%\n", serd_strerror(st));
+		auto st = reader.read_chunk();
+		if (st == serd::Status::failure || !msg_node) {
+			continue;  // Read no node (e.g. a directive)
+		} else if (st != serd::Status::success) {
+			_world.log().error("Read error: %1%\n", serd::strerror(st));
 			continue;
 		}
 
-		// Build an LV2_Atom at chunk.buf from the message
-		forge.read(*world, model, _msg_node);
+		// Build an LV2_Atom from the message
+		auto atom = forge.read(*model, *msg_node);
 
-		// Call _iface methods based on atom content
-		ar.write(forge.atom());
+		// Call _iface methods with forged atom
+		ar.write(atom);
 
 		// Reset everything for the next iteration
-		forge.clear();
-		sord_node_free(world->c_obj(), _msg_node);
-		_msg_node = nullptr;
+		msg_node.reset();
 	}
 
 	// Lock RDF world
@@ -190,10 +168,7 @@ SocketReader::run()
 
 	// Destroy everything
 	f.reset();
-	sord_inserter_free(_inserter);
-	serd_reader_end_stream(reader);
-	serd_reader_free(reader);
-	sord_free(model);
+	reader.finish();
 	_socket.reset();
 }
 
diff --git a/src/SocketWriter.cpp b/src/SocketWriter.cpp
index 910f67f3..41254ef3 100644
--- a/src/SocketWriter.cpp
+++ b/src/SocketWriter.cpp
@@ -31,12 +31,13 @@
 
 namespace ingen {
 
-SocketWriter::SocketWriter(URIMap&            map,
+SocketWriter::SocketWriter(serd::World&       world,
+                           URIMap&            map,
                            URIs&              uris,
                            const URI&         uri,
                            SPtr<Raul::Socket> sock)
-	: TurtleWriter(map, uris, uri)
-	, _socket(std::move(sock))
+	: TurtleWriter(world, map, uris, uri)
+    , _socket(std::move(sock))
 {}
 
 void
diff --git a/src/StreamWriter.cpp b/src/StreamWriter.cpp
index d8a93a0b..32831601 100644
--- a/src/StreamWriter.cpp
+++ b/src/StreamWriter.cpp
@@ -21,14 +21,15 @@
 
 namespace ingen {
 
-StreamWriter::StreamWriter(URIMap&             map,
+StreamWriter::StreamWriter(serd::World&        world,
+                           URIMap&             map,
                            URIs&               uris,
                            const URI&          uri,
                            FILE*               stream,
                            ColorContext::Color color)
-	: TurtleWriter(map, uris, uri)
-	, _stream(stream)
-	, _color(color)
+	: TurtleWriter(world, map, uris, uri)
+    , _stream(stream)
+    , _color(color)
 {}
 
 size_t
diff --git a/src/TurtleWriter.cpp b/src/TurtleWriter.cpp
index 1deb2e13..e8a38561 100644
--- a/src/TurtleWriter.cpp
+++ b/src/TurtleWriter.cpp
@@ -19,72 +19,43 @@
 #include "ingen/URIMap.hpp"
 #include "lv2/atom/atom.h"
 
-#define USTR(s) ((const uint8_t*)(s))
-
 namespace ingen {
 
-static size_t
-c_text_sink(const void* buf, size_t len, void* stream)
-{
-	auto* writer = static_cast<TurtleWriter*>(stream);
-	return writer->text_sink(buf, len);
-}
-
-static SerdStatus
-write_prefix(void* handle, const SerdNode* name, const SerdNode* uri)
-{
-	serd_writer_set_prefix((SerdWriter*)handle, name, uri);
-	return SERD_SUCCESS;
-}
-
-TurtleWriter::TurtleWriter(URIMap& map, URIs& uris, URI uri)
-    : AtomWriter(map, uris, *this)
+TurtleWriter::TurtleWriter(serd::World& world,
+                           URIMap&      map,
+                           URIs&        uris,
+                           const URI&   uri)
+    : AtomWriter(world, map, uris, *this)
     , _map(map)
-    , _sratom(sratom_new(&map.urid_map_feature()->urid_map))
-    , _base(SERD_NODE_NULL)
-    , _base_uri(SERD_URI_NULL)
-    , _uri(std::move(uri))
+    , _streamer(world,
+                map.urid_map_feature()->urid_map,
+                map.urid_unmap_feature()->urid_unmap)
+    , _base(serd::make_uri("ingen:/")) // Make relative URIs like bundle paths
+    , _env(_base)
+    , _writer(world,
+              serd::Syntax::Turtle,
+              {},
+              _env,
+              [&](const char* str, size_t len) { return text_sink(str, len); })
+    , _uri(uri)
     , _wrote_prefixes(false)
 {
-	// Use <ingen:/> as base URI, so relative URIs are like bundle paths
-	_base = serd_node_from_string(SERD_URI, (const uint8_t*)"ingen:/");
-	serd_uri_parse(_base.buf, &_base_uri);
-
-	// Set up serialisation environment
-	_env = serd_env_new(&_base);
-	serd_env_set_prefix_from_strings(_env, USTR("atom"),  USTR("http://lv2plug.in/ns/ext/atom#"));
-	serd_env_set_prefix_from_strings(_env, USTR("doap"),  USTR("http://usefulinc.com/ns/doap#"));
-	serd_env_set_prefix_from_strings(_env, USTR("ingen"), USTR(INGEN_NS));
-	serd_env_set_prefix_from_strings(_env, USTR("lv2"),   USTR("http://lv2plug.in/ns/lv2core#"));
-	serd_env_set_prefix_from_strings(_env, USTR("midi"),  USTR("http://lv2plug.in/ns/ext/midi#"));
-	serd_env_set_prefix_from_strings(_env, USTR("owl"),   USTR("http://www.w3.org/2002/07/owl#"));
-	serd_env_set_prefix_from_strings(_env, USTR("patch"), USTR("http://lv2plug.in/ns/ext/patch#"));
-	serd_env_set_prefix_from_strings(_env, USTR("rdf"),   USTR("http://www.w3.org/1999/02/22-rdf-syntax-ns#"));
-	serd_env_set_prefix_from_strings(_env, USTR("rdfs"),  USTR("http://www.w3.org/2000/01/rdf-schema#"));
-	serd_env_set_prefix_from_strings(_env, USTR("xsd"),   USTR("http://www.w3.org/2001/XMLSchema#"));
-
-	// Make a Turtle writer that writes to text_sink
-	_writer = serd_writer_new(
-		SERD_TURTLE,
-		(SerdStyle)(SERD_STYLE_RESOLVED|SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED),
-		_env,
-		&_base_uri,
-		c_text_sink,
-		this);
-
-	// Configure sratom to write directly to the writer (and thus text_sink)
-	sratom_set_sink(_sratom,
-	                (const char*)_base.buf,
-	                (SerdStatementSink)serd_writer_write_statement,
-	                (SerdEndSink)serd_writer_end_anon,
-	                _writer);
+	// Set namespace prefixes
+	_env.set_prefix("atom",  "http://lv2plug.in/ns/ext/atom#");
+	_env.set_prefix("doap",  "http://usefulinc.com/ns/doap#");
+	_env.set_prefix("ingen", INGEN_NS);
+	_env.set_prefix("lv2",   "http://lv2plug.in/ns/lv2core#");
+	_env.set_prefix("midi",  "http://lv2plug.in/ns/ext/midi#");
+	_env.set_prefix("owl",   "http://www.w3.org/2002/07/owl#");
+	_env.set_prefix("patch", "http://lv2plug.in/ns/ext/patch#");
+	_env.set_prefix("rdf",   "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+	_env.set_prefix("rdfs",  "http://www.w3.org/2000/01/rdf-schema#");
+	_env.set_prefix("xsd",   "http://www.w3.org/2001/XMLSchema#");
 }
 
 TurtleWriter::~TurtleWriter()
 {
-	sratom_free(_sratom);
-	serd_writer_free(_writer);
-	serd_env_free(_env);
+	_writer.finish();
 }
 
 bool
@@ -92,13 +63,13 @@ TurtleWriter::write(const LV2_Atom* msg, int32_t)
 {
 	if (!_wrote_prefixes) {
 		// Write namespace prefixes once to reduce traffic
-		serd_env_foreach(_env, write_prefix, _writer);
+		_env.write_prefixes(_writer.sink());
+		_writer.finish();
 		_wrote_prefixes = true;
 	}
 
-	sratom_write(_sratom, &_map.urid_unmap_feature()->urid_unmap, 0,
-	             nullptr, nullptr, msg->type, msg->size, LV2_ATOM_BODY_CONST(msg));
-	serd_writer_finish(_writer);
+	_streamer.write(_writer.sink(), *msg);
+	_writer.finish();
 	return true;
 }
 
diff --git a/src/URI.cpp b/src/URI.cpp
index f7b64209..fd649393 100644
--- a/src/URI.cpp
+++ b/src/URI.cpp
@@ -22,102 +22,39 @@
 
 namespace ingen {
 
-URI::URI()
-	: _uri(SERD_URI_NULL)
-	, _node(SERD_NODE_NULL)
-{}
-
 URI::URI(const std::string& str)
-	: _uri(SERD_URI_NULL)
-	, _node(serd_node_new_uri_from_string((const uint8_t*)str.c_str(),
-                                          nullptr,
-                                          &_uri))
-{}
-
-URI::URI(const char* str)
-	: _uri(SERD_URI_NULL)
-	, _node(serd_node_new_uri_from_string((const uint8_t*)str, nullptr, &_uri))
-{}
-
-URI::URI(const std::string& str, const URI& base)
-	: _uri(SERD_URI_NULL)
-	, _node(serd_node_new_uri_from_string((const uint8_t*)str.c_str(),
-                                          &base._uri,
-                                          &_uri))
-{}
-
-URI::URI(SerdNode node)
-	: _uri(SERD_URI_NULL)
-	, _node(serd_node_new_uri_from_node(&node, nullptr, &_uri))
+	: serd::Node{serd::make_uri(str)}
 {
-	assert(node.type == SERD_URI);
 }
 
-URI::URI(SerdNode node, SerdURI uri)
-	: _uri(uri)
-	, _node(node)
-{
-	assert(node.type == SERD_URI);
-}
-
-URI::URI(const Sord::Node& node)
-	: URI(*node.to_serd_node())
-{
-}
-
-URI::URI(const FilePath& path)
-	: _uri(SERD_URI_NULL)
-	, _node(serd_node_new_file_uri((const uint8_t*)path.c_str(),
-                                   nullptr,
-                                   &_uri,
-                                   true))
-{}
-
-URI::URI(const URI& uri)
-	: _uri(SERD_URI_NULL)
-	, _node(serd_node_new_uri(&uri._uri, nullptr, &_uri))
-{}
-
-URI&
-URI::operator=(const URI& uri)
+URI::URI(const char* str)
+	: serd::Node{serd::make_uri(str)}
 {
-	if (&uri != this) {
-		serd_node_free(&_node);
-		_node = serd_node_new_uri(&uri._uri, nullptr, &_uri);
-	}
-
-	return *this;
 }
 
-URI::URI(URI&& uri) noexcept
-	: _uri(uri._uri)
-	, _node(uri._node)
+URI::URI(const serd::NodeView& node)
+	: serd::Node{node}
 {
-	uri._node = SERD_NODE_NULL;
-	uri._uri  = SERD_URI_NULL;
+	assert(cobj());
+	assert(node.type() == serd::NodeType::URI);
 }
 
-URI&
-URI::operator=(URI&& uri) noexcept
+URI::URI(const serd::Node& node)
+	: serd::Node{node}
 {
-	_node     = uri._node;
-	_uri      = uri._uri;
-	uri._node = SERD_NODE_NULL;
-	uri._uri  = SERD_URI_NULL;
-	return *this;
+	assert(cobj());
+	assert(node.type() == serd::NodeType::URI);
 }
 
-URI::~URI()
+URI::URI(const FilePath& path)
+	: serd::Node{serd::make_file_uri(path.string())}
 {
-	serd_node_free(&_node);
 }
 
 URI
 URI::make_relative(const URI& base) const
 {
-	SerdURI  uri;
-	SerdNode node = serd_node_new_relative_uri(&_uri, &base._uri, nullptr, &uri);
-	return URI(node, uri);
+	return URI(serd::make_relative_uri(std::string(*this), base));
 }
 
 }  // namespace ingen
diff --git a/src/URIs.cpp b/src/URIs.cpp
index dfe6b867..9c83a637 100644
--- a/src/URIs.cpp
+++ b/src/URIs.cpp
@@ -43,21 +43,8 @@ URIs::Quark::Quark(Forge&      forge,
 	: URI(str)
 	, urid(forge.make_urid(URI(str)))
 	, uri(forge.alloc_uri(str))
-	, lnode(lilv_new_uri(lworld, str))
 {}
 
-URIs::Quark::Quark(const Quark& copy)
-	: URI(copy)
-	, urid(copy.urid)
-	, uri(copy.uri)
-	, lnode(lilv_node_duplicate(copy.lnode))
-{}
-
-URIs::Quark::~Quark()
-{
-	lilv_node_free(lnode);
-}
-
 #define NS_RDF   "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 #define NS_RDFS  "http://www.w3.org/2000/01/rdf-schema#"
 
diff --git a/src/World.cpp b/src/World.cpp
index 41e69826..12d10bb2 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -37,7 +37,7 @@
 #include "lilv/lilv.h"
 #include "lv2/log/log.h"
 #include "lv2/urid/urid.h"
-#include "sord/sordmm.hpp"
+#include "serd/serd.hpp"
 
 #include <cstdint>
 #include <list>
@@ -90,7 +90,6 @@ public:
 		: argc(nullptr)
 		, argv(nullptr)
 		, lv2_features(nullptr)
-		, rdf_world(new Sord::World())
 		, lilv_world(lilv_world_new(), lilv_world_free)
 		, uri_map(log, map, unmap)
 		, forge(uri_map)
@@ -108,16 +107,16 @@ public:
 		lilv_world_load_all(lilv_world.get());
 
 		// Set up RDF namespaces
-		rdf_world->add_prefix("atom",  "http://lv2plug.in/ns/ext/atom#");
-		rdf_world->add_prefix("doap",  "http://usefulinc.com/ns/doap#");
-		rdf_world->add_prefix("ingen", INGEN_NS);
-		rdf_world->add_prefix("lv2",   "http://lv2plug.in/ns/lv2core#");
-		rdf_world->add_prefix("midi",  "http://lv2plug.in/ns/ext/midi#");
-		rdf_world->add_prefix("owl",   "http://www.w3.org/2002/07/owl#");
-		rdf_world->add_prefix("patch", "http://lv2plug.in/ns/ext/patch#");
-		rdf_world->add_prefix("rdf",   "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
-		rdf_world->add_prefix("rdfs",  "http://www.w3.org/2000/01/rdf-schema#");
-		rdf_world->add_prefix("xsd",   "http://www.w3.org/2001/XMLSchema#");
+		env.set_prefix("atom",  "http://lv2plug.in/ns/ext/atom#");
+		env.set_prefix("doap",  "http://usefulinc.com/ns/doap#");
+		env.set_prefix("ingen", INGEN_NS);
+		env.set_prefix("lv2",   "http://lv2plug.in/ns/lv2core#");
+		env.set_prefix("midi",  "http://lv2plug.in/ns/ext/midi#");
+		env.set_prefix("owl",   "http://www.w3.org/2002/07/owl#");
+		env.set_prefix("patch", "http://lv2plug.in/ns/ext/patch#");
+		env.set_prefix("rdf",   "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+		env.set_prefix("rdfs",  "http://www.w3.org/2000/01/rdf-schema#");
+		env.set_prefix("xsd",   "http://www.w3.org/2001/XMLSchema#");
 
 		// Load internal 'plugin' information into lilv world
 		LilvNode* rdf_type = lilv_new_uri(
@@ -183,7 +182,8 @@ public:
 	int*              argc;
 	char***           argv;
 	LV2Features*      lv2_features;
-	UPtr<Sord::World> rdf_world;
+	serd::World       rdf_world;
+	serd::Env         env;
 	LilvWorldUPtr     lilv_world;
 	URIMap            uri_map;
 	Forge             forge;
@@ -219,7 +219,8 @@ World::load_configuration(int& argc, char**& argv)
 	_impl->argv = &argv;
 
 	// Parse default configuration files
-	const auto files = _impl->conf.load_default("ingen", "options.ttl");
+	const auto files =
+		_impl->conf.load_default(rdf_world(), "ingen", "options.ttl");
 	for (const auto& f : files) {
 		_impl->log.info("Loaded configuration %1%\n", f);
 	}
@@ -247,7 +248,8 @@ Log&           World::log()  { return _impl->log; }
 
 std::mutex& World::rdf_mutex() { return _impl->rdf_mutex; }
 
-Sord::World* World::rdf_world()  { return _impl->rdf_world.get(); }
+serd::World& World::rdf_world()  { return _impl->rdf_world; }
+serd::Env&   World::env()        { return _impl->env; }
 LilvWorld*   World::lilv_world() { return _impl->lilv_world.get(); }
 
 LV2Features& World::lv2_features() { return *_impl->lv2_features; }
diff --git a/src/client/ClientStore.cpp b/src/client/ClientStore.cpp
index a42efd16..2fdfa4a7 100644
--- a/src/client/ClientStore.cpp
+++ b/src/client/ClientStore.cpp
@@ -59,6 +59,7 @@ ClientStore::clear()
 void
 ClientStore::add_object(SPtr<ObjectModel> object)
 {
+	_log.info("Add object %1%\n", object->path());
 	// If we already have "this" object, merge the existing one into the new
 	// one (with precedence to the new values).
 	auto existing = find(object->path());
diff --git a/src/client/PluginModel.cpp b/src/client/PluginModel.cpp
index f0e3c3a0..b0a2c5cb 100644
--- a/src/client/PluginModel.cpp
+++ b/src/client/PluginModel.cpp
@@ -38,8 +38,6 @@ namespace client {
 LilvWorld*         PluginModel::_lilv_world   = nullptr;
 const LilvPlugins* PluginModel::_lilv_plugins = nullptr;
 
-Sord::World* PluginModel::_rdf_world = nullptr;
-
 PluginModel::PluginModel(URIs&             uris,
                          const URI&        uri,
                          const Atom&       type,
@@ -65,7 +63,7 @@ PluginModel::PluginModel(URIs&             uris,
 
 	if (uris.ingen_Internal == _type) {
 		set_property(uris.doap_name,
-		             uris.forge.alloc(std::string(uri.fragment().substr(1))));
+		             uris.forge.alloc(uri.fragment().substr(1)));
 	}
 }
 
@@ -103,7 +101,7 @@ PluginModel::get_property(const URI& key) const
 
 	// No lv2:symbol from data or engine, invent one
 	if (key == _uris.lv2_symbol) {
-		string str        = this->uri();
+		string str        = this->uri().str();
 		size_t last_delim = last_uri_delim(str);
 		while (last_delim != string::npos &&
 		       !contains_alpha_after(str, last_delim)) {
@@ -264,12 +262,13 @@ heading(const std::string& text, bool html, unsigned level)
 }
 
 static std::string
-link(const std::string& addr, bool html)
+link(const URI& addr, bool html)
 {
 	if (html) {
-		return std::string("<a href=\"") + addr + "\">" + addr + "</a>";
+		return std::string("<a href=\"") + addr.str() + "\">" + addr.str() +
+		       "</a>";
 	} else {
-		return addr;
+		return addr.str();
 	}
 }
 
diff --git a/src/client/PluginUI.cpp b/src/client/PluginUI.cpp
index a997d716..72a1fba0 100644
--- a/src/client/PluginUI.cpp
+++ b/src/client/PluginUI.cpp
@@ -224,9 +224,9 @@ PluginUI::create(ingen::World&          world,
 bool
 PluginUI::instantiate()
 {
-	const URIs&       uris       = _world.uris();
-	const std::string plugin_uri = _block->plugin()->uri();
-	LilvWorld*        lworld     = _world.lilv_world();
+	const URIs& uris       = _world.uris();
+	const URI&  plugin_uri = _block->plugin()->uri();
+	LilvWorld*  lworld     = _world.lilv_world();
 
 	// Load seeAlso files to access data like portNotification descriptions
 	lilv_world_load_resource(lworld, lilv_ui_get_uri(_ui));
diff --git a/src/client/wscript b/src/client/wscript
index 394c9e4d..c42046e1 100644
--- a/src/client/wscript
+++ b/src/client/wscript
@@ -10,7 +10,7 @@ def build(bld):
               target          = 'ingen_client',
               install_path    = '${LIBDIR}',
               use             = 'libingen',
-              uselib          = 'GLIBMM LV2 LILV SUIL RAUL SERD SORD SIGCPP')
+              uselib          = 'GLIBMM LV2 LILV SUIL RAUL SERD SIGCPP')
 
     obj.source = '''
         BlockModel.cpp
diff --git a/src/gui/App.cpp b/src/gui/App.cpp
index dfa34998..0e2292eb 100644
--- a/src/gui/App.cpp
+++ b/src/gui/App.cpp
@@ -87,7 +87,7 @@ App::App(ingen::World& world)
 	, _requested_plugins(false)
 	, _is_plugin(false)
 {
-	_world.conf().load_default("ingen", "gui.ttl");
+	_world.conf().load_default(_world.rdf_world(), "ingen", "gui.ttl");
 
 	WidgetFactory::get_widget_derived("connect_win", _connect_window);
 	WidgetFactory::get_widget_derived("messages_win", _messages_window);
@@ -99,7 +99,6 @@ App::App(ingen::World& world)
 	_about_dialog->property_program_name() = "Ingen";
 	_about_dialog->property_logo_icon_name() = "ingen";
 
-	PluginModel::set_rdf_world(*world.rdf_world());
 	PluginModel::set_lilv_world(world.lilv_world());
 
 	using namespace std::placeholders;
@@ -178,7 +177,8 @@ App::attach(SPtr<ingen::Interface> client)
 	}
 
 	if (_world.conf().option("dump").get<int32_t>()) {
-		_dumper = SPtr<StreamWriter>(new StreamWriter(_world.uri_map(),
+		_dumper = SPtr<StreamWriter>(new StreamWriter(_world.rdf_world(),
+		                                              _world.uri_map(),
 		                                              _world.uris(),
 		                                              URI("ingen:/client"),
 		                                              stderr,
@@ -469,8 +469,11 @@ App::quit(Gtk::Window* dialog_parent)
 	Gtk::Main::quit();
 
 	try {
-		const std::string path = _world.conf().save(
-			_world.uri_map(), "ingen", "gui.ttl", Configuration::GUI);
+		const std::string path = _world.conf().save(_world.rdf_world(),
+		                                             _world.uri_map(),
+		                                             "ingen",
+		                                             "gui.ttl",
+		                                             Configuration::GUI);
 		std::cout << fmt("Saved GUI settings to %1%\n", path);
 	} catch (const std::exception& e) {
 		std::cerr << fmt("Error saving GUI settings (%1%)\n", e.what());
diff --git a/src/gui/ConnectWindow.cpp b/src/gui/ConnectWindow.cpp
index 209475e0..a7b2cbf7 100644
--- a/src/gui/ConnectWindow.cpp
+++ b/src/gui/ConnectWindow.cpp
@@ -228,7 +228,7 @@ ConnectWindow::connect(bool existing)
 	if (_mode == Mode::CONNECT_REMOTE) {
 		std::string uri_str = world.conf().option("connect").ptr<char>();
 		if (existing) {
-			uri_str = world.interface()->uri();
+			uri_str = world.interface()->uri().str();
 			_connect_stage = 1;
 			SPtr<client::SocketClient> client = dynamic_ptr_cast<client::SocketClient>(
 				world.interface());
diff --git a/src/gui/GraphCanvas.cpp b/src/gui/GraphCanvas.cpp
index 13b17bdf..40373a1a 100644
--- a/src/gui/GraphCanvas.cpp
+++ b/src/gui/GraphCanvas.cpp
@@ -636,7 +636,7 @@ serialise_arc(GanvEdge* arc, void* data)
 
 	gui::Arc* garc = dynamic_cast<gui::Arc*>(Glib::wrap(GANV_EDGE(arc)));
 	if (garc) {
-		serialiser->serialise_arc(Sord::Node(), garc->model());
+		serialiser->serialise_arc({}, garc->model());
 	}
 }
 
@@ -690,7 +690,7 @@ GraphCanvas::paste()
 	// Figure out the copy graph base path
 	Raul::Path copy_root("/");
 	if (base_uri) {
-		std::string base = *base_uri;
+		std::string base = base_uri->str();
 		if (base[base.size() - 1] == '/') {
 			base = base.substr(0, base.size() - 1);
 		}
diff --git a/src/gui/LoadPluginWindow.cpp b/src/gui/LoadPluginWindow.cpp
index bb84f96f..32a35c65 100644
--- a/src/gui/LoadPluginWindow.cpp
+++ b/src/gui/LoadPluginWindow.cpp
@@ -459,7 +459,7 @@ LoadPluginWindow::filter_changed()
 			field = get_author_name(plugin);
 			break;
 		case CriteriaColumns::Criteria::URI:
-			field = plugin->uri();
+			field = plugin->uri().str();
 			break;
 		}
 
diff --git a/src/gui/NodeMenu.cpp b/src/gui/NodeMenu.cpp
index e2478592..ef0e80dc 100644
--- a/src/gui/NodeMenu.cpp
+++ b/src/gui/NodeMenu.cpp
@@ -235,11 +235,11 @@ NodeMenu::on_save_preset_activated()
 }
 
 void
-NodeMenu::on_preset_activated(const std::string& uri)
+NodeMenu::on_preset_activated(const URI& uri)
 {
 	_app->set_property(block()->uri(),
 	                   _app->uris().pset_preset,
-	                   _app->forge().make_urid(URI(uri)));
+	                   _app->forge().make_urid(uri));
 }
 
 bool
diff --git a/src/gui/NodeMenu.hpp b/src/gui/NodeMenu.hpp
index 2a3268b4..799abef3 100644
--- a/src/gui/NodeMenu.hpp
+++ b/src/gui/NodeMenu.hpp
@@ -60,7 +60,7 @@ protected:
 	void on_menu_enabled();
 	void on_menu_randomize();
 	void on_save_preset_activated();
-	void on_preset_activated(const std::string& uri);
+	void on_preset_activated(const URI& uri);
 
 	Gtk::MenuItem*      _popup_gui_menuitem;
 	Gtk::CheckMenuItem* _embed_gui_menuitem;
diff --git a/src/gui/Port.cpp b/src/gui/Port.cpp
index 14f87fc1..63e90bdd 100644
--- a/src/gui/Port.cpp
+++ b/src/gui/Port.cpp
@@ -289,8 +289,8 @@ Port::build_uri_menu()
 	// Add a menu item for each such class
 	for (const auto& v : values) {
 		if (!v.first.empty()) {
-			const std::string qname = world.rdf_world()->prefixes().qualify(v.second);
-			const std::string label = qname + " - " + v.first;
+			const auto        qname = world.env().qualify(v.second);
+			const std::string label = std::string(*qname) + " - " + v.first;
 			menu->items().push_back(Gtk::Menu_Helpers::MenuElem(label));
 			Gtk::MenuItem* menu_item = &(menu->items().back());
 			menu_item->signal_activate().connect(
diff --git a/src/gui/PropertiesWindow.cpp b/src/gui/PropertiesWindow.cpp
index 9912f73a..32ed24e5 100644
--- a/src/gui/PropertiesWindow.cpp
+++ b/src/gui/PropertiesWindow.cpp
@@ -114,7 +114,8 @@ PropertiesWindow::add_property(const URI& key, const Atom& value)
 	LilvNode*   prop = lilv_new_uri(world.lilv_world(), key.c_str());
 	std::string name = rdfs::label(world, prop);
 	if (name.empty()) {
-		name = world.rdf_world()->prefixes().qualify(key);
+		const auto qname = world.env().qualify(key);
+		name = qname ? std::string(*qname) : key.str();
 	}
 	Gtk::Label* label = new Gtk::Label(
 	        std::string("<a href=\"") + key.string() + "\">" + name + "</a>",
diff --git a/src/gui/ingen_gui_lv2.cpp b/src/gui/ingen_gui_lv2.cpp
index 4817e9ae..4107d5c0 100644
--- a/src/gui/ingen_gui_lv2.cpp
+++ b/src/gui/ingen_gui_lv2.cpp
@@ -143,6 +143,7 @@ instantiate(const LV2UI_Descriptor*   descriptor,
 	// Set up an engine interface that writes LV2 atoms
 	ui->engine = SPtr<ingen::Interface>(
 		new ingen::AtomWriter(
+			ui->world->rdf_world(),
 			ui->world->uri_map(), ui->world->uris(), *ui->sink));
 
 	ui->world->set_interface(ui->engine);
diff --git a/src/gui/wscript b/src/gui/wscript
index b33bd31e..2a52849e 100644
--- a/src/gui/wscript
+++ b/src/gui/wscript
@@ -57,11 +57,10 @@ def build(bld):
             LILV
             LV2
             RAUL
-            SIGCPP
             SERD
-            SORD
-            SRATOM
+            SIGCPP
             SOUP
+            SRATOM
             SUIL
             WEBKIT
     ''')
@@ -124,4 +123,4 @@ def build(bld):
               target       = 'ingen_gui_lv2',
               install_path = '${LV2DIR}/ingen.lv2/',
               use          = 'libingen libingen_gui',
-              uselib       = 'LV2 SERD SORD SRATOM LILV RAUL GLIBMM GTKMM')
+              uselib       = 'LV2 SERD SRATOM LILV RAUL GLIBMM GTKMM')
diff --git a/src/ingen/ingen.cpp b/src/ingen/ingen.cpp
index 15544c10..214e9e01 100644
--- a/src/ingen/ingen.cpp
+++ b/src/ingen/ingen.cpp
@@ -200,31 +200,26 @@ main(int argc, char** argv)
 			*world, *engine_interface, graph, parent, symbol);
 	} else if (conf.option("server-load").is_valid()) {
 		const char* path = conf.option("server-load").ptr<char>();
-		if (serd_uri_string_has_scheme((const uint8_t*)path)) {
+		if (serd_uri_string_has_scheme(path)) {
 			std::cout << "Loading " << path << " (server side)" << std::endl;
 			engine_interface->copy(URI(path), main_uri());
 		} else {
-			SerdNode uri = serd_node_new_file_uri(
-				(const uint8_t*)path, nullptr, nullptr, true);
-			std::cout << "Loading " << (const char*)uri.buf
-			          << " (server side)" << std::endl;
-			engine_interface->copy(URI((const char*)uri.buf), main_uri());
-			serd_node_free(&uri);
+			serd::Node uri = serd::make_file_uri(path);
+			std::cout << "Loading " << uri << " (server side)" << std::endl;
+			engine_interface->copy(URI(uri), main_uri());
 		}
 	}
 
 	// Save the currently loaded graph
 	if (conf.option("save").is_valid()) {
 		const char* path = conf.option("save").ptr<char>();
-		if (serd_uri_string_has_scheme((const uint8_t*)path)) {
+		if (serd_uri_string_has_scheme(path)) {
 			std::cout << "Saving to " << path << std::endl;
 			engine_interface->copy(main_uri(), URI(path));
 		} else {
-			SerdNode uri = serd_node_new_file_uri(
-				(const uint8_t*)path, nullptr, nullptr, true);
-			std::cout << "Saving to " << (const char*)uri.buf << std::endl;
-			engine_interface->copy(main_uri(), URI((const char*)uri.buf));
-			serd_node_free(&uri);
+			serd::Node uri = serd::make_file_uri(path);
+			std::cout << "Saving to " << uri << std::endl;
+			engine_interface->copy(main_uri(), URI(uri));
 		}
 	}
 
@@ -256,8 +251,12 @@ main(int argc, char** argv)
 	}
 
 	// Save configuration to restore preferences on next run
-	const std::string path = conf.save(
-		world->uri_map(), "ingen", "options.ttl", Configuration::GLOBAL);
+	const std::string path = conf.save(world->rdf_world(),
+	                                   world->uri_map(),
+	                                   "ingen",
+	                                   "options.ttl",
+	                                   Configuration::GLOBAL);
+
 	std::cout << fmt("Saved configuration to %1%\n", path);
 
 	engine_interface.reset();
diff --git a/src/server/Engine.cpp b/src/server/Engine.cpp
index 8256981b..f1a86792 100644
--- a/src/server/Engine.cpp
+++ b/src/server/Engine.cpp
@@ -68,31 +68,37 @@ INGEN_THREAD_LOCAL unsigned ThreadManager::flags(0);
 bool               ThreadManager::single_threaded(true);
 
 Engine::Engine(ingen::World& world)
-	: _world(world)
-	, _options(new LV2Options(world.uris()))
-	, _buffer_factory(new BufferFactory(*this, world.uris()))
-	, _maid(new Raul::Maid)
-	, _worker(new Worker(world.log(), event_queue_size()))
-	, _sync_worker(new Worker(world.log(), event_queue_size(), true))
-	, _broadcaster(new Broadcaster())
-	, _control_bindings(new ControlBindings(*this))
-	, _block_factory(new BlockFactory(world))
-	, _undo_stack(new UndoStack(world.uris(), world.uri_map()))
-	, _redo_stack(new UndoStack(world.uris(), world.uri_map()))
-	, _post_processor(new PostProcessor(*this))
-	, _pre_processor(new PreProcessor(*this))
-	, _event_writer(new EventWriter(*this))
-	, _interface(_event_writer)
-	, _atom_interface(
-		new AtomReader(world.uri_map(), world.uris(), world.log(), *_interface))
-	, _root_graph(nullptr)
-	, _cycle_start_time(0)
-	, _rand_engine(reinterpret_cast<uintptr_t>(this))
-	, _uniform_dist(0.0f, 1.0f)
-	, _quit_flag(false)
-	, _reset_load_flag(false)
-	, _atomic_bundles(world.conf().option("atomic-bundles").get<int32_t>())
-	, _activated(false)
+    : _world(world)
+    , _options(new LV2Options(world.uris()))
+    , _buffer_factory(new BufferFactory(*this, world.uris()))
+    , _maid(new Raul::Maid)
+    , _worker(new Worker(world.log(), event_queue_size()))
+    , _sync_worker(new Worker(world.log(), event_queue_size(), true))
+    , _broadcaster(new Broadcaster())
+    , _control_bindings(new ControlBindings(*this))
+    , _block_factory(new BlockFactory(world))
+    , _undo_stack(new UndoStack(world.rdf_world(),
+                                world.uris(),
+                                world.uri_map()))
+    , _redo_stack(new UndoStack(world.rdf_world(),
+                                world.uris(),
+                                world.uri_map()))
+    , _post_processor(new PostProcessor(*this))
+    , _pre_processor(new PreProcessor(*this))
+    , _event_writer(new EventWriter(*this))
+    , _interface(_event_writer)
+    , _atom_interface(new AtomReader(world.uri_map(),
+                                     world.uris(),
+                                     world.log(),
+                                     *_interface))
+    , _root_graph(nullptr)
+    , _cycle_start_time(0)
+    , _rand_engine(reinterpret_cast<uintptr_t>(this))
+    , _uniform_dist(0.0f, 1.0f)
+    , _quit_flag(false)
+    , _reset_load_flag(false)
+    , _atomic_bundles(world.conf().option("atomic-bundles").get<int32_t>())
+    , _activated(false)
 {
 	if (!world.store()) {
 		world.set_store(std::make_shared<ingen::Store>());
@@ -125,7 +131,8 @@ Engine::Engine(ingen::World& world)
 		_interface = std::make_shared<Tee>(
 			Tee::Sinks{
 				_event_writer,
-				std::make_shared<StreamWriter>(world.uri_map(),
+				std::make_shared<StreamWriter>(world.rdf_world(),
+				                               world.uri_map(),
 				                               world.uris(),
 				                               URI("ingen:/engine"),
 				                               stderr,
diff --git a/src/server/Event.hpp b/src/server/Event.hpp
index 7da4b955..f5c826b4 100644
--- a/src/server/Event.hpp
+++ b/src/server/Event.hpp
@@ -133,7 +133,7 @@ protected:
 	}
 
 	inline bool pre_process_done(Status st, const URI& subject) {
-		_err_subject = subject;
+		_err_subject = std::string{subject};
 		return pre_process_done(st);
 	}
 
diff --git a/src/server/LV2Plugin.cpp b/src/server/LV2Plugin.cpp
index 01357d8d..40ba34e7 100644
--- a/src/server/LV2Plugin.cpp
+++ b/src/server/LV2Plugin.cpp
@@ -68,7 +68,7 @@ LV2Plugin::update_properties()
 Raul::Symbol
 LV2Plugin::symbol() const
 {
-	std::string working = uri();
+	std::string working(uri());
 	if (working.back() == '/') {
 		working = working.substr(0, working.length() - 1);
 	}
diff --git a/src/server/PreProcessor.cpp b/src/server/PreProcessor.cpp
index 872302c0..76e0ac73 100644
--- a/src/server/PreProcessor.cpp
+++ b/src/server/PreProcessor.cpp
@@ -176,8 +176,10 @@ PreProcessor::run()
 	UndoStack& undo_stack = *_engine.undo_stack();
 	UndoStack& redo_stack = *_engine.redo_stack();
 	AtomWriter undo_writer(
+		_engine.world().rdf_world(),
 		_engine.world().uri_map(), _engine.world().uris(), undo_stack);
 	AtomWriter redo_writer(
+		_engine.world().rdf_world(),
 		_engine.world().uri_map(), _engine.world().uris(), redo_stack);
 
 	ThreadManager::set_flag(THREAD_PRE_PROCESS);
diff --git a/src/server/SocketListener.cpp b/src/server/SocketListener.cpp
index b4b50a14..51e07b2b 100644
--- a/src/server/SocketListener.cpp
+++ b/src/server/SocketListener.cpp
@@ -92,7 +92,7 @@ ingen_listen(Engine* engine, Raul::Socket* unix_sock, Raul::Socket* net_sock)
 	// Bind UNIX socket and create PID-less symbolic link
 	const URI unix_uri(unix_scheme + unix_path);
 	bool      make_link = true;
-	if (!unix_sock->bind(unix_uri) || !unix_sock->listen()) {
+	if (!unix_sock->bind(unix_uri.str()) || !unix_sock->listen()) {
 		world.log().error("Failed to create UNIX socket\n");
 		unix_sock->close();
 		make_link = false;
@@ -130,7 +130,7 @@ ingen_listen(Engine* engine, Raul::Socket* unix_sock, Raul::Socket* net_sock)
 	const int port = world.conf().option("engine-port").get<int32_t>();
 	std::ostringstream ss;
 	ss << "tcp://*:" << port;
-	if (!net_sock->bind(URI(ss.str())) || !net_sock->listen()) {
+	if (!net_sock->bind(ss.str()) || !net_sock->listen()) {
 		world.log().error("Failed to create TCP socket\n");
 		net_sock->close();
 	} else {
diff --git a/src/server/SocketServer.hpp b/src/server/SocketServer.hpp
index f3f02a26..86f81d65 100644
--- a/src/server/SocketServer.hpp
+++ b/src/server/SocketServer.hpp
@@ -43,14 +43,16 @@ public:
 		, _sink(world.conf().option("dump").get<int32_t>()
 		        ? SPtr<Interface>(
 			        new Tee({SPtr<Interface>(new EventWriter(engine)),
-					         SPtr<Interface>(new StreamWriter(world.uri_map(),
+					         SPtr<Interface>(new StreamWriter(world.rdf_world(),
+					                                          world.uri_map(),
 					                                          world.uris(),
 					                                          URI("ingen:/engine"),
 					                                          stderr,
 					                                          ColorContext::Color::CYAN))}))
 		        : SPtr<Interface>(new EventWriter(engine)))
 		, _reader(new SocketReader(world, *_sink.get(), sock))
-		, _writer(new SocketWriter(world.uri_map(),
+		, _writer(new SocketWriter(world.rdf_world(),
+		                           world.uri_map(),
 		                           world.uris(),
 		                           URI(sock->uri()),
 		                           sock))
diff --git a/src/server/UndoStack.cpp b/src/server/UndoStack.cpp
index a94617a5..d6829200 100644
--- a/src/server/UndoStack.cpp
+++ b/src/server/UndoStack.cpp
@@ -23,15 +23,14 @@
 #include "lv2/patch/patch.h"
 #include "lv2/urid/urid.h"
 #include "serd/serd.h"
-#include "sratom/sratom.h"
+#include "sratom/sratom.hpp"
 
 #include <ctime>
+#include <fstream>
 #include <iterator>
 #include <memory>
 
-#define NS_RDF (const uint8_t*)"http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-
-#define USTR(s) ((const uint8_t*)(s))
+#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 
 namespace ingen {
 namespace server {
@@ -120,9 +119,9 @@ UndoStack::pop()
 struct BlankIDs {
 	explicit BlankIDs(char c='b') : c(c) {}
 
-	SerdNode get() {
+	serd::Node get() {
 		snprintf(buf, sizeof(buf), "%c%u", c, n++);
-		return serd_node_from_string(SERD_BLANK, USTR(buf));
+		return serd::make_blank(buf);
 	}
 
 	char       buf[16]{};
@@ -131,126 +130,120 @@ struct BlankIDs {
 };
 
 struct ListContext {
-	explicit ListContext(BlankIDs& ids, unsigned flags, const SerdNode* s, const SerdNode* p)
-		: ids(ids)
-		, s(*s)
-		, p(*p)
-		, flags(flags | SERD_LIST_O_BEGIN)
+	explicit ListContext(BlankIDs&            ids,
+	                     serd::StatementFlags flags,
+	                     const serd::Node&    s,
+	                     const serd::Node&    p)
+	    : ids(ids)
+	    , s(s)
+	    , p(p)
+	    , flags(flags | serd::StatementFlag::list_O)
 	{}
 
-	SerdNode start_node(SerdWriter* writer) {
-		const SerdNode node = ids.get();
-		serd_writer_write_statement(writer, flags, nullptr, &s, &p, &node, nullptr, nullptr);
+	serd::Node start_node(serd::Writer& writer) {
+		const serd::Node node = ids.get();
+		writer.sink().write(flags, s, p, node);
 		return node;
 	}
 
-	void append(SerdWriter* writer, unsigned oflags, const SerdNode* value) {
+	void append(serd::Writer&        writer,
+	            serd::StatementFlags oflags,
+	            const serd::Node&    value)
+	{
 		// s p node
-		const SerdNode node = start_node(writer);
+		const serd::Node node = start_node(writer);
 
 		// node rdf:first value
-		p     = serd_node_from_string(SERD_URI, NS_RDF "first");
-		flags = SERD_LIST_CONT;
-		serd_writer_write_statement(writer, flags|oflags, nullptr, &node, &p, value, nullptr, nullptr);
+		p     = serd::make_uri(NS_RDF "first");
+		flags = {};
+		writer.sink().write(flags | oflags, node, p, value);
 
-		end_node(writer, &node);
+		end_node(writer, node);
 	}
 
-	void end_node(SerdWriter*, const SerdNode* node) {
+	void end_node(serd::Writer&, const serd::Node& node) {
 		// Prepare for next call: node rdf:rest ...
-		s = *node;
-		p = serd_node_from_string(SERD_URI, NS_RDF "rest");
+		s = node;
+		p = serd::make_uri(NS_RDF "rest");
 	}
 
-	void end(SerdWriter* writer) {
-		const SerdNode nil = serd_node_from_string(SERD_URI, NS_RDF "nil");
-		serd_writer_write_statement(writer, flags, nullptr, &s, &p, &nil, nullptr, nullptr);
+	void end(serd::Writer& writer) {
+		const serd::Node nil = serd::make_uri(NS_RDF "nil");
+		writer.sink().write(flags, s, p, nil);
 	}
 
-	BlankIDs& ids;
-	SerdNode  s;
-	SerdNode  p;
-	unsigned  flags;
+	BlankIDs&            ids;
+	serd::Node           s;
+	serd::Node           p;
+	serd::StatementFlags flags;
 };
 
 void
-UndoStack::write_entry(Sratom*                 sratom,
-                       SerdWriter*             writer,
-                       const SerdNode* const   subject,
+UndoStack::write_entry(sratom::Streamer&       streamer,
+                       serd::Writer&           writer,
+                       const serd::Node&       subject,
                        const UndoStack::Entry& entry)
 {
 	char time_str[24];
 	strftime(time_str, sizeof(time_str), "%FT%T", gmtime(&entry.time));
 
-	// entry rdf:type ingen:UndoEntry
-	SerdNode p = serd_node_from_string(SERD_URI, USTR(INGEN_NS "time"));
-	SerdNode o = serd_node_from_string(SERD_LITERAL, USTR(time_str));
-	serd_writer_write_statement(writer, SERD_ANON_CONT, nullptr, subject, &p, &o, nullptr, nullptr);
+	writer.sink().write({},
+	                    subject,
+	                    serd::make_uri(INGEN_NS "time"),
+	                    serd::make_string(time_str));
 
-	p = serd_node_from_string(SERD_URI, USTR(INGEN_NS "events"));
+	serd::Node p = serd::make_uri(INGEN_NS "events");
 
 	BlankIDs    ids('e');
-	ListContext ctx(ids, SERD_ANON_CONT, subject, &p);
+	ListContext ctx(ids, {}, subject, p);
 
 	for (const LV2_Atom* atom : entry.events) {
-		const SerdNode node = ctx.start_node(writer);
-
-		p         = serd_node_from_string(SERD_URI, NS_RDF "first");
-		ctx.flags = SERD_LIST_CONT;
-		sratom_write(sratom, &_map.urid_unmap_feature()->urid_unmap, SERD_LIST_CONT,
-		             &node, &p,
-		             atom->type, atom->size, LV2_ATOM_BODY_CONST(atom));
+		const serd::Node node = ctx.start_node(writer);
 
-		ctx.end_node(writer, &node);
+		p         = serd::make_uri(NS_RDF "first");
+		ctx.flags = {};
+		streamer.write(writer.sink(), node, p, *atom);
+		ctx.end_node(writer, node);
 	}
 
 	ctx.end(writer);
 }
 
 void
-UndoStack::save(FILE* stream, const char* name)
+UndoStack::save(std::ofstream& stream, const char* name)
 {
-	SerdEnv* env = serd_env_new(nullptr);
-	serd_env_set_prefix_from_strings(env, USTR("atom"),  USTR(LV2_ATOM_PREFIX));
-	serd_env_set_prefix_from_strings(env, USTR("ingen"), USTR(INGEN_NS));
-	serd_env_set_prefix_from_strings(env, USTR("patch"), USTR(LV2_PATCH_PREFIX));
-
-	const SerdNode base = serd_node_from_string(SERD_URI, USTR("ingen:/"));
-	SerdURI        base_uri;
-	serd_uri_parse(base.buf, &base_uri);
-
-	SerdWriter* writer = serd_writer_new(
-		SERD_TURTLE,
-		(SerdStyle)(SERD_STYLE_RESOLVED|SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED),
-		env,
-		&base_uri,
-		serd_file_sink,
-		stream);
+	serd::Env env;
+	env.set_prefix("atom",  LV2_ATOM_PREFIX);
+	env.set_prefix("ingen", INGEN_NS);
+	env.set_prefix("patch", LV2_PATCH_PREFIX);
+
+	const serd::Node base = serd::make_uri("ingen:/");
+
+	serd::Writer writer(_world,
+	                    serd::Syntax::Turtle,
+	                    {},
+	                    env,
+	                    stream);
 
 	// Configure sratom to write directly to the writer (and thus the socket)
-	Sratom* sratom = sratom_new(&_map.urid_map_feature()->urid_map);
-	sratom_set_sink(sratom,
-	                (const char*)base.buf,
-	                (SerdStatementSink)serd_writer_write_statement,
-	                (SerdEndSink)serd_writer_end_anon,
-	                writer);
+	sratom::Streamer streamer{_world,
+	                          _map.urid_map_feature()->urid_map,
+	                          _map.urid_unmap_feature()->urid_unmap};
 
-	SerdNode s = serd_node_from_string(SERD_BLANK, (const uint8_t*)name);
-	SerdNode p = serd_node_from_string(SERD_URI, USTR(INGEN_NS "entries"));
+	serd::Node s = serd::make_blank(name);
+	serd::Node p = serd::make_uri(INGEN_NS "entries");
 
 	BlankIDs    ids('u');
-	ListContext ctx(ids, 0, &s, &p);
+	ListContext ctx(ids, {}, s, p);
 	for (const Entry& e : _stack) {
-		const SerdNode entry = ids.get();
-		ctx.append(writer, SERD_ANON_O_BEGIN, &entry);
-		write_entry(sratom, writer, &entry, e);
-		serd_writer_end_anon(writer, &entry);
+		const serd::Node entry = ids.get();
+		ctx.append(writer, serd::StatementFlag::anon_O, entry);
+		write_entry(streamer, writer, entry, e);
+		writer.sink().end(entry);
 	}
 	ctx.end(writer);
 
-	sratom_free(sratom);
-	serd_writer_finish(writer);
-	serd_writer_free(writer);
+	writer.finish();
 }
 
 } // namespace server
diff --git a/src/server/UndoStack.hpp b/src/server/UndoStack.hpp
index 04021b99..7d79e7fc 100644
--- a/src/server/UndoStack.hpp
+++ b/src/server/UndoStack.hpp
@@ -21,8 +21,7 @@
 #include "ingen/ingen.h"
 #include "lv2/atom/atom.h"
 #include "lv2/atom/util.h"
-#include "serd/serd.h"
-#include "sratom/sratom.h"
+#include "serd/serd.hpp"
 
 #include <cstdint>
 #include <cstdio>
@@ -30,6 +29,9 @@
 #include <cstring>
 #include <ctime>
 #include <deque>
+#include <iosfwd>
+
+namespace sratom { class Streamer; }
 
 namespace ingen {
 
@@ -80,7 +82,10 @@ public:
 		std::deque<LV2_Atom*> events;
 	};
 
-	UndoStack(URIs& uris, URIMap& map) : _uris(uris), _map(map), _depth(0) {}
+	UndoStack(serd::World& world, URIs& uris, URIMap& map)
+		: _world(world), _uris(uris), _map(map), _depth(0)
+	{
+	}
 
 	int  start_entry();
 	bool write(const LV2_Atom* msg, int32_t default_id=0) override;
@@ -89,17 +94,18 @@ public:
 	bool  empty() const { return _stack.empty(); }
 	Entry pop();
 
-	void save(FILE* stream, const char* name="undo");
+	void save(std::ofstream& stream, const char* name="undo");
 
 private:
 	bool ignore_later_event(const LV2_Atom* first,
 	                        const LV2_Atom* second) const;
 
-	void write_entry(Sratom*         sratom,
-	                 SerdWriter*     writer,
-	                 const SerdNode* subject,
-	                 const Entry&    entry);
+	void write_entry(sratom::Streamer& streamer,
+	                 serd::Writer&     writer,
+	                 const serd::Node& subject,
+	                 const Entry&      entry);
 
+	serd::World&      _world;
 	URIs&             _uris;
 	URIMap&           _map;
 	std::deque<Entry> _stack;
diff --git a/src/server/events/Copy.cpp b/src/server/events/Copy.cpp
index 5418af4b..5ac31f55 100644
--- a/src/server/events/Copy.cpp
+++ b/src/server/events/Copy.cpp
@@ -131,9 +131,10 @@ Copy::engine_to_engine(PreProcessContext& ctx)
 }
 
 static bool
-ends_with(const std::string& str, const std::string& end)
+ends_with(const URI& uri, const std::string& end)
 {
-    if (str.length() >= end.length()) {
+	const auto& str = uri.str();
+	if (str.length() >= end.length()) {
         return !str.compare(str.length() - end.length(), end.length(), end);
     }
     return false;
diff --git a/src/server/events/Delta.cpp b/src/server/events/Delta.cpp
index 0a7b05ea..ab751b76 100644
--- a/src/server/events/Delta.cpp
+++ b/src/server/events/Delta.cpp
@@ -35,6 +35,8 @@
 #include "ingen/World.hpp"
 #include "raul/Maid.hpp"
 
+#include <boost/optional/optional_io.hpp>
+
 #include <mutex>
 #include <set>
 #include <string>
@@ -364,7 +366,7 @@ Delta::pre_process(PreProcessContext& ctx)
 						_status = Status::BAD_VALUE_TYPE;
 					}
 				} else if (key == uris.pset_preset) {
-					URI uri;
+					boost::optional<URI> uri;
 					if (uris.forge.is_uri(value)) {
 						const std::string uri_str = uris.forge.str(value, false);
 						if (URI::is_valid(uri_str)) {
@@ -374,9 +376,9 @@ Delta::pre_process(PreProcessContext& ctx)
 						uri = URI(FilePath(value.ptr<char>()));
 					}
 
-					if (!uri.empty()) {
+					if (uri) {
 						op = SpecialType::PRESET;
-						if ((_state = block->load_preset(uri))) {
+						if ((_state = block->load_preset(*uri))) {
 							lilv_state_emit_port_values(
 								_state, s_add_set_event, this);
 						} else {
diff --git a/src/server/ingen_lv2.cpp b/src/server/ingen_lv2.cpp
index 57663344..8a639765 100644
--- a/src/server/ingen_lv2.cpp
+++ b/src/server/ingen_lv2.cpp
@@ -60,8 +60,7 @@
 #include "raul/RingBuffer.hpp"
 #include "raul/Semaphore.hpp"
 #include "raul/Symbol.hpp"
-#include "serd/serd.h"
-#include "sord/sordmm.hpp"
+#include "serd/serd.hpp"
 
 #include <algorithm>
 #include <cstdint>
@@ -123,7 +122,8 @@ public:
 		          engine.world().uris(),
 		          engine.world().log(),
 		          *engine.world().interface().get())
-		, _writer(engine.world().uri_map(),
+		, _writer(engine.world().rdf_world(),
+		          engine.world().uri_map(),
 		          engine.world().uris(),
 		          *this)
 		, _from_ui(ui_ring_size(block_length))
@@ -460,11 +460,10 @@ struct IngenPlugin {
 static Lib::Graphs
 find_graphs(const URI& manifest_uri)
 {
-	Sord::World world;
+	serd::World world;
 	Parser      parser;
 
 	const std::set<Parser::ResourceRecord> resources = parser.find_resources(
-		world,
 		manifest_uri,
 		URI(INGEN__Graph));
 
@@ -512,11 +511,8 @@ ingen_instantiate(const LV2_Descriptor*    descriptor,
 
 	set_bundle_path(bundle_path);
 	const std::string manifest_path = ingen::bundle_file_path("manifest.ttl");
-	SerdNode          manifest_node = serd_node_new_file_uri(
-		(const uint8_t*)manifest_path.c_str(), nullptr, nullptr, true);
-
-	Lib::Graphs graphs = find_graphs(URI((const char*)manifest_node.buf));
-	serd_node_free(&manifest_node);
+	serd::Node        manifest_node = serd::make_file_uri(manifest_path);
+	Lib::Graphs       graphs        = find_graphs(URI(manifest_node));
 
 	const LV2Graph* graph = nullptr;
 	for (const auto& g : graphs) {
@@ -823,13 +819,9 @@ LV2Graph::LV2Graph(Parser::ResourceRecord record)
 Lib::Lib(const char* bundle_path)
 {
 	ingen::set_bundle_path(bundle_path);
-	const std::string manifest_path = ingen::bundle_file_path("manifest.ttl");
-	SerdNode          manifest_node = serd_node_new_file_uri(
-		(const uint8_t*)manifest_path.c_str(), nullptr, nullptr, true);
-
-	graphs = find_graphs(URI((const char*)manifest_node.buf));
 
-	serd_node_free(&manifest_node);
+	const auto manifest_path = ingen::bundle_file_path("manifest.ttl");
+	graphs = find_graphs(serd::make_file_uri(manifest_path.string()));
 }
 
 static void
diff --git a/src/server/wscript b/src/server/wscript
index 00588915..137b85c9 100644
--- a/src/server/wscript
+++ b/src/server/wscript
@@ -53,7 +53,7 @@ def build(bld):
             mix.cpp
     '''
 
-    core_libs = 'LV2 LILV RAUL SERD SORD SRATOM'
+    core_libs = 'LV2 LILV RAUL SERD SRATOM'
 
     bld(features        = 'cxx cxxshlib',
         source          = core_source,
diff --git a/src/wscript b/src/wscript
index 72c7d48c..39789778 100644
--- a/src/wscript
+++ b/src/wscript
@@ -41,7 +41,7 @@ def build(bld):
         vnum            = bld.env.INGEN_VERSION,
         install_path    = '${LIBDIR}',
         lib             = lib,
-        uselib          = 'LV2 LILV RAUL SERD SORD SRATOM',
+        uselib          = 'LV2 LILV RAUL SERD SRATOM',
         cxxflags        = (['-fvisibility=hidden'] +
                            bld.env.PTHREAD_CFLAGS + bld.env.INGEN_TEST_CXXFLAGS),
         linkflags       = bld.env.PTHREAD_LINKFLAGS + bld.env.INGEN_TEST_LINKFLAGS)
diff --git a/tests/empty.ingen/main.ttl b/tests/empty.ingen/main.ttl
index 8b60b3aa..4a0925c3 100644
--- a/tests/empty.ingen/main.ttl
+++ b/tests/empty.ingen/main.ttl
@@ -46,4 +46,3 @@
 	lv2:symbol "notify" ;
 	a atom:AtomPort ,
 		lv2:OutputPort .
-
diff --git a/tests/ingen_test.cpp b/tests/ingen_test.cpp
index 476fab64..96117e8f 100644
--- a/tests/ingen_test.cpp
+++ b/tests/ingen_test.cpp
@@ -15,6 +15,7 @@
 */
 
 #include "TestClient.hpp"
+
 #include "ingen_config.h"
 
 #include "ingen/Atom.hpp"
@@ -37,15 +38,15 @@
 #include "ingen/runtime_paths.hpp"
 #include "ingen/types.hpp"
 #include "raul/Path.hpp"
-#include "serd/serd.h"
-#include "sord/sordmm.hpp"
-#include "sratom/sratom.h"
+#include "serd/serd.hpp"
+#include "sratom/sratom.hpp"
 
 #include <chrono>
 #include <cstdint>
 #include <cstdlib>
 #include <iostream>
 #include <string>
+#include <string>
 #include <utility>
 
 using namespace std;
@@ -124,9 +125,8 @@ main(int argc, char** argv)
 
 	// Read commands
 
-	AtomForge forge(world->uri_map().urid_map_feature()->urid_map);
-
-	sratom_set_object_mode(&forge.sratom(), SRATOM_OBJECT_MODE_BLANK_SUBJECT);
+	AtomForge forge(world->rdf_world(),
+	                world->uri_map().urid_map_feature()->urid_map);
 
 	// AtomReader to read commands from a file and send them to engine
 	AtomReader atom_reader(world->uri_map(),
@@ -140,47 +140,46 @@ main(int argc, char** argv)
 	world->interface()->set_respondee(client);
 	world->engine()->register_client(client);
 
-	SerdURI cmds_base;
-	SerdNode cmds_file_uri = serd_node_new_file_uri(
-		(const uint8_t*)run_path.c_str(),
-		nullptr, &cmds_base, true);
-	Sord::Model* cmds = new Sord::Model(*world->rdf_world(),
-	                                    (const char*)cmds_file_uri.buf);
-	SerdEnv* env = serd_env_new(&cmds_file_uri);
-	cmds->load_file(env, SERD_TURTLE, run_path);
-	Sord::Node nil;
+	serd::Node  run_uri = serd::make_file_uri(run_path.string());
+	serd::Model cmds(world->rdf_world(),
+	                 serd::ModelFlag::index_SPO | serd::ModelFlag::index_OPS);
+
+	serd::Env      env(run_uri);
+	serd::Inserter inserter(cmds, env);
+	serd::Reader   reader(
+		world->rdf_world(), serd::Syntax::Turtle, {}, inserter.sink(), 4096);
+
+	reader.start_file(run_path.string());
+	reader.read_document();
+	reader.finish();
+
 	int n_events = 0;
 	for (;; ++n_events) {
-		std::string subject_str = fmt("msg%1%", n_events);
-		Sord::URI subject(*world->rdf_world(), subject_str,
-		                  (const char*)cmds_file_uri.buf);
-		Sord::Iter iter = cmds->find(subject, nil, nil);
-		if (iter.end()) {
+		const auto subject = serd::make_resolved_uri(
+			std::string("msg") + std::to_string(n_events), run_uri);
+		if (!cmds.ask(subject, {}, {})) {
 			break;
 		}
 
-		forge.clear();
-		forge.read(*world->rdf_world(), cmds->c_obj(), subject.c_obj());
+		auto atom = forge.read(cmds, subject);
 
 #if 0
-		const LV2_Atom* atom = forge.atom();
-		cerr << "READ " << atom->size << " BYTES" << endl;
-		cerr << sratom_to_turtle(
-			sratom,
-			&world->uri_map().urid_unmap_feature()->urid_unmap,
-			(const char*)cmds_file_uri.buf,
-			nullptr, nullptr, atom->type, atom->size, LV2_ATOM_BODY(atom)) << endl;
+		sratom::Streamer streamer{
+		        world->rdf_world(),
+		        world->uri_map().urid_map_feature()->urid_map,
+		        world->uri_map().urid_unmap_feature()->urid_unmap};
+
+		auto str = streamer.to_string(env, *atom);
+		cerr << "Read " << atom->size << " bytes:\n" << str << endl;
 #endif
 
-		if (!atom_reader.write(forge.atom(), n_events + 1)) {
+		if (!atom_reader.write(atom, n_events + 1)) {
 			return EXIT_FAILURE;
 		}
 
 		world->engine()->flush_events(std::chrono::milliseconds(20));
 	}
 
-	delete cmds;
-
 	// Save resulting graph
 	auto              r        = world->store()->find(Raul::Path("/"));
 	const std::string base     = run_path.stem();
@@ -212,9 +211,6 @@ main(int argc, char** argv)
 	const FilePath    redo_path = filesystem::current_path() / redo_name;
 	world->serialiser()->write_bundle(r->second, URI(redo_path));
 
-	serd_env_free(env);
-	serd_node_free(&cmds_file_uri);
-
 	// Shut down
 	world->engine()->deactivate();
 
diff --git a/tests/tst_FilePath.cpp b/tests/tst_FilePath.cpp
index 768371fe..68f734f0 100644
--- a/tests/tst_FilePath.cpp
+++ b/tests/tst_FilePath.cpp
@@ -18,8 +18,7 @@
 
 #include "ingen/FilePath.hpp"
 #include "ingen/fmt.hpp"
-
-#include <boost/utility/string_view.hpp>
+#include "serd/serd.hpp"
 
 #include <string>
 
@@ -91,7 +90,7 @@ main(int, char**)
 	path += 'r';
 	EXPECT_EQ(path, "/a/bar/car");
 
-	path += boost::string_view("/d");
+	path += serd::StringView("/d");
 	EXPECT_EQ(path, "/a/bar/car/d");
 
 	const FilePath apple("apple");
diff --git a/wscript b/wscript
index 8ce958ef..7076f330 100644
--- a/wscript
+++ b/wscript
@@ -6,7 +6,7 @@ from waflib import Logs, Options, Utils
 from waflib.extras import autowaf
 
 # Package version
-INGEN_VERSION       = '0.5.1'
+INGEN_VERSION       = '1.0.0'
 INGEN_MAJOR_VERSION = '0'
 
 # Mandatory waf variables
@@ -67,10 +67,9 @@ def configure(conf):
     conf.check_pkg('lv2 >= 1.16.0', uselib_store='LV2')
     conf.check_pkg('lilv-0 >= 0.21.5', uselib_store='LILV')
     conf.check_pkg('suil-0 >= 0.8.7', uselib_store='SUIL')
-    conf.check_pkg('sratom-0 >= 0.4.6', uselib_store='SRATOM')
+    conf.check_pkg('sratom-1 >= 1.0.0', uselib_store='SRATOM')
     conf.check_pkg('raul-1 >= 1.0.0', uselib_store='RAUL')
-    conf.check_pkg('serd-0 >= 0.30.3', uselib_store='SERD', mandatory=False)
-    conf.check_pkg('sord-0 >= 0.12.0', uselib_store='SORD', mandatory=False)
+    conf.check_pkg('serd-1 >= 1.0.0', uselib_store='SERD', mandatory=False)
     conf.check_pkg('portaudio-2.0', uselib_store='PORTAUDIO', mandatory=False)
     conf.check_pkg('sigc++-2.0', uselib_store='SIGCPP', mandatory=False)
 
@@ -91,7 +90,6 @@ def configure(conf):
                         defines     = '_GNU_SOURCE=1',
                         define_name = 'HAVE_VASPRINTF',
                         mandatory   = False)
-
     conf.check(define_name = 'HAVE_LIBDL',
                lib         = 'dl',
                mandatory   = False)
@@ -216,7 +214,7 @@ def build(bld):
         target       = 'ingen',
         includes     = ['.'],
         use          = 'libingen',
-        uselib       = 'SERD SORD SRATOM RAUL LILV LV2',
+        uselib       = 'SERD SRATOM RAUL LILV LV2',
         install_path = '${BINDIR}')
 
     # Test program
@@ -227,7 +225,7 @@ def build(bld):
                 target       = 'tests/%s' % i,
                 includes     = ['.'],
                 use          = 'libingen',
-                uselib       = 'SERD SORD SRATOM RAUL LILV LV2',
+                uselib       = 'SERD SRATOM RAUL LILV LV2',
                 install_path = '',
                 cxxflags     = bld.env.INGEN_TEST_CXXFLAGS,
                 linkflags    = bld.env.INGEN_TEST_LINKFLAGS)
-- 
cgit v1.2.1