shithub: sox

Download patch

ref: 8534b0efb42887309e80f2fd72d14147be4a5cb3
parent: 53e5c79a161bd7a94bf50f4eca555add41fdf85d
author: robs <robs>
date: Thu Jan 31 14:48:38 EST 2008

Fix [1864216] comments mangled when writing ogg-vorbis

--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,24 @@
 This file contains a list of all changes starting after the release of
 sox-11gamma.
 
+sox-14.0.2	2008-xx-xx
+----------
+
+  File formats:
+
+  Effects:
+
+  Other new features:
+
+  o Command line support for multiple file comments.  (robs)
+
+  Bug fixes:
+
+  o Fix [1864216] comments mangled when writing ogg-vorbis.  (robs)
+
+  Internal improvements:
+
+
 sox-14.0.1	2008-01-29
 ----------
 
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 
-AC_INIT(SoX, 14.0.1, sox-devel@lists.sourceforge.net)
+AC_INIT(SoX, 14.0.2, sox-devel@lists.sourceforge.net)
 
 dnl Find target architecture
 AC_CANONICAL_TARGET
--- a/sox.1
+++ b/sox.1
@@ -587,20 +587,6 @@
 effect is not specified on the
 command line it will be invoked internally with default parameters.
 .TP
-\fB\-\-comment \fITEXT\fR
-Specify the comment text to store in the output file header (where
-applicable).
-.SP
-SoX will provide a default comment if this option (or
-.BR \-\-comment\-file )
-is not given; to specify that no comment should be stored in the output file,
-use 
-.B "\-\-comment \(dq\(dq" .
-.TP
-\fB\-\-comment\-file \fIFILENAME\fR
-Specify a file containing the comment text to store in the output
-file header (where applicable).
-.TP
 \fB\-r, \fB\-\-rate\fR \fIRATE\fR
 Gives the sample rate in Hz of the file.  To cause the output file to have
 a different sample rate than the input file, include this option with
@@ -720,6 +706,23 @@
 .SS Output File Format Options
 These options apply only to the output file and may precede only the output
 filename on the command line.
+.TP
+\fB\-\-add\-comment \fITEXT\fR
+Append a comment in the output file header (where applicable).
+.TP
+\fB\-\-comment \fITEXT\fR
+Specify the comment text to store in the output file header (where
+applicable).
+.SP
+SoX will provide a default comment if this option (or
+.BR \-\-comment\-file )
+is not given; to specify that no comment should be stored in the output file,
+use 
+.B "\-\-comment \(dq\(dq" .
+.TP
+\fB\-\-comment\-file \fIFILENAME\fR
+Specify a file containing the comment text to store in the output
+file header (where applicable).
 .TP
 \fB\-C\fR, \fB\-\-compression\fR \fIFACTOR\fR
 The compression factor for variably compressing output file formats.  If
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -286,7 +286,7 @@
 #end !HAVE_LIBLTDL
 endif
 
-EXTRA_DIST = tests.sh testall.sh tests.bat testall.bat monkey.au monkey.wav sox_sample_test.c amr-wb-test CMakeLists.txt soxstdint.h.cmake soxconfig.h.cmake example1.c
+EXTRA_DIST = tests.sh testall.sh tests.bat testall.bat monkey.au monkey.wav sox_sample_test.c amr-wb-test CMakeLists.txt soxstdint.h.cmake soxconfig.h.cmake example1.c test-comments
 
 all: sox$(EXEEXT) play rec sox_sample_test$(EXEEXT) example1$(EXEEXT)
 
--- a/src/aiff.c
+++ b/src/aiff.c
@@ -97,7 +97,9 @@
                        releaseLoopBegin = 0, releaseLoopEnd = 0;
         sox_ssize_t seekto = 0;
         sox_size_t ssndsize = 0;
+        char *annotation;
         char *author;
+        char *comment = NULL;
         char *copyright;
         char *nametext;
 
@@ -286,19 +288,25 @@
                             sox_readb(ft, (unsigned char *)&trash8);
                 }
                 else if (strncmp(buf, "ANNO", 4) == 0) {
-                        rc = textChunk(&(ft->comment), "Annotation:", ft);
-                        if (rc)
-                        {
-                          /* Fail already called in function */
-                          return(SOX_EOF);
-                        }
+                  rc = textChunk(&annotation, "Annotation:", ft);
+                  if (rc)
+                  {
+                    /* Fail already called in function */
+                    return(SOX_EOF);
+                  }
+                  if (annotation)
+                    append_comments(&ft->comments, annotation);
+                  free(annotation);
                 }
                 else if (strncmp(buf, "COMT", 4) == 0) {
-                  rc = commentChunk(&(ft->comment), "Comment:", ft);
+                  rc = commentChunk(&comment, "Comment:", ft);
                   if (rc) {
                     /* Fail already called in function */
                     return(SOX_EOF);
                   }
+                  if (comment)
+                    append_comments(&ft->comments, comment);
+                  free(comment);
                 }
                 else if (strncmp(buf, "AUTH", 4) == 0) {
                   /* Author chunk */
@@ -743,6 +751,7 @@
         unsigned i;
         sox_size_t padded_comment_size = 0, comment_size = 0;
         sox_size_t comment_chunk_size = 0;
+        char * comment = cat_comments(ft->comments);
 
         /* MARK and INST chunks */
         if (ft->instr.nloops) {
@@ -770,9 +779,9 @@
 
         /* COMT comment chunk -- holds comments text with a timestamp and marker id */
         /* We calculate the comment_chunk_size if we will be writing a comment */
-        if (ft->comment)
+        if (ft->comments)
         {
-          comment_size = strlen(ft->comment);
+          comment_size = strlen(comment);
           /* Must put an even number of characters out.
            * True 68k processors OS's seem to require this.
            */
@@ -789,7 +798,7 @@
         sox_writes(ft, "AIFF"); /* File type */
 
         /* Now we write the COMT comment chunk using the precomputed sizes */
-        if (ft->comment)
+        if (ft->comments)
         {
           sox_writes(ft, "COMT");
           sox_writedw(ft, comment_chunk_size);
@@ -807,10 +816,11 @@
 
           /* now write the count and the bytes of text */
           sox_writew(ft, padded_comment_size);
-          sox_writes(ft, ft->comment);
+          sox_writes(ft, comment);
           if (comment_size != padded_comment_size)
                 sox_writes(ft, " ");
         }
+        free(comment);
 
         /* COMM chunk -- describes encoding (and #frames) */
         sox_writes(ft, "COMM");
--- a/src/au.c
+++ b/src/au.c
@@ -268,7 +268,8 @@
                  */
                 buf[hdr_size] = '\0';
 
-                ft->comment = buf;
+                append_comments(&ft->comments, buf);
+                free(buf);
         }
         /* Needed for seeking */
         ft->length = data_size/ft->signal.size;
@@ -410,6 +411,7 @@
         uint32_t channels;
         int   x;
         int   comment_size;
+        char * comment = cat_comments(ft->comments);
 
         encoding = sox_ausunencoding(ft->signal.size, ft->signal.encoding);
         if (encoding == SUN_ENCODING_UNKNOWN) {
@@ -439,15 +441,9 @@
         magic = SUN_MAGIC;
         sox_writedw(ft, magic);
 
-        /* Info field is at least 4 bytes. Here I force it to something
-         * useful when there is no comments.
-         */
-        if (ft->comment == NULL)
-                ft->comment = xstrdup("SoX");
-
         hdr_size = SUN_HDRSIZE;
 
-        comment_size = strlen(ft->comment) + 1; /*+1 = null-term. */
+        comment_size = strlen(comment) + 1; /*+1 = null-term. */
         if (comment_size < 4)
             comment_size = 4; /* minimum size */
 
@@ -465,10 +461,11 @@
         channels = ft->signal.channels;
         sox_writedw(ft, channels);
 
-        sox_writes(ft, ft->comment);
+        sox_writes(ft, comment);
 
         /* Info must be 4 bytes at least and null terminated. */
-        x = strlen(ft->comment);
+        x = strlen(comment);
+        free(comment);
         for (;x < 3; x++)
             sox_writeb(ft, 0);
 
--- a/src/cdr.c
+++ b/src/cdr.c
@@ -52,7 +52,6 @@
         ft->signal.size = SOX_SIZE_16BIT;
         ft->signal.encoding = SOX_ENCODING_SIGN2;
         ft->signal.channels = 2;
-        ft->comment = NULL;
 
 /* Need length for seeking */
         if(ft->seekable){
--- a/src/cvsd.c
+++ b/src/cvsd.c
@@ -457,6 +457,7 @@
 {
         struct cvsdpriv *p = (struct cvsdpriv *) ft->priv;
         size_t len;
+        char * comment = cat_comments(ft->comments);
 
         memset(hdr->Filename, 0, sizeof(hdr->Filename));
         len = strlen(ft->filename);
@@ -470,11 +471,12 @@
         hdr->Srate = p->cvsd_rate/100;
         hdr->Days = hdr->Custom1 = hdr->Custom2 = 0;
         memset(hdr->Info, 0, sizeof(hdr->Info));
-        len = strlen(ft->comment);
+        len = strlen(comment);
         if (len >= sizeof(hdr->Info))
                 len = sizeof(hdr->Info)-1;
-        memcpy(hdr->Info, ft->comment, len);
+        memcpy(hdr->Info, comment, len);
         memset(hdr->extend, 0, sizeof(hdr->extend));
+        free(comment);
 }
 
 /* ---------------------------------------------------------------------- */
--- a/src/flac.c
+++ b/src/flac.c
@@ -83,26 +83,18 @@
     decoder->total_samples = metadata->data.stream_info.total_samples;
   }
   else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
-    size_t i, comment_size = 0;
+    size_t i;
 
     if (metadata->data.vorbis_comment.num_comments == 0)
       return;
 
-    if (ft->comment != NULL) {
-      sox_warn("FLAC: multiple Vorbis comment block ignored");
+    if (ft->comments != NULL) {
+      sox_warn("multiple Vorbis comment block ignored");
       return;
     }
 
     for (i = 0; i < metadata->data.vorbis_comment.num_comments; ++i)
-      comment_size += metadata->data.vorbis_comment.comments[i].length + 1;
-
-    ft->comment = (char *) xcalloc(comment_size, sizeof(char));
-
-    for (i = 0; i < metadata->data.vorbis_comment.num_comments; ++i) {
-      strcat(ft->comment, (char const *) metadata->data.vorbis_comment.comments[i].entry);
-      if (i != metadata->data.vorbis_comment.num_comments - 1)
-        strcat(ft->comment, "\n");
-    }
+      append_comment(&ft->comments, (char const *) metadata->data.vorbis_comment.comments[i].entry);
   }
 }
 
@@ -231,7 +223,7 @@
   Decoder * decoder = (Decoder *) ft->priv;
 
   if (!FLAC__stream_decoder_finish(decoder->flac) && decoder->eof)
-    sox_warn("FLAC decoder MD5 checksum mismatch.");
+    sox_warn("decoder MD5 checksum mismatch.");
   FLAC__stream_decoder_delete(decoder->flac);
   return SOX_SUCCESS;
 }
@@ -422,37 +414,22 @@
     ++encoder->num_metadata;
   }
 
