| 1 | /*
|
|---|
| 2 | * Copyright (c) 2014 Jiri Svoboda
|
|---|
| 3 | * All rights reserved.
|
|---|
| 4 | *
|
|---|
| 5 | * Redistribution and use in source and binary forms, with or without
|
|---|
| 6 | * modification, are permitted provided that the following conditions
|
|---|
| 7 | * are met:
|
|---|
| 8 | *
|
|---|
| 9 | * - Redistributions of source code must retain the above copyright
|
|---|
| 10 | * notice, this list of conditions and the following disclaimer.
|
|---|
| 11 | * - Redistributions in binary form must reproduce the above copyright
|
|---|
| 12 | * notice, this list of conditions and the following disclaimer in the
|
|---|
| 13 | * documentation and/or other materials provided with the distribution.
|
|---|
| 14 | * - The name of the author may not be used to endorse or promote products
|
|---|
| 15 | * derived from this software without specific prior written permission.
|
|---|
| 16 | *
|
|---|
| 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|---|
| 18 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|---|
| 19 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|---|
| 20 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|---|
| 21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|---|
| 22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|---|
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|---|
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|---|
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|---|
| 26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|---|
| 27 | */
|
|---|
| 28 |
|
|---|
| 29 | /** @addtogroup trackmod
|
|---|
| 30 | * @{
|
|---|
| 31 | */
|
|---|
| 32 | /**
|
|---|
| 33 | * @file Protracker module (.mod).
|
|---|
| 34 | */
|
|---|
| 35 |
|
|---|
| 36 | #include <errno.h>
|
|---|
| 37 | #include <stdio.h>
|
|---|
| 38 | #include <stdlib.h>
|
|---|
| 39 | #include <mem.h>
|
|---|
| 40 |
|
|---|
| 41 | #include "byteorder.h"
|
|---|
| 42 | #include "protracker.h"
|
|---|
| 43 | #include "trackmod.h"
|
|---|
| 44 | #include "types/protracker.h"
|
|---|
| 45 |
|
|---|
| 46 | /** Sample tag decoding table entry*/
|
|---|
| 47 | typedef struct {
|
|---|
| 48 | /** Tag */
|
|---|
| 49 | const char *tag;
|
|---|
| 50 | /** Number of channels */
|
|---|
| 51 | unsigned channels;
|
|---|
| 52 | } smptag_desc_t;
|
|---|
| 53 |
|
|---|
| 54 | /** Sample tag decoding table */
|
|---|
| 55 | static smptag_desc_t smp_tags[] = {
|
|---|
| 56 | { .tag = "M.K.", .channels = 4 },
|
|---|
| 57 | { .tag = "M!K!", .channels = 4 },
|
|---|
| 58 | { .tag = "2CHN", .channels = 2 },
|
|---|
| 59 | { .tag = "6CHN", .channels = 6 },
|
|---|
| 60 | { .tag = "8CHN", .channels = 8 },
|
|---|
| 61 | { .tag = "10CH", .channels = 10 },
|
|---|
| 62 | { .tag = "12CH", .channels = 12 },
|
|---|
| 63 | { .tag = "14CH", .channels = 14 },
|
|---|
| 64 | { .tag = "16CH", .channels = 16 },
|
|---|
| 65 | { .tag = "18CH", .channels = 18 },
|
|---|
| 66 | { .tag = "20CH", .channels = 20 },
|
|---|
| 67 | { .tag = "22CH", .channels = 22 },
|
|---|
| 68 | { .tag = "24CH", .channels = 24 },
|
|---|
| 69 | { .tag = "26CH", .channels = 26 },
|
|---|
| 70 | { .tag = "28CH", .channels = 28 },
|
|---|
| 71 | { .tag = "30CH", .channels = 30 },
|
|---|
| 72 | { .tag = "32CH", .channels = 32 },
|
|---|
| 73 | };
|
|---|
| 74 |
|
|---|
| 75 | /** Decode sample tag.
|
|---|
| 76 | *
|
|---|
| 77 | * @param tag Tag
|
|---|
| 78 | * @param channels Place to store number or channels.
|
|---|
| 79 | * @return EOK on success, EINVAL if tag is not recognized.
|
|---|
| 80 | */
|
|---|
| 81 | static int smp_tag_decode(uint8_t *tag, size_t *channels)
|
|---|
| 82 | {
|
|---|
| 83 | size_t nentries = sizeof(smp_tags) / sizeof(smptag_desc_t);
|
|---|
| 84 | size_t i;
|
|---|
| 85 |
|
|---|
| 86 | for (i = 0; i < nentries; i++) {
|
|---|
| 87 | if (memcmp(tag, smp_tags[i].tag, 4) == 0) {
|
|---|
| 88 | *channels = smp_tags[i].channels;
|
|---|
| 89 | return EOK;
|
|---|
| 90 | }
|
|---|
| 91 | }
|
|---|
| 92 |
|
|---|
| 93 | return EINVAL;
|
|---|
| 94 | }
|
|---|
| 95 |
|
|---|
| 96 | /** Get number of patterns stored in file.
|
|---|
| 97 | *
|
|---|
| 98 | * @param olist Order list
|
|---|
| 99 | * @return Number of patterns in file
|
|---|
| 100 | */
|
|---|
| 101 | static size_t order_list_get_npatterns(protracker_order_list_t *olist)
|
|---|
| 102 | {
|
|---|
| 103 | size_t i;
|
|---|
| 104 | size_t max_pat;
|
|---|
| 105 |
|
|---|
| 106 | max_pat = 0;
|
|---|
| 107 | for (i = 0; i < protracker_olist_len; i++) {
|
|---|
| 108 | if (olist->order_list[i] > max_pat)
|
|---|
| 109 | max_pat = olist->order_list[i];
|
|---|
| 110 | }
|
|---|
| 111 |
|
|---|
| 112 | return 1 + max_pat;
|
|---|
| 113 | }
|
|---|
| 114 |
|
|---|
| 115 | /** Load protracker module.
|
|---|
| 116 | *
|
|---|
| 117 | * @param fname File name
|
|---|
| 118 | * @param rmodule Place to store pointer to newly loaded module.
|
|---|
| 119 | * @return EOK on success, ENONEM if out of memory, EIO on I/O error
|
|---|
| 120 | * or if any error is found in the format of the file.
|
|---|
| 121 | */
|
|---|
| 122 | int trackmod_protracker_load(char *fname, trackmod_module_t **rmodule)
|
|---|
| 123 | {
|
|---|
| 124 | FILE *f = NULL;
|
|---|
| 125 | trackmod_module_t *module = NULL;
|
|---|
| 126 | protracker_31smp_t mod31;
|
|---|
| 127 | protracker_15smp_t mod15;
|
|---|
| 128 | protracker_order_list_t *order_list;
|
|---|
| 129 | protracker_smp_t *sample;
|
|---|
| 130 | size_t nread;
|
|---|
| 131 | size_t samples;
|
|---|
| 132 | size_t channels;
|
|---|
| 133 | size_t patterns;
|
|---|
| 134 | size_t cells;
|
|---|
| 135 | size_t i, j;
|
|---|
| 136 | int rc;
|
|---|
| 137 |
|
|---|
| 138 | f = fopen(fname, "rb");
|
|---|
| 139 | if (f == NULL) {
|
|---|
| 140 | printf("Error opening file.\n");
|
|---|
| 141 | rc = EIO;
|
|---|
| 142 | goto error;
|
|---|
| 143 | }
|
|---|
| 144 |
|
|---|
| 145 | nread = fread(&mod31, 1, sizeof(protracker_31smp_t), f);
|
|---|
| 146 | if (nread < sizeof(protracker_15smp_t)) {
|
|---|
| 147 | printf("File too small.\n");
|
|---|
| 148 | rc = EIO;
|
|---|
| 149 | goto error;
|
|---|
| 150 | }
|
|---|
| 151 |
|
|---|
| 152 | if (nread == sizeof(protracker_31smp_t)) {
|
|---|
| 153 | /* Could be 31-sample variant */
|
|---|
| 154 | rc = smp_tag_decode(mod31.sample_tag, &channels);
|
|---|
| 155 | if (rc != EOK) {
|
|---|
| 156 | samples = 15;
|
|---|
| 157 | channels = 4;
|
|---|
| 158 | } else {
|
|---|
| 159 | samples = 31;
|
|---|
| 160 | }
|
|---|
| 161 | } else {
|
|---|
| 162 | samples = 15;
|
|---|
| 163 | channels = 4;
|
|---|
| 164 | }
|
|---|
| 165 |
|
|---|
| 166 | if (samples == 15) {
|
|---|
| 167 | memcpy(&mod15, &mod31, sizeof(protracker_15smp_t));
|
|---|
| 168 |
|
|---|
| 169 | rc = fseek(f, sizeof(protracker_15smp_t), SEEK_SET);
|
|---|
| 170 | if (rc != 0) {
|
|---|
| 171 | printf("Error seeking.\n");
|
|---|
| 172 | rc = EIO;
|
|---|
| 173 | goto error;
|
|---|
| 174 | }
|
|---|
| 175 |
|
|---|
| 176 | order_list = &mod15.order_list;
|
|---|
| 177 | sample = mod15.sample;
|
|---|
| 178 | } else {
|
|---|
| 179 | order_list = &mod31.order_list;
|
|---|
| 180 | sample = mod31.sample;
|
|---|
| 181 | }
|
|---|
| 182 |
|
|---|
| 183 | patterns = order_list_get_npatterns(order_list);
|
|---|
| 184 |
|
|---|
| 185 | module = trackmod_module_new();
|
|---|
| 186 | if (module == NULL) {
|
|---|
| 187 | printf("Out of memory.\n");
|
|---|
| 188 | rc = ENOMEM;
|
|---|
| 189 | goto error;
|
|---|
| 190 | }
|
|---|
| 191 |
|
|---|
| 192 | module->channels = channels;
|
|---|
| 193 |
|
|---|
| 194 | module->samples = samples;
|
|---|
| 195 | module->sample = calloc(sizeof(trackmod_sample_t), samples);
|
|---|
| 196 | if (module->sample == NULL) {
|
|---|
| 197 | printf("Out of memory.\n");
|
|---|
| 198 | rc = ENOMEM;
|
|---|
| 199 | goto error;
|
|---|
| 200 | }
|
|---|
| 201 |
|
|---|
| 202 | module->patterns = patterns;
|
|---|
| 203 | module->pattern = calloc(sizeof(trackmod_pattern_t), patterns);
|
|---|
| 204 | if (module->pattern == NULL) {
|
|---|
| 205 | printf("Out of memory.\n");
|
|---|
| 206 | rc = ENOMEM;
|
|---|
| 207 | goto error;
|
|---|
| 208 | }
|
|---|
| 209 |
|
|---|
| 210 | /* Order list */
|
|---|
| 211 | module->ord_list_len = order_list->order_list_len;
|
|---|
| 212 | module->ord_list = calloc(sizeof(size_t), module->ord_list_len);
|
|---|
| 213 | if (module->ord_list == NULL) {
|
|---|
| 214 | printf("Out of memory.\n");
|
|---|
| 215 | rc = ENOMEM;
|
|---|
| 216 | goto error;
|
|---|
| 217 | }
|
|---|
| 218 |
|
|---|
| 219 | for (i = 0; i < order_list->order_list_len; i++) {
|
|---|
| 220 | module->ord_list[i] = order_list->order_list[i];
|
|---|
| 221 | }
|
|---|
| 222 |
|
|---|
| 223 | /* Load patterns */
|
|---|
| 224 |
|
|---|
| 225 | cells = channels * protracker_pattern_rows;
|
|---|
| 226 |
|
|---|
| 227 | for (i = 0; i < patterns; i++) {
|
|---|
| 228 | module->pattern[i].rows = protracker_pattern_rows;
|
|---|
| 229 | module->pattern[i].channels = channels;
|
|---|
| 230 | module->pattern[i].data = calloc(sizeof(uint32_t), cells);
|
|---|
| 231 |
|
|---|
| 232 | nread = fread(module->pattern[i].data,
|
|---|
| 233 | sizeof(uint32_t), cells, f);
|
|---|
| 234 | if (nread != cells) {
|
|---|
| 235 | printf("Error reading pattern.\n");
|
|---|
| 236 | rc = EIO;
|
|---|
| 237 | goto error;
|
|---|
| 238 | }
|
|---|
| 239 |
|
|---|
| 240 | /* Convert byte order */
|
|---|
| 241 | for (j = 0; j < cells; j++) {
|
|---|
| 242 | module->pattern[i].data[j] = uint32_t_be2host(
|
|---|
| 243 | module->pattern[i].data[j]);
|
|---|
| 244 | }
|
|---|
| 245 | }
|
|---|
| 246 |
|
|---|
| 247 | /* Load samples */
|
|---|
| 248 | for (i = 0; i < samples; i++) {
|
|---|
| 249 | module->sample[i].length =
|
|---|
| 250 | uint16_t_be2host(sample[i].length) * 2;
|
|---|
| 251 | module->sample[i].data = calloc(1, module->sample[i].length);
|
|---|
| 252 | if (module->sample[i].data == NULL) {
|
|---|
| 253 | printf("Error allocating sample.\n");
|
|---|
| 254 | rc = ENOMEM;
|
|---|
| 255 | goto error;
|
|---|
| 256 | }
|
|---|
| 257 |
|
|---|
| 258 | nread = fread(module->sample[i].data, 1, module->sample[i].length,
|
|---|
| 259 | f);
|
|---|
| 260 | if (nread != module->sample[i].length) {
|
|---|
| 261 | printf("Error reading sample.\n");
|
|---|
| 262 | rc = EIO;
|
|---|
| 263 | goto error;
|
|---|
| 264 | }
|
|---|
| 265 |
|
|---|
| 266 | module->sample[i].def_vol = sample[i].def_vol;
|
|---|
| 267 | module->sample[i].loop_start =
|
|---|
| 268 | uint16_t_be2host(sample[i].loop_start) * 2;
|
|---|
| 269 | module->sample[i].loop_len =
|
|---|
| 270 | uint16_t_be2host(sample[i].loop_len) * 2;
|
|---|
| 271 | if (module->sample[i].loop_len <= 2)
|
|---|
| 272 | module->sample[i].loop_len = 0;
|
|---|
| 273 | }
|
|---|
| 274 |
|
|---|
| 275 | (void) fclose(f);
|
|---|
| 276 |
|
|---|
| 277 | *rmodule = module;
|
|---|
| 278 | return EOK;
|
|---|
| 279 | error:
|
|---|
| 280 | if (module != NULL)
|
|---|
| 281 | trackmod_module_destroy(module);
|
|---|
| 282 | if (f != NULL)
|
|---|
| 283 | (void) fclose(f);
|
|---|
| 284 | return rc;
|
|---|
| 285 | }
|
|---|
| 286 |
|
|---|
| 287 | /** @}
|
|---|
| 288 | */
|
|---|