diff --git a/src/canvas.js b/src/canvas.js
index 99294e5c6c49bac839c86b897330a50db12f911d..7ff97f7615b0d0054a02c3f9fdb5a23534acaad4 100644
--- a/src/canvas.js
+++ b/src/canvas.js
@@ -20,6 +20,58 @@ let stroke_colour = "blue"
 export const MIN_STROKE_RADIUS = 0.1
 export const MAX_STROKE_RADIUS = 3.9
 
+const MAX_POINT_DISTANCE = 5
+const MAX_PRESSURE_DELTA = 0.05
+
+// Interpolate a path so that:
+// - The distance between two adjacent points is capped at MAX_POINT_DISTANCE.
+// - The pressure delta between two adjacent points is capped at
+//   MAX_PRESSURE_DELTA
+// If paths are too choppy, try decreasing these constants.
+const smoothPath = ([...path]) => {
+  // Apply MAX_POINT_DISTANCE.
+  for (let i = 1; i < path.length; i++) {
+    const dx = path[i][0] - path[i - 1][0]
+    const dy = path[i][1] - path[i - 1][1]
+    const dw = path[i][2] - path[i - 1][2]
+    const distance = Math.hypot(dx, dy)
+    const segmentsToSplit = Math.ceil(distance / MAX_POINT_DISTANCE)
+    const newPoints = []
+    for (let j = 1; j < segmentsToSplit; j++) {
+      newPoints.push([
+        path[i - 1][0] + (dx / segmentsToSplit) * j,
+        path[i - 1][1] + (dy / segmentsToSplit) * j,
+        path[i - 1][2] + (dw / segmentsToSplit) * j,
+        path[i - 1][3],
+        path[i - 1][4],
+      ])
+    }
+    path.splice(i, 0, ...newPoints)
+    i += newPoints.length
+  }
+
+  // Apply MAX_PRESSURE_DELTA.
+  for (let i = 1; i < path.length; i++) {
+    const dx = path[i][0] - path[i - 1][0]
+    const dy = path[i][1] - path[i - 1][1]
+    const dw = path[i][2] - path[i - 1][2]
+    const segmentsToSplit = Math.ceil(dw / MAX_PRESSURE_DELTA)
+    const newPoints = []
+    for (let j = 1; j < segmentsToSplit; j++) {
+      newPoints.push([
+        path[i - 1][0] + (dx / segmentsToSplit) * j,
+        path[i - 1][1] + (dy / segmentsToSplit) * j,
+        path[i - 1][2] + (dw / segmentsToSplit) * j,
+        path[i - 1][3],
+        path[i - 1][4],
+      ])
+    }
+    path.splice(i, 0, ...newPoints)
+    i += newPoints.length
+  }
+  return path
+}
+
 const getStrokeRadius = (pressure) => {
   return MIN_STROKE_RADIUS + pressure * (MAX_STROKE_RADIUS - MIN_STROKE_RADIUS)
 }
@@ -82,7 +134,7 @@ export const renderPath = (id, points) => {
       pathGroupElem.appendChild(circleElem)
     } else {
       // Further split up segments based on thickness.
-      const subpath_ = subpath.slice()
+      const subpath_ = smoothPath(subpath)
       let w = subpath_[0][2]
       for (let i = 1; i < subpath_.length; i++) {
         if (subpath_[i][2] != w) {