/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define NEED_BYTESWAP #define NEED_TIME #include "headers.h" #include #include #include #include "sndfile.h" #include "log.h" #include "util.h" #include "fmt.h" // for it_decompress8 / it_decompress16 static void _csf_reset(song_t *csf) { unsigned int i; csf->flags = 0; csf->pan_separation = 128; csf->num_voices = 0; csf->freq_factor = csf->tempo_factor = 128; csf->initial_global_volume = 128; csf->current_global_volume = 128; csf->initial_speed = 6; csf->initial_tempo = 125; csf->process_row = 0; csf->row = 0; csf->current_pattern = 0; csf->current_order = 0; csf->process_order = 0; csf->mixing_volume = 0x30; memset(csf->message, 0, sizeof(csf->message)); csf->row_highlight_major = 16; csf->row_highlight_minor = 4; /* This is intentionally crappy quality, so that it's very obvious if it didn't get initialized */ csf->mix_flags = 0; csf_set_wave_config(csf, 4000, 8, 1); memset(csf->voices, 0, sizeof(csf->voices)); memset(csf->voice_mix, 0, sizeof(csf->voice_mix)); memset(csf->samples, 0, sizeof(csf->samples)); memset(csf->instruments, 0, sizeof(csf->instruments)); memset(csf->orderlist, 0xFF, sizeof(csf->orderlist)); memset(csf->patterns, 0, sizeof(csf->patterns)); csf_reset_midi_cfg(csf); csf_forget_history(csf); for (i = 0; i < MAX_PATTERNS; i++) { csf->pattern_size[i] = 64; csf->pattern_alloc_size[i] = 64; } for (i = 0; i < MAX_SAMPLES; i++) { csf->samples[i].c5speed = 8363; csf->samples[i].volume = 64 * 4; csf->samples[i].global_volume = 64; } for (i = 0; i < MAX_CHANNELS; i++) { csf->channels[i].panning = 128; csf->channels[i].volume = 64; csf->channels[i].flags = 0; } } ////////////////////////////////////////////////////////// // song_t song_t *csf_allocate(void) { song_t *csf = calloc(1, sizeof(song_t)); _csf_reset(csf); return csf; } void csf_free(song_t *csf) { if (csf) { csf_destroy(csf); free(csf); } } static void _init_envelope(song_envelope_t *env, int n) { env->nodes = 2; env->ticks[0] = 0; env->ticks[1] = 100; env->values[0] = n; env->values[1] = n; } void csf_init_instrument(song_instrument_t *ins, int samp) { int n; _init_envelope(&ins->vol_env, 64); _init_envelope(&ins->pan_env, 32); _init_envelope(&ins->pitch_env, 32); ins->global_volume = 128; ins->panning = 128; ins->midi_bank = -1; ins->midi_program = -1; ins->pitch_pan_center = 60; // why does pitch/pan not use the same note values as everywhere else?! for (n = 0; n < 128; n++) { ins->sample_map[n] = samp; ins->note_map[n] = n + 1; } } song_instrument_t *csf_allocate_instrument(void) { song_instrument_t *ins = calloc(1, sizeof(song_instrument_t)); csf_init_instrument(ins, 0); return ins; } void csf_free_instrument(song_instrument_t *i) { free(i); } void csf_destroy(song_t *csf) { int i; for (i = 0; i < MAX_PATTERNS; i++) { if (csf->patterns[i]) { csf_free_pattern(csf->patterns[i]); csf->patterns[i] = NULL; } } for (i = 1; i < MAX_SAMPLES; i++) { song_sample_t *pins = &csf->samples[i]; if (pins->data) { csf_free_sample(pins->data); pins->data = NULL; } } for (i = 0; i < MAX_INSTRUMENTS; i++) { if (csf->instruments[i]) { csf_free_instrument(csf->instruments[i]); csf->instruments[i] = NULL; } } _csf_reset(csf); } song_note_t *csf_allocate_pattern(uint32_t rows) { return calloc(rows * MAX_CHANNELS, sizeof(song_note_t)); } void csf_free_pattern(void *pat) { free(pat); } /* Note: this function will appear in valgrind to be a sieve for memory leaks. It isn't; it's just being confused by the adjusted pointer being stored. */ signed char *csf_allocate_sample(uint32_t nbytes) { signed char *p = calloc(1, (nbytes + 39) & ~7); // magic if (p) p += 16; return p; } void csf_free_sample(void *p) { if (p) free(p - 16); } void csf_forget_history(song_t *csf) { free(csf->histdata); csf->histdata = NULL; csf->histlen = 0; gettimeofday(&csf->editstart, NULL); } /* --------------------------------------------------------------------------------------------------------- */ /* Counting and checking stuff. */ static int name_is_blank(char *name) { int n; for (n = 0; n < 25; n++) { if (name[n] != '\0' && name[n] != ' ') return 0; } return 1; } const song_note_t blank_pattern[64 * 64]; const song_note_t *blank_note = blank_pattern; // Same thing, really. int csf_note_is_empty(song_note_t *note) { return !memcmp(note, blank_pattern, sizeof(song_note_t)); } int csf_pattern_is_empty(song_t *csf, int n) { if (!csf->patterns[n]) return 1; if (csf->pattern_size[n] != 64) return 0; return !memcmp(csf->patterns[n], blank_pattern, sizeof(blank_pattern)); } int csf_sample_is_empty(song_sample_t *smp) { return (smp->data == NULL && name_is_blank(smp->name) && smp->filename[0] == '\0' && smp->c5speed == 8363 && smp->volume == 64*4 //mphack && smp->global_volume == 64 && smp->panning == 0 && !(smp->flags & (CHN_LOOP | CHN_SUSTAINLOOP | CHN_PANNING)) && smp->length == 0 && smp->loop_start == 0 && smp->loop_end == 0 && smp->sustain_start == 0 && smp->sustain_end == 0 && smp->vib_type == VIB_SINE && smp->vib_rate == 0 && smp->vib_depth == 0 && smp->vib_speed == 0 ); } static int env_is_blank(song_envelope_t *env, int value) { return (env->nodes == 2 && env->loop_start == 0 && env->loop_end == 0 && env->sustain_start == 0 && env->sustain_end == 0 && env->ticks[0] == 0 && env->ticks[1] == 100 && env->values[0] == value && env->values[1] == value ); } int csf_instrument_is_empty(song_instrument_t *ins) { int n; if (!ins) return 1; for (n = 0; n < NOTE_LAST - NOTE_FIRST; n++) { if (ins->sample_map[n] != 0 || ins->note_map[n] != (n + NOTE_FIRST)) return 0; } return (name_is_blank(ins->name) && ins->filename[0] == '\0' && ins->flags == 0 /* No envelopes, loop points, panning, or carry flags set */ && ins->nna == NNA_NOTECUT && ins->dct == DCT_NONE && ins->dca == DCA_NOTECUT && env_is_blank(&ins->vol_env, 64) && ins->global_volume == 128 && ins->fadeout == 0 && ins->vol_swing == 0 && env_is_blank(&ins->pan_env, 32) && ins->panning == 32*4 //mphack && ins->pitch_pan_center == 60 // C-5 (blah) && ins->pitch_pan_separation == 0 && ins->pan_swing == 0 && env_is_blank(&ins->pitch_env, 32) && ins->ifc == 0 && ins->ifr == 0 && ins->midi_channel_mask == 0 && ins->midi_program == -1 && ins->midi_bank == -1 ); } // IT-compatible: last order of "main song", or 0 int csf_last_order(song_t *csf) { int n = 0; while (n < MAX_ORDERS && csf->orderlist[n] != ORDER_LAST) n++; return n ? n - 1 : 0; } // Total count of orders in orderlist before end of data int csf_get_num_orders(song_t *csf) { int n = MAX_ORDERS; while (n >= 0 && csf->orderlist[--n] == ORDER_LAST) { } return n + 1; } // Total number of non-empty patterns in song, according to csf_pattern_is_empty int csf_get_num_patterns(song_t *csf) { int n = MAX_PATTERNS - 1; while (n && csf_pattern_is_empty(csf, n)) n--; return n+ 1; } int csf_get_num_samples(song_t *csf) { int n = MAX_SAMPLES - 1; while (n > 0 && csf_sample_is_empty(csf->samples + n)) n--; return n; } int csf_get_num_instruments(song_t *csf) { int n = MAX_INSTRUMENTS - 1; while (n > 0 && csf_instrument_is_empty(csf->instruments[n])) n--; return n; } int csf_first_blank_sample(song_t *csf, int start) { int n; for (n = MAX(start, 1); n < MAX_SAMPLES; n++) { if (csf_sample_is_empty(csf->samples + n)) return n; } return -1; } int csf_first_blank_instrument(song_t *csf, int start) { int n; for (n = MAX(start, 1); n < MAX_INSTRUMENTS; n++) { if (csf_instrument_is_empty(csf->instruments[n])) return n; } return -1; } ////////////////////////////////////////////////////////////////////////// // Misc functions midi_config_t default_midi_config; void csf_reset_midi_cfg(song_t *csf) { memcpy(&csf->midi_config, &default_midi_config, sizeof(default_midi_config)); } void csf_copy_midi_cfg(song_t *dest, song_t *src) { memcpy(&dest->midi_config, &src->midi_config, sizeof(midi_config_t)); } int csf_set_wave_config(song_t *csf, uint32_t rate,uint32_t bits,uint32_t channels) { int reset = ((csf->mix_frequency != rate) || (csf->mix_bits_per_sample != bits) || (csf->mix_channels != channels)); csf->mix_channels = channels; csf->mix_frequency = rate; csf->mix_bits_per_sample = bits; csf_init_player(csf, reset); return 1; } int csf_set_resampling_mode(song_t *csf, uint32_t mode) { uint32_t d = csf->mix_flags & ~(SNDMIX_NORESAMPLING|SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); switch(mode) { case SRCMODE_NEAREST: d |= SNDMIX_NORESAMPLING; break; case SRCMODE_LINEAR: break; case SRCMODE_SPLINE: d |= SNDMIX_HQRESAMPLER; break; case SRCMODE_POLYPHASE: d |= (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); break; default: return 0; } csf->mix_flags = d; return 1; } // This used to use some retarded positioning based on the total number of rows elapsed, which is useless. // However, the only code calling this function is in this file, to set it to the start, so I'm optimizing // out the row count. static void set_current_pos_0(song_t *csf) { song_voice_t *v = csf->voices; for (uint32_t i = 0; i < MAX_VOICES; i++, v++) { memset(v, 0, sizeof(*v)); v->cutoff = 0x7F; v->volume = 256; if (i < MAX_CHANNELS) { v->panning = csf->channels[i].panning; v->global_volume = csf->channels[i].volume; v->flags = csf->channels[i].flags; } else { v->panning = 128; v->global_volume = 64; } } csf->current_global_volume = csf->initial_global_volume; csf->current_speed = csf->initial_speed; csf->current_tempo = csf->initial_tempo; } void csf_set_current_order(song_t *csf, uint32_t position) { for (uint32_t j = 0; j < MAX_VOICES; j++) { song_voice_t *v = csf->voices + j; v->period = 0; v->note = v->new_note = v->new_instrument = 0; v->portamento_target = 0; v->n_command = 0; v->cd_patloop = 0; v->patloop_row = 0; v->cd_tremor = 0; // modplug sets vib pos to 16 in old effects mode for some reason *shrug* v->vibrato_position = (csf->flags & SONG_ITOLDEFFECTS) ? 0 : 0x10; v->tremolo_position = 0; } if (position > MAX_ORDERS) position = 0; if (!position) set_current_pos_0(csf); csf->process_order = position - 1; csf->process_row = PROCESS_NEXT_ORDER; csf->row = 0; csf->break_row = 0; /* set this to whatever row to jump to */ csf->tick_count = 1; csf->row_count = 0; csf->buffer_count = 0; csf->flags &= ~(SONG_PATTERNLOOP|SONG_ENDREACHED); } void csf_reset_playmarks(song_t *csf) { int n; for (n = 1; n < MAX_SAMPLES; n++) { csf->samples[n].played = 0; } for (n = 1; n < MAX_INSTRUMENTS; n++) { if (csf->instruments[n]) csf->instruments[n]->played = 0; } } /* --------------------------------------------------------------------------------------------------------- */ #define SF_FAIL(name, n) \ ({ log_appendf(4, "%s: internal error: unsupported %s %d", __FUNCTION__, name, n); return 0; }) uint32_t csf_read_sample(song_sample_t *sample, uint32_t flags, const void *filedata, uint32_t memsize) { uint32_t len = 0, mem; const char *buffer = (const char *) filedata; if (sample->flags & CHN_ADLIB) return 0; // no sample data if (!sample || sample->length < 1 || !buffer) return 0; // validate the read flags before anything else switch (flags & SF_BIT_MASK) { case SF_7: case SF_8: case SF_16: case SF_24: case SF_32: break; default: SF_FAIL("bit width", flags & SF_BIT_MASK); } switch (flags & SF_CHN_MASK) { case SF_M: case SF_SI: case SF_SS: break; default: SF_FAIL("channel mask", flags & SF_CHN_MASK); } switch (flags & SF_END_MASK) { case SF_LE: case SF_BE: break; default: SF_FAIL("endianness", flags & SF_END_MASK); } switch (flags & SF_ENC_MASK) { case SF_PCMS: case SF_PCMU: case SF_PCMD: case SF_IT214: case SF_IT215: case SF_AMS: case SF_DMF: case SF_MDL: case SF_PTM: break; default: SF_FAIL("encoding", flags & SF_ENC_MASK); } if ((flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)) != 0) { SF_FAIL("extra flag", flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)); } if (sample->length > MAX_SAMPLE_LENGTH) sample->length = MAX_SAMPLE_LENGTH; mem = sample->length+6; sample->flags &= ~(CHN_16BIT|CHN_STEREO); switch (flags & SF_BIT_MASK) { case SF_16: case SF_24: case SF_32: // these are all stuffed into 16 bits. mem *= 2; sample->flags |= CHN_16BIT; } switch (flags & SF_CHN_MASK) { case SF_SI: case SF_SS: mem *= 2; sample->flags |= CHN_STEREO; } if ((sample->data = csf_allocate_sample(mem)) == NULL) { sample->length = 0; return 0; } switch(flags) { // 1: 8-bit unsigned PCM data case RS_PCM8U: { len = sample->length; if (len > memsize) len = sample->length = memsize; signed char *data = sample->data; for (uint32_t j=0; jlength; if (len > memsize) break; signed char *data = sample->data; const signed char *p = (const signed char *)buffer; int delta = 0; for (uint32_t j=0; jlength * 2; if (len > memsize) break; short *data = (short *)sample->data; short *p = (short *)buffer; unsigned short tmp; int delta16 = 0; for (uint32_t j=0; jlength * 2; if (len <= memsize) memcpy(sample->data, buffer, len); short int *data = (short int *)sample->data; for (uint32_t j=0; jlength * 2; if (len > memsize) len = memsize & ~1; if (len > 1) { signed char *data = (signed char *)sample->data; signed char *src = (signed char *)buffer; for (uint32_t j=0; jlength * 2; if (len <= memsize) memcpy(sample->data, buffer, len); short int *data = (short int *)sample->data; for (uint32_t j=0; jlength * 2; if (len*2 <= memsize) { signed char *data = (signed char *)sample->data; signed char *src = (signed char *)buffer; for (uint32_t j=0; jlength; signed char *psrc = (signed char *)buffer; signed char *data = (signed char *)sample->data; if (len*2 > memsize) break; for (uint32_t j=0; jlength; short int *psrc = (short int *)buffer; short int *data = (short int *)sample->data; if (len*4 > memsize) break; for (uint32_t j=0; jdata, sample->length, buffer, memsize, (flags == RS_IT2158), 1); } else { it_decompress16(sample->data, sample->length, buffer, memsize, (flags == RS_IT21516), 1); } break; case RS_IT2148S: case RS_IT21416S: case RS_IT2158S: case RS_IT21516S: len = memsize; if (len < 4) break; if (flags == RS_IT2148S || flags == RS_IT2158S) { uint32_t offset = it_decompress8(sample->data, sample->length, buffer, memsize, (flags == RS_IT2158S), 2); it_decompress8(sample->data + 1, sample->length, buffer + offset, memsize - offset, (flags == RS_IT2158S), 2); } else { uint32_t offset = it_decompress16(sample->data, sample->length, buffer, memsize, (flags == RS_IT21516S), 2); it_decompress16(sample->data + 2, sample->length, buffer + offset, memsize - offset, (flags == RS_IT21516S), 2); } break; // 8-bit interleaved stereo samples case RS_STIPCM8S: case RS_STIPCM8U: { int iadd = 0; if (flags == RS_STIPCM8U) { iadd = -0x80; } len = sample->length; if (len*2 > memsize) len = memsize >> 1; uint8_t * psrc = (uint8_t *)buffer; uint8_t * data = (uint8_t *)sample->data; for (uint32_t j=0; jlength; if (len*4 > memsize) len = memsize >> 2; short int *psrc = (short int *)buffer; short int *data = (short int *)sample->data; for (uint32_t j=0; j 9) { const char *psrc = buffer; char packcharacter = buffer[8], *pdest = (char *)sample->data; len += bswapLE32(*((uint32_t *)(buffer+4))); if (len > memsize) len = memsize; uint32_t dmax = sample->length; if (sample->flags & CHN_16BIT) dmax <<= 1; AMSUnpack(psrc+9, len-9, pdest, dmax, packcharacter); } break; #endif // PTM 8bit delta to 16-bit sample case RS_PTM8DTO16: { len = sample->length * 2; if (len > memsize) break; signed char *data = (signed char *)sample->data; signed char delta8 = 0; for (uint32_t j=0; jdata; for (uint32_t j=0; j= 8) { // first 4 bytes indicate packed length len = bswapLE32(*((uint32_t *) buffer)); len = MIN(len, memsize) + 4; uint8_t * data = (uint8_t *)sample->data; uint8_t * ibuf = (uint8_t *)(buffer + 4); uint32_t bitbuf = bswapLE32(*((uint32_t *)ibuf)); uint32_t bitnum = 32; uint8_t dlt = 0, lowbyte = 0; ibuf += 4; // TODO move all this junk to fmt/compression.c for (uint32_t j=0; jlength; j++) { uint8_t hibyte; uint8_t sign; if (flags == RS_MDL16) lowbyte = (uint8_t)mdl_read_bits(&bitbuf, &bitnum, &ibuf, 8); sign = (uint8_t)mdl_read_bits(&bitbuf, &bitnum, &ibuf, 1); if (mdl_read_bits(&bitbuf, &bitnum, &ibuf, 1)) { hibyte = (uint8_t)mdl_read_bits(&bitbuf, &bitnum, &ibuf, 3); } else { hibyte = 8; while (!mdl_read_bits(&bitbuf, &bitnum, &ibuf, 1)) hibyte += 0x10; hibyte += mdl_read_bits(&bitbuf, &bitnum, &ibuf, 4); } if (sign) hibyte = ~hibyte; dlt += hibyte; if (flags == RS_MDL8) { data[j] = dlt; } else { #ifdef WORDS_BIGENDIAN data[j<<1] = dlt; data[(j<<1)+1] = lowbyte; #else data[j<<1] = lowbyte; data[(j<<1)+1] = dlt; #endif } } } break; #if 0 case RS_DMF8: case RS_DMF16: len = memsize; if (len >= 4) { uint32_t maxlen = sample->length; if (sample->flags & CHN_16BIT) maxlen <<= 1; uint8_t * ibuf = (uint8_t *)buffer; uint8_t * ibufmax = (uint8_t *)(buffer+memsize); len = DMFUnpack((uint8_t *)sample->data, ibuf, ibufmax, maxlen); } break; #endif // PCM 24-bit signed -> load sample, and normalize it to 16-bit case RS_PCM24S: case RS_PCM32S: len = sample->length * 3; if (flags == RS_PCM32S) len += sample->length; if (len > memsize) break; if (len > 4*8) { uint32_t slsize = (flags == RS_PCM32S) ? 4 : 3; uint8_t * src = (uint8_t *)buffer; int32_t max = 255; if (flags == RS_PCM32S) src++; for (uint32_t j=0; j max) max = l; if (-l > max) max = -l; } max = (max / 128) + 1; signed short *dest = (signed short *)sample->data; for (uint32_t k=0; k load sample, and normalize it to 16-bit case RS_STIPCM24S: case RS_STIPCM32S: len = sample->length * 6; if (flags == RS_STIPCM32S) len += sample->length * 2; if (len > memsize) break; if (len > 8*8) { uint32_t slsize = (flags == RS_STIPCM32S) ? 4 : 3; uint8_t * src = (uint8_t *)buffer; int32_t max = 255; if (flags == RS_STIPCM32S) src++; for (uint32_t j=0; j max) max = l; if (-l > max) max = -l; } max = (max / 128) + 1; signed short *dest = (signed short *)sample->data; for (uint32_t k=0; klength; if (len*4 > memsize) len = memsize >> 2; const uint8_t * psrc = (const uint8_t *)buffer; short int *data = (short int *)sample->data; for (uint32_t j=0; jflags &= ~(CHN_16BIT | CHN_STEREO); len = sample->length = MIN(sample->length, memsize); for (uint32_t j = 0; j < len; j++) sample->data[j] = CLAMP(buffer[j] * 2, -128, 127); break; // Default: 8-bit signed PCM data default: printf("DEFAULT: %d\n", flags); case SF(8,M,BE,PCMS): /* endianness is irrelevant for 8-bit samples */ case SF(8,M,LE,PCMS): sample->flags &= ~(CHN_16BIT | CHN_STEREO); len = sample->length; if (len > memsize) len = sample->length = memsize; memcpy(sample->data, buffer, len); break; } if (len > memsize) { if (sample->data) { sample->length = 0; csf_free_sample(sample->data); sample->data = NULL; } return 0; } csf_adjust_sample_loop(sample); return len; } /* --------------------------------------------------------------------------------------------------------- */ void csf_adjust_sample_loop(song_sample_t *sample) { if (!sample->data) return; if (sample->loop_end > sample->length) sample->loop_end = sample->length; if (sample->loop_start+2 >= sample->loop_end) { sample->loop_start = sample->loop_end = 0; sample->flags &= ~CHN_LOOP; } // poopy, removing all that loop-hacking code has produced... very nasty sounding loops! // so I guess I should rewrite the crap at the end of the sample at least. uint32_t len = sample->length; if (sample->flags & CHN_16BIT) { short int *data = (short int *)sample->data; // Adjust end of sample if (sample->flags & CHN_STEREO) { data[len*2+6] = data[len*2+4] = data[len*2+2] = data[len*2] = data[len*2-2]; data[len*2+7] = data[len*2+5] = data[len*2+3] = data[len*2+1] = data[len*2-1]; } else { data[len+4] = data[len+3] = data[len+2] = data[len+1] = data[len] = data[len-1]; } } else { signed char *data = sample->data; // Adjust end of sample if (sample->flags & CHN_STEREO) { data[len*2+6] = data[len*2+4] = data[len*2+2] = data[len*2] = data[len*2-2]; data[len*2+7] = data[len*2+5] = data[len*2+3] = data[len*2+1] = data[len*2-1]; } else { data[len+4] = data[len+3] = data[len+2] = data[len+1] = data[len] = data[len-1]; } } } void csf_import_s3m_effect(song_note_t *m, int from_it) { uint32_t effect = m->effect; uint32_t param = m->param; switch (effect + 0x40) { case 'A': effect = FX_SPEED; break; case 'B': effect = FX_POSITIONJUMP; break; case 'C': effect = FX_PATTERNBREAK; if (!from_it) param = (param >> 4) * 10 + (param & 0x0F); break; case 'D': effect = FX_VOLUMESLIDE; break; case 'E': effect = FX_PORTAMENTODOWN; break; case 'F': effect = FX_PORTAMENTOUP; break; case 'G': effect = FX_TONEPORTAMENTO; break; case 'H': effect = FX_VIBRATO; break; case 'I': effect = FX_TREMOR; break; case 'J': effect = FX_ARPEGGIO; break; case 'K': effect = FX_VIBRATOVOL; break; case 'L': effect = FX_TONEPORTAVOL; break; case 'M': effect = FX_CHANNELVOLUME; break; case 'N': effect = FX_CHANNELVOLSLIDE; break; case 'O': effect = FX_OFFSET; break; case 'P': effect = FX_PANNINGSLIDE; break; case 'Q': effect = FX_RETRIG; break; case 'R': effect = FX_TREMOLO; break; case 'S': effect = FX_SPECIAL; // convert old SAx to S8x if (!from_it && ((param & 0xf0) == 0xa0)) param = 0x80 | ((param & 0xf) ^ 8); break; case 'T': effect = FX_TEMPO; break; case 'U': effect = FX_FINEVIBRATO; break; case 'V': effect = FX_GLOBALVOLUME; if (!from_it) param *= 2; break; case 'W': effect = FX_GLOBALVOLSLIDE; break; case 'X': effect = FX_PANNING; if (!from_it) { if (param == 0xa4) { effect = FX_SPECIAL; param = 0x91; } else if (param > 0x7f) { param = 0xff; } else { param *= 2; } } break; case 'Y': effect = FX_PANBRELLO; break; case 'Z': effect = FX_MIDI; break; default: effect = 0; } m->effect = effect; m->param = param; }