Index: boot/Makefile.common
===================================================================
--- boot/Makefile.common	(revision ccfe9c3c9dac64d390a6797468978df3f1231cab)
+++ boot/Makefile.common	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -186,4 +186,5 @@
 	$(USPACE_PATH)/app/loc/loc \
 	$(USPACE_PATH)/app/mixerctl/mixerctl \
+	$(USPACE_PATH)/app/modplay/modplay \
 	$(USPACE_PATH)/app/logset/logset \
 	$(USPACE_PATH)/app/mkfat/mkfat \
Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision ccfe9c3c9dac64d390a6797468978df3f1231cab)
+++ uspace/Makefile	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -58,4 +58,5 @@
 	app/mkexfat \
 	app/mkmfs \
+	app/modplay \
 	app/nterm \
 	app/redir \
@@ -224,4 +225,5 @@
 	lib/nic \
 	lib/ext4 \
+	lib/trackmod \
 	lib/uri \
 	lib/usb \
Index: uspace/Makefile.common
===================================================================
--- uspace/Makefile.common	(revision ccfe9c3c9dac64d390a6797468978df3f1231cab)
+++ uspace/Makefile.common	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -144,4 +144,5 @@
 
 LIBSCSI_PREFIX = $(LIB_PREFIX)/scsi
+LIBTRACKMOD_PREFIX = $(LIB_PREFIX)/trackmod
 
 LIBBITHENGE_PREFIX = $(LIB_PREFIX)/bithenge