-  if (ft->comment != NULL && * ft->comment != '\0') {
+  if (ft->comments) {     /* Make the comment structure */
     FLAC__StreamMetadata_VorbisComment_Entry entry;
-    char * comments, * comment, * end_of_comment;
+    int i;
 
     encoder->metadata[encoder->num_metadata] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
-
-    /* Check if there is a FIELD=value pair already in the comment; if not, add one */
-    if (strchr(ft->comment, '=') == NULL) {
-      static const char prepend[] = "COMMENT=";
-      comments = xmalloc(strlen(ft->comment) + sizeof(prepend));
-      strcpy(comments, prepend);
-      strcat(comments, ft->comment);
-    }
-    else
-      comments = strdup(ft->comment);
-
-    comment = comments;
-
-    do {
-      entry.entry = (FLAC__byte *) comment;
-      end_of_comment = strchr(comment, '\n');
-      if (end_of_comment != NULL) {
-        *end_of_comment = '\0';
-        comment = end_of_comment + 1;
-      }
-      entry.length = strlen((char const *) entry.entry);
-
+    for (i = 0; ft->comments[i]; ++i) {
+      static const char prepend[] = "Comment=";
+      char * text = xcalloc(strlen(prepend) + strlen(ft->comments[i]) + 1, sizeof(*text));
+      /* Prepend `Comment=' if no field-name already in the comment */
+      if (!strchr(ft->comments[i], '='))
+        strcpy(text, prepend);
+      entry.entry = (FLAC__byte *) strcat(text, ft->comments[i]);
+      entry.length = strlen(text);
       FLAC__metadata_object_vorbiscomment_append_comment(encoder->metadata[encoder->num_metadata], entry, /*copy= */ sox_true);
-    } while (end_of_comment != NULL);
-
-    free(comments);
+      free(text);
+    }
     ++encoder->num_metadata;
   }
 
--- a/src/maud.c
+++ b/src/maud.c
@@ -157,8 +157,6 @@
                                 return(SOX_EOF);
                         }
                         
-                        ft->comment = 0;
-                        
                         continue;
                 }
                 
--- a/src/sf.c
+++ b/src/sf.c
@@ -32,7 +32,7 @@
  */
 static void readcodes(sox_format_t * ft, SFHEADER *sfhead)
 {
-        char *commentbuf = NULL, *sfcharp, *newline;
+        char *commentbuf = NULL, *sfcharp;
         sox_size_t bsize;
         sox_bool finished = sox_false;
         SFCODE *sfcodep;
@@ -52,16 +52,13 @@
                 case SF_COMMENT:
                         commentbuf = (char *) xmalloc(bsize + 1);
                         memcpy(commentbuf, sfcharp, bsize);
-                        sox_report("IRCAM comment: %s", sfcharp);
                         commentbuf[bsize] = '\0';
-                        if((newline = strchr(commentbuf, '\n')) != NULL)
-                                *newline = '\0';
                         break;
                 }
                 sfcodep = (SFCODE *) (sfcharp + bsize);
         } while(!finished);
-        if(commentbuf != NULL)  /* handles out of memory condition as well */
-                ft->comment = commentbuf;
+        append_comments(&ft->comments, commentbuf);
+        free(commentbuf);
 }
 
 static int sox_sfseek(sox_format_t * ft, sox_size_t offset)
@@ -166,6 +163,7 @@
         SFCODE *sfcodep;
         char *sfcharp;
         int rc;
+        char * comment = cat_comments(ft->comments);
 
         /* Needed for rawwrite() */
         rc = sox_rawstartwrite(ft);
@@ -201,11 +199,12 @@
         memcpy(&sfhead.sfinfo, &sf->info, sizeof(struct sfinfo));
         sfcodep = (SFCODE *) (&sfhead.sfinfo + 1);
         sfcodep->code = SF_COMMENT;
-        sfcodep->bsize = strlen(ft->comment) + sizeof(SFCODE);
+        sfcodep->bsize = strlen(comment) + sizeof(SFCODE);
         while (sfcodep->bsize % 4)
                 sfcodep->bsize++;
         sfcharp = (char *) sfcodep;
-        strcpy(sfcharp + sizeof(SFCODE), ft->comment);
+        strcpy(sfcharp + sizeof(SFCODE), comment);
+        free(comment);
         sfcodep = (SFCODE *) (sfcharp + sfcodep->bsize);
         sfcodep->code = SF_END;
         sfcodep->bsize = sizeof(SFCODE);
--- a/src/skelform.c
+++ b/src/skelform.c
@@ -62,11 +62,7 @@
   ft->signal.size = SOX_SIZE_BYTE; /* or WORD ... */
   ft->signal.encoding = SOX_ENCODING_UNSIGNED; /* or SIGN2 ... */
   ft->signal.channels = 1; /* or 2 or 4 */
