diff --git a/pd/doc/5.reference/ab-help.pd b/pd/doc/5.reference/ab-help.pd
new file mode 100755
index 0000000000000000000000000000000000000000..4e4747a0f6eb4fe97f21158210ae26aee9a6c44f
--- /dev/null
+++ b/pd/doc/5.reference/ab-help.pd
@@ -0,0 +1,136 @@
+#N canvas 717 240 480 339 12;
+#X abframe 1;
+#N canvas 870 153 488 334 12;
+#X obj 27 32 inlet;
+#X obj 27 61 t f b b;
+#X obj 27 140 expr 2*$f1*$f2+$f3;
+#X obj 27 169 outlet;
+#X obj 88 104 f \$1;
+#X obj 150 104 f \$2;
+#X obj 252 225 abinfo;
+#X obj 252 254 print abinfo;
+#X msg 219 188 name;
+#X msg 264 188 instances;
+#X msg 344 188 within;
+#X obj 27 233 bng 15 250 50 0 empty empty empty 17 7 0 10 #fcfcfc #000000
+#000000;
+#X obj 27 257 f \$0;
+#X obj 27 286 print;
+#X text 94 61 private abstractions can have arguments...;
+#X text 26 202 ... and have their own \$0;
+#X obj 115 233 bng 15 250 50 0 empty empty empty 17 7 0 10 #fcfcfc
+#000000 #000000;
+#X obj 115 257 ab bar;
+#X floatatom 115 286 5 0 0 0 - - -, f 5;
+#X text 218 98 abinfo yields the name of the abstraction \, the number
+of its instances \, and the names of other private abstractions they
+are contained in (if any), f 35;
+#X connect 0 0 1 0;
+#X connect 1 0 2 0;
+#X connect 1 1 4 0;
+#X connect 1 2 5 0;
+#X connect 2 0 3 0;
+#X connect 4 0 2 1;
+#X connect 5 0 2 2;
+#X connect 6 0 7 0;
+#X connect 8 0 6 0;
+#X connect 9 0 6 0;
+#X connect 10 0 6 0;
+#X connect 11 0 12 0;
+#X connect 12 0 13 0;
+#X connect 16 0 17 0;
+#X connect 17 0 18 0;
+#X abpush foo;
+#N canvas 869 280 453 305 12;
+#X obj 23 21 bng 15 250 50 0 empty empty empty 17 7 0 10 #fcfcfc #000000
+#000000;
+#X obj 23 45 ab bar;
+#X floatatom 23 74 5 0 0 0 - - -, f 5;
+#X text 81 45 <-- open "bar" and try abinfo "within"!;
+#X obj 23 203 ab other_foobar;
+#X text 20 233 Normally \, private abstractions such as [ab bar] above
+have global scope \, so they are visible through the entire root patch
+\, including all its subpatches and private abstractions., f 57;
+#X text 20 109 Note that @foobar has local scope (indicated by the
+"@" prefix) \, which includes all subpatches \, but doesn't cross (private)
+abstraction boundaries. Thus the @foobar inside the other_foobar abstraction
+below is a different @foobar than this one.;
+#X connect 0 0 1 0;
+#X connect 1 0 2 0;
+#X abpush @foobar;
+#N canvas 1023 266 454 304 12;
+#X obj 44 54 f \$0;
+#X obj 44 83 outlet;
+#X obj 44 25 inlet;
+#X obj 147 63 abinfo;
+#X obj 147 92 print abinfo;
+#X msg 114 26 name;
+#X msg 159 26 instances;
+#X msg 239 26 within;
+#X connect 0 0 1 0;
+#X connect 2 0 0 0;
+#X connect 3 0 4 0;
+#X connect 5 0 3 0;
+#X connect 6 0 3 0;
+#X connect 7 0 3 0;
+#X abpush bar foo @foobar;
+#N canvas 788 330 452 303 12;
+#X obj 22 24 ab @foobar;
+#X text 112 24 <-- click here;
+#X abpush other_foobar @foobar;
+#N canvas 950 315 452 304 12;
+#X text 16 31 Note that this instance of @foobar is different from
+the one in the main patch!;
+#X abpush other_foobar#@foobar other_foobar;
+#X abframe 0;
+#X floatatom 31 12 5 0 0 0 - - -, f 5;
+#X floatatom 31 70 5 0 0 0 - - -, f 5;
+#X obj 31 41 ab foo 3 2;
+#X obj 267 89 bng 15 250 50 0 empty empty empty 17 7 0 10 #fcfcfc #000000
+#000000;
+#X obj 267 113 abdefs;
+#X obj 267 142 print defs;
+#X floatatom 30 159 5 0 0 0 - - -, f 5;
+#X floatatom 30 217 5 0 0 0 - - -, f 5;
+#X floatatom 128 71 5 0 0 0 - - -, f 5;
+#X text 127 28 ... and cloned, f 10;
+#X obj 128 99 pack f f;
+#X msg 128 128 \$2 \$1;
+#X obj 128 187 unpack f f;
+#X floatatom 128 216 5 0 0 0 - - -, f 5;
+#X floatatom 195 216 5 0 0 0 - - -, f 5;
+#X text 29 117 ab's can be copied..., f 11;
+#X obj 30 188 ab foo 3 5;
+#X text 265 13 abdefs prints the list of all private abstractions contained
+in a patch \, along with their instance counts, f 27;
+#X text 22 266 CAVEAT: Private abstractions are identified by their
+name. In contrast to one-offs \, editing their name may create a new
+(and empty) abstraction!;
+#X obj 128 158 abclone foo 4 5;
+#X obj 181 73 hradio 15 0 0 4 empty empty empty 0 -8 0 10 #fcfcfc #000000
+#000000 0;
+#X obj 267 230 ab @foobar;
+#N canvas 432 306 417 220 META 0;
+#X text 13 47 LIBRARY internal;
+#X text 13 1 KEYWORDS abstraction private;
+#X text 13 24 DESCRIPTION creates a private abstraction;
+#X text 13 70 AUTHOR Guillem Bartrina;
+#X text 13 116 RELEASE_DATE 2020;
+#X text 13 139 HELP_PATCH_AUTHORS Albert Gräf;
+#X text 13 93 WEBSITE https://agraef.github.io/purr-data/;
+#X restore 408 306 pd META;
+#X text 265 174 a "local" abstraction which illustrates local scope
+as well as abinfo "within"., f 27;
+#X connect 0 0 2 0;
+#X connect 2 0 1 0;
+#X connect 3 0 4 0;
+#X connect 4 0 5 0;
+#X connect 6 0 16 0;
+#X connect 8 0 10 0;
+#X connect 10 0 11 0;
+#X connect 11 0 19 0;
+#X connect 12 0 13 0;
+#X connect 12 1 14 0;
+#X connect 16 0 7 0;
+#X connect 19 0 12 0;
+#X connect 20 0 10 1;
diff --git a/pd/nw/dialog_abstractions.html b/pd/nw/dialog_abstractions.html
new file mode 100644
index 0000000000000000000000000000000000000000..cf74b55a5702240f202c1f3ebf22ef1b973b04b3
--- /dev/null
+++ b/pd/nw/dialog_abstractions.html
@@ -0,0 +1,213 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link id="page_style" rel="stylesheet" type="text/css" href="css/default.css">
+  </head>
+  <body class="dialog_body prefs_body" style="overflow: hidden;">
+    <div class="container noselect prefs_container">
+      <table id="titlebar">
+        <tr>
+          <td style="width: 100%;">
+            <div id="titlebar_title">Abstractions</div>
+          </td>
+          <td id="titlebar_buttons_td">
+            <div class="titlebar_buttons">
+              <div id="titlebar_close_button" onclick="cancel();">&#215</div>
+            </div>
+          </td>
+        </tr>
+      </table>
+      <form>
+        <fieldset style="width: 90%">
+          <legend data-i18n="abstractions.private"></legend>
+            <strong data-i18n="abstractions.global"></strong>
+            <hr>
+            <table style="width: 90%; table-layout: fixed;">
+            </table>
+            <br>
+            <strong data-i18n="abstractions.local"></strong>
+            <hr>
+            <table style="width: 90%; table-layout: fixed;">
+            </table>
+            <br>
+            <button id="selectall_button" type="button" onClick="selectall()" data-i18n="[title]abstractions.selectall_tt" style="float: right;">
+              <span data-i18n="abstractions.selectall"></span>
+            </button>
+        </fieldset>
+        <!--
+        <hr>
+        <fieldset style="width: 90%; border: 2px solid grey">
+          <legend data-i18n="abstractions.filebased"></legend>
+          <i>NOT IMPLEMENTED</i>
+          <table style="width: 90%; table-layout: fixed;">
+          </table>
+        </fieldset>
+        <hr>
+        -->
+        <div class="submit_buttons">
+          <button id="delete_button" type="button" onClick="deleteselected()" data-i18n="[title]abstractions.delete_tt">
+            <span data-i18n="abstractions.delete"></span>
+          </button>
+          <button type="button" onClick="cancel()" data-i18n="[title]abstractions.close_tt">
+            <span data-i18n="abstractions.close"></span>
+          </button>
+        </div>
+      </form>
+    </div>
+  <script>
+"use strict";
+var gui = require("nw.gui");
+var pdgui = require("./pdgui.js");
+
+// For translations
+var l = pdgui.get_local_string;
+
+pdgui.skin.apply(window);
+
+var pd_object_callback;
+var canvas;
+var deletequeue;
+
+function selectall() {
+    var checkboxes = document.querySelectorAll('input[type="checkbox"]:not(:checked)');
+    for(var i = 0, n = checkboxes.length; i < n; i++) {
+      if(!checkboxes[i].checked)
+      {
+        checkboxes[i].click();
+      }
+    }
+}
+
+function deleteselected() {
+    pdgui.pdsend(pd_object_callback, "delabstractions", Array.from(deletequeue).join(" "));
+    cancel();
+}
+
+function cancel() {
+    pdgui.pdsend(pd_object_callback, "cancel");
+}
+
+function populate_form(attrs) {
+    var filebased_abs = attrs.filebased_abs,
+        private_abs = attrs.private_abs,
+        i,
+        zero = 0;
+    canvas = attrs.canvas;
+    deletequeue = new Set();
+    var tables = document.querySelectorAll("table"),
+        privateglobal_table = tables[1],
+        privatelocal_table = tables[2];
+        //filebased_table = tables[2]
+
+    /*
+    for(i = 0; i < filebased_abs.length; i += 2)
+    {
+      var row = document.createElement("tr");
+          cell1 = document.createElement("td"),
+          cell2 = document.createElement("td"),
+          cell3 = document.createElement("td");
+      
+      cell1.textContent = filebased_abs[i];
+      cell1.style.setProperty("width", "70%");
+      cell2.textContent = filebased_abs[i+1];
+      cell2.style.setProperty("width", "20%");
+      row.appendChild(cell1);
+      row.appendChild(cell2);
+      row.appendChild(cell3);
+      filebased_table.appendChild(row);
+    }
+    */
+
+    for(i = 0; i < private_abs.length; i += 2)
+    {
+      var row = document.createElement("tr"),
+          cell1 = document.createElement("td"),
+          cell2 = document.createElement("td"),
+          cell3 = document.createElement("td");
+      
+      cell1.textContent = private_abs[i];
+      cell1.style.setProperty("width", "70%");
+      cell2.textContent = private_abs[i+1];
+      cell2.style.setProperty("width", "20%");
+
+      if(private_abs[i+1] === 0)
+      {
+        let input_elem = document.createElement("input"),
+            j = i;
+        input_elem.type = "checkbox";
+        input_elem.style.setProperty("max-height", "10px");
+        input_elem.style.setProperty("top", "0px");
+        input_elem.onchange = function() {
+            if(input_elem.checked) {
+                deletequeue.add(private_abs[j]);
+            } else {
+                deletequeue.delete(private_abs[j]);
+            }
+            get_elem("delete_button").disabled = (deletequeue.size === 0);
+        };
+        cell3.appendChild(input_elem);
+        zero++;
+      }
+
+      row.appendChild(cell1);
+      row.appendChild(cell2);
+      row.appendChild(cell3);
+
+      if(private_abs[i][0] === '@') {
+          privatelocal_table.appendChild(row);
+      } else {
+          privateglobal_table.appendChild(row);
+      }
+    }
+
+    get_elem("selectall_button").disabled = (zero === 0);
+    get_elem("delete_button").disabled = true;
+}
+
+// This gets called from the nw_create_window function in index.html
+// It provides us with our window id from the C side.  Once we have it
+// we can create the menu and register event callbacks
+function register_window_id(gfxstub, attrs) {
+    pd_object_callback = gfxstub;
+    add_events(gfxstub);
+    // not sure that we need this for properties windows
+    //pdgui.canvas_map(gfxstub);
+    translate_form();
+    populate_form(attrs);
+    // We don't turn on rendering of the "container" div until
+    // We've finished displaying all the spans and populating the
+    // labels and form elements.  That makes it more efficient and
+    // snappier, at least on older machines.
+    document.getElementsByClassName("container")[0].style.setProperty("display", "inline");
+
+    gui.Window.get().setResizable(false);
+}
+
+function get_elem(name) {
+    return document.getElementById(name);
+}
+
+// Stop-gap translator
+function translate_form() {
+    var elements = document.querySelectorAll("[data-i18n]"),
+        data,
+        i;
+    for (i = 0; i < elements.length; i++) {
+        data = elements[i].dataset.i18n;
+        if (data.slice(0, 7) === "[title]") {
+            elements[i].title = l(data.slice(7));
+        } else {
+            elements[i].textContent = l(data);
+        }
+    }
+}
+
+function add_events(name) {
+    gui.Window.get().on("close", function () {
+        cancel();
+    });
+    pdgui.dialog_bindings(name);
+}
+  </script>
+  </body>
+</html>
diff --git a/pd/nw/dialog_external.html b/pd/nw/dialog_external.html
index 28e4d3f9e155b7f688c0191183be1b623207633c..a0305f715ce6de93a52f5336dd25fe47b4b3aa84 100644
--- a/pd/nw/dialog_external.html
+++ b/pd/nw/dialog_external.html
@@ -149,7 +149,8 @@ function parse_attrs(attrs) {
             if (token.length > 1) {
                 elem.type = token[token.length - 1];
                 if (elem.type !== "symbol" &&
-                    elem.type !== "toggle") {
+                    elem.type !== "toggle" &&
+                    elem.type !== "hidden") {
                     // no suffix defaults to "number"
                     elem.type = "number";
                 } else {
@@ -218,6 +219,7 @@ function get_input_type(t) {
     return t === "symbol" ? "text" :
            t === "number" ? "text" : 
            t === "toggle" ? "checkbox":
+           t === "hidden" ? "hidden":
                             "text";
 }
 
diff --git a/pd/nw/locales/de/translation.json b/pd/nw/locales/de/translation.json
index 771b7727414d157de9e587e90fc8461022b63403..73ccfc8b2cad4fb9d7911edb1f2ac4d748a75394 100644
--- a/pd/nw/locales/de/translation.json
+++ b/pd/nw/locales/de/translation.json
@@ -239,6 +239,8 @@
     "visible_ancestor_tt": "Zeige den nächsten aktuell sichtbaren Vorgänger des aktuellen Fensters",
     "pdwin": "Pd-Fenster",
     "pdwin_tt": "Zeige das Pd-Hauptfenster",
+    "abstractions": "Abstraktionen",
+    "abstractions_tt": "Abstraktionen abfragen und verwalten",
 
     "media": "Medien",
 
@@ -506,5 +508,17 @@
     "building_index": "Erstelle Index...",
     "no_results": "Keine Resultate gefunden.",
     "search_placeholder": "Suche Pd-Doku"
+  },
+  "abstractions": {
+    "filebased": "Datei-basierte Abstraktionen",
+    "private": "Private Abstraktionen",
+    "global": "Globaler Geltungsbereich",
+    "local": "Lokaler Geltungsbereich",
+    "selectall": "Alles auswählen",
+    "selectall_tt": "Wähle alle uninstanziierten Definitionen aus",
+    "delete": "Löschen",
+    "delete_tt": "Löscht alle ausgewählten Definitionen",
+    "close": "Schließen",
+    "close_tt": "Schließt das Dialog-Fenster"
   }
 }
diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json
index 9883efa2ad6b5c00c31197fe706471397d2b987b..ee65f1aa393cb47133587fdb9a875daddab2284d 100644
--- a/pd/nw/locales/en/translation.json
+++ b/pd/nw/locales/en/translation.json
@@ -239,6 +239,8 @@
     "visible_ancestor_tt": "give focus to the closest ancestor of this window that is currently visible",
     "pdwin": "Pd Window",
     "pdwin_tt": "Give focus to the main Pd window",
+    "abstractions": "Abstractions",
+    "abstractions_tt": "Query and manage abstractions",
 
     "media": "Media",
 
@@ -506,5 +508,17 @@
     "building_index": "Building index...",
     "no_results": "No results found.",
     "search_placeholder": "Search Pd Docs"
+  },
+  "abstractions": {
+    "filebased": "File-based abstractions",
+    "private": "Private abstractions",
+    "global": "Global scope",
+    "local": "Local scope",
+    "selectall": "Select all",
+    "selectall_tt": "Select all definitions with zero instances",
+    "delete": "Delete",
+    "delete_tt": "Delete all selected definitions",
+    "close": "Close",
+    "close_tt": "Close the dialog window"
   }
 }
