From 2d583214ac6cd42616695fb6795995bd8bfce6d7 Mon Sep 17 00:00:00 2001
From: Jonathan Wilkes <jon.w.wilkes@gmail.com>
Date: Mon, 2 Nov 2020 22:48:35 -0500
Subject: [PATCH] add right outlet for soundfiler, improve error messages for
 it and writesf~

---
 pd/doc/5.reference/soundfiler-help.pd         |  65 +-
 pd/src/d_soundfile.c                          | 597 ++++++++++++------
 scripts/regression_tests.pd                   |   8 +-
 .../soundfiler_read_coverage.pd               |  56 ++
 .../soundfiler_write_coverage.pd              |  56 ++
 .../writesf~_open_coverage.pd                 |  37 ++
 scripts/utils/method-error.pd                 |  20 +
 7 files changed, 610 insertions(+), 229 deletions(-)
 create mode 100644 scripts/regression_tests/soundfiler_read_coverage.pd
 create mode 100644 scripts/regression_tests/soundfiler_write_coverage.pd
 create mode 100644 scripts/regression_tests/writesf~_open_coverage.pd
 create mode 100644 scripts/utils/method-error.pd

diff --git a/pd/doc/5.reference/soundfiler-help.pd b/pd/doc/5.reference/soundfiler-help.pd
index 3fa80f90a..f24e1a5d9 100644
--- a/pd/doc/5.reference/soundfiler-help.pd
+++ b/pd/doc/5.reference/soundfiler-help.pd
@@ -1,10 +1,10 @@
-#N canvas 429 34 555 619 10;
+#N canvas 75 64 555 619 10;
 #X obj 0 595 cnv 15 552 21 empty \$0-pddp.cnv.footer empty 20 12 0
-14 -228856 -66577 0;
+14 #dcdcdc #404040 0;
 #X obj 0 0 cnv 15 552 40 empty \$0-pddp.cnv.header soundfiler 3 12
-0 18 -204280 -1 0;
-#X obj 0 387 cnv 3 550 3 empty \$0-pddp.cnv.inlets inlets 8 12 0 13
--228856 -1 0;
+0 18 #c4dcdc #000000 0;
+#X obj 0 352 cnv 3 550 3 empty \$0-pddp.cnv.inlets inlets 8 12 0 13
+#dcdcdc #000000 0;
 #N canvas 490 283 494 344 META 0;
 #X text 12 105 LIBRARY internal;
 #X text 12 145 WEBSITE http://crca.ucsd.edu/~msp/;
@@ -19,16 +19,16 @@ Wilkes revised the patch to conform to the PDDP template for Pd version
 #X text 12 5 KEYWORDS control array filesystem;
 #X text 12 165 RELEASE_DATE 1997;
 #X restore 500 597 pd META;
-#X obj 0 487 cnv 3 550 3 empty \$0-pddp.cnv.outlets outlets 8 12 0
-13 -228856 -1 0;
+#X obj 0 452 cnv 3 550 3 empty \$0-pddp.cnv.outlets outlets 8 12 0
+13 #dcdcdc #000000 0;
 #X obj 0 529 cnv 3 550 3 empty \$0-pddp.cnv.argument arguments 8 12
-0 13 -228856 -1 0;
+0 13 #dcdcdc #000000 0;
 #X obj 0 554 cnv 3 550 3 empty \$0-pddp.cnv.more_info more_info 8 12
-0 13 -228856 -1 0;
+0 13 #dcdcdc #000000 0;
 #X text 98 533 (none);
 #N canvas 217 519 428 106 Related_objects 0;
 #X obj 0 1 cnv 15 425 20 empty \$0-pddp.cnv.subheading empty 3 12 0
-14 -204280 -1 0;
+14 #c4dcdc #000000 0;
 #X text 7 2 [soundfiler] Related Objects;
 #X obj 22 43 tabwrite~;
 #X obj 22 69 tabread4~;
@@ -36,16 +36,16 @@ Wilkes revised the patch to conform to the PDDP template for Pd version
 #X obj 143 69 writesf~;
 #X obj 87 69 readsf~;
 #X restore 102 597 pd Related_objects;
-#X obj 78 396 cnv 17 3 80 empty \$0-pddp.cnv.let.0 0 5 9 0 16 -228856
--162280 0;
-#X text 98 495 float;
-#X obj 78 496 cnv 17 3 17 empty \$0-pddp.cnv.let.0 0 5 9 0 16 -228856
--162280 0;
+#X obj 78 361 cnv 17 3 80 empty \$0-pddp.cnv.let.0 0 5 9 0 16 #dcdcdc
+#9c9c9c 0;
+#X text 98 460 float;
+#X obj 78 461 cnv 17 3 17 empty \$0-pddp.cnv.let.0 0 5 9 0 16 #dcdcdc
+#9c9c9c 0;
 #X obj 477 10 soundfiler;
 #X text 11 23 read and write soundfiles to arrays;
-#X text 98 395 read;
-#X text 98 412 write;
-#X text 168 495 - the output specifies the total number of samples
+#X text 98 360 read;
+#X text 98 377 write;
+#X text 168 460 - the output specifies the total number of samples
 that have been read or written.;
 #X obj 20 293 soundfiler;
 #X floatatom 20 317 0 0 0 0 - - -;
@@ -54,16 +54,16 @@ that have been read or written.;
 #X text 322 224 write a file;
 #X text 358 268 write stereo;
 #N canvas 0 0 450 300 (subpatch) 0;
-#X array sf-array1 77971 float 0;
-#X coords 0 1 77971 -1 130 70 1;
-#X restore 135 306 graph;
+#X array sf-array1 77971 float 0 black black;
+#X coords 0 1 77970 -1 130 50 1;
+#X restore 185 296 graph;
 #N canvas 0 0 450 300 (subpatch) 0;
-#X array sf-array2 77971 float 0;
-#X coords 0 1 77971 -1 130 70 1;
-#X restore 288 306 graph;
+#X array sf-array2 77971 float 0 black black;
+#X coords 0 1 77970 -1 130 50 1;
+#X restore 338 296 graph;
 #N canvas 110 93 428 434 flags 0;
 #X obj 0 0 cnv 15 425 20 empty \$0-pddp.cnv.subheading empty 3 12 0
-14 -204280 -1 0;
+14 #c4dcdc #000000 0;
 #X text 19 37 When reading you can leave soundfiler to figure out which
 of the three known soundfile formats the file belongs to or override
 all header information using the "-raw" flag.;
@@ -88,9 +88,9 @@ prefers.;
 #X text 17 400 The number of channels is limited to 64;
 #X text 37 371 -rate <sample rate>;
 #X text 7 1 [soundfiler] Flags;
-#X restore 172 459 pd flags;
-#X text 168 412 - write a soundfile.;
-#X text 169 428 The "read" and "write" messages accept flags. See the
+#X restore 172 424 pd flags;
+#X text 168 377 - write a soundfile.;
+#X text 169 393 The "read" and "write" messages accept flags. See the
 subpatch below for details:;
 #X msg 20 138 read ../sound/bell.aiff sf-array2;
 #X msg 20 161 read -resize ../sound/bell.aiff sf-array2;
@@ -102,7 +102,7 @@ subpatch below for details:;
 ;
 #X text 399 197 overriding everything;
 #X text 398 183 ...or even;
-#X text 168 395 - read a soundfile.;
+#X text 168 360 - read a soundfile.;
 #X text 17 41 The [soundfiler] object reads and writes floating point
 arrays to binary soundfiles which may contain 2 or 3 byte fixed point
 or 4 byte floating point samples in wave \, aiff \, or next formats
