ref: 483ff27f9d5067fd597dae09161d07a3857293b6
parent: ec628c4dfbf62891e311567034936a0b2cb584c4
author: Jacob Moody <moody@posixcafe.org>
date: Wed Aug 23 11:57:00 EDT 2023
games/voc^(enc dec): move from audio and add encoder
--- a/rc/bin/play
+++ b/rc/bin/play
@@ -70,7 +70,7 @@
case *audio/mus*
games/mus | midi
case *audio/x-voc*
- audio/vocdec
+ games/vocdec
case *pls*
awk 'BEGIN {FS="="} /^File/{print $2}' | play1 list plain
case *
--- a/sys/man/1/audio
+++ b/sys/man/1/audio
@@ -1,6 +1,6 @@
.TH AUDIO 1
.SH NAME
-mp3dec, mp3enc, oggdec, oggenc, flacdec, flacenc, sundec, wavdec, vocdec, pcmconv, mixfs \- decode and encode audio files
+mp3dec, mp3enc, oggdec, oggenc, flacdec, flacenc, sundec, wavdec pcmconv, mixfs \- decode and encode audio files
.SH SYNOPSIS
.B audio/mp3dec
[
@@ -29,8 +29,6 @@
]
.br
.B audio/sundec
-.br
-.B audio/vocdec
.PP
.B audio/oggenc
.br
--- /dev/null
+++ b/sys/man/1/vocdec
@@ -1,0 +1,45 @@
+.TH VOCDEC 1
+.SH NAME
+vocdec, vocenc \- decode and encode VOC files
+.SH SYNOPSIS
+.B games/vocdec
+.br
+.B games/vocenc
+.I fmt
+.SH DESCRIPTION
+.I Vocdec
+and
+.I vocenc
+process and create Creative Voice formatted audio files.
+.PP
+.I Vocdec
+reads a VOC file on stdin and outputs a raw audio stream
+in the default format understood by
+.BR /dev/audio ,
+that is signed 16 bit little endian, two channels, 44.1kHz.
+.PP
+.I Vocenc
+reads raw audio on stdin and outputs a VOC formatted file
+on stdout. The
+.I fmt
+argument specifies both the input and output raw audio format
+using the same string format as
+.IR pcmconv (1).
+.SH EXAMPLES
+Create a VOC file from microphone input:
+.IP
+.EX
+</dev/audio audio/pcmconv -o u8c1r44100 | \\
+ games/vocenc u8c1r44100 >out.voc
+.EE
+.SH SOURCE
+.B /sys/src/games/vocdec.c
+.br
+.B /sys/src/games/vocenc.c
+.SH SEE ALSO
+.IR audio (1),
+.IR audio (3),
+.IR play (1)
+.SH HISTORY
+Vocdec first appeared in 9front (February, 2023).
+Vocenc was added later (August, 2023).
--- a/sys/src/cmd/audio/mkfile
+++ b/sys/src/cmd/audio/mkfile
@@ -1,7 +1,7 @@
</$objtype/mkfile
LIBS=libogg libvorbis libFLAC libtags
-PROGS=pcmconv oggdec oggenc mp3dec mp3enc flacdec flacenc wavdec sundec vocdec mixfs readtags zuke scream
+PROGS=pcmconv oggdec oggenc mp3dec mp3enc flacdec flacenc wavdec sundec mixfs readtags zuke scream
#libs must be made first
DIRS=$LIBS $PROGS
--- a/sys/src/cmd/audio/vocdec/mkfile
+++ /dev/null
@@ -1,8 +1,0 @@
-</$objtype/mkfile
-<../config
-
-OFILES=vocdec.$O
-
-TARG=vocdec
-
-</sys/src/cmd/mkone
--- a/sys/src/cmd/audio/vocdec/vocdec.c
+++ /dev/null
@@ -1,149 +1,0 @@
-#include <u.h>
-#include <libc.h>
-
-uchar
-get(void)
-{
- uchar b;
-
- if(read(0, &b, 1) != 1)
- sysfatal("read: %r");
- return b;
-}
-
-uint
-get2(void)
-{
- uchar b[2];
-
- if(readn(0, b, 2) != 2)
- sysfatal("read: %r");
- return (b[0]<<0) | (b[1]<<8);
-}
-
-uint
-get3(void)
-{
- uchar b[3];
-
- if(readn(0, b, 3) != 3)
- sysfatal("read: %r");
- return (b[0]<<0) | (b[1]<<8) | (b[2]<<16);
-}
-
-uint
-get4(void)
-{
- uchar b[4];
-
- if(readn(0, b, 4) != 4)
- sysfatal("read: %r");
- return (b[0]<<0) | (b[1]<<8) | (b[2]<<16) | (b[3]<<24);
-}
-
-typedef struct Block Block;
-struct Block {
- uchar type;
- uint size;
- uint freq;
- uint codec;
- uchar chan;
- uint bits;
-};
-
-char*
-codec(int c)
-{
- switch(c){
- case 0x00:
- return "u8";
- case 0x04:
- return "s16";
- case 0x06:
- return "µ8";
- case 0x07:
- return "a8";
- default:
- sysfatal("unsupported");
- }
- return nil;
-}
-
-void
-usage(void)
-{
- fprint(2, "usage: %s", argv0);
- exits("usage");
-}
-
-void
-main(int argc, char **argv)
-{
- Block b;
- static uchar buf[2048];
- char fmt[32], size[32];
- ushort chksum, ver;
- int n;
-
- ARGBEGIN{
- default:
- usage();
- break;
- }ARGEND;
-
- if(readn(0, buf, 20) != 20 || memcmp(buf, "Creative Voice File\x1a", 20) != 0)
- sysfatal("not a voc file");
-
- get2(); /* gulp */
- ver = get2();
- chksum = get2();
- if(~ver + 0x1234 != chksum)
- sysfatal("invalid checksum");
-
- memset(&b, 0, sizeof b);
- for(;;){
- /* files may end without a proper block */
- if(read(0, &b.type, 1) != 1 || b.type == 0)
- break;
- b.size = get3();
-
- switch(b.type){
- case 1:
- b.freq = 1000000 / (256 - get());
- b.codec = get();
- b.chan = 1;
- b.size -= 2;
- break;
- case 2:
- if(b.freq == 0)
- sysfatal("block 2 without defined codec");
- break;
- case 9:
- b.freq = get4();
- b.bits = get();
- b.chan = get();
- b.codec = get2();
- get4(); /* reserved */
- b.size -= 4+1+1+2+4;
- break;
- default:
- while(b.size != 0){
- n = b.size;
- if(n > sizeof buf)
- n = sizeof buf;
- if(readn(0, buf, n) != n)
- break;
- b.size -= n;
- }
- break;
- }
-
- snprint(fmt, sizeof fmt, "%sc%dr%d", codec(b.codec), b.chan, b.freq);
- snprint(size, sizeof size, "%d", b.size);
- if(fork() == 0){
- execl("/bin/audio/pcmconv", "pcmconv", "-i", fmt, "-l", size, nil);
- sysfatal("exec: %r");
- }
- waitpid();
- }
-}
--- a/sys/src/games/mkfile
+++ b/sys/src/games/mkfile
@@ -21,6 +21,8 @@
dpic\
todpic\
turtle\
+ vocenc\
+ vocdec\
OFILES=
HFILES=
--- /dev/null
+++ b/sys/src/games/vocdec.c
@@ -1,0 +1,118 @@
+#include <u.h>
+#include <libc.h>
+
+#define GET16(p) ((u16int)(p)[0] | (u16int)(p)[1]<<8)
+#define GET24(p) ((u32int)(p)[0] | (u32int)(p)[1]<<8 | (u32int)(p)[2]<<16)
+#define GET32(p) ((u32int)(p)[0] | (u32int)(p)[1]<<8 | (u32int)(p)[2]<<16 | (u32int)(p)[3]<<24)
+
+typedef struct Block Block;
+struct Block {
+ uchar type;
+ uint size;
+ uint freq;
+ uint codec;
+ uchar chan;
+ uint bits;
+};
+
+char*
+codec(int c)
+{
+ switch(c){
+ case 0x00:
+ return "u8";
+ case 0x04:
+ return "s16";
+ case 0x06:
+ return "µ8";
+ case 0x07:
+ return "a8";
+ default:
+ sysfatal("unsupported");
+ }
+ return nil;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s", argv0);
+ exits("usage");
+}
+
+Block b;
+
+void
+main(int argc, char **argv)
+{
+ uchar buf[8192];
+ char fmt[32], size[32];
+ ushort chksum, ver;
+ int n;
+
+ ARGBEGIN{
+ default:
+ usage();
+ break;
+ }ARGEND;
+
+ if(readn(0, buf, 20+2+2+2) != 20+2+2+2 || memcmp(buf, "Creative Voice File\x1a", 20) != 0)
+ sysfatal("not a voc file");
+
+ ver = GET16(buf+20+2);
+ chksum = GET16(buf+20+2+2);
+ if(~ver + 0x1234 != chksum)
+ sysfatal("invalid checksum");
+
+ for(;;){
+ if(readn(0, buf, 4) != 4)
+ break;
+ b.type = buf[0];
+ b.size = GET24(buf+1);
+
+ switch(b.type){
+ case 0:
+ exits(nil);
+ case 1:
+ if(readn(0, buf, 2) != 2)
+ sysfatal("truncated block");
+ b.codec = buf[0];
+ b.freq = 1000000 / (256 - buf[1]);
+ b.chan = 1;
+ b.size -= 2;
+ break;
+ case 2:
+ if(b.freq == 0)
+ sysfatal("block 2 without defined codec");
+ break;
+ case 9:
+ if(readn(0, buf, 4+1+1+2+4) != 4+1+1+2+4)
+ sysfatal("truncated block");
+ b.freq = GET32(buf);
+ b.bits = buf[4];
+ b.chan = buf[5];
+ b.codec = buf[6];
+ b.size -= 4+1+1+2+4;
+ break;
+ default:
+ while(b.size != 0){
+ n = b.size;
+ if(n > sizeof buf)
+ n = sizeof buf;
+ if(read(0, buf, n) <= 0)
+ sysfatal("truncated block");
+ b.size -= n;
+ }
+ break;
+ }
+
+ snprint(fmt, sizeof fmt, "%sc%dr%d", codec(b.codec), b.chan, b.freq);
+ snprint(size, sizeof size, "%d", b.size);
+ if(fork() == 0){
+ execl("/bin/audio/pcmconv", "pcmconv", "-i", fmt, "-l", size, nil);
+ sysfatal("exec: %r");
+ }
+ waitpid();
+ }
+ exits(nil);
+}
--- /dev/null
+++ b/sys/src/games/vocenc.c
@@ -1,0 +1,124 @@
+#include <u.h>
+#include <libc.h>
+
+#define PUT16(p, u) ((p)[1] = (u)>>8, (p)[0] = (u))
+#define PUT24(p, u) ((p)[2] = (u)>>16, (p)[1] = (u)>>8, (p)[0] = (u))
+#define PUT32(p, u) ((p)[3] = (u)>>24, (p)[2] = (u)>>16, (p)[1] = (u)>>8, (p)[0] = (u))
+
+typedef struct Desc Desc;
+struct Desc
+{
+ int rate;
+ int channels;
+ int bits;
+ Rune fmt;
+};
+
+Desc
+mkdesc(char *f)
+{
+ Desc d;
+ Rune r;
+ char *p;
+
+ memset(&d, 0, sizeof(d));
+ p = f;
+ while(*p != 0){
+ p += chartorune(&r, p);
+ switch(r){
+ case L'r':
+ d.rate = strtol(p, &p, 10);
+ break;
+ case L'c':
+ d.channels = strtol(p, &p, 10);
+ break;
+ case L'm':
+ r = L'µ';
+ case L's':
+ case L'u':
+ case L'a':
+ case L'µ':
+ d.fmt = r;
+ d.bits = strtol(p, &p, 10);
+ break;
+ default:
+ goto Bad;
+ }
+ }
+ if(d.rate <= 0)
+ goto Bad;
+ if(d.bits <= 0 || d.bits > 16)
+ goto Bad;
+ if(d.bits == 16 && d.fmt != 's')
+ goto Bad;
+ return d;
+Bad:
+ sysfatal("bad format: %s", f);
+ return d;
+}
+
+int
+codec(Rune r)
+{
+ switch(r){
+ case 'u':
+ return 0x0;
+ case 's':
+ return 0x04;
+ case L'µ':
+ return 0x06;
+ case 'a':
+ return 0x07;
+ default:
+ sysfatal("bad format");
+ }
+ return -1;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s fmt\n", argv0);
+ sysfatal("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ Desc o;
+ char magic[] = "Creative Voice File\x1a";
+ uchar buf[16+8192];
+ long n;
+ int c;
+ enum{ hdrsz = 0x1a, ver = 0x010a };
+
+ ARGBEGIN{
+ default:
+ usage();
+ break;
+ }ARGEND;
+ if(argc < 1)
+ usage();
+
+ o = mkdesc(argv[0]);
+ write(1, magic, sizeof magic - 1);
+ PUT16(buf, hdrsz);
+ PUT16(buf+2, ver);
+ PUT16(buf+4, ~ver + 0x1234);
+ write(1, buf, 2+2+2);
+
+ while((n = read(0, buf+16, sizeof buf-16)) > 0){
+ buf[0] = 0x9;
+ PUT24(buf+1, n+12);
+ PUT32(buf+4, o.rate);
+ buf[8] = o.bits;
+ buf[9] = o.channels;
+ c = codec(o.fmt);
+ PUT16(buf+10, c);
+ PUT32(buf+12, 0x0);
+ write(1, buf, 16+n);
+ }
+ buf[0] = 0;
+ write(1, buf, 1);
+ exits(nil);
+}