diff --git a/pd/nw/locales/fr/translation.json b/pd/nw/locales/fr/translation.json
index 8783b6b71d0c58f93416c5f5d5e1b8a0b69aa7b7..ccdc3bc4b7524cabb3a5cd4a4a2b8ac2f9b74667 100644
--- a/pd/nw/locales/fr/translation.json
+++ b/pd/nw/locales/fr/translation.json
@@ -239,6 +239,8 @@
     "visible_ancestor_tt": "Donner le focus à l'ancêtre le plus proche de cette fenêtre qui est actuellement visible",
     "pdwin":    "Fenêtre Console Pd",
     "pdwin_tt": "Donner le focus à la fenêtre principale de Pd",
+    "abstractions": "Abstractions",
+    "abstractions_tt": "Interroger et gérer les abstractions",
 
     "media": "Média",
 
@@ -281,7 +283,7 @@
   "canvas": {
     "paste_clipboard_prompt": "Attention: vous êtes sur le point de coller du 'code Pd' provenant de l'extérieur de Pd. Voulez-vous continuer ?",
     "save_dialog": {
-      "prompt": "Voulez-vous enregistrer les modifications apportées à ce patch ?",
+      "prompt": "Voulez-vous enregistrer les modifications apportées à",
       "yes":    "Oui",
       "yes_tt": "Enregistrer les modifications dans le fichier avant de fermer le patch ?",
       "no":    "Non",
@@ -506,5 +508,17 @@
     "building_index": "Construction de l'index...",
     "no_results": "Aucun résultat trouvé !",
     "search_placeholder": "Chercher dans les Docs Pd"
+  },
+  "abstractions": {
+    "filebased": "Abstractions basées sur des fichiers",
+    "private": "Abstractions privées",
+    "global": "Portée globale",
+    "local": "Portée locale",
+    "selectall": "Tout sélectionner",
+    "selectall_tt": "Sélectionner toutes les définitions avec zéro instance",
+    "delete": "Supprimer",
+    "delete_tt": "Supprimer toutes les définitions sélectionnées",
+    "close": "Fermer",
+    "close_tt": "Fermer la fenêtre de dialogue"
   }
 }
diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index daaf82b91da5dac0e37229e03dff117ed96a4ad7..12ad04b38017af8c17751e270d5393f87976a646 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -2029,6 +2029,12 @@ function nw_create_patch_window_menus(gui, w, name) {
             pdgui.raise_pd_window();
         }
     });
+    minit(m.win.abstractions, {
+        enabled: true,
+        click: function () {
+            pdgui.pdsend(name, "getabstractions");
+        }
+    });
 
     // Media menu
     minit(m.media.audio_on, {
diff --git a/pd/nw/pd_menus.js b/pd/nw/pd_menus.js
index a9dd9e69d189c98ec4f18a408af1a1c9f66b4489..0e3646546aef68d406085f90b7e36a24411df77a 100644
--- a/pd/nw/pd_menus.js
+++ b/pd/nw/pd_menus.js
@@ -579,6 +579,11 @@ function create_menu(gui, type) {
             key: shortcuts.menu.pdwin.key,
             modifiers: shortcuts.menu.pdwin.modifiers
         }));
+        winman_menu.append(new gui.MenuItem({ type: "separator" }));
+        winman_menu.append(m.win.abstractions = new gui.MenuItem({
+            label: l("menu.abstractions"),
+            tooltip: l("menu.abstractions_tt")
+        }));
     }
 
     // Media menu
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index ea9189f2e774546b00056fb527a3f45bf3bbfc27..12cd6347ba633b474ecfaede78316c4a57f82bd0 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -6026,6 +6026,13 @@ function gui_external_dialog(did, external_name, attr_array) {
         });
 }
 
+function gui_abstractions_dialog(cid, gfxstub, filebased_abs, private_abs) {
+    var attrs = { canvas: cid, filebased_abs: filebased_abs,
+                    private_abs: private_abs };
+    dialogwin[gfxstub] = create_window(gfxstub, "abstractions", 300, 
+        Math.min(600, (private_abs.length*10 + 190+(nw_os_is_osx?20:0))), 0, 0, attrs);
+}
+
 // Global settings
 
 function gui_pd_dsp(state) {
diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c
index fa1447ff8906bca79c2a6df747f33c778c1e3d96..9ae490767fb08c0927d0aef3666729a0db965acd 100644
--- a/pd/src/g_canvas.c
+++ b/pd/src/g_canvas.c
@@ -95,6 +95,8 @@ static t_symbol *canvas_newdirectory = &s_;
 static int canvas_newargc;
 static t_atom *canvas_newargv;
 
+static t_ab_definition *canvas_newabsource = 0;
+
     /* maintain the list of visible toplevels for the GUI's "windows" menu */
 void canvas_updatewindowlist( void)
 {
@@ -157,6 +159,12 @@ void glob_setfilename(void *dummy, t_symbol *filesym, t_symbol *dirsym)
     canvas_newdirectory = dirsym;
 }
 
+/* set the source for the next canvas, it will be an ab instance */
+void canvas_setabsource(t_ab_definition *abdef)
+{
+    canvas_newabsource = abdef;
+}
+
 t_canvas *canvas_getcurrent(void)
 {
     return ((t_canvas *)pd_findbyclass(&s__X, canvas_class));
@@ -231,12 +239,16 @@ t_symbol *canvas_realizedollar(t_canvas *x, t_symbol *s)
 
 t_symbol *canvas_getcurrentdir(void)
 {
-    t_canvasenvironment *e = canvas_getenv(canvas_getcurrent());
-    return (e->ce_dir);
+    return (canvas_getdir(canvas_getcurrent()));
 }
 
+/* ** refactored function, check for errors */
 t_symbol *canvas_getdir(t_canvas *x)
 {
+    x = canvas_getrootfor(x);
+    /* in the case the root is an ab instance, we borrow the
+        dir from the main root canvas (where the definition is stored) */
+    if(x->gl_isab) x = x->gl_absource->ad_owner;
     t_canvasenvironment *e = canvas_getenv(x);
     return (e->ce_dir);
 }
@@ -410,6 +422,7 @@ static int calculate_zoom(t_float zoom_hack)
 }
 
 int canvas_dirty_broadcast_all(t_symbol *name, t_symbol *dir, int mess);
+int canvas_dirty_broadcast_ab(t_canvas *x, t_ab_definition *abdef, int mess);
 
     /* make a new glist.  It will either be a "root" canvas or else
     it appears as a "text" object in another window (canvas_getcurrent() 
@@ -486,6 +499,17 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv)
     }
     else x->gl_env = 0;
 
+    x->gl_abdefs = 0;
+    /* if canvas_newabsource is set means that
+        this canvas is going to be an ab instance */
+    if(canvas_newabsource)
+    {
+        x->gl_isab = 1;
+        x->gl_absource = canvas_newabsource;
+        canvas_newabsource = 0;
+    }
+    else x->gl_isab = 0;
+
     x->gl_subdirties = 0;
     x->gl_dirties = 0;
 
@@ -542,8 +566,11 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv)
         get the number of dirty instances of this same abstraction */
     if(x->gl_env)
     {
-        x->gl_dirties = canvas_dirty_broadcast_all(x->gl_name,
+        if(!x->gl_isab)
+            x->gl_dirties = canvas_dirty_broadcast_all(x->gl_name,
                                 canvas_getdir(x), 0);
+        else
+            x->gl_dirties = canvas_dirty_broadcast_ab(canvas_getrootfor_ab(x), x->gl_absource, 0);
     }
 
     return(x);
@@ -741,6 +768,14 @@ t_symbol *canvas_makebindsym(t_symbol *s)
     return (gensym(buf));
 }
 
+t_symbol *canvas_makebindsym_ab(t_symbol *s)
+{
+    char buf[MAXPDSTRING];
+    snprintf(buf, MAXPDSTRING-1, "ab-%s", s->s_name);
+    buf[MAXPDSTRING-1] = 0;
+    return (gensym(buf));
+}
+
 int garray_getname(t_garray *x, t_symbol **namep);
 
 void canvas_args_to_string(char *namebuf, t_canvas *x)
@@ -822,6 +857,8 @@ void canvas_dirtyclimb(t_canvas *x, int n)
 
 void clone_iterate(t_pd *z, t_canvas_iterator it, void* data);
 int clone_match(t_pd *z, t_symbol *name, t_symbol *dir);
+int clone_isab(t_pd *z);
+int clone_matchab(t_pd *z, t_ab_definition *source);
 
 static void canvas_dirty_common(t_canvas *x, int mess)
 {
@@ -868,7 +905,7 @@ static int canvas_dirty_broadcast(t_canvas *x, t_symbol *name, t_symbol *dir, in
     {
         if(pd_class(&g->g_pd) == canvas_class)
         {
-            if(canvas_isabstraction((t_canvas *)g)
+            if(canvas_isabstraction((t_canvas *)g) && !((t_canvas *)g)->gl_isab
                 && ((t_canvas *)g)->gl_name == name
                 && canvas_getdir((t_canvas *)g) == dir)
             {
@@ -876,7 +913,9 @@ static int canvas_dirty_broadcast(t_canvas *x, t_symbol *name, t_symbol *dir, in
                 canvas_dirty_common((t_canvas *)g, mess);
             }
             else
+            {
                 res += canvas_dirty_broadcast((t_canvas *)g, name, dir, mess);
+            }
         }
         else if(pd_class(&g->g_pd) == clone_class)
         {
@@ -910,6 +949,67 @@ int canvas_dirty_broadcast_all(t_symbol *name, t_symbol *dir, int mess)
         res += canvas_dirty_broadcast(x, name, dir, mess);
     return (res);
 }
+
+/* same but for ab */
+
+typedef struct _dirty_broadcast_ab_data
+{
+    t_ab_definition *abdef;
+    int mess;
+    int *res;
+} t_dirty_broadcast_ab_data;
+
+static void canvas_dirty_deliver_ab_packed(t_canvas *x, t_dirty_broadcast_ab_data *data)
+{
+    *data->res += (x->gl_dirty > 0);
+    canvas_dirty_common(x, data->mess);
+}
+
+static void canvas_dirty_broadcast_ab_packed(t_canvas *x, t_dirty_broadcast_ab_data *data);
+
+int canvas_dirty_broadcast_ab(t_canvas *x, t_ab_definition *abdef, int mess)
+{
+    int res = 0;
+    t_gobj *g;
+    for (g = x->gl_list; g; g = g->g_next)
+    {
+        if(pd_class(&g->g_pd) == canvas_class)
+        {
+            if(canvas_isabstraction((t_canvas *)g) && ((t_canvas *)g)->gl_isab
+                && ((t_canvas *)g)->gl_absource == abdef)
+            {
+                res += (((t_canvas *)g)->gl_dirty > 0);
+                canvas_dirty_common((t_canvas *)g, mess);
+            }
+            else if(!canvas_isabstraction((t_canvas *)g) || ((t_canvas *)g)->gl_isab)
+            {
+                res += canvas_dirty_broadcast_ab((t_canvas *)g, abdef, mess);
+            }
+        }
+        else if(pd_class(&g->g_pd) == clone_class)
+        {
+            int cres = 0;
+            t_dirty_broadcast_ab_data data;
+            data.abdef = abdef; data.mess = mess; data.res = &cres;
+            if(clone_matchab(&g->g_pd, abdef))
+            {
+                clone_iterate(&g->g_pd, canvas_dirty_deliver_ab_packed, &data);
+            }
+            else if(clone_isab(&g->g_pd))
+            {
+                clone_iterate(&g->g_pd, canvas_dirty_broadcast_ab_packed, &data);
+            }
+            res += cres;
+        }
+    }
+    return (res);
+}
+
+static void canvas_dirty_broadcast_ab_packed(t_canvas *x, t_dirty_broadcast_ab_data *data)
+{
+    *data->res = canvas_dirty_broadcast_ab(x, data->abdef, data->mess);
+}
+
     /* mark a glist dirty or clean */
 void canvas_dirty(t_canvas *x, t_floatarg n)
 {
@@ -924,14 +1024,19 @@ void canvas_dirty(t_canvas *x, t_floatarg n)
 
         /* set dirtiness visual markings */
         canvas_dirtyclimb(x2, (unsigned)n);
+
         /* in the case it is an abstraction, we tell all other
             instances that there is eiher one more dirty instance or
             one less dirty instance */
         if(canvas_isabstraction(x2)
             && (x2->gl_owner || x2->gl_isclone))
         {
-            canvas_dirty_broadcast_all(x2->gl_name, canvas_getdir(x2),
-                (x2->gl_dirty ? 1 : -1));
+            if(!x2->gl_isab)
+                canvas_dirty_broadcast_all(x2->gl_name, canvas_getdir(x2),
+                    (x2->gl_dirty ? 1 : -1));
+            else
+                canvas_dirty_broadcast_ab(canvas_getrootfor_ab(x2), x2->gl_absource,
+                    (x2->gl_dirty ? 1 : -1));
         }
     }
 }
@@ -1157,17 +1262,28 @@ int glist_getfont(t_glist *x)
 }
 
 extern void canvas_group_free(t_pd *x);
+static void canvas_deregister_ab(t_canvas *x, t_ab_definition *a);
 
 void canvas_free(t_canvas *x)
 {
     //fprintf(stderr,"canvas_free %zx\n", (t_uint)x);
 
+    /* crude hack. in the case it was a clone instance, it shouldn't have an owner.
+        For ab instances, we have set the owner inside clone_free because we need it
+        in order to deregister the dependencies.
+        here we set it to NULL again to prevent any error in the functions called bellow */
+    t_canvas *aux = x->gl_owner;
+    if(x->gl_isclone) x->gl_owner = 0;
+
     /* in the case it is a dirty abstraction, we tell all other
         instances that there is one less dirty instance */
     if(canvas_isabstraction(x) && x->gl_dirty
         && (x->gl_owner || x->gl_isclone))
     {
-        canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), -1);
+        if(!x->gl_isab)
+            canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), -1);
+        else
+            canvas_dirty_broadcast_ab(canvas_getrootfor_ab(x), x->gl_absource, -1);
     }
 
     t_gobj *y;
