Index: uspace/app/fontedit/fontedit.c
===================================================================
--- uspace/app/fontedit/fontedit.c	(revision 7bef2d8af93a2cf0b0e06f70616af764765f6459)
+++ uspace/app/fontedit/fontedit.c	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -142,8 +142,13 @@
 static void font_edit_ctrl_key(font_edit_t *fedit, kbd_event_t *event)
 {
+	errno_t rc;
+
 	switch (event->key) {
 	case KC_S:
 		printf("Save!\n");
 		(void) gfx_glyph_bmp_save(fedit->gbmp);
+		rc = gfx_typeface_save(fedit->typeface, "/test.tpf");
+		if (rc != EOK)
+			printf("Error saving typeface.\n");
 		font_edit_paint(fedit);
 		break;
Index: uspace/lib/gfxfont/include/gfx/typeface.h
===================================================================
--- uspace/lib/gfxfont/include/gfx/typeface.h	(revision 7bef2d8af93a2cf0b0e06f70616af764765f6459)
+++ uspace/lib/gfxfont/include/gfx/typeface.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -48,4 +48,5 @@
 extern gfx_font_info_t *gfx_typeface_first_font(gfx_typeface_t *);
 extern gfx_font_info_t *gfx_typeface_next_font(gfx_font_info_t *);
+extern errno_t gfx_typeface_save(gfx_typeface_t *, const char *);
 
 #endif
Index: uspace/lib/gfxfont/meson.build
===================================================================
--- uspace/lib/gfxfont/meson.build	(revision 7bef2d8af93a2cf0b0e06f70616af764765f6459)
+++ uspace/lib/gfxfont/meson.build	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'gfx' ]
+deps = [ 'gfx', 'riff' ]
 src = files(
 	'src/font.c',
Index: uspace/lib/gfxfont/private/font.h
===================================================================
--- uspace/lib/gfxfont/private/font.h	(revision 7bef2d8af93a2cf0b0e06f70616af764765f6459)
+++ uspace/lib/gfxfont/private/font.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -44,4 +44,5 @@
 #include <types/gfx/font.h>
 #include <types/gfx/typeface.h>
+#include <riff/chunk.h>
 
 /** Font
@@ -85,4 +86,5 @@
 extern errno_t gfx_font_splice_at_glyph(gfx_font_t *, gfx_glyph_t *,
     gfx_rect_t *);
+extern errno_t gfx_font_save(gfx_font_info_t *, riffw_t *);
 
 #endif
Index: uspace/lib/gfxfont/private/glyph.h
===================================================================
--- uspace/lib/gfxfont/private/glyph.h	(revision 7bef2d8af93a2cf0b0e06f70616af764765f6459)
+++ uspace/lib/gfxfont/private/glyph.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -41,4 +41,5 @@
 #include <gfx/coord.h>
 #include <types/gfx/glyph.h>
+#include <riff/chunk.h>
 
 /** Glyph
@@ -76,4 +77,5 @@
 extern errno_t gfx_glyph_transfer(gfx_glyph_t *, gfx_coord_t, gfx_bitmap_t *,
     gfx_rect_t *);
+extern errno_t gfx_glyph_save(gfx_glyph_t *, riffw_t *);
 
 #endif
Index: uspace/lib/gfxfont/private/tpf_file.h
===================================================================
--- uspace/lib/gfxfont/private/tpf_file.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
+++ uspace/lib/gfxfont/private/tpf_file.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libgfxfont
+ * @{
+ */
+/**
+ * @file TPF file definitions
+ *
+ */
+
+#ifndef _GFX_PRIVATE_TPF_FILE_H
+#define _GFX_PRIVATE_TPF_FILE_H
+
+enum {
+	/** Typeface RIFF format ID */
+	FORM_TPFC = 0x43465054,
+
+	/** Font list type */
+	LTYPE_font = 0x746e6f66,
+
+	/** Font properties chunk ID */
+	CKID_fprp = 0x70727066,
+	/** Font metrics chunk ID */
+	CKID_fmtr = 0x72746d66,
+	/** Font bitmap chunk ID */
+	CKID_fbmp = 0x706d6266,
+
+	/** Glyph list type */
+	LTYPE_glph = 0x68706c67,
+
+	/** Glyph metrics chunk ID */
+	CKID_gmtr = 0x72746d67,
+	/** Glyph patterns chunk ID */
+	CKID_gpat = 0x74617067,
+	/** Glyph rectangle/origin chunk ID */
+	CKID_gror = 0x726f7267
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/gfxfont/src/font.c
===================================================================
--- uspace/lib/gfxfont/src/font.c	(revision 7bef2d8af93a2cf0b0e06f70616af764765f6459)
+++ uspace/lib/gfxfont/src/font.c	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -44,4 +44,5 @@
 #include "../private/font.h"
 #include "../private/glyph.h"
+#include "../private/tpf_file.h"
 #include "../private/typeface.h"
 
@@ -359,4 +360,148 @@
 }
 
+/** Save font properties to RIFF TPF file.
+ *
+ * @param props Font properties
+ * @param riffw RIFF writer
+ * @return EOK on success or an error code
+ */
+static errno_t gfx_font_props_save(gfx_font_props_t *props, riffw_t *riffw)
+{
+	errno_t rc;
+	riff_wchunk_t propsck;
+
+	rc = riff_wchunk_start(riffw, CKID_fprp, &propsck);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_write(riffw, (void *) props, sizeof(*props));
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_end(riffw, &propsck);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Save font metrics to RIFF TPF file.
+ *
+ * @param metrics Font metrics
+ * @param riffw RIFF writer
+ * @return EOK on success or an error code
+ */
+static errno_t gfx_font_metrics_save(gfx_font_metrics_t *metrics,
+    riffw_t *riffw)
+{
+	errno_t rc;
+	riff_wchunk_t mtrck;
+
+	rc = riff_wchunk_start(riffw, CKID_fmtr, &mtrck);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_write(riffw, (void *) metrics, sizeof(*metrics));
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_end(riffw, &mtrck);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Save font bitmap to RIFF TPF file.
+ *
+ * @param font Font
+ * @param riffw RIFF writer
+ * @return EOK on success or an error code
+ */
+static errno_t gfx_font_bitmap_save(gfx_font_t *font, riffw_t *riffw)
+{
+	errno_t rc;
+	riff_wchunk_t bmpck;
+	gfx_bitmap_alloc_t alloc;
+
+	rc = gfx_bitmap_get_alloc(font->bitmap, &alloc);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_start(riffw, CKID_fbmp, &bmpck);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_write_uint32(riffw, font->rect.p1.x);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_write_uint32(riffw, font->rect.p1.y);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_write_uint32(riffw, 8 * sizeof(uint32_t));
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_write(riffw, (void *) alloc.pixels,
+	    font->rect.p1.x * font->rect.p1.y * sizeof(uint32_t));
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_end(riffw, &bmpck);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Save font into RIFF TPF file.
+ *
+ * @param finfo Font info
+ * @param riffw RIFF writer
+ */
+errno_t gfx_font_save(gfx_font_info_t *finfo, riffw_t *riffw)
+{
+	errno_t rc;
+	riff_wchunk_t fontck;
+	gfx_glyph_t *glyph;
+
+	rc = riff_wchunk_start(riffw, CKID_LIST, &fontck);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_write_uint32(riffw, LTYPE_font);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_font_props_save(&finfo->props, riffw);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_font_metrics_save(&finfo->font->metrics, riffw);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_font_bitmap_save(finfo->font, riffw);
+	if (rc != EOK)
+		return rc;
+
+	glyph = gfx_font_first_glyph(finfo->font);
+	while (glyph != NULL) {
+		rc = gfx_glyph_save(glyph, riffw);
+		if (rc != EOK)
+			return rc;
+
+		glyph = gfx_font_next_glyph(glyph);
+	}
+
+	rc = riff_wchunk_end(riffw, &fontck);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
 /** @}
  */
Index: uspace/lib/gfxfont/src/glyph.c
===================================================================
--- uspace/lib/gfxfont/src/glyph.c	(revision 7bef2d8af93a2cf0b0e06f70616af764765f6459)
+++ uspace/lib/gfxfont/src/glyph.c	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -44,4 +44,5 @@
 #include "../private/font.h"
 #include "../private/glyph.h"
+#include "../private/tpf_file.h"
 
 /** Initialize glyph metrics structure.
@@ -314,4 +315,136 @@
 }
 
+/** Save glyph metrics to RIFF TPF file.
+ *
+ * @param metrics Glyph metrics
+ * @param riffw RIFF writer
+ * @return EOK on success or an error code
+ */
+static errno_t gfx_glyph_metrics_save(gfx_glyph_metrics_t *metrics,
+    riffw_t *riffw)
+{
+	errno_t rc;
+	riff_wchunk_t mtrck;
+
+	rc = riff_wchunk_start(riffw, CKID_gmtr, &mtrck);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_write(riffw, (void *) metrics, sizeof(*metrics));
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_end(riffw, &mtrck);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Save glyph patterns to RIFF TPF file.
+ *
+ * @param glyph Glyph
+ * @param riffw RIFF writer
+ * @return EOK on success or an error code
+ */
+static errno_t gfx_glyph_patterns_save(gfx_glyph_t *glyph, riffw_t *riffw)
+{
+	errno_t rc;
+	riff_wchunk_t patck;
+	gfx_glyph_pattern_t *pat;
+	const char *str;
+
+	rc = riff_wchunk_start(riffw, CKID_gpat, &patck);
+	if (rc != EOK)
+		return rc;
+
+	pat = gfx_glyph_first_pattern(glyph);
+	while (pat != NULL) {
+		str = gfx_glyph_pattern_str(pat);
+
+		rc = riff_wchunk_write(riffw, (void *) str, 1 + str_size(str));
+		if (rc != EOK)
+			return rc;
+
+		pat = gfx_glyph_next_pattern(pat);
+	}
+
+	rc = riff_wchunk_end(riffw, &patck);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Save glyph rectangle/origin to RIFF TPF file.
+ *
+ * @param glyph Glyph
+ * @param riffw RIFF writer
+ * @return EOK on success or an error code
+ */
+static errno_t gfx_glyph_rectangle_origin_save(gfx_glyph_t *glyph,
+    riffw_t *riffw)
+{
+	errno_t rc;
+	riff_wchunk_t rorck;
+
+	rc = riff_wchunk_start(riffw, CKID_gror, &rorck);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_write(riffw, (void *) &glyph->rect,
+	    sizeof(glyph->rect));
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_write(riffw, (void *) &glyph->origin,
+	    sizeof(glyph->origin));
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_end(riffw, &rorck);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Save glyph into RIFF TPF file.
+ *
+ * @param glyph Glyph
+ * @param riffw RIFF writer
+ */
+errno_t gfx_glyph_save(gfx_glyph_t *glyph, riffw_t *riffw)
+{
+	errno_t rc;
+	riff_wchunk_t glyphck;
+
+	rc = riff_wchunk_start(riffw, CKID_LIST, &glyphck);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_write_uint32(riffw, LTYPE_glph);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_glyph_metrics_save(&glyph->metrics, riffw);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_glyph_patterns_save(glyph, riffw);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_glyph_rectangle_origin_save(glyph, riffw);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_end(riffw, &glyphck);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
 /** @}
  */
Index: uspace/lib/gfxfont/src/typeface.c
===================================================================
--- uspace/lib/gfxfont/src/typeface.c	(revision 7bef2d8af93a2cf0b0e06f70616af764765f6459)
+++ uspace/lib/gfxfont/src/typeface.c	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -41,7 +41,9 @@
 #include <gfx/typeface.h>
 #include <mem.h>
+#include <riff/chunk.h>
 #include <stdlib.h>
 #include "../private/font.h"
 #include "../private/glyph.h"
+#include "../private/tpf_file.h"
 #include "../private/typeface.h"
 
@@ -111,4 +113,54 @@
 }
 
+/** Save typeface into a TPF file.
+ *
+ * @param tface Typeface
+ * @param fname Destination file name
+ * @return EOK on success or an error code
+ */
+errno_t gfx_typeface_save(gfx_typeface_t *tface, const char *fname)
+{
+	riffw_t *riffw = NULL;
+	errno_t rc;
+	gfx_font_info_t *finfo;
+	riff_wchunk_t riffck;
+
+	rc = riff_wopen(fname, &riffw);
+	if (rc != EOK)
+		return rc;
+
+	rc = riff_wchunk_start(riffw, CKID_RIFF, &riffck);
+	if (rc != EOK)
+		goto error;
+
+	rc = riff_write_uint32(riffw, FORM_TPFC);
+	if (rc != EOK)
+		goto error;
+
+	finfo = gfx_typeface_first_font(tface);
+	while (finfo != NULL) {
+		/* Save font */
+		rc = gfx_font_save(finfo, riffw);
+		if (rc != EOK)
+			goto error;
+
+		finfo = gfx_typeface_next_font(finfo);
+	}
+
+	rc = riff_wchunk_end(riffw, &riffck);
+	if (rc != EOK)
+		goto error;
+
+	rc = riff_wclose(riffw);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+error:
+	if (riffw != NULL)
+		riff_wclose(riffw);
+	return rc;
+}
+
 /** @}
  */
Index: uspace/lib/meson.build
===================================================================
--- uspace/lib/meson.build	(revision 7bef2d8af93a2cf0b0e06f70616af764765f6459)
+++ uspace/lib/meson.build	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -68,4 +68,5 @@
 	'pcut',
 	'posix',
+	'riff',
 	'scsi',
 	'sif',
Index: uspace/lib/riff/include/riff/chunk.h
===================================================================
--- uspace/lib/riff/include/riff/chunk.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
+++ uspace/lib/riff/include/riff/chunk.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libriff
+ * @{
+ */
+/**
+ * @file RIFF chunk.
+ */
+
+#ifndef RIFF_CHUNK_H
+#define RIFF_CHUNK_H
+
+#include <stddef.h>
+#include <types/riff/chunk.h>
+
+extern int riff_wopen(const char *, riffw_t **);
+extern int riff_wclose(riffw_t *);
+extern int riff_wchunk_start(riffw_t *, riff_ckid_t, riff_wchunk_t *);
+extern int riff_wchunk_end(riffw_t *, riff_wchunk_t *);
+extern int riff_wchunk_write(riffw_t *, void *, size_t);
+extern int riff_write_uint32(riffw_t *, uint32_t);
+
+extern int riff_ropen(const char *, riffr_t **);
+extern int riff_rclose(riffr_t *);
+extern int riff_read_uint32(riffr_t *, uint32_t *);
+extern int riff_rchunk_start(riffr_t *, riff_rchunk_t *);
+extern int riff_rchunk_end(riffr_t *, riff_rchunk_t *);
+extern int riff_rchunk_read(riffr_t *, riff_rchunk_t *, void *, size_t,
+    size_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/riff/include/riff/rwave.h
===================================================================
--- uspace/lib/riff/include/riff/rwave.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
+++ uspace/lib/riff/include/riff/rwave.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libriff
+ * @{
+ */
+/**
+ * @file Waveform Audio File Format (WAVE).
+ */
+
+#ifndef RIFF_WAVE_H
+#define RIFF_WAVE_H
+
+#include <types/riff/rwave.h>
+
+extern int rwave_wopen(const char *, rwave_params_t *params, rwavew_t **);
+extern int rwave_write_samples(rwavew_t *, void *, size_t);
+extern int rwave_wclose(rwavew_t *);
+
+extern int rwave_ropen(const char *, rwave_params_t *params, rwaver_t **);
+extern int rwave_read_samples(rwaver_t *, void *, size_t, size_t *);
+extern int rwave_rclose(rwaver_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/riff/include/types/riff/chunk.h
===================================================================
--- uspace/lib/riff/include/types/riff/chunk.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
+++ uspace/lib/riff/include/types/riff/chunk.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2015 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libriff
+ * @{
+ */
+/**
+ * @file RIFF chunk types.
+ */
+
+#ifndef RIFF_TYPES_CHUNK_H
+#define RIFF_TYPES_CHUNK_H
+
+#include <stdint.h>
+#include <stdio.h>
+
+typedef uint32_t riff_ckid_t;
+typedef uint32_t riff_cksize_t;
+
+/** RIFF chunk for reading */
+typedef struct {
+	long ckstart;
+	riff_ckid_t ckid;
+	riff_cksize_t cksize;
+} riff_rchunk_t;
+
+/** RIFF chunk for writing */
+typedef struct {
+	long ckstart;
+} riff_wchunk_t;
+
+/** RIFF chunk info */
+typedef struct {
+	long ckstart;
+	riff_ckid_t ckid;
+	riff_cksize_t cksize;
+} riff_ckinfo_t;
+
+/** RIFF writer */
+typedef struct {
+	FILE *f;
+	/** Chunk start offset */
+	long ckstart;
+} riffw_t;
+
+/** RIFF reader */
+typedef struct {
+	FILE *f;
+} riffr_t;
+
+enum {
+	/** RIFF chunk ID */
+	CKID_RIFF = 0x46464952,
+	/** LIST chunk ID */
+	CKID_LIST = 0x5453494C,
+
+	/** WAVE RIFF form ID */
+	FORM_WAVE = 0x45564157,
+	/** fmt chunk ID */
+	CKID_fmt = 0x20746d66,
+	/** data chunk ID */
+	CKID_data = 0x61746164,
+
+	/** PCM wave format */
+	WFMT_PCM = 0x0001
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/riff/include/types/riff/rwave.h
===================================================================
--- uspace/lib/riff/include/types/riff/rwave.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
+++ uspace/lib/riff/include/types/riff/rwave.h	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2015 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libriff
+ * @{
+ */
+/**
+ * @file Waveform Audio File Format (WAVE) types.
+ */
+
+#ifndef RIFF_TYPES_WAVE_H
+#define RIFF_TYPES_WAVE_H
+
+#include <stdint.h>
+#include <types/riff/chunk.h>
+
+/** WAVE format chunk data
+ *
+ * Actual data structure in the RIFF file
+ */
+typedef struct {
+	/** Format category */
+	uint16_t format_tag;
+	/** Number of channels */
+	uint16_t channels;
+	/** Sampling rate */
+	uint32_t smp_sec;
+	/** For buffer estimation */
+	uint32_t avg_bytes_sec;
+	/** Data block size */
+	uint16_t block_align;
+	/** Bits per sample (PCM only) */
+	uint16_t bits_smp;
+} rwave_fmt_t;
+
+/** RIFF WAVE params
+ *
+ * Used by the API
+ */
+typedef struct {
+	/** Number of channels */
+	int channels;
+	/** Number of bits per sample */
+	int bits_smp;
+	/** Sample frequency in Hz */
+	int smp_freq;
+} rwave_params_t;
+
+/** RIFF WAVE writer */
+typedef struct {
+	/** RIFF writer */
+	riffw_t *rw;
+	/** Buffer size in bytes */
+	size_t bufsize;
+	/** Conversion buffer */
+	void *buf;
+	/** WAVE file parameters */
+	rwave_params_t params;
+	/** RIFF WAVE chunk */
+	riff_wchunk_t wave;
+	/** data chunk */
+	riff_wchunk_t data;
+} rwavew_t;
+
+/** RIFF WAVE reader */
+typedef struct {
+	/** RIFF reader */
+	riffr_t *rr;
+	/** RIFF WAVE chunk */
+	riff_rchunk_t wave;
+	/** data chunk */
+	riff_rchunk_t data;
+} rwaver_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/riff/meson.build
===================================================================
--- uspace/lib/riff/meson.build	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
+++ uspace/lib/riff/meson.build	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2020 Jiri Svoboda
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+src = files(
+	'src/chunk.c',
+	'src/rwave.c',
+)
Index: uspace/lib/riff/src/chunk.c
===================================================================
--- uspace/lib/riff/src/chunk.c	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
+++ uspace/lib/riff/src/chunk.c	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) 2015 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libriff
+ * @{
+ */
+/**
+ * @file RIFF chunk.
+ */
+
+#include <assert.h>
+#include <byteorder.h>
+#include <errno.h>
+#include <macros.h>
+#include <riff/chunk.h>
+#include <stdlib.h>
+
+/** Open RIFF file for writing
+ *
+ * @param fname File name
+ * @param rrw   Place to store pointer to RIFF writer
+ *
+ * @return EOK on success, ENOMEM if out of memory, EIO if failed to open
+ *         file.
+ */
+int riff_wopen(const char *fname, riffw_t **rrw)
+{
+	riffw_t *rw;
+
+	rw = calloc(1, sizeof(riffw_t));
+	if (rw == NULL)
+		return ENOMEM;
+
+	rw->f = fopen(fname, "wb");
+	if (rw->f == NULL) {
+		free(rw);
+		return EIO;
+	}
+
+	*rrw = rw;
+	return EOK;
+}
+
+/** Close RIFF for writing.
+ *
+ * @param rw RIFF writer
+ * @return EOK on success. On write error EIO is returned and RIFF writer
+ *         is destroyed anyway.
+ */
+int riff_wclose(riffw_t *rw)
+{
+	int rv;
+
+	rv = fclose(rw->f);
+	free(rw);
+
+	return (rv == 0) ? EOK : EIO;
+}
+
+/** Write uint32_t value into RIFF file
+ *
+ * @param rw RIFF writer
+ * @param v  Value
+ * @return EOK on success, EIO on error.
+ */
+int riff_write_uint32(riffw_t *rw, uint32_t v)
+{
+	uint32_t vle;
+
+	vle = host2uint32_t_le(v);
+	if (fwrite(&vle, 1, sizeof(vle), rw->f) < sizeof(vle))
+		return EIO;
+
+	return EOK;
+}
+
+/** Begin writing chunk.
+ *
+ * @param rw     RIFF writer
+ * @param ckid   Chunk ID
+ * @param wchunk Pointer to chunk structure to fill in
+ *
+ * @return EOK on success, EIO on write error
+ */
+int riff_wchunk_start(riffw_t *rw, riff_ckid_t ckid, riff_wchunk_t *wchunk)
+{
+	long pos;
+	int rc;
+
+	pos = ftell(rw->f);
+	if (pos < 0)
+		return EIO;
+
+	wchunk->ckstart = pos;
+
+	rc = riff_write_uint32(rw, ckid);
+	if (rc != EOK) {
+		assert(rc == EIO);
+		return EIO;
+	}
+
+	rc = riff_write_uint32(rw, 0);
+	if (rc != EOK) {
+		assert(rc == EIO);
+		return EIO;
+	}
+
+	return EOK;
+}
+
+/** Finish writing chunk.
+ *
+ * @param rw     RIFF writer
+ * @param wchunk Pointer to chunk structure
+ *
+ * @return EOK on success, EIO error.
+ */
+int riff_wchunk_end(riffw_t *rw, riff_wchunk_t *wchunk)
+{
+	long pos;
+	long cksize;
+	int rc;
+
+	pos = ftell(rw->f);
+	if (pos < 0)
+		return EIO;
+
+	cksize = pos - wchunk->ckstart - 8;
+
+	if (fseek(rw->f, wchunk->ckstart + 4, SEEK_SET) < 0)
+		return EIO;
+
+	rc = riff_write_uint32(rw, cksize);
+	if (rc != EOK) {
+		assert(rc == EIO);
+		return EIO;
+	}
+
+	if (fseek(rw->f, pos, SEEK_SET) < 0)
+		return EIO;
+
+	return EOK;
+}
+
+/** Write data into RIFF file.
+ *
+ * @param rw    RIFF writer
+ * @param data  Pointer to data
+ * @param bytes Number of bytes to write
+ *
+ * @return EOK on success, EIO on error.
+ */
+int riff_wchunk_write(riffw_t *rw, void *data, size_t bytes)
+{
+	size_t nw;
+
+	nw = fwrite(data, 1, bytes, rw->f);
+	if (nw != bytes)
+		return EIO;
+
+	return EOK;
+}
+
+/** Open RIFF file for reading.
+ *
+ * @param fname File name
+ * @param rrr   Place to store pointer to RIFF reader
+ *
+ * @return EOK on success, ENOMEM if out of memory, EIO if failed to open
+ *         file..
+ */
+int riff_ropen(const char *fname, riffr_t **rrr)
+{
+	riffr_t *rr;
+	int rc;
+
+	rr = calloc(1, sizeof(riffr_t));
+	if (rr == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rr->f = fopen(fname, "rb");
+	if (rr->f == NULL) {
+		rc = EIO;
+		goto error;
+	}
+
+	*rrr = rr;
+	return EOK;
+error:
+	if (rr != NULL && rr->f != NULL)
+		fclose(rr->f);
+	free(rr);
+	return rc;
+}
+
+/** Close RIFF for reading.
+ *
+ * @param rr RIFF reader
+ * @return EOK on success, EIO on error.
+ */
+int riff_rclose(riffr_t *rr)
+{
+	int rc;
+
+	rc = fclose(rr->f);
+	free(rr);
+	return rc == 0 ? EOK : EIO;
+}
+
+/** Read uint32_t from RIFF file.
+ *
+ * @param rr RIFF reader
+ * @param v  Place to store value
+ * @return EOK on success, EIO on error.
+ */
+int riff_read_uint32(riffr_t *rr, uint32_t *v)
+{
+	uint32_t vle;
+
+	if (fread(&vle, 1, sizeof(vle), rr->f) < sizeof(vle))
+		return EIO;
+
+	*v = uint32_t_le2host(vle);
+	return EOK;
+}
+
+/** Start reading RIFF chunk.
+ *
+ * @param rr     RIFF reader
+ * @param rchunk Pointer to chunk structure to fill in
+ *
+ * @return EOK on success, EIO on error.
+ */
+int riff_rchunk_start(riffr_t *rr, riff_rchunk_t *rchunk)
+{
+	int rc;
+	long pos;
+
+	pos = ftell(rr->f);
+	if (pos < 0) {
+		rc = EIO;
+		goto error;
+	}
+
+	rchunk->ckstart = pos;
+	rc = riff_read_uint32(rr, &rchunk->ckid);
+	if (rc != EOK)
+		goto error;
+	rc = riff_read_uint32(rr, &rchunk->cksize);
+	if (rc != EOK)
+		goto error;
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Return file offset where chunk ends
+ *
+ * @param rchunk RIFF chunk
+ * @return File offset just after last data byte of the chunk
+ */
+static long riff_rchunk_get_end(riff_rchunk_t *rchunk)
+{
+	return rchunk->ckstart + 8 + rchunk->cksize;
+}
+
+/** Return file offset of first (non-padding) byte after end of chunk.
+ *
+ * @param rchunk RIFF chunk
+ * @return File offset of first non-padding byte after end of chunk
+ */
+static long riff_rchunk_get_ndpos(riff_rchunk_t *rchunk)
+{
+	long ckend;
+
+	ckend = riff_rchunk_get_end(rchunk);
+	if ((ckend % 2) != 0)
+		return ckend + 1;
+	else
+		return ckend;
+}
+
+/** Finish reading RIFF chunk.
+ *
+ * Seek to the first byte after end of chunk.
+ *
+ * @param rr     RIFF reader
+ * @param rchunk Chunk structure
+ * @return EOK on success, EIO on error.
+ */
+int riff_rchunk_end(riffr_t *rr, riff_rchunk_t *rchunk)
+{
+	long ckend;
+
+	ckend = riff_rchunk_get_ndpos(rchunk);
+	if (fseek(rr->f, ckend, SEEK_SET) < 0)
+		return EIO;
+
+	return EOK;
+}
+
+/** Read data from RIFF chunk.
+ *
+ * Attempt to read @a bytes bytes from the chunk. If there is less data
+ * left until the end of the chunk, less will be read. The actual number
+ * of bytes read is returned in @a *nbytes (can even be 0).
+ *
+ * @param rr RIFF reader
+ * @param rchunk RIFF chunk
+ * @param buf Buffer to read to
+ * @param bytes Number of bytes to read
+ * @param nread Place to store number of bytes actually read
+ *
+ * @return EOK on success, ELIMIT if file position is not within @a rchunk,
+ *         EIO on I/O error.
+ */
+int riff_rchunk_read(riffr_t *rr, riff_rchunk_t *rchunk, void *buf,
+    size_t bytes, size_t *nread)
+{
+	long pos;
+	long ckend;
+	long toread;
+
+	pos = ftell(rr->f);
+	if (pos < 0)
+		return EIO;
+
+	ckend = riff_rchunk_get_end(rchunk);
+	if (pos < rchunk->ckstart || pos > ckend)
+		return ELIMIT;
+
+	toread = min(bytes, (size_t)(ckend - pos));
+	if (toread == 0) {
+		*nread = 0;
+		return EOK;
+	}
+
+	*nread = fread(buf, 1, toread, rr->f);
+	if (*nread == 0)
+		return EIO;
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/lib/riff/src/rwave.c
===================================================================
--- uspace/lib/riff/src/rwave.c	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
+++ uspace/lib/riff/src/rwave.c	(revision d53af3c8e49cf53bc3b0b68fb0fcadb78291e77f)
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2015 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libriff
+ * @{
+ */
+/**
+ * @file Waveform Audio File Format (WAVE).
+ */
+
+#include <assert.h>
+#include <byteorder.h>
+#include <errno.h>
+#include <macros.h>
+#include <mem.h>
+#include <riff/chunk.h>
+#include <riff/rwave.h>
+#include <stdlib.h>
+
+/** Encode format chunk data.
+ *
+ * @params params	WAVE parameters
+ * @params fmt		Pointer to fmt chunk data structure to fill in
+ */
+static void rwave_encode_fmt(rwave_params_t *params, rwave_fmt_t *fmt)
+{
+	int bytes_smp;
+
+	bytes_smp = (params->bits_smp + 7) / 8;
+
+	fmt->format_tag = host2uint16_t_le(WFMT_PCM);
+	fmt->channels = host2uint16_t_le(params->channels);
+	fmt->smp_sec = host2uint32_t_le(params->smp_freq);
+	fmt->avg_bytes_sec = host2uint32_t_le(bytes_smp * params->smp_freq *
+	    params->channels);
+	fmt->block_align = host2uint16_t_le(bytes_smp);
+	fmt->bits_smp = host2uint16_t_le(params->bits_smp);
+}
+
+/** Decode format chunk data.
+ *
+ * @param fmt	 Pointer to format chunk data
+ * @param params WAVE parameters to fill in
+ *
+ * @return EOK on success, EINVAL if format is not supported.
+ */
+static int rwave_decode_fmt(rwave_fmt_t *fmt, rwave_params_t *params)
+{
+	uint16_t fmt_tag;
+
+	fmt_tag = uint16_t_le2host(fmt->format_tag);
+	printf("fmt_tag=0x%x\n", fmt_tag);
+	if (fmt_tag != WFMT_PCM)
+		return EINVAL;
+
+	params->channels = uint16_t_le2host(fmt->channels);
+	params->smp_freq = uint32_t_le2host(fmt->smp_sec);
+	params->bits_smp = uint16_t_le2host(fmt->bits_smp);
+
+	return EOK;
+}
+
+/** Open WAVE file for writing.
+ *
+ * @param fname  File name
+ * @param params WAVE file parameters
+ * @param rww    Place to store pointer to WAVE writer
+ *
+ * @return EOK on success, EIO on I/O error, ENOMEM if out of memory.
+ */
+int rwave_wopen(const char *fname, rwave_params_t *params, rwavew_t **rww)
+{
+	riff_wchunk_t fmt;
+	rwave_fmt_t rwfmt;
+	int rc;
+	rwavew_t *ww;
+
+	rwave_encode_fmt(params, &rwfmt);
+
+	ww = calloc(1, sizeof(rwavew_t));
+	if (ww == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ww->bufsize = 4096;
+	ww->buf = calloc(1, ww->bufsize);
+	if (ww->buf == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/* Make a copy of parameters */
+	ww->params = *params;
+
+	rc = riff_wopen(fname, &ww->rw);
+	if (rc != EOK) {
+		assert(rc == EIO || rc == ENOMEM);
+		goto error;
+	}
+
+	rc = riff_wchunk_start(ww->rw, CKID_RIFF, &ww->wave);
+	if (rc != EOK)
+		goto error;
+
+	rc = riff_write_uint32(ww->rw, FORM_WAVE);
+	if (rc != EOK)
+		goto error;
+
+	rc = riff_wchunk_start(ww->rw, CKID_fmt, &fmt);
+	if (rc != EOK)
+		goto error;
+
+	rc = riff_wchunk_write(ww->rw, &rwfmt, sizeof(rwfmt));
+	if (rc != EOK)
+		goto error;
+
+	rc = riff_wchunk_end(ww->rw, &fmt);
+	if (rc != EOK)
+		goto error;
+
+	rc = riff_wchunk_start(ww->rw, CKID_data, &ww->data);
+	if (rc != EOK)
+		goto error;
+
+	*rww = ww;
+	return EOK;
+error:
+	if (ww != NULL)
+		free(ww->buf);
+	if (ww->rw != NULL)
+		riff_wclose(ww->rw);
+	free(ww);
+	return rc;
+}
+
+/** Write samples to WAVE file.
+ *
+ * @param ww    WAVE writer
+ * @param data  Pointer to data
+ * @param bytes Number of bytes to write
+ *
+ * @return EOK on success, EIO on I/O error, ENOTSUP if sample format is
+ *         not supported.
+ */
+int rwave_write_samples(rwavew_t *ww, void *data, size_t bytes)
+{
+	size_t i;
+	uint16_t *d16, *b16;
+	size_t now;
+	int rc;
+
+	/* Convert sample data to little endian */
+
+	while (bytes > 0) {
+		now = min(bytes, ww->bufsize);
+
+		switch (ww->params.bits_smp / 8) {
+		case 1:
+			memcpy(ww->buf, data, now);
+			break;
+		case 2:
+			b16 = (uint16_t *)ww->buf;
+			d16 = (uint16_t *)data;
+			for (i = 0; i < now / 2; i++) {
+				b16[i] = host2uint16_t_le(d16[i]);
+			}
+			break;
+		default:
+			return ENOTSUP;
+		}
+
+		rc = riff_wchunk_write(ww->rw, ww->buf, now);
+		if (rc != EOK) {
+			assert(rc == EIO);
+			return rc;
+		}
+
+		bytes -= now;
+		data += now;
+	}
+
+	return EOK;
+}
+
+/** Close WAVE file for writing.
+ *
+ * @param ww WAVE writer
+ * @return EOK on success, EIO on I/O error - in which case @a ww is destroyed
+ *         anyway.
+ */
+int rwave_wclose(rwavew_t *ww)
+{
+	int rc;
+
+	rc = riff_wchunk_end(ww->rw, &ww->wave);
+	if (rc == EOK)
+		rc = riff_wchunk_end(ww->rw, &ww->data);
+
+	rc = riff_wclose(ww->rw);
+
+	ww->rw = NULL;
+	free(ww->buf);
+	free(ww);
+
+	return rc;
+}
+
+/** Open WAVE file for reading.
+ *
+ * @param fname  File name
+ * @param params WAVE file parameters
+ * @param rwaver Place to store pointer to new wave reader
+ *
+ * @return EOK on success, EIO on I/O error, ENOMEM if out of memory
+ */
+int rwave_ropen(const char *fname, rwave_params_t *params, rwaver_t **rwr)
+{
+	rwaver_t *wr = NULL;
+	uint32_t form_id;
+	riff_rchunk_t fmt;
+	rwave_fmt_t wfmt;
+	size_t nread;
+	int rc;
+
+	wr = calloc(1, sizeof(rwaver_t));
+	if (wr == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rc = riff_ropen(fname, &wr->rr);
+	if (rc != EOK) {
+		assert(rc == EIO || rc == ENOMEM);
+		goto error;
+	}
+
+	rc = riff_rchunk_start(wr->rr, &wr->wave);
+	if (rc != EOK) {
+		assert(rc == EIO);
+		goto error;
+	}
+
+	rc = riff_read_uint32(wr->rr, &form_id);
+	if (rc != EOK) {
+		assert(rc == EIO);
+		goto error;
+	}
+
+	if (form_id != FORM_WAVE) {
+		printf("wrong form ID\n");
+		rc = EIO;
+		goto error;
+	}
+
+	rc = riff_rchunk_start(wr->rr, &fmt);
+	if (rc != EOK) {
+		assert(rc == EIO);
+		goto error;
+	}
+
+	if (fmt.ckid != CKID_fmt) {
+		printf("not fmt chunk\n");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rc = riff_rchunk_read(wr->rr, &fmt, &wfmt, sizeof(rwave_fmt_t), &nread);
+	if (rc != EOK) {
+		printf("error reading fmt chunk\n");
+		assert(rc == EIO || rc == ELIMIT);
+		rc = EIO;
+		goto error;
+	}
+
+	if (nread < sizeof(rwave_fmt_t)) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = riff_rchunk_end(wr->rr, &fmt);
+	if (rc != EOK) {
+		assert(rc == EIO);
+		goto error;
+	}
+
+	rc = rwave_decode_fmt(&wfmt, params);
+	if (rc != EOK) {
+		printf("decode fmt fail\n");
+		assert(rc == EINVAL);
+		rc = EIO;
+		goto error;
+	}
+
+	rc = riff_rchunk_start(wr->rr, &wr->data);
+	if (rc != EOK) {
+		assert(rc == EIO);
+		goto error;
+	}
+
+	if (wr->data.ckid != CKID_data) {
+		printf("not data ckid\n");
+		rc = EIO;
+		goto error;
+	}
+
+	*rwr = wr;
+	return EOK;
+error:
+	if (wr != NULL && wr->rr != NULL)
+		riff_rclose(wr->rr);
+	free(wr);
+	return rc;
+}
+
+/** Read samples from WAVE file.
+ *
+ * @param wr    WAVE reader
+ * @param buf   Buffer for storing the data
+ * @param bytes Number of bytes to read
+ * @param nread Place to store number of bytes actually read (>= 0).
+ *
+ * @return EOK if zero or more bytes successfully read and @a *nread is set,
+ *         EIO on I/O error.
+ */
+int rwave_read_samples(rwaver_t *wr, void *buf, size_t bytes, size_t *nread)
+{
+	int rc;
+
+	rc = riff_rchunk_read(wr->rr, &wr->data, buf, bytes, nread);
+	if (rc != EOK) {
+		assert(rc == EIO || rc == ELIMIT);
+		return EIO;
+	}
+
+	return EOK;
+}
+
+/** Close WAVE file for reading.
+ *
+ * @param wr WAVE reader
+ *
+ * @return EOK on success, EIO on I/O error in which case @a wr is destroyed
+ *         anyway.
+ */
+int rwave_rclose(rwaver_t *wr)
+{
+	int rc;
+
+	rc = riff_rchunk_end(wr->rr, &wr->wave);
+	if (rc != EOK) {
+		assert(rc == EIO);
+		goto error;
+	}
+
+	riff_rclose(wr->rr);
+	free(wr);
+	return EOK;
+error:
+	return rc;
+}
+
+/** @}
+ */