Index: uspace/app/modplay/Makefile
===================================================================
--- uspace/app/modplay/Makefile	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
+++ uspace/app/modplay/Makefile	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -0,0 +1,44 @@
+#
+# Copyright (c) 2014 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.
+#
+
+USPACE_PREFIX = ../..
+LIBS = \
+	$(LIBTRACKMOD_PREFIX)/libtrackmod.a \
+	$(LIBHOUND_PREFIX)/libhound.a \
+	$(LIBPCM_PREFIX)/libpcm.a
+
+EXTRA_CFLAGS = \
+	-I$(LIBTRACKMOD_PREFIX) \
+	-I$(LIBHOUND_PREFIX)/include \
+	-I$(LIBPCM_PREFIX)/include
+BINARY = modplay
+
+SOURCES = \
+	modplay.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/app/modplay/modplay.c
===================================================================
--- uspace/app/modplay/modplay.c	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
+++ uspace/app/modplay/modplay.c	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2014 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 edit
+ * @brief Amiga music module player.
+ * @{
+ */
+/**
+ * @file
+ */
+
+#include <errno.h>
+#include <hound/client.h>
+#include <io/console.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <protracker.h>
+#include <trackmod.h>
+
+static bool quit = false;
+
+static void modplay_key_press(kbd_event_t *ev)
+{
+	if ((ev->mods & KM_ALT) == 0 &&
+	    (ev->mods & KM_SHIFT) == 0 &&
+	    (ev->mods & KM_CTRL) != 0) {
+		if (ev->key == KC_Q)
+			quit = true;
+	}
+}
+
+static void modplay_event(cons_event_t *event)
+{
+	switch (event->type) {
+	case CEV_KEY:
+		if (event->ev.key.type == KEY_PRESS) {
+			modplay_key_press(&event->ev.key);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	trackmod_module_t *mod;
+	trackmod_modplay_t *modplay;
+	hound_context_t *hound;
+	console_ctrl_t *con;
+	cons_event_t event;
+	suseconds_t timeout;
+	pcm_format_t format;
+	void *buffer;
+	size_t buffer_size;
+	int rc;
+
+	if (argc != 2) {
+		printf("syntax: modplay <filename.mod>\n");
+		return 1;
+	}
+
+	con = console_init(stdin, stdout);
+
+	rc = trackmod_protracker_load(argv[1], &mod);
+	if (rc != EOK) {
+		printf("Error loading %s.\n", argv[1]);
+		return 1;
+	}
+
+
+	format.channels = 1;
+	format.sampling_rate = 44100;
+#ifdef __LE__
+	format.sample_format = PCM_SAMPLE_SINT16_LE;
+#else
+	format.sample_format = PCM_SAMPLE_SINT16_BE;
+#endif
+	buffer_size = format.sampling_rate;
+
+	buffer = malloc(buffer_size);
+	if (buffer == NULL) {
+		printf("Error allocating audio buffer.\n");
+		return 1;
+	}
+
+	hound = hound_context_create_playback(argv[1], format, buffer_size);
+	if (hound == NULL) {
+		printf("Error creating playback context.\n");
+		return 1;
+	}
+
+	rc = hound_context_connect_target(hound, HOUND_DEFAULT_TARGET);
+	if (rc != EOK) {
+		printf("Error connecting default audio target (%d).\n", rc);
+		return 1;
+	}
+
+	rc = trackmod_modplay_create(mod, format.sampling_rate, &modplay);
+	if (rc != EOK) {
+		printf("Error setting up playback.\n");
+		return 1;
+	}
+
+	printf("Playing '%s'. Press Ctrl+Q to quit.\n", argv[1]);
+
+	while (true) {
+		timeout = 0;
+		if (console_get_event_timeout(con, &event, &timeout))
+			modplay_event(&event);
+		if (quit)
+			break;
+
+		trackmod_modplay_get_samples(modplay, buffer, buffer_size);
+
+		rc = hound_write_main_stream(hound, buffer, buffer_size);
+		if (rc != EOK) {
+			printf("Error writing audio stream.\n");
+			break;
+		}
+	}
+
+	hound_context_destroy(hound);
+	trackmod_modplay_destroy(modplay);
+	trackmod_module_destroy(mod);
+
+	return 0;
+}
+
+
+/** @}
+ */
Index: uspace/lib/trackmod/Makefile
===================================================================
--- uspace/lib/trackmod/Makefile	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
+++ uspace/lib/trackmod/Makefile	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2014 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.
+#
+
+USPACE_PREFIX = ../..
+LIBRARY = libtrackmod
+
+SOURCES = \
+	protracker.c \
+	trackmod.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/lib/trackmod/protracker.c
===================================================================
--- uspace/lib/trackmod/protracker.c	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
+++ uspace/lib/trackmod/protracker.c	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2014 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 trackmod
+ * @{
+ */
+/**
+ * @file Protracker module (.mod).
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <mem.h>
+
+#include "byteorder.h"
+#include "protracker.h"
+#include "trackmod.h"
+#include "types/protracker.h"
+
+/** Sample tag decoding table entry*/
+typedef struct {
+	/** Tag */
+	const char *tag;
+	/** Number of channels */
+	unsigned channels;
+} smptag_desc_t;
+
+/** Sample tag decoding table */
+static smptag_desc_t smp_tags[] = {
+	{ .tag = "M.K.", .channels = 4 },
+	{ .tag = "M!K!", .channels = 4 },
+	{ .tag = "2CHN", .channels = 2 },
+	{ .tag = "6CHN", .channels = 6 },
+	{ .tag = "8CHN", .channels = 8 },
+	{ .tag = "10CH", .channels = 10 },
+	{ .tag = "12CH", .channels = 12 },
+	{ .tag = "14CH", .channels = 14 },
+	{ .tag = "16CH", .channels = 16 },
+	{ .tag = "18CH", .channels = 18 },
+	{ .tag = "20CH", .channels = 20 },
+	{ .tag = "22CH", .channels = 22 },
+	{ .tag = "24CH", .channels = 24 },
+	{ .tag = "26CH", .channels = 26 },
+	{ .tag = "28CH", .channels = 28 },
+	{ .tag = "30CH", .channels = 30 },
+	{ .tag = "32CH", .channels = 32 },
+};
+
+/** Decode sample tag.
+ *
+ * @param tag      Tag
+ * @param channels Place to store number or channels.
+ * @return         EOK on success, EINVAL if tag is not recognized.
+ */
+static int smp_tag_decode(uint8_t *tag, size_t *channels)
+{
+	size_t nentries = sizeof(smp_tags) / sizeof(smptag_desc_t);
+	size_t i;
+
+	for (i = 0; i < nentries; i++) {
+		if (memcmp(tag, smp_tags[i].tag, 4) == 0) {
+			*channels = smp_tags[i].channels;
+			return EOK;
+		}
+	}
+
+	return EINVAL;
+}
+
+/** Get number of patterns stored in file.
+ *
+ * @param olist Order list
+ * @return      Number of patterns in file
+ */
+static size_t order_list_get_npatterns(protracker_order_list_t *olist)
+{
+	size_t i;
+	size_t max_pat;
+
+	max_pat = 0;
+	for (i = 0; i < protracker_olist_len; i++) {
+		if (olist->order_list[i] > max_pat)
+			max_pat = olist->order_list[i];
+	}
+
+	return 1 + max_pat;
+}
+
+/** Load protracker module.
+ *
+ * @param fname   File name
+ * @param rmodule Place to store pointer to newly loaded module.
+ * @return        EOK on success, ENONEM if out of memory, EIO on I/O error
+ *                or if any error is found in the format of the file.
+ */
+int trackmod_protracker_load(char *fname, trackmod_module_t **rmodule)
+{
+	FILE *f = NULL;
+	trackmod_module_t *module = NULL;
+	protracker_31smp_t mod31;
+	protracker_15smp_t mod15;
+	protracker_order_list_t *order_list;
+	protracker_smp_t *sample;
+	size_t nread;
+	size_t samples;
+	size_t channels;
+	size_t patterns;
+	size_t cells;
+	size_t i, j;
+	int rc;
+
+	f = fopen(fname, "rb");
+	if (f == NULL) {
+		printf("Error opening file.\n");
+		rc = EIO;
+		goto error;
+	}
+
+	nread = fread(&mod31, 1, sizeof(protracker_31smp_t), f);
+	if (nread < sizeof(protracker_15smp_t)) {
+		printf("File too small.\n");
+		rc = EIO;
+		goto error;
+	}
+
+	if (nread == sizeof(protracker_31smp_t)) {
+		/* Could be 31-sample variant */
+		rc = smp_tag_decode(mod31.sample_tag, &channels);
+		if (rc != EOK) {
+			samples = 15;
+			channels = 4;
+		} else {
+			samples = 31;
+		}
+	} else {
+		samples = 15;
+		channels = 4;
+	}
+
+	if (samples == 15) {
+		memcpy(&mod15, &mod31, sizeof(protracker_15smp_t));
+
+		rc = fseek(f, sizeof(protracker_15smp_t), SEEK_SET);
+		if (rc != 0) {
+			printf("Error seeking.\n");
+			rc = EIO;
+			goto error;
+		}
+
+		order_list = &mod15.order_list;
+		sample = mod15.sample;
+	} else {
+		order_list = &mod31.order_list;
+		sample = mod31.sample;
+	}
+
+	patterns = order_list_get_npatterns(order_list);
+
+	module = trackmod_module_new();
+	if (module == NULL) {
+		printf("Out of memory.\n");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	module->channels = channels;
+
+	module->samples = samples;
+	module->sample = calloc(sizeof(trackmod_sample_t), samples);
+	if (module->sample == NULL) {
+		printf("Out of memory.\n");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	module->patterns = patterns;
+	module->pattern = calloc(sizeof(trackmod_pattern_t), patterns);
+	if (module->pattern == NULL) {
+		printf("Out of memory.\n");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/* Order list */
+	module->ord_list_len = order_list->order_list_len;
+	module->ord_list = calloc(sizeof(size_t), module->ord_list_len);
+	if (module->ord_list == NULL) {
+		printf("Out of memory.\n");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	for (i = 0; i < order_list->order_list_len; i++) {
+		module->ord_list[i] = order_list->order_list[i];
+	}
+
+	/* Load patterns */
+
+	cells = channels * protracker_pattern_rows;
+
+	for (i = 0; i < patterns; i++) {
+		module->pattern[i].rows = protracker_pattern_rows;
+		module->pattern[i].channels = channels;
+		module->pattern[i].data = calloc(sizeof(uint32_t), cells);
+
+		nread = fread(module->pattern[i].data,
+		    sizeof(uint32_t), cells, f);
+		if (nread != cells) {
+			printf("Error reading pattern.\n");
+			rc = EIO;
+			goto error;
+		}
+
+		/* Convert byte order */
+		for (j = 0; j < cells; j++) {
+			module->pattern[i].data[j] = uint32_t_be2host(
+			    module->pattern[i].data[j]);
+		}
+	}
+
+	/* Load samples */
+	for (i = 0; i < samples; i++) {
+		module->sample[i].length =
+		    uint16_t_be2host(sample[i].length) * 2;
+		module->sample[i].data = calloc(1, module->sample[i].length);
+		if (module->sample[i].data == NULL) {
+			printf("Error allocating sample.\n");
+			rc = ENOMEM;
+			goto error;
+		}
+
+		nread = fread(module->sample[i].data, 1, module->sample[i].length,
+			f);
+		if (nread != module->sample[i].length) {
+			printf("Error reading sample.\n");
+			rc = EIO;
+			goto error;
+		}
+
+		module->sample[i].def_vol = sample[i].def_vol;
+		module->sample[i].loop_start =
+		    uint16_t_be2host(sample[i].loop_start) * 2;
+		module->sample[i].loop_len =
+		    uint16_t_be2host(sample[i].loop_len) * 2;
+		if (module->sample[i].loop_len <= 2)
+			module->sample[i].loop_len = 0;
+	}
+
+	(void) fclose(f);
+
+	*rmodule = module;
+	return EOK;
+error:
+	if (module != NULL)
+		trackmod_module_destroy(module);
+	if (f != NULL)
+		(void) fclose(f);
+	return rc;
+}
+
+/** @}
+ */
Index: uspace/lib/trackmod/protracker.h
===================================================================
--- uspace/lib/trackmod/protracker.h	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
+++ uspace/lib/trackmod/protracker.h	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2014 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 trackmod
+ * @{
+ */
+/**
+ * @file Protracker module (.mod).
+ */
+
+#ifndef PROTRACKER_H
+#define PROTRACKER_H
+
+#include "types/trackmod.h"
+
+extern int trackmod_protracker_load(char *, trackmod_module_t **);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/trackmod/trackmod.c
===================================================================
--- uspace/lib/trackmod/trackmod.c	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
+++ uspace/lib/trackmod/trackmod.c	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -0,0 +1,541 @@
+/*
+ * Copyright (c) 2014 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 trackmod
+ * @{
+ */
+/**
+ * @file Tracker module handling library.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "macros.h"
+#include "trackmod.h"
+
+/** Tunables */
+enum {
+	amp_factor = 16
+};
+
+/** Standard definitions set in stone */
+enum {
+	/** Base sample clock */
+	base_clock = 8363 * 428,
+	/** Maximum sample volume */
+	vol_max = 63,
+	/** Default TPR */
+	def_tpr = 6,
+	/** Default BPM */
+	def_bpm = 125
+};
+
+static size_t trackmod_get_next_ord_idx(trackmod_modplay_t *);
+
+/** Destroy sample.
+ *
+ * @param sample Sample
+ */
+static void trackmod_sample_destroy(trackmod_sample_t *sample)
+{
+	free(sample->data);
+}
+
+/** Destroy pattern.
+ *
+ * @param pattern Pattern
+ */
+static void trackmod_pattern_destroy(trackmod_pattern_t *pattern)
+{
+	free(pattern->data);
+}
+
+/** Create new empty module structure.
+ *
+ * @return New module
+ */
+trackmod_module_t *trackmod_module_new(void)
+{
+	return calloc(1, sizeof(trackmod_module_t));
+}
+
+/** Destroy module.
+ *
+ * @param module Module
+ */
+void trackmod_module_destroy(trackmod_module_t *module)
+{
+	size_t i;
+
+	/* Destroy samples */
+	if (module->sample != NULL) {
+		for (i = 0; i < module->samples; i++)
+			trackmod_sample_destroy(&module->sample[i]);
+		free(module->sample);
+	}
+
+	/* Destroy patterns */
+	if (module->pattern != NULL) {
+		for (i = 0; i < module->patterns; i++)
+			trackmod_pattern_destroy(&module->pattern[i]);
+		free(module->pattern);
+	}
+
+	free(module->ord_list);
+	free(module);
+}
+
+/** Return current pattern.
+ *
+ * @param modplay Module playback
+ * @return        Pattern
+ */
+static trackmod_pattern_t *trackmod_cur_pattern(trackmod_modplay_t *modplay)
+{
+	unsigned pat_idx;
+
+	pat_idx = modplay->module->ord_list[modplay->ord_idx];
+	return &modplay->module->pattern[pat_idx];
+}
+
+/** Decode pattern cell.
+ *
+ * @param pattern Pattern
+ * @param row     Row number
+ * @param channel Channel number
+ * @param cell    Place to store decoded cell
+ */
+static void trackmod_pattern_get_cell(trackmod_pattern_t *pattern,
+    size_t row, size_t channel, trackmod_cell_t *cell)
+{
+	uint32_t code;
+
+	code = pattern->data[row * pattern->channels + channel];
+	cell->period = (code >> (4 * 4)) & 0xfff;
+	cell->sample = (((code >> (7 * 4)) & 0xf) << 4) |
+	    ((code >> (3 * 4)) & 0xf);
+	cell->effect = code & 0xfff;
+}
+
+/** Process note (period, sample index)
+ *
+ * @param modplay Module playback
+ * @param i       Channel number
+ * @param cell    Cell
+ */
+static void trackmod_process_note(trackmod_modplay_t *modplay, size_t i,
+    trackmod_cell_t *cell)
+{
+	trackmod_chan_t *chan = &modplay->chan[i];
+	size_t smpidx;
+
+	smpidx = (cell->sample - 1) % modplay->module->samples;
+	chan->sample = &modplay->module->sample[smpidx];
+	chan->smp_pos = 0;
+	chan->lsmp = 0;
+	chan->period = cell->period;
+	chan->volume = modplay->chan[i].sample->def_vol;
+}
+
+/** Process Set volume effect.
+ *
+ * @param modplay Module playback
+ * @param chan    Channel number
+ * @param param   Effect parameter
+ */
+static void trackmod_effect_set_volume(trackmod_modplay_t *modplay, size_t chan,
+    uint8_t param)
+{
+	modplay->chan[chan].volume = param & vol_max;
+}
+
+/** Process Pattern break effect.
+ *
+ * @param modplay Module playback
+ * @param chan    Channel number
+ * @param param   Effect parameter
+ */
+static void trackmod_effect_pattern_break(trackmod_modplay_t *modplay,
+    size_t chan, uint8_t param)
+{
+	size_t next_idx;
+	trackmod_pattern_t *next_pat;
+
+	next_idx = trackmod_get_next_ord_idx(modplay);
+	next_pat = &modplay->module->pattern[next_idx]; 
+
+	modplay->pat_break = true;
+	modplay->pat_break_row = param % next_pat->rows;
+}
+
+/** Process Set speed effect.
+ *
+ * @param modplay Module playback
+ * @param chan    Channel number
+ * @param param   Effect parameter
+ */
+static void trackmod_effect_set_speed(trackmod_modplay_t *modplay, size_t chan,
+    uint8_t param)
+{
+	if (param > 0 && param < 32)
+		modplay->tpr = param;
+	else if (param > 0)
+		modplay->bpm = param;
+}
+
+/** Process effect.
+ *
+ * @param modplay Module playback
+ * @param chan    Channel number
+ * @param cell    Cell
+ */
+static void trackmod_process_effect(trackmod_modplay_t *modplay, size_t chan,
+    trackmod_cell_t *cell)
+{
+	uint8_t param8;
+
+	param8 = cell->effect & 0xff;
+
+	switch (cell->effect & 0xf00) {
+	case 0xc00:
+		trackmod_effect_set_volume(modplay, chan, param8);
+		break;
+	case 0xd00:
+		trackmod_effect_pattern_break(modplay, chan, param8);
+		break;
+	case 0xf00:
+		trackmod_effect_set_speed(modplay, chan, param8);
+		break;
+	default:
+		break;
+	}
+}
+
+/** Process pattern cell.
+ *
+ * @param modplay Module playback
+ * @param chan    Channel number
+ * @param cell    Cell
+ */
+static void trackmod_process_cell(trackmod_modplay_t *modplay, size_t chan,
+    trackmod_cell_t *cell)
+{
+	if (cell->period != 0 && cell->sample != 0)
+		trackmod_process_note(modplay, chan, cell);
+
+	trackmod_process_effect(modplay, chan, cell);
+}
+
+/** Process pattern row.
+ *
+ * @param modplay Module playback
+ */
+static void trackmod_process_row(trackmod_modplay_t *modplay)
+{
+	trackmod_pattern_t *pattern;
+	trackmod_cell_t cell;
+	size_t i;
+
+	pattern = trackmod_cur_pattern(modplay);
+
+	for (i = 0; i < modplay->module->channels; i++) {
+		trackmod_pattern_get_cell(pattern, modplay->row, i, &cell);
+		if (modplay->debug)
+			printf("%4d %02x %03x |", cell.period, cell.sample, cell.effect);
+		trackmod_process_cell(modplay, i, &cell);
+	}
+
+	if (modplay->debug)
+		printf("\n");
+}
+
+/** Get next order list index.
+ *
+ * @param modplay Module playback
+ * @return        Next order list index
+ */
+static size_t trackmod_get_next_ord_idx(trackmod_modplay_t *modplay)
+{
+	size_t ord_idx;
+
+	ord_idx = modplay->ord_idx + 1;
+	if (ord_idx >= modplay->module->ord_list_len)
+		ord_idx = 0; /* XXX */
+
+	return ord_idx;
+}
+
+/** Advance to next pattern.
+ *
+ * @param modplay Module playback
+ */
+static void trackmod_next_pattern(trackmod_modplay_t *modplay)
+{
+	if (modplay->debug)
+		printf("Next pattern\n");
+
+	modplay->row = 0;
+	modplay->ord_idx = trackmod_get_next_ord_idx(modplay);
+
+	/* If we are doing a pattern break */
+	if (modplay->pat_break) {
+		modplay->row = modplay->pat_break_row;
+		modplay->pat_break = false;
+	}
+}
+
+/** Advance to next row.
+ *
+ * @param modplay Module playback
+ */
+static void trackmod_next_row(trackmod_modplay_t *modplay)
+{
+	trackmod_pattern_t *pattern;
+
+	pattern = trackmod_cur_pattern(modplay);
+
+	modplay->tick = 0;
+	++modplay->row;
+	if (modplay->row >= pattern->rows || modplay->pat_break)
+		trackmod_next_pattern(modplay);
+
+	trackmod_process_row(modplay);
+}
+
+/** Advance to next tick.
+ *
+ * @param modplay Module playback
+ */
+static void trackmod_next_tick(trackmod_modplay_t *modplay)
+{
+	modplay->smp = 0;
+	++modplay->tick;
+	if (modplay->tick >= modplay->tpr)
+		trackmod_next_row(modplay);
+}
+
+/** Create module playback object.
+ *
+ * @param module   Module
+ * @param smp_freq Sampling frequency
+ * @param rmodplay Place to store pointer to module playback object
+ */
+int trackmod_modplay_create(trackmod_module_t *module,
+    unsigned smp_freq, trackmod_modplay_t **rmodplay)
+{
+	trackmod_modplay_t *modplay = NULL;
+
+	modplay = calloc(1, sizeof(trackmod_modplay_t));
+	if (modplay == NULL)
+		goto error;
+
+	modplay->module = module;
+	modplay->smp_freq = smp_freq;
+	modplay->frame_size = sizeof(int16_t);
+	modplay->ord_idx = 0;
+	modplay->row = 0;
+	modplay->tick = 0;
+	modplay->smp = 0;
+
+	modplay->tpr = def_tpr;
+	modplay->bpm = def_bpm;
+
+	modplay->chan = calloc(module->channels,
+	    sizeof(trackmod_chan_t));
+	if (modplay->chan == NULL)
+		goto error;
+
+	trackmod_process_row(modplay);
+
+	*rmodplay = modplay;
+	return EOK;
+error:
+	if (modplay != NULL)
+		free(modplay->chan);
+	free(modplay);
+	return ENOMEM;
+}
+
+/** Destroy module playback object.
+ *
+ * @param modplay Module playback
+ */
+void trackmod_modplay_destroy(trackmod_modplay_t *modplay)
+{
+	free(modplay->chan);
+	free(modplay);
+}
+
+/** Get number of samples per tick.
+ *
+ * @param modplay Module playback
+ * @return        Number of samples per tick
+ */
+static size_t samples_per_tick(trackmod_modplay_t *modplay)
+{
+	return modplay->smp_freq * 10 / 4 / modplay->bpm;
+}
+
+/** Get number of samples remaining in current tick.
+ *
+ * @param modplay Module playback
+ * @return        Number of remaining samples in tick
+ */
+static size_t samples_remain_tick(trackmod_modplay_t *modplay)
+{
+	/* XXX Integer samples per tick is a simplification */
+	return samples_per_tick(modplay) - modplay->smp;
+}
+
+/** Advance sample position to next frame.
+ *
+ * @param chan Channel playback
+ */
+static void chan_smp_next_frame(trackmod_chan_t *chan)
+{
+	chan->lsmp = chan->sample->data[chan->smp_pos];
+	++chan->smp_pos;
+
+	if (chan->sample->loop_len == 0) {
+		/* No looping */
+		if (chan->smp_pos >= chan->sample->length) {
+			chan->sample = NULL;
+			chan->smp_pos = 0;
+		}
+	} else {
+		/** Looping */
+		if (chan->smp_pos >= chan->sample->loop_start +
+		    chan->sample->loop_len) {
+			chan->smp_pos = chan->sample->loop_start;
+		}
+	}
+}
+
+/** Render next sample on channel.
+ *
+ * @param modplay Module playback
+ * @param cidx    Channel number
+ * @return        Sample value
+ */
+static int trackmod_chan_next_sample(trackmod_modplay_t *modplay,
+    size_t cidx)
+{
+	int sl, sn;
+	int period, clk;
+	int s;
+
+	trackmod_chan_t *chan = &modplay->chan[cidx];
+
+	if (chan->sample == NULL)
+		return 0;
+
+	/*
+	 * Linear interpolation. Note this is slightly simplified:
+	 * We ignore the half-sample offset and the boundary condition
+	 * at the end of the sample (we should extend with zero).
+	 */
+	sl = (int)chan->lsmp * amp_factor * chan->volume / vol_max;
+	sn = (int)chan->sample->data[chan->smp_pos] * amp_factor *
+	    chan->volume / vol_max;
+
+	period = (int)chan->period;
+	clk = (int)chan->smp_clk;
+
+	s = (sl * (period - clk) + sn * clk) / period;
+
+	chan->smp_clk += base_clock / modplay->smp_freq;
+	while (chan->sample != NULL && chan->smp_clk >= chan->period) {
+		chan->smp_clk -= chan->period;
+		chan_smp_next_frame(chan);
+	}
+
+	return s;
+}
+
+/** Render a segment of samples contained entirely within a tick.
+ *
+ * @param modplay Module playback
+ * @param buffer  Buffer for storing audio data
+ * @param bufsize Size of @a buffer in bytes
+ */
+static void get_samples_within_tick(trackmod_modplay_t *modplay,
+    void *buffer, size_t bufsize)
+{
+	size_t nsamples;
+	size_t smpidx;
+	size_t chan;
+	int s;
+
+	nsamples = bufsize / modplay->frame_size;
+
+	for (smpidx = 0; smpidx < nsamples; smpidx++) {
+		s = 0;
+		for (chan = 0; chan < modplay->module->channels; chan++) {
+			s += trackmod_chan_next_sample(modplay, chan);
+		}
+
+		((int16_t *)buffer)[smpidx] = s;
+	}
+
+	modplay->smp += nsamples;
+}
+
+/** Render a segment of samples.
+ *
+ * @param modplay Module playback
+ * @param buffer  Buffer for storing audio data
+ * @param bufsize Size of @a buffer in bytes
+ */
+void trackmod_modplay_get_samples(trackmod_modplay_t *modplay,
+    void *buffer, size_t bufsize)
+{
+	size_t nsamples;
+	size_t rsmp;
+	size_t now;
+
+	nsamples = bufsize / modplay->frame_size;
+	while (nsamples > 0) {
+		rsmp = samples_remain_tick(modplay);
+		if (rsmp == 0)
+			trackmod_next_tick(modplay);
+
+		rsmp = samples_remain_tick(modplay);
+		now = min(rsmp, nsamples);
+
+		get_samples_within_tick(modplay, buffer,
+		    now * modplay->frame_size);
+		nsamples -= now;
+		buffer += now * modplay->frame_size;
+	}
+}
+
+/** @}
+ */
Index: uspace/lib/trackmod/trackmod.h
===================================================================
--- uspace/lib/trackmod/trackmod.h	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
+++ uspace/lib/trackmod/trackmod.h	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2014 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 trackmod
+ * @{
+ */
+/**
+ * @file Tracker module handling library.
+ */
+
+#ifndef TRACKMOD_H
+#define TRACKMOD_H
+
+#include "types/trackmod.h"
+
+extern trackmod_module_t *trackmod_module_new(void);
+extern void trackmod_module_destroy(trackmod_module_t *);
+extern int trackmod_modplay_create(trackmod_module_t *, unsigned,
+    trackmod_modplay_t **);
+extern void trackmod_modplay_destroy(trackmod_modplay_t *);
+extern void trackmod_modplay_get_samples(trackmod_modplay_t *, void *, size_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/trackmod/types/protracker.h
===================================================================
--- uspace/lib/trackmod/types/protracker.h	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
+++ uspace/lib/trackmod/types/protracker.h	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2014 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 trackmod
+ * @{
+ */
+/**
+ * @file Protracker module (.mod) types.
+ */
+
+#ifndef TYPES_PROTRACKER_H
+#define TYPES_PROTRACKER_H
+
+#include <stdint.h>
+
+enum {
+	/** Module name size */
+	protracker_mod_name_size = 20,
+	/** Sample name size */
+	protracker_smp_name_size = 22,
+	/** Order list max length */
+	protracker_olist_len = 128,
+	/** Number of rows in a pattern */
+	protracker_pattern_rows = 64
+};
+
+/** Protracker initial header. */
+typedef struct {
+	/** Module name */
+	uint8_t name[protracker_mod_name_size];
+} protracker_hdr_t;
+
+/** Protracker sample header. */
+typedef struct {
+	/** Sample name, padded with zeros */
+	uint8_t name[protracker_smp_name_size];
+	/** Sample length in words */
+	uint16_t length;
+	/** Finetune value */
+	uint8_t finetune;
+	/** Default volume */
+	uint8_t def_vol;
+	/** Loop start in words */
+	uint16_t loop_start;
+	/** Loop length in words */
+	uint16_t loop_len;
+} protracker_smp_t;
+
+/** Protracker order list. */
+typedef struct {
+	/** Number of used entries */
+	uint8_t order_list_len;
+	uint8_t mark;
+	/** Order list */
+	uint8_t order_list[protracker_olist_len];
+} protracker_order_list_t;
+
+/** Protracker 15-sample variant headers. */
+typedef struct {
+	/** Initial header */
+	protracker_hdr_t hdr;
+	/** Sample headers. */
+	protracker_smp_t sample[15];
+	/** Order list. */
+	protracker_order_list_t order_list;
+} protracker_15smp_t;
+
+/** Protracker 31-sample variant headers. */
+typedef struct {
+	/** Initial header */
+	protracker_hdr_t hdr;
+	/** Sample headers. */
+	protracker_smp_t sample[31];
+	/** Order list. */
+	protracker_order_list_t order_list;
+	/** Sample tag. */
+	uint8_t sample_tag[4];
+} protracker_31smp_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/trackmod/types/trackmod.h
===================================================================
--- uspace/lib/trackmod/types/trackmod.h	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
+++ uspace/lib/trackmod/types/trackmod.h	(revision 7e69e0e9418526683ac074433809479bcfed9ea9)
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2014 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 trackmod
+ * @{
+ */
+/**
+ * @file Tracker module handling library types.
+ */
+
+#ifndef TYPES_TRACKMOD_H
+#define TYPES_TRACKMOD_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/** Sample */
+typedef struct {
+	/** Length in frames */
+	size_t length;
+	/** Sample data */
+	int8_t *data;
+	/** Loop start position in frames */
+	size_t loop_start;
+	/** Loop length in frames or 0 - no looping */
+	size_t loop_len;
+	/** Default volume (0..63) */
+	uint8_t def_vol;
+} trackmod_sample_t;
+
+/** Pattern */
+typedef struct {
+	/** Number of rows */
+	size_t rows;
+	/** Number of channels */
+	size_t channels;
+	/** Pattern data */
+	uint32_t *data;
+} trackmod_pattern_t;
+
+/** Module. */
+typedef struct {
+	/** Number of channels */
+	size_t channels;
+	/** Number of samples */
+	size_t samples;
+	/** Samples */
+	trackmod_sample_t *sample;
+	/** Number of patterns */
+	size_t patterns;
+	/** Patterns */
+	trackmod_pattern_t *pattern;
+	/** Order list length */
+	size_t ord_list_len;
+	/** Order list */
+	size_t *ord_list;
+} trackmod_module_t;
+
+/** Channel playback */
+typedef struct {
+	trackmod_sample_t *sample;
+	/** Value of sample before current position */
+	int8_t lsmp;
+	/** Sample position (in frames) */
+	size_t smp_pos;
+	/** Sample position (clock ticks within frame) */
+	size_t smp_clk;
+	/** Period */
+	unsigned period;
+	/** Volume */
+	uint8_t volume;
+} trackmod_chan_t;
+
+/** Module playback. */
+typedef struct {
+	/** Module */
+	trackmod_module_t *module;
+	/** Sampling frequency */
+	unsigned smp_freq;
+	/** Frame size (bytes per sample * channels) */
+	size_t frame_size;
+
+	/** Current position, order list index */
+	size_t ord_idx;
+	/** Current position, row within pattern */
+	size_t row;
+	/** Current position, tick within row */
+	unsigned tick;
+	/** Current position, sample within tick */
+	unsigned smp;
+
+	/** Channel playback state */
+	trackmod_chan_t *chan;
+
+	/** BPM (beats per minute) */
+	unsigned bpm;
+	/** TPR (ticks per row) */
+	unsigned tpr;
+
+	/** If true, break from pattern at end of current row */
+	bool pat_break;
+	/** If @c pat_break is true, row number where to jump in next pattern */
+	size_t pat_break_row;
+	/** Debug mode, print messages to stdout. */
+	bool debug;
+} trackmod_modplay_t;
+
+/** Pattern cell (decoded) */
+typedef struct {
+	/** Sample period */
+	unsigned period;
+	/** Sample number */
+	unsigned sample;
+	/** Effect */
+	unsigned effect;
+} trackmod_cell_t;
+
+#endif
+
+/** @}
+ */