-  {
-    char *comment = "any comment in file header.";
-    ft->comment = xmalloc(sizeof(comment));
-    strcpy(ft->comment, comment);
-  }
+  append_comment(&ft->comments, "any comment in file header.");
 
   /* If your format doesn't have a header then samples_in_file
    * can be determined by the file size.
--- a/src/smp.c
+++ b/src/smp.c
@@ -243,9 +243,8 @@
           ;
         sprintf(smp->comment, "%.*s: %.*s", namelen+1, header.name,
                 commentlen+1, header.comments);
-        ft->comment = smp->comment;
+        append_comments(&ft->comments, smp->comment);
 
-        sox_report("SampleVision file name and comments: %s", ft->comment);
         /* Extract out the sample size (always intel format) */
         sox_readdw(ft, &(smp->NoOfSamps));
         /* mark the start of the sample data */
@@ -336,6 +335,7 @@
 {
         smp_t smp = (smp_t) ft->priv;
         struct smpheader header;
+        char * comment = cat_comments(ft->comments);
 
         /* If you have to seek around the output file */
         if (! ft->seekable)
@@ -352,7 +352,8 @@
         memcpy(header.Id, SVmagic, sizeof(header.Id));
         memcpy(header.version, SVvers, sizeof(header.version));
         sprintf(header.comments, "%-*s", COMMENTLEN - 1, "Converted using Sox.");
-        sprintf(header.name, "%-*.*s", NAMELEN, NAMELEN, ft->comment);
+        sprintf(header.name, "%-*.*s", NAMELEN, NAMELEN, comment);
+        free(comment);
 
         /* Write file header */
         if(sox_writebuf(ft, &header, HEADERSIZE) != HEADERSIZE)
--- a/src/sox.c
+++ b/src/sox.c
@@ -92,7 +92,7 @@
   sox_signalinfo_t signal;
   double volume;
   double replay_gain;
-  char const * comment;
+  comments_t comments;
 
   sox_format_t * ft;  /* libSoX file descriptor */
   sox_size_t volume_clips;
@@ -232,6 +232,7 @@
 "",
 "-c, --channels CHANNELS  number of channels in audio data",
 "-C, --compression FACTOR  compression factor for output format",
+"--add-comment TEXT  Append output file comment",
 "--comment TEXT  Specify comment text for the output file",
 "--comment-file FILENAME  file containing comment text for the output file",
 "--endian little|big|swap  set endianness; swap means opposite to default",
@@ -404,23 +405,21 @@
   exit(1);
 }
 
-static void set_replay_gain(char const * comment, file_t f)
+static void set_replay_gain(comments_t comments, file_t f)
 {
   rg_mode rg = replay_gain_mode;
   int try = 2; /* Will try to find the other GAIN if preferred one not found */
+  size_t i, n = num_comments(comments);
 
   if (rg != RG_off) while (try--) {
-    char const * p = comment;
     char const * target =
       rg == RG_track? "REPLAYGAIN_TRACK_GAIN=" : "REPLAYGAIN_ALBUM_GAIN=";
-    do {
-      if (strncasecmp(p, target, strlen(target)) == 0) {
-        f->replay_gain = atof(p + strlen(target));
+    for (i = 0; i < n; ++i) {
+      if (strncasecmp(comments[i], target, strlen(target)) == 0) {
+        f->replay_gain = atof(comments[i] + strlen(target));
         return;
       }
-      while (*p && *p!= '\n') ++p;
-      while (*p && strchr("\r\n\t\f ", *p)) ++p;
-    } while (*p);
+    }
     rg ^= RG_track ^ RG_album;
   }
 }
@@ -642,12 +641,12 @@
   /* Check for misplaced input/output-specific options */
   for (i = 0; i < input_count; ++i) {
     if (files[i]->signal.compression != HUGE_VAL)
-      usage("A compression factor can only be given for an output file");
-    if (files[i]->comment != NULL)
-      usage("A comment can only be given for an output file");
+      usage("A compression factor can be given only for an output file");
+    if (files[i]->comments != NULL)
+      usage("Comments can be given only for an output file");
   }
   if (ofile->volume != HUGE_VAL)
-    usage("-v can only be given for an input file;\n"
+    usage("-v can be given only for an input file;\n"
             "\tuse `vol' to set the output file volume");
 
   signal(SIGINT, SIG_IGN); /* So child pipes aren't killed by track skip */
@@ -680,8 +679,7 @@
         (files[j]->ft->handler->flags & SOX_FILE_DEVICE) != 0 &&
         (files[j]->ft->handler->flags & SOX_FILE_PHONY) == 0)
       show_progress = SOX_OPTION_YES;
-    if (files[j]->ft->comment)
-      set_replay_gain(files[j]->ft->comment, f);
+    set_replay_gain(files[j]->ft->comments, f);
   }
   signal(SIGINT, SIG_DFL);
 
@@ -749,11 +747,11 @@
   return 0;
 }
 