@@ -1207,6 +1323,26 @@ void canvas_free(t_canvas *x)
         canvas_takeofflist(x);
     if (x->gl_svg)                   /* for groups, free the data */
         canvas_group_free(x->gl_svg);
+
+    /* freeing an ab instance */
+    if(x->gl_isab)
+    {
+        x->gl_absource->ad_numinstances--;
+        canvas_deregister_ab((x->gl_isclone ? aux : x->gl_owner),
+            x->gl_absource);
+    }
+
+    /* free stored ab definitions */
+    t_ab_definition *d = x->gl_abdefs, *daux;
+    while(d)
+    {
+        daux = d->ad_next;
+        binbuf_free(d->ad_source);
+        freebytes(d->ad_dep, sizeof(t_ab_definition*)*d->ad_numdep);
+        freebytes(d->ad_deprefs, sizeof(int)*d->ad_numdep);
+        freebytes(d, sizeof(t_ab_definition));
+        d = daux;
+    }
 }
 
 /* ----------------- lines ---------- */
@@ -1740,13 +1876,17 @@ static int canvas_should_bind(t_canvas *x)
 
 static void canvas_bind(t_canvas *x)
 {
-    if (canvas_should_bind(x))
+    if (x->gl_isab) /* if it is an ab instance, we bind it to symbol 'ab-<name>' */
+        pd_bind(&x->gl_pd, canvas_makebindsym_ab(x->gl_name));
+    else if (canvas_should_bind(x))
         pd_bind(&x->gl_pd, canvas_makebindsym(x->gl_name));
 }
 
 static void canvas_unbind(t_canvas *x)
 {
-    if (canvas_should_bind(x))
+    if (x->gl_isab)
+        pd_unbind(&x->gl_pd, canvas_makebindsym_ab(x->gl_name));
+    else if (canvas_should_bind(x))
         pd_unbind(&x->gl_pd, canvas_makebindsym(x->gl_name));
 }
 
@@ -2029,11 +2169,531 @@ void canvas_redrawallfortemplatecanvas(t_canvas *x, int action)
     canvas_redrawallfortemplate(0, action);
 }
 