@@ -113,7 +113,14 @@ and unsupplied channels are zeroed out).;
 #X obj 4 597 pddp/pddplink all_about_help_patches.pd -text Usage Guide
 ;
 #X obj 98 575 pddp/pddplink all_about_arrays.pd;
+#X obj 78 497 cnv 17 3 17 empty \$0-pddp.cnv.let.0 0 5 9 0 16 #dcdcdc
+#9c9c9c 0;
+#X text 98 496 list;
+#X obj 77 318 print sf_stats;
+#X text 168 496 - stats for the file being read or written: <samplerate>
+<headersize> <nchannels> <bytespersample> <endianness>;
 #X connect 17 0 18 0;
+#X connect 17 1 43 0;
 #X connect 28 0 17 0;
 #X connect 29 0 17 0;
 #X connect 30 0 17 0;
diff --git a/pd/src/d_soundfile.c b/pd/src/d_soundfile.c
index a8a1e1962..f0720e7c3 100644
--- a/pd/src/d_soundfile.c
+++ b/pd/src/d_soundfile.c
@@ -675,201 +675,293 @@ static void soundfile_xferin_float(int sfchannels, int nvecs, t_float **vecs,
     */
 
 
-static void argerror(void *obj, const char *fmt, ...)
+static void argerror(void *obj, t_symbol *s, int argc, t_atom *argv,
+    const char *fmt, ...)
 {
-    char msg[MAXPDSTRING];
+    char error_str[MAXPDSTRING];
+    char *user_msg; /* message from user that triggered the error */
+    int len;
     char *classname = class_getname(pd_class((t_pd *)obj));
     va_list ap;
+    t_binbuf *b = binbuf_new();
     va_start(ap, fmt);
-    vsnprintf(msg, MAXPDSTRING-1, fmt, ap);
+    vsnprintf(error_str, MAXPDSTRING-1, fmt, ap);
     va_end(ap);
-    pd_error(obj, "%s: %s", classname, msg);
+
+    binbuf_addv(b, "s", s);
+    binbuf_add(b, argc, argv);
+    binbuf_gettext(b, &user_msg, &len); /* not null-terminated! */
+    
+    pd_error(obj, "%s: '%.*s': %s",
+        classname,
+        len < MAXPDSTRING ? len : MAXPDSTRING,
+        user_msg,
+        error_str);
 }
 
-static int verify_flag(void *obj, char *flag)
+static int flag_missing_floatarg(void *obj, t_symbol *s, int argc,
+    t_atom *argv, char *flag, int flagc, t_atom *flagv)
 {
-    if (!flag)
+        /* First check if our flag has an arg at all. If not, error out.
+           (flagc includes the flag itself.) */
+    if (flagc < 2)
     {
-        argerror(obj, "empty symbol detected");
-        return 0;
+        argerror(obj, s, argc, argv,
+            "'%s' flag expects a float argument", flag);
+        return 1;
     }
-    if (flag[0] != '-')
+    if (flagv[1].a_type != A_FLOAT)
     {
-        argerror(obj, "expected '-%s' flag but got '%s'", flag, flag);
-        return 0;
+        argerror(obj, s, argc, argv, "'%s' flag expects a float but got '%s'",
+           flag,
+           flagv[1].a_type == A_SYMBOL ?
+           flagv[1].a_w.w_symbol->s_name :
+           "unexpected arg type");
+        return 1;
     }
-    return 1;
+    return 0;
 }
 
-static int flag_and_floatarg(void *obj, char *flag, int argc, t_atom *argv)
+static int flag_has_unexpected_floatarg(void *obj, t_symbol *s,
+    int argc, t_atom *argv, char *flag, int flagc, t_atom *flagv)
 {
-    if (!verify_flag(obj, flag))
-        return 0;
-    if (argc < 2)
-    {
-        argerror(obj, "need a float argument for '-%s' flag", flag);
-        return 0;
-    }
-    if (argv[1].a_type != A_FLOAT)
+    if (flagc < 2) return 0;
+    if (flagv[1].a_type == A_FLOAT)
     {
-        argerror(obj, "'-%s' flag expects float but got '%s'",
-           flag,
-           argv[1].a_type == A_SYMBOL ?
-           argv[1].a_w.w_symbol->s_name :
-           "unexpected arg type");
-        return 0;
+        argerror(obj, s, argc, argv,
+            "'%s' flag does not accept a float argument", flag);
+        return 1;
     }
-    return 1;
+    return 0;
 }
 
-static int matchstring(char *str, char *flag)
+    /* catch filenames that are flags, with and without pre-fixed '-' */
+static int file_is_a_flag_name(t_symbol *sym, int *nodash)
 {
-    return (!strcmp(str, flag) || !strcmp(str+1, flag));
+    char *s = sym->s_name;
+    if (s[0] == '-')
+    {
+        s++;
+        *nodash = 1;
+    }
+    else
+        *nodash = 0;
+
+    if (!strcmp(s, "skip")
+        || !strcmp(s, "nframes")
+        || !strcmp(s, "bytes")
+        || !strcmp(s, "normalize")
+        || !strcmp(s, "wave")
+        || !strcmp(s, "nextstep")
+        || !strcmp(s, "aiff")
+        || !strcmp(s, "big")
+        || !strcmp(s, "little")
+        || !strcmp(s, "r")
+        || !strcmp(s, "rate"))
+    {
+        return 1;
+    }
+    else
+        return 0;
 }
 
     /* the routine which actually does the work should LATER also be called
     from garray_write16. */
 
     /* Parse arguments for writing.  The "obj" argument is only for flagging
-    errors.  For streaming to a file the "normalize", "onset" and "nframes"
+    errors.  For streaming to a file the "normalize", "skip" and "nframes"
     arguments shouldn't be set but the calling routine flags this. */
 
-static int soundfiler_writeargparse(void *obj, int *p_argc, t_atom **p_argv,
+    /* Also note that streaming objects like writesf~ don't take args after
+       the filename, while soundfiler does to specify the source tables */
+static int soundfiler_writeargparse(void *obj, t_symbol *s,
+    int *p_argc, t_atom **p_argv,
     t_symbol **p_filesym,
     int *p_filetype, int *p_bytespersamp, int *p_swap, int *p_bigendian,
     int *p_normalize, long *p_onset, long *p_nframes, t_float *p_rate)
 {
+    /* copies for convenience, and for the ruthless mutation below. :) */
     int argc = *p_argc;
+    int ac = argc;
     t_atom *argv = *p_argv;
+    t_atom *av = argv;
     int bytespersamp = 2, bigendian = 0,
         endianness = -1, swap, filetype = -1, normalize = 0;
     long onset = 0, nframes = 0x7fffffff;
     t_symbol *filesym;
     t_float rate = -1;
     
-    while (argc > 0 && argv->a_type == A_SYMBOL)
+    while (ac > 0 && atom_getsymbol(av)->s_name[0] == '-')
     {
-        char *flag = argv->a_w.w_symbol->s_name;
-        if (matchstring(flag, "skip"))
+        char *flag = av->a_w.w_symbol->s_name;
+        if (!strcmp(flag, "-skip"))
         {
-            if (!flag_and_floatarg(obj, flag, argc, argv))
+            if (flag_missing_floatarg(obj, s, argc, argv, flag, ac, av))
                 goto usage;
-            if ((onset = argv[1].a_w.w_float) < 0)
+            if ((onset = av[1].a_w.w_float) < 0)
             {
-                argerror(obj, "'-skip' flag does not allow a negative number");
+                argerror(obj, s, argc, argv,
+                    "'-skip' flag does not allow a negative number");
                 goto usage;
             }
-            argc -= 2; argv += 2;
+            ac -= 2; av += 2;
         }
-        else if (matchstring(flag, "nframes"))
+        else if (!strcmp(flag, "-nframes"))
         {
-            if (!flag_and_floatarg(obj, flag, argc, argv))
+            if (flag_missing_floatarg(obj, s, argc, argv, flag, ac, av))
                 goto usage;
-            if ((nframes = argv[1].a_w.w_float) < 0)
+            if ((nframes = av[1].a_w.w_float) < 0)
             {
-                argerror(obj, "'-nframes' flag does not allow a negative "
-                    "number");
+                argerror(obj, s, argc, argv,
+                    "'-nframes' flag does not allow a negative number");
                 goto usage;
             }
-            argc -= 2; argv += 2;
+            ac -= 2; av += 2;
         }
-        else if (matchstring(flag, "bytes"))
+        else if (!strcmp(flag, "-bytes"))
         {
-            if (!flag_and_floatarg(obj, flag, argc, argv))
+            if (flag_missing_floatarg(obj, s, argc, argv, flag, ac, av))
                 goto usage;
-            if ((bytespersamp = argv[1].a_w.w_float) < 2 ||
+            if ((bytespersamp = av[1].a_w.w_float) < 2 ||
                    bytespersamp > 4)
             {
-                argerror(obj, "'-bytes' flag requires a number "
-                    "between 2 and 4");
+                argerror(obj, s, argc, argv,
+                    "'-bytes' flag requires a number between 2 and 4");
                 goto usage;
             }
-            argc -= 2; argv += 2;
+            ac -= 2; av += 2;
         }
-        else if (matchstring(flag, "normalize"))
+        else if (!strcmp(flag, "-normalize"))
         {
-            if (!verify_flag(obj, flag))
+            if (flag_has_unexpected_floatarg(obj, s, argc, argv,
+                flag, ac, av))
+            {
                 goto usage;
+            }
             normalize = 1;
-            argc -= 1; argv += 1;
+            ac -= 1; av += 1;
         }
-        else if (matchstring(flag, "wave"))
+        else if (!strcmp(flag, "-wave"))
         {
-            if (!verify_flag(obj, flag))
+            if (flag_has_unexpected_floatarg(obj, s, argc, argv,
+                flag, ac, av))
+            {
                 goto usage;
+            }
             filetype = FORMAT_WAVE;
-            argc -= 1; argv += 1;
+            ac -= 1; av += 1;
         }
-        else if (matchstring(flag, "nextstep"))
+        else if (!strcmp(flag, "-nextstep"))
         {
-            if (!verify_flag(obj, flag))
+            if (flag_has_unexpected_floatarg(obj, s, argc, argv,
+                flag, ac, av))
+            {
                 goto usage;
+            }
             filetype = FORMAT_NEXT;
-            argc -= 1; argv += 1;
+            ac -= 1; av += 1;
         }
-        else if (matchstring(flag, "aiff"))
+        else if (!strcmp(flag, "-aiff"))
         {
-            if (!verify_flag(obj, flag))
-               goto usage;
+            if (flag_has_unexpected_floatarg(obj, s, argc, argv,
+                flag, ac, av))
+            {
+                goto usage;
+            }
             filetype = FORMAT_AIFF;
-            argc -= 1; argv += 1;
+            ac -= 1; av += 1;
         }
-        else if (matchstring(flag, "big"))
+        else if (!strcmp(flag, "-big"))
         {
-            if (!verify_flag(obj, flag))
-               goto usage;
-
+            if (flag_has_unexpected_floatarg(obj, s, argc, argv,
+                flag, ac, av))
+            {
+                goto usage;
+            }
             endianness = 1;
-            argc -= 1; argv += 1;
+            ac -= 1; av += 1;
         }
-        else if (matchstring(flag, "little"))
+        else if (!strcmp(flag, "-little"))
         {
-            if (!verify_flag(obj, flag))
+            if (flag_has_unexpected_floatarg(obj, s, argc, argv,
+                flag, ac, av))
+            {
                 goto usage;
+            }
             endianness = 0;
-            argc -= 1; argv += 1;
+            ac -= 1; av += 1;
         }
-        else if (matchstring(flag, "r") || matchstring(flag, "rate"))
+        else if (!strcmp(flag, "-r") || !strcmp(flag, "-rate"))
         {
-            if (!flag_and_floatarg(obj, flag, argc, argv))
+            if (flag_missing_floatarg(obj, s, argc, argv,
+                flag, ac, av))
+            {
                 goto usage;
-            if ((rate = argv[1].a_w.w_float) <= 0)
+            }
+            if ((rate = av[1].a_w.w_float) <= 0)
             {
-                argerror(obj, "'%s' flag must have a float arg greater than "
-                    "zero", flag);
+                argerror(obj, s, argc, argv,
+                    "'%s' flag must have a float arg greater than zero", flag);
                 goto usage;
             }
-            argc -= 2; argv += 2;
+            ac -= 2; av += 2;
         }
         else
         {
-            argerror(obj, "unknown flag '%s'", flag);
+            argerror(obj, s, argc, argv, "unknown flag '%s'", flag);
             goto usage;
         }
     }
-    if (!argc || argv->a_type != A_SYMBOL)
+    if (ac < 1)
+    {
+            /* a bit tricky-- writesf~ "open" method doesn't need table args */
+        argerror(obj, s, argc, argv, "%s",
+            s == gensym("open") ? "need a filename" :
+            "need a filename and table argument(s)");
         goto usage;
-    filesym = argv->a_w.w_symbol;
-    
+    }
+        /* Now that we know we have at least one arg, let's make sure it's
+           a symbol. */
+    if (av->a_type != A_SYMBOL)
+    {
+        argerror(obj, s, argc, argv, "filename must be a symbol");
+        goto usage;
+    }
+    filesym = av->a_w.w_symbol;
+        /* check if filesym is a flag name, and warn if so. */
+    int nodash;
+    if (file_is_a_flag_name(filesym, &nodash))
+    {
+        post("warning: filename '%s' looks like a flag%s",
+            filesym->s_name, nodash ? " name" : "");
+    }
         /* check if format not specified and fill in */
     if (filetype < 0) 
     {
         if (strlen(filesym->s_name) >= 5 &&
-                        (!strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".aif") ||
-                        !strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".AIF")))
-                filetype = FORMAT_AIFF;
+             (!strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".aif") ||
+              !strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".AIF")))
+        {
+            filetype = FORMAT_AIFF;
+        }
         if (strlen(filesym->s_name) >= 6 &&
-                        (!strcmp(filesym->s_name + strlen(filesym->s_name) - 5, ".aiff") ||
-                        !strcmp(filesym->s_name + strlen(filesym->s_name) - 5, ".AIFF")))
-                filetype = FORMAT_AIFF;
+             (!strcmp(filesym->s_name + strlen(filesym->s_name) - 5, ".aiff") ||
+              !strcmp(filesym->s_name + strlen(filesym->s_name) - 5, ".AIFF")))
+        {
+            filetype = FORMAT_AIFF;
+        }
         if (strlen(filesym->s_name) >= 5 &&
-                        (!strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".snd") ||
-                        !strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".SND")))
-                filetype = FORMAT_NEXT;
+             (!strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".snd") ||
+              !strcmp(filesym->s_name + strlen(filesym->s_name) - 4, ".SND")))
+        {
+            filetype = FORMAT_NEXT;
+        }
         if (strlen(filesym->s_name) >= 4 &&
-                        (!strcmp(filesym->s_name + strlen(filesym->s_name) - 3, ".au") ||
-                        !strcmp(filesym->s_name + strlen(filesym->s_name) - 3, ".AU")))
-                filetype = FORMAT_NEXT;
+             (!strcmp(filesym->s_name + strlen(filesym->s_name) - 3, ".au") ||
+              !strcmp(filesym->s_name + strlen(filesym->s_name) - 3, ".AU")))
+        {
+            filetype = FORMAT_NEXT;
+        }
         if (filetype < 0)
             filetype = FORMAT_WAVE;
     }