-static char * read_comment_file(char const * const filename)
+static void read_comment_file(comments_t * comments, char const * const filename)
 {
-  sox_bool file_error;
-  int file_length = 0;
-  char * result;
+  int c;
+  size_t text_length = 100;
+  char * text = xmalloc(text_length + 1);
   FILE * file = fopen(filename, "rt");
 
   if (file == NULL) {
@@ -760,26 +758,26 @@
     sox_fail("Cannot open comment file %s", filename);
     exit(1);
   }
-  file_error = fseeko(file, (off_t)0, SEEK_END);
-  if (!file_error) {
-    file_length = ftello(file);
-    file_error |= file_length < 0;
-    if (!file_error) {
-      result = xmalloc((unsigned)file_length + 1);
-      rewind(file);
-      file_error |= fread(result, (unsigned)file_length, 1, file) != 1;
+  do {
+    size_t i = 0;
+
+    while ((c = getc(file)) != EOF && !strchr("\r\n", c)) {
+      if (i == text_length)
+        text = xrealloc(text, (text_length <<= 1) + 1);
+      text[i++] = c;
     }
-  }
-  if (file_error) {
-    sox_fail("Error reading comment file %s", filename);
-    exit(1);
-  }
-  fclose(file);
+    if (ferror(file)) {
+      sox_fail("Error reading comment file %s", filename);
+      exit(1);
+    }
+    if (i) {
+      text[i] = '\0';
+      append_comment(comments, text);
+    }
+  } while (c != EOF);
 
-  while (file_length && result[file_length - 1] == '\n')
-    --file_length;
-  result[file_length] = '\0';
-  return result;
+  fclose(file);
+  free(text);
 }
 
 static char *getoptstr = "+abc:defghilmnoqr:st:uv:wxABC:DLMNRSUV::X12348";
@@ -786,6 +784,7 @@
 
 static struct option long_options[] =
   {
+    {"add-comment"     , required_argument, NULL, 0},
     {"buffer"          , required_argument, NULL, 0},
     {"combine"         , required_argument, NULL, 0},
     {"comment-file"    , required_argument, NULL, 0},
@@ -870,6 +869,11 @@
     case 0:         /* Long options with no short equivalent. */
       switch (option_index) {
       case 0:
+        if (optarg)
+          append_comment(&f->comments, optarg);
+        break;
+
+      case 1:
 #define SOX_BUFMIN 16
         if (sscanf(optarg, "%i %c", &i, &dummy) != 1 || i <= SOX_BUFMIN) {
         sox_fail("Buffer size `%s' must be > %d", optarg, SOX_BUFMIN);
@@ -878,19 +882,22 @@
         sox_globals.bufsiz = i;
         break;
 
-      case 1:
+      case 2:
         combine_method = enum_option(option_index, combine_methods);
         break;
 
-      case 2:
-        f->comment = read_comment_file(optarg);
-        break;
-
       case 3:
-        f->comment = xstrdup(optarg);
+        append_comment(&f->comments, "");
+        read_comment_file(&f->comments, optarg);
         break;
 
       case 4:
+        append_comment(&f->comments, "");
+        if (optarg)
+          append_comment(&f->comments, optarg);
+        break;
+
+      case 5:
         switch (enum_option(option_index, endian_options)) {
           case ENDIAN_little: f->signal.reverse_bytes = SOX_IS_BIGENDIAN; break;
           case ENDIAN_big: f->signal.reverse_bytes = SOX_IS_LITTLEENDIAN; break;
@@ -898,23 +905,23 @@
         }
         break;
 
-      case 5:
+      case 6:
         interactive = sox_true;
         break;
 
-      case 6:
+      case 7:
         usage_effect(optarg);
         break;
 
-      case 7:
+      case 8:
         sox_effects_globals.plot = enum_option(option_index, plot_methods);
         break;
 
-      case 8:
+      case 9:
         replay_gain_mode = enum_option(option_index, rg_modes);
         break;
 
-      case 9:
+      case 10:
         display_SoX_version(stdout);
         exit(0);
         break;
@@ -1090,11 +1097,14 @@
   if (f->volume != HUGE_VAL)
     fprintf(stderr, "Level adjust   : %g (linear gain)\n" , f->volume);
 
-  if (!(f->ft->handler->flags & SOX_FILE_DEVICE) && f->ft->comment) {
-    if (strchr(f->ft->comment, '\n'))
-      fprintf(stderr, "Comments       : \n%s\n", f->ft->comment);
-    else
-      fprintf(stderr, "Comment        : '%s'\n", f->ft->comment);
+  if (!(f->ft->handler->flags & SOX_FILE_DEVICE) && f->ft->comments) {
+    if (num_comments(f->ft->comments) > 1) {
+      comments_t p = f->ft->comments;
+      fprintf(stderr, "Comments       : \n");
+      do fprintf(stderr, "%s\n", *p);
+      while (*++p);
+    }
+    else fprintf(stderr, "Comment        : '%s'\n", f->ft->comments[0]);
   }
   fprintf(stderr, "\n");
 }
@@ -1410,12 +1420,19 @@
   sox_loopinfo_t loops[SOX_MAX_NLOOPS];
   double factor;
   int i;
-  char const *comment = NULL;
+  comments_t comments = copy_comments(files[0]->ft->comments);
+  comments_t p = ofile->comments;
 
-  if (ofile->comment == NULL)
-    comment = files[0]->ft->comment ? files[0]->ft->comment : "Processed by SoX";
-  else if (*ofile->comment != '\0')
-      comment = ofile->comment;
+  if (!comments && !p)
+    append_comment(&comments, "Processed by SoX");
+  else if (p) {
+    if (!(*p)[0]) {
+      delete_comments(&comments);
+      ++p;
+    }
+    while (*p)
+      append_comment(&comments, *p++);
+  }
 
   /*
    * copy loop info, resizing appropriately
@@ -1435,10 +1452,11 @@
                         ofile->filename,
                         &ofile->signal,
                         ofile->filetype,
-                        comment,
+                        comments,
                         olen,
                         &files[0]->ft->instr,
                         loops);
+  delete_comments(&comments);
 
   if (!ofile->ft)
     /* sox_open_write() will call sox_warn for most errors.
--- a/src/sox.h
+++ b/src/sox.h
@@ -302,6 +302,8 @@
 #define SOX_MAX_FILE_PRIVSIZE    1000
 #define SOX_MAX_NLOOPS           8
 
+typedef char * * comments_t;
+
 struct sox_format {
   /* Placing priv at the start of this structure ensures that it gets aligned
    * in memory in the optimal way for any structure to be cast over it. */
@@ -316,7 +318,7 @@
   sox_size_t       clips;                /* increment if clipping occurs */
   char             *filename;            /* file name */
   char             *filetype;            /* type of file */
-  char             *comment;             /* comment string */
+  comments_t       comments;             /* comment strings */
   FILE             *fp;                  /* File stream pointer */
   int              sox_errno;            /* Failure error codes */
   char             sox_errstr[256];      /* Extend Failure text */
@@ -361,7 +363,7 @@
     const char *path,
     const sox_signalinfo_t *info,
     const char *filetype,
-    const char *comment,
+    comments_t comments,
     sox_size_t length,
     const sox_instrinfo_t *instr,
     const sox_loopinfo_t *loops);
--- a/src/sox_i.h
+++ b/src/sox_i.h
@@ -132,10 +132,13 @@
 char *strdup(const char *s);
 #endif
 
+
+
+/*---------------------------- Declared in misc.c ----------------------------*/
+
 /* Read and write basic data types from "ft" stream.  Uses ft->swap for
  * possible byte swapping.
  */
-/* declared in misc.c */
 size_t sox_readbuf(sox_format_t * ft, void *buf, sox_size_t len);
 int sox_skipbytes(sox_format_t * ft, sox_size_t n);
 int sox_padbytes(sox_format_t * ft, sox_size_t n);
@@ -199,7 +202,10 @@
 uint32_t sox_swap3(uint32_t udw);
 double sox_swapdf(double d);
 
-/* util.c */
+
+
+/*---------------------------- Declared in util.c ----------------------------*/
+
 typedef void (*sox_output_message_handler_t)(unsigned level, const char *filename, const char *fmt, va_list ap);
 void sox_output_message(FILE *file, const char *filename, const char *fmt, va_list ap);
 
@@ -224,6 +230,15 @@
 ;
 #endif
 
+size_t num_comments(comments_t comments);
+void append_comment(comments_t * comments, char const * comment);
+void append_comments(comments_t * comments, char const * comment);
+comments_t copy_comments(comments_t comments);
+void delete_comments(comments_t * comments);
+char * cat_comments(comments_t comments);
+
+
+
 #ifdef WORDS_BIGENDIAN
 #define SOX_IS_BIGENDIAN 1
 #define SOX_IS_LITTLEENDIAN 0
@@ -274,11 +289,10 @@
 extern const char sox_writerr[];
 extern uint8_t const cswap[256];
 
-/*-----------------------------------------------------------------------------
- * File Handlers
- *-----------------------------------------------------------------------------
- */
 
+
+/*------------------------------ File Handlers -------------------------------*/
+
 /* Psion record header check, defined in misc.c and used in prc.c and auto.c */
 extern const char prc_header[41];
 int prc_checkheader(sox_format_t * ft, char *head);
@@ -315,11 +329,10 @@
 #define sox_rawstopread sox_format_nothing
 #define sox_rawstopwrite sox_format_nothing
 
-/*-----------------------------------------------------------------------------
- * Effects
- *-----------------------------------------------------------------------------
- */
 
+
+/*--------------------------------- Effects ----------------------------------*/
+
 int sox_usage(sox_effect_t * effp);
 typedef const sox_effect_handler_t *(*sox_effect_fn_t)(void);
 extern sox_effect_fn_t sox_effect_fns[];
@@ -359,4 +372,5 @@
   sox_sample_t **ibufc, **obufc; /* Channel interleave buffers */
   sox_effects_globals_t global_info;
 };
+
 #endif
--- a/src/soxconfig.h.cmake
+++ b/src/soxconfig.h.cmake
@@ -1,4 +1,4 @@
-#define PACKAGE_VERSION "14.0.1"
+#define PACKAGE_VERSION "14.0.2"
 
 #cmakedefine EXTERNAL_GSM             1
 #cmakedefine HAVE_ALSA                1
--- a/src/soxio.c
+++ b/src/soxio.c
@@ -277,7 +277,7 @@
     const char *path,
     const sox_signalinfo_t *info,
     const char *filetype,
-    const char *comment,
+    comments_t comments,
     sox_size_t length,
     const sox_instrinfo_t *instr,
     const sox_loopinfo_t *loops)
@@ -347,7 +347,7 @@
         ft->seekable = is_seekable(ft);
     }
 
-    ft->comment = xstrdup(comment);
+    ft->comments = copy_comments(comments);
 
     if (loops)
         for (i = 0; i < SOX_MAX_NLOOPS; i++)
@@ -532,10 +532,7 @@
         fclose(ft->fp);
     free(ft->filename);
     free(ft->filetype);
-    /* Currently, since startread() mallocs comments, stopread
-     * is expected to also free it. */
-    if (ft->mode == 'w')
-        free(ft->comment);
+    delete_comments(&ft->comments);
 
     free(ft);
     return rc;
