I think it would be most efficient to have a string type be a length followed by that many unsigned chars, similar to a Pascal string but with the length being something like a 32-bit integer. It would not be added to pd's symbol list. The atom whose type was string would have to contain a pointer to the first byte of the string, and a length. Multibyte characters would just be counted as multiple characters when calculating the length, so the length would be the number of bytes in the string, not the number of characters. It looks too easy to me...In m_pd.h, add: A_STRING to t_atomtype at line 139 of m_pd.h. Add t_string * w_string; to t_word at line 122 of m_pd.h. Add the typedef: typedef struct _string /* pointer to a string */ { unsigned long s_length; /* length of string in bytes */ unsigned char *s_data; /* pointer to 1st byte of string */ } t_string; at line 106 of m_pd.h ...so a string atom would have a_type = A_STRING and a_w = a_w.w_string, which points to a t_string containing the length and a pointer to the string. This we did first and now we are compiling pd on linux to see if it breaks anything...not so far. If pd is otherwise able to handle atom types it doesn't know about (?), all the string manipulation objects could be built as externals. As I see it the atom types are defined in m_pd.h and registered in m_class.c, which would need to have functions added to it to handle string, like adding: t_symbol s_string = {"string", 0, 0}; to the list of t_symbols around line 555, and changing: static t_symbol *symlist[] = { &s_pointer, &s_float, &s_symbol, &s_bang, &s_list, &s_anything, &s_signal, &s__N, &s__X, &s_x, &s_y, &s_}; to: static t_symbol *symlist[] = { &s_pointer, &s_float, &s_symbol, &s_bang, &s_list, &s_anything, &s_signal, &s__N, &s__X, &s_x, &s_y, &s_, &s_string}; ...and pd_typedmess needs a handler for string messages around line 658: if (s == &s_string) { if (argc == 1) (*c->c_stringmethod)(x, argv->a_w.w_string); else goto badarg; return; } ...and a handler for messages with string arguments with other names, just befor case A_SYMBOL, line 728: case A_STRING:/* MP 20070106 string type */ post("pd_typedmess A_STRING"); if (!argc) goto badarg; if (argv->a_type == A_STRING) { post("argv->a_type == A_STRING, argc = %d, narg= %d", argc, narg); *ap = (t_int)(argv->a_w.w_string); mp = 1; } argc--; argv++; narg++; ap++; break; Still in m_class.c, around line 344 we add: void class_addstring(t_class *c, t_method fn) { c->c_stringmethod = (t_stringmethod)fn; } This is used to register a string handler with pd so that incoming strings will be sent to fn. We added: else if (sel == &s_string) /* MP 20070106 string type */ { post("class_addmethod: %p", fn); if (argtype != A_STRING || va_arg(ap, t_atomtype)) goto phooey; class_addstring(c, fn); } around line 300 of m_class.c in case someone calls class_addmethod instead of class_addstring with a string selector. Then in m_pd.h, line 418: EXTERN void class_addstring(t_class *c, t_method fn); and at line 447: #define class_addstring(x, y) class_addstring((x), (t_method)(y)) Also add: EXTERN t_symbol s_string; at line 226 of m_pd.h. In m_imp.h we need to define the stringmethod function at line 28: typedef void (*t_stringmethod)(t_pd *x, t_string *st); and, inside struct _class we add a place to store a pointer to the stringmethod at line 45: t_stringmethod c_stringmethod; . As long as no externals are accessing struct _class directly, this should be safe. Add to m_pd.c, line 287 the function to be called when a string arrives at an inlet: void pd_string(t_pd *x, t_string *st) { (*(*x)->c_stringmethod)(x, st); } We need to add a prototype for pd_string in m_pd.h at line 340ish: EXTERN void pd_string(t_pd *x, t_string *st); Along the lines of pd_defaultlist() in m_class.c, which handles list messages for objects that don't have list methods, one could add a pd_defaultstring(), which attempts to convert strings into symbols/floats/lists, instead of calling pd_defaultanything(), which would print "no method for string". But it needs to be understood that it might not do it correctly, which is Not A Good Thing, but no worse than comments getting mangled. Maybe a [string unpack] object would be better: it could attempt to unpack a string into specified types, so the user could decide if a string like "123" is meant to represent a float or a symbol. In class_new() in m_class.c we add: static void pd_defaultstring(t_pd *x, t_string *st); around line 22, c->c_stringmethod = pd_defaultstring; at line 208. Add static void pd_defaultstring(t_pd *x, t_string *st) { /* for now just reject it, later convert to symbol/float/list */ pd_error(x, "%s: no method for string so far...", (*x)->c_name->s_name); } around line 40. And now outlets: Add to m_pd.h, line 364ish: EXTERN void outlet_string(t_outlet *x, t_string *st); This will send a string through an outlet, by calling the pd_string method on all the inlets connected to the outlet. It's defined in m_obj.c, line 369ish: void outlet_string(t_outlet *x, t_string *st) { t_outconnect *oc; if(++stackcount >= STACKITER) outlet_stackerror(x); else for (oc = x->o_connections; oc; oc = oc->oc_next) pd_string(oc->oc_to, st); --stackcount; } We defined pd_string in m_pd.c. To add a string-capable outlet to an object, we already have outlet_new() in m_obj.c. And inlets: static void inlet_string(t_inlet *x, t_string *st) { if (x->i_symfrom == &s_string) pd_vmess(x->i_dest, x->i_symto, "t", st); else if (!x->i_symfrom) pd_string(x->i_dest, st); else inlet_wrong(x, &s_string); } should be added around line 113 of m_obj.c. What is x->i_symfrom? It is equal to s_string if the inlet is string-capable. See stringinlet_new, below. When is it equal to zero? Also in m_obj.c, obj_init needs a line around 255: class_addstring(inlet_class, inlet_string); In m_class.c, line 794ish we add the line: case 't': SETSTRING(at, va_arg(ap, t_string *)); break; to pd_vmess. Now we need to add SETSTRING to m_pd.h, line 267ish: #define SETSTRING(atom, st) ((atom)->a_type = A_STRING, (atom)->a_w.w_string = (st)) To be able to add the inlet to an object we need to add this to m_obj.c around line 200: t_inlet *stringinlet_new(t_object *owner, t_string **stp) { t_inlet *x = (t_inlet *)pd_new(stringinlet_class), *y, *y2; x->i_owner = owner; x->i_dest = 0; x->i_symfrom = &s_string; x->i_stringslot = stp; x->i_next = 0; if (y = owner->ob_inlet) { while (y2 = y->i_next) y = y2; y->i_next = x; } else owner->ob_inlet = x; return (x); } We need to add stringinlet_class at line 37 of m_obj.c: static t_class *inlet_class, *pointerinlet_class, *floatinlet_class, *symbolinlet_class, *stringinlet_class; Also define a stringslot and add it to the union inletunion at line 12 of m_obj.c: union inletunion { t_symbol *iu_symto; t_gpointer *iu_pointerslot; t_float *iu_floatslot; t_symbol **iu_symslot; t_string **iu_stringslot; t_sample iu_floatsignalvalue; }; and at line 35ish: #define i_stringslot i_un.iu_stringslot Now the only thing that doesn't work properly is messages with strings replacing their $ arguments. For instance, sending [append $1( to a [str join] should set the appended string (also set by the creation argument or the second inlet), but instead gives "no method for string" with a string, and crashes pd with non-string values. Hop on over to g_text.c, where message lives. There's a t_message class: typedef struct _message { t_text m_text; t_messresponder m_messresponder; t_glist *m_glist; t_clock *m_clock; } t_message; ...and a t_messresponder which keeps the message's outlet: typedef struct _messresponder { t_pd mr_pd; t_outlet *mr_outlet; } t_messresponder; The message object is set up in g_text_setup, not message_setup as you might expect. I guess this is to stop a patcher from adding one of these as a [message] object.(?) g_text_setup also sets up messresponder and gatom. Less naively we add: case A_STRING: if (nargs == 1) pd_string(target, stackwas->a_w.w_string); else pd_list(target, 0, nargs, stackwas); after gotmess in m_binbuf.c, line 678ish. This results in pd_string being called when a message receives a string, but because message's string method is default_string, the string doesn't get through. So what we need to do now is set message's string method... At line 1337 of g_text.c add: class_addstring(message_class, message_string); and static void message_string(t_message *x, t_string *st) { t_atom at; SETSTRING(&at, st); binbuf_eval(x->m_text.te_binbuf, &x->m_messresponder.mr_pd, 1, &at); } at line 310. Now we can send a string to a [set $1( message to [string join] and it will set the append string just as if the string went directly to the second inlet. Unfortunately, other kinds of message will usually crash pd. Probably we need a way to be sure that an [append $1( or [compare $1( message really has a string attached. Or is the fact that we never specified a m_messresponder for string types to blame? We add: static void messresponder_string(t_messresponder *x, t_string *st) { /* MP 20070107 string type */ outlet_string(x->mr_outlet, st); } at line 279 of g_text.c. Same thing happens. Add EXTERN t_string *atom_getstring(t_atom *a); to m_pd.h line 280ish and t_string *atom_getstring(t_atom *a) /* MP 20070108 */ { static unsigned char c = 0;/* a default string to avoid null pointers. This should be somewhere else...? */ static t_string st = {1L, &c}; if (a->a_type == A_STRING) return (a->a_w.w_string); else return (&st); } around line 36 of m_atom.c All these additions should work without breaking anything else as long as externals access pd only via the functions exposed in m_pd.h. Probably we'll need to add somthing in g_io.c as well...