+/* ------------------------------- ab ------------------------ */
+
+static char ab_templatecanvas[] = "#N canvas;\n";
+
+/* create an ab instance from its source */
+static t_pd *do_create_ab(t_ab_definition *abdef, int argc, t_atom *argv)
+{
+    canvas_setargs(argc, argv);
+    int dspstate = canvas_suspend_dsp();
+    glob_setfilename(0, abdef->ad_name, gensym("[ab]"));
+
+    /* set ab source, next canvas is going to be a private abstraction */
+    canvas_setabsource(abdef);
+    binbuf_eval(abdef->ad_source, 0, 0, 0);
+    canvas_initbang((t_canvas *)(s__X.s_thing));
+
+    glob_setfilename(0, &s_, &s_);
+    canvas_resume_dsp(dspstate);
+    canvas_popabstraction((t_canvas *)(s__X.s_thing));
+    canvas_setargs(0, 0);
+
+    /* open the canvas if we are creating it for the first time */
+    canvas_vis((t_canvas *)newest, !glist_amreloadingabstractions
+                                    && !abdef->ad_numinstances
+                                    && binbuf_getnatom(abdef->ad_source) == 3);
+
+    return(newest);
+}
+
+/* get root canvas crossing ab boundaries, where ab definitions are stored */
+t_canvas *canvas_getrootfor_ab(t_canvas *x)
+{
+    if ((!x->gl_owner && !x->gl_isclone) || (canvas_isabstraction(x) && !x->gl_isab))
+        return (x);
+    else if (x->gl_isab) /* shortcut + workaround for clones (since they haven't owner)*/
+        return (x->gl_absource->ad_owner);
+    else
+        return (canvas_getrootfor_ab(x->gl_owner));
+}
+
+/* check if the dependency graph has a cycle, assuming an new edge between parent and
+    current nodes if there is a cycle, a visual scheme of the cycle is stored in 'res' */
+static int ab_check_cycle(t_ab_definition *current, t_ab_definition *parent, int pathlen,
+    char *path, char *res)
+{
+    if(current == parent)
+    {
+        sprintf(path+pathlen, "[ab %s]", current->ad_name->s_name);
+        strcpy(res, path);
+        return (1);
+    }
+    else
+    {
+        /* if it is a local private abstraction, get rid of classmember-like names (only used internally) */
+        char *hash = strrchr(current->ad_name->s_name, '#');
+        if(!hash) hash = current->ad_name->s_name;
+        else hash += 1;
+        int len = strlen(hash);
+        sprintf(path+pathlen, "[ab %s]<-", hash);
+        pathlen += (len+7);
+        int i, cycle = 0;
+        for(i = 0; !cycle && i < current->ad_numdep; i++)
+        {
+            cycle = ab_check_cycle(current->ad_dep[i], parent, pathlen, path, res);
+        }
+        pathlen -= (len+7);
+        return (cycle);
+    }
+}
+
+/* try to register a new dependency into the dependency graph,
+    returns 0 and the scheme in 'res' if a dependency issue is found */
+static int canvas_register_ab(t_canvas *x, t_ab_definition *a, char *res)
+{
+    /* climb to closest ab */
+    while(x && !x->gl_isab)
+        x = x->gl_owner;
+
+    if(x && x->gl_isab)
+    {
+        t_ab_definition *f = x->gl_absource;
+
+        int i, found = 0;
+        for(i = 0; !found && i < a->ad_numdep; i++)
+            found = (a->ad_dep[i] == f);
+
+        if(!found)
+        {
+            char path[MAXPDSTRING];
+            sprintf(path, "[ab %s]<-", a->ad_name->s_name);
+            if(!ab_check_cycle(f, a, strlen(path), path, res))
+            {
+                /* no dependency issues found so we add the new dependency */
+                a->ad_dep =
+                    (t_ab_definition **)resizebytes(a->ad_dep, sizeof(t_ab_definition *)*a->ad_numdep,
+                            sizeof(t_ab_definition *)*(a->ad_numdep+1));
+                a->ad_deprefs =
+                    (int *)resizebytes(a->ad_deprefs, sizeof(int)*a->ad_numdep,
+                            sizeof(int)*(a->ad_numdep+1));
+                a->ad_dep[a->ad_numdep] = f;
+                a->ad_deprefs[a->ad_numdep] = 1;
+                a->ad_numdep++;
+            }
+            else return (0);
+        }
+        else
+        {
+            a->ad_deprefs[i-1]++;
+        }
+    }
+    return (1);
+}
+
+static void canvas_deregister_ab(t_canvas *x, t_ab_definition *a)
+{
+    /* climb to closest ab */
+    while(x && !x->gl_isab)
+        x = x->gl_owner;
+
+    if(x && x->gl_isab)
+    {
+        t_ab_definition *f = x->gl_absource;
+
+        int i, found = 0;
+        for(i = 0; !found && i < a->ad_numdep; i++)
+            found = (a->ad_dep[i] == f);
+
+        if(found)
+        {
+            a->ad_deprefs[i-1]--;
+
+            if(!a->ad_deprefs[i-1])
+            {
+                /* we can delete the dependency since there are no instances left */
+                t_ab_definition **ad =
+                        (t_ab_definition **)getbytes(sizeof(t_ab_definition *) * (a->ad_numdep - 1));
+                int *adr = (int *)getbytes(sizeof(int) * (a->ad_numdep - 1));
+                memcpy(ad, a->ad_dep, sizeof(t_ab_definition *) * (i-1));
+                memcpy(ad+(i-1), a->ad_dep+i, sizeof(t_ab_definition *) * (a->ad_numdep - i));
+                memcpy(adr, a->ad_deprefs, sizeof(int) * (i-1));
+                memcpy(adr+(i-1), a->ad_deprefs+i, sizeof(int) * (a->ad_numdep - i));
+                freebytes(a->ad_dep, sizeof(t_ab_definition *) * a->ad_numdep);
+                freebytes(a->ad_deprefs, sizeof(int) * a->ad_numdep);
+                a->ad_numdep--;
+                a->ad_dep = ad;
+                a->ad_deprefs = adr;
+            }
+        }
+        else bug("canvas_deregister_ab");
+    }
+}
+
+/* tries to find an ab definition given its name */
+static t_ab_definition *canvas_find_ab(t_canvas *x, t_symbol *name)
+{
+    t_canvas *c = canvas_getrootfor_ab(x);
+    t_ab_definition* d;
+    for (d = c->gl_abdefs; d; d = d->ad_next)
+    {
+        if (d->ad_name == name)
+            return d;
+    }
+    return 0;
+}
+
+/* tries to add a new ab definition. returns the definition if it has been added, 0 otherwise */
+static t_ab_definition *canvas_add_ab(t_canvas *x, t_symbol *name, t_binbuf *source)
+{
+    if(!canvas_find_ab(x, name))
+    {
+        t_canvas *c = canvas_getrootfor_ab(x);
+        t_ab_definition *abdef = (t_ab_definition *)getbytes(sizeof(t_ab_definition));
+
+        abdef->ad_name = name;
+        abdef->ad_source = source;
+        abdef->ad_numinstances = 0;
+        abdef->ad_owner = c;
+        abdef->ad_numdep = 0;
+        abdef->ad_dep = (t_ab_definition **)getbytes(0);
+        abdef->ad_deprefs = (int *)getbytes(0);
+        abdef->ad_visflag = 0;
+
+        abdef->ad_next = c->gl_abdefs;
+        c->gl_abdefs = abdef;
+        return (abdef);
+    }
+    return (0);
+}
+
+static int canvas_del_ab(t_canvas *x, t_symbol *name)
+{
+    t_canvas *c = canvas_getrootfor_ab(x);
+    t_ab_definition *abdef, *abdefpre;
+    for(abdef = c->gl_abdefs, abdefpre = 0; abdef; abdefpre = abdef, abdef = abdef->ad_next)
+    {
+        if(abdef->ad_name == name && !abdef->ad_numinstances)
+        {
+            if(abdefpre) abdefpre->ad_next = abdef->ad_next;
+            else c->gl_abdefs = abdef->ad_next;
+            binbuf_free(abdef->ad_source);
+            freebytes(abdef->ad_dep, sizeof(t_ab_definition*)*abdef->ad_numdep);
+            freebytes(abdef->ad_deprefs, sizeof(int)*abdef->ad_numdep);
+            freebytes(abdef, sizeof(t_ab_definition));
+            return (1);
+        }
+    }
+    return (0);
+}
+
+/* given the ab definitions list, returns its topological ordering */
+
+static int currvisflag = 0;
+
+static void ab_topological_sort_rec(t_ab_definition *a, t_ab_definition **stack, int *head)
+{
+    a->ad_visflag = currvisflag;
+
+    int i;
+    for(i = 0; i < a->ad_numdep; i++)
+    {
+        if(a->ad_dep[i]->ad_visflag != currvisflag)
+            ab_topological_sort_rec(a->ad_dep[i], stack, head);
+    }
+
+    stack[*head] = a;
+    (*head)++;
+}
+
+static void ab_topological_sort(t_ab_definition *abdefs, t_ab_definition **stack, int *head)
+{
+    currvisflag++;
+    t_ab_definition *abdef;
+    for(abdef = abdefs; abdef; abdef = abdef->ad_next)
+    {
+        if(abdef->ad_visflag != currvisflag)
+            ab_topological_sort_rec(abdef, stack, head);
+    }
+}
+
+/* saves all ab definition within the scope into the b binbuf,
+    they are sorted topollogially before saving in order to get exactly the
+    same state (ab objects that can't be instantiated due dependencies) when reloading the file */
+void canvas_saveabdefinitionsto(t_canvas *x, t_binbuf *b)
+{
+    if(!x->gl_abdefs)
+        return;
+
+    int numabdefs = 0;
+    t_ab_definition *abdef;
+    for(abdef = x->gl_abdefs; abdef; abdef = abdef->ad_next)
+        numabdefs++;
+
+    t_ab_definition **stack =
+        (t_ab_definition **)getbytes(sizeof(t_ab_definition *) * numabdefs);
+    int head = 0;
+    ab_topological_sort(x->gl_abdefs, stack, &head);
+
+    int i, fra = 0;
+    for(i = 0; i < head; i++)
+    {
+        if(stack[i]->ad_numinstances)
+        {
+            if(!fra)
+            {
+                binbuf_addv(b, "ssi;", gensym("#X"), gensym("abframe"), 1);
+                fra = 1;
+            }
+
+            binbuf_add(b, binbuf_getnatom(stack[i]->ad_source), binbuf_getvec(stack[i]->ad_source));
+            binbuf_addv(b, "sss", gensym("#X"), gensym("abpush"), stack[i]->ad_name);
+
+            int j;
+            for(j = 0; j < stack[i]->ad_numdep; j++)
+                binbuf_addv(b, "s", stack[i]->ad_dep[j]->ad_name);
+            binbuf_addsemi(b);
+        }
+    }
+    if(fra) binbuf_addv(b, "ssi;", gensym("#X"), gensym("abframe"), 0);
+
+    freebytes(stack, sizeof(t_ab_definition *) * numabdefs);
+}
+
+/* saves last canvas as an ab definition */
+static void canvas_abpush(t_canvas *x, t_symbol *s, int argc, t_atom *argv)
+{
+    canvas_pop(x, 0);
+
+    t_canvas *c = canvas_getcurrent();
+    t_symbol *name = argv[0].a_w.w_symbol;
+    t_binbuf *source = binbuf_new();
+    x->gl_env = dummy_canvas_env(canvas_getdir(x)->s_name); //to save it as a root canvas
+    mess1(&((t_text *)x)->te_pd, gensym("saveto"), source);
+    x->gl_env = 0;
+
+    t_ab_definition *n;
+    if(!(n = canvas_add_ab(c, name, source)))
+    {
+        error("canvas_abpush: ab definition for '%s' already exists, skipping",
+                name->s_name);
+    }
+    else
+    {
+        if(argc > 1)
+        {
+            /* restore all dependencies, to get exactly the
+                same state (ab objects that can't be instantiated due dependencies) as before */
+            n->ad_numdep = argc-1;
+            n->ad_dep =
+                (t_ab_definition **)resizebytes(n->ad_dep, 0, sizeof(t_ab_definition *)*n->ad_numdep);
+            n->ad_deprefs =
+                (int *)resizebytes(n->ad_deprefs, 0, sizeof(int)*n->ad_numdep);
+
+            int i;
+            for(i = 1; i < argc; i++)
+            {
+                t_symbol *abname = argv[i].a_w.w_symbol;
+                t_ab_definition *absource = canvas_find_ab(c, abname);
+                if(!absource) { bug("canvas_abpush"); return; }
+                n->ad_dep[i-1] = absource;
+                n->ad_deprefs[i-1] = 0;
+            }
+        }
+    }
+
+    pd_free(&x->gl_pd);
+}
+
+/* extends the name for a local ab, using a classmember-like format */
+static t_symbol *ab_extend_name(t_canvas *x, t_symbol *s)
+{
+    char res[MAXPDSTRING];
+    t_canvas *next = canvas_getrootfor(x);
+    if(next->gl_isab)
+        sprintf(res, "%s#%s", next->gl_absource->ad_name->s_name, s->s_name);
+    else
+        strcpy(res, s->s_name);
+    return gensym(res);
+}
+
+int abframe = 0;
+static void canvas_abframe(t_canvas *x, t_float val)
+{
+    abframe = val;
+}
+
+extern t_class *text_class;
+
+/* creator for "ab" objects */
+static void *ab_new(t_symbol *s, int argc, t_atom *argv)
+{
+    if(abframe)
+        /* return dummy text object so that creator
+            does not throw an error */
+        return pd_new(text_class);
+
+    t_canvas *c = canvas_getcurrent();
+
+    if (argc && argv[0].a_type != A_SYMBOL)
+    {
+        error("ab_new: ab name must be a symbol");
+        newest = 0;
+    }
+    else
+    {
+        t_symbol *name = (argc ? argv[0].a_w.w_symbol : gensym("(ab)"));
+        t_ab_definition *source;
+
+        if(name->s_name[0] == '@') /* is local ab */
+            name = ab_extend_name(c, name);
+
+        if(!(source = canvas_find_ab(c, name)))
+        {
+            t_binbuf *b = binbuf_new();
+            binbuf_text(b, ab_templatecanvas, strlen(ab_templatecanvas));
+            source = canvas_add_ab(c, name, b);
+        }
+
+        char res[MAXPDSTRING];
+        if(canvas_register_ab(c, source, res))
+        {
+            newest = do_create_ab(source, (argc ? argc-1 : 0), (argc ? argv+1 : 0));
+            source->ad_numinstances++;
+        }
+        else
+        {
+            if(!glist_amreloadingabstractions)
+                error("ab_new: can't insantiate ab within itself\n cycle: %s", res);
+            newest = 0;
+        }
+    }
+    return (newest);
+}
+
+static void canvas_getabstractions(t_canvas *x)
+{
+    t_canvas *c = canvas_getrootfor_ab(x),
+             *r = canvas_getrootfor(x);
+    gfxstub_deleteforkey(x);
+    char *gfxstub = gfxstub_new2(&x->gl_pd, &x->gl_pd);
+    t_ab_definition *abdef;
+    gui_start_vmess("gui_abstractions_dialog", "xs", x, gfxstub);
+    gui_start_array();
+    gui_end_array();
+    gui_start_array();
+    for(abdef = c->gl_abdefs; abdef; abdef = abdef->ad_next)
+    {
+        char *hash = strrchr(abdef->ad_name->s_name, '#');
+        if(!hash)
+        {
+            if(abdef->ad_name->s_name[0] != '@' || !r->gl_isab)
+            {
+                gui_s(abdef->ad_name->s_name);
+                gui_i(abdef->ad_numinstances);
+            }
+        }
+        else
+        {
+            *hash = '\0';
+            if(r->gl_isab &&
+                gensym(abdef->ad_name->s_name) == r->gl_absource->ad_name)
+            {
+                gui_s(hash+1);
+                gui_i(abdef->ad_numinstances);
+            }
+            *hash = '#';
+        }
+    }
+    gui_end_array();
+    gui_end_vmess();
+}
+
+static void canvas_delabstractions(t_canvas *x, t_symbol *s, int argc, t_atom *argv)
+{
+    t_symbol *name;
+    int i;
+    for(i = 0; i < argc; i++)
+    {
+        name = atom_getsymbol(argv++);
+        if(name->s_name[0] == '@')
+            name = ab_extend_name(x, name);
+        if(!canvas_del_ab(x, name))
+            bug("canvas_delabstractions");
+    }
+    startpost("info: a total of [%d] ab definitions have been deleted\n      > ", argc);
+    postatom(argc, argv-argc);
+    endpost();
+}
+
+/* --------- */
+
+static t_class *abdefs_class;
+
+typedef struct _abdefs
+{
+    t_object x_obj;
+    t_canvas *x_canvas;
+} t_abdefs;
+
+static void *abdefs_new(void)
+{
+    t_abdefs *x = (t_abdefs *)pd_new(abdefs_class);
+    x->x_canvas = canvas_getcurrent();
+    outlet_new(&x->x_obj, &s_list);
+    return (x);
+}
+
+static void abdefs_get(t_abdefs *x);
+static void abdefs_bang(t_abdefs *x)
+{
+    abdefs_get(x);
+}
+
+static void abdefs_get(t_abdefs *x)
+{
+    t_canvas *c = canvas_getrootfor_ab(x->x_canvas),
+             *r = canvas_getrootfor(x->x_canvas);
+    t_binbuf *out = binbuf_new();
+    t_ab_definition *abdef;
+    for(abdef = c->gl_abdefs; abdef; abdef = abdef->ad_next)
+    {
+        char *hash = strrchr(abdef->ad_name->s_name, '#');
+        if(!hash)
+        {
+            if(abdef->ad_name->s_name[0] != '@' || !r->gl_isab)
+            {
+                binbuf_addv(out, "si", abdef->ad_name, abdef->ad_numinstances);
+            }
+        }
+        else
+        {
+            *hash = '\0';
+            if(r->gl_isab &&
+                gensym(abdef->ad_name->s_name) == r->gl_absource->ad_name)
+            {
+                binbuf_addv(out, "si", gensym(hash+1), abdef->ad_numinstances);
+            }
+            *hash = '#';
+        }
+    }
+    outlet_list(x->x_obj.ob_outlet, &s_list,
+        binbuf_getnatom(out), binbuf_getvec(out));
+    binbuf_free(out);
+}
+
+static void abdefs_instances(t_abdefs *x, t_symbol *s)
+{
+    t_ab_definition *abdef;
+    if((abdef = canvas_find_ab(x->x_canvas, s)))
+    {
+        t_atom at[1];
+        SETFLOAT(at, abdef->ad_numinstances);
+        outlet_list(x->x_obj.ob_outlet, &s_list, 1, at);
+    }
+    else
+        error("abdefs: couldn't find definition for '%s'", s->s_name);
+}
+
 /* --------- */
 
 static void canvas_showdirty(t_canvas *x)
 {
-    canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), 2);
+    if(!x->gl_isab)
+        canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), 2);
+    else
+        canvas_dirty_broadcast_ab(canvas_getrootfor_ab(x), x->gl_absource, 2);
 }
 
 /* ------------------------------- declare ------------------------ */