--- /dev/null
+++ b/src/test-comments
@@ -1,0 +1,93 @@
+#!/bin/sh -x
+
+tmp=/tmp/`basename $0`-$$
+
+check () {
+  f=$1; shift
+  : > $tmp.expected
+  lines=0
+  while [ $# != 0 ]; do
+    echo "$1" >> $tmp.expected
+    shift
+    lines=`expr $lines + 1`
+  done
+  ./sox -V $f -n 2> $tmp.info
+  if [ $lines -eq 1 ]; then
+    grep "^Comment " $tmp.info|sed "s/[^']*'//"|sed "s/'$//" > $tmp.comments
+  else
+    head -n -1 $tmp.info|tac|head -n -`grep -n ^Comments $tmp.info|sed "s/:.*//"`|tac>$tmp.comments
+  fi
+  cmp $tmp.comments $tmp.expected || exit 1
+}
+
+check_file () {
+  ./sox -V $1 -n 2> $tmp.info
+  head -n -1 $tmp.info|tac|head -n -`grep -n ^Comments $tmp.info|sed "s/:.*//"`|tac>$tmp.comments
+  cmp $tmp.comments $2 || exit 1
+}
+
+com0="Processed by SoX"
+com1="foo bar"
+com2="bar foo"
+
+./sox monkey.au $tmp.au # Apply default comment
+check $tmp.au "$com0"
+
+cp $tmp.au $tmp.comment.au
+
+cat > $tmp.i << .
+TITLE=The First Track
+ARTIST=A Band Of Musicians
+ALBUM=A Collection Of Songs
+DATE=2008
+TRACKNUMBER=01
+TRACKTOTAL=14
+GENRE=Music
+REPLAYGAIN_TRACK_PEAK=1.00515676
+REPLAYGAIN_TRACK_GAIN=-3.97 dB
+REPLAYGAIN_ALBUM_PEAK=1.00515676
+REPLAYGAIN_ALBUM_GAIN=-2.88 dB
+.
+
+./sox monkey.au --comment-file $tmp.i $tmp.comments.au
+check_file $tmp.comments.au $tmp.i
+
+./sox monkey.au --comment= $tmp.au # Don't apply default comment
+check $tmp.au
+
+./sox monkey.au --add-comment "$com1" $tmp.au
+check $tmp.au "$com1"
+
+./sox $tmp.comment.au --add-comment "$com1" $tmp.au
+check $tmp.au "$com0" "$com1"
+
+./sox monkey.au --add-comment "$com1" --add-comment "$com2" $tmp.au
+check $tmp.au "$com1" "$com2"
+
+./sox $tmp.comment.au --add-comment "$com1" --add-comment "$com2" $tmp.au
+check $tmp.au "$com0" "$com1" "$com2"
+
+./sox $tmp.comments.au --comment= $tmp.au
+check $tmp.au
+
+./sox $tmp.comments.au --comment "$com1" $tmp.au
+check $tmp.au "$com1"
+
+./sox $tmp.comments.au --add-comment "$com1" $tmp.au
+cp $tmp.i $tmp.j
+echo "$com1" >> $tmp.j
+check_file $tmp.au $tmp.j
+
+./sox $tmp.comments.au --add-comment "$com1" --add-comment "$com2" $tmp.au
+echo "$com2" >> $tmp.j
+check_file $tmp.au $tmp.j
+
+# FIXME: smp aiff mp3
+./sox $tmp.comments.au $tmp.flac
+./sox $tmp.flac $tmp.sf
+./sox $tmp.sf $tmp.ogg
+./sox $tmp.ogg $tmp.au
+check_file $tmp.au $tmp.i
+
+rm -f $tmp.*
+exit 0
--- a/src/util.c
+++ b/src/util.c
@@ -11,9 +11,11 @@
  */
 
 #include "sox_i.h"
+#include <assert.h>
 #include <string.h>
 #include <stdarg.h>
 
+
 void sox_output_message(FILE *file, const char *filename, const char *fmt, va_list ap)
 {
   char const * slash_pos = LAST_SLASH(filename);
@@ -139,4 +141,83 @@
         return end;
     }
     return NULL;
+}
+
+
+
+/*--------------------------------- Comments ---------------------------------*/
+
+size_t num_comments(comments_t comments)
+{
+  size_t result = 0;
+  if (!comments)
+    return 0;
+  while (*comments++)
+    ++result;
+  return result;
+}
+
+void append_comment(comments_t * comments, char const * comment)
+{
+  size_t n = num_comments(*comments);
+  *comments = xrealloc(*comments, (n + 2) * sizeof(**comments));
+  assert(comment);
+  (*comments)[n++] = xstrdup(comment);
+  (*comments)[n] = 0;
+}
+
+void append_comments(comments_t * comments, char const * comment)
+{
+  char * end;
+  if (comment) {
+    while ((end = strchr(comment, '\n'))) {
+      size_t len = end - comment;
+      char * c = xmalloc((len + 1) * sizeof(*c));
+      strncpy(c, comment, len);
+      c[len] = '\0';
+      append_comment(comments, c);
+      comment += len + 1;
+      free(c);
+    }
+    if (*comment)
+      append_comment(comments, comment);
+  }
+}
+
+comments_t copy_comments(comments_t comments)
+{
+  comments_t result = 0;
+
+  if (comments) while (*comments)
+    append_comment(&result, *comments++);
+  return result;
+}
+
+void delete_comments(comments_t * comments)
+{
+  comments_t p = *comments;
+
+  if (p) while (*p)
+    free(*p++);
+  free(*comments);
+  *comments = 0;
+}
+
+char * cat_comments(comments_t comments)
+{
+  comments_t p = comments;
+  size_t len = 0;
+  char * result;
+
+  if (p) while (*p)
+    len += strlen(*p++) + 1;
+
+  result = xcalloc(len? len : 1, sizeof(*result));
+
+  if ((p = comments) && *p) {
+    strcpy(result, *p);
+    while (*++p)
+      strcat(strcat(result, "\n"), *p);
+  }
+  return result;
 }
--- a/src/vorbis.c
+++ b/src/vorbis.c
@@ -15,9 +15,6 @@
  * any purpose.  This copyright notice must be maintained.
  * Lance Norskog And Sundry Contributors are not responsible for
  * the consequences of using this software.
- *
- * TODO: When reading in comments, it doesn't understand how to read
- * more than one comment and doesn't know how to parse KEY=value.
  */
 
 #include "sox_i.h"
@@ -43,37 +40,39 @@
 
 /* Private data for Ogg Vorbis file */
 typedef struct vorbis_enc {
-        ogg_stream_state os;
-        ogg_page         og;
-        ogg_packet       op;
+  ogg_stream_state os;
+  ogg_page og;
+  ogg_packet op;
 
-        vorbis_dsp_state vd;
-        vorbis_block     vb;
-        vorbis_info      vi;
+  vorbis_dsp_state vd;
+  vorbis_block vb;
+  vorbis_info vi;
 } vorbis_enc_t;
 
 typedef struct vorbisstuff {
-/* Decoding data */
-        OggVorbis_File *vf;
-        char *buf;
-        sox_size_t buf_len;
-        sox_size_t start;
-        sox_size_t end;  /* Unsent data samples in buf[start] through buf[end-1] */
-        int current_section;
-        int eof;
+  /* Decoding data */
+  OggVorbis_File *vf;
+  char *buf;
+  sox_size_t buf_len;
+  sox_size_t start;
+  sox_size_t end;     /* Unsent data samples in buf[start] through buf[end-1] */
+  int current_section;
+  int eof;
 
-        vorbis_enc_t *vorbis_enc_data;
+  vorbis_enc_t *vorbis_enc_data;
 } *vorbis_t;
 
 /******** Callback functions used in ov_open_callbacks ************/
-static int myclose (void *datasource UNUSED)
+static int myclose(void *datasource UNUSED)
 {
-        /* Do nothing so sox can close the file for us */
-        return 0;
+  /* Do nothing so sox can close the file for us */
+  return 0;
 }
 