@@ -878,7 +970,8 @@ static int soundfiler_writeargparse(void *obj, int *p_argc, t_atom **p_argv,
     {
         if (filetype == FORMAT_AIFF)
         {
-            pd_error(obj, "AIFF floating-point file format unavailable");
+            argerror(obj, s, argc, argv,
+                "AIFF floating-point file format unavailable");
             goto usage;
         }
     }
@@ -902,10 +995,10 @@ static int soundfiler_writeargparse(void *obj, int *p_argc, t_atom **p_argv,
     else bigendian = endianness;
     swap = (bigendian != garray_ambigendian());
     
-    argc--; argv++;
+    ac--; av++;
     
-    *p_argc = argc;
-    *p_argv = argv;
+    *p_argc = ac;
+    *p_argv = av;
     *p_filesym = filesym;
     *p_filetype = filetype;
     *p_bytespersamp = bytespersamp;
@@ -941,8 +1034,8 @@ static int create_soundfile(t_canvas *canvas, const char *filename,
         if (strcmp(filenamebuf + strlen(filenamebuf)-4, ".snd"))
             strcat(filenamebuf, ".snd");
         if (bigendian)
-            strncpy(nexthdr->ns_fileid, ".snd", 4);
-        else strncpy(nexthdr->ns_fileid, "dns.", 4);
+            memcpy(nexthdr->ns_fileid, ".snd", 4);
+        else memcpy(nexthdr->ns_fileid, "dns.", 4);
         nexthdr->ns_onset = swap4(sizeof(*nexthdr), swap);
         nexthdr->ns_length = 0;
         nexthdr->ns_format = swap4((bytespersamp == 3 ? NS_FORMAT_LINEAR_24 :
@@ -960,18 +1053,18 @@ static int create_soundfile(t_canvas *canvas, const char *filename,
         if (strcmp(filenamebuf + strlen(filenamebuf)-4, ".aif") &&
             strcmp(filenamebuf + strlen(filenamebuf)-5, ".aiff"))
                 strcat(filenamebuf, ".aif");
-        strncpy(aiffhdr->a_fileid, "FORM", 4);
+        memcpy(aiffhdr->a_fileid, "FORM", 4);
         aiffhdr->a_chunksize = swap4((uint32_t)(datasize +
             sizeof(*aiffhdr) + 4), swap);
-        strncpy(aiffhdr->a_aiffid, "AIFF", 4);
-        strncpy(aiffhdr->a_fmtid, "COMM", 4);
+        memcpy(aiffhdr->a_aiffid, "AIFF", 4);
+        memcpy(aiffhdr->a_fmtid, "COMM", 4);
         aiffhdr->a_fmtchunksize = swap4(18, swap);
         aiffhdr->a_nchannels = swap2(nchannels, swap);
         longtmp = swap4((uint32_t)nframes, swap);
         memcpy(&aiffhdr->a_nframeshi, &longtmp, 4);
         aiffhdr->a_bitspersamp = swap2(8 * bytespersamp, swap);
         makeaiffsamprate(samplerate, aiffhdr->a_samprate);
-        strncpy(((char *)(&aiffhdr->a_samprate))+10, "SSND", 4);
+        memcpy(((char *)(&aiffhdr->a_samprate))+10, "SSND", 4);
         longtmp = swap4((uint32_t)(datasize + 8), swap);
         memcpy(((char *)(&aiffhdr->a_samprate))+14, &longtmp, 4);
         memset(((char *)(&aiffhdr->a_samprate))+18, 0, 8);
@@ -982,11 +1075,11 @@ static int create_soundfile(t_canvas *canvas, const char *filename,
         long datasize = nframes * nchannels * bytespersamp;
         if (strcmp(filenamebuf + strlen(filenamebuf)-4, ".wav"))
             strcat(filenamebuf, ".wav");
-        strncpy(wavehdr->w_fileid, "RIFF", 4);
+        memcpy(wavehdr->w_fileid, "RIFF", 4);
         wavehdr->w_chunksize = swap4((uint32_t)(datasize +
             sizeof(*wavehdr) - 8), swap);
-        strncpy(wavehdr->w_waveid, "WAVE", 4);
-        strncpy(wavehdr->w_fmtid, "fmt ", 4);
+        memcpy(wavehdr->w_waveid, "WAVE", 4);
+        memcpy(wavehdr->w_fmtid, "fmt ", 4);
         wavehdr->w_fmtchunksize = swap4(16, swap);
         wavehdr->w_fmttag =
             swap2((bytespersamp == 4 ? WAV_FLOAT : WAV_INT), swap);
@@ -996,11 +1089,10 @@ static int create_soundfile(t_canvas *canvas, const char *filename,
             swap4((int)(samplerate * nchannels * bytespersamp), swap);
         wavehdr->w_nblockalign = swap2(nchannels * bytespersamp, swap);
         wavehdr->w_nbitspersample = swap2(8 * bytespersamp, swap);
-        strncpy(wavehdr->w_datachunkid, "data", 4);
+        memcpy(wavehdr->w_datachunkid, "data", 4);
         wavehdr->w_datachunksize = swap4((uint32_t)datasize, swap);
         headersize = sizeof(t_wave);
     }
-
     canvas_makefilename(canvas, filenamebuf, buf2, FILENAME_MAX);
     sys_bashfilename(buf2, buf2);
     if ((fd = sys_open(buf2, BINCREATE, 0666)) < 0)
@@ -1383,9 +1475,17 @@ static void soundfiler_readascii(t_soundfiler *x, char *filename,
         -maxsize <max-size>
     */
 
+#define RAWSYNTAX "'-raw' flag syntax: " \
+                  "<headersize> <channels> <bytespersample> " \
+                  "<endianness: 'b' for big, 'l' for little, 'n' for auto>"
+
 static void soundfiler_read(t_soundfiler *x, t_symbol *s,
     int argc, t_atom *argv)
 {
+    /* copies of argc, argv so we can iterate without mutating
+       the originals (needed for error reporting below) */
+    int ac = argc;
+    t_atom *av = argv;
     t_soundfile_info info;
     int resize = 0, i, j;
     long skipframes = 0, finalsize = 0,
@@ -1404,48 +1504,100 @@ static void soundfiler_read(t_soundfiler *x, t_symbol *s,
     info.headersize = -1;
     info.bigendian = 0;
     info.bytelimit = 0x7fffffff;
-    while (argc > 0 && argv->a_type == A_SYMBOL)
+    while (ac > 0 && atom_getsymbol(av)->s_name[0] == '-')
     {
-        char *flag = argv->a_w.w_symbol->s_name;
-        if (matchstring(flag, "skip"))
+        char *flag = av->a_w.w_symbol->s_name;
+        if (!strcmp(flag, "-skip"))
         {
-            if (!flag_and_floatarg(x, flag, argc, argv))
-                goto usage;
-            if ((skipframes = argv[1].a_w.w_float) < 0)
+            if (flag_missing_floatarg(x, s, argc, argv, flag, ac, av))
+                goto done;
+            if ((skipframes = av[1].a_w.w_float) < 0)
             {
-                argerror(x, "'-skip' flag does not allow a negative number");
-                goto usage;
+                argerror(x, s, argc, argv,
+                    "'-skip' flag does not allow a negative number");
+                goto done;
             }
-            argc -= 2; argv += 2;
+            ac -= 2; av += 2;
         }
-        else if (matchstring(flag, "ascii"))
+        else if (!strcmp(flag, "-ascii"))
         {
-            if (!verify_flag(x, flag))
-                goto usage;
             if (info.headersize >= 0)
                 post("soundfiler_read: '-raw' overidden by '-ascii'");
             ascii = 1;
-            argc--; argv++;
+            ac--; av++;
         }
-        else if (matchstring(flag, "raw"))
+        else if (!strcmp(flag, "-raw"))
         {
-            if (!verify_flag(x, flag))
-                goto usage;
             if (ascii)
                 post("soundfiler_read: '-raw' overridden by '-ascii'");
-            if (argc < 5 ||
-                argv[1].a_type != A_FLOAT ||
-                ((info.headersize = argv[1].a_w.w_float) < 0) ||
-                argv[2].a_type != A_FLOAT ||
-                ((info.channels = argv[2].a_w.w_float) < 1) ||
-                (info.channels > MAXSFCHANS) || 
-                argv[3].a_type != A_FLOAT ||
-                ((info.bytespersample = argv[3].a_w.w_float) < 2) || 
-                    (info.bytespersample > 4) ||
-                argv[4].a_type != A_SYMBOL ||
-                    ((endianness = argv[4].a_w.w_symbol->s_name[0]) != 'b'
-                    && endianness != 'l' && endianness != 'n'))
-                        goto usage;
+            if (ac < 5)
+            {
+                argerror(x, s, argc, argv,
+                    "'-raw' flag needs four arguments\n" RAWSYNTAX);
+                goto done;
+            }
+            if (av[1].a_type != A_FLOAT)
+            {
+                argerror(x, s, argc, argv,
+                  "'-raw' flag needs a float for the headersize\n" RAWSYNTAX);
+                goto done;
+            }
+            info.headersize = av[1].a_w.w_float;
+            if (info.headersize < 0)
+            {
+                argerror(x, s, argc, argv,
+                    "'-raw' headersize cannot be less than zero\n"RAWSYNTAX);
+                goto done;
+            }
+            if (av[2].a_type != A_FLOAT)
+            {
+                argerror(x, s, argc, argv,
+                  "'-raw' flag needs a float to specify channels\n"RAWSYNTAX);
+                goto done;
+            }
+            info.channels = av[2].a_w.w_float;
+            if (info.channels < 1)
+            {
+                argerror(x, s, argc, argv,
+                    "'-raw' flag needs at least one channel\n" RAWSYNTAX);
+                goto done;
+            }
+            if (info.channels > MAXSFCHANS)
+            {
+                argerror(x, s, argc, argv,
+                  "'-raw' channels value %d exceeds "
+                  "maximum of %d channels\n" RAWSYNTAX,
+                    info.channels, MAXSFCHANS);
+                goto done;
+            }
+            if (av[3].a_type != A_FLOAT)
+            {
+                argerror(x, s, argc, argv,
+                    "'-raw' flag needs a float to specify "
+                    "bytes per sample\n" RAWSYNTAX);
+                goto done;
+            }
+            info.bytespersample = av[3].a_w.w_float;
+            if (info.bytespersample < 2)
+            {
+                argerror(x, s, argc, argv,
+                    "'-raw' bytes per sample must be at least 2\n" RAWSYNTAX);
+                goto done;
+            }
+            if (info.bytespersample > 4)
+            {
+                argerror(x, s, argc, argv,
+                   "'-raw' bytes per sample must be less than 4\n" RAWSYNTAX);
+                goto done;
+            }
+            if (av[4].a_type != A_SYMBOL ||
+                ((endianness = av[4].a_w.w_symbol->s_name[0]) != 'b'
+                  && endianness != 'l' && endianness != 'n'))
+            {
+                argerror(x, s, argc, argv,
+                   "'-raw' endianness must be 'l' or 'b' or 'n'\n" RAWSYNTAX);
+                goto done;
+            }
             if (endianness == 'b')
                 info.bigendian = 1;
             else if (endianness == 'l')
@@ -1453,61 +1605,85 @@ static void soundfiler_read(t_soundfiler *x, t_symbol *s,
             else
                 info.bigendian = garray_ambigendian();
             info.samplerate = sys_getsr();
-            argc -= 5; argv += 5;
+            ac -= 5; av += 5;
         }
-        else if (matchstring(flag, "resize"))
+        else if (!strcmp(flag, "-resize"))
         {
-            if (!verify_flag(x, flag))
-                goto usage;
+            if (flag_has_unexpected_floatarg(x, s, argc, argv,
+                flag, ac, av))
+            {
+                goto done;
+            }
             resize = 1;
-            argc -= 1; argv += 1;
+            ac -= 1; av += 1;
         }
-        else if (matchstring(flag, "maxsize"))
+        else if (!strcmp(flag, "-maxsize"))
         {
-            if (!flag_and_floatarg(x, flag, argc, argv))
-                goto usage;
-            maxsize = argv[1].a_w.w_float;
-            if (maxsize > LONG_MAX)
+            if (flag_missing_floatarg(x, s, argc, argv, flag, ac, av))
+                goto done;
+            t_float fmax = av[1].a_w.w_float;
+            if (fmax > (t_float)LONG_MAX)
             {
-                argerror(x, "'-maxsize' flag cannot be greater than %d. "
-                    "Setting '-maxsize' to %d and continuing", LONG_MAX,
-                    LONG_MAX);
+                argerror(x, s, argc, argv,
+                    "'-maxsize' overflow detected. "
+                    "Setting '-maxsize' to maximum legal value (%g) and "
+                    "continuing...", (t_float)LONG_MAX);
                 maxsize = LONG_MAX;
             }
-            if (maxsize < 0)
+            else if (fmax < 0.)
             {
-                argerror(x, "'-maxsize' flag cannot be less than zero");
-                goto usage;
+                argerror(x, s, argc, argv,
+                    "'-maxsize' flag cannot be less than zero");
+                goto done;
+            }
+            else
+            {
+                maxsize = (long)fmax;
             }
             resize = 1;     /* maxsize implies resize. */
-            argc -= 2; argv += 2;
+            ac -= 2; av += 2;
         }
         else
         {
-            argerror(x, "unknown flag '%s'", flag);
-            goto usage;
+            argerror(x, s, argc, argv, "unknown flag '%s'", flag);
+            goto done;
         }
     }
-    if (argc < 1 || argc > MAXSFCHANS + 1 || argv[0].a_type != A_SYMBOL)
-        goto usage;
-    filename = argv[0].a_w.w_symbol->s_name;
-    argc--; argv++;
+    if (ac < 1)
+    {
+        argerror(x, s, argc, argv, "need filename and table argument(s)");
+        goto done;
+    }
+    if (ac > MAXSFCHANS + 1)
+    {
+        argerror(x, s, argc, argv,
+            "cannot write more than %d channels", MAXSFCHANS);
+        goto done;
+    }
+    if (av->a_type != A_SYMBOL)
+    {
+        argerror(x, s, argc, argv, "filename must be a symbol");
+        goto done;
+    }
+    filename = av[0].a_w.w_symbol->s_name;
+    ac--; av++;
     
-    for (i = 0; i < argc; i++)
+    for (i = 0; i < ac; i++)
     {
         int vecsize;
-        if (argv[i].a_type != A_SYMBOL)
-            goto usage;
+        if (av[i].a_type != A_SYMBOL)
+            goto done;
         if (!(garrays[i] =
-            (t_garray *)pd_findbyclass(argv[i].a_w.w_symbol, garray_class)))
+            (t_garray *)pd_findbyclass(av[i].a_w.w_symbol, garray_class)))
         {
-            pd_error(x, "%s: no such table", argv[i].a_w.w_symbol->s_name);
+            argerror(x, s, argc, argv, "%s: no such table",
+                av[i].a_w.w_symbol->s_name);
             goto done;
         }
         else if (!garray_getfloatwords(garrays[i], &vecsize, 
                 &vecs[i]))
             error("%s: bad template for tabwrite",
-                argv[i].a_w.w_symbol->s_name);
+                av[i].a_w.w_symbol->s_name);
         if (finalsize && finalsize != vecsize && !resize)
         {
             post("soundfiler_read: arrays have different lengths; resizing...");
@@ -1517,7 +1693,7 @@ static void soundfiler_read(t_soundfiler *x, t_symbol *s,
     }
     if (ascii)
     {
-        soundfiler_readascii(x, filename, argc, garrays, vecs, resize,
+        soundfiler_readascii(x, filename, ac, garrays, vecs, resize,
             finalsize);
         return;
     }
@@ -1525,21 +1701,33 @@ static void soundfiler_read(t_soundfiler *x, t_symbol *s,
     
     if (fd < 0)
     {
-        pd_error(x, "soundfiler_read: %s: %s", filename, (errno == EIO ?
+        argerror(x, s, argc, argv, "%s: %s", filename, (errno == EIO ?
             "unknown or bad header format" : strerror(errno)));
-        goto done;
+        /* don't bail yet so we can potentially give a warning below */
     }
 
+    /* check if the filename looks like a flag; if so, post a warning */
+    int nodash;
+    if (file_is_a_flag_name(gensym(filename), &nodash))
+    {
+        post("warning: filename '%s' looks like a flag%s",
+            filename, nodash ? " name" : "");
+    }
+
+    /* Now that we've posted our warning, bail if we couldn't open the file */
+    if (fd < 0)
+        goto done;
+
     if (resize)
     {
             /* figure out what to resize to */
         long poswas, eofis, framesinfile;
-        
+
         poswas = lseek(fd, 0, SEEK_CUR);
         eofis = lseek(fd, 0, SEEK_END);
         if (poswas < 0 || eofis < 0 || eofis < poswas)
         {
-            pd_error(x, "soundfiler_read: lseek failed: %ld..%ld", poswas,
+            argerror(x, s, argc, argv, "lseek failed: %ld..%ld", poswas,
                 eofis);
             goto done;
         }
@@ -1547,7 +1735,7 @@ static void soundfiler_read(t_soundfiler *x, t_symbol *s,
         framesinfile = (eofis - poswas) / (info.channels * info.bytespersample);
         if (framesinfile > maxsize)
         {
-            pd_error(x, "soundfiler_read: truncated to %ld elements", maxsize);
+            argerror(x, s, argc, argv, "truncated to %ld elements", maxsize);
             framesinfile = maxsize;
         }
         if (framesinfile > info.bytelimit /
@@ -1557,18 +1745,19 @@ static void soundfiler_read(t_soundfiler *x, t_symbol *s,
                 (info.channels * info.bytespersample);
         }
         finalsize = framesinfile;
-        for (i = 0; i < argc; i++)
+        for (i = 0; i < ac; i++)
         {
             int vecsize;
 
             garray_resize_long(garrays[i], finalsize);
-                /* for sanity's sake let's clear the save-in-patch flag here */
+
+            /* for sanity's sake let's clear the save-in-patch flag here */
             garray_setsaveit(garrays[i], 0);
             if (!garray_getfloatwords(garrays[i], &vecsize, &vecs[i])
                 || (vecsize != framesinfile))
             {
                 /* if the resize failed, garray_resize reported the error */
-                pd_error(x, "resize failed");
+                argerror(x, s, argc, argv, "resize failed");
                 goto done;
             }
         }
@@ -1586,14 +1775,14 @@ static void soundfiler_read(t_soundfiler *x, t_symbol *s,
         nitems = fread(sampbuf, info.channels * info.bytespersample, thisread,
             fp);
         if (nitems <= 0) break;
-        soundfile_xferin_float(info.channels, argc, (t_float **)vecs, itemsread,
+        soundfile_xferin_float(info.channels, ac, (t_float **)vecs, itemsread,
             (unsigned char *)sampbuf, nitems, info.bytespersample,
             info.bigendian, sizeof(t_word)/sizeof(t_sample));
         itemsread += nitems;
     }
         /* zero out remaining elements of vectors */
         
-    for (i = 0; i < argc; i++)
+    for (i = 0; i < ac; i++)
     {
         int vecsize;
         if (garray_getfloatwords(garrays[i], &vecsize, &vecs[i]))
@@ -1601,7 +1790,7 @@ static void soundfiler_read(t_soundfiler *x, t_symbol *s,
                 vecs[i][j].w_float = 0;
     }
         /* zero out vectors in excess of number of channels */
-    for (i = info.channels; i < argc; i++)
+    for (i = info.channels; i < ac; i++)
     {
         int vecsize;
         t_word *foo;
@@ -1610,15 +1799,10 @@ static void soundfiler_read(t_soundfiler *x, t_symbol *s,
                 foo[j].w_float = 0;
     }
         /* do all graphics updates */
-    for (i = 0; i < argc; i++)
+    for (i = 0; i < ac; i++)
         garray_redraw(garrays[i]);
     fclose(fp);
     fd = -1;
-    goto done;
-usage:
-    post("usage: read [flags] filename tablename...");
-    post("flags: -skip <n> -resize -maxsize <n> ...");
-    post("-raw <headerbytes> <channels> <bytespersamp> <endian (b, l, or n)>.");
 done:
     if (fd >= 0)
         sys_close(fd);
@@ -1632,6 +1816,9 @@ done:
 long soundfiler_dowrite(void *obj, t_canvas *canvas,
     int argc, t_atom *argv, t_soundfile_info *info)
 {
+    /* workaround for ruthless argc/argv mutation in writeargparse... */
+    int original_argc = argc;
+    t_atom *original_argv = argv;
     int swap, filetype, normalize, i;
     long onset, nframes, itemswritten = 0, j;
     t_garray *garrays[MAXSFCHANS];
@@ -1643,13 +1830,26 @@ long soundfiler_dowrite(void *obj, t_canvas *canvas,
     t_float samplerate;
     t_symbol *filesym;
 
-    if (soundfiler_writeargparse(obj, &argc, &argv, &filesym, &filetype,
+    if (soundfiler_writeargparse(obj, gensym("write"), &argc, &argv,
+        &filesym, &filetype,
         &info->bytespersample, &swap, &info->bigendian, &normalize, &onset,
         &nframes, &samplerate))
                 goto usage;
     info->channels = argc;
-    if (info->channels < 1 || info->channels > MAXSFCHANS)
+        /* Need at least one table name for a channel to write... */
+    if (info->channels < 1)
+    {
+        argerror(obj, gensym("write"), original_argc, original_argv,
+            "argument for table name missing");
+        goto usage;
+    }
+        /* Can't have more than max number of channels to write */
+    if (info->channels > MAXSFCHANS)
+    {
+        argerror(obj, gensym("write"), original_argc, original_argv,
+            "cannot have more than %d channels", MAXSFCHANS);
         goto usage;
+    }
     if (samplerate < 0)
         info->samplerate = sys_getsr();
     else
@@ -1658,13 +1858,19 @@ long soundfiler_dowrite(void *obj, t_canvas *canvas,
     {
         int vecsize;
         if (argv[i].a_type != A_SYMBOL)
+        {
+            argerror(obj, gensym("write"), original_argc, original_argv,
+                "table name must be a symbol");
             goto usage;
+        }
         if (!(garrays[i] =
             (t_garray *)pd_findbyclass(argv[i].a_w.w_symbol, garray_class)))
         {
-            pd_error(obj, "%s: no such table", argv[i].a_w.w_symbol->s_name);
+            argerror(obj, gensym("write"), original_argc, original_argv,
+                "%s: no such table", argv[i].a_w.w_symbol->s_name);
             goto fail;
         }
+            /* need to check this one-- */
         else if (!garray_getfloatwords(garrays[i], &vecsize, &vecs[i]))
             error("%s: bad template for tabwrite",
                 argv[i].a_w.w_symbol->s_name);
@@ -1673,7 +1879,8 @@ long soundfiler_dowrite(void *obj, t_canvas *canvas,
     }
     if (nframes <= 0)
     {
-        pd_error(obj, "soundfiler_write: no samples at onset %ld", onset);
+        argerror(obj, gensym("write"), original_argc, original_argv,
+            "no samples at onset %ld", onset);
         goto fail;
     }
         /* find biggest sample for normalizing */
@@ -1738,10 +1945,6 @@ long soundfiler_dowrite(void *obj, t_canvas *canvas,
     }
     return ((float)itemswritten); 
 usage:
-    post("usage: write [flags] filename tablename...");
-    post("flags: -skip <n> -nframes <n> -bytes <n> -wave -aiff -nextstep ...");
-    post("-big -little -normalize");
-    post("(defaults to a 16-bit wave file).");
 fail:
     if (fd >= 0)
         sys_close(fd);
@@ -2800,17 +3003,15 @@ static void writesf_open(t_writesf *x, t_symbol *s, int argc, t_atom *argv)
     {
         writesf_stop(x);
     }
-    if (soundfiler_writeargparse(x, &argc,
+    if (soundfiler_writeargparse(x, gensym("open"), &argc,
         &argv, &filesym, &filetype, &bytespersamp, &swap, &bigendian,
         &normalize, &onset, &nframes, &samplerate))
     {
-        pd_error(x,
-            "writesf~: usage: open [-bytes [234]] [-wave,-nextstep,-aiff] ...");
-        post("... [-big,-little] [-rate ####] filename");
+            /* errors handled above in soundfiler_writeargparse */
         return;
     }
     if (normalize || onset || (nframes != 0x7fffffff))
-        pd_error(x, "normalize/onset/nframes argument to writesf~: ignored");
+        pd_error(x, "normalize/skip/nframes argument to writesf~: ignored");
     if (argc)
         pd_error(x, "extra argument(s) to writesf~: ignored");
     pthread_mutex_lock(&x->x_mutex);
diff --git a/scripts/regression_tests.pd b/scripts/regression_tests.pd
index 4b8a8c20c..1e09b6335 100644
--- a/scripts/regression_tests.pd
+++ b/scripts/regression_tests.pd
@@ -1,4 +1,4 @@
-#N canvas -9 -9 771 392 12;
+#N canvas 128 123 771 392 12;
 #X obj 465 281 r \$0-result;
 #X obj 212 239 bng 15 250 50 0 empty empty Run_all 17 7 0 10 #fcfcfc
 #000000 #000000;
@@ -25,7 +25,7 @@ is handy for some binbuf tests.;
 #X obj 198 659 rtest makefilename_double_percent;
 #X obj 198 710 rtest makefilename_code_coverage;
 #N canvas 461 242 450 323 (subpatch) 0;
-#X restore 201 2605 pd;
+#X restore 201 2765 pd;
 #X obj 198 761 rtest makefilename_default;
 #X obj 198 812 rtest makefilename_default_bang;
 #X obj 198 863 rtest makefilename_float;
@@ -66,6 +66,8 @@ is handy for some binbuf tests.;
 #X obj 198 2416 rtest inlet~_fwd_large_message;
 #X obj 198 2471 rtest pow~_negative_numbers;
 #X obj 198 2526 rtest encapsulate;
+#X obj 198 2581 rtest soundfiler_read_coverage;
+#X obj 198 2636 rtest writesf~_open_coverage;
 #X connect 0 0 27 0;
 #X connect 1 0 4 0;
 #X connect 2 0 42 0;
@@ -117,3 +119,5 @@ is handy for some binbuf tests.;
 #X connect 57 0 58 0;
 #X connect 58 0 59 0;
 #X connect 59 0 60 0;
+#X connect 60 0 61 0;
+#X connect 61 0 62 0;
diff --git a/scripts/regression_tests/soundfiler_read_coverage.pd b/scripts/regression_tests/soundfiler_read_coverage.pd
new file mode 100644
index 000000000..ca022f36b
--- /dev/null
+++ b/scripts/regression_tests/soundfiler_read_coverage.pd
@@ -0,0 +1,56 @@
+#N canvas 106 64 1067 916 12;
+#X obj 53 609 ../utils/method-error soundfiler;
+#X obj 168 684 route bang;
+#X obj 168 713 f 0;
+#X obj 235 713 b;
+#X obj 235 742 f 1;
+#X obj 53 773 list prepend;
+#X obj 53 638 list prepend this message should trigger an error:;
+#X obj 53 10 inlet;
+#X msg 53 89 bang;
+#X obj 53 581 receive \$0-;
+#N canvas 771 533 450 425 \$0-too-many-channels 0;
+#X obj 80 31 inlet;
+#X obj 80 60 f \$0;
+#X msg 80 110 \; \$1- read z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z;
+#X connect 0 0 1 0;
+#X connect 1 0 2 0;
+#X restore 169 128 pd \$0-too-many-channels;
+#X msg 169 97 bang;
+#X obj 53 39 trigger bang bang;
+#X text 182 24 This just covers the flags. We probably need to add
+file-loading tests at some point.;
+#X obj 53 802 outlet;
+#X msg 53 158 \; \$1- \$2 -skip \; \$1- \$2 -skip rope \; \$1- \$2
+-skip -1 \; \$1- \$2 -raw \; \$1- \$2 -raw dope \; \$1- \$2 -raw \;
+\$1- \$2 -raw 0 \; \$1- \$2 -raw 0 0 \; \$1- \$2 -raw 0 0 0 \; \$1-
+\$2 -raw dope 0 0 0 \; \$1- \$2 -raw 0 dope 0 0 \; \$1- \$2 -raw 0
+1 dope 0 \; \$1- \$2 -raw 0 1 2 dope \; \$1- \$2 -raw -1 0 0 0 \; \$1-
+\$2 -raw 0 0 2 l \; \$1- \$2 -raw 0 1024 2 n \; \$1- \$2 -raw 0 1 1
+n \; \$1- \$2 -raw 0 1 5 n \; \$1- \$2 -raw 0 1 2 z \; \$1- \$2 -resize
+12 \; \$1- \$2 -maxsize \; \$1- \$2 -maxsize 1e+19 \; \$1- \$2 -reginald
+\; \$1- \$2;
+#X obj 53 118 list \$0 read;
+#X connect 0 0 6 0;
+#X connect 0 1 1 0;
+#X connect 1 0 2 0;
+#X connect 1 1 3 0;
+#X connect 2 0 5 1;
+#X connect 3 0 4 0;
+#X connect 4 0 5 1;
+#X connect 5 0 14 0;
+#X connect 6 0 5 0;
+#X connect 7 0 12 0;
+#X connect 8 0 16 0;
+#X connect 9 0 0 0;
+#X connect 11 0 10 0;
+#X connect 12 0 8 0;
+#X connect 12 1 11 0;
+#X connect 16 0 15 0;
diff --git a/scripts/regression_tests/soundfiler_write_coverage.pd b/scripts/regression_tests/soundfiler_write_coverage.pd
new file mode 100644
index 000000000..aaeee8cfa
--- /dev/null
+++ b/scripts/regression_tests/soundfiler_write_coverage.pd
@@ -0,0 +1,56 @@
+#N canvas 641 120 1067 916 12;
+#X obj 53 669 ../utils/method-error soundfiler;
+#X obj 168 744 route bang;
+#X obj 168 773 f 0;
+#X obj 235 773 b;
+#X obj 235 802 f 1;
+#X obj 53 833 list prepend;
+#X obj 53 698 list prepend this message should trigger an error:;
+#X obj 53 10 inlet;
+#X msg 53 89 bang;
+#X obj 53 641 receive \$0-;
+#N canvas 771 542 450 425 \$0-too-many-channels 1;
+#X obj 80 31 inlet;
+#X obj 80 60 f \$0;
+#X msg 80 110 \; \$1- write z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z
+z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z;
+#X connect 0 0 1 0;
+#X connect 1 0 2 0;
+#X restore 169 128 pd \$0-too-many-channels;
+#X msg 169 97 bang;
+#X obj 53 39 trigger bang bang;
+#X text 182 24 This just covers the flags. We probably need to add
+file-loading tests at some point.;
+#X obj 53 862 outlet;
+#X obj 53 118 list \$0 write;
+#X msg 53 158 \; \$1- \$2 -skip \; \$1- \$2 -skip rope \; \$1- \$2
+-skip -1 \; \$1- \$2 -nframes \; \$1- \$2 -nframes dope \; \$1- \$2
+-nframes -1 \; \$1- \$2 -normalize 12 \; \$1- \$2 -bytes \; \$1- \$2
+-bytes 1 \; \$1- \$2 -bytes 5 \; \$1- \$2 -resize 12 \; \$1- \$2 -wave
+1 \; \$1- \$2 -nextstep 1 \; \$1- \$2 -aiff 1 \; \$1- \$2 -big 1 \;
+\$1- \$2 -little 1 \; \$1- \$2 -r \; \$1- \$2 -rate \; \$1- \$2 -r
+0 \; \$1- \$2 -rate 0 \; \$1- \$2 -reginald \; \$1- \$2 \; \$1- \$2
+12 \; \$1- \$2 foo 12 \; \$1- \$2 -bytes 4 -aiff foo \; \$1- \$2 foo
+no_table \;;
+#X connect 0 0 6 0;
+#X connect 0 1 1 0;
+#X connect 1 0 2 0;
+#X connect 1 1 3 0;
+#X connect 2 0 5 1;
+#X connect 3 0 4 0;
+#X connect 4 0 5 1;
+#X connect 5 0 14 0;
+#X connect 6 0 5 0;
+#X connect 7 0 12 0;
+#X connect 8 0 15 0;
+#X connect 9 0 0 0;
+#X connect 11 0 10 0;
+#X connect 12 0 8 0;
+#X connect 12 1 11 0;
+#X connect 15 0 16 0;
diff --git a/scripts/regression_tests/writesf~_open_coverage.pd b/scripts/regression_tests/writesf~_open_coverage.pd
new file mode 100644
index 000000000..70bb25d73
--- /dev/null
+++ b/scripts/regression_tests/writesf~_open_coverage.pd
@@ -0,0 +1,37 @@
+#N canvas 72 64 1067 916 12;
+#X obj 138 754 route bang;
+#X obj 138 783 f 0;
+#X obj 205 783 b;
+#X obj 205 812 f 1;
+#X obj 23 843 list prepend;
+#X obj 23 708 list prepend this message should trigger an error:;
+#X obj 23 20 inlet;
+#X msg 23 99 bang;
+#X obj 23 651 receive \$0-;
+#X text 42 54 This just covers the flags. We probably need to add file-loading
+tests at some point.;
+#X obj 23 872 outlet;
+#X msg 23 168 \; \$1- \$2 -skip \; \$1- \$2 -skip rope \; \$1- \$2
+-skip -1 \; \$1- \$2 -nframes \; \$1- \$2 -nframes dope \; \$1- \$2
+-nframes -1 \; \$1- \$2 -normalize 12 \; \$1- \$2 -bytes \; \$1- \$2
+-bytes 1 \; \$1- \$2 -bytes 5 \; \$1- \$2 -resize 12 \; \$1- \$2 -wave
+1 \; \$1- \$2 -nextstep 1 \; \$1- \$2 -aiff 1 \; \$1- \$2 -big 1 \;
+\$1- \$2 -little 1 \; \$1- \$2 -r \; \$1- \$2 -rate \; \$1- \$2 -r
+0 \; \$1- \$2 -rate 0 \; \$1- \$2 -reginald \; \$1- \$2 \; \$1- \$2
+12 \; \$1- \$2 foo 12 \; \$1- \$2 -bytes 4 -aiff foo \; \$1- \$2 foo
+extra_arg \;;
+#X obj 23 128 list \$0 open;
+#X obj 23 679 ../utils/method-error writesf~;
+#X connect 0 0 1 0;
+#X connect 0 1 2 0;
+#X connect 1 0 4 1;
+#X connect 2 0 3 0;
+#X connect 3 0 4 1;
+#X connect 4 0 10 0;
+#X connect 5 0 4 0;
+#X connect 6 0 7 0;
+#X connect 7 0 12 0;
+#X connect 8 0 13 0;
+#X connect 12 0 11 0;
+#X connect 13 0 5 0;
+#X connect 13 1 0 0;
diff --git a/scripts/utils/method-error.pd b/scripts/utils/method-error.pd
new file mode 100644
index 000000000..bc2664b8a
--- /dev/null
+++ b/scripts/utils/method-error.pd
@@ -0,0 +1,20 @@
+#N canvas 861 254 781 553 12;
+#X obj 96 28 inlet;
+#X obj 201 125 unpost;
+#X obj 176 178 list;
+#X obj 176 267 outlet;
+#X obj 240 178 \$1;
+#X obj 96 267 outlet;
+#X obj 96 86 trigger anything bang anything bang;
+#X text 143 29 test a method that triggers;
+#X text 143 49 an error;
+#X text 55 220 the message;
+#X text 159 221 the error (formatted as a single symbol);
+#X connect 0 0 6 0;
+#X connect 1 0 2 1;
+#X connect 1 1 4 0;
+#X connect 2 0 3 0;
+#X connect 6 0 5 0;
+#X connect 6 1 2 0;
+#X connect 6 2 1 0;
+#X connect 6 3 2 1;
-- 
GitLab