@@ -2915,8 +3575,29 @@ void g_canvas_setup(void)
     class_addcreator((t_newmethod)table_new, gensym("table"),
         A_DEFSYM, A_DEFFLOAT, 0);
 
+/* ------------------- */
+
     class_addmethod(canvas_class, (t_method)canvas_showdirty,
         gensym("showdirty"), 0);
+
+/*---------------------------- ab ------------------- */
+
+    class_addcreator((t_newmethod)ab_new, gensym("ab"), A_GIMME, 0);
+    class_addmethod(canvas_class, (t_method)canvas_abpush,
+        gensym("abpush"), A_GIMME, 0);
+    class_addmethod(canvas_class, (t_method)canvas_abframe,
+        gensym("abframe"), A_FLOAT, 0);
+
+    abdefs_class = class_new(gensym("abdefs"), (t_newmethod)abdefs_new,
+        0, sizeof(t_abdefs), CLASS_DEFAULT, A_NULL);
+    class_addbang(abdefs_class, (t_method)abdefs_bang);
+    class_addmethod(abdefs_class, (t_method)abdefs_get, gensym("get"), 0);
+    class_addmethod(abdefs_class, (t_method)abdefs_instances, gensym("instances"), A_SYMBOL, 0);
+
+    class_addmethod(canvas_class, (t_method)canvas_getabstractions,
+        gensym("getabstractions"), 0);
+    class_addmethod(canvas_class, (t_method)canvas_delabstractions,
+        gensym("delabstractions"), A_GIMME, 0);
 /*---------------------------- declare ------------------- */
     declare_class = class_new(gensym("declare"), (t_newmethod)declare_new,
         (t_method)declare_free, sizeof(t_declare), CLASS_NOINLET, A_GIMME, 0);
diff --git a/pd/src/g_canvas.h b/pd/src/g_canvas.h
index 4cafe3265a5d2e704745a5b7d1b23a49d317e7f3..cde9837014ad4a16ab8b7ca2eb8275062a2b1f7f 100644
--- a/pd/src/g_canvas.h
+++ b/pd/src/g_canvas.h
@@ -169,6 +169,24 @@ typedef struct _tick    /* where to put ticks on x or y axes */
     int k_lperb;        /* little ticks per big; 0 if no ticks to draw */
 } t_tick;
 
+/* the t_ab_definition structure holds an ab definiton and all the attributes we need
+    to handle it */
+typedef struct _ab_definition
+{
+    t_symbol *ad_name;      /* id for the ab definition */
+    t_binbuf *ad_source;    /* binbuf where the source is stored */
+    int ad_numinstances;    /* number of instances */
+    struct _ab_definition *ad_next;     /* next ab definition */
+    t_canvas *ad_owner;     /* canvas that stores this definition */
+
+    /* dependency graph stuff */
+    int ad_numdep;      /* number of other ab definitions that it depends on */
+    struct _ab_definition **ad_dep;     /* the actual ab defintitions */
+    int *ad_deprefs;    /*  number of instances that define the dependency */
+    int ad_visflag;     /* visited flag for topological sort algorithm */
+} t_ab_definition;
+
+
 /* the t_glist structure, which describes a list of elements that live on an
 area of a window.
 
@@ -241,6 +259,11 @@ struct _glist
 
     int gl_subdirties;     /* number of descending dirty abstractions */
     int gl_dirties;        /* number of diry instances, for multiple dirty warning */
+
+    unsigned int gl_isab:1;         /* is an ab instance */
+    t_ab_definition *gl_absource;   /* ab definition pointer,
+                                        in the case it is an ab instance */
+    t_ab_definition *gl_abdefs;     /* stored ab definitions */
 };
 
 #define gl_gobj gl_obj.te_g
@@ -560,6 +583,8 @@ EXTERN void canvas_setcurrent(t_canvas *x);
 EXTERN void canvas_unsetcurrent(t_canvas *x);
 EXTERN t_symbol *canvas_realizedollar(t_canvas *x, t_symbol *s);
 EXTERN t_canvas *canvas_getrootfor(t_canvas *x);
+EXTERN t_canvas *canvas_getrootfor_ab(t_canvas *x);
+EXTERN int abframe;
 EXTERN void canvas_dirty(t_canvas *x, t_floatarg n);
 EXTERN int canvas_getfont(t_canvas *x);
 typedef int (*t_canvasapply)(t_canvas *x, t_int x1, t_int x2, t_int x3);
diff --git a/pd/src/g_clone.c b/pd/src/g_clone.c
index c06f8dba7fb293b955382fa085feb4137933ec64..d5ba76414de9c785cf1e71efc617c821f3da4c27 100644
--- a/pd/src/g_clone.c
+++ b/pd/src/g_clone.c
@@ -63,6 +63,7 @@ typedef struct _clone
     int x_phase;
     int x_startvoice;   /* number of first voice, 0 by default */
     int x_suppressvoice; /* suppress voice number as $1 arg */
+    t_canvas *x_owner;  /* clone owner */
 } t_clone;
 
 /* the given 'it' function is executed over each of the underlying canvases
@@ -80,8 +81,23 @@ int clone_match(t_pd *z, t_symbol *name, t_symbol *dir)
     t_clone *x = (t_clone *)z;
     if (!x->x_n)
         return (0);
-    return (x->x_vec[0].c_gl->gl_name == name &&
-        canvas_getdir(x->x_vec[0].c_gl) == dir);
+    return (!x->x_vec[0].c_gl->gl_isab
+            && x->x_vec[0].c_gl->gl_name == name
+            && canvas_getdir(x->x_vec[0].c_gl) == dir);
+}
+
+int clone_isab(t_pd *z)
+{
+    t_clone *x = (t_clone *)z;
+    if (!x->x_n)
+        return (0);
+    return (x->x_vec[0].c_gl->gl_isab);
+}
+
+int clone_matchab(t_pd *z, t_ab_definition *source)
+{
+    t_clone *x = (t_clone *)z;
+    return (clone_isab(z) && x->x_vec[0].c_gl->gl_absource == source);
 }
 
 void obj_sendinlet(t_object *x, int n, t_symbol *s, int argc, t_atom *argv);
@@ -185,6 +201,13 @@ static void clone_free(t_clone *x)
         for (i = 0; i < x->x_n; i++)
         {
             canvas_closebang(x->x_vec[i].c_gl);
+            if(x->x_vec[i].c_gl->gl_isab)
+            {
+                /* crude hack. since clones don't have owner,
+                    we set it manually to allow the clone to
+                    deregister the dependencies */
+                x->x_vec[i].c_gl->gl_owner = x->x_owner;
+            }
             pd_free(&x->x_vec[i].c_gl->gl_pd);
             t_freebytes(x->x_outvec[i],
                 x->x_nout * sizeof(*x->x_outvec[i]));
@@ -238,7 +261,8 @@ void clone_setn(t_clone *x, t_floatarg f)
     {
         t_canvas *c;
         t_out *outvec;
-        SETFLOAT(x->x_argv, x->x_startvoice + i);
+        /* in the case they are [ab]s, the instance number is one atom beyond */
+        SETFLOAT((x->x_vec[0].c_gl->gl_isab ? x->x_argv+1 : x->x_argv), x->x_startvoice + i);
         if (!(c = clone_makeone(x->x_s, x->x_argc - x->x_suppressvoice,
             x->x_argv + x->x_suppressvoice)))
         {
@@ -364,6 +388,7 @@ static void clone_dsp(t_clone *x, t_signal **sp)
     }
 }
 
+static int clone_newabclone = 0;
 static void *clone_new(t_symbol *s, int argc, t_atom *argv)
 {
     t_clone *x = (t_clone *)pd_new(clone_class);
@@ -403,10 +428,27 @@ static void *clone_new(t_symbol *s, int argc, t_atom *argv)
     else goto usage;
         /* store a copy of the argmuents with an extra space (argc+1) for
         supplying an instance number, which we'll bash as we go. */
-    x->x_argc = argc - 1;
-    x->x_argv = getbytes(x->x_argc * sizeof(*x->x_argv));
-    memcpy(x->x_argv, argv+1, x->x_argc * sizeof(*x->x_argv));
-    SETFLOAT(x->x_argv, x->x_startvoice);
+    if(clone_newabclone)
+    /* we are creating a clone from an [ab] definition, we use the same creation
+        method as for normal clones but the name we pass to objectmaker is
+        'ab <name>' instead of just '<name>' */
+    {
+        x->x_argc = argc;
+        x->x_argv = getbytes(x->x_argc * sizeof(*x->x_argv));
+        memcpy(x->x_argv, argv, x->x_argc * sizeof(*x->x_argv));
+        SETSYMBOL(x->x_argv, x->x_s);
+        SETFLOAT(x->x_argv+1, x->x_startvoice);
+        x->x_s = gensym("ab");
+        x->x_owner = canvas_getcurrent();
+        clone_newabclone = 0;
+    }
+    else
+    {
+        x->x_argc = argc - 1;
+        x->x_argv = getbytes(x->x_argc * sizeof(*x->x_argv));
+        memcpy(x->x_argv, argv+1, x->x_argc * sizeof(*x->x_argv));
+        SETFLOAT(x->x_argv, x->x_startvoice);
+    }
     if (!(c = clone_makeone(x->x_s, x->x_argc - x->x_suppressvoice,
         x->x_argv + x->x_suppressvoice)))
             goto fail;
@@ -454,10 +496,20 @@ fail:
     return (0);
 }
 
+/* creator for [ab]-based clones */
+static void *abclone_new(t_symbol *s, int argc, t_atom *argv)
+{
+    clone_newabclone = 1;
+    return clone_new(s, argc, argv);
+}
+
 void clone_setup(void)
 {
     clone_class = class_new(gensym("clone"), (t_newmethod)clone_new,
         (t_method)clone_free, sizeof(t_clone), CLASS_NOINLET, A_GIMME, 0);
+
+    class_addcreator((t_newmethod)abclone_new, gensym("abclone"), A_GIMME, 0);
+
     class_addmethod(clone_class, (t_method)clone_click, gensym("click"),
         A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0);
     class_addmethod(clone_class, (t_method)clone_loadbang, gensym("loadbang"),
diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c
index bddca8c714b742e82441174e9193baa8d59b6926..532cf960c24569c9ee62a6a4abbd644cd3598864 100644
--- a/pd/src/g_editor.c
+++ b/pd/src/g_editor.c
@@ -1288,7 +1288,21 @@ void canvas_undo_paste(t_canvas *x, void *z, int action)
     }
 }
 
+void canvas_dirtyclimb(t_canvas *x, int n);
+void clone_iterate(t_pd *z, t_canvas_iterator it, void* data);
 int clone_match(t_pd *z, t_symbol *name, t_symbol *dir);
+int clone_isab(t_pd *z);
+int clone_matchab(t_pd *z, t_ab_definition *source);
+
+/* packed data passing structure for glist_doreload */
+typedef struct _reload_data
+{
+    t_symbol *n;
+    t_symbol *d;
+    t_gobj *e;
+} t_reload_data;
+
+static void glist_doreload_packed(t_canvas *x, t_reload_data *data);
 
     /* recursively check for abstractions to reload as result of a save. 
     Don't reload the one we just saved ("except") though. */
@@ -1310,7 +1324,7 @@ static void glist_doreload(t_glist *gl, t_symbol *name, t_symbol *dir,
             /* remake the object if it's an abstraction that appears to have
             been loaded from the file we just saved */
         remakeit = (g != except && pd_class(&g->g_pd) == canvas_class &&
-            canvas_isabstraction((t_canvas *)g) &&
+            canvas_isabstraction((t_canvas *)g) && !((t_canvas *)g)->gl_isab &&
                 ((t_canvas *)g)->gl_name == name &&
                     canvas_getdir((t_canvas *)g) == dir);
 
@@ -1375,16 +1389,32 @@ static void glist_doreload(t_glist *gl, t_symbol *name, t_symbol *dir,
         if (g != except && pd_class(&g->g_pd) == canvas_class &&
 
             (!canvas_isabstraction((t_canvas *)g) ||
+                 ((t_canvas *)g)->gl_isab ||
                  ((t_canvas *)g)->gl_name != name ||
                  canvas_getdir((t_canvas *)g) != dir)
            )
                 glist_doreload((t_canvas *)g, name, dir, except);
+
+        /* also reload the instances within ab-based clone objects
+            *COMMENT: missing recursive case for abstraction-based clone
+                objects that don't match with the one we are reloading?  */
+        if(pd_class(&g->g_pd) == clone_class && clone_isab(&g->g_pd))
+        {
+            t_reload_data d;
+            d.n = name; d.d = dir; d.e = except;
+            clone_iterate(&g->g_pd, glist_doreload_packed, &d);
+        }
         g = g->g_next;
     }
     if (!hadwindow && gl->gl_havewindow)
         canvas_vis(glist_getcanvas(gl), 0);
 }
 
+static void glist_doreload_packed(t_canvas *x, t_reload_data *data)
+{
+    glist_doreload(x, data->n, data->d, data->e);
+}
+
     /* this flag stops canvases from being marked "dirty" if we have to touch
     them to reload an abstraction; also suppress window list update */
 int glist_amreloadingabstractions = 0;
@@ -1402,6 +1432,89 @@ void canvas_reload(t_symbol *name, t_symbol *dir, t_gobj *except)
     canvas_resume_dsp(dspwas);
 }
 
+/* packed data passing structure for glist_doreload_ab */
+typedef struct _reload_ab_data
+{
+    t_ab_definition *a;
+    t_gobj *e;
+} t_reload_ab_data;
+
+static void glist_doreload_ab_packed(t_canvas *x, t_reload_ab_data *data);
+
+/* recursive ab reload method */
+static void glist_doreload_ab(t_canvas *x, t_ab_definition *a, t_gobj *e)
+{
+    t_gobj *g;
+    int i, nobj = glist_getindex(x, 0);
+    int found = 0, remakeit = 0;
+
+    for (g = x->gl_list, i = 0; g && i < nobj; i++)
+    {
+        remakeit = (g != e && pd_class(&g->g_pd) == canvas_class && canvas_isabstraction((t_canvas *)g)
+            && ((t_canvas *)g)->gl_isab && ((t_canvas *)g)->gl_absource == a);
+
+        remakeit = remakeit || (pd_class(&g->g_pd) == clone_class && clone_matchab(&g->g_pd, a));
+
+        if(remakeit)
+        {
+            canvas_create_editor(x);
+            if (!found)
+            {
+                glist_noselect(x);
+                found = 1;
+            }
+            glist_select(x, g);
+        }
+        /* since this one won't be reloaded, we need to trigger initbang manually */
+        else if(g == e)
+            canvas_initbang((t_canvas *)g);
+        g = g->g_next;
+    }
+    if (found)
+    {
+        canvas_cut(x);
+        canvas_undo_undo(x);
+        glist_noselect(x);
+    }
+
+    for (g = x->gl_list, i = 0; g && i < nobj; i++)
+    {
+        if(pd_class(&g->g_pd) == canvas_class && (!canvas_isabstraction((t_canvas *)g)
+                    || (((t_canvas *)g)->gl_isab && (((t_canvas *)g)->gl_absource != a))))
+            glist_doreload_ab((t_canvas *)g, a, e);
+
+        /* also reload the instances within ab-based clone objects */
+        if(pd_class(&g->g_pd) == clone_class
+            && clone_isab(&g->g_pd) && !clone_matchab(&g->g_pd, a))
+        {
+            t_reload_ab_data d;
+            d.a = a; d.e = e;
+            clone_iterate(&g->g_pd, glist_doreload_ab_packed, &d);
+        }
+        g = g->g_next;
+    }
+}
+
+static void glist_doreload_ab_packed(t_canvas *x, t_reload_ab_data *data)
+{
+    glist_doreload_ab(x, data->a, data->e);
+}
+
+/* reload ab instances */
+void canvas_reload_ab(t_canvas *x)
+{
+    t_canvas *c = canvas_getrootfor_ab(x);
+    int dspwas = canvas_suspend_dsp();
+    glist_amreloadingabstractions = 1;
+    glist_doreload_ab(c, x->gl_absource, &x->gl_gobj);
+    glist_amreloadingabstractions = 0;
+    canvas_resume_dsp(dspwas);
+    /* we set the dirty flag of the root canvas (where the ab definitions
+        are stored) because its file contents (in this case, the definition
+        for an specific ab) has been edited */
+    canvas_dirty(c, 1);
+}
+
 /* --------- 6. apply  ----------- */
 
 typedef struct _undo_apply        
