├── .gitignore ├── LICENSE ├── Makefile ├── README.txt ├── formats ├── mod.txt └── p61a.txt └── src ├── buffer.c ├── buffer.h ├── log.c ├── log.h ├── main.c ├── options.c ├── options.h ├── player61a.c ├── player61a.h ├── protracker.c └── protracker.h /.gitignore: -------------------------------------------------------------------------------- 1 | modpack 2 | out 3 | reference 4 | *.swp 5 | .idea 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Jesper Svennevid 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CCFLAGS= 2 | LDFLAGS= 3 | 4 | all: out modpack 5 | 6 | out: 7 | mkdir out 8 | 9 | clean: 10 | rm -rf out modpack 11 | 12 | modpack: out/main.o out/protracker.o out/player61a.o out/log.o out/buffer.o out/options.o 13 | $(CC) -o $@ $^ $(LDFLAGS) 14 | 15 | out/%.o: src/%.c 16 | $(CC) -c -o $@ $(CCFLAGS) $< 17 | 18 | out/readme.h: README.txt 19 | cat $< | tr "\`" " " | xxd -i > $@ 20 | 21 | SHARED_HEADERS=src/buffer.h src/log.h src/options.h 22 | 23 | out/main.o: src/main.c src/protracker.h out/readme.h $(SHARED_HEADERS) 24 | out/protracker.o: src/protracker.c src/protracker.h $(SHARED_HEADERS) 25 | out/player61a.o: src/player61a.c src/player61a.h src/protracker.h $(SHARED_HEADERS) 26 | out/log.o: src/log.c src/log.h 27 | out/buffer.o: src/buffer.c src/buffer.h 28 | out/options.o: src/options.c src/options.h 29 | 30 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Modpack - Optimize, compress and convert ProTracker/P61A modules 2 | ================================================================ 3 | 4 | Arguments are processed from left to right. This means you can write more 5 | than one output if needed. 6 | 7 | Importing / exporting modules: 8 | 9 | -in:FORMAT NAME Load module in specified format. 10 | -out:FORMAT NAME Save module in specified format. 11 | 12 | Available formats: 13 | 14 | mod Protracker 15 | p61a The Player 6.1A 16 | 17 | If NAME is -, standard input/output will be utilized. 18 | 19 | -opts:OPTIONS Set import/export options 20 | 21 | P61A export options: 22 | 23 | sign Add signature when exporting ('P61A') (disabled) 24 | 4bit[=RANGE] Compress specified samples to 4-bit (disabled) 25 | delta Delta-encode samples (disabled) 26 | [-]compress_patterns Compress pattern data (enabled) 27 | [-]song Write song data to output (enabled) 28 | [-]samples Write sample data to output (enabled) 29 | 30 | Preceeding a boolean option with a minus ('-') will disable the option. 31 | 32 | Range examples: 33 | 34 | [1] Apply to sample 1 35 | [4-7] Apply to sample 4-7 36 | [1-4:8-12] Apply to sample 1-4 and 8-12 (5-7 is not affected) 37 | 38 | 39 | Optimization options: 40 | 41 | -optimize OPTIONS 42 | 43 | Available options: 44 | 45 | unused_patterns Remove unused patterns 46 | unused_samples Remove unused samples (sample index is preserved) 47 | trim Trim tailing null data in samples (not looped samples) 48 | trim_loops Also trim looped samples (implies 'trim') 49 | identical_samples Merge identical samples (pattern data is rewritten 50 | to match) 51 | compact_samples Remove empty space in the sample table 52 | clean Clean effects in pattern data 53 | clean:e8 Remove E8x from pattern data (implies 'clean', not 54 | enabled by 'all') 55 | all Apply all available optimizes (where applicable) 56 | 57 | Preceeding a boolean option with a minus ('-') will disable the option. 58 | 59 | Miscellaneous: 60 | 61 | -d N Set log level (0 = info, 1 = debug, 2 = trace) 62 | -q Quiet mode 63 | 64 | Remove unused patterns and samples, and re-save as MOD: 65 | 66 | modpack -in:mod in.mod -optimize unused_patterns,unused_samples 67 | -out:mod out.mod 68 | 69 | Fully optimize module and export P61A (song and samples separately): 70 | 71 | modpack -in:mod test.mod -optimize all -opts:-samples -out:p61a test.p61 72 | -opts:-song -out:p61a test.smp 73 | -------------------------------------------------------------------------------- /formats/mod.txt: -------------------------------------------------------------------------------- 1 | OFFSET LENGTH BLOCK NAME 2 | 0 20 Header Name (zero filled) 3 | 4 | 20 22 Sample #1 Name (zero filled) 5 | 42 2 Sample #1 Length (words) 6 | 44 1 Sample #1 Finetone (low nibble) 7 | 45 1 Sample #1 Volume (0..64) 8 | 46 2 Sample #1 Repeat Offset 9 | 48 2 Sample #1 Repeat Length 10 | 11 | 50 30 Sample #2 12 | 80 30 Sample #3 13 | 110 30 Sample #4 14 | 140 30 Sample #5 15 | 170 30 Sample #6 16 | 200 30 Sample #7 17 | 230 30 Sample #8 18 | 260 30 Sample #9 19 | 290 30 Sample #10 20 | 320 30 Sample #11 21 | 350 30 Sample #12 22 | 380 30 Sample #13 23 | 410 30 Sample #14 24 | 440 30 Sample #15 25 | 470 30 Sample #16 26 | 500 30 Sample #17 27 | 530 30 Sample #18 28 | 560 30 Sample #19 29 | 590 30 Sample #20 30 | 620 30 Sample #21 31 | 650 30 Sample #22 32 | 680 30 Sample #23 33 | 710 30 Sample #24 34 | 740 30 Sample #25 35 | 770 30 Sample #26 36 | 800 30 Sample #27 37 | 830 30 Sample #28 38 | 860 30 Sample #29 39 | 890 30 Sample #30 40 | 920 30 Sample #31 41 | 42 | 950 1 Song Positions (1..128) 43 | 950 1 Song Repeat Position (deprecated, set to 127) 44 | 952 128 Song Patterns 45 | 1080 4 Song 'M.K.' / 'M!K!' / 'FLT4' (And others, but for PT61 we don't care...) 46 | 47 | 1084 1024 Pattern #0 48 | ... Up to 128 patterns... 49 | 50 | ? X Sample Data #0 51 | ... Up to 31 sample data blocks 52 | 53 | EOF 54 | -------------------------------------------------------------------------------- /formats/p61a.txt: -------------------------------------------------------------------------------- 1 | OFFSET LENGTH BLOCK NAME 2 | -4 4 Header Signature ('P61A') (Optional) 3 | 0 2 Header Sample Offset (Bytes) 4 | 2 1 Header Max Pattern 5 | 3 1 Header Sample Count (1-31) (Bit 6: 4-bit compression, Bit 7: delta compression) 6 | 7 | * SAMPLE INFO * 8 | 9 | 0 2 Sample Sample Size (Words) 10 | 2 1 Sample Finetone (bit 0-3) (Bit 6: compression) 11 | 3 1 Sample Volume (0-64) 12 | 4 2 Sample Repeat Start Offset (Words) (0xffff == sample does not repeat) 13 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void buffer_init(buffer_t* buffer, size_t elemsize) 8 | { 9 | buffer->size = 0; 10 | buffer->capacity = 0; 11 | buffer->elemsize = elemsize; 12 | 13 | buffer->data = NULL; 14 | } 15 | 16 | void buffer_set(buffer_t* buffer, const uint8_t* data, size_t length) 17 | { 18 | buffer->size = length; 19 | buffer->capacity = 0; 20 | 21 | buffer->data = (uint8_t*)data; 22 | } 23 | 24 | void buffer_release(buffer_t* buffer) 25 | { 26 | if (buffer->data && buffer->capacity > 0) 27 | free(buffer->data); 28 | buffer_init(buffer, 0); 29 | } 30 | 31 | void buffer_reset(buffer_t* buffer) 32 | { 33 | buffer->size = 0; 34 | } 35 | 36 | void* buffer_alloc(buffer_t* buffer, size_t elements) 37 | { 38 | size_t newSize = buffer->size + (elements * buffer->elemsize); 39 | if (newSize > buffer->capacity) 40 | { 41 | size_t newCapacity = buffer->capacity > 0 ? (buffer->capacity * 15)/10 : newSize * 2; 42 | newCapacity = newCapacity < newSize ? newSize : newCapacity; 43 | 44 | uint8_t* newData = malloc(newCapacity); 45 | memcpy(newData, buffer->data, buffer->size); 46 | 47 | if (buffer->data) 48 | free(buffer->data); 49 | 50 | buffer->capacity = newCapacity; 51 | buffer->data = newData; 52 | } 53 | 54 | uint8_t* data = buffer->data + buffer->size; 55 | buffer->size = newSize; 56 | 57 | return data; 58 | } 59 | 60 | void* buffer_add(buffer_t* buffer, const void* data, size_t size) 61 | { 62 | assert(buffer->elemsize == 1); // TODO: should we support non byte-arrays? 63 | 64 | void* temp = buffer_alloc(buffer, size); 65 | memcpy(temp, data, size); 66 | return temp; 67 | } 68 | 69 | void* buffer_get(const buffer_t* buffer, size_t index) 70 | { 71 | assert(index < (buffer->size / buffer->elemsize)); 72 | return buffer->data + index * buffer->elemsize; 73 | } 74 | 75 | size_t buffer_offset(const buffer_t* buffer, const void* member) 76 | { 77 | return (((const uint8_t*)member) - buffer->data) / buffer->elemsize; 78 | } 79 | 80 | size_t buffer_count(const buffer_t* buffer) 81 | { 82 | return buffer->size / buffer->elemsize; 83 | } 84 | -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef struct buffer_t 7 | { 8 | size_t size; 9 | size_t capacity; 10 | size_t elemsize; 11 | 12 | uint8_t* data; 13 | } buffer_t; 14 | 15 | void buffer_init(buffer_t* buffer, size_t elemsize); 16 | void buffer_set(buffer_t* buffer, const uint8_t* data, size_t length); 17 | void buffer_release(buffer_t* buffer); 18 | void buffer_reset(buffer_t* buffer); 19 | 20 | void* buffer_alloc(buffer_t* buffer, size_t elements); 21 | void* buffer_add(buffer_t* buffer, const void* data, size_t size); 22 | void* buffer_get(const buffer_t* buffer, size_t index); 23 | size_t buffer_offset(const buffer_t* buffer, const void* member); 24 | size_t buffer_count(const buffer_t* buffer); 25 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | #include 4 | #include 5 | 6 | static int log_level = LOG_LEVEL_INFO; 7 | 8 | void set_log_level(int new_level) 9 | { 10 | log_level = new_level; 11 | } 12 | 13 | void log_msg(int level, const char* format, ...) 14 | { 15 | if (level > log_level) 16 | { 17 | return; 18 | } 19 | 20 | va_list ap; 21 | va_start(ap, format); 22 | vfprintf(stderr, format, ap); 23 | va_end(ap); 24 | } 25 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define LOG_LEVEL_NONE (-3) 4 | #define LOG_LEVEL_ERROR (-2) 5 | #define LOG_LEVEL_WARN (-1) 6 | #define LOG_LEVEL_INFO (0) 7 | #define LOG_LEVEL_DEBUG (1) 8 | #define LOG_LEVEL_TRACE (2) 9 | 10 | void set_log_level(int level); 11 | void log_msg(int level, const char* format, ...); 12 | 13 | #define LOG_ERROR(...) log_msg(LOG_LEVEL_ERROR, "ERROR: " __VA_ARGS__) 14 | #define LOG_WARN(...) log_msg(LOG_LEVEL_WARN, "WARN: " __VA_ARGS__) 15 | #define LOG_INFO(...) log_msg(LOG_LEVEL_INFO, __VA_ARGS__) 16 | #define LOG_DEBUG(...) log_msg(LOG_LEVEL_DEBUG, __VA_ARGS__) 17 | #define LOG_TRACE(...) log_msg(LOG_LEVEL_TRACE, __VA_ARGS__) 18 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "protracker.h" 2 | #include "player61a.h" 3 | #include "buffer.h" 4 | #include "options.h" 5 | #include "log.h" 6 | 7 | static const unsigned char help_text[] = { 8 | #include "../out/readme.h" 9 | , 0x00 10 | }; 11 | 12 | #include 13 | #include 14 | 15 | static bool show_help(int argc, char* argv[]); 16 | static protracker_t* module_load(const char* filename, const char* format); 17 | 18 | int main(int argc, char* argv[]) 19 | { 20 | if (show_help(argc, argv)) 21 | { 22 | return 0; 23 | } 24 | 25 | protracker_t* module = NULL; 26 | const char* options = ""; 27 | size_t i; 28 | 29 | for (i = 1; i < argc; ++i) 30 | { 31 | const char* arg = argv[i]; 32 | const char* opt = i < (argc-1) ? argv[i+1] : NULL; 33 | 34 | if (!strncmp("-in:", arg, 4)) 35 | { 36 | if (module) 37 | { 38 | protracker_free(module); 39 | module = NULL; 40 | } 41 | 42 | if (!opt) 43 | { 44 | LOG_ERROR("No filename specified.\n"); 45 | break; 46 | } 47 | 48 | const char* format = arg+4; 49 | const char* filename = opt; 50 | 51 | LOG_INFO("Loading '%s'...\n", filename); 52 | 53 | module = module_load(filename, format); 54 | if (!module) 55 | { 56 | break; 57 | } 58 | 59 | ++i; 60 | } 61 | else if (!strncmp("-out:", arg, 5)) 62 | { 63 | if (!opt) 64 | { 65 | LOG_ERROR("No filename specified.\n"); 66 | break; 67 | } 68 | 69 | const char* format = arg+5; 70 | const char* filename = opt; 71 | 72 | buffer_t buffer; 73 | buffer_init(&buffer, 1); 74 | 75 | FILE* fp = NULL; 76 | int success = 0; 77 | 78 | do 79 | { 80 | if (!strcmp("mod", format)) 81 | { 82 | if (!protracker_convert(&buffer, module, options)) 83 | { 84 | LOG_ERROR("Conversion to ProTracker failed.\n"); 85 | break; 86 | } 87 | } 88 | else if (!strcmp("p61a", format)) 89 | { 90 | if (!player61a_convert(&buffer, module, options)) 91 | { 92 | LOG_ERROR("Conversion to The Player 6.1A failed.\n"); 93 | break; 94 | } 95 | } 96 | else 97 | { 98 | LOG_ERROR("Unknown output format '%s'.\n", format); 99 | break; 100 | } 101 | 102 | LOG_INFO("Writing result to '%s'...", filename); 103 | 104 | if (!strcmp(filename, "-")) 105 | { 106 | fp = stdout; 107 | } 108 | else 109 | { 110 | fp = fopen(filename, "wb"); 111 | if (!fp) 112 | { 113 | LOG_INFO("failed to open '%s' for writing.\n", filename); 114 | break; 115 | } 116 | } 117 | 118 | size_t size = buffer_count(&buffer); 119 | if ((size > 0) && (fwrite(buffer_get(&buffer, 0), 1, size, fp) != size)) 120 | { 121 | LOG_INFO("failed to write %lu bytes.\n", size); 122 | break; 123 | } 124 | 125 | LOG_INFO("done.\n"); 126 | success = 1; 127 | } 128 | while (0); 129 | 130 | if (fp && (fp != stdout)) 131 | { 132 | fclose(fp); 133 | } 134 | 135 | buffer_release(&buffer); 136 | 137 | ++i; 138 | } 139 | else if (!strncmp("-opts:", arg, 6)) 140 | { 141 | options = arg + 6; 142 | } 143 | else if (!strcmp("-optimize", arg)) 144 | { 145 | if (!opt) 146 | { 147 | LOG_ERROR("No options specified for optimization.\n"); 148 | break; 149 | } 150 | 151 | bool all = has_option(opt, "all", false); 152 | 153 | if (has_option(opt, "unused_patterns", false) || all) 154 | { 155 | protracker_remove_unused_patterns(module); 156 | } 157 | 158 | if (has_option(opt, "trim", false) || all) 159 | { 160 | protracker_trim_samples(module); 161 | } 162 | 163 | if (has_option(opt, "unused_samples", false) || all) 164 | { 165 | protracker_remove_unused_samples(module); 166 | } 167 | 168 | if (has_option(opt, "identical_samples", false) || all) 169 | { 170 | protracker_remove_identical_samples(module); 171 | } 172 | 173 | if (has_option(opt, "compact_samples", false) || all) 174 | { 175 | protracker_compact_sample_indexes(module); 176 | } 177 | 178 | if (has_option(opt, "clean", false) || has_option(opt, "clean:e8", false) || all) 179 | { 180 | protracker_clean_effects(module, opt); 181 | } 182 | 183 | ++i; 184 | } 185 | else if (!strcmp("-d", arg)) 186 | { 187 | if (!opt) 188 | { 189 | LOG_ERROR("No argument specified for debug info.\n"); 190 | break; 191 | } 192 | 193 | set_log_level(strtoul(opt, NULL, 10)); 194 | ++i; 195 | } 196 | else if (!strcmp("-q", arg)) 197 | { 198 | set_log_level(LOG_LEVEL_NONE); 199 | } 200 | } 201 | 202 | if (module) 203 | { 204 | protracker_free(module); 205 | } 206 | 207 | return (i == argc) ? 0 : 1; 208 | } 209 | 210 | static bool show_help(int argc, char* argv[]) 211 | { 212 | bool help = argc < 2; 213 | for (size_t i = 1; i < argc; ++i) 214 | { 215 | if (!strcmp("-h", argv[i]) || !strcmp("--help", argv[i])) 216 | { 217 | help = true; 218 | } 219 | } 220 | if (!help) 221 | { 222 | return false; 223 | } 224 | 225 | LOG_INFO("%s", help_text); 226 | return true; 227 | } 228 | 229 | static protracker_t* module_load(const char* filename, const char* format) 230 | { 231 | protracker_t* module = NULL; 232 | FILE* fp = NULL; 233 | 234 | buffer_t buffer; 235 | buffer_init(&buffer, 1); 236 | 237 | do 238 | { 239 | if (strcmp("-", filename)) 240 | { 241 | fp = fopen(filename, "rb"); 242 | if (!fp) 243 | { 244 | LOG_ERROR("Failed to open file '%s'.\n", filename); 245 | break; 246 | } 247 | } 248 | else 249 | { 250 | fp = stdin; 251 | } 252 | 253 | char buf[1024]; 254 | do { 255 | size_t sz = fread(buf, 1, sizeof(buf), fp); 256 | buffer_add(&buffer, buf, sz); 257 | if (sz < sizeof(buf)) 258 | { 259 | break; 260 | } 261 | } while (true); 262 | 263 | if (!strcmp("mod", format)) 264 | { 265 | module = protracker_load(&buffer); 266 | } 267 | else if (!strcmp("p61a", format)) 268 | { 269 | module = player61a_load(&buffer); 270 | } 271 | else 272 | { 273 | LOG_ERROR("Unknown input format '%s'.\n", format); 274 | break; 275 | } 276 | 277 | if (!module) 278 | { 279 | LOG_ERROR("Failed to load module '%s'.\n", filename); 280 | break; 281 | } 282 | } 283 | while (false); 284 | 285 | if (fp && (fp != stdin)) 286 | { 287 | fclose(fp); 288 | } 289 | 290 | buffer_release(&buffer); 291 | 292 | return module; 293 | } 294 | -------------------------------------------------------------------------------- /src/options.c: -------------------------------------------------------------------------------- 1 | #include "options.h" 2 | #include "log.h" 3 | 4 | #include 5 | #include 6 | 7 | bool has_option(const char* options, const char* name, bool defaultValue) 8 | { 9 | const char* begin = options; 10 | const char* end = begin + strlen(begin); 11 | size_t namelen = strlen(name); 12 | 13 | while (begin != end) 14 | { 15 | const char* curr = strstr(begin, name); 16 | if (!curr) 17 | { 18 | break; 19 | } 20 | 21 | if ((curr[namelen] != '\0') && (curr[namelen] != ',') && (curr[namelen] != '=')) 22 | { 23 | begin = curr + 1; 24 | continue; 25 | } 26 | 27 | 28 | return !((curr != options) && (curr[-1] == '-')); 29 | } 30 | 31 | 32 | return defaultValue; 33 | } 34 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | bool has_option(const char* options, const char* name, bool defaultValue); 6 | -------------------------------------------------------------------------------- /src/player61a.c: -------------------------------------------------------------------------------- 1 | #include "player61a.h" 2 | #include "options.h" 3 | #include "log.h" 4 | 5 | /* 6 | ;0 = two files (song + samples) 7 | ;1 = sign 'P61A' 8 | ;2 = no samples 9 | ;3 = tempo 10 | ;4 = icon 11 | ;5 = delta - 8 bit delta 12 | ;6 = sample packing - 4 bit delta 13 | 14 | */ 15 | 16 | /* 17 | 18 | 0 / 4: P61A 19 | 4 / 2: ??? 20 | 7 / 1: Number of patterns? 21 | 22 | 4 / 2: Sample buffer offset 23 | 24 | 25 | */ 26 | 27 | #include 28 | 29 | static const char* signature = "P61A"; 30 | 31 | static void build_samples(player61a_t* output, const protracker_t* module, const char* options, uint32_t* usecode) 32 | { 33 | LOG_DEBUG("Building sample table:\n"); 34 | 35 | bool usage[PT_NUM_SAMPLES]; 36 | size_t sample_count = protracker_get_used_samples(module, usage); 37 | 38 | for (size_t i = 0; i < PT_NUM_SAMPLES; ++i) 39 | { 40 | const protracker_sample_t* input = &(module->sample_headers[i]); 41 | p61a_sample_t* sample = &(output->sample_headers[i]); 42 | 43 | if (!usage[i]) 44 | { 45 | continue; 46 | } 47 | 48 | uint16_t length; 49 | if (!input->length) 50 | { 51 | // empty 52 | 53 | sample->length = 1; 54 | sample->finetone = 0; 55 | sample->volume = 0; 56 | sample->repeat_offset = 0xffff; 57 | } 58 | else if (input->repeat_length > 1) 59 | { 60 | // looping 61 | 62 | length = input->repeat_offset + input->repeat_length; 63 | LOG_TRACE(" #%lu - %u bytes (looped)\n", (i+1), length * 2); 64 | 65 | if (length != input->length) 66 | { 67 | LOG_WARN("Looped sample #%lu truncated (%u -> %u bytes).\n", (i+1), input->length * 2, length * 2); 68 | } 69 | 70 | sample->length = length; 71 | sample->finetone = input->finetone; 72 | sample->volume = input->volume > 64 ? 64 : input->volume; 73 | sample->repeat_offset = input->repeat_offset; 74 | } 75 | else 76 | { 77 | // not looping 78 | 79 | length = input->length; 80 | LOG_TRACE(" #%lu - %u bytes\n", (i+1), input->length * 2); 81 | 82 | sample->length = length; 83 | sample->finetone = input->finetone; 84 | sample->volume = input->volume > 64 ? 64 : input->volume; 85 | sample->repeat_offset = 0xffff; 86 | } 87 | 88 | if (sample->finetone) 89 | { 90 | *usecode |= 1; // mark finetones used in usecode 91 | } 92 | 93 | // TODO: compression / delta encoding 94 | 95 | if (input->length > 0) 96 | { 97 | buffer_add(&(output->samples), module->sample_data[i], length * 2); 98 | } 99 | else 100 | { 101 | uint16_t temp = 0; 102 | buffer_add(&(output->samples), &temp, sizeof(temp)); 103 | } 104 | } 105 | 106 | LOG_DEBUG(" %lu samples used.\n", sample_count); 107 | 108 | output->header.sample_count = (uint8_t)sample_count; 109 | } 110 | 111 | /* 112 | 113 | * P61 Pattern Format: 114 | 115 | * o = If set compression info follows 116 | * n = Note (6 bits) 117 | * i = Instrument (5 bits) 118 | * c = Command (4 bits) 119 | * b = Info byte (8 bits) 120 | 121 | * onnnnnni iiiicccc bbbbbbbb Note, instrument and command 122 | * o110cccc bbbbbbbb Only command 123 | * o1110nnn nnniiiii Note and instrument 124 | * o1111111 Empty note 125 | 126 | * Compression info: 127 | 128 | * 00nnnnnn n empty rows follow 129 | * 10nnnnnn n same rows follow (for faster testing) 130 | * 01nnnnnn oooooooo Jump o (8 bit offset) bytes back for n rows 131 | * 11nnnnnn oooooooo oooooooo Jump o (16 bit offset) bytes back for n rows 132 | 133 | */ 134 | 135 | 136 | #define CHANNEL_ALL (0x00) 137 | #define CHANNEL_COMMAND (0x60) 138 | #define CHANNEL_NOTE_INSTRUMENT (0x70) 139 | #define CHANNEL_EMPTY (0x7f) 140 | #define CHANNEL_COMPRESSED (0x80) 141 | 142 | #define COMPRESSION_CMD_BITS (0xc0) 143 | #define COMPRESSION_DATA_BITS (0x3f) 144 | 145 | #define COMPRESSION_EMPTY_ROWS (0x00) // Next N rows are empty 146 | #define COMPRESSION_REPEAT_ROWS (0x80) // Repeat this row N times 147 | #define COMPRESSION_JUMP (0x40) // 8-bit jump 148 | #define COMPRESSION_JUMP_LONG (0x80) // 16-bit jump (requires COMPRESSION_JUMP) 149 | 150 | static uint16_t periods[] = { 151 | 856,808,762,720,678,640,604,570,538,508,480,453, // octave 1 152 | 428,404,381,360,339,320,302,285,269,254,240,226, // octave 2 153 | 214,202,190,180,170,160,151,143,135,127,120,113 // octave 3 154 | }; 155 | 156 | static uint8_t index_from_period(uint16_t period) 157 | { 158 | for (size_t i = 0; i < (sizeof(periods) / sizeof(uint16_t)); ++i) 159 | { 160 | if (periods[i] == period) 161 | { 162 | return (uint8_t)(i+1); 163 | } 164 | } 165 | return 0; 166 | } 167 | 168 | static uint16_t period_from_index(uint8_t index) 169 | { 170 | if (!index || index > (sizeof(periods) / sizeof(uint16_t))) 171 | { 172 | return 0; 173 | } 174 | 175 | return periods[index-1]; 176 | } 177 | 178 | typedef struct 179 | { 180 | size_t patterns; 181 | size_t samples; 182 | } p61a_offsets_t; 183 | 184 | static size_t get_sample_offset(const player61a_t* module) 185 | { 186 | size_t curr = 0; 187 | 188 | curr += sizeof(p61a_header_t); // header 189 | curr += sizeof(p61a_sample_t) * module->header.sample_count; // sample headers 190 | curr += sizeof(p61a_pattern_offset_t) * module->header.pattern_count; // pattern offsets 191 | curr += module->song.length+1; // song positions (+0xff) 192 | 193 | curr += buffer_count(&(module->patterns)); // tracks 194 | 195 | if (curr & 1) 196 | { 197 | ++curr; 198 | } 199 | 200 | return curr; 201 | } 202 | 203 | static size_t get_channel_length(const p61a_channel_t* channel) 204 | { 205 | if ((channel->data[0] & CHANNEL_EMPTY) == CHANNEL_EMPTY) 206 | return 1; 207 | 208 | if ((channel->data[0] & CHANNEL_NOTE_INSTRUMENT) == CHANNEL_NOTE_INSTRUMENT) 209 | return 2; 210 | 211 | if ((channel->data[0] & CHANNEL_COMMAND) == CHANNEL_COMMAND) 212 | return 2; 213 | 214 | return 3; 215 | } 216 | 217 | static size_t to_p61a_channel(p61a_channel_t* out, const protracker_channel_t* in, const protracker_pattern_row_t* row, size_t channel_index, uint32_t* usecode) 218 | { 219 | uint8_t instrument = protracker_get_sample(in); 220 | uint16_t period = protracker_get_period(in); 221 | protracker_effect_t effect = protracker_get_effect(in); 222 | 223 | uint8_t note = index_from_period(period); 224 | 225 | bool has_command = (effect.cmd + effect.data.value) != 0; 226 | switch (effect.cmd) 227 | { 228 | case PT_CMD_ARPEGGIO: 229 | { 230 | if (effect.data.value) 231 | { 232 | effect.cmd = PT_CMD_8; // P61A uses 8 for arpeggio 233 | } 234 | } 235 | break; 236 | 237 | case PT_CMD_SLIDE_UP: 238 | case PT_CMD_SLIDE_DOWN: 239 | { 240 | has_command = effect.data.value != 0; 241 | } 242 | break; 243 | 244 | // TODO: these commands need processing 245 | case PT_CMD_CONTINUE_SLIDE: 246 | case PT_CMD_CONTINUE_VIBRATO: 247 | case PT_CMD_VOLUME_SLIDE: 248 | case PT_CMD_POS_JUMP: 249 | case PT_CMD_PATTERN_BREAK: 250 | { 251 | } 252 | break; 253 | 254 | case PT_CMD_SET_VOLUME: 255 | { 256 | effect.data.value = effect.data.value > 64 ? 64 : effect.data.value; 257 | } 258 | break; 259 | 260 | case PT_CMD_8: // 8xy -> E8y 261 | { 262 | effect.cmd = PT_CMD_EXTENDED; 263 | effect.data.ext.cmd = PT_ECMD_E8; 264 | } 265 | break; 266 | 267 | case PT_CMD_EXTENDED: 268 | { 269 | switch (effect.data.ext.cmd) 270 | { 271 | case PT_ECMD_FILTER: // E0x 272 | { 273 | effect.data.ext.value = (effect.data.ext.value & 1) << 1; 274 | } 275 | break; 276 | 277 | case PT_ECMD_CUT_SAMPLE: // ECx 278 | { 279 | if (effect.data.ext.value == 0) 280 | { 281 | effect.cmd = PT_CMD_SET_VOLUME; 282 | effect.data.value = 0; 283 | } 284 | } 285 | break; 286 | 287 | case PT_ECMD_FINESLIDE_UP: // E1x 288 | case PT_ECMD_FINESLIDE_DOWN: // E2x 289 | case PT_ECMD_RETRIGGER_SAMPLE: // E9x 290 | case PT_ECMD_FINE_VOLUME_SLIDE_UP: // EAx 291 | case PT_ECMD_FINE_VOLUME_SLIDE_DOWN: // EBx 292 | case PT_ECMD_DELAY_SAMPLE: // EDx 293 | case PT_ECMD_DELAY_PATTERN: // EEx 294 | { 295 | has_command = effect.data.ext.value != 0; 296 | } 297 | break; 298 | } 299 | } 300 | break; 301 | } 302 | 303 | if (has_command) 304 | { 305 | *usecode |= (effect.cmd == PT_CMD_EXTENDED) ? (1 << (effect.data.ext.cmd + 16)) : (1 << (effect.cmd)); 306 | } 307 | else 308 | { 309 | effect.cmd = 0; 310 | effect.data.value = 0; 311 | } 312 | 313 | // empty channel 314 | if (!note && !instrument && !has_command) 315 | { 316 | // o1111111 317 | out->data[0] = CHANNEL_EMPTY; 318 | out->data[1] = out->data[2] = 0; 319 | return 1; 320 | } 321 | 322 | // note + instrument 323 | if (note && instrument && !has_command) 324 | { 325 | // o1110nnn nnniiiii 326 | out->data[0] = CHANNEL_NOTE_INSTRUMENT | ((note >> 3) & 0x7); 327 | out->data[1] = ((note << 5) & 0xe0) | (instrument & 0x1f); 328 | out->data[2] = 0; 329 | return 2; 330 | } 331 | 332 | // command only 333 | if (!note && !instrument && has_command) 334 | { 335 | // o110cccc bbbbbbbb 336 | out->data[0] = CHANNEL_COMMAND | (effect.cmd & 0x0f); 337 | out->data[1] = effect.data.value; 338 | out->data[2] = 0; 339 | return 2; 340 | } 341 | 342 | // note + instrument + command 343 | // onnnnnni iiiicccc bbbbbbbb 344 | 345 | out->data[0] = CHANNEL_ALL | ((note << 1) & 0x7e) | ((instrument >> 4) & 0x01); 346 | out->data[1] = ((instrument << 4) & 0xf0) | (effect.cmd & 0x0f); 347 | out->data[2] = effect.data.value; 348 | return 3; 349 | } 350 | 351 | static bool to_protracker_channel(protracker_channel_t* out, const p61a_channel_t* in) 352 | { 353 | memset(out, 0, sizeof(protracker_channel_t)); 354 | 355 | if ((in->data[0] & CHANNEL_EMPTY) == CHANNEL_EMPTY) 356 | { 357 | // CHANNEL_EMPTY 358 | } 359 | else if ((in->data[0] & CHANNEL_NOTE_INSTRUMENT) == CHANNEL_NOTE_INSTRUMENT) 360 | { 361 | // CHANNEL_NOTE_INSTRUMENT - o1110nnn nnniiiii 362 | uint8_t note = ((in->data[0] & 0x07) << 3) | ((in->data[1] & 0xe0) >> 5); 363 | uint8_t sample = in->data[1] & 0x1f; 364 | 365 | protracker_set_period(out, period_from_index(note)); 366 | protracker_set_sample(out, sample); 367 | } 368 | else if ((in->data[0] & CHANNEL_COMMAND) == CHANNEL_COMMAND) 369 | { 370 | // CHANNEL_COMMAND - o110cccc bbbbbbbb 371 | protracker_effect_t effect; 372 | effect.cmd = in->data[0] & 0x0f; 373 | effect.data.value = in->data[1]; 374 | 375 | protracker_set_effect(out, &effect); 376 | } 377 | else 378 | { 379 | // CHANNEL_ALL - onnnnnni iiiicccc bbbbbbbb 380 | uint8_t note = (in->data[0] & 0x7e) >> 1; 381 | uint8_t sample = ((in->data[0] & 0x01) << 4) | ((in->data[1] & 0xf0) >> 4); 382 | 383 | protracker_effect_t effect; 384 | effect.cmd = in->data[1] & 0x0f; 385 | effect.data.value = in->data[2]; 386 | 387 | protracker_set_period(out, period_from_index(note)); 388 | protracker_set_sample(out, sample); 389 | protracker_set_effect(out, &effect); 390 | } 391 | 392 | { 393 | protracker_effect_t effect = protracker_get_effect(out); 394 | 395 | switch (effect.cmd) 396 | { 397 | case PT_CMD_8: 398 | { 399 | effect.cmd = PT_CMD_ARPEGGIO; 400 | protracker_set_effect(out, &effect); 401 | } 402 | break; 403 | } 404 | } 405 | 406 | return true; 407 | } 408 | 409 | static size_t build_track(p61a_channel_t* channel, const protracker_pattern_t* pattern, size_t channel_index, uint32_t* usecode) 410 | { 411 | for (size_t i = 0; i < PT_PATTERN_ROWS; ++i) 412 | { 413 | const protracker_pattern_row_t* row = &(pattern->rows[i]); 414 | const protracker_channel_t* in = &(row->channels[channel_index]); 415 | 416 | to_p61a_channel(&(channel[i]), in, row, channel_index, usecode); 417 | } 418 | return PT_PATTERN_ROWS; 419 | } 420 | 421 | static void build_patterns(player61a_t* output, const protracker_t* input, const char* options, uint32_t* usecode) 422 | { 423 | LOG_DEBUG("Converting patterns...\n"); 424 | 425 | output->header.pattern_count = input->num_patterns; 426 | 427 | output->song.length = input->song.length; 428 | memcpy(output->song.positions, input->song.positions, sizeof(uint8_t) * PT_NUM_POSITIONS); 429 | 430 | output->pattern_offsets = malloc(input->num_patterns * sizeof(p61a_pattern_offset_t)); 431 | memset(output->pattern_offsets, 0, input->num_patterns * sizeof(p61a_pattern_offset_t)); 432 | 433 | for (size_t j = 0; j < PT_NUM_CHANNELS; ++j) 434 | { 435 | for (size_t i = 0; i < input->num_patterns; ++i) 436 | { 437 | p61a_channel_t track[PT_PATTERN_ROWS]; 438 | 439 | size_t length = build_track(track, &(input->patterns[i]), j, usecode); 440 | 441 | size_t offset = buffer_count(&(output->patterns)); 442 | 443 | output->pattern_offsets[i].channels[j] = offset; 444 | 445 | for (size_t k = 0; k < length; ++k) 446 | { 447 | const p61a_channel_t* channel = &(track[k]); 448 | buffer_add(&(output->patterns), channel, get_channel_length(channel)); 449 | } 450 | 451 | // LOG_TRACE("CH %lu,%lu: %lu\n", i, j, buffer_count(&(output->patterns)) - offset); 452 | } 453 | } 454 | } 455 | 456 | static void player61a_create(player61a_t* module) 457 | { 458 | memset(module, 0, sizeof(player61a_t)); 459 | 460 | buffer_init(&(module->patterns), 1); 461 | buffer_init(&(module->samples), 1); 462 | } 463 | 464 | static void player61a_destroy(player61a_t* module) 465 | { 466 | free(module->pattern_offsets); 467 | 468 | buffer_release(&(module->patterns)); 469 | buffer_release(&(module->samples)); 470 | } 471 | 472 | #if 0 473 | 474 | static int8_t deltas[] = { 475 | 0,1,2,4,8,16,32,64,128,-64,-32,-16,-8,-4,-2,-1 476 | }; 477 | 478 | #endif 479 | 480 | static void write_song(buffer_t* buffer, const player61a_t* module, const char* options) 481 | { 482 | if (has_option(options, "sign", false)) 483 | { 484 | LOG_TRACE(" - Adding signature.\n"); 485 | buffer_add(buffer, signature, strlen(signature)); 486 | } 487 | 488 | // header 489 | 490 | { 491 | p61a_header_t header; 492 | 493 | header.sample_offset = htons(get_sample_offset(module)); 494 | header.pattern_count = module->header.pattern_count; 495 | header.sample_count = module->header.sample_count; 496 | 497 | buffer_add(buffer, &header, sizeof(header)); 498 | } 499 | 500 | // sample headers 501 | 502 | for (size_t i = 0; i < module->header.sample_count; ++i) 503 | { 504 | const p61a_sample_t* in = &(module->sample_headers[i]); 505 | p61a_sample_t sample; 506 | 507 | bool empty = in->length == 0; 508 | 509 | if (!empty) 510 | { 511 | sample.length = htons(in->length); 512 | sample.finetone = in->finetone; 513 | sample.volume = in->volume; 514 | sample.repeat_offset = htons(in->repeat_offset); 515 | } 516 | else 517 | { 518 | sample.length = htons(1); 519 | sample.finetone = 0; 520 | sample.volume = 0; 521 | sample.repeat_offset = htons(0xffff); 522 | } 523 | 524 | buffer_add(buffer, &sample, sizeof(sample)); 525 | } 526 | 527 | // pattern offsets 528 | 529 | for (size_t i = 0; i < module->header.pattern_count; ++i) 530 | { 531 | p61a_pattern_offset_t offset; 532 | for (size_t j = 0; j < PT_NUM_CHANNELS; ++j) 533 | { 534 | offset.channels[j] = htons(module->pattern_offsets[i].channels[j]); 535 | } 536 | 537 | buffer_add(buffer, &offset, sizeof(offset)); 538 | } 539 | 540 | // tune positions 541 | 542 | { 543 | buffer_add(buffer, module->song.positions, module->song.length); 544 | 545 | uint8_t temp = 0xff; 546 | buffer_add(buffer, &temp, sizeof(temp)); 547 | } 548 | 549 | // tracks 550 | 551 | size_t pattern_size = buffer_count(&(module->patterns)); 552 | if (pattern_size) 553 | { 554 | buffer_add(buffer, buffer_get(&(module->patterns), 0), pattern_size); 555 | } 556 | 557 | // align samples 558 | 559 | if (buffer_count(buffer) & 1) 560 | { 561 | uint8_t c = 0; 562 | buffer_add(buffer, &c, 1); 563 | } 564 | } 565 | 566 | static void write_samples(buffer_t* buffer, const player61a_t* module) 567 | { 568 | size_t size = buffer_count(&(module->samples)); 569 | if (size > 0) 570 | { 571 | buffer_add(buffer, buffer_get(&(module->samples), 0), size); 572 | } 573 | } 574 | 575 | bool player61a_convert(buffer_t* buffer, const protracker_t* module, const char* options) 576 | { 577 | LOG_INFO("Converting to The Player 6.1A...\n"); 578 | 579 | player61a_t temp; 580 | player61a_create(&temp); 581 | uint32_t usecode = 0; 582 | 583 | build_samples(&temp, module, options, &usecode); 584 | build_patterns(&temp, module, options, &usecode); 585 | 586 | LOG_TRACE("usecode: %08x\n", usecode); 587 | 588 | if (has_option(options, "song", true)) 589 | { 590 | LOG_DEBUG(" - Writing song data...\n"); 591 | write_song(buffer, &temp, options); 592 | } 593 | 594 | if (has_option(options, "samples", true)) 595 | { 596 | LOG_DEBUG(" - Writing sample data...\n"); 597 | write_samples(buffer, &temp); 598 | } 599 | 600 | player61a_destroy(&temp); 601 | 602 | return true; 603 | } 604 | 605 | static const uint8_t* read_sample_headers(p61a_sample_t* sample_headers, size_t sample_count, const uint8_t* curr, const uint8_t* max) 606 | { 607 | LOG_TRACE("Samples:\n"); 608 | for (size_t i = 0; i < sample_count; ++i) 609 | { 610 | if ((max - curr) < sizeof(p61a_sample_t)) 611 | { 612 | LOG_ERROR("Premature end of data before sample #%lu.\n", (i+1)); 613 | return NULL; 614 | } 615 | 616 | p61a_sample_t sample; 617 | memcpy(&sample, curr, sizeof(sample)); 618 | curr += sizeof(sample); 619 | 620 | sample.length = ntohs(sample.length); 621 | sample.finetone = sample.finetone; 622 | sample.volume = sample.volume; 623 | sample.repeat_offset = ntohs(sample.repeat_offset); 624 | 625 | LOG_TRACE(" #%02u - length: $%04X, finetone: %u, volume: %u, repeat offset: $%04X\n", 626 | (i+1), 627 | sample.length, 628 | sample.finetone, 629 | sample.volume, 630 | sample.repeat_offset 631 | ); 632 | 633 | sample_headers[i] = sample; 634 | } 635 | 636 | return curr; 637 | } 638 | 639 | static const uint8_t* read_pattern_offsets(p61a_pattern_offset_t* pattern_offsets, size_t pattern_count, const uint8_t* curr, const uint8_t* max) 640 | { 641 | LOG_TRACE("Pattern Offsets:\n"); 642 | for (size_t i = 0; i < pattern_count; ++i) 643 | { 644 | if ((max - curr) < sizeof(p61a_pattern_offset_t)) 645 | { 646 | LOG_ERROR("Premature end of data before pattern offset %lu.\n", i); 647 | return NULL; 648 | } 649 | 650 | p61a_pattern_offset_t offset; 651 | memcpy(&offset, curr, sizeof(offset)); 652 | curr += sizeof(offset); 653 | 654 | LOG_TRACE(" #%lu:", i); 655 | for (size_t j = 0; j < PT_NUM_CHANNELS; ++j) 656 | { 657 | offset.channels[j] = ntohs(offset.channels[j]); 658 | LOG_TRACE(" %04X", offset.channels[j]); 659 | } 660 | LOG_TRACE("\n"); 661 | 662 | pattern_offsets[i] = offset; 663 | } 664 | return curr; 665 | } 666 | 667 | static const uint8_t* read_song_positions(p61a_song_t* song, const uint8_t* curr, const uint8_t* max) 668 | { 669 | LOG_TRACE("Song Positions:\n "); 670 | for (size_t i = 0; i < PT_NUM_POSITIONS; ++i) 671 | { 672 | if ((max - curr) < sizeof(uint8_t)) 673 | { 674 | LOG_ERROR("Premature end of data before song position %lu.\n", i); 675 | return NULL; 676 | } 677 | 678 | uint8_t position = *curr++; 679 | if (position == 0xff) 680 | { 681 | song->length = i; 682 | break; 683 | } 684 | 685 | LOG_TRACE(" %u", position); 686 | 687 | song->positions[i] = position; 688 | } 689 | LOG_TRACE("\n"); 690 | 691 | return curr; 692 | } 693 | 694 | static size_t decompress_track(p61a_pattern_t* pattern, size_t channel_index, size_t offset, size_t maxrows, const uint8_t* track, bool deref, const uint8_t* base) 695 | { 696 | LOG_TRACE("decompress_track(%lu, %lu%s)\n", offset, maxrows, deref ? ", deref" : ""); 697 | 698 | while(offset < PT_PATTERN_ROWS) 699 | { 700 | uint8_t c0 = *track++, c1 = 0, c2 = 0; 701 | p61a_channel_t out = { 0 }; 702 | 703 | if ((c0 & CHANNEL_EMPTY) == CHANNEL_EMPTY) 704 | { 705 | LOG_TRACE(" %02lu %04x: %02x ", offset, ((track-1)-base) & 0xffff, c0); 706 | if (!(c0 & CHANNEL_COMPRESSED)) 707 | { 708 | ++offset; 709 | } 710 | } 711 | else if ((c0 & CHANNEL_NOTE_INSTRUMENT) == CHANNEL_NOTE_INSTRUMENT) 712 | { 713 | out.data[0] = c0; 714 | out.data[1] = c1 = *track++; 715 | 716 | LOG_TRACE(" %02lu %04x: %02x%02x ", offset, ((track-2)-base) & 0xffff, c0, c1); 717 | 718 | pattern->rows[offset++].channels[channel_index] = out; 719 | } 720 | else if ((c0 & CHANNEL_COMMAND) == CHANNEL_COMMAND) 721 | { 722 | out.data[0] = c0; 723 | out.data[1] = c1 = *track++; 724 | 725 | LOG_TRACE(" %02lu %04x: %02x%02x ", offset, ((track-2)-base) & 0xffff, c0, c1); 726 | 727 | pattern->rows[offset++].channels[channel_index] = out; 728 | } 729 | else 730 | { 731 | out.data[0] = c0; 732 | out.data[1] = c1 = *track++; 733 | out.data[2] = c2 = *track++; 734 | 735 | LOG_TRACE(" %02lu %04x: %02x%02x%02x", offset, ((track-3)-base) & 0xffff, c0, c1, c2); 736 | 737 | pattern->rows[offset++].channels[channel_index] = out; 738 | } 739 | 740 | protracker_channel_t ptc; 741 | 742 | to_protracker_channel(&ptc, &out); 743 | 744 | char buf[32]; 745 | protracker_channel_to_text(&ptc, buf, sizeof(buf)); 746 | 747 | LOG_TRACE(" %s", buf); 748 | 749 | if (c0 & CHANNEL_COMPRESSED) 750 | { 751 | uint8_t d0 = *track++; 752 | LOG_TRACE(" %02x", d0); 753 | 754 | if (d0 & COMPRESSION_JUMP) 755 | { 756 | uint8_t rows = (d0 & COMPRESSION_DATA_BITS) + 1; 757 | uint16_t dist = *track++; 758 | LOG_TRACE("%02x", dist); 759 | if (d0 & COMPRESSION_JUMP_LONG) 760 | { 761 | uint8_t d2 = *track++; 762 | LOG_TRACE("%02x", d2); 763 | dist = (dist << 8)|d2; 764 | } 765 | 766 | LOG_TRACE(" (%s JUMP %u %04x)\n", (d0 & COMPRESSION_JUMP_LONG) ? "LONG" : "SHORT", rows, dist); 767 | 768 | offset = decompress_track(pattern, channel_index, offset, rows, track - dist, true, base); 769 | } 770 | else if (d0 & COMPRESSION_REPEAT_ROWS) 771 | { 772 | uint8_t rows = (d0 & COMPRESSION_DATA_BITS); 773 | 774 | LOG_TRACE(" (REPEAT %d)\n", rows); 775 | 776 | for (size_t i = 0; i < rows; ++i) 777 | { 778 | pattern->rows[offset++].channels[channel_index] = out; 779 | } 780 | } 781 | else if ((d0 & COMPRESSION_CMD_BITS) == COMPRESSION_EMPTY_ROWS) 782 | { 783 | uint8_t rows = (d0 & COMPRESSION_DATA_BITS); 784 | 785 | LOG_TRACE(" (EMPTY %d)\n", rows); 786 | 787 | offset += rows; 788 | } 789 | } 790 | else 791 | { 792 | LOG_TRACE("\n"); 793 | } 794 | 795 | if (maxrows > 0 && ((--maxrows) == 0)) 796 | { 797 | break; 798 | } 799 | } 800 | 801 | LOG_TRACE(" - DONE (%lu)\n", offset); 802 | 803 | return offset; 804 | } 805 | 806 | static const uint8_t* read_patterns(p61a_pattern_t* patterns, p61a_pattern_offset_t* pattern_offsets, size_t pattern_count, const uint8_t* curr, const uint8_t* max) 807 | { 808 | for (size_t i = 0; i < pattern_count; ++i) 809 | { 810 | p61a_pattern_t* pattern = &(patterns[i]); 811 | const p61a_pattern_offset_t* offsets = &(pattern_offsets[i]); 812 | 813 | for (size_t j = 0; j < PT_NUM_CHANNELS; ++j) 814 | { 815 | size_t current_row = 0; 816 | const uint8_t* track = curr + offsets->channels[j]; 817 | 818 | LOG_TRACE("Pattern #%lu, track #%lu:\n", i,j); 819 | 820 | decompress_track(pattern, j, 0, 0, curr + offsets->channels[j], false, curr + offsets->channels[j]); 821 | } 822 | } 823 | 824 | return curr; 825 | } 826 | 827 | protracker_t* player61a_load(const buffer_t* buffer) 828 | { 829 | LOG_DEBUG("Loading Player 6.1A module...\n"); 830 | 831 | p61a_pattern_t* patterns = NULL; 832 | 833 | protracker_t module; 834 | protracker_create(&module); 835 | 836 | size_t signature_length = strlen(signature); 837 | 838 | do 839 | { 840 | size_t size = buffer_count(buffer); 841 | if (size < sizeof(p61a_header_t) + signature_length) 842 | { 843 | LOG_ERROR("Premature end of data before header.\n"); 844 | break; 845 | } 846 | 847 | const uint8_t* raw = buffer_get(buffer, 0); 848 | 849 | // check for P61A (and skip it) 850 | if (!memcmp(signature, raw, signature_length)) 851 | { 852 | raw += signature_length; 853 | size -= signature_length; 854 | } 855 | 856 | const uint8_t* curr = raw; 857 | const uint8_t* max = curr + size; 858 | 859 | // header 860 | 861 | p61a_header_t header; 862 | memcpy(&header, curr, sizeof(header)); 863 | curr += sizeof(p61a_header_t); 864 | 865 | header.sample_offset = ntohs(header.sample_offset); 866 | LOG_TRACE("Header:\n Sample Offset: %u\n Patterns:%u\n Sample count:%u\n", header.sample_offset, header.pattern_count, header.sample_count); 867 | 868 | if (!header.pattern_count) 869 | { 870 | LOG_ERROR("Invalid pattern count in header. (%u)\n", header.pattern_count); 871 | break; 872 | } 873 | 874 | if (header.sample_count > PT_NUM_SAMPLES) 875 | { 876 | LOG_ERROR("Invalid sample count in header. (%u > %u)\n", header.sample_count, PT_NUM_SAMPLES); 877 | break; 878 | } 879 | 880 | // sample headers 881 | 882 | p61a_sample_t sample_headers[header.sample_count]; 883 | if (!(curr = read_sample_headers(sample_headers, header.sample_count, curr, max))) 884 | { 885 | break; 886 | } 887 | 888 | // pattern offsets 889 | 890 | p61a_pattern_offset_t pattern_offsets[header.pattern_count]; 891 | if (!(curr = read_pattern_offsets(pattern_offsets, header.pattern_count, curr, max))) 892 | { 893 | break; 894 | } 895 | 896 | // song positions 897 | 898 | p61a_song_t song = { 0 }; 899 | if (!(curr = read_song_positions(&song, curr, max))) 900 | { 901 | break; 902 | } 903 | 904 | // patterns 905 | 906 | p61a_pattern_t* patterns = malloc(sizeof(p61a_pattern_t) * header.pattern_count); 907 | memset(patterns, 0, sizeof(p61a_pattern_t) * header.pattern_count); 908 | if (!(curr = read_patterns(patterns, pattern_offsets, header.pattern_count, curr, max))) 909 | { 910 | break; 911 | } 912 | 913 | // PT: header 914 | 915 | for (size_t i = 0; i < header.sample_count; ++i) 916 | { 917 | const p61a_sample_t* in = &(sample_headers[i]); 918 | protracker_sample_t* out = &(module.sample_headers[i]); 919 | 920 | out->length = in->length; 921 | out->finetone = in->finetone & 0x0f; 922 | out->volume = in->volume; 923 | 924 | if (in->repeat_offset == 0xffff) 925 | { 926 | out->repeat_offset = 0; 927 | out->repeat_length = 1; 928 | } 929 | else 930 | { 931 | out->repeat_offset = in->repeat_offset; 932 | out->repeat_length = in->length - in->repeat_offset; 933 | } 934 | } 935 | 936 | // PT: Song 937 | 938 | module.song.length = song.length; 939 | module.song.restart_position = 127; 940 | memcpy(module.song.positions, song.positions, song.length); 941 | 942 | // PT: Patterns 943 | 944 | module.patterns = malloc(sizeof(protracker_pattern_t) * header.pattern_count); 945 | module.num_patterns = header.pattern_count; 946 | 947 | for (size_t i = 0; i < header.pattern_count; ++i) 948 | { 949 | const p61a_pattern_t* in_pattern = &(patterns[i]); 950 | protracker_pattern_t* out_pattern = &(module.patterns[i]); 951 | 952 | LOG_TRACE("Pattern #%lu:\n", i); 953 | 954 | for (size_t j = 0; j < PT_PATTERN_ROWS; ++j) 955 | { 956 | const p61a_pattern_row_t* in_row = &(in_pattern->rows[j]); 957 | protracker_pattern_row_t* out_row = &(out_pattern->rows[j]); 958 | 959 | LOG_TRACE("%02lu:", j); 960 | 961 | for (size_t k = 0; k < PT_NUM_CHANNELS; ++k) 962 | { 963 | const p61a_channel_t* in = &(in_row->channels[k]); 964 | protracker_channel_t* out = &(out_row->channels[k]); 965 | 966 | to_protracker_channel(out, in); 967 | 968 | char buf[32]; 969 | protracker_channel_to_text(out, buf, sizeof(buf)); 970 | 971 | LOG_TRACE(" %s", buf); 972 | } 973 | 974 | LOG_TRACE("\n"); 975 | } 976 | } 977 | 978 | // PT: Sample Data 979 | 980 | const uint8_t* samples = raw + header.sample_offset; 981 | for (size_t i = 0; i < header.sample_count; ++i) 982 | { 983 | const p61a_sample_t* sample = &(sample_headers[i]); 984 | 985 | if (!sample->length) 986 | { 987 | continue; 988 | } 989 | 990 | size_t bytes = sample->length * 2; 991 | uint8_t* out = module.sample_data[i] = malloc(bytes); 992 | memcpy(out, samples, bytes); 993 | 994 | samples += bytes; 995 | } 996 | 997 | free(patterns); 998 | protracker_t* out = malloc(sizeof(protracker_t)); 999 | *out = module; 1000 | 1001 | return out; 1002 | } 1003 | while (false); 1004 | 1005 | free(patterns); 1006 | protracker_destroy(&module); 1007 | 1008 | return NULL; 1009 | } 1010 | -------------------------------------------------------------------------------- /src/player61a.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "protracker.h" 4 | 5 | /* 6 | swap d4 7 | move.l usec(a5),d2 8 | cmp #$e,d4 9 | bne.b eie 10 | move #$f,d4 11 | rol #4,d1 12 | and d1,d4 13 | add #16,d4 ; E-command -> Add 16 14 | eie bset d4,d2 15 | move.l d2,usec(a5) ; Mark command used 16 | */ 17 | 18 | #define P61A_CHANNEL_BYTES (3) 19 | 20 | typedef struct __attribute__((__packed__)) 21 | { 22 | uint16_t length; // length in words (when looping, subtract repeat offset for loop length) 23 | uint8_t finetone; // 0x0f = finetone, 0x20 = sample compression 24 | uint8_t volume; // 0-64 25 | uint16_t repeat_offset; // offset in words, 0xffff = no loop 26 | } p61a_sample_t; 27 | 28 | typedef struct __attribute__((__packed__)) 29 | { 30 | uint16_t sample_offset; 31 | uint8_t pattern_count; 32 | uint8_t sample_count; // 0x1f = sample count (1-31), 0x20 = 4-bit compression 0x40 = delta compression 33 | } p61a_header_t; 34 | 35 | typedef struct __attribute__((__packed__)) 36 | { 37 | uint16_t channels[PT_NUM_CHANNELS]; 38 | } p61a_pattern_offset_t; 39 | 40 | typedef struct 41 | { 42 | size_t length; 43 | uint8_t positions[PT_NUM_POSITIONS]; 44 | } p61a_song_t; 45 | 46 | typedef struct 47 | { 48 | uint8_t data[P61A_CHANNEL_BYTES]; 49 | } p61a_channel_t; 50 | 51 | typedef struct 52 | { 53 | p61a_channel_t channels[PT_NUM_CHANNELS]; 54 | } p61a_pattern_row_t; 55 | 56 | typedef struct 57 | { 58 | p61a_pattern_row_t rows[PT_PATTERN_ROWS]; 59 | } p61a_pattern_t; 60 | 61 | typedef struct 62 | { 63 | uint32_t usecode; 64 | 65 | // samples 66 | 67 | p61a_header_t header; 68 | p61a_sample_t sample_headers[PT_NUM_SAMPLES]; 69 | 70 | p61a_song_t song; 71 | 72 | p61a_pattern_offset_t* pattern_offsets; 73 | 74 | buffer_t patterns; 75 | buffer_t samples; 76 | } player61a_t; 77 | 78 | bool player61a_convert(buffer_t* buffer, const protracker_t* module, const char* opts); 79 | protracker_t* player61a_load(const buffer_t* buffer); 80 | 81 | -------------------------------------------------------------------------------- /src/protracker.c: -------------------------------------------------------------------------------- 1 | #include "protracker.h" 2 | #include "buffer.h" 3 | #include "options.h" 4 | #include "log.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | static uint16_t octaves[5][12] = { 14 | { 1712,1616,1525,1440,1357,1281,1209,1141,1077,1017, 961, 907 }, // 0 15 | { 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453 }, // 1 16 | { 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226 }, // 2 17 | { 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113 }, // 3 18 | { 107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57 } // 4 19 | }; 20 | 21 | static char* notes[] = { 22 | "C-","C#","D-","D#","E-","F-","F#","G-","G#","A-","A#","B-" 23 | }; 24 | 25 | void protracker_channel_to_text(const protracker_channel_t* channel, char* out, size_t buflen) 26 | { 27 | uint8_t sample = protracker_get_sample(channel); 28 | uint16_t period = protracker_get_period(channel); 29 | protracker_effect_t effect = protracker_get_effect(channel); 30 | 31 | if (sample > 0 && period > 0) 32 | { 33 | for (size_t i = 0; i < 5; ++i) 34 | { 35 | for (size_t j = 0; j < 12; ++j) 36 | { 37 | if (octaves[i][j] == period) 38 | { 39 | snprintf(out, buflen, "%s%ld%02X%1X%1X%1X", notes[j], i, sample, effect.cmd, effect.data.ext.cmd, effect.data.ext.value); 40 | return; 41 | } 42 | } 43 | } 44 | 45 | snprintf(out, buflen, "???%02X%1X%1X%1X", sample, effect.cmd, effect.data.ext.cmd, effect.data.ext.value); 46 | } 47 | else 48 | { 49 | snprintf(out, buflen, "---%02X%1X%1X%1X", sample, effect.cmd, effect.data.ext.cmd, effect.data.ext.value); 50 | } 51 | } 52 | 53 | static void process_sample_header(protracker_sample_t* sample, const uint8_t* in, size_t index) 54 | { 55 | memcpy(sample, in, sizeof(protracker_sample_t)); 56 | 57 | sample->length = ntohs(sample->length); 58 | sample->repeat_offset = ntohs(sample->repeat_offset); 59 | sample->repeat_length = ntohs(sample->repeat_length); 60 | 61 | char sample_name[sizeof(sample->name)+1]; 62 | memset(sample_name, 0, sizeof(sample_name)); 63 | memcpy(sample_name, sample->name, sizeof(sample->name)); 64 | LOG_TRACE(" #%02u - length: $%04X, repeat offset: $%04X, repeat length: $%04X, name: '%s'\n", 65 | index+1, 66 | sample->length, 67 | sample->repeat_offset, 68 | sample->repeat_length, 69 | sample_name 70 | ); 71 | } 72 | 73 | static const uint8_t* process_sample_data(protracker_t* module, const uint8_t* in, const uint8_t* max) 74 | { 75 | size_t i; 76 | for (i = 0; i < PT_NUM_SAMPLES; ++i) 77 | { 78 | const protracker_sample_t* sample = &(module->sample_headers[i]); 79 | 80 | if (in > max) 81 | { 82 | LOG_ERROR("Premature end of data before sample #%lu.\n", (i+1)); 83 | break; 84 | } 85 | 86 | if (!sample->length) 87 | { 88 | continue; 89 | } 90 | 91 | size_t bytes = sample->length * 2; 92 | 93 | uint8_t* data = module->sample_data[i] = malloc(bytes); 94 | if (!data) 95 | { 96 | return NULL; 97 | } 98 | 99 | memcpy(data, in, bytes); 100 | 101 | for (size_t i = 0; i < 256 && i < bytes; ++i) 102 | { 103 | if ((i & 15) == 0) 104 | { 105 | LOG_TRACE("\n%04X:", i); 106 | } 107 | 108 | LOG_TRACE("%02X", data[i]); 109 | } 110 | LOG_TRACE("\n"); 111 | 112 | LOG_TRACE(" #%lu - %u bytes\n", i+1, bytes); 113 | 114 | in += bytes; 115 | } 116 | 117 | return (i == PT_NUM_SAMPLES) ? in : NULL; 118 | } 119 | 120 | protracker_t* protracker_load(const buffer_t* buffer) 121 | { 122 | LOG_DEBUG("Loading Protracker module...\n"); 123 | 124 | protracker_t module; 125 | protracker_create(&module); 126 | 127 | do 128 | { 129 | size_t size = buffer_count(buffer); 130 | if (size < sizeof(protracker_header_t)) 131 | { 132 | LOG_ERROR("Premature end of data before header.\n"); 133 | break; 134 | } 135 | const uint8_t* raw = buffer_get(buffer, 0); 136 | 137 | const uint8_t* curr = raw; 138 | const uint8_t* max = curr + size; 139 | 140 | // Module header 141 | 142 | memcpy(&module.header, curr, sizeof(protracker_header_t)); 143 | curr += sizeof(protracker_header_t); 144 | 145 | char mod_name[sizeof(module.header.name)+1]; 146 | memset(mod_name, 0, sizeof(mod_name)); 147 | memcpy(mod_name, module.header.name, sizeof(module.header.name)); 148 | LOG_TRACE("Header:\n Name: '%s'\n", mod_name); 149 | 150 | // Sample headers 151 | 152 | LOG_TRACE("Samples:\n"); 153 | for (size_t i = 0; i < PT_NUM_SAMPLES; ++i) 154 | { 155 | if (curr > max) 156 | { 157 | LOG_ERROR("Premature end of data before sample %lu.\n", i); 158 | break; 159 | } 160 | 161 | process_sample_header(&(module.sample_headers[i]), curr, i); 162 | curr += sizeof(protracker_sample_t); 163 | } 164 | 165 | if (curr > max) 166 | { 167 | LOG_ERROR("Premature end of data before song data.\n"); 168 | break; 169 | } 170 | 171 | // Song 172 | 173 | memcpy(&module.song, curr, sizeof(protracker_song_t)); 174 | curr += sizeof(protracker_song_t); 175 | 176 | LOG_TRACE("Song:\n Positions: %u (%u)\n", module.song.length, module.song.restart_position); 177 | 178 | LOG_TRACE(" Patterns:"); 179 | bool positions_valid = true; 180 | uint8_t max_pattern = 0; 181 | for (size_t i = 0; i < PT_NUM_POSITIONS; ++i) 182 | { 183 | uint8_t pattern_index = module.song.positions[i]; 184 | max_pattern = max_pattern < pattern_index ? pattern_index : max_pattern; 185 | LOG_TRACE(" %u", pattern_index); 186 | } 187 | LOG_TRACE("\n"); 188 | if (!positions_valid) 189 | { 190 | break; 191 | } 192 | 193 | if (memcmp("M.K.", curr, 4) && memcmp("M!K!", curr, 4) && memcmp("FLT4", curr, 4) && memcmp("4CHN", curr, 4)) 194 | { 195 | LOG_ERROR("Could not find magic word, is this a ProTracker module?\n"); 196 | break; 197 | } 198 | curr += 4; 199 | 200 | // Patterns 201 | 202 | module.num_patterns = max_pattern + 1; 203 | module.patterns = malloc(module.num_patterns * sizeof(protracker_pattern_t)); 204 | 205 | for (size_t i = 0; i < module.num_patterns; ++i) 206 | { 207 | if (curr > max) 208 | { 209 | LOG_ERROR("Premature end of data before pattern %lu.\n", i); 210 | break; 211 | } 212 | 213 | memcpy(&module.patterns[i], curr, sizeof(protracker_pattern_t)); 214 | 215 | LOG_TRACE("Pattern #%lu:\n", i); 216 | for(size_t j = 0; j < PT_PATTERN_ROWS; ++j) 217 | { 218 | const protracker_pattern_row_t* pos = &(module.patterns[i].rows[j]); 219 | 220 | LOG_TRACE(" #%02lu:", j); 221 | 222 | for(size_t k = 0; k < PT_NUM_CHANNELS; ++k) 223 | { 224 | const protracker_channel_t* channel = &(pos->channels[k]); 225 | 226 | char channel_string[32]; 227 | 228 | protracker_channel_to_text(channel, channel_string, sizeof(channel_string)); 229 | 230 | LOG_TRACE(" %s", channel_string); 231 | } 232 | 233 | LOG_TRACE("\n"); 234 | } 235 | 236 | curr += sizeof(protracker_pattern_t); 237 | } 238 | 239 | LOG_TRACE("Sample Data:\n"); 240 | const uint8_t* end = process_sample_data(&module, curr, max); 241 | 242 | if (!end) 243 | { 244 | LOG_ERROR("Failed to load sample data.\n"); 245 | break; 246 | } 247 | 248 | if (max < end) 249 | { 250 | LOG_WARN("%lu bytes not consumed while loading.\n", end - max); 251 | } 252 | else if (max > end) 253 | { 254 | LOG_ERROR("%lu bytes missing while loading.\n", max - end); 255 | break; 256 | } 257 | 258 | LOG_DEBUG("Protracker module loaded successfully.\n"); 259 | 260 | protracker_t* output = malloc(sizeof(protracker_t)); 261 | if (!output) 262 | { 263 | LOG_ERROR("Failed to allocate module block"); 264 | break; 265 | } 266 | *output = module; 267 | 268 | return output; 269 | } while (0); 270 | 271 | LOG_ERROR("Failed to load Protracker module.\n"); 272 | 273 | protracker_destroy(&module); 274 | 275 | return NULL; 276 | } 277 | 278 | bool protracker_convert(buffer_t* buffer, const protracker_t* module, const char* options) 279 | { 280 | LOG_INFO("Exporting ProTracker module\n"); 281 | 282 | LOG_TRACE(" - Header\n"); 283 | 284 | buffer_add(buffer, &(module->header), sizeof(protracker_header_t)); 285 | 286 | LOG_TRACE(" - Samples\n"); 287 | 288 | for (size_t i = 0; i < PT_NUM_SAMPLES; ++i) 289 | { 290 | protracker_sample_t sample = module->sample_headers[i]; 291 | 292 | sample.length = htons(sample.length); 293 | sample.repeat_offset = htons(sample.repeat_offset); 294 | sample.repeat_length = htons(sample.repeat_length); 295 | 296 | buffer_add(buffer, &sample, sizeof(protracker_sample_t)); 297 | } 298 | 299 | LOG_TRACE(" - Song\n"); 300 | 301 | buffer_add(buffer, &(module->song), sizeof(protracker_song_t)); 302 | buffer_add(buffer, "M.K.", 4); 303 | 304 | LOG_TRACE(" - Patterns (%lu)\n", module->num_patterns); 305 | 306 | for (size_t i = 0; i < module->num_patterns; ++i) 307 | { 308 | const protracker_pattern_t* pattern = &(module->patterns[i]); 309 | buffer_add(buffer, pattern, sizeof(protracker_pattern_t)); 310 | } 311 | 312 | LOG_TRACE(" - Sample Data\n"); 313 | 314 | for (size_t i = 0; i < PT_NUM_SAMPLES; ++i) 315 | { 316 | const protracker_sample_t* sample = &(module->sample_headers[i]); 317 | 318 | if (!sample->length) 319 | { 320 | continue; 321 | } 322 | 323 | buffer_add(buffer, module->sample_data[i], sample->length * 2); 324 | } 325 | 326 | return true; 327 | } 328 | 329 | void protracker_create(protracker_t* module) 330 | { 331 | memset(module, 0, sizeof(protracker_t)); 332 | } 333 | 334 | void protracker_destroy(protracker_t* module) 335 | { 336 | free(module->patterns); 337 | for (size_t i = 0; i < PT_NUM_SAMPLES; ++i) 338 | { 339 | free(module->sample_data[i]); 340 | } 341 | } 342 | 343 | void protracker_free(protracker_t* module) 344 | { 345 | protracker_destroy(module); 346 | free(module); 347 | } 348 | 349 | uint8_t protracker_get_sample(const protracker_channel_t* channel) 350 | { 351 | return (channel->data[0] & 0x10) | ((channel->data[2] & 0xf0) >> 4); 352 | 353 | } 354 | 355 | uint16_t protracker_get_period(const protracker_channel_t* channel) 356 | { 357 | return ((channel->data[0] & 0x0f) << 8) + channel->data[1]; 358 | } 359 | 360 | protracker_effect_t protracker_get_effect(const protracker_channel_t* channel) 361 | { 362 | protracker_effect_t effect; 363 | 364 | effect.cmd = (channel->data[2] & 0x0f); 365 | effect.data.value = channel->data[3]; 366 | 367 | return effect; 368 | } 369 | 370 | void protracker_set_sample(protracker_channel_t* channel, uint8_t sample) 371 | { 372 | channel->data[0] = (channel->data[0] & 0x0f) | (sample & 0x10); 373 | channel->data[2] = (channel->data[2] & 0x0f) | ((sample & 0x0f) << 4); 374 | } 375 | 376 | void protracker_set_period(protracker_channel_t* channel, uint16_t period) 377 | { 378 | channel->data[0] = (channel->data[0] & 0xf0) | ((period & 0x0f00) >> 8); 379 | channel->data[1] = period & 0x00ff; 380 | } 381 | 382 | void protracker_set_effect(protracker_channel_t* channel, const protracker_effect_t* effect) 383 | { 384 | channel->data[2] = (channel->data[2] & 0xf0) | effect->cmd; 385 | channel->data[3] = effect->data.value; 386 | } 387 | 388 | typedef struct 389 | { 390 | bool* usage; 391 | } sample_usage_data; 392 | 393 | void sample_usage_filter(const protracker_channel_t* channel, uint8_t index, void* data) 394 | { 395 | sample_usage_data* internal = (sample_usage_data*)data; 396 | uint8_t sample = protracker_get_sample(channel); 397 | 398 | if (sample > 0) 399 | { 400 | internal->usage[sample - 1] = true; 401 | } 402 | } 403 | 404 | size_t protracker_get_used_samples(const protracker_t* module, bool* usage) 405 | { 406 | memset(usage, 0, sizeof(bool) * PT_NUM_SAMPLES); 407 | 408 | sample_usage_data usage_data = { usage }; 409 | protracker_scan_notes(module, sample_usage_filter, &usage_data); 410 | 411 | size_t count = 0; 412 | for (size_t i = 0; i < PT_NUM_SAMPLES; ++i) 413 | { 414 | if (usage[i]) 415 | { 416 | ++ count; 417 | } 418 | } 419 | 420 | return count; 421 | } 422 | 423 | size_t protracker_get_pattern_count(const protracker_t* module) 424 | { 425 | uint8_t max_pattern = 0; 426 | 427 | for (uint8_t i = 0; i < module->song.length; ++i) 428 | { 429 | if (module->song.positions[i] > max_pattern) 430 | max_pattern = module->song.positions[i]; 431 | } 432 | 433 | return max_pattern+1; 434 | } 435 | 436 | void protracker_remove_unused_patterns(protracker_t* module) 437 | { 438 | size_t used_patterns = protracker_get_pattern_count(module); 439 | size_t total_patterns = module->num_patterns; 440 | 441 | LOG_DEBUG("Removing unused patterns...\n"); 442 | 443 | for (size_t i = module->song.length; i < PT_NUM_POSITIONS; ++i) 444 | { 445 | module->song.positions[i] = 0; 446 | } 447 | 448 | size_t num_patterns = 0; 449 | for (size_t i = 0; i < module->num_patterns; ++i) 450 | { 451 | bool used = false; 452 | for (size_t j = 0, m = module->song.length; j < m; ++j) 453 | { 454 | if (module->song.positions[j] == i) 455 | { 456 | used = true; 457 | break; 458 | } 459 | } 460 | 461 | size_t pattern_index = num_patterns; 462 | if (!used) 463 | { 464 | LOG_TRACE(" #%lu - not used, removing...\n", i); 465 | } 466 | else 467 | { 468 | ++ num_patterns; 469 | } 470 | 471 | if (pattern_index == i) 472 | { 473 | continue; 474 | } 475 | 476 | module->patterns[pattern_index] = module->patterns[i]; 477 | 478 | for (size_t j = 0; j < PT_NUM_POSITIONS; ++j) 479 | { 480 | size_t curr_index = module->song.positions[j]; 481 | if (curr_index == i) 482 | { 483 | module->song.positions[j] -= (i - pattern_index); 484 | } 485 | } 486 | } 487 | 488 | module->num_patterns = num_patterns; 489 | } 490 | 491 | void protracker_remove_unused_samples(protracker_t* module) 492 | { 493 | LOG_DEBUG("Removing unused samples...\n"); 494 | 495 | bool used[PT_NUM_SAMPLES]; 496 | size_t sample_count = protracker_get_used_samples(module, used); 497 | 498 | for (size_t i = 0; i < PT_NUM_SAMPLES; ++i) 499 | { 500 | if (used[i] || !module->sample_headers[i].length) 501 | { 502 | continue; 503 | } 504 | 505 | LOG_TRACE(" #%lu - not used, removing...\n", (i+1)); 506 | 507 | free(module->sample_data[i]); 508 | module->sample_data[i] = NULL; 509 | module->sample_headers[i].length = 0; 510 | module->sample_headers[i].repeat_offset = 0; 511 | module->sample_headers[i].repeat_length = 0; 512 | } 513 | } 514 | 515 | void protracker_trim_samples(protracker_t* module) 516 | { 517 | LOG_DEBUG("Trimming samples...\n"); 518 | 519 | for (size_t i = 0; i < PT_NUM_SAMPLES; ++i) 520 | { 521 | protracker_sample_t* sample = &(module->sample_headers[i]); 522 | if (!sample->length || sample->repeat_length > 1) 523 | { 524 | continue; 525 | } 526 | 527 | const uint8_t* data = module->sample_data[i]; 528 | ssize_t sample_end; 529 | 530 | for (sample_end = (sample->length) - 1; sample_end >= 0; --sample_end) 531 | { 532 | size_t byte_offset = sample_end << 1; 533 | if (data[byte_offset] || data[byte_offset+1]) 534 | { 535 | break; 536 | } 537 | } 538 | 539 | size_t sample_length = (sample_end + 1); 540 | if (sample_length == sample->length) 541 | { 542 | continue; 543 | } 544 | 545 | LOG_TRACE(" #%lu - %lu -> %lu bytes (%lu bytes saved)\n", (i + 1), sample->length * 2, sample_length * 2, (sample->length - sample_length) * 2); 546 | sample->length = sample_length; 547 | } 548 | } 549 | 550 | void protracker_clean_effects(protracker_t* module, const char* options) 551 | { 552 | LOG_DEBUG("Cleaning effects...\n"); 553 | 554 | bool clean_e8 = has_option(options, "clean:e8", false); 555 | 556 | for (size_t i = 0; i < module->num_patterns; ++i) 557 | { 558 | protracker_pattern_t* pattern = &(module->patterns[i]); 559 | 560 | for (size_t j = 0; j < PT_PATTERN_ROWS; ++j) 561 | { 562 | protracker_pattern_row_t* row = &(pattern->rows[j]); 563 | 564 | bool has_break = false; 565 | for (size_t k = 0; k < PT_NUM_CHANNELS; ++k) 566 | { 567 | protracker_channel_t* channel = &(row->channels[k]); 568 | protracker_effect_t effect = protracker_get_effect(channel); 569 | 570 | switch (effect.cmd) 571 | { 572 | case PT_CMD_POS_JUMP: 573 | case PT_CMD_PATTERN_BREAK: 574 | { 575 | if (has_break) 576 | { 577 | LOG_TRACE(" (P:%lu,R:%lu,C:%lu) - Removed POS. JUMP/PAT. BREAK\n", i, j, k); 578 | memset(&effect, 0, sizeof(effect)); 579 | } 580 | has_break = true; 581 | } 582 | break; 583 | 584 | case PT_CMD_EXTENDED: 585 | { 586 | switch (effect.data.ext.cmd) 587 | { 588 | case PT_ECMD_E8: 589 | { 590 | if (clean_e8) 591 | { 592 | LOG_TRACE(" (P:%lu,R:%lu,C:%lu) - Removed E8x\n", i, j, k); 593 | memset(&effect, 0, sizeof(effect)); 594 | } 595 | } 596 | break; 597 | } 598 | } 599 | break; 600 | } 601 | 602 | protracker_set_effect(channel, &effect); 603 | } 604 | } 605 | } 606 | } 607 | 608 | 609 | typedef struct 610 | { 611 | uint8_t src, dest; 612 | } sample_replace_data; 613 | 614 | void sample_replace_filter(protracker_channel_t* channel, uint8_t index, void* data) 615 | { 616 | sample_replace_data* internal = (sample_replace_data*)data; 617 | uint8_t sample = protracker_get_sample(channel); 618 | if (sample == internal->src) 619 | { 620 | protracker_set_sample(channel, internal->dest); 621 | } 622 | } 623 | 624 | void protracker_remove_identical_samples(protracker_t* module) 625 | { 626 | LOG_DEBUG("Removing identical samples...\n"); 627 | 628 | for (size_t i = 0; i < PT_NUM_SAMPLES; ++i) 629 | { 630 | const protracker_sample_t* src = &(module->sample_headers[i]); 631 | 632 | if (!src->length) 633 | { 634 | continue; 635 | } 636 | 637 | for (size_t j = i + 1; j < PT_NUM_SAMPLES; ++j) 638 | { 639 | protracker_sample_t* dest = &(module->sample_headers[j]); 640 | 641 | if ( 642 | (src->length != dest->length) || 643 | (src->finetone != dest->finetone) || 644 | (src->volume != dest->volume) || 645 | (src->repeat_offset != dest->repeat_offset) || 646 | (src->repeat_length != dest->repeat_length) 647 | ) 648 | { 649 | continue; 650 | } 651 | 652 | if (memcmp(module->sample_data[i], module->sample_data[j], src->length * 2)) 653 | { 654 | continue; 655 | } 656 | 657 | LOG_TRACE(" #%lu equals #%lu, merging...\n", (i+1), (j+1)); 658 | 659 | sample_replace_data replace_data = { 660 | (j+1), (i+1) 661 | }; 662 | 663 | protracker_transform_notes(module, sample_replace_filter, &replace_data); 664 | 665 | free(module->sample_data[j]); 666 | module->sample_data[j] = NULL; 667 | module->sample_headers[j].length = 0; 668 | module->sample_headers[j].repeat_offset = 0; 669 | module->sample_headers[j].repeat_length = 0; 670 | } 671 | } 672 | } 673 | 674 | typedef struct 675 | { 676 | uint8_t sample, delta; 677 | } compact_sample_data; 678 | 679 | static void compact_sample_filter(protracker_channel_t* channel, uint8_t index, void* data) 680 | { 681 | compact_sample_data* internal = (compact_sample_data*)data; 682 | uint8_t sample = protracker_get_sample(channel); 683 | 684 | if (sample == internal->sample) 685 | { 686 | protracker_set_sample(channel, sample-internal->delta); 687 | } 688 | } 689 | 690 | void protracker_compact_sample_indexes(protracker_t* module) 691 | { 692 | LOG_DEBUG("Compacting sample indexes...\n"); 693 | 694 | bool used[PT_NUM_SAMPLES]; 695 | size_t sample_count = protracker_get_used_samples(module, used); 696 | 697 | for (size_t i = 0, sample_offset = 0; i < PT_NUM_SAMPLES; ++i) 698 | { 699 | size_t sample_index = sample_offset; 700 | bool remove = false; 701 | if (used[i]) 702 | { 703 | ++ sample_offset; 704 | } 705 | else 706 | { 707 | bool existing = false; 708 | for (size_t j = i + 1; j < PT_NUM_SAMPLES; ++j) 709 | { 710 | if (module->sample_headers[j].length) 711 | { 712 | existing = true; 713 | break; 714 | } 715 | } 716 | 717 | if (existing) 718 | { 719 | LOG_TRACE(" #%lu - not used, compacting.\n", (i+1)); 720 | } 721 | } 722 | 723 | if (sample_index == i) 724 | { 725 | continue; 726 | } 727 | 728 | memcpy(&(module->sample_headers[sample_index]), &(module->sample_headers[i]), sizeof(protracker_sample_t)); 729 | module->sample_data[sample_index] = module->sample_data[i]; 730 | 731 | compact_sample_data compact_data = { (uint8_t)(i+1), i - sample_index }; 732 | protracker_transform_notes(module, compact_sample_filter, &compact_data); 733 | } 734 | 735 | for (size_t i = sample_count; i < PT_NUM_SAMPLES; ++i) 736 | { 737 | memset(&(module->sample_headers[i]), 0, sizeof(protracker_sample_t)); 738 | module->sample_data[i] = 0; 739 | } 740 | } 741 | 742 | void protracker_transform_notes(protracker_t* module, void (*transform)(protracker_channel_t*, uint8_t index, void* data), void* data) 743 | { 744 | for (size_t i = 0, n = module->song.length; i < n; ++i) 745 | { 746 | protracker_pattern_t* pattern = &(module->patterns[module->song.positions[i]]); 747 | 748 | for (size_t j = 0; j < PT_PATTERN_ROWS; ++j) 749 | { 750 | protracker_pattern_row_t* row = &(pattern->rows[j]); 751 | 752 | for (size_t k = 0; k < PT_NUM_CHANNELS; ++k) 753 | { 754 | protracker_channel_t* channel = &(row->channels[k]); 755 | 756 | transform(channel, k, data); 757 | } 758 | } 759 | } 760 | } 761 | 762 | void protracker_scan_notes(const protracker_t* module, void (*scan)(const protracker_channel_t*, uint8_t index, void* data), void* data) 763 | { 764 | for (size_t i = 0, n = module->song.length; i < n; ++i) 765 | { 766 | const protracker_pattern_t* pattern = &(module->patterns[module->song.positions[i]]); 767 | 768 | for (size_t j = 0; j < PT_PATTERN_ROWS; ++j) 769 | { 770 | const protracker_pattern_row_t* row = &(pattern->rows[j]); 771 | 772 | for (size_t k = 0; k < PT_NUM_CHANNELS; ++k) 773 | { 774 | const protracker_channel_t* channel = &(row->channels[k]); 775 | 776 | scan(channel, k, data); 777 | } 778 | } 779 | } 780 | } 781 | -------------------------------------------------------------------------------- /src/protracker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "buffer.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define PT_NUM_SAMPLES (31) 10 | #define PT_NUM_POSITIONS (128) 11 | #define PT_NUM_CHANNELS (4) 12 | #define PT_PATTERN_ROWS (64) 13 | 14 | #define PT_CMD_ARPEGGIO (0) 15 | #define PT_CMD_SLIDE_UP (1) 16 | #define PT_CMD_SLIDE_DOWN (2) 17 | #define PT_CMD_SLIDE_TO_NOTE (3) 18 | #define PT_CMD_VIBRATO (4) 19 | #define PT_CMD_CONTINUE_SLIDE (5) 20 | #define PT_CMD_CONTINUE_VIBRATO (6) 21 | #define PT_CMD_TREMOLO (7) 22 | #define PT_CMD_8 (8) 23 | #define PT_CMD_SET_SAMPLE_OFS (9) 24 | #define PT_CMD_VOLUME_SLIDE (10) 25 | #define PT_CMD_POS_JUMP (11) 26 | #define PT_CMD_SET_VOLUME (12) 27 | #define PT_CMD_PATTERN_BREAK (13) 28 | #define PT_CMD_EXTENDED (14) // Extended command 29 | #define PT_CMD_SET_SPEED (15) 30 | 31 | #define PT_ECMD_FILTER (0) 32 | #define PT_ECMD_FINESLIDE_UP (1) 33 | #define PT_ECMD_FINESLIDE_DOWN (2) 34 | #define PT_ECMD_SET_GLISSANDO (3) 35 | #define PT_ECMD_SET_VIBRATO_WAVEFORM (4) 36 | #define PT_ECMD_SET_FINETUNE_VALUE (5) 37 | #define PT_ECMD_LOOP_PATTERN (6) 38 | #define PT_ECMD_SET_TREMOLO_WAVEFORM (7) 39 | #define PT_ECMD_E8 (8) 40 | #define PT_ECMD_RETRIGGER_SAMPLE (9) 41 | #define PT_ECMD_FINE_VOLUME_SLIDE_UP (10) 42 | #define PT_ECMD_FINE_VOLUME_SLIDE_DOWN (11) 43 | #define PT_ECMD_CUT_SAMPLE (12) 44 | #define PT_ECMD_DELAY_SAMPLE (13) 45 | #define PT_ECMD_DELAY_PATTERN (14) 46 | #define PT_ECMD_INVERT_LOOP (15) 47 | 48 | typedef struct __attribute__((__packed__)) 49 | { 50 | char name[20]; 51 | } protracker_header_t; 52 | 53 | typedef struct __attribute__((__packed__)) 54 | { 55 | char name[22]; 56 | uint16_t length; // sample length in words (1 word == 2 bytes) 57 | uint8_t finetone; // low nibble 58 | uint8_t volume; // sample volume (0..64) 59 | uint16_t repeat_offset; 60 | uint16_t repeat_length; 61 | } protracker_sample_t; 62 | 63 | typedef struct __attribute__((__packed__)) 64 | { 65 | uint8_t length; 66 | uint8_t restart_position; 67 | uint8_t positions[PT_NUM_POSITIONS]; 68 | } protracker_song_t; 69 | 70 | typedef struct __attribute__((__packed__)) 71 | { 72 | uint8_t data[4]; 73 | } protracker_channel_t; 74 | 75 | typedef struct 76 | { 77 | uint8_t cmd:4; 78 | 79 | union { 80 | struct { 81 | uint8_t value:4; 82 | uint8_t cmd:4; 83 | } ext; 84 | uint8_t value; 85 | } data; 86 | } protracker_effect_t; 87 | 88 | typedef struct __attribute__((__packed__)) 89 | { 90 | protracker_channel_t channels[PT_NUM_CHANNELS]; 91 | } protracker_pattern_row_t; 92 | 93 | typedef struct __attribute__((__packed__)) 94 | { 95 | protracker_pattern_row_t rows[PT_PATTERN_ROWS]; 96 | } protracker_pattern_t; 97 | 98 | typedef struct __attribute__((__packed__)) 99 | { 100 | protracker_header_t header; 101 | 102 | protracker_song_t song; 103 | 104 | protracker_pattern_t* patterns; 105 | size_t num_patterns; 106 | 107 | protracker_sample_t sample_headers[PT_NUM_SAMPLES]; 108 | uint8_t* sample_data[PT_NUM_SAMPLES]; 109 | } protracker_t; 110 | 111 | void protracker_create(protracker_t* module); 112 | void protracker_destroy(protracker_t* module); 113 | void protracker_free(protracker_t* module); 114 | 115 | protracker_t* protracker_load(const buffer_t* buffer); 116 | bool protracker_convert(buffer_t* buffer, const protracker_t* module, const char* opts); 117 | 118 | uint8_t protracker_get_sample(const protracker_channel_t* channel); 119 | uint16_t protracker_get_period(const protracker_channel_t* channel); 120 | protracker_effect_t protracker_get_effect(const protracker_channel_t* channel); 121 | 122 | void protracker_set_sample(protracker_channel_t* channel, uint8_t sample); 123 | void protracker_set_period(protracker_channel_t* channel, uint16_t period); 124 | void protracker_set_effect(protracker_channel_t* channel, const protracker_effect_t* effect); 125 | 126 | /** 127 | * 128 | * Get sample usage in tune 129 | * 130 | * module - ProTracker module 131 | * usage - Usage table (must allow for PT_NUM_SAMPLES entries) 132 | * 133 | * Returns number of active samples 134 | * 135 | **/ 136 | size_t protracker_get_used_samples(const protracker_t* module, bool* usage); 137 | 138 | /** 139 | * 140 | * module - ProTracker module 141 | * 142 | * returns number of patterns used in module 143 | * 144 | **/ 145 | size_t protracker_get_pattern_count(const protracker_t* module); 146 | 147 | /** 148 | * 149 | * Remove patterns that are not included in the module 150 | * 151 | **/ 152 | void protracker_remove_unused_patterns(protracker_t* module); 153 | 154 | /** 155 | * 156 | * Remove samples from module that are not by any pattern (sample index is preserved) 157 | * 158 | **/ 159 | void protracker_remove_unused_samples(protracker_t* module); 160 | 161 | /** 162 | * 163 | * Remove samples that identical 164 | * 165 | **/ 166 | void protracker_remove_identical_samples(protracker_t* module); 167 | 168 | /** 169 | * 170 | * Compact sample indexes to remove empty space in sample list 171 | * 172 | **/ 173 | void protracker_compact_sample_indexes(protracker_t* module); 174 | 175 | /** 176 | * 177 | * Trim sample data, removing zero bytes from the end of non-looping samples 178 | * 179 | **/ 180 | void protracker_trim_samples(protracker_t* module); 181 | 182 | /** 183 | * 184 | * Clean effects, removing unnecessary effects and downgrading them to simpler variations 185 | * 186 | **/ 187 | void protracker_clean_effects(protracker_t* module, const char* options); 188 | 189 | /** 190 | * 191 | * Pattern iterator to simplify transforming protracker pattern data 192 | * 193 | **/ 194 | void protracker_transform_notes(protracker_t* module, void (*transform)(protracker_channel_t* channel, uint8_t index, void* data), void* data); 195 | 196 | /** 197 | * 198 | * Pattern iterator to simplify scanning protracker pattern data 199 | * 200 | **/ 201 | void protracker_scan_notes(const protracker_t* module, void (*scan)(const protracker_channel_t* channel, uint8_t index, void* data), void* data); 202 | 203 | /** 204 | * 205 | * Build a text string out of a protracker channel 206 | * 207 | * channel - Channel to print 208 | * out - Output buffer 209 | * buflen - Buffer size 210 | * 211 | **/ 212 | void protracker_channel_to_text(const protracker_channel_t* channel, char *out, size_t buflen); 213 | --------------------------------------------------------------------------------