-static int _fseeko64_wrap(FILE *f, ogg_int64_t off, int whence) {
+static int _fseeko64_wrap(FILE * f, ogg_int64_t off, int whence)
+{
   int ret = fseeko(f, off, whence);
+
   if (ret == EBADF)
     ret = -1;
   return ret;
@@ -91,115 +90,84 @@
  */
 static int startread(sox_format_t * ft)
 {
-        vorbis_t vb = (vorbis_t) ft->priv;
-        vorbis_info *vi;
-        vorbis_comment *vc;
-        sox_size_t comment_size;
-        int i, offset;
+  vorbis_t vb = (vorbis_t) ft->priv;
+  vorbis_info *vi;
+  vorbis_comment *vc;
+  int i;
 
-        ov_callbacks callbacks = {
-                (size_t (*)(void *, size_t, size_t, void *))  fread,
-                (int (*)(void *, ogg_int64_t, int))           _fseeko64_wrap,
-                (int (*)(void *))                             myclose,
-                (long (*)(void *))                            ftell
-        };
+  ov_callbacks callbacks = {
+    (size_t(*)(void *, size_t, size_t, void *)) fread,
+    (int (*)(void *, ogg_int64_t, int)) _fseeko64_wrap,
+    (int (*)(void *)) myclose,
+    (long (*)(void *)) ftell
+  };
 
-        /* Allocate space for decoding structure */
-        vb->vf = (OggVorbis_File *)xmalloc(sizeof(OggVorbis_File));
+  /* Allocate space for decoding structure */
+  vb->vf = (OggVorbis_File *) xmalloc(sizeof(OggVorbis_File));
 
-        /* Init the decoder */
-        if (ov_open_callbacks((void *)ft->fp,vb->vf,NULL,0,callbacks) < 0)
-        {
-                sox_fail_errno(ft,SOX_EHDR,
-                              "Input not an Ogg Vorbis audio stream");
-                return (SOX_EOF);
-        }
+  /* Init the decoder */
+  if (ov_open_callbacks((void *) ft->fp, vb->vf, NULL, 0, callbacks) < 0) {
+    sox_fail_errno(ft, SOX_EHDR, "Input not an Ogg Vorbis audio stream");
+    return (SOX_EOF);
+  }
 
-        /* Get info about the Ogg Vorbis stream */
-        vi = ov_info(vb->vf, -1);
-        vc = ov_comment(vb->vf, -1);
+  /* Get info about the Ogg Vorbis stream */
+  vi = ov_info(vb->vf, -1);
+  vc = ov_comment(vb->vf, -1);
 
-        /* Record audio info */
-        ft->signal.rate = vi->rate;
-        ft->signal.size = SOX_SIZE_16BIT;
-        ft->signal.encoding = SOX_ENCODING_VORBIS;
-        ft->signal.channels = vi->channels;
+  /* Record audio info */
+  ft->signal.rate = vi->rate;
+  ft->signal.size = SOX_SIZE_16BIT;
+  ft->signal.encoding = SOX_ENCODING_VORBIS;
+  ft->signal.channels = vi->channels;
 
-        /* ov_pcm_total doesn't work on non-seekable files so
-         * skip that step in that case.  Also, it reports
-         * "frame"-ish results so we must * channels.
-         */
-        if (ft->seekable)
-            ft->length = ov_pcm_total(vb->vf, -1) * ft->signal.channels;
+  /* ov_pcm_total doesn't work on non-seekable files so
+   * skip that step in that case.  Also, it reports
+   * "frame"-ish results so we must * channels.
+   */
+  if (ft->seekable)
+    ft->length = ov_pcm_total(vb->vf, -1) * ft->signal.channels;
 
-        /* Record comments */
-        if (vc->comments == 0)
-                ft->comment = NULL;
-        else
-        {
-                comment_size = 0;
+  /* Record comments */
+  for (i = 0; i < vc->comments; i++)
+    append_comment(&ft->comments, vc->user_comments[i]);
 
-                for (i = 0; i < vc->comments; i++)
-                        comment_size += vc->comment_lengths[i] + 1;
+  /* Setup buffer */
+  vb->buf_len = DEF_BUF_LEN;
+  vb->buf = xcalloc(vb->buf_len, sizeof(char));
+  vb->start = vb->end = 0;
 
-                ft->comment = (char *)xcalloc(comment_size, sizeof(char));
+  /* Fill in other info */
+  vb->eof = 0;
+  vb->current_section = -1;
 
-                offset = 0;
-                for (i = 0; i < vc->comments; i++)
-                {
-                        strncpy(ft->comment + offset, vc->user_comments[i],
-                                (size_t)vc->comment_lengths[i]);
-                        offset += vc->comment_lengths[i];
-                        ft->comment[offset] = '\n';
-                        offset++;
-                }
-                /* On last comment, end string by overwriting last \n */
-                if (offset > 0)
-                    offset--;
-                ft->comment[offset] = 0;
-        }
+  return (SOX_SUCCESS);
+}
 
-        /* Setup buffer */
-        vb->buf_len = DEF_BUF_LEN;
-        vb->buf = xcalloc(vb->buf_len, sizeof(char));
-        vb->start = vb->end = 0;
 
-        /* Fill in other info */
-        vb->eof = 0;
-        vb->current_section = -1;
-
-        return (SOX_SUCCESS);
-}
-
-
 /* Refill the buffer with samples.  Returns BUF_EOF if the end of the
-   vorbis data was reached while the buffer was being filled,
-   BUF_ERROR is something bad happens, and BUF_DATA otherwise */
-static int refill_buffer (vorbis_t vb)
+ * vorbis data was reached while the buffer was being filled,
+ * BUF_ERROR is something bad happens, and BUF_DATA otherwise */
+static int refill_buffer(vorbis_t vb)
 {
-        int num_read;
+  int num_read;
 
-        if (vb->start == vb->end) /* Samples all played */
-                vb->start = vb->end = 0;
+  if (vb->start == vb->end)     /* Samples all played */
+    vb->start = vb->end = 0;
 
-        while (vb->end < vb->buf_len)
-        {
-                num_read = ov_read(vb->vf, vb->buf + vb->end,
-                                   (int)(vb->buf_len - vb->end), 0, 2, 1,
-                                   &vb->current_section);
-
-                if (num_read == 0)
-                        return (BUF_EOF);
-                else if (num_read == OV_HOLE)
-                        sox_warn("Warning: hole in stream; probably harmless");
-                else if (num_read < 0)
-                        return (BUF_ERROR);
-                else
-                        vb->end += num_read;
-
-        }
-
-        return (BUF_DATA);
+  while (vb->end < vb->buf_len) {
+    num_read = ov_read(vb->vf, vb->buf + vb->end,
+        (int) (vb->buf_len - vb->end), 0, 2, 1, &vb->current_section);
+    if (num_read == 0)
+      return (BUF_EOF);
+    else if (num_read == OV_HOLE)
+      sox_warn("Warning: hole in stream; probably harmless");
+    else if (num_read < 0)
+      return (BUF_ERROR);
+    else
+      vb->end += num_read;
+  }
+  return (BUF_DATA);
 }
 
 
@@ -210,35 +178,32 @@
  * Return number of samples read.
  */
 
-static sox_size_t read(sox_format_t * ft, sox_sample_t *buf, sox_size_t len)
+static sox_size_t read(sox_format_t * ft, sox_sample_t * buf, sox_size_t len)
 {
-        vorbis_t vb = (vorbis_t) ft->priv;
-        sox_size_t i;
-        int ret;
-        sox_sample_t l;
+  vorbis_t vb = (vorbis_t) ft->priv;
+  sox_size_t i;
+  int ret;
+  sox_sample_t l;
 
 
-        for(i = 0; i < len; i++) {
-                if (vb->start == vb->end)
-                {
-                        if (vb->eof)
-                                break;
-                        ret = refill_buffer(vb);
-                        if (ret == BUF_EOF || ret == BUF_ERROR)
-                        {
-                            vb->eof = 1;
-                            if (vb->end == 0)
-                                break;
-                        }
-                }
+  for (i = 0; i < len; i++) {
+    if (vb->start == vb->end) {
+      if (vb->eof)
+        break;
+      ret = refill_buffer(vb);
+      if (ret == BUF_EOF || ret == BUF_ERROR) {
+        vb->eof = 1;
+        if (vb->end == 0)
+          break;
+      }
+    }
 
-                l = (vb->buf[vb->start+1]<<24)
-                        | (0xffffff &  (vb->buf[vb->start]<<16));
-                *(buf + i) = l;
-                vb->start += 2;
-        }
-
-        return i;
+    l = (vb->buf[vb->start + 1] << 24)
+        | (0xffffff & (vb->buf[vb->start] << 16));
+    *(buf + i) = l;
+    vb->start += 2;
+  }
+  return i;
 }
 
 /*
@@ -247,23 +212,24 @@
  */
 static int stopread(sox_format_t * ft)
 {
-        vorbis_t vb = (vorbis_t) ft->priv;
+  vorbis_t vb = (vorbis_t) ft->priv;
 
-        free(vb->buf);
-        ov_clear(vb->vf);
+  free(vb->buf);
+  ov_clear(vb->vf);
 
-        return (SOX_SUCCESS);
+  return (SOX_SUCCESS);
 }
 
 /* Write a page of ogg data to a file.  Taken directly from encode.c in
-   oggenc.   Returns the number of bytes written. */
-static int oe_write_page(ogg_page *page, sox_format_t * ft)
+ * oggenc.   Returns the number of bytes written. */
+static int oe_write_page(ogg_page * page, sox_format_t * ft)
 {
-        int written;
-        written = sox_writebuf(ft, page->header,(sox_size_t)page->header_len);
-        written += sox_writebuf(ft, page->body,(sox_size_t)page->body_len);
+  int written;
 
-        return written;
+  written = sox_writebuf(ft, page->header, (sox_size_t) page->header_len);
+  written += sox_writebuf(ft, page->body, (sox_size_t) page->body_len);
+
+  return written;
 }
 
 /* Write out the header packets.  Derived mostly from encode.c in oggenc.
@@ -274,158 +240,158 @@
   ogg_packet header_comments;
   ogg_packet header_codebooks;
   vorbis_comment vc;
-  char * comment = NULL;
+  int i, ret = HEADER_OK;
 
   memset(&vc, 0, sizeof(vc));
-  if (ft->comment && *ft->comment) {           /* Make the comment structure */
-    ++vc.comments;
-    vc.user_comments = xcalloc(1, sizeof(*vc.user_comments));
-    vc.comment_lengths = xcalloc(1, sizeof(*vc.comment_lengths));
-
-    /* Add a FIELD=value pair if not one already in the comment */
-    comment = xcalloc(1, strlen(ft->comment) + strlen("COMMENT=") + 1);
-    if (!strchr(ft->comment,'='))
-      strcpy(comment, "COMMENT=");
-    
-    vc.user_comments[0] = strcat(comment, ft->comment);
-    vc.comment_lengths[0] = strlen(comment);
+  vc.comments = num_comments(ft->comments);
+  if (vc.comments) {     /* Make the comment structure */
+    vc.comment_lengths = xcalloc(vc.comments, sizeof(*vc.comment_lengths));
+    vc.user_comments = xcalloc(vc.comments, sizeof(*vc.user_comments));
+    for (i = 0; i < vc.comments; ++i) {
+      static const char prepend[] = "Comment=";
+      char * text = xcalloc(strlen(prepend) + strlen(ft->comments[i]) + 1, sizeof(*text));
+      /* Prepend `Comment=' if no field-name already in the comment */
+      if (!strchr(ft->comments[i], '='))
+        strcpy(text, prepend);
+      vc.user_comments[i] = strcat(text, ft->comments[i]);
+      vc.comment_lengths[i] = strlen(text);
+    }
   }
-  vorbis_analysis_headerout(                   /* Build the packets */
+  vorbis_analysis_headerout(    /* Build the packets */
       &ve->vd, &vc, &header_main, &header_comments, &header_codebooks);
 
-  ogg_stream_packetin(&ve->os,&header_main);   /* And stream them out */
-  ogg_stream_packetin(&ve->os,&header_comments);
-  ogg_stream_packetin(&ve->os,&header_codebooks);
+  ogg_stream_packetin(&ve->os, &header_main);   /* And stream them out */
+  ogg_stream_packetin(&ve->os, &header_comments);
+  ogg_stream_packetin(&ve->os, &header_codebooks);
 
-  while (ogg_stream_flush(&ve->os, &ve->og)) {
-    int ret = oe_write_page(&ve->og, ft);
-    if (!ret) {
-      free(comment);
-      return HEADER_ERROR;
-    }
-  }
-  free(comment);
-  return HEADER_OK;
+  while (ogg_stream_flush(&ve->os, &ve->og) && ret == HEADER_OK)
+    if (!oe_write_page(&ve->og, ft))
+      ret = HEADER_ERROR;
+  for (i = 0; i < vc.comments; ++i)
+    free(vc.user_comments[i]);
+  free(vc.user_comments);
+  free(vc.comment_lengths);
+  return ret;
 }
 
 static int startwrite(sox_format_t * ft)
 {
-        vorbis_t vb = (vorbis_t) ft->priv;
-        vorbis_enc_t *ve;
-        long rate;
-        double quality = 3; /* Default compression quality gives ~112kbps */
+  vorbis_t vb = (vorbis_t) ft->priv;
+  vorbis_enc_t *ve;
+  long rate;
+  double quality = 3;           /* Default compression quality gives ~112kbps */
 
-        ft->signal.size = SOX_SIZE_16BIT;
-        ft->signal.encoding = SOX_ENCODING_VORBIS;
+  ft->signal.size = SOX_SIZE_16BIT;
+  ft->signal.encoding = SOX_ENCODING_VORBIS;
 
-        /* Allocate memory for all of the structures */
-        ve = vb->vorbis_enc_data = (vorbis_enc_t *)xmalloc(sizeof(vorbis_enc_t));
+  /* Allocate memory for all of the structures */
+  ve = vb->vorbis_enc_data = (vorbis_enc_t *) xmalloc(sizeof(vorbis_enc_t));
 
-        vorbis_info_init(&ve->vi);
+  vorbis_info_init(&ve->vi);
 
-        /* TODO */
-        rate = ft->signal.rate;
-        if (rate)
-            sox_fail_errno(ft, SOX_EHDR, "Error setting up Ogg Vorbis encorder - make sure you've specied a sane rate and number of channels");
+  /* TODO */
+  rate = ft->signal.rate;
+  if (rate)
+    sox_fail_errno(ft, SOX_EHDR,
+      "Error setting-up Ogg Vorbis encoder; check sample-rate & # of channels");
 
-        /* Use encoding to average bit rate of VBR as specified by the -C option */
-        if (ft->signal.compression != HUGE_VAL)
-        {
-            if (ft->signal.compression < -1 || ft->signal.compression > 10)
-            {
-                sox_fail_errno(ft,SOX_EINVAL,
-                              "Vorbis compression quality nust be between -1 and 10");
-                return SOX_EOF;
-            }
-            quality = ft->signal.compression;
-        }
-        vorbis_encode_init_vbr(&ve->vi, (int)ft->signal.channels, (int)ft->signal.rate, (float)(quality / 10));
+  /* Use encoding to average bit rate of VBR as specified by the -C option */
+  if (ft->signal.compression != HUGE_VAL) {
+    if (ft->signal.compression < -1 || ft->signal.compression > 10) {
+      sox_fail_errno(ft, SOX_EINVAL,
+                     "Vorbis compression quality nust be between -1 and 10");
+      return SOX_EOF;
+    }
+    quality = ft->signal.compression;
+  }
+  vorbis_encode_init_vbr(&ve->vi, (int) ft->signal.channels,
+                         (int) ft->signal.rate, (float) (quality / 10));
 
-        vorbis_analysis_init(&ve->vd, &ve->vi);
-        vorbis_block_init(&ve->vd, &ve->vb);
+  vorbis_analysis_init(&ve->vd, &ve->vi);
+  vorbis_block_init(&ve->vd, &ve->vb);
 
-        ogg_stream_init(&ve->os, rand()); /* Random serial number */
+  ogg_stream_init(&ve->os, rand());     /* Random serial number */
 
-        if (write_vorbis_header(ft, ve) == HEADER_ERROR)
-        {
-            sox_fail_errno(ft,SOX_EHDR,
-                          "Error writing header for Ogg Vorbis audio stream");
-            return (SOX_EOF);
-        }
+  if (write_vorbis_header(ft, ve) == HEADER_ERROR) {
+    sox_fail_errno(ft, SOX_EHDR,
+                   "Error writing header for Ogg Vorbis audio stream");
+    return (SOX_EOF);
+  }
 
-        return(SOX_SUCCESS);
+  return (SOX_SUCCESS);
 }
 
-static sox_size_t write(sox_format_t * ft, const sox_sample_t *buf, sox_size_t len)
+static sox_size_t write(sox_format_t * ft, const sox_sample_t * buf,
+                        sox_size_t len)
 {
-        vorbis_t vb = (vorbis_t) ft->priv;
-        vorbis_enc_t *ve = vb->vorbis_enc_data;
-        sox_size_t samples = len / ft->signal.channels;
-        float **buffer = vorbis_analysis_buffer(&ve->vd, (int)samples);
-        sox_size_t i, j;
-        int ret;
-        int eos = 0;
+  vorbis_t vb = (vorbis_t) ft->priv;
+  vorbis_enc_t *ve = vb->vorbis_enc_data;
+  sox_size_t samples = len / ft->signal.channels;
+  float **buffer = vorbis_analysis_buffer(&ve->vd, (int) samples);
+  sox_size_t i, j;
+  int ret;
+  int eos = 0;
 
-        /* Copy samples into vorbis buffer */
-        for (i = 0; i < samples; i++)
-                for (j = 0; j < ft->signal.channels; j++)
-                        buffer[j][i] = buf[i*ft->signal.channels + j]
-                                / ((float)SOX_SAMPLE_MAX);
+  /* Copy samples into vorbis buffer */
+  for (i = 0; i < samples; i++)
+    for (j = 0; j < ft->signal.channels; j++)
+      buffer[j][i] = buf[i * ft->signal.channels + j]
+          / ((float) SOX_SAMPLE_MAX);
 
-        vorbis_analysis_wrote(&ve->vd, (int)samples);
+  vorbis_analysis_wrote(&ve->vd, (int) samples);
 
-        while(vorbis_analysis_blockout(&ve->vd,&ve->vb)==1)
-        {
-                /* Do the main analysis, creating a packet */
-                vorbis_analysis(&ve->vb, &ve->op);
-                vorbis_bitrate_addblock(&ve->vb);
+  while (vorbis_analysis_blockout(&ve->vd, &ve->vb) == 1) {
+    /* Do the main analysis, creating a packet */
+    vorbis_analysis(&ve->vb, &ve->op);
+    vorbis_bitrate_addblock(&ve->vb);
 
-                /* Add packet to bitstream */
-                while (vorbis_bitrate_flushpacket(&ve->vd, &ve->op))
-                {
-                    ogg_stream_packetin(&ve->os,&ve->op);
+    /* Add packet to bitstream */
+    while (vorbis_bitrate_flushpacket(&ve->vd, &ve->op)) {
+      ogg_stream_packetin(&ve->os, &ve->op);
 
-                    /* If we've gone over a page boundary, we can do actual
-                     * output, so do so (for however many pages are available)
-                     */
+      /* If we've gone over a page boundary, we can do actual
+       * output, so do so (for however many pages are available)
+       */
 
-                    while(!eos)
-                    {
-                        int result = ogg_stream_pageout(&ve->os,&ve->og);
-                        if(!result) break;
+      while (!eos) {
+        int result = ogg_stream_pageout(&ve->os, &ve->og);
 
-                        ret = oe_write_page(&ve->og, ft);
-                        if(!ret)
-                            return 0;
+        if (!result)
+          break;
+
+        ret = oe_write_page(&ve->og, ft);
+        if (!ret)
+          return 0;
 
-                        if(ogg_page_eos(&ve->og))
-                            eos = 1;
-                    }
-                }
-        }
+        if (ogg_page_eos(&ve->og))
+          eos = 1;
+      }
+    }
+  }
 
-        return (len);
+  return (len);
 }
 
 static int stopwrite(sox_format_t * ft)
 {
-        vorbis_t vb = (vorbis_t) ft->priv;
-        vorbis_enc_t *ve = vb->vorbis_enc_data;
+  vorbis_t vb = (vorbis_t) ft->priv;
+  vorbis_enc_t *ve = vb->vorbis_enc_data;
 
-        /* Close out the remaining data */
-        write(ft, NULL, 0);
+  /* Close out the remaining data */
+  write(ft, NULL, 0);
 
-        ogg_stream_clear(&ve->os);
-        vorbis_block_clear(&ve->vb);
-        vorbis_dsp_clear(&ve->vd);
-        vorbis_info_clear(&ve->vi);
+  ogg_stream_clear(&ve->os);
+  vorbis_block_clear(&ve->vb);
+  vorbis_dsp_clear(&ve->vd);
+  vorbis_info_clear(&ve->vi);
 
-        return (SOX_SUCCESS);
+  return (SOX_SUCCESS);
 }
 
-static int seek(sox_format_t * ft, sox_size_t offset) 
+static int seek(sox_format_t * ft, sox_size_t offset)
 {
-  vorbis_t vb = (vorbis_t)ft->priv;
+  vorbis_t vb = (vorbis_t) ft->priv;
+
   return ov_pcm_seek(vb->vf, offset / ft->signal.channels)? SOX_EOF:SOX_SUCCESS;
 }
 
@@ -433,7 +399,7 @@
 
 const sox_format_handler_t *sox_vorbis_format_fn(void)
 {
-  static const char * names[] = {"vorbis", "ogg", NULL};
+  static const char *names[] = {"vorbis", "ogg", NULL};
   static sox_format_handler_t handler = {
     names, SOX_FILE_SEEK,
     startread, read, stopread,
--- a/src/wav.c
+++ b/src/wav.c
@@ -41,6 +41,7 @@
     unsigned short samplesPerBlock;
     unsigned short blockAlign;
     sox_size_t dataStart;           /* need to for seeking */
+    char           * comment;
     int ignoreSize;                 /* ignoreSize allows us to process 32-bit WAV files that are
                                      * greater then 2 Gb and can't be represented by the
                                      * 32-bit size field. */
@@ -898,9 +899,9 @@
         if (sox_seeki(ft, (sox_ssize_t)len, SEEK_CUR) == SOX_SUCCESS &&
             findChunk(ft, "LIST", &len) != SOX_EOF)
         {
-            ft->comment = (char*)xmalloc(256);
+            wav->comment = (char*)xmalloc(256);
             /* Initialize comment to a NULL string */
-            ft->comment[0] = 0;
+            wav->comment[0] = 0;
             while(!sox_eof(ft))
             {
                 if (sox_reads(ft,magic,4) == SOX_EOF)
@@ -934,12 +935,12 @@
                             break;
                         }
                         sox_reads(ft,text,len);
-                        if (strlen(ft->comment) + strlen(text) < 254)
+                        if (strlen(wav->comment) + strlen(text) < 254)
                         {
-                            if (ft->comment[0] != 0)
-                                strcat(ft->comment,"\n");
+                            if (wav->comment[0] != 0)
+                                strcat(wav->comment,"\n");
 
-                            strcat(ft->comment,text);
+                            strcat(wav->comment,text);
                         }
                         if (strlen(text) < len)
                            sox_seeki(ft, (sox_ssize_t)(len - strlen(text)), SEEK_CUR); 
@@ -953,12 +954,12 @@
                             break;
                         }
                         sox_reads(ft,text,len);
-                        if (strlen(ft->comment) + strlen(text) < 254)
+                        if (strlen(wav->comment) + strlen(text) < 254)
                         {
-                            if (ft->comment[0] != 0)
-                                strcat(ft->comment,"\n");
+                            if (wav->comment[0] != 0)
+                                strcat(wav->comment,"\n");
 
-                            strcat(ft->comment,text);
+                            strcat(wav->comment,text);
                         }
                         if (strlen(text) < len)
                            sox_seeki(ft, (sox_ssize_t)(len - strlen(text)), SEEK_CUR); 
@@ -1107,8 +1108,8 @@
     free(wav->packet);
     free(wav->samples);
     free(wav->iCoefs);
-    free(ft->comment);
-    ft->comment = NULL;
+    free(wav->comment);
+    wav->comment = NULL;
 
     switch (ft->signal.encoding)
     {