@@ -6188,6 +6301,7 @@ void gobj_dirty(t_gobj *x, t_glist *g, int state)
     t_rtext *y = glist_findrtext(g, (t_text *)x);
     gui_vmess("gui_gobj_dirty", "xsi", g, rtext_gettag(y), state);
 }
+
     /* tell the gui to display a specific message in the
         top right corner */
 void canvas_warning(t_canvas *x, int warid)
diff --git a/pd/src/g_graph.c b/pd/src/g_graph.c
index addca713431c6b62119aa0d8463a30a4210ed205..8691141adfa76d481997ed4d22d23e14731b0050 100644
--- a/pd/src/g_graph.c
+++ b/pd/src/g_graph.c
@@ -202,6 +202,8 @@ int canvas_hasarray(t_canvas *x)
 /* JMZ: emit a closebang message */
 void canvas_closebang(t_canvas *x);
 
+void canvas_dirtyclimb(t_canvas *x, int n);
+
     /* delete an object from a glist and free it */
 void glist_delete(t_glist *x, t_gobj *y)
 {
diff --git a/pd/src/g_readwrite.c b/pd/src/g_readwrite.c
index 672d536bb12c9ba32c39d515fedfafc944271bf3..007dcc0ee3f156ce1a686233496b4a1154912ac8 100644
--- a/pd/src/g_readwrite.c
+++ b/pd/src/g_readwrite.c
@@ -103,6 +103,7 @@ void canvas_saved(t_glist *x, t_symbol *s, int argc, t_atom *argv)
 }
 
 void canvas_savedeclarationsto(t_canvas *x, t_binbuf *b);
+void canvas_saveabdefinitionsto(t_canvas *x, t_binbuf *b);
 
     /* the following routines read "scalars" from a file into a canvas. */
 
@@ -830,6 +831,9 @@ static void canvas_saveto(t_canvas *x, t_binbuf *b)
         }
         canvas_savedeclarationsto(x, b);
     }
+
+    canvas_saveabdefinitionsto(x, b);
+
     for (y = x->gl_list; y; y = y->g_next)
         gobj_save(y, b);
 
@@ -976,26 +980,55 @@ static void canvas_savetofile(t_canvas *x, t_symbol *filename, t_symbol *dir,
     binbuf_free(b);
 }
 
+void canvas_reload_ab(t_canvas *x);
+
+/* updates the shared ab definition and reloads all instances */
+static void canvas_save_ab(t_canvas *x, t_floatarg fdestroy)
+{
+    if(!x->gl_absource) bug("canvas_save_ab");
+
+    t_binbuf *b = binbuf_new();
+    canvas_savetemplatesto(x, b, 1);
+    canvas_saveto(x, b);
+
+    binbuf_free(x->gl_absource->ad_source);
+    x->gl_absource->ad_source = b;
+
+    canvas_dirty(x, 0);
+    canvas_reload_ab(x);
+
+    if (fdestroy != 0) //necessary?
+        vmess(&x->gl_pd, gensym("menuclose"), "f", 1.);
+}
+
 static void canvas_menusaveas(t_canvas *x, t_floatarg fdestroy)
 {
     t_canvas *x2 = canvas_getrootfor(x);
-    gui_vmess("gui_canvas_saveas", "xssi",
-        x2,
-        (strncmp(x2->gl_name->s_name, "Untitled", 8) ?
-            x2->gl_name->s_name : "title"),
-        canvas_getdir(x2)->s_name,
-        fdestroy != 0);
+    if(!x->gl_isab)
+        gui_vmess("gui_canvas_saveas", "xssi",
+            x2,
+            (strncmp(x2->gl_name->s_name, "Untitled", 8) ?
+                x2->gl_name->s_name : "title"),
+            canvas_getdir(x2)->s_name,
+            fdestroy != 0);
+    else if(x->gl_dirty)
+        canvas_save_ab(x2, fdestroy);
 }
 
 static void canvas_menusave(t_canvas *x, t_floatarg fdestroy)
 {
     t_canvas *x2 = canvas_getrootfor(x);
     char *name = x2->gl_name->s_name;
-    if (*name && strncmp(name, "Untitled", 8)
-            && (strlen(name) < 4 || strcmp(name + strlen(name)-4, ".pat")
-                || strcmp(name + strlen(name)-4, ".mxt")))
-            canvas_savetofile(x2, x2->gl_name, canvas_getdir(x2), fdestroy);
-    else canvas_menusaveas(x2, fdestroy);
+    if(!x->gl_isab)
+    {
+        if (*name && strncmp(name, "Untitled", 8)
+                && (strlen(name) < 4 || strcmp(name + strlen(name)-4, ".pat")
+                    || strcmp(name + strlen(name)-4, ".mxt")))
+                canvas_savetofile(x2, x2->gl_name, canvas_getdir(x2), fdestroy);
+        else canvas_menusaveas(x2, fdestroy);
+    }
+    else if(x->gl_dirty)
+        canvas_save_ab(x2, fdestroy);
 }
 
 static void canvas_menuprint(t_canvas *x)
diff --git a/pd/src/x_interface.c b/pd/src/x_interface.c
index fe05f3c52b87b5b3387cbb4cc6f54965918d4fb6..6753ea626236c574d981ccfa9753e4e22a037aae 100644
--- a/pd/src/x_interface.c
+++ b/pd/src/x_interface.c
@@ -331,6 +331,14 @@ typedef struct _debuginfo {
     t_object x_obj;
 } t_debuginfo;
 
+static t_class *abinfo_class;
+
+typedef struct _abinfo
+{
+    t_object x_obj;
+    t_ab_definition *abdef;
+} t_abinfo;
+
 /* used by all the *info objects */
 
 static int info_to_console = 0;
@@ -1629,6 +1637,74 @@ void objectinfo_setup(void)
     post("stable objectinfo methods: class");
 }
 
+/* -------------------------- abinfo ------------------------------ */
+
+void *abinfo_new(void)
+{
+    t_abinfo *x;
+    t_canvas *c = canvas_getcurrent();
+    while(c->gl_owner && !c->gl_isab) c = c->gl_owner;
+    if(c->gl_isab)
+    {
+        x = (t_abinfo *)pd_new(abinfo_class);
+        x->abdef = c->gl_absource;
+        outlet_new(&x->x_obj, &s_list);
+    }
+    else
+    {
+        if(!abframe)
+        {
+            error("abinfo: only instantiable inside an ab object");
+            x = 0;
+        }
+        else
+            x = pd_new(text_class);
+    }
+    return (x);
+}
+
+void abinfo_name(t_abinfo *x, t_symbol *s, int argc, t_atom *argv)
+{
+    t_atom at[1];
+    SETSYMBOL(at, x->abdef->ad_name);
+    info_out((t_text *)x, s, 1, at);
+}
+
+void abinfo_instances(t_abinfo *x, t_symbol *s, int argc, t_atom *argv)
+{
+    t_atom at[1];
+    SETFLOAT(at, x->abdef->ad_numinstances);
+    info_out((t_text *)x, s, 1, at);
+}
+
+void abinfo_within(t_abinfo *x, t_symbol *s, int argc, t_atom *argv)
+{
+    t_binbuf *buf = binbuf_new();
+    int i;
+    for(i = 0; i < x->abdef->ad_numdep; i++)
+    {
+        binbuf_addv(buf, "s", x->abdef->ad_dep[i]->ad_name);
+    }
+    info_out((t_text *)x, s, binbuf_getnatom(buf), binbuf_getvec(buf));
+    binbuf_free(buf);
+}
+
+//ADD MORE METHODS
+
+void abinfo_setup(void)
+{
+    abinfo_class = class_new(gensym("abinfo"), (t_newmethod)abinfo_new, 0,
+        sizeof(t_abinfo), CLASS_DEFAULT, 0);
+
+    class_addmethod(abinfo_class, (t_method)abinfo_name,
+        gensym("name"), A_GIMME, 0);
+    class_addmethod(abinfo_class, (t_method)abinfo_instances,
+        gensym("instances"), A_GIMME, 0);
+    class_addmethod(abinfo_class, (t_method)abinfo_within,
+        gensym("within"), A_GIMME, 0);
+}
+
+
 void debuginfo_print(t_debuginfo *x)
 {
     info_print((t_text *)x);
@@ -1665,6 +1741,7 @@ void x_interface_setup(void)
     classinfo_setup();
     debuginfo_setup();
     objectinfo_setup();
+    abinfo_setup();
     pdinfo_setup();
     print_setup();
     unpost_setup();