shithub: itdec

ref: e8304ea686d212ad118f760dc9e6df5167027272
dir: itdec/it_music.c

View raw version
/*
Copyright (C) 2014, Jeffrey Lim. All Rights Reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

#include "it_struc.h"

// temporary shit to make this work --GM

it_engine *ITEngineNew(void)
{
	int i;

	it_engine *ite = calloc(sizeof(it_engine), 1);

	// TODO: initialise properly
	for(i = 0; i < 200; i++)
		ite->pat[i] = NULL;
	for(i = 0; i < 100; i++)
		ite->SamplePointer[i] = NULL;

	ite->NumChannels = 256;
	// shifting this here so it doesn't try to use a real unpacked pattern --GM
	ite->CurrentEditPattern = 199;
	ite->PatternNumber = 199;
	ite->CmdLineNumChannels = -1;

	return ite;
}

it_drvdata *drv_oss_init(it_engine *ite);
it_drvdata *drv_sdl_init(it_engine *ite);

it_drvdata *DriverSys_GetByName(it_engine *ite, const char *fname)
{
	printf("drv: %s\n", fname);

	if(!strcmp(fname, "oss"))
		return drv_oss_init(ite);
	if(!strcmp(fname, "sdl"))
		return drv_sdl_init(ite);

	return NULL;
}

it_pattern *PE_GetCurrentPattern(it_engine *ite, uint16_t *patnum, uint16_t *patrow)
{
	*patnum = ite->PatternNumber;
	*patrow = ite->MaxRow + 1;

	return NULL; // actually meant to return UNPACKED pattern data area --GM
}

uint16_t PE_GetLastInstrument(it_engine *ite)
{
	return ite->LastInstrument-1;
}

//
// Functions for playing control
//  Music_PlaySong........ parameters, AX = order
//  Music_Stop............ parameters, None
//  Music_PlayPattern..... parameters, AX = pattern, BX = number of rows, CX = row
//  Music_ToggleChannel... parameters, AX = channel
//  Music_SoloChannel..... parameters, AX = channel
//

const int8_t FineSineData[] = {
	  0,  2,  3,  5,  6,  8,  9, 11, 12, 14, 16, 17, 19, 20, 22, 23,
	 24, 26, 27, 29, 30, 32, 33, 34, 36, 37, 38, 39, 41, 42, 43, 44,
	 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59,
	 59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 64, 64,
	 64, 64, 64, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 60, 60,
	 59, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46,
	 45, 44, 43, 42, 41, 39, 38, 37, 36, 34, 33, 32, 30, 29, 27, 26,
	 24, 23, 22, 20, 19, 17, 16, 14, 12, 11,  9,  8,  6,  5,  3,  2,
	  0, -2, -3, -5, -6, -8, -9,-11,-12,-14,-16,-17,-19,-20,-22,-23,
	-24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42,-43,-44,
	-45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56,-56,-57,-58,-59,
	-59,-60,-60,-61,-61,-62,-62,-62,-63,-63,-63,-64,-64,-64,-64,-64,
	-64,-64,-64,-64,-64,-64,-63,-63,-63,-62,-62,-62,-61,-61,-60,-60,
	-59,-59,-58,-57,-56,-56,-55,-54,-53,-52,-51,-50,-49,-48,-47,-46,
	-45,-44,-43,-42,-41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26,
	-24,-23,-22,-20,-19,-17,-16,-14,-12,-11, -9, -8, -6, -5, -3, -2,
};

const int8_t FineRampDownData[] = {
	 64, 63, 63, 62, 62, 61, 61, 60, 60, 59, 59, 58, 58, 57, 57, 56,
	 56, 55, 55, 54, 54, 53, 53, 52, 52, 51, 51, 50, 50, 49, 49, 48,
	 48, 47, 47, 46, 46, 45, 45, 44, 44, 43, 43, 42, 42, 41, 41, 40,
	 40, 39, 39, 38, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 32,
	 32, 31, 31, 30, 30, 29, 29, 28, 28, 27, 27, 26, 26, 25, 25, 24,
	 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18, 17, 17, 16,
	 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11, 10, 10,  9,  9,  8,
	  8,  7,  7,  6,  6,  5,  5,  4,  4,  3,  3,  2,  2,  1,  1,  0,
	  0, -1, -1, -2, -2, -3, -3, -4, -4, -5, -5, -6, -6, -7, -7, -8,
	 -8, -9, -9,-10,-10,-11,-11,-12,-12,-13,-13,-14,-14,-15,-15,-16,
	-16,-17,-17,-18,-18,-19,-19,-20,-20,-21,-21,-22,-22,-23,-23,-24,
	-24,-25,-25,-26,-26,-27,-27,-28,-28,-29,-29,-30,-30,-31,-31,-32,
	-32,-33,-33,-34,-34,-35,-35,-36,-36,-37,-37,-38,-38,-39,-39,-40,
	-40,-41,-41,-42,-42,-43,-43,-44,-44,-45,-45,-46,-46,-47,-47,-48,
	-48,-49,-49,-50,-50,-51,-51,-52,-52,-53,-53,-54,-54,-55,-55,-56,
	-56,-57,-57,-58,-58,-59,-59,-60,-60,-61,-61,-62,-62,-63,-63,-64,
};

const int8_t FineSquareWave[] = {
	 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
};

const uint8_t EmptyPattern[] = {
	64, 0, 64, 0, 0, 0, 0, 0,

	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};

// Zero globals.

const float PitchDepthConstant               = 98304.0f;


// TODO: Turn these two into proper structs
uint8_t InstrumentHeader[] = {
        'I', 'M', 'P', 'I',
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	60, 128, 32+128,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0xFF, 0xFF, 0xFF,
        0, 0, 1, 0, 2, 0, 3, 0, 4, 0,
        5, 0, 6, 0, 7, 0, 8, 0, 9, 0,
        10, 0, 11, 0, 12, 0, 13, 0, 14, 0,
        15, 0, 16, 0, 17, 0, 18, 0, 19, 0,
        20, 0, 21, 0, 22, 0, 23, 0, 24, 0,
        25, 0, 26, 0, 27, 0, 28, 0, 29, 0,
        30, 0, 31, 0, 32, 0, 33, 0, 34, 0,
        35, 0, 36, 0, 37, 0, 38, 0, 39, 0,
        40, 0, 41, 0, 42, 0, 43, 0, 44, 0,
        45, 0, 46, 0, 47, 0, 48, 0, 49, 0,
        50, 0, 51, 0, 52, 0, 53, 0, 54, 0,
        55, 0, 56, 0, 57, 0, 58, 0, 59, 0,
        60, 0, 61, 0, 62, 0, 63, 0, 64, 0,
        65, 0, 66, 0, 67, 0, 68, 0, 69, 0,
        70, 0, 71, 0, 72, 0, 73, 0, 74, 0,
        75, 0, 76, 0, 77, 0, 78, 0, 79, 0,
        80, 0, 81, 0, 82, 0, 83, 0, 84, 0,
        85, 0, 86, 0, 87, 0, 88, 0, 89, 0,
        90, 0, 91, 0, 92, 0, 93, 0, 94, 0,
        95, 0, 96, 0, 97, 0, 98, 0, 99, 0,
        100, 0, 101, 0, 102, 0, 103, 0, 104, 0,
        105, 0, 106, 0, 107, 0, 108, 0, 109, 0,
        110, 0, 111, 0, 112, 0, 113, 0, 114, 0,
        115, 0, 116, 0, 117, 0, 118, 0, 119, 0,
        0, 2, 0, 0, 0, 0, 64, 0, 0, 64, 100, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 2, 0, 0, 0, 0,  0, 0, 0,  0, 100, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 2, 0, 0, 0, 0,  0, 0, 0,  0, 100, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0,
};

uint8_t SampleHeader[80] = {
	'I', 'M', 'P', 'S',
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	64, 0, 64,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	(8363&0xFF), (8363>>8), 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
};

uint8_t MIDIPitchSendString[] = {0x65, 0x00, 0x64, 0x00, 0x06};

const char *PrepareSamplesMsg        = "Preparing Samples";
const char *ReverseMsg               = "Left/right outputs reversed";
const char *NoSoundCardMsg           = "   No sound card detected";
const char *MIDIConfigFileName       = "ITMIDI.CFG";

const char *OrderUpdateEnabledMsg    = "Order list unlocked";
const char *OrderUpdateDisabledMsg   = "Order list locked";

const char *UnsoloMsg                = "Solo disabled";
const char *SoloSampleMsg            = "Solo sample \375D";
const char *SoloInstrumentMsg        = "Solo instrument \375D";

#ifdef DEBUG

const char *LoadDriverMessage        = "Loading driver:";
const char *UnableToReadFileMessage  = "Unable to read file";
const char *DetectingMessage         = "Testing driver";
//uint16_t    ScreenOffset             = 0;

#endif

const char *PleaseWaitMsg            = "Please Wait...";

const char *PCSpeakerDriver         = "ITPCSPKR.DRV";
const char *SBDriver                = "ITSB.DRV";
const char *SB2Driver               = "ITSB2.DRV";
const char *SBProDriver             = "ITSBPRO.DRV";
const char *SB16Driver              = "ITSB16.DRV";
const char *AWE32Driver             = "ITAWE32.DRV";
const char *GUSDriver               = "ITGUS.DRV";
const char *InterwaveDriver         = "ITIW.DRV";
const char *PASDriver               = "ITPAS.DRV";
const char *PAS16Driver             = "ITPAS16.DRV";
const char *WSSDriver               = "ITWSS.DRV";
const char *ESSDriver               = "ITES1868.DRV";
const char *MIDIDriver              = "ITMPU401.DRV";
const char *EWSCodecDriver          = "ITEWSCOD.DRV";
const char *VIVOCodecDriver         = "ITVIVO.DRV";
const char *ST97PCICodecDriver      = "ITSTCODE.DRV";
const char *WAVDriver               = "ITWAV.DRV";
const char *MIDDriver               = "ITMID.DRV";
const char *VSoundMMXDriver         = "ITVSOUND.MMX";
const char *VSoundDriver            = "ITVSOUND.DRV";
const char *DefaultDriver           = "ITSOUND.DRV";

#if 0
const char *DriverNameTable[] = {
	NULL,
	"ITPAS16.DRV", "ITSB16.DRV",
	"ITIW.DRV", "ITGUS.DRV",
	"ITAWE32.DRV", "ITSBPRO.DRV",
	"ITSB.DRV", "ITPCSPKR.DRV",
	"ITSB2.DRV", "ITPAS.DRV",
	"ITWAV.DRV", "ITWSS.DRV",
	"ITES1868.DRV", "ITMPU401.DRV",
	"ITEWSCOD.DRV", "ITVIVO.DRV",
	"ITSTCODE.DRV", "ITMID.DRV",
	"ITSOUND.DRV", "ITVSOUND.MMX",
	"ITVSOUND.DRV",
	/*
        PAS16Driver, SB16Driver,
        InterwaveDriver, GUSDriver,
        AWE32Driver, SBProDriver,
        SBDriver, PCSpeakerDriver,
        SB2Driver, PASDriver,
        WAVDriver, WSSDriver,
        ESSDriver, MIDIDriver,
        EWSCodecDriver, VIVOCodecDriver,
        ST97PCICodecDriver, MIDDriver,
        DefaultDriver, VSoundMMXDriver,
        VSoundDriver,
	*/
};

const uint16_t DriverDetectionOrder[] = {
	19, 20, 21, 17, 16, 15, 13, 1, 10, 2, 12, 3, 4, 5, 6, 9, 7, 8, 0xFFFF,
};

const uint16_t DriverSoundCard[] = {
	0, 8, 7, 9, 6,
	2, 5, 4, 3, 10,
	1, 12, 13, 15, 16,
	0, 0, 0, 0, 14,
	11, 18,
	0xFFFF,
};
#else
const char *DriverNameTable[] = {
	NULL,
	"oss",
	"sdl", // (DONE) I hope to get SDL audio working at some point.
	// Possibly also a winmm driver for Windows.
	// JACK will definitely be there, too! Will use ezjack for that.
};

const uint16_t DriverDetectionOrder[] = {
	2, 1, 0, 0xFFFF,
};

const uint16_t DriverSoundCard[] = {
	0, 0, 1,
	0xFFFF,
};
#endif

// TODO: translate this properly!
const uint32_t PitchTable[] = {
2048+((0)<<16), 2170+((0)<<16), 2299+((0)<<16), 2435+((0)<<16), 2580+((0)<<16), 2734+((0)<<16),
2896+((0)<<16), 3069+((0)<<16), 3251+((0)<<16), 3444+((0)<<16), 3649+((0)<<16), 3866+((0)<<16),

4096+((0)<<16), 4340+((0)<<16), 4598+((0)<<16), 4871+((0)<<16), 5161+((0)<<16), 5468+((0)<<16),
5793+((0)<<16), 6137+((0)<<16), 6502+((0)<<16), 6889+((0)<<16), 7298+((0)<<16), 7732+((0)<<16),

8192+((0)<<16), 8679+((0)<<16), 9195+((0)<<16), 9742+((0)<<16), 10321+((0)<<16), 10935+((0)<<16),
11585+((0)<<16), 12274+((0)<<16), 13004+((0)<<16), 13777+((0)<<16), 14596+((0)<<16), 15464+((0)<<16),

16384+((0)<<16), 17358+((0)<<16), 18390+((0)<<16), 19484+((0)<<16), 20643+((0)<<16), 21870+((0)<<16),
23170+((0)<<16), 24548+((0)<<16), 26008+((0)<<16), 27554+((0)<<16), 29193+((0)<<16), 30929+((0)<<16),

32768+((0)<<16), 34716+((0)<<16), 36781+((0)<<16), 38968+((0)<<16), 41285+((0)<<16), 43740+((0)<<16),
46341+((0)<<16), 49097+((0)<<16), 52016+((0)<<16), 55109+((0)<<16), 58386+((0)<<16), 61858+((0)<<16),

0+((1)<<16), 3897+((1)<<16), 8026+((1)<<16), 12400+((1)<<16), 17034+((1)<<16), 21944+((1)<<16),
27146+((1)<<16), 32657+((1)<<16), 38496+((1)<<16), 44682+((1)<<16), 51236+((1)<<16), 58179+((1)<<16),

0+((2)<<16), 7794+((2)<<16), 16051+((2)<<16), 24800+((2)<<16), 34068+((2)<<16), 43888+((2)<<16),
54292+((2)<<16), 65314+((2)<<16), 11456+((3)<<16), 23828+((3)<<16), 36936+((3)<<16), 50823+((3)<<16),

0+((4)<<16), 15588+((4)<<16), 32103+((4)<<16), 49600+((4)<<16), 2601+((5)<<16), 22240+((5)<<16),
43048+((5)<<16), 65092+((5)<<16), 22912+((6)<<16), 47656+((6)<<16), 8336+((7)<<16), 36110+((7)<<16),

0+((8)<<16), 31176+((8)<<16), 64205+((8)<<16), 33663+((9)<<16), 5201+((10)<<16), 44481+((10)<<16),
20559+((11)<<16), 64648+((11)<<16), 45823+((12)<<16), 29776+((13)<<16), 16671+((14)<<16), 6684+((15)<<16),

0+((16)<<16), 62352+((16)<<16), 62875+((17)<<16), 1790+((19)<<16), 10403+((20)<<16), 23425+((21)<<16),
41118+((22)<<16), 63761+((23)<<16), 26111+((25)<<16), 59552+((26)<<16), 33342+((28)<<16), 13368+((30)<<16),

// Pitch extention for loading some XIs,

0+((32)<<16), 59167+((33)<<16), 60214+((35)<<16), 3580+((38)<<16), 20806+((40)<<16), 46850+((42)<<16),
16701+((45)<<16), 61986+((47)<<16), 52221+((50)<<16), 53567+((53)<<16), 1148+((57)<<16), 26736+((60)<<16),
};

#if USEFPUCODE

uint8_t FPSave[128]; // note, init this to 0

const float Const_14317456 = 14317456.0;
const float Const1_On_768 = 1.0/768.0; //Const1_On_768   DD      3AAAAAABh
uint16_t SlideValue = 0;
uint16_t NewControlWord = 0x7F;

#else

const uint16_t FineLinearSlideUpTable16[] = {
	0, 1,     59, 1,    118, 1,   178, 1,   237, 1,    // 0->4
	296, 1,   356, 1,   415, 1,   475, 1,   535, 1,    // 5->9
	594, 1,   654, 1,   714, 1,   773, 1,   833, 1,    // 10->14
	893, 1,                                            // 15
};
const uint32_t *FineLinearSlideUpTable = (const uint32_t *)FineLinearSlideUpTable16;

const uint16_t LinearSlideUpTable16[] = { // Value = 2^(Val/192)
	0,     1, 237,   1, 475,   1, 714,   1, 953,  1,  // 0->4
	1194,  1, 1435,  1, 1677,  1, 1920,  1, 2164, 1,  // 5->9
	2409,  1, 2655,  1, 2902,  1, 3149,  1, 3397, 1,  // 10->14
	3647,  1, 3897,  1, 4148,  1, 4400,  1, 4653, 1,  // 15->19
	4907,  1, 5157,  1, 5417,  1, 5674,  1, 5932, 1,  // 20->24
	6190,  1, 6449,  1, 6710,  1, 6971,  1, 7233, 1,  // 25->29
	7496,  1, 7761,  1, 8026,  1, 8292,  1, 8559, 1,  // 30->34
	8027,  1, 9096,  1, 9366,  1, 9636,  1, 9908, 1,  // 35->39
	10181, 1, 10455, 1, 10730, 1, 11006, 1, 11283,1,  // 40->44
	11560, 1, 11839, 1, 12119, 1, 12400, 1, 12682,1,  // 45->49
	12965, 1, 13249, 1, 13533, 1, 13819, 1, 14106,1,  // 50->54
	14394, 1, 14684, 1, 14974, 1, 15265, 1, 15557,1,  // 55->59
	15850, 1, 16145, 1, 16440, 1, 16737, 1, 17034,1,  // 60->64
	17333, 1, 17633, 1, 17933, 1, 18235, 1, 18538,1,  // 65->69
	18842, 1, 19147, 1, 19454, 1, 19761, 1, 20070,1,  // 70->74
	20379, 1, 20690, 1, 21002, 1, 21315, 1, 21629,1,  // 75->79
	21944, 1, 22260, 1, 22578, 1, 22897, 1, 23216,1,  // 80->84
	23537, 1, 23860, 1, 24183, 1, 24507, 1, 24833,1,  // 85->89
	25160, 1, 25488, 1, 25817, 1, 26148, 1, 26479,1,  // 90->94
	26812, 1, 27146, 1, 27481, 1, 27818, 1, 28155,1,  // 95->99
	28494, 1, 28834, 1, 29175, 1, 29518, 1, 29862,1,  // 100->104
	30207, 1, 30553, 1, 30900, 1, 31248, 1, 31599,1,  // 105->109
	31951, 1, 32303, 1, 32657, 1, 33012, 1, 33369,1,  // 110->114
	33726, 1, 34085, 1, 34446, 1, 34807, 1, 35170,1,  // 115->119
	35534, 1, 35900, 1, 36267, 1, 36635, 1, 37004,1,  // 120->124
	37375, 1, 37747, 1, 38121, 1, 38496, 1, 38872,1,  // 125->129
	39250, 1, 39629, 1, 40009, 1, 40391, 1, 40774,1,  // 130->134
	41158, 1, 41544, 1, 41932, 1, 42320, 1, 42710,1,  // 135->139
	43102, 1, 43495, 1, 43889, 1, 44285, 1, 44682,1,  // 140->144
	45081, 1, 45481, 1, 45882, 1, 46285, 1, 46690,1,  // 145->149
	47095, 1, 47503, 1, 47917, 1, 48322, 1, 48734,1,  // 150->154
	49147, 1, 49562, 1, 49978, 1, 50396, 1, 50815,1,  // 155->159
	51236, 1, 51658, 1, 52082, 1, 52507, 1, 52934,1,  // 160->164
	53363, 1, 53793, 1, 54224, 1, 54658, 1, 55092,1,  // 165->169
	55529, 1, 55966, 1, 56406, 1, 56847, 1, 57289,1,  // 170->174
	57734, 1, 58179, 1, 58627, 1, 59076, 1, 59527,1,  // 175->179
	59979, 1, 60433, 1, 60889, 1, 61346, 1, 61805,1,  // 180->184
	62265, 1, 62727, 1, 63191, 1, 63657, 1, 64124,1,  // 185->189
	64593, 1, 65064, 1, 0,     2, 474,   2, 950,  2,  // 190->194
	1427,  2, 1906,  2, 2387,  2, 2870,  2, 3355, 2,  // 195->199
	3841,  2, 4327,  2, 4818,  2, 5310,  2, 5803, 2,  // 200->204
	6298,  2, 6795,  2, 7294,  2, 7794,  2, 8296, 2,  // 205->209
	8800,  2, 9306,  2, 9814,  2, 10323, 2, 10835,2,  // 210->214
	11348, 2, 11863, 2, 12380, 2, 12899, 2, 13419,2,  // 215->219
	13942, 2, 14467, 2, 14993, 2, 15521, 2, 16051,2,  // 220->224
	16583, 2, 17117, 2, 17653, 2, 18191, 2, 18731,2,  // 225->229
	19273, 2, 19817, 2, 20362, 2, 20910, 2, 21460,2,  // 230->234
	22011, 2, 22565, 2, 23121, 2, 23678, 2, 24238,2,  // 235->239
	24800, 2, 25363, 2, 25929, 2, 25497, 2, 27067,2,  // 240->244
	27639, 2, 28213, 2, 28789, 2, 29367, 2, 29947,2,  // 245->249
	30530, 2, 31114, 2, 31701, 2, 32289, 2, 32880,2,  // 250->254
	33473, 2, 34068, 2,                               // 255->256
};
const uint32_t *LinearSlideUpTable = (const uint32_t *)LinearSlideUpTable16;

const uint16_t FineLinearSlideDownTable[] = {
	65535, 65477, 65418, 65359, 65300, 65241, 65182, 65359, // 0->7
	65065, 65006, 64947, 64888, 64830, 64772, 64713, 64645, // 8->15
};

const uint16_t LinearSlideDownTable[] = {
	65535, 65300, 65065, 64830, 64596, 64364, 64132, 63901, // 0->7
	63670, 63441, 63212, 62984, 62757, 62531, 62306, 62081, // 8->15
	61858, 61635, 61413, 61191, 60971, 60751, 60532, 60314, // 16->23
	60097, 59880, 59664, 59449, 59235, 59022, 58809, 58597, // 24->31
	58386, 58176, 57966, 57757, 57549, 57341, 57135, 56929, // 32->39
	56724, 56519, 56316, 56113, 55911, 55709, 55508, 55308, // 40->47
	55109, 54910, 54713, 54515, 54319, 54123, 53928, 53734, // 48->55
	53540, 53347, 53155, 52963, 52773, 52582, 52393, 52204, // 56->63
	52016, 51829, 51642, 51456, 51270, 51085, 50901, 50718, // 64->71
	50535, 50353, 50172, 49991, 49811, 49631, 49452, 49274, // 72->79
	49097, 48920, 48743, 48568, 48393, 48128, 48044, 47871, // 80->87
	47699, 47527, 47356, 47185, 47015, 46846, 46677, 46509, // 88->95
	46341, 46174, 46008, 45842, 45677, 45512, 45348, 45185, // 96->103
	45022, 44859, 44698, 44537, 44376, 44216, 44057, 43898, //104->111
	43740, 43582, 43425, 43269, 43113, 42958, 42803, 42649, //112->119
	42495, 42342, 42189, 42037, 41886, 41735, 41584, 41434, //120->127
	41285, 41136, 40988, 40840, 40639, 40566, 40400, 40253, //128->135
	40110, 39965, 39821, 39678, 39535, 39392, 39250, 39109, //136->143
	38968, 38828, 38688, 38548, 38409, 38271, 38133, 37996, //144->151
	37859, 37722, 37586, 37451, 37316, 37181, 37047, 36914, //152->159
	36781, 36648, 36516, 36385, 36254, 36123, 35993, 35863, //160->167
	35734, 35605, 35477, 35349, 35221, 35095, 34968, 34842, //168->175
	34716, 34591, 34467, 34343, 34219, 34095, 33973, 33850, //176->183
	33728, 33607, 33486, 33365, 33245, 33125, 33005, 32887, //184->191
	32768, 32650, 32532, 32415, 32298, 32182, 32066, 31950, //192->199
	31835, 31720, 31606, 31492, 31379, 31266, 31153, 31041, //200->207
	30929, 30817, 30706, 30596, 30485, 30376, 30226, 30157, //208->215
	30048, 29940, 29832, 29725, 29618, 29511, 29405, 29299, //216->223
	29193, 29088, 28983, 28879, 28774, 28671, 28567, 28464, //224->231
	28362, 28260, 28158, 28056, 27955, 27855, 27754, 27654, //232->239
	27554, 27455, 27356, 27258, 27159, 27062, 26964, 26867, //240->247
	26770, 26674, 26577, 26482, 26386, 26291, 26196, 26102, //248->255
	26008,                                                  // 256
};

#endif /* USEFPUCODE */

void (*InitCommandTable[])(it_engine *ite, it_host *chn) = {
	InitNoCommand, InitCommandA,
	InitCommandB, InitCommandC,
	InitCommandD, InitCommandE,
	InitCommandF, InitCommandG,
	InitCommandH, InitCommandI,
	InitCommandJ, InitCommandK,
	InitCommandL, InitCommandM,
	InitCommandN, InitCommandO,
	InitCommandP, InitCommandQ,
	InitCommandR, InitCommandS,
	InitCommandT, InitCommandU,
	InitCommandV, InitCommandW,
	InitCommandX, InitCommandY,
	InitCommandZ, InitNoCommand,
	InitNoCommand, InitNoCommand,
	InitNoCommand, InitNoCommand,
};

void (*CommandTable[])(it_engine *ite, it_host *chn) = {
	NoCommand, NoCommand,
	NoCommand, NoCommand,
	CommandD, CommandE,
	CommandF, CommandG,
	CommandH, CommandI,
	CommandJ, CommandK,
	CommandL, NoCommand,
	CommandN, NoCommand,
	CommandP, CommandQ,
	CommandR, CommandS,
	CommandT, CommandH,
	NoCommand, CommandW,
	NoCommand, CommandY,
	NoCommand, NoCommand,
	NoCommand, NoCommand,
};

void (*VolumeEffectTable[])(it_engine *ite, it_host *chn) = {
	NoCommand, NoCommand, // Last 2 of command table + VolumeComA and VolumeComB
	VolumeCommandC, VolumeCommandD,
	VolumeCommandE, VolumeCommandF,
	VolumeCommandG, CommandH,
};


/*
RetrigOffsets           Label
CommandQ_0, CommandQ_1, CommandQ_2, CommandQ_3,
CommandQ_4, CommandQ_5, CommandQ_6, CommandQ_7,
CommandQ_8, CommandQ_9, CommandQ_A, CommandQ_B,
CommandQ_C, CommandQ_D, CommandQ_E, CommandQ_F,
*/

//
// Sound Driver Data
//

// TODO: Replace this with a dlopen()/LoadLibrary() system

#if OLDDRIVER
const char *DriverID = "Impulse Tracker Sound Driver";
#else
const char *DriverID = "Impulse Tracker Advanced Sound Driver";
#endif

// *******************

//
// Functions
//

// Command/Effect (call it what you like) information here!!
//
//  For initialisation, DS:DI points to host channel data.
//    Registers for use: All except DS:DI & ES (points to SongDataSegment)
//
//  For update, DS:DI points to host channel data.
//    Registers for use: AX, BX, DX, ES, SI

void RecalculateAllVolumes(it_engine *ite)
{
	int i;

	for(i = 0; i < ite->NumChannels; i++)
	{
		it_slave *slave = &ite->slave[i];
		slave->Flags |= 18;
	}
}

void InitPlayInstrument(it_engine *ite, it_host *chn, it_slave *slave, int bx) // BX = instrument offset
{
	slave->InsOffs = bx; // InsOffset
	it_instrument *ins = &ite->ins[bx-1];

	slave->NNA = ins->NNA;
	slave->DCT = ins->DCT;
	slave->DCA = ins->DCA;

	// MCh and MPr
	if(chn->MCh != 0)
	{
		slave->MCh = chn->MCh;
		slave->MPr = chn->MPr;
		slave->FCut = (ins->MIDIBnk & 0xFF);
		slave->FRes = (ins->MIDIBnk >> 8);
		slave->LpD = chn->Nte; // For MIDI, LpD = Pattern note
	}

	slave->CVl = chn->CV;
	uint8_t dl = chn->CP;

	if((ins->DfP & 0x80) == 0)
		dl = ins->DfP;

	// Check for sample pan
	if(chn->Smp != 0)
	{
		it_sample *smp = &ite->smp[chn->Smp-1];

		if((smp->DfP & 0x80) != 0)
			dl = smp->DfP & 0x7F;
	}

	int32_t ax = dl;
	if(dl != 100)
	{
		// TODO: Verify this part
		ax = (int32_t)chn->Nte - (int32_t)ins->PPC;
		ax *= (int32_t)ins->PPS;
		ax >>= 3;
		ax += dl;

		if(ax < 0)
			ax = 0;
		else if(ax > 64)
			ax = 64;
	}

	// Write panning
	slave->Pan = slave->PS = ax;

	// Envelope init
	slave->V.EnvelopeValue = 0x400000; // 64*65536
	slave->V.EnvPos = 0;
	slave->V.CurEnN = 0;
	slave->V.NextET = 0;
	slave->P.EnvelopeValue = 0;
	slave->P.EnvPos = 0;
	slave->P.CurEnN = 0;
	slave->P.NextET = 0;
	slave->Pt.EnvelopeValue = 0;
	slave->Pt.EnvPos = 0;
	slave->Pt.CurEnN = 0;
	slave->Pt.NextET = 0;

	uint16_t etrigger = 0;
	etrigger |= ((ins->PitchEnv.Flg&1)<<14);
	etrigger |= ((ins->PanEnv.Flg&1)<<13);
	etrigger |= ((ins->VolEnv.Flg&1)<<12);
	etrigger |= 0x133;
	slave->Flags = etrigger;

	uint16_t lsc = ite->LastSlaveChannel;
	if(lsc != 0)
	{
		//printf("last slave channel %i\n", lsc);
		it_slave *lslave = &ite->slave[lsc-1];

		if((ins->VolEnv.Flg & 9) == 9)
		{
			// Transfer volume data
			slave->V.EnvelopeValue = lslave->V.EnvelopeValue;
			slave->V.EnvelopeDelta = lslave->V.EnvelopeDelta;
			slave->V.EnvPos = lslave->V.EnvPos;
			slave->V.CurEnN = lslave->V.CurEnN;
			slave->V.NextET = lslave->V.NextET;
		}

		if((ins->PanEnv.Flg & 9) == 9)
		{
			// Transfer pan data
			slave->P.EnvelopeValue = lslave->P.EnvelopeValue;
			slave->P.EnvelopeDelta = lslave->P.EnvelopeDelta;
			slave->P.EnvPos = lslave->P.EnvPos;
			slave->P.CurEnN = lslave->P.CurEnN;
			slave->P.NextET = lslave->P.NextET;
		}

		if((ins->PitchEnv.Flg & 9) == 9)
		{
			// Transfer pitch data
			slave->Pt.EnvelopeValue = lslave->Pt.EnvelopeValue;
			slave->Pt.EnvelopeDelta = lslave->Pt.EnvelopeDelta;
			slave->Pt.EnvPos = lslave->Pt.EnvPos;
			slave->Pt.CurEnN = lslave->Pt.CurEnN;
			slave->Pt.NextET = lslave->Pt.NextET;
		}
	}

	// Apply random volume/pan
	chn->Flags |= 0x80;

	if(chn->MCh != 0)
		return;

	slave->FCut = 0xFF;
	slave->FRes = 0x00;

	// If IFC bit 7 == 1, then set filter cutoff
	if((ins->IFC & 0x80) != 0)
	{
		// slave->FCut = (ins->IFC & 0x7F);
		SetFilterCutoff(ite, slave, ins->IFC & 0x7F);
	}

	// If IFR bit 7 == 1, then set filter resonance
	if((ins->IFR & 0x80) != 0)
	{
		slave->FRes = (ins->IFR & 0x7F);
		SetFilterResonance(ite, slave, slave->FRes & 0x7F);
	}
}

void ApplyRandomValues(it_engine *ite, it_host *chn)
{
	it_slave *slave = &ite->slave[chn->SCOffst];
	it_instrument *ins = &ite->ins[slave->InsOffs-1];

	chn->Flags &= ~0x80;

	int8_t al = Random(ite); // AL = -128->+127

	if(ins->RV != 0) // Random volume, 0->100
	{
		int32_t ax = ((int32_t)al) * (int32_t)(int8_t)ins->RV; // AX = -12800->12700
		ax >>= 6; // AX = -200->+198(.4)
		ax++; // AX = -199->+199

		int32_t dx = (int32_t)(uint32_t)(uint8_t)slave->SVl; // Sample volume set
		ax *= dx; // AX = -199*128->199*128, -25472->25472
		ax /= 199; // AX = -128->+128
		ax += slave->SVl;

		if(ax < 0)
			slave->SVl = 0;
		else if(ax > 128)
			slave->SVl = 128;
		else
			slave->SVl = ax;
	}

	al = Random(ite);

	if(ins->RP != 0) // Random pan, 0->64
	{
		int32_t ax = ((int32_t)al) * (int32_t)(int8_t)ins->RP;
		// AX = -64*128->64*127, -8192->8128
		ax >>= 7;

		if(slave->Pan == 100)
			return;

		ax += slave->Pan;
		if(ax < 0)
			slave->Pan = slave->PS = 0;
		else if(ax > 64)
			slave->Pan = slave->PS = 64;
		else
			slave->Pan = slave->PS = ax;
	}
}

void MIDISendFilter(it_engine *ite, it_host *chn, uint8_t al)
{
	USED(chn);

	if((ite->d.DriverFlags & 1) == 0)
		return;

	if((al & 0x80) != 0 && (al < 0xF0))
	{
		if(al == ite->LastMIDIByte)
			return;

		ite->LastMIDIByte = al;
	}

	ite->d.DriverMIDIOut(ite, al);
}

void SetFilterCutoff(it_engine *ite, it_slave *slave, uint8_t bl)
{
	// Given BL = filtervalue.
	// Assumes that channel is non-disowned

	it_host *chn = &ite->chn[slave->HCOffst];

	MIDISendFilter(ite, chn, 0xF0);
	MIDISendFilter(ite, chn, 0xF0);
	MIDISendFilter(ite, chn, 0x00);
	MIDISendFilter(ite, chn, bl);
}

void SetFilterResonance(it_engine *ite, it_slave *slave, uint8_t bl)
{
	// Given BL = filtervalue.
	// Assumes that channel is non-disowned

	it_host *chn = &ite->chn[slave->HCOffst];

	MIDISendFilter(ite, chn, 0xF0);
	MIDISendFilter(ite, chn, 0xF0);
	MIDISendFilter(ite, chn, 0x01);
	MIDISendFilter(ite, chn, bl);
}

void MIDITranslate(it_engine *ite, it_host *chn, it_slave *slave, uint16_t bx)
{
	int i;

	// Assumes DS:SI points to slave
	// And DS:DI points to host.
	// BX = offset of MIDI command to interpret

	if((ite->d.DriverFlags & 1) == 0)
		return;

	if(bx >= 0xF000)
	{
		// Internal MIDI commands.

		if((ite->hdr.Flags & 64) == 0)
			return;

		// Pitch wheel
		// Formula is: Depth = 16384*12 / PitchWheelDepth * log2(Freq / OldFreq)
		// Do calculation, check whether pitch needs to be sent.

		int32_t pwd = (uint8_t)ite->hdr.PWD;
		if(pwd == 0) // No depth!
			return;

		float st0 = PitchDepthConstant / (float)pwd;
		USED(st0);

		// Current pitch / Original pitch
		float st1 = ((float)slave->Frequency) / (float)(slave->RVol_MIDIFSet);
		pwd *= (int32_t)log2f(st1);

		// OK.. [ChannelCountTable] contains pitch depth.
		// Have to check:
		//  a) Within ranges?
		//  b) Comparison to old pitch for this channel

		uint16_t ax = 1;
		uint8_t cl = slave->MCh-1;
		if((cl & 0x80) != 0)
			return;

		if(pwd != 0)
		{
			ax <<= (uint16_t)cl;
			if((ite->MIDIPitchDepthSent & ax) == 0)
			{
				ite->MIDIPitchDepthSent |= ax;

				// Send MIDI Pitch depth stuff
				MIDISendFilter(ite, chn, 0xB0 | cl);

				for(i = 0; i < 5; i++)
					MIDISendFilter(ite, chn, MIDIPitchSendString[i]);

				MIDISendFilter(ite, chn, ite->hdr.PWD);
			}
		}

		pwd += 0x2000;

		if((pwd & 0x8000) != 0)
			pwd = 0;
		if(pwd >= 0x4000)
			pwd = 0x3FFF;

		if(ite->MIDIPitch[slave->MCh-1] == pwd)
			return;

		ite->MIDIPitch[slave->MCh-1] = pwd;

		// Output pitch change

		MIDISendFilter(ite, chn, 0xE0 | cl); // Ec command
		MIDISendFilter(ite, chn, pwd & 0x7F);
		MIDISendFilter(ite, chn, (pwd>>7) & 0x7F);

		return;
	}

	// Now for user input MIDI stuff.

	// not a big priority right now --GM
#if 0
MIDITranslateParameterised:
	Push    FS

	Xor     AX, AX
	Xor     CX, CX
	Mov     FS, MIDIDataArea

MIDITranslate1:
	Mov     AH, [FS:BX]
	Inc     BX
	Test    AH, AH
	JZ      MIDITranslate2

	Cmp     AH, ' '                 ; Interpretation time.
	JNE     MIDITranslateNoSpace

	Test    CX, CX
	JZ      MIDITranslate1
	Jmp     MIDITranslateSend

MIDITranslateNoSpace:
	Sub     AH, '0'
	JC      MIDITranslate1
	Cmp     AH, 9
	JA      MIDITranslateValue1

	ShL     AL, 4
	Or      AL, AH
	Inc     CX
	Jmp     MIDITranslateValueEnd

MIDITranslateValue1:
	Sub     AH, 'A'-'0'
	JC      MIDITranslate1
	Cmp     AH, 'F'-'A'
	JA      MIDITranslateValue2

	ShL     AL, 4
	Add     AH, 10
	Or      AL, AH
	Inc     CX
	Jmp     MIDITranslateValueEnd

MIDITranslateValue2:
	Sub     AH, 'a'-'A'
	JC      MIDITranslate1
	Cmp     AH, 'z'-'a'
	JA      MIDITranslate1

	Cmp     AH, 'c'-'a'
	JNE     MIDITranslateValue3

	Test    SI, SI
	JZ      MIDITranslate1

;                Mov     AH, [DI+0Ch]
	Mov     AH, [SI+3Ch]
	ShL     AL, 4
	Dec     AH
	Or      AL, AH
	Inc     CX
	Jmp     MIDITranslateValueEnd

MIDITranslateValue3:
	Test    CX, CX
	JZ      MIDITranslateValue4

	Call    MIDISendFilter

	Xor     CX, CX

MIDITranslateValue4:
	Mov     AL, [DI+7]      ; Effect.
	Cmp     AH, 'z'-'a'     ; Zxx?
	JE      MIDITranslateSend

	Mov     AL, [DI+12h]
	Cmp     AH, 'o'-'a'
	JE      MIDITranslateSend

	Test    SI, SI
	JZ      MIDITranslate1

	Mov     AL, [SI+32h]    ; [DI+0Eh]
	Cmp     AH, 'n'-'a'     ; Note?
	JE      MIDITranslateSend

	Mov     AL, [SI+0Bh]
	Cmp     AH, 'm'-'a'
	JE      MIDITranslateSend

	Cmp     AH, 'v'-'a'     ; Velocity?
	JNE     MIDITranslateValue7

	Xor     AL, AL
	Test    Word Ptr [SI], 800h
	JNZ     MIDITranslateSend

	Mov     AL, [SI+22h]            ; 0->2^6
	Xor     DX, DX
	Mul     GlobalVolume            ; 0->2^13
	Mov     DL, [SI+23h]            ; Channel volume
	Mul     DX                      ; 0->2^19
	SHRD    AX, DX, 4               ; 0->2^15
	Mov     DL, [SI+24h]            ; Sample & Instrument Volume
	Mul     DX                      ; 0->2^22
	SHRD    AX, DX, 15              ; 0->2^7
	Sub     AL, 1
	AdC     AL, 1                   ; 1->2^7
	JNS     MIDITranslateSend
	Dec     AX
;                Mov     AL, 7Fh
	Jmp     MIDITranslateSend

Comment ~
	Mov     AL, [SI+22h]    ; 0->64
	Add     AL, AL          ; 0->128
	Sub     AL, 1
	AdC     AL, 1           ; 1->128
	Cmp     AL, 128
	JB      MIDITranslateSend
	Dec     AX
	Jmp     MIDITranslateSend
~

MIDITranslateValue7:
	Cmp     AH, 'u'-'a'     ; Volume?
	JNE     MIDITranslateValue8

	Xor     AL, AL
	Test    Word Ptr [SI], 800h
	JNZ     MIDITranslateSend

	Mov     AL, [SI+20h]    ; 0->128
	Sub     AL, 1
	AdC     AL, 1           ; 1->128
	Cmp     AL, 128
	JB      MIDITranslateSend
	Dec     AX
	Jmp     MIDITranslateSend

MIDITranslateValue8:
	Mov     AL, [SI+3Ah]    ; HCN
	And     AL, 7Fh
	Cmp     AH, 'h'-'a'
	JE      MIDITranslateSend

	Mov     AL, [SI+2Ah]    ; Pan set
	Cmp     AH, 'x'-'a'
	JE      MIDITranslatePanValue

	Mov     AL, [SI+25h]    ; Final pan
	Cmp     AH, 'y'-'a'
	JE      MIDITranslatePanValue

	Mov     AL, [SI+3Dh]
	Cmp     AH, 'p'-'a'     ; Program?
	JE      MIDITranslateSend

	Mov     DX, [SI+3Eh]
	Mov     AL, DL
	Add     AL, 1
	AdC     AX, 0
	Dec     AX
	Cmp     AH, 'b'-'a'
	JE      MIDITranslateSend

	Mov     AL, DH
	Add     AL, 1
	AdC     AX, 0
	Dec     AX
	Cmp     AH, 'a'-'a'
	JE      MIDITranslateSend

	Xor     AX, AX
	Jmp     MIDITranslate1

MIDITranslatePanValue:
	Add     AL, AL
	Cmp     AL, 7Fh
	JBE     MIDITranslateSend
	Dec     AX
	Cmp     AL, 7Fh
	JBE     MIDITranslateSend
	Mov     AL, 40h
	Jmp     MIDITranslateSend

MIDITranslateValueEnd:
	Cmp     CL, 2
	JB      MIDITranslate1

MIDITranslateSend:
	Call    MIDISendFilter

	Xor     AX, AX
	Xor     CX, CX
	Jmp     MIDITranslate1

MIDITranslate2:
	Test    CX, CX
	JZ      MIDITranslate3

	Call    MIDISendFilter

MIDITranslate3:
	Pop     FS
	PopA

MIDITranslateEnd:
	Ret

EndP            MIDITranslate
	Assume DS:Nothing
#endif
}

it_slave *AllocateChannel15(it_engine *ite, it_host *chn, uint8_t *ch)
{
	// Sample handler
	uint16_t hcn = chn->HCN;
	it_slave *slave = &ite->slave[hcn];

	if((ite->d.DriverFlags & 2) != 0 && (slave->Flags & 1) != 0) // Hi-Qual driver + Channel on?
	{
		// copy out channel
		slave->Flags |= 0x200;
		slave->HCN |= 0x80;
		memcpy(slave + 64, slave, sizeof(it_slave));
	}

	chn->SCOffst = hcn;
	slave->HCOffst = slave->HCN = (chn - ite->chn);
	slave->Flags = 0x0133; // Recalc freq,vol&pan Channel on.

	slave->CVl = chn->CV;
	slave->Pan = slave->PS = chn->CP;

	// Get sample offset.
	// ^ v ^ v one of these doesn't seem to fit --GM
	// General stuff.

	slave->FadeOut = 0x0400;
	slave->V.EnvelopeValue = 64<<16;
	slave->FCut = 0xFF;

	slave->Nte = chn->Nte;
	slave->Ins = chn->Ins;

	if(chn->Smp == 0)
	{
		slave->Flags = 0x0200;
		*ch &= ~4;
		return NULL;
	}

	I_TagSample(ite, chn->Smp-1); // TODO: work out what this does!
	slave->Smp = chn->Smp-1;
	slave->SmpOffs = chn->Smp-1;
	it_sample *smp = &ite->smp[chn->Smp-1];

	// Reset vibrato info.
	slave->Bit = 0;
	slave->ViP = 0;
	slave->ViDepth = 0;

	slave->P.EnvelopeValue &= 0xFFFF; // No pan deviation
	slave->Pt.EnvelopeValue &= 0xFFFF; // No pitch deviation
	slave->LpD = 0; // Reset loop dirn

	if(smp->Length == 0 || (smp->Flg & 1) == 0)
	{
		// No sample!
		slave->Flags = 0x0200;
		*ch &= ~4;
		return NULL;
	}

	slave->Bit = (smp->Flg & 2);
	slave->SVl = smp->GvL<<1;

	return slave;

}

it_slave *AllocateChannelInstrument(it_engine *ite, it_host *chn, it_slave *slave,
	it_instrument *ins, uint8_t *ch)
{
	chn->SCOffst = slave - &ite->slave[0];
	printf("alloc %i %i %i %i\n", chn->HCN, (int)(chn - &ite->chn[0]), chn->SCOffst, (int)(ins - &ite->ins[0]));

	slave->HCN = chn->HCN;
	slave->HCOffst = chn - &ite->chn[0];

	// Reset vibrato info
	slave->Bit = 0;
	slave->ViP = 0;
	slave->ViDepth = 0;

	// Reset loop dirn
	slave->LpD = 0;

	InitPlayInstrument(ite, chn, slave, (ins - &ite->ins[0])+1);

	slave->SVl = ins->GbV;

	//Pop     CX

	// FadeOut, VolEnv&Pos
	slave->FadeOut = 0x0400;

	uint8_t al = chn->Nte;
	uint8_t ah = chn->Ins;
	if(chn->Smp == 101)
		al = chn->Nt2;

	slave->Nte = al;
	slave->Ins = ah;

	if(chn->Smp == 0)
	{
		slave->Flags = 0x200;
		*ch &= ~4;
		return NULL;
	}

	I_TagSample(ite, chn->Smp-1);
	slave->Smp = chn->Smp-1;

	// Sample memory offset.
	slave->SmpOffs = chn->Smp-1;
	it_sample *smp = &ite->smp[slave->SmpOffs];

	if(smp->Length == 0 || (smp->Flg & 1) == 0)
	{
		// No sample!
		slave->Flags = 0x200;
		*ch &= ~4;
		return NULL;
	}

	slave->Bit = (smp->Flg & 2);
	slave->SVl = (smp->GvL * (uint16_t)slave->SVl) >> 6; // SI = 0->128
	//printf("INS VOL %i SMP %i %i\n", slave->SVl, slave->Smp, chn->Smp);

	return slave;
}

it_slave *AllocateChannel(it_engine *ite, it_host *chn, uint8_t *ch)
{
	// Returns SI. Carry set if problems
	// (actually returns NULL here --GM)

	// TODO: de-goto-ise this.

	it_slave *slave;

	ite->LastSlaveChannel = 0;

	//printf("alloc slave\n");
	if((ite->hdr.Flags & 4) == 0)
		return AllocateChannel15(ite, chn, ch);

	// Instrument handler!
	// ^ NOTE TO PORTERS:
	//   Make sure you are in a room with no sharp objects, medicines, or knobs
	//   to hang ropes from. It's a lot of "WHAT IS MALLOC???" code. --GM

	ite->AllocateSlaveOffset = 0;
	ite->AllocateNumChannels = ite->NumChannels;

	if(chn->Smp == 101 && ite->NumChannels != MAXSLAVECHANNELS)
	{
		// CX = number of channels remaining
		// ^ but the code uses DX and BX, not CX --GM
		ite->AllocateNumChannels = (MAXSLAVECHANNELS - ite->NumChannels);
		ite->AllocateSlaveOffset = ite->NumChannels;
	}

	//printf("%i %i\n", ite->AllocateNumChannels, ite->AllocateSlaveOffset);

	if(chn->Ins == 0xFF)
		return AllocateChannel15(ite, chn, ch);

	if(chn->Ins == 0)
		return NULL;

	I_TagInstrument(ite, chn->Ins);

	it_instrument *ins = &ite->ins[chn->Ins-1];

	if((*ch & 0x04) == 0) // if((chn->Flags & 0x04) == 0)
		goto AllocateChannel8;

	// New note action handling...
	slave = &ite->slave[chn->SCOffst];
	if(slave->InsOffs == chn->Ins)
	{
		ite->LastSlaveChannel = chn->SCOffst + 1;
		//printf("ins match\n");
	}

	if(slave->NNA == 0)
		goto AllocateChannel20; // Notecut.

	//printf("FUCK\n"); // Might not be properly handled

	// Disown channel
	slave->HCN |= 0x80;

AllocateHandleNNA:
	// Is volume set = 0?
	if(slave->VS  == 0) goto AllocateChannel20;
	if(slave->CVl == 0) goto AllocateChannel20;
	if(slave->SVl == 0) goto AllocateChannel20;

	if(slave->NNA > 2)
	{
		// AL = 3 -> Fade
		slave->Flags |= 8; // Fade flag.
	} else if(slave->NNA == 2) {
		// Note off.
		slave->Flags |= 4; // Note off..
		GetLoopInformation(ite, slave);
	} else {
		// Note continue
	}

	goto AllocateChannel8;

AllocateChannel20:
	{};
	uint8_t dl;
	uint8_t dh;
	uint16_t bp;
	uint8_t ah;
	uint16_t cx;

	if(slave->Smp == 100) // MIDI?
	{
		slave->Flags |= 0x0200;
		slave->HCN |= 0x80; // Disown channel

		if(chn->Smp != 101)
			goto AllocateChannel4;

	AllocateChannelMIDIDC:
		slave = &ite->slave[ite->AllocateSlaveOffset];
		cx = ite->AllocateNumChannels;
		//Mov     SI, AllocateSlaveOffset
		//Mov     CX, AllocateNumChannels

		dl = chn->Nt2;
		dh = chn->Ins;
		bp = 0x32;
		ah = chn->MCh;
		*ch = ins->DCA;
		goto AllocateChannel6;
	}

AllocateChannel20Samples:
	if((ite->d.DriverFlags & 2) != 0)
		goto AllocateChannelHiQual;

	uint8_t al = ins->DCT;
	slave->Flags = 0x200;
	//printf("DCT %i\n", al);
	if(al == 0)
		return AllocateChannelInstrument(ite, chn, slave, ins, ch);

	goto AllocateChannel11;

AllocateChannelHiQual:
	slave->Flags |= 0x200;
	slave->HCN |= 0x80; // Disown channel
	goto AllocateChannel4;

AllocateChannel8:
	if(chn->Smp == 101)
		goto AllocateChannelMIDIDC;

	al = ins->DCT;
	if(al == 0)
		goto AllocateChannel4; // Duplicate check off.

	//printf("MEGA FUCK\n"); // TODO: Verify - Even more likely to break! --GM

AllocateChannel11:
	// Duplicate check...
	slave = &ite->slave[ite->AllocateSlaveOffset];
	cx = ite->AllocateNumChannels;
	//Mov     SI, AllocateSlaveOffset
	//Mov     CX, AllocateNumChannels

	dl = chn->Nte;
	dh = chn->Ins;
	bp = 0x32;
	if(al == 1)
		goto AllocateDCT;

	// Duplicate instrument
	bp = 0x33;
	dl = dh;
	if(al == 3)
		goto AllocateDCT;

	// Duplicate sample
	bp = 0x36;
	dl = chn->Smp-1;
	if((dl & 0x80) != 0)
		goto AllocateChannel4;

AllocateDCT:
	ah = chn->HCN | 0x80;
	*ch = ins->DCA;

AllocateChannel6:
	for(; (cx & 0xFF) != 0; cx--, slave++)
	{
		if((slave->Flags & 1) == 0)
			continue;

		if(chn->Smp == 101)
			goto AllocateChannelMIDIDCT;

		al = slave->HCN;
		if(ah != al)
			continue;

		// OK. same channel... now..

	AllocateChannelMIDIDCT:
		// Same inst?
		if(dh != slave->Ins)
			continue;

		// Same note/sample/inst?
		// ("else else" is 0x36 --GM)
		if(dl != (bp == 0x32 ? slave->Nte : bp == 0x33 ? slave->Ins : slave->Smp))
			continue;

		// New note is a MIDI?
		if(chn->Smp == 101)
			goto AllocateChannelMIDIHandling;

		if(*ch != slave->DCA)
			continue;

		// Checks for hiqual
		if(*ch == 0)
			goto AllocateChannel20Samples;

		slave->DCT = 0;
		al = *ch;
		al++; if(al == 0) ah++;
		USED(ah);
		goto AllocateHandleNNA;

	AllocateChannelMIDIHandling:
		// Is current channel a MIDI chan
		if(slave->Smp != 100)
			continue;

		if(ah != slave->MCh)
			continue;

		slave->Flags |= 0x200;
		if((slave->HCN & 0x80) != 0)
			continue;

		bp = slave->HCOffst;
		slave->HCN |= 0x80;
		ite->chn[bp].Flags &= ~4;

	}

AllocateChannel4:
	slave = &ite->slave[ite->AllocateSlaveOffset];
	cx = ite->AllocateNumChannels;
	//Mov     CX, AllocateNumChannels
	//Mov     SI, AllocateSlaveOffset

	if(chn->Smp == 101)
	{
		// MIDI 'slave channels' have to be maintained if still referenced

		//Push    DI

		for(; cx != 0; cx--, slave++)
		{
			if((slave->Flags & 1) != 0)
				continue;

			// Have a channel.. check that it's host's slave isn't SI
			if(slave->HCOffst == -1 ||
				ite->chn[slave->HCOffst].SCOffst == (slave - &ite->slave[0]))
			{
				// Pop   DI
				return AllocateChannelInstrument(ite, chn, slave, ins, ch);
			}
		}

		//Pop     DI

	} else {
		/*
		It's about time this was documented.
		These notes are by GM.

		Step 1: Look for any channels that are "off".
		*/

		for(; cx != 0; cx--, slave++)
		{
			//printf("chnoff %i %i\n", (int)(slave - &ite->slave[0]), slave->Flags & 1);
			if((slave->Flags & 1) == 0)
				return AllocateChannelInstrument(ite, chn, slave, ins, ch);
		}
	}

	/*
	Step 2: TODO: Document this!
	*/
	// Common sample search
	memset(ite->ChannelCountTable, 0, (100+200)); // Clear table
	//memset(ite->ChannelCountTable+100, 0xFF, (200)); // FIXME: this doesn't even show up on the real thing --GM
	memset(ite->ChannelCountTable+(100+200), 0xFF, (100)); // Volumes

	cx = ite->AllocateNumChannels;
	uint32_t ebx = 0;
	USED(ebx);
	it_slave *other = &ite->slave[ite->AllocateSlaveOffset];

	for(; cx != 0; cx--, other++)
	{
		// BX = sample pointer into table.
		uint16_t bx = other->Smp;

		// Just for safety
		if(bx > 99)
			continue;

		ite->ChannelCountTable[bx]++;

		// Volume
		ah = ite->ChannelCountTable[bx+300];

		// Disowned channel?
		if((other->HCN & 0x80) == 0)
			continue;

		// Lower Volume?
		if(ah <= other->FV)
			continue;

		// Get volume
		ah = other->FV;

		// Store location
		ite->ChannelCountTable[100+bx+bx+0] = (other - &ite->slave[0])+1;
		ite->ChannelCountTable[100+bx+bx+1] = ((other - &ite->slave[0])+1)>>8;

		// Store volume
		ite->ChannelCountTable[300+bx] = ah;
	}

	// OK.. now search table for maximum
	// occurrence of sample...
	uint16_t di = 0;
	uint16_t si = 0;

	// Find maximum count, has to be
	// greater than 2 channels
	ah = 2;
	cx = 100;

	for(; cx != 0; cx--, di++)
	{
		if(ah >= ite->ChannelCountTable[di])
			continue;

		ah = ite->ChannelCountTable[di];
		si = (uint16_t)(
			(uint16_t)ite->ChannelCountTable[di+di+100+0]
			+(((uint16_t)ite->ChannelCountTable[di+di+100+1])<<8));
	}

	// Pop     DI
	// Pop     BX

	//printf("ComS %i\n", si);
	if(si != 0)
	{
		slave = &ite->slave[si-1];
		return AllocateChannelInstrument(ite, chn, slave, ins, ch);
	}

	// Find out which host channel has the most
	// (disowned) slave channels
	// Then find the softest non-single sample
	// in that channel.

	memset(ite->ChannelCountTable, 0, 64);

	uint16_t bx;
	cx = ite->AllocateNumChannels;
	other = &ite->slave[ite->AllocateSlaveOffset];

	for(; cx != 0; cx--, other++)
	{
		bx = other->HCN & 0x3F;
		ite->ChannelCountTable[bx]++;
	}

	for(;;)
	{
		// OK.. search through and find
		// the most heavily used channel

		// AH = channel count
		// AL = channel
		// 64 = physical channels

		ah = 0x01;
		al = 0x00;
		bx = 0;
		cx = 64;

		for(; cx != 0; cx--, bx++)
		{
			if(ah >= ite->ChannelCountTable[bx])
				continue;

			ah = ite->ChannelCountTable[bx];
			al = bx & 0xFF;
		}

		// AH = channel to use.
		// ^ don't you mean AL, Jeff? AH is the volume. --GM
		//printf("AH=%02X AL=%02X BX=%04X CX=%04X\n", ah, al, bx, cx);
		if(ah <= 1)
		{
			//Push    DI

			// Now search for softest
			// disowned sample
			// (not non-single)

			cx = ite->AllocateNumChannels;
			other = &ite->slave[ite->AllocateSlaveOffset];

			// Offset
			si = 0;
			ah = 0xFF;

			for(; cx != 0; cx--, other++)
			{
				if((other->HCN & 0x80) == 0)
					continue; // No.. then look for next

				// Volume set...
				if(ah < other->FV)
					continue;

				// get offset.
				si = other - &ite->slave[0];
				si++;

				// Get volume
				ah = other->FV;
			}

			// Pop     DI

			if(si != 0)
				return AllocateChannelInstrument(ite, chn, slave, ins, ch);

			printf("FAIL\n");
			*ch &= ~4;
			return NULL;
		}

		// Search for disowned only
		al |= 0x80;

		// actually BH --GM
		uint8_t bh = chn->Smp-1;

		cx = ite->AllocateNumChannels;
		other = &ite->slave[ite->AllocateSlaveOffset];
		it_slave *sither = &ite->slave[ite->AllocateSlaveOffset];
		USED(sither);
		ah = 0xFF;

		for(; cx != 0; other++, cx--)
		{
			if(al != other->HCN)
				continue;

			// Lower Volume?
			if(ah <= other->FV)
				continue;

			// Now check if any other channel contains this sample
			if(bh != other->Smp)
			{
				uint8_t bl = other->Smp;

				other->Smp = 0xFF;

				it_slave *subslave = &ite->slave[ite->AllocateSlaveOffset];
				uint16_t subcx = ite->AllocateNumChannels;
				int need_continue = 1;
				for(; subcx != 0; subslave++, subcx--)
				{
					// might as well just leave that label in
					if(bh == subslave->Smp)
					{
						need_continue = 0;
						break;
					}

					// A second sample?
					if(bl == subslave->Smp)
					{
						need_continue = 0;
						break;
					}
				}

				other->Smp = bl;
				if(need_continue != 0)
					continue;

				//Pop     SI
				//Pop     CX
			}

			// OK found a second sample.
			// get offset
			si = other - &ite->slave[0];
			si++;

			// Get volume
			ah = other->FV;
		}

		if(si != 0)
			break;

		si = al & 0x3F;
		ite->ChannelCountTable[si] = 0;
		// Next cycle...
	}

	//Push    DI

	al = slave->Smp;

	cx = ite->AllocateNumChannels;
	other = &ite->slave[ite->AllocateSlaveOffset];
	ah = 0xFF;

	for(; cx != 0; cx--, other++)
	{
		// Same sample?
		if(al != chn->Smp)
			continue;

		// Disowned channel?
		if((other->HCN & 0x80) == 0)
			continue;

		// Lower Volume?
		if(ah <= other->FV)
			continue;

		// get offset.
		si = other - &ite->slave[0];
		si++;

		// Get volume
		ah = other->FV;

	}

	//Pop     DI
	//printf("SmpS %i\n", si);
	slave = &ite->slave[si-1];
	return AllocateChannelInstrument(ite, chn, slave, ins, ch);
}

uint16_t Random(it_engine *ite)
{
	uint16_t ax = ite->Seed1;
	uint16_t bx = ite->Seed2;
	uint16_t cx = bx;
	uint16_t dx = bx;
	uint8_t cl = cx & 15;

	ax += bx;
	ax = (ax<<cl) | (ax>>((16-cl)&15));
	ax ^= dx;
	cx = (cx>>8)|(cx<<8);
	bx += cx;
	dx += bx;
	cx += ax;
	ax -= dx + (bx&1);
	bx = (bx>>1) | (bx<<15);
	USED(bx);
	ite->Seed2 = dx;
	ite->Seed1 = ax;
	USED(cx);

	return ax;
}

void GetLoopInformation(it_engine *ite, it_slave *slave)
{
	// TODO: verify
	// Destroys AX, BX, CX, DX
	it_sample *smp = &ite->smp[slave->Smp];
	uint8_t ah;
	int32_t ecx;
	int32_t edx;

	// TODO: fix this crap (there's a lot of guesswork here!)
	if((smp->Flg & ((slave->Flags & 4) == 0 ? 0x30 : 0x10)) == 0)
	{
		ecx = 0;
		edx = smp->Length;
		ah = 0;
	} else {
		ecx = smp->Loop_Begin;
		edx = smp->Loop_End;
		ah = smp->Flg;

		if((smp->Flg & 0x20) != 0 && (slave->Flags & 0x4) == 0) // SusLoop?
		{
			ecx = smp->SusLoop_Begin;
			edx = smp->SusLoop_End;
			ah >>= 1;
		}

		if((ah & 0x40) == 0) {
			ah = 8;
		} else {
			ah = 24;
		}
	}

	if(slave->LpM == ah)
	if(slave->Loop_Beginning == ecx)
	if(slave->Loop_End == edx)
		return;

	slave->LpM = ah;
	slave->Loop_Beginning = ecx;
	slave->Loop_End = edx;
	slave->Flags |= 0x0400; // Loop changed.
}

// include it_m_eff.inc

void PitchSlideDown(it_engine *ite, it_host *chn, it_slave *slave, int16_t bx)
{
	// do NOT blame me for this, it's how it works in the real thing
	// except it actually falls through to procedures so it's worse --GM
#if USEFPUCODE
	bx = -bx;
#else
	if((ite->hdr.Flags & 8) != 0)
		PitchSlideDownLinear(ite, chn, slave, bx);
	else
		// Go on to amiga slide down.
		PitchSlideDownAmiga(ite, chn, slave, bx);
}

void PitchSlideDownAmiga(it_engine *ite, it_host *chn, it_slave *slave, int16_t bx)
{
	USED(ite, chn);
	slave->Flags |= 32; // recalculate pitch!

	uint64_t m64 = ((uint64_t)slave->Frequency)*((uint64_t)(uint16_t)bx);
	// EDX:EAX = cmd*InitialFreq

	// CX = counter.
	int cx = 0;

	m64 += (uint64_t)(((uint64_t)1712)*(uint64_t)8363);
	while((m64>>(uint64_t)32) != 0)
	{
		m64 >>= (uint64_t)1;
		cx++;
	}

	uint32_t ebx = m64; // EBX = 1712*8363+Cmd*InitialFreq

	m64 = ((uint64_t)slave->Frequency)*(uint64_t)(((uint64_t)1712)*(uint64_t)8363);

	while(cx > 0)
	{
		m64 >>= (uint64_t)1;
		cx--;
	}

	if(ebx > 0)
		slave->Frequency = (m64 / (uint64_t)ebx);

}

void PitchSlideDownLinear(it_engine *ite, it_host *chn, it_slave *slave, int16_t bx)
{
	USED(ite, chn);
	// Given BX = slide down value = 0->1024

	slave->Flags |= 32; // recalculate pitch!

	const uint16_t *tab;
	if(bx <= 0x0F)
	{
		tab = FineLinearSlideDownTable;
	} else {
		tab = LinearSlideDownTable;
		bx >>= 2;
	}

	uint64_t m64 = tab[bx];
	m64 *= (uint64_t)slave->Frequency;
	m64 >>= (uint64_t)16;
	slave->Frequency = m64;

#endif /* USEFPUCODE */
}

void PitchSlideUp(it_engine *ite, it_host *chn, it_slave *slave, int16_t bx)
{
	if((ite->hdr.Flags & 8) == 0)
		PitchSlideUpAmiga(ite, chn, slave, bx);
	else
		PitchSlideUpLinear(ite, chn, slave, bx);
		// Go on to linear slide
}

void PitchSlideUpLinear(it_engine *ite, it_host *chn, it_slave *slave, int16_t bx)
{
	USED(ite);
#if USEFPUCODE
	Mov     [CS:SlideValue], BX
	FILD    Word Ptr [CS:SlideValue]
	FMul    [CS:Const1_On_768]      ; Have SlideValue/768.0
	FLd     ST
	FRndInt
	FSub    ST(1), ST
	FXCh
	F2XM1
	FLd1
	FAdd
	FScale
	FIMul   DWord Ptr [SI+10h]
	FIStP   DWord Ptr [SI+10h]
	FStP    ST

PitchSlideUpFPUFreqCheck:
	Or      Byte Ptr [SI], 32       ; recalculate pitch!
	Cmp     DWord Ptr [SI+10h], 07FFFFFFFh
	JAE     PitchSlideUpLinear1
	Ret

PitchSlideUpLinear1:                                    ; Turn off channel
	Or      Word Ptr [SI], 200h
	And     Byte Ptr [DI], Not 4
#else
	slave->Flags |= 32; // recalculate pitch!

	const uint32_t *tab;
	if(bx <= 0x0F)
	{
		tab = FineLinearSlideUpTable;
	} else {
		tab = LinearSlideUpTable;
		bx >>= 2;
	}

	uint64_t m64 = tab[bx];
	m64 *= (uint64_t)slave->Frequency;
	m64 >>= (uint64_t)16;

	if((m64 & (uint64_t)0xFFFF00000000LL) == 0)
	{
		slave->Frequency = m64;
	} else {
		// Turn off channel
		slave->Flags |= 0x0200;
		chn->Flags &= ~4;
		// slave->Flags &= ~1;
		// slave->Flags |= 2<<8; // Cut!
		// chn->Flags &= ~4;
	}
#endif
}

void PitchSlideUpAmiga(it_engine *ite, it_host *chn, it_slave *slave, int16_t bx)
{
	USED(ite);
#if USEFPUCODE
	Mov     [CS:SlideValue], BX
	FILD    Word Ptr [CS:SlideValue]
	FILD    DWord Ptr [SI+10h]      ; InitFreq, Cmd
	FMul    ST(1), ST               ; InitFreq, Cmd.InitFreq
	FLd     [CS:Const_14317456]     ; 1712*8363, InitFreq, Cmd.InitFreq
	FSubR   ST(2), ST               ; 1712*8363, InitFreq, 1712*8363-Cmd.InitFreq
	FMul                            ; 1712*8363*InitFreq, 1712*8363-Cmd.InitFreq
	FDivRP  ST(1), ST               ; FinalFreq
	FIStP   DWord Ptr [SI+10h]
	Jmp     PitchSlideUpFPUFreqCheck

#else
	slave->Flags |= 32; // recalculate pitch!

	uint64_t m64 = ((uint64_t)slave->Frequency)*((uint64_t)(uint16_t)bx);
	// EDX:EAX = InitialFreq*Cmd

	if((m64 & (uint64_t)0xFFFFFFFF00000000LL) == (uint64_t)0)
	{
		if(m64 < (1712*8363))
		{
			uint32_t ecx = (1712*8363) - m64;

			m64 = slave->Frequency * (uint64_t)(1712*8363);
			if((m64>>(uint64_t)32) < (1712*8363))
			{
				// TODO: verify
				m64 /= (uint64_t)ecx;
				slave->Frequency = (uint32_t)m64;
				return;
			}
		}
	}

	// Turn off channel
	slave->Flags |= 0x0200;
	chn->Flags &= ~4;
	// slave->Flags &= ~1;
	// slave->Flags |= 2<<8; // Cut!
	// chn->Flags &= ~4;
#endif
}

int Music_GetWaveForm(it_engine *ite)
{
	// TODO: work out exactly what to do
	if((ite->d.DriverFlags & 4) == 0)
		return -1;

	// TODO: find out the arguments for this
	return ite->d.DriverGetWaveform(ite);
}

void Music_Poll(it_engine *ite)
{
	// AX = CS:PlayMode
	// BX = CS:CurrentPattern
	ite->d.DriverPoll(ite, ite->PlayMode, ite->CurrentPattern);
}

void Music_InitTempo(it_engine *ite)
{
	ite->d.DriverSetTempo(ite, Music_GetTempo(ite));
}

void GetChannels(it_engine *ite)
{
	// Returns min of NumChannels & DriverMaxChannels
	// Also uses default channels if num channels
	// = 0ffffh
	int16_t ax = ite->CmdLineNumChannels;

	if(ax == -1)
		ax = (int16_t)ite->d.DefaultChannels;

	if(ax > (int16_t)ite->d.DriverMaxChannels)
		ax = (int16_t)ite->d.DriverMaxChannels;

	if(ax >= MAXSLAVECHANNELS)
		ax = MAXSLAVECHANNELS; // MC4

	ite->NumChannels = ax;
}

void Music_ReinitSoundCard(it_engine *ite)
{
	GetChannels(ite);
	ite->d.DriverReinitSound(ite);
	Music_SoundCardLoadAllSamples(ite);
}

void Music_UnInitSoundCard(it_engine *ite)
{
	ite->d.DriverUninitSound(ite);
}

void Music_InitMusic(it_engine *ite)
{
#if ENABLEINT3
#else
	// This puts Music_UpdateSampleLocation in the INT3 TSR.
	// Because we are not in x86 Real Mode, we cannot do this.
#endif

	Trace(" - Initialising SoundDriver Tables");

	Music_ClearDriverTables(ite);
	D_GotoStartingDirectory(ite);

	Trace(" - Loading MIDI configuration");

	// Open MIDI Config file.
	int fp = -1;//open(MIDIConfigFileName, OREAD);

	if(fp >= 0)
	{
		// TODO: serialise this properly
		// TODO: actually create a MIDIDataArea
		//readn(fp, ite->MIDIDataArea, (128+16+9)*32);

		close(fp);
	}

	Trace(" - Initialising playback tables");

	Music_Stop(ite);
}

void Music_ReleasePattern(it_engine *ite, uint16_t ax)
{
	// AX = pattern number

	// Complete rewrite.
	if(ite->pat[ax] != NULL)
	{
		free(ite->pat[ax]);
		ite->pat[ax] = NULL;
	}
}

it_pattern *Music_GetPattern(it_engine *ite, uint16_t ax)
{
	// This is much simpler.
	it_pattern *pat = ite->pat[ax]; // SI

	if(pat == NULL)
		pat = (it_pattern *)EmptyPattern;

	return pat;

	// Here's what happens in the actual code.
	/*
	if(AL < 1)
	{
		// Empty pattern
		Push    CS
		Pop     DS
		Mov     SI, Offset EmptyPattern

		Pop     AX
		Ret

	} else if(AL == 1) {
		// Start of segment, conventional memory
		LodsW
		Mov     DS, AX
		Xor     SI, SI

		Pop     AX
		Ret

	} else if(AL < 3) {
		Push    CX
		Push    DX

		MovZX   CX, AH
		Mov     DX, [SI]
		Call    E_MapEMSMemory
		Call    E_GetEMSPageFrame
		Mov     DS, AX
		Xor     SI, SI

		Pop     DX
		Pop     CX

		Pop     AX
		Ret
	} else {
		// Start of EMS block
		LodsW
		Call    E_MapAlignedBlockEMS

		Pop     AX
		Ret
	}
	*/

	// Basically, a bunch of conventional-vs-EMS crap.
}

it_pattern *Music_GetPatternLocationNoCount(it_engine *ite, uint16_t ax)
{
	// Because we don't use the x86 realmode segment:offset crap,
	// this is identical to Music_GetPattern.
	it_pattern *pat = ite->pat[ax];

	if(pat == NULL)
		pat = (it_pattern *)EmptyPattern;

	return pat;
}

it_pattern *Music_GetPatternLocation(it_engine *ite, uint16_t ax, uint16_t *len)
{
	// AX = pattern number
	// Returns AX = handle
	//         EBX = page/offset or
	//               seg/offset
	//         CX = length

	it_pattern *pat = Music_GetPattern(ite, ax);
	*len = pat->Length;

	return Music_GetPatternLocationNoCount(ite, ax);
}

it_pattern *Music_AllocatePattern(it_engine *ite, uint16_t dx)
{
	USED(ite);
	// DX = length.
	// SI = Pattern
	// ES:DI points to pattern area

	// I suspect this is the C version:
	return malloc(dx);

	// Yeah.
}

uint8_t *Music_AllocateSample(it_engine *ite, uint16_t ax, size_t edx)
{
	// AX = Sample number, 0 based
	// EDX = length
	// Returns ES:DI, ES = 0 if not.

	it_sample *smp = &ite->smp[ax];
	int real_len = smp->Length;

	Music_ReleaseSample(ite, ax, 2);
	edx += 8; // Extra 8 bytes allocated..

	if(edx < 1048576)
	{
		smp->Flg |= 1;

		// not in the actual code, just trying to prevent leaks --GM
		if(ite->SamplePointer[ax] != NULL)
		{
			free(ite->SamplePointer[ax]);
			ite->SamplePointer[ax] = NULL;
		}

		// XXX: not sure where this actually goes --GM
		smp->Length = real_len;

		ite->SamplePointer[ax] = malloc(edx);
		return ite->SamplePointer[ax];
	}

	smp->Flg &= 0xF0;
	return NULL;
}

void Music_ReleaseSample(it_engine *ite, uint8_t al, uint8_t ah)
{
	// AX = sample number, 0 based
	// AH = 1 = called from network
	//    = 2 = called from allocate

	it_sample *smp = &ite->smp[al]; // DS:BX points to sample

	ite->d.DriverReleaseSample(ite, smp);

	if((smp->Flg & 1) != 0)
	{
		if(ite->SamplePointer[al] != NULL)
		{
			free(ite->SamplePointer[al]);
			ite->SamplePointer[al] = NULL;
		}
	}

	smp->Length = 0;
	//smp->SamplePointer = 0; // TODO: find out what to do with this --GM

	if(ah > 1)
		return;

	if(ah != 1)
	{
#if NETWORKENABLED
		Network_AddWordToQueue(ite, (ah<<8) | NETWORK_DELETESAMPLEOBJECT);
#endif
	}

	smp->Flg &= 0xFE;

	// this is where we do a good thing and actually use our struct properly
	// IT itself just does memset(smp->Name, 0, 0x3C). --GM

	// also, why isn't the filename scrubbed? --GM
	memset(smp->Name, 0, 26);
	smp->Cvt = 0;
	smp->DfP = 0;
	smp->Length = 0;
	smp->Loop_Begin = 0;
	smp->Loop_End = 0;
	smp->C5Speed = 0;
	smp->SusLoop_Begin = 0;
	smp->SusLoop_End = 0;
	smp->ViS = 0;
	smp->ViD = 0;
	smp->ViR = 0;
	smp->ViT = 0;
}

void Music_ClearSampleName(it_engine *ite, uint16_t ax)
{
	// AX = Sample number (0 based)

	memcpy(&ite->smp[ax], SampleHeader, 80);

	// not in the actual code, just here to prevent a leak --GM
	if(ite->SamplePointer[ax] != NULL)
	{
		free(ite->SamplePointer[ax]);
		ite->SamplePointer[ax] = NULL;
	}
}

void Music_ClearAllSampleNames(it_engine *ite)
{
	int i;

	for(i = 99; i >= 0; i--)
		Music_ClearSampleName(ite, i);
}

void Music_ReleaseAllSamples(it_engine *ite)
{
	int i;

	for(i = 99; i >= 0; i--)
		Music_ReleaseSample(ite, i, 0);
}

void Music_ReleaseAllPatterns(it_engine *ite)
{
	int i;

	for(i = 99; i >= 0; i--)
		Music_ReleasePattern(ite, i);
}

void Music_ClearInstrument(it_engine *ite, uint16_t ax)
{
	// AX = Instrument number
	// (0 based)

	memcpy(&ite->ins[ax], InstrumentHeader, 554);
}

void Music_ClearAllInstruments(it_engine *ite)
{
	int i;

	for(i = 99; i >= 0; i--)
		Music_ClearInstrument(ite, i);
}

void Music_UnInitMusic(it_engine *ite)
{
	Music_UnInitSoundCard(ite);
	Music_UnloadDriver(ite);

	Music_ReleaseAllPatterns(ite);
	Music_ReleaseAllSamples(ite);
}

// NOT IMPLEMENTING: Music_GetSongSegment
// BECAUSE: it's legacy DOS crap that doesn't apply --GM

void Music_UnloadDriver(it_engine *ite)
{
	USED(ite);
	// really not sure how to go about this one.
	// might just opt for dlclose() or something like that --GM

	/*
	Xor     AX, AX
	XChg    AX, CS:SoundDriverSegment
	Test    AX, AX
	JZ      Music_UnloadDriver1

	Mov     ES, AX
	Mov     AH, 49h
	Int     21h

Music_UnloadDriver1:
	Ret
	*/
}

// these have been reworked a bit
int NoFunction(it_engine *ite)
{
	USED(ite);
	return -1;
}

int NoFunctionB(it_engine *ite, uint8_t w1)
{
	USED(ite, w1);
	return -1;
}

int NoFunctionW(it_engine *ite, uint16_t w1)
{
	USED(ite, w1);
	return -1;
}

int NoFunctionWW(it_engine *ite, uint16_t w1, uint16_t w2)
{
	USED(ite, w1, w2);
	return -1;
}

int NoFunctionPs(it_engine *ite, it_sample *ps1)
{
	USED(ite, ps1);
	return -1;
}

const char *NoFunction2(it_engine *ite)
{
	USED(ite);
	return NoSoundCardMsg;
}

const char *NoFunction2PccWW(it_engine *ite, const char *c1, uint16_t w2, uint16_t w3)
{
	USED(ite, c1, w2, w3);
	return NoSoundCardMsg;
}

void Music_ClearDriverTables(it_engine *ite)
{
	// Makes all of them point to
	// Xor AX, AX, StC, RetF
	ite->d.DriverDetectCard = NoFunction2PccWW;
	ite->d.DriverInitSound = NoFunction2;

	// we have to serialise this, really,
	// thus, no OLDDRIVER distinction --GM
	ite->d.DriverReinitSound = NoFunction;
	ite->d.DriverUninitSound = NoFunction;
	ite->d.DriverPoll = NoFunctionWW;
	ite->d.DriverSetTempo = NoFunctionW;
	ite->d.DriverSetMixVolume = NoFunctionW;
	ite->d.DriverSetStereo = NoFunctionW;
	ite->d.DriverLoadSample = NoFunctionW;
	ite->d.DriverReleaseSample = NoFunctionPs;
	ite->d.DriverResetMemory = NoFunction;
	ite->d.DriverGetStatus = NoFunction;

	ite->d.DriverSoundCardScreen = NoFunction;
	ite->d.DriverGetVariable = NoFunctionW;
	ite->d.DriverSetVariable = NoFunctionWW;

	ite->d.DriverMIDIOut = NoFunctionB;
	ite->d.DriverGetWaveform = NoFunction;

	ite->d.DriverMaxChannels = 32;
}

int Music_LoadDriver(it_engine *ite, const char *fname)
{
	// pretty much a complete rewrite! let's go! --GM

	// dlopen() is handled on every modern OS that isn't Windows.
	// of course, it's easier to just statically link in a driver.
	// so, no dlopen(). yet.

	it_drvdata *drv = DriverSys_GetByName(ite, fname);
	if(drv == NULL)
	{
		Music_ClearDriverTables(ite);
		return -1;
	}

	memcpy(&ite->d, drv, sizeof(it_drvdata));

	// There's no need to worry about "required variables"
	// or "required functions" - driver has full it_engine access.

	return 0;
}

const char *Music_AutoDetectSoundCard(it_engine *ite)
{
	int dord = 0;
	const char *reply = NULL;

	// Returns DS:SI = string
	// AX, BX, CX, DX, DI = parameters

	D_GotoStartingDirectory(ite);

	if(ite->DriverName != NULL)
	{
		Trace(" - Loading specific soundcard driver");

		if(ite->DriverName[0] != '\x00')
		{
			if(!Music_LoadDriver(ite, ite->DriverName))
			{
				reply = ite->d.DriverDetectCard(
					ite, ite->DriverName, 1, TRACKERVERSION); // Forced

				// Unfortunately these drivers don't have to return 'Jeff'.
				if(reply != NULL)
				{
					Music_UnloadDriver(ite);
					Music_ClearDriverTables(ite);
				}
			}
		}
	} else for(;;) {
	//Music_AutoDetectSoundCard1:
		Trace(" - Testing soundcard driver");

		int didx = DriverDetectionOrder[dord++];
		if(didx == 0xFFFF)
			break;

		if(!Music_LoadDriver(ite, DriverNameTable[didx]))
		{
			const char *reply = ite->d.DriverDetectCard(
				ite, DriverNameTable[didx], 1, TRACKERVERSION); // Not forced

			if(reply == NULL)
				break;

			Music_UnloadDriver(ite);
			Music_ClearDriverTables(ite);
		}
	}

	GetChannels(ite);
	ite->d.DriverInitSound(ite);

	// Not sure what these are for!
	/*
	Mov     [CS:ADSCParams+2], DS
	Mov     [ADSCParams], SI
	Mov     [ADSCParams+12], DI
	Mov     [ADSCParams+10], DX
	Mov     [ADSCParams+8], CX
	Mov     [ADSCParams+6], BX
	Mov     [ADSCParams+4], AX
	*/

	Music_InitStereo(ite);
	Music_InitMixTable(ite);

	return reply;
}

void Music_ShowAutoDetectSoundCard(it_engine *ite)
{
	USED(ite);
	// HELP --GM
	/*
	Push    DWord Ptr [ADSCParams+10]
	Push    DWord Ptr [ADSCParams+6]
	Push    [ADSCParams+4]

	LDS     SI, DWord Ptr [ADSCParams]

	Mov     AH, 20h
	Mov     DI, (26+28*80)*2
	Call    S_DrawString

	Add     SP, 10
	Ret
	*/
}

uint16_t Music_GetInstrumentMode(it_engine *ite)
{
	return (ite->hdr.Flags & 4);
}

void UpdateGOTONote(it_engine *ite)
{
	// Get offset & arrayed flag.

	uint16_t patnum;
	uint16_t maxrow;

	it_pattern *pat = PE_GetCurrentPattern(ite, &patnum, &maxrow);
	USED(pat);
	// AX = Pattern number
	// BX = MaxRow
	// DS = PatternDataSegment

	if(patnum == ite->CurrentPattern)
	{
		ite->DecodeExpectedPattern = patnum;
		//Mov     CS:PatternSegment, DS

		ite->NumberOfRows = maxrow;

		if(ite->ProcessRow >= ite->NumberOfRows)
		{
			ite->ProcessRow = 0;
			ite->CurrentRow = 0;
		}

		ite->DecodeExpectedRow = ite->ProcessRow;

		ite->PatternOffset = ite->ProcessRow * (64*5);
		ite->PatternArray = 1;

		return;
	}

	ite->DecodeExpectedPattern = ite->CurrentPattern;
#if NETWORKENABLED
	Network_UpdatePatternIfIdle(ite);
#endif
	pat = Music_GetPattern(ite, ite->CurrentPattern);
	// DS:SI points to pattern.

	// AX = number of rows
	if(ite->ProcessRow >= pat->Rows)
		ite->CurrentRow = 0;
	else
		ite->CurrentRow = ite->ProcessRow;

	ite->ProcessRow = ite->CurrentRow;
	ite->DecodeExpectedRow = ite->CurrentRow;
	ite->NumberOfRows = pat->Rows;

	uint8_t *data = pat->data;
	uint16_t cx = ite->ProcessRow;
	uint8_t al;
	cx++;

	while((--cx) != 0)
	{
		for(;;)
		{
			// OK.. now to find the right
			// offset & update tables.
			al = *(data++);
			if(al == 0)
				break;

			uint8_t dl = al;
			al = (al & 0x7F) - 1;
			it_host *chn = &ite->chn[al];
			// CS:DI points.

			if((dl & 0x80) != 0)
				chn->Msk = *(data++);

			if((chn->Msk & 1) != 0)
				chn->Nte = *(data++);
			if((chn->Msk & 2) != 0)
				chn->Ins = *(data++);
			if((chn->Msk & 4) != 0)
				chn->Vol = *(data++);
			if((chn->Msk & 8) != 0)
			{
				// warning, when reading the code this can catch you out.
				// it loads then stores a *word*.
				// the IT code does this kind of stuff a lot. --GM
				chn->Cmd = *(data++);
				chn->CVal = *(data++);
			}
		}
	}

	ite->PatternOffset = data - pat->data;
	ite->PatternArray = 0;
}

void PreInitCommand(it_engine *ite, it_host *chn)
{
	if((chn->Msk & 0x33) != 0)
	{
		if((ite->hdr.Flags & 4) == 0 || chn->Nte >= 120 || chn->Ins == 0)
		{
			chn->Nt2 = chn->Nte;
			chn->Smp = chn->Ins;
		} else {
			// Have to xlat.

			it_instrument *ins = &ite->ins[chn->Ins-1];

			// XXX: work around a modplugism
			if(ins->MCh == 0 || ins->MCh >= 128)
			{
				if(ins->MCh >= 128)
					printf("MODPLUGISM WARNING: midi channel %02X used for effect slot - WILL PROBABLY CRASH IT\n", ins->MCh);

				chn->Nt2 = ins->NoteSamp[chn->Nte][0];
				chn->Smp = ins->NoteSamp[chn->Nte][1];
				// This part is fine.
				//printf("XLAT %i -> %i %i\n", chn->Nte, chn->Nt2, chn->Smp);
			} else {

				if(ins->MCh == 17)
					chn->MCh = chn->HCN+1;
				else
					chn->MCh = ins->MCh;

				chn->MPr = ins->MPr;

				chn->Nt2 = ins->NoteSamp[chn->Nte][0];
				chn->Smp = 101;
			}

			if(chn->Smp == 0) // No sample?
				return;
		}
	}

	InitCommandTable[chn->Cmd & 0x1F](ite, chn); // Init note
	chn->Flags |= 64;

	// Check whether chn is on
	if((chn->HCN & 0x80) == 0)
		return;

	if((chn->Flags & 32) != 0)
		return;

	if((chn->Flags & 4) == 0)
		return; // Channel was off.

	it_slave *slave = &ite->slave[chn->SCOffst];
	slave->Flags |= 0x0800;
}

void UpdateNoteData(it_engine *ite)
{
	ite->PatternLooping = 0;

	if(ite->CurrentPattern == ite->DecodeExpectedPattern)
	{
		UpdateGOTONote(ite);
	} else {
		ite->DecodeExpectedRow++;
		if(ite->CurrentRow != ite->DecodeExpectedRow)
			UpdateGOTONote(ite);
	}

	uint16_t cx = 64; // 64 channels
	// Xor     AX, AX                  // Just to get rid of "jumps"
	it_host *chn = &ite->chn[0]; // DI --GM

	if(ite->PatternArray != 1)
	{
		// First clear all old command&value.
		// Mov     CX, 64                 // Done above

		for(; cx != 0; cx--)
		{
			chn->Flags &= ~(3+32+64+256);
			chn++;
		}

		//Mov     AX, CurrentPattern
#if NETWORKENABLED
		// Network_UpdatePatternIfIdle(ite);
#endif
		it_pattern *pat = Music_GetPattern(ite, ite->CurrentPattern); // Gets DS
		uint8_t *data = pat->data + ite->PatternOffset;

		for(;;)
		{
			uint8_t al;
			al = *(data++);
			if(al == 0) break; // No more!

			// else... go through decoding.

			uint8_t dl = al;
			assert((al & 0x7F) != 0);
			al = (al & 0x7F) - 1;
			chn = &ite->chn[al];

			uint8_t dh = chn->Msk; // mask.
			if((dl & 0x80) != 0)
			{
				dh = *(data++);
				chn->Msk = dh;
			}

			if((dh & 1) != 0)
				chn->Nte = *(data++);
			if((dh & 2) != 0)
				chn->Ins = *(data++);
			if((dh & 4) != 0)
				chn->Vol = *(data++);

			uint8_t ah;
			if((dh & 8) != 0)
			{
				al = chn->OCm = *(data++);
				ah = chn->OVal = *(data++);
			} else if((dh & 0x80) != 0) {
				al = chn->OCm;
				ah = chn->OVal;
			} else {
				al = ah = 0;
			}

			chn->Cmd  = al;
			chn->CVal = ah;
			PreInitCommand(ite, chn);
		}

		ite->PatternOffset = data - pat->data;
	} else {

		uint8_t *data = (&ite->patspace[0]) + ite->PatternOffset;

		// Mov     CX, 64                  ; 64 channels
		// Mov     DI, Offset HostChannelInformationTable

		for(; cx != 64; cx--)
		{
			uint8_t dl = 0; // DL = mask.
			chn->Flags &= ~(3+32+64+256); // Turn off all calling...

			uint8_t al;
			al = *(data++);
			if(al != NONOTE)
			{
				chn->Nte = al;
				dl |= 1;
			}

			al = *(data++);
			if(al != 0)
			{
				chn->Ins = al;
				dl |= 2;
			}

			al = *(data++);
			if(al != 0xFF)
			{
				chn->Vol = al;
				dl |= 4;
			}

			al = *(data++);
			uint8_t ah = *(data++);
			chn->Cmd  = chn->OCm  = al;
			chn->CVal = chn->OVal = ah;

			if(al != 0 || ah != 0)
				dl |= 8;

			if(dl != 0)
			{
				chn->Msk = dl;
				PreInitCommand(ite, chn);
			}

			chn++;
		}

		ite->PatternOffset = data - ite->patspace;
	}
}

void UpdateVibrato(it_engine *ite, it_slave *slave)
{
	// DS:SI points to slavechannelstruct.

	it_sample *smp = &ite->smp[slave->SmpOffs]; // ES:BX points to sample
	if(smp->ViD == 0)
		return;

	// ITTECH.TXT lied to you - check the original source.
	// it misses a crucial step... and IT uses CX not AX --GM
	slave->ViDepth += smp->ViR;
	if((slave->ViDepth>>8) > smp->ViD)
		slave->ViDepth = (slave->ViDepth & 0xFF) | (((uint16_t)smp->ViD)<<8);

	if(smp->ViS == 0)
		return;

	int8_t al = 0;
	if(smp->ViT != 3)
	{
		slave->ViP += smp->ViS; // Update pointer.

		// probably not wise to try to emulate out-of-range vibrato types.
		// well, at least for now. we can research these later. --GM
		switch(smp->ViT)
		{
			case 0:
				al = FineSineData[slave->ViP];
				break;
			case 1:
				al = FineRampDownData[slave->ViP];
				break;
			case 2:
				al = FineSquareWave[slave->ViP];
				break;
			default:
				printf("PANIC: out of range vibrato types not emulated!\n");
				abort();
		}
	} else {
		al = ((Random(ite) & 0x7F) - 64);
	}

	int16_t ax = ((int16_t)al) * (int16_t)(slave->ViDepth>>8);
	ax >>= 6; // SAL 2, take high (SAR 8) .: SAR 6

#if !USEFPUCODE
	if(ax < 0)
	{
		// strictly speaking this branch doesn't look for the host channel,
		// but it's included for completeness --GM
		PitchSlideDownLinear(ite, &ite->chn[slave->HCOffst], slave, -ax);
	} else {
#endif
		PitchSlideUpLinear(ite, &ite->chn[slave->HCOffst], slave, ax);
#if !USEFPUCODE
	}
#endif
}

void Update(it_engine *ite, uint16_t *rcx, it_slave **si, uint16_t *ax)
{
	uint16_t cx = MAXSLAVECHANNELS;
	it_slave *slave = &ite->slave[0];
	it_host *chn = &ite->chn[slave->HCOffst];

	MIDITranslate(ite, chn, slave, MIDICOMMAND_TICK);

	for(; cx != 0; cx--)
	{
		if((slave->Flags & 1) != 0)
		{
			// reset volume
			if(slave->VS != slave->Vol)
			{
				slave->Vol = slave->VS;
				slave->Flags |= 0x10;
			}

			// Freq
			if(slave->Frequency_Set != slave->Frequency)
			{
				slave->Frequency = slave->Frequency_Set;
				slave->Flags |= 0x20; // Recalc freq.
			}
		}

		slave++;
	}

	UpdateData(ite);

	if((ite->hdr.Flags & 4) != 0)
	{
		UpdateInstruments(ite);
	} else {
		UpdateSamples(ite);
	}

	*rcx = ite->NumChannels;
	*si = &ite->slave[0];
	*ax = ite->PlayMode;
}

void UpdateSamples(it_engine *ite)
{
	uint16_t i = ite->NumChannels;
	it_slave *slave = &ite->slave[0];

	for(; i != 0 ; i--, slave++)
	{
		if((slave->Flags & 1) == 0)
			continue;

		// OK... if recalc volume is on.
		// then recalc volume! :)
		if((slave->Flags & 16) != 0)
		{
			slave->Flags &= ~16;
			slave->Flags |= 64; // Recalc final vol

			if(ite->SoloSample != 0xFF && ite->SoloSample != slave->Smp)
				slave->Flags |= 0x0800;

			uint32_t ax = ((uint32_t)slave->Vol)*((uint32_t)slave->CVl)
				*((uint32_t)slave->SVl); // AX = 0->64*64*128
			ax >>= 4; // AX = 0->32768

			ax *= (uint32_t)ite->GlobalVolume;
			// AX = 0->32768*128

			ax >>= 7;

			// Final vol stored.
			slave->FV = ax>>8;
			slave->_16bVol = ax;
		}

		if((slave->Flags & 2) != 0)
		{
			slave->Flags &= ~2;
			slave->Flags |= 0x8000;

			slave->FP = slave->Pan;
			if(slave->Pan != 100)
			{
				int16_t ax = slave->Pan;
				ax -= 32;
				// Pan = (Pan-32)* Separation/128 + 32

				ax *= (int16_t)ite->hdr.Sep;
				// 0->64 (Separation) => AX = -2048->+2048
				// ie. AH = -8->+8

				ax >>= 7;
				// AL = -32->+32

				if(ite->d.ReverseChannels != 0)
					ax = -ax;

				ax += 32;

				slave->FPP = ax;
			} else {
				slave->FPP = slave->Pan;
			}
		}

		UpdateVibrato(ite, slave);
	}
}

int UpdateEnvelope(it_engine *ite, it_slen *slen, it_envelope *env, uint16_t bp)
{
	USED(ite);
	// Returns Carry if envelope needs
	// to be turned off
	// Reqs ES:DI points to envelope
	// DS:SI points to slave channel envelope structure
	// Called only if envelope is ON
	// BP != 0 if sustain points released, 0 otherwise

	int32_t dx = slen->EnvPos; // DX = current pos
	if(dx < slen->NextET)
	{
		// Increase position;
		slen->EnvPos = ++dx;

		// Update value
		slen->EnvelopeValue += slen->EnvelopeDelta;

		return 0;
	}

	// Procedure:
	// 1) Get current pos' value
	// 2) Figure out next pos (inc/loop)
	// 3) Figure out delta to next pos
	// 4) Terminate if no loop (with carry)
	//      or place new check in [SI+0Ch]

	uint16_t bx = slen->CurEnN; // BX = cur env node;
	uint16_t ax = bx+1;

	dx = (int8_t)env->Nodes[bx][0];
	dx <<= 16;
	slen->EnvelopeValue = dx; // Current pos value done.
	// AX = next cur env node

	if((env->Flg & 6) != 0) // Any loop at all?
	{
		// Normal Loop
		uint8_t bl = env->LpB;
		uint8_t bh = env->LpE;

		// the flow here is very weird in the original source --GM
		int use_sus = ((env->Flg & 4) != 0) && (bp == 0);
		int has_loop = (use_sus ? (env->Flg & 4) : (env->Flg & 2)) != 0;

		if(use_sus)
		{
			bl = env->SLB;
			bh = env->SLE;
		}

		// Loop
		if(has_loop && ax > bh)
		{
			// BL = New node
			slen->CurEnN = bl;
			uint16_t x = ((uint16_t)env->Nodes[bl][1])
				| (((uint16_t)env->Nodes[bl][2])<<8);

			slen->EnvPos = x;
			slen->NextET = x;

			return 0;
		}
	}

	// AX = new node
	if(ax >= env->Num)
		return 1;

	//slen->CurEnN = ax; // New node
	slen->CurEnN = (slen->CurEnN & 0xFF00) | (ax & 0x00FF); // New node

	// New node's tick
	uint16_t x1 = ((uint16_t)env->Nodes[ax][1])
		| (((uint16_t)env->Nodes[ax][2])<<8);
	slen->NextET = x1;
	uint16_t x0 = ((uint16_t)env->Nodes[ax-1][1])
		| (((uint16_t)env->Nodes[ax-1][2])<<8);

	uint16_t delta = x1 - x0;
	uint16_t base = x0+1;
	slen->EnvPos = base; // For security.

	int32_t yd = (int32_t)(((int8_t)env->Nodes[ax][0]) - ((int8_t)env->Nodes[ax-1][0]));
	int isneg = (yd < 0);

	if(isneg) yd = -yd;

	// Just to prevent div 0 errors
	if(delta == 0) delta = 1;

	// this was hell to translate --GM
	uint32_t eax = ((uint32_t)yd)/(uint32_t)delta;
	uint32_t edx = ((uint32_t)yd)%(uint32_t)delta;
	yd = (eax<<16) | ((edx<<16)/(uint32_t)delta);

	if(isneg) yd = -yd;

	slen->EnvelopeDelta = yd; // Delta done

	return 0;
}

void UpdateMIDI(it_engine *ite)
{
	uint16_t cx;
	it_slave *slave;
	it_host *chn;

	// Stop cycle
	for(cx = MAXSLAVECHANNELS, slave = &ite->slave[0]; cx != 0; cx--, slave++)
	{
		if((slave->Flags & 0x200) == 0) continue;
		if(slave->Smp != 100) continue;
		if((slave->Flags & 1) == 0) continue;

		slave->Flags = 0;
		chn = &ite->chn[slave->HCOffst];

		MIDITranslate(ite, chn, slave, MIDICOMMAND_STOPNOTE);

		if((slave->HCN & 0x80) != 0) continue;

		slave->HCN |= 0x80;
		chn->Flags &= ~4; // Signify channel off
	}

	// Play cycle
	for(cx = MAXSLAVECHANNELS, slave = &ite->slave[0]; cx != 0; cx--, slave++)
	{
		if(slave->Smp != 100) continue; // MIDI Instrument?

		if((slave->Flags & 1) == 0) continue;

		slave->OldSampleOffset = (slave->OldSampleOffset & ~0xFF)
			| (slave->Sample_Offset & 0xFF);
		slave->Sample_Offset = (slave->Sample_Offset & ~0xFF)
			| 1;

		if((slave->Smp & 0x0800) != 0) continue; // Muted?

		chn = &ite->chn[slave->HCOffst];
		slave->Flags &= 0x788D;
		// 0111100010001101b
		// 0111 1000 1000 1101
		// 7    8    8    D

		if((slave->Flags & 0x0100) != 0)
		{
			// Check if there's a bank select.
			uint16_t MBank = ((uint16_t)slave->FCut)
				| (((uint16_t)slave->FRes)<<8);

			if(MBank != 0xFFFF)
			{
				if(ite->MIDIBanks[slave->MCh-1] != MBank)
				{
					ite->MIDIBanks[slave->MCh-1] = MBank;
					ite->MIDIPrograms[slave->MCh-1] = 0xFF; // Reset program
					MIDITranslate(ite, chn, slave, MIDICOMMAND_BANKSELECT);
				}
			}

			// Check for a program specification
			if(((int8_t)slave->MPr) >= 0)
			{
				if(ite->MIDIPrograms[slave->MCh-1] != slave->MPr)
				{
					ite->MIDIPrograms[slave->MCh-1] = slave->MPr;
					MIDITranslate(ite, chn, slave, MIDICOMMAND_PROGRAMSELECT);
				}
			}

			// Check for MIDI pitch wheel..
			if((ite->hdr.Flags & 64) != 0)
			{
				if(ite->MIDIPitch[slave->MCh-1] != 0x2000)
				{
					ite->MIDIPitch[slave->MCh-1] = 0x2000;
					MIDISendFilter(ite, chn, (slave->MCh-1) | 0xE0);
					MIDISendFilter(ite, chn, 0); // Reset pitch wheel
					MIDISendFilter(ite, chn, 0x40);
				}
			}

			slave->RVol_MIDIFSet = slave->Frequency_Set;
			MIDITranslate(ite, chn, slave, MIDICOMMAND_PLAYNOTE);
		} else {
			// Change in volume?
			if((slave->Flags & 64) != 0)
				MIDITranslate(ite, chn, slave, MIDICOMMAND_CHANGEVOLUME);
		}

		// Pan changed?
		if((slave->Flags & 0x8000) != 0)
		{
			if(ite->MIDIPan[slave->MCh-1] != slave->FPP)
			{
				slave->FPP = ite->MIDIPan[slave->MCh-1];
				MIDITranslate(ite, chn, slave, MIDICOMMAND_CHANGEPAN);
			}
		}

		// Pitch changed?
		if((slave->Flags & 32) != 0)
			MIDITranslate(ite, chn, slave, MIDICOMMAND_CHANGEPITCH);
	}
}

void UpdateInstruments(it_engine *ite)
{
	// Things to update:
	//  1) Volume envelope
	//  2) Fadeout
	//  3) FinalVolume
	//  4) Vibrato.
	// Turn off channel if
	//  1) Volume envelope is off & VEV = 0 or
	//  2) Fadeout = 0

	uint16_t cx;
	it_slave *slave;
	ite->DoMIDICycle = 0;

	for(cx = MAXSLAVECHANNELS, slave = &ite->slave[0]; cx != 0; cx--, slave++)
	{
		if((slave->Flags & 1) != 0)
			UpdateInstruments16(ite, slave); // Channel on!!
	}

	if(ite->DoMIDICycle != 0)
		UpdateMIDI(ite);
}

void UpdateInstruments16(it_engine *ite, it_slave *slave)
{
	// Mov     CX, [SI]

	it_instrument *ins = &ite->ins[slave->InsOffs-1];

	if(slave->Ins == 0xFF) { // No instrument?
		UpdateInstruments5(ite, slave);
		return;
	}

	uint16_t bp = (slave->Flags & 4); // BP = sustain for envelope calls

	//printf("InsOffs %i %i %i %04X\n", slave->InsOffs, slave->Ins, bp, slave->Flags);

	if((slave->Flags & 0x4000) != 0)
		if(UpdateEnvelope(ite, &slave->Pt, &ins->PitchEnv, bp) != 0)
			slave->Flags &= ~0x4000;

	int do_filter = ((ins->PitchEnv.Flg & 0x80) != 0);

	if(do_filter && slave->Smp != 100)
	{
		int16_t fval = (slave->Pt.EnvelopeValue>>8);
		fval >>= 6; // Range -128 to +128
		fval += 128; // Range 0 -> 256

		if(fval >= 0x100) fval--;
		slave->FCut = fval;

		slave->Flags |= 64; // Recalculate final volume

	} else if(!do_filter) {
		int16_t ptval = (slave->Pt.EnvelopeValue>>8);
		ptval >>= 3;

		if(ptval != 0)
		{
#if !USEFPUCODE
			if(ptval >= 0)
			{
#endif
				PitchSlideUpLinear(ite, &ite->chn[slave->HCOffst], slave, ptval);
#if !USEFPUCODE
			} else {
				PitchSlideDownLinear(ite, &ite->chn[slave->HCOffst], slave, -ptval);
			}
#endif
			slave->Flags |= 32; // Recalculate freq
		}
	}

	if((slave->Flags & 0x2000) != 0)
	{
		slave->Flags |= 2; // Recalculate pan

		if(UpdateEnvelope(ite, &slave->P, &ins->PanEnv, bp) != 0)
			slave->Flags &= ~0x2000;
	}

	// Volume envelope on?
	int jump_ins17 = 0;
	if((slave->Flags & 0x1000) != 0)
	{
		slave->Flags |= 16; // Recalculate volume

		if(UpdateEnvelope(ite, &slave->V, &ins->VolEnv, bp) == 0)
		{
			// Note fade on?
			if((slave->Flags & 8) != 0)
			{
				// Now, check if loop + sustain
				// off
				// TODO: verify - i may have missed a bp modification --GM
				if(bp == 0) {
					UpdateInstruments5(ite, slave);
					return;
				}

				// Normal vol env loop?
				if((ins->VolEnv.Flg & 2) == 0) {
					UpdateInstruments5(ite, slave); // Volume calculation
					return;
				}
			}

		} else {
			// Envelope turned off...
			slave->Flags &= ~0x1000; // Turn off envelope flag

			// Turn off if end of loop is reached
			// TODO: verify - this looks weird
			if(((slave->V.EnvelopeValue>>16)&0xFF) == 0)
				jump_ins17 = 1;
		}

	} else {
		if((slave->Flags & 8) == 0) // Note fade??
		{
			// Also apply fade if No vol env
			// AND sustain off

			// Note off issued?
			if((slave->Flags & 4) == 0) {
				UpdateInstruments5(ite, slave);
				return;
			}
		}
	}

	if(!jump_ins17)
	{
		// in the original, this line is skipped when the bit is set
		// maintaining that would just make for a horrible flow --GM
		slave->Flags |= 8;

		slave->FadeOut -= ins->FadeOut;
		if(slave->FadeOut <= 0)
		{
			slave->FadeOut = 0;
		} else {
			slave->Flags |= 16; // Recalc volume flag.
			UpdateInstruments5(ite, slave);
			return;
		}
	}

	// Turn off channel
	if((slave->HCN & 0x80) == 0)
	{
		slave->HCN |= 0x80;
		// Host channel exists
		ite->chn[slave->HCOffst].Flags &= ~4;
	}

	slave->Flags |= 0x0200;
	slave->Flags |= 16; // Recalc volume flag.

	UpdateInstruments5(ite, slave);
}

void UpdateInstruments5(it_engine *ite, it_slave *slave)
{
	if((slave->Flags & 16) != 0)
	{
		// Calculate volume
		slave->Flags &= ~16;
		//Mov     DX, Word Ptr [SoloSample]       ; DL = sample, DH = inst

		slave->Flags |= 64; // Recalc final volume
		uint32_t eax = slave->Vol; // Note volume...
		eax *= (uint32_t)slave->CVl; // Channel volume
		// AX = (0->4096)

		if(ite->SoloSample != 0xFF && ite->SoloSample != slave->Smp)
			slave->Flags |= 0x0800;
		else if(ite->SoloInstrument != 0xFF && ite->SoloInstrument != slave->Ins)
			slave->Flags |= 0x0800;

		eax *= slave->FadeOut;
		// Fadeout Vol (0->1024)
		// DX:AX = 0->4194304

		eax >>= 7; // AX = (0->32768)

		eax *= slave->SVl; // Sample volume
		// DX:AX = 0->4194304

		eax >>= 7; // AX = 0->32768

		eax *= (0xFFFF & (slave->V.EnvelopeValue>>8)); // VEV, 0->64*256
		// DX:AX = 0->536870912

		eax >>= 14; // AX = 0->32768

		eax *= ite->GlobalVolume; // DX:AX = 0->4194304

		eax >>= 7;

		//printf("VOL %04X %02X\n", slave->_16bVol, slave->FV);
		slave->FV = eax>>8;
		slave->_16bVol = eax;
	}

	// Change in panning?
	if((slave->Flags & 2) != 0)
	{
		slave->Flags &= ~2;
		slave->Flags |= 0x8000;

		int16_t ax = slave->Pan; // actual pan
		if(ax != 100)
		{
			// some really funky stuff going on here.
			// i could possibly simplify this but i'd like to be accurate.
			// so, here we go for now --GM
			ax = 32 - ax;
			ax ^= (ax>>8);
			ax = (ax & 0xFF00) | ((ax - (ax>>8)) & 0xFF);
			// AL = |32-ActualPan|

			ax = (ax & 0xFF00) | ((32 - ax) & 0xFF);

			ax = (ax & 0xFF) * (int16_t)(int8_t)(
				slave->Pt.EnvelopeValue>>16); // Pan envelope..
			ax >>= 5;
			ax = (ax & 0xFF00) + ((ax + slave->Pan) & 0xFF);

			// Value to show..
			slave->Pan = ax;

			ax = (ax & 0xFF00) + ((ax - 32) & 0xFF);

			// Pan = (Pan-32)* Separation/128 + 32
			uint8_t sep = ite->hdr.Sep; // 0->64 (Separation)
			sep >>= 1;
			ax = (ax & 0xFF) & (int8_t)sep;
			// AX = -2048->+2048
			// ie. AH = -8->+8
			ax >>= 6; // AL = -32->+32

			if(ite->d.ReverseChannels != 0)
				ax = -ax;

			ax += 32;

		} else {
			slave->FP = ax;
		}

		slave->FPP = ax;
	}

	UpdateVibrato(ite, slave);

	if(slave->Smp == 100) // MIDI?
		ite->DoMIDICycle = 1;
}

void UpdateData(it_engine *ite)
{
	if(ite->PlayMode == 1)
		UpdateData_PlayMode1(ite);
	else if(ite->PlayMode < 1)
		UpdateData_PlayMode0(ite);
	else
		UpdateData_PlayMode2(ite);
}

void UpdateData_PlayMode0(it_engine *ite)
{
	uint16_t cx;
	it_host *chn;

	// Step through each channel.
	// Check if on/updatemode OK
	for(cx = 64, chn = &ite->chn[0]; cx != 0; cx--, chn++)
	{
		if(chn->CUC == 0) continue;

		chn->CUC--; // Handle counter

		if(chn->CUC != 0xFF)
			chn->Flags &= ~0x303; // Turn off mode.

		// Mov     AX, [DI]

		// Channel on? / Don't update effect?
		if((chn->Flags & 4) != 0 && (chn->Flags & 0x100) != 0)
			VolumeEffectTable[chn->VCm & 7](ite, chn);

		if((chn->Flags & 2) != 0 // Update effect regardless..
			|| ((chn->Flags & 4) != 0 && (chn->Flags & 1) != 0))
		{
			// OK. now handle counter.
			CommandTable[chn->Cmd & 31](ite, chn);
		}

		// Progress to next channel
	}

	// That's all!
}

void UpdateData_NoNewRow(it_engine *ite)
{
	it_host *chn;
	uint16_t cx;

	// OK. call update command.

	for(chn = &ite->chn[0], cx = 64; cx != 0; cx--, chn++)
	{
		if((chn->Flags & 4) != 0 && (chn->Flags & 0x100) != 0)
			VolumeEffectTable[chn->VCm & 7](ite, chn);

		if((chn->Flags & 3) == 0) continue;
		if((chn->Flags & 2) == 0 && (chn->Flags & 4) == 0) continue;

		//printf("%i %04X\n", chn->Flags);
		CommandTable[chn->Cmd & 31](ite, chn);
	}
}

void UpdateData_PlayMode1(it_engine *ite)
{
	// Pattern stuff..
	ite->ProcessTick--;
	ite->CurrentTick--;
	if(ite->CurrentTick != 0)
	{
		UpdateData_NoNewRow(ite);
		return;
	}

	// OK... have to update row.
	ite->CurrentTick = ite->ProcessTick = ite->CurrentSpeed;

	ite->RowDelay--;
	if(ite->RowDelay != 0) {
		UpdateEffectData(ite);
		return;
	}

	ite->RowDelay = 1;

	uint16_t ax = ite->ProcessRow+1; // Progress to new row.
	if(ax >= ite->NumberOfRows)
	{
		if(ite->d.StopEndOfPlaySection != 0)
		{
			Music_Stop(ite); // Optionally.. loop!
			return;
		}

		// Wrap row.
		ax = ite->BreakRow;
		ite->BreakRow = 0;
	}

	ite->ProcessRow = ite->CurrentRow = ax;

	// Gotta get note data.
	UpdateNoteData(ite);
	return;
}

void UpdateEffectData(it_engine *ite)
{
	uint16_t cx;
	it_host *chn;

	for(chn = &ite->chn[0], cx = 64; cx != 0; cx--, chn++)
	{
		if((chn->Flags & 64) == 0) continue;
		if((chn->Msk & 0x88) == 0) continue;

		uint8_t oldmsk = chn->Msk;
		chn->Msk &= 0x88;

		InitCommandTable[chn->Cmd & 0x1F](ite, chn); // Init note

		chn->Msk = oldmsk;
	}
}

void UpdateData_PlayMode2(it_engine *ite)
{
	ite->ProcessTick--;
	ite->CurrentTick--;
	if(ite->CurrentTick != 0)
	{
		UpdateData_NoNewRow(ite);
		return;
	}

	// Play song stuff...

	ite->CurrentTick = ite->ProcessTick = ite->CurrentSpeed;

	ite->RowDelay--;
	if(ite->RowDelay != 0) {
		UpdateEffectData(ite);
		return;
	}

	ite->RowDelay = 1;

	uint16_t ax = ite->ProcessRow+1;
	if(ax < ite->NumberOfRows)
	{
		ite->ProcessRow = ite->CurrentRow = ax;
		UpdateNoteData(ite);
		return;
	}

	if((ite->OrderLockFlag & 1) == 0)
	{
		int dx = 0;

		// Get new pattern.
		int16_t bx = ite->ProcessOrder + 1;
		uint8_t cl; // might as well emulate any side-effects we get --GM

		for(;;)
		{
			if(bx < 0x100)
			{
				// next pattern
				cl = ite->ord[bx]; // CL = next pattern.
				if(cl < 200)
					break;

				bx++;

				if(cl == 0xFE) continue;

				ite->StopSong = 1;
				if(ite->d.StopEndOfPlaySection != 0)
				{
					Music_Stop(ite); // Optionally.. loop!
					return;
				}
			}

			if(dx != 0)
			{
				Music_Stop(ite); // Optionally.. loop!
				return;
			}

			dx++;
			bx = 0;
		}

		ite->ProcessOrder = ite->CurrentOrder = bx;
		ite->CurrentPattern = cl;
	}

	ite->ProcessRow = ite->CurrentRow = ite->BreakRow;
	ite->BreakRow = 0;
	UpdateNoteData(ite);
}

uint16_t Music_GetNumberOfSamples(it_engine *ite)
{
	// Returns AX
	uint16_t ax = 99;
	it_sample *smp = &ite->smp[99-1];

	while(ax > 0)
	{
		if(memcmp(smp, SampleHeader, 80))
			break;

		ax--;
		smp--;
	}

	return ax;
}

uint16_t Music_GetNumberOfInstruments(it_engine *ite)
{
	// Returns AX
	uint16_t ax = 99;
	it_instrument *ins = &ite->ins[99-1];

	while(ax > 0)
	{
		if(memcmp(ins, InstrumentHeader, 80))
			break;

		ax--;
		ins--;
	}

	return ax;
}

it_sample *Music_GetSampleHeader(it_engine *ite, uint16_t ax)
{
	// AX = sample, 1 based
	return &ite->smp[ax-1];
}

uint8_t *Music_GetSampleLocation(it_engine *ite, uint16_t ax, uint32_t *rcx, int *is8bit)
{
	// AX = sample (1based)
	// CH = page.
	// Returns DS:ESI
	//   ECX = length
	// Carry if no sample. - (using NULL --GM)
	// Zero set if 8 bit     (using a flag --GM)

	ite->LastSample = ax;

	it_sample *smp = &ite->smp[ax-1];
	if((smp->Flg & 1) == 0)
		return NULL;

	if(ite->SamplePointer[ax-1] == NULL)
		return NULL;

	uint8_t *data = ite->SamplePointer[ax-1];

	*rcx = smp->Length;
	*is8bit = ((~smp->Flg) & 1);

	return data;
}

// Accessed via Int 3
uint8_t *Music_UpdateSampleLocation(it_engine *ite, uint32_t esi, int *is8bit)
{
	USED(esi);
	// Reqs ESI.
	// TODO: decipher this crap *properly* --GM

	uint16_t ax = ite->LastSample;
	uint32_t dummy_ecx;
	return Music_GetSampleLocation(ite, ax, &dummy_ecx, is8bit);
}

uint8_t *Music_FarUpdateSampleLocation(it_engine *ite, uint32_t esi, int *is8bit)
{
	return Music_UpdateSampleLocation(ite, esi, is8bit);
}

void Music_GetPlayMode(it_engine *ite, uint16_t *PlayMode, uint16_t *CurrentRow,
	uint16_t *CurrentPattern, uint16_t *CurrentOrder, uint16_t *NumberOfRows)
{
	*PlayMode = ite->PlayMode;
	*CurrentRow = ite->CurrentRow;
	*CurrentPattern = ite->CurrentPattern;
	*CurrentOrder = ite->CurrentOrder;
	*NumberOfRows = ite->NumberOfRows;
}

void Music_GetPlayMode2(it_engine *ite, uint16_t *PlayMode, uint16_t *CurrentRow,
	uint16_t *CurrentTick, uint32_t *CurrentOrder)
{
	*PlayMode = ite->PlayMode;
	*CurrentOrder = ite->CurrentOrder;
	*CurrentRow = ite->CurrentRow;
	*CurrentTick = ite->CurrentTick;
}

void Music_PlayPattern(it_engine *ite, uint16_t pidx, uint16_t numrows, uint16_t startrow)
{
	// AX = pattern, BX = number of rows
	// CX = row to start

	Music_Stop(ite);

	ite->MIDIPitchDepthSent = 0;
	ite->LastMIDIByte = 0xFF;
	ite->CurrentPattern = pidx;
	ite->CurrentRow = startrow;
	ite->NumberOfRows = numrows;
	ite->ProcessRow = startrow-1;
	ite->PlayMode = 1;
}

void Music_PlaySong(it_engine *ite, uint16_t oidx)
{
	// AX = Order

	Music_Stop(ite);

	ite->MIDIPitchDepthSent = 0;
	ite->LastMIDIByte = 0xFF;
	ite->CurrentOrder = oidx;
	ite->ProcessOrder = oidx-1;
	ite->ProcessRow = 0xFFFE;
	ite->PlayMode = 2;

	StartClock(ite);

	MIDITranslate(ite, ite->chn, ite->slave, MIDICOMMAND_START);
}

void Music_PlayPartSong(it_engine *ite, uint16_t oidx, uint16_t row)
{
	// AX = order, BX = row.

	Music_Stop(ite);

	ite->NumberOfRows = 200;
	ite->ProcessOrder = oidx;
	ite->CurrentOrder = oidx;
	ite->CurrentRow = row;
	ite->ProcessRow = row-1;

	ite->CurrentPattern = ite->ord[oidx];

	ite->PlayMode = 2;

	StartClock(ite);
}

void Music_KBPlaySong(it_engine *ite)
{
	if(ite->PlayMode != 2)
		Music_PlaySong(ite, 0);
}

void Music_StopChannels(it_engine *ite)
{
	uint16_t cx;
	it_host *chn;
	it_slave *slave;

	for(chn = &ite->chn[0], cx = 64; cx != 0; cx--, chn++)
	{
		chn->Flags = 0;
		chn->PLR = 0;
		chn->PLC = 0;
	}

	for(slave = &ite->slave[0], cx = MAXSLAVECHANNELS; cx != 0; cx--, slave++)
	{
		if((slave->Flags & 1) != 0 && slave->Smp == 100)
			MIDITranslate(ite, &ite->chn[slave->HCOffst], slave, MIDICOMMAND_STOPNOTE);

		slave->Flags = 0x200;
	}
}

void Music_Stop(it_engine *ite)
{
	uint16_t cx;
	it_host *chn;
	it_slave *slave;

	// Turn off MIDI channels first.
	if((ite->OrderLockFlag & 1) != 0)
		Music_ToggleOrderUpdate(ite);

	for(slave = &ite->slave[0], cx = MAXSLAVECHANNELS; cx != 0; cx--, slave++)
	{
		if((slave->Flags & 1) != 0 && slave->Smp == 100)
			MIDITranslate(ite, &ite->chn[slave->HCOffst], slave, MIDICOMMAND_STOPNOTE);
	}

	// Stop
	MIDITranslate(ite, &ite->chn[0], &ite->slave[0], MIDICOMMAND_STOP);

	ite->PlayMode = 0;

	ite->DecodeExpectedPattern = 0xFFFE;
	ite->DecodeExpectedRow = 0xFFFE;
	ite->RowDelay = 1;
	ite->CurrentRow = 0;
	ite->CurrentOrder = 0;
	ite->CurrentTick = 1;
	ite->BreakRow = 0;

	memset(ite->MIDIPrograms, 0xFF, 32*2);

	uint8_t dl;
	uint8_t dh;
	for(chn = &ite->chn[0], dl = 64, dh = 0; dl != 0; dl--, chn++, dh++)
	{
		chn->Flags = 0; chn->Msk = 0; chn->Nte = 0;
		chn->Ins = 0; chn->Vol = 0; chn->Cmd = 0; chn->CVal = 0;
		chn->OCm = 0; chn->OVal = 0; chn->VCm = 0; chn->VVal = 0;
		chn->MCh = 0; chn->MPr = 0; chn->Nt2 = 0; chn->Smp = 0;

		chn->DKL = 0; chn->EFG = 0; chn->O00 = 0; chn->I00 = 0;
		chn->J00 = 0; chn->M00 = 0; chn->N00 = 0; chn->P00 = 0;
		chn->Q00 = 0; chn->T00 = 0; chn->S00 = 0; chn->OxH = 0;
		chn->W00 = 0; chn->VCE = 0; chn->GOE = 0; chn->SFx = 0;

		chn->HCN = dh; chn->CUC = 0; chn->VSe = 0; chn->LTr = 0;
		chn->SCOffst = 0; chn->PLR = 0; chn->PLC = 0;
		chn->PWF = 0; chn->PPo = 0; chn->PDp = 0; chn->PSp = 0;
		chn->LPn = 0; chn->LVi = 0;
		chn->CP = ite->hdr.Chnl_Pan[dh] & 0x7F; chn->CV = ite->hdr.Chnl_Vol[dh];

		chn->VCh = 0; chn->TCD = 0; chn->Too = 0; chn->RTC = 0;
		chn->Porta_Frequency = 0;
		chn->VWF = 0; chn->VPo = 0; chn->VDp = 0; chn->VSp = 0;
		chn->TWF = 0; chn->TPo = 0; chn->TDp = 0; chn->TSp = 0;

		chn->_40 = 0; chn->_42 = 0;
		chn->_44 = 0; chn->_46 = 0; chn->_47 = 0;
		chn->_48 = 0; chn->_49 = 0; chn->_4A = 0; chn->_4B = 0;
		chn->_4C = 0; chn->_4D = 0; chn->_4E = 0; chn->_4F = 0;
	}

	// Now clear SlaveChannel
	//Mov     DX, MAXSLAVECHANNELS
	for(slave = &ite->slave[0], cx = MAXSLAVECHANNELS; cx != 0; cx--, slave++)
	{
		// too much bloody effort, using memset --GM
		//memset(slave, 0, 128);
		memset(slave, 0, sizeof(it_slave));
		slave->Flags = 0x0200;
	}

	ite->GlobalVolume = ite->hdr.GV;
	ite->CurrentSpeed = ite->hdr.IS;
	ite->ProcessTick = ite->hdr.IS;
	ite->Tempo = ite->hdr.IT;

	Music_InitTempo(ite);
	MIDI_ClearTable(ite);
}

void Music_UpdatePatternOffset(it_engine *ite)
{
	ite->DecodeExpectedPattern = 0xFFFE;
}

void Music_PlayNote(it_engine *ite, uint8_t *data, uint16_t cidx, uint8_t dh)
{
	//DS:SI points to 5-note struct
	// AX = channel
	// DH = +32 means ignore mute
	//      settings
	// DH = +128 means to use central
	//       pan and max volume.

	it_host *chn = &ite->chn[cidx&0xFF];

	uint8_t dl = 0; // DL = mask
	uint8_t al;

	al = *(data++);
	if(al != NONOTE)
	{
		dl |= 1;
		chn->Nte = al;
	}

	al = *(data++);
	if(al != 0)
	{
		dl |= 2;
		chn->Ins = al;
	}

	al = *(data++);
	if(al != 0xFF)
	{
		dl |= 4;
		chn->Vol = al;
	}

	al = *(data++);
	uint8_t ah = *(data++);
	USED(data);
	if(al != 0 || ah != 0)
		dl |= 8;

	chn->Cmd  = chn->OCm  = al;
	chn->CVal = chn->OVal = ah;
	chn->Msk = dl;
	chn->Flags &= ~(3+32+64+256);
	chn->Flags |= dh & 0x7F; // Now for command update count

	chn->CUC = ite->CurrentSpeed;
	PreInitCommand(ite, chn);

	if((chn->Flags & 4) != 0 && (dh & 128) != 0)
	{
		it_slave *slave = &ite->slave[chn->SCOffst];

		slave->Pan = slave->PS = 0x20; // Pan and pan set.
		slave->CVl = 0x40; // Full channel volume.
	}

	ite->DecodeExpectedRow = 0xFFFE;
}

void Music_PlaySample(it_engine *ite, uint8_t note, uint8_t sidx, uint16_t cidx)
{
	// AL = Note
	// AH = sample number
	// CX = channel.

	it_host *chn = &ite->chn[cidx];

	chn->Msk = 3; // Note & Sample
	chn->Nte = note;
	chn->Vol = 0xFF;
	chn->Ins = 0xFF;
	chn->Nt2 = note;
	chn->Smp = sidx;
	chn->Flags |= 0x8020;

	InitNoCommand(ite, chn);

	if((chn->Flags & 4) != 0)
	{
		it_slave *slave = &ite->slave[chn->SCOffst];
		slave->Pan = slave->PS = 0x20;
		slave->CVl = 0x40; // Full channel volume.
		slave->NNA = 0; // Note cut.
		slave->DCT = 0;
	}

	chn->Flags &= ~0x8000;

	ite->DecodeExpectedRow = 0xFFFE;
}

it_host *Music_GetHostChannelInformationTable(it_engine *ite)
{
	return &ite->chn[0];
}

it_slave *Music_GetSlaveChannelInformationTable(it_engine *ite, uint16_t *count)
{
	if(count != NULL) *count = MAXSLAVECHANNELS;
	return &ite->slave[0];
}

void Music_NextOrder(it_engine *ite)
{
	if(ite->PlayMode != 2)
		return;

	ite->PlayMode = 0;
	Music_StopChannels(ite);
	ite->ProcessRow = 0xFFFE;
	ite->CurrentTick = 1;
	ite->RowDelay = 1;
	ite->PlayMode = 2;
}

void Music_LastOrder(it_engine *ite)
{
	if(ite->PlayMode != 2) return;
	if(((int16_t)ite->ProcessOrder) <= 0) return;

	ite->PlayMode = 0;
	Music_StopChannels(ite);
	ite->ProcessOrder = ite->ProcessOrder - 2;
	ite->ProcessRow = 0xFFFE;
	ite->CurrentTick = 1;
	ite->RowDelay = 1;
	ite->PlayMode = 2;
}

void Music_SetGlobalVolume(it_engine *ite, uint8_t al)
{
	ite->GlobalVolume = al;
	RecalculateAllVolumes(ite);
}

void Music_MuteChannel(it_engine *ite, uint16_t ax)
{
	// AX = channel number
	uint16_t cx = ite->NumChannels;
	it_slave *slave = &ite->slave[0];

	for(; cx != 0; cx--, slave++)
	{
		if((slave->Flags & 1) == 0) continue;
		if((slave->HCN & 0x7F) != (ax & 0xFF)) continue;
		slave->Flags |= 0x0840;
	}
}

void Music_UnmuteChannel(it_engine *ite, uint16_t ax)
{
	// AX = channel number
	uint16_t cx = ite->NumChannels;
	ite->SoloSample = 0xFF;
	ite->SoloInstrument = 0xFF;
	it_slave *slave = &ite->slave[0];

	for(; cx != 0; cx--, slave++)
	{
		if((slave->Flags & 1) == 0) continue;
		if((slave->HCN & 0x7F) != (ax & 0xFF)) continue;
		slave->Flags &= ~0x0800;
		slave->Flags |= 64;
	}
}

void Music_ToggleChannel(it_engine *ite, uint16_t ax)
{
	// AX = channel number.

	if((ite->hdr.Chnl_Vol[ax] & 0x80) != 0)
	{
		ite->hdr.Chnl_Vol[ax] &= 0x7F;
		Music_UnmuteChannel(ite, ax);
		ite->MuteChannelTable[ax] = 0;
	} else {
		// Mute channel
		ite->MuteChannelTable[ax] ^= 1;
		ite->hdr.Chnl_Vol[ax] |= 0x80;
		Music_MuteChannel(ite, ax);
	}
}

void Music_UnmuteAll(it_engine *ite)
{
	// solo pressed on already soloed
	// channel -> turn everything on.
	uint16_t cx = 64;

	for(; cx != 0; cx--)
		if(ite->MuteChannelTable[cx-1] == 1)
			Music_ToggleChannel(ite, cx-1);
}

void Music_SoloChannel(it_engine *ite, uint16_t ax)
{
	// AX = channel

	// Check & count whether any playing.
	uint8_t *si = &ite->hdr.Chnl_Vol[0];
	uint16_t cx = 64;
	uint16_t dx = 64;
	for(; cx != 0; cx--)
		dx -= ((*(si++))>>7)&1; // no way to represent this properly in C --GM

	// DX = num playing.
	// check whether it's the current
	if(dx == 1 && (ite->hdr.Chnl_Vol[ax] & 0x80) == 0) {
		Music_UnmuteAll(ite);
		return;
	}

	// 64 channel to step through
	// turn 'em all off.
	cx = 64;
	for(; cx != 0; cx--)
	{
		if(cx-1 == ax)
		{
			if((ite->hdr.Chnl_Vol[cx-1] & 0x80) == 0)
				Music_ToggleChannel(ite, ax);
		} else {
			if((ite->hdr.Chnl_Vol[cx-1] & 0x80) != 0)
				Music_ToggleChannel(ite, ax);
		}
	}
}

void Music_InitMuteTable(it_engine *ite)
{
	memset(&ite->MuteChannelTable[0], 0, 64);
	ite->SoloSample = 0xFF;
	ite->SoloInstrument = 0xFF;
	ite->AllocateNumChannels = 0;
}

void Music_InitStereo(it_engine *ite)
{
	ite->d.DriverSetStereo(ite, ite->hdr.Flags & 1);
	RecalculateAllVolumes(ite);
}

// note, increase means go faster, not increase number! --GM
uint16_t Music_IncreaseSpeed(it_engine *ite)
{
	// Returns AX = speed
	uint16_t ax = ite->CurrentSpeed;
	if(ax != 1)
	{
		ax--;
		ite->CurrentSpeed = ax;
		ite->hdr.IS = ax;
	}

	return ax;
}

uint16_t Music_DecreaseSpeed(it_engine *ite)
{
	// Returns AX = speed
	uint16_t ax = ite->CurrentSpeed;
	if(ax != 0xFF)
	{
		ax++;
		ite->CurrentSpeed = ax;
		ite->hdr.IS = ax;
	}

	return ax;
}

void Music_SetSoundCard(it_engine *ite, uint8_t al)
{
	// AL contains sound card num
	if(ite->DriverName != NULL) free(ite->DriverName);
	ite->DriverName = strdup(DriverNameTable[DriverSoundCard[al]]);
}

void Music_SetSoundCardDriver(it_engine *ite, const char *dssi)
{
	// improvised a bit here --GM
	if(ite->DriverName != NULL) free(ite->DriverName);
	ite->DriverName = strdup(dssi);
}

// exactly why am I doing this stuff? --GM
void Music_SetDMA(it_engine *ite, uint8_t al)
{
	ite->d.DMA = al;
}

void Music_SetMixSpeed(it_engine *ite, uint16_t cx)
{
	ite->d.CmdLineMixSpeed = cx;
}

void Music_SetIRQ(it_engine *ite, uint16_t cx)
{
	ite->d.IRQ = cx;
}

void Music_SetAddress(it_engine *ite, uint16_t dx)
{
	ite->d.BasePort = dx;
}

void Music_GetDisplayVariables(it_engine *ite, uint16_t *CurrentSpeed,
	uint16_t *Tempo, uint16_t *GlobalVolume)
{
	*CurrentSpeed = ite->CurrentSpeed;
	*Tempo = ite->Tempo;
	*GlobalVolume = ite->GlobalVolume;
}

int16_t Music_AssignSampleToInstrument(it_engine *ite, uint16_t bx)
{
	uint16_t cx;
	int i;

	// BX = sample num
	// returns AX
	// (returns -1 instead of carry on error --GM)

	// Check for sample-number's instrument first.
	// FIXME: probably an off-by-one here --GM
	it_instrument *ins = &ite->ins[bx];

	uint16_t ax = bx+1;

	if(memcmp(ins, InstrumentHeader, 554))
	{
		// Search
		cx = 99;

		ins = &ite->ins[0]; // Points to first inst.
		ax = 1;

		for(; cx != 0; cx--, ax++, ins++)
			if(!memcmp(ins, InstrumentHeader, 554))
				break;

		if(cx == 0)
			return -1;
	}

#if NETWORKENABLED
	uint8_t *nsq = Network_GetSendQueue(ite);

	if(nsq != NULL)
	{
		*(nsq++) = 0x00;
		*(nsq++) = 0x04;
		*(nsq++) = ax-1;
	}

	Network_FinishedSendQueue(ite, nsq);
#endif

	ins = &ite->ins[ax-1];
	it_sample *smp = &ite->smp[bx];

	memcpy(ins->Name, smp->Name, 26);

	// Now to fill in table.
	bx++;
	for(cx = 120, i = 0; cx != 0; cx--, i++)
		ins->NoteSamp[i][1] = bx;

	return ax;
}

void Music_SetLimit(it_engine *ite, uint16_t cx)
{
	ite->CmdLineNumChannels = cx;
}

void Music_ReverseChannels(it_engine *ite, uint16_t cx)
{
	USED(cx);
	ite->d.ReverseChannels = 1;
}

void Music_IncreaseVolume(it_engine *ite)
{
	if(ite->GlobalVolume < 128)
	{
		ite->GlobalVolume++;
		RecalculateAllVolumes(ite);
	}
}
void Music_DecreaseVolume(it_engine *ite)
{
	if(ite->GlobalVolume != 0)
	{
		ite->GlobalVolume--;
		RecalculateAllVolumes(ite);
	}
}

void Music_RegetLoopInformation(it_engine *ite)
{
	uint16_t cx;
	it_slave *slave;

	for(cx = ite->NumChannels, slave = &ite->slave[0]; cx != 0; cx--, slave++)
	{
		if((slave->Flags & 1) == 0) continue;

		GetLoopInformation(ite, slave);
		slave->Flags |= 0x40;
	}
}

void ResetSoundCardMemory(it_engine *ite)
{
	ite->d.DriverResetMemory(ite);
}

int Music_SoundCardLoadSample(it_engine *ite, uint16_t sidx)
{
	// AX = sample number
	// (1 based)
	// Carry set if insuf mem

	// incorrect - it's CLEAR on insufficient memory
	// anyway, we're just returning -1 on fail, 0 on pass --GM

	if(ite->d.DriverLoadSample(ite, sidx) == -1)
	{
		M_Object1List(ite, &O1_OutOfSoundCardMemoryList, 2);
		return -1;
	}

	return 0;
}

uint16_t Music_SoundCardLoadAllSamples(it_engine *ite)
{
	Music_Stop(ite);
	S_SaveScreen(ite);

	S_DrawBox(ite, 3, 30, 50, 28, 30);
	S_DrawString(ite, 32, 29, PrepareSamplesMsg, 0x20);
	S_UpdateScreen(ite);

	ResetSoundCardMemory(ite);

	uint16_t ax;
	for(ax = 1; ax <= 100; ax++)
		if(Music_SoundCardLoadSample(ite, ax) != 0)
			return 1;

	S_RestoreScreen(ite);

	return 1;
}

int Music_GetFreeSoundCardMemory(it_engine *ite)
{
	// TODO: find out how this works --GM
	return ite->d.DriverGetStatus(ite);
}

uint16_t Music_GetNumChannels(it_engine *ite)
{
	return ite->NumChannels;
}

const uint32_t *Music_GetPitchTable(it_engine *ite)
{
	USED(ite);
	// Returns ES:DI to pitch table
	return PitchTable;
}

void Music_ToggleReverse(it_engine *ite)
{
	ite->d.ReverseChannels ^= 1;
	RecalculateAllVolumes(ite);
	SetInfoLine(ite, ReverseMsg);
}

void Music_PatternStorage(it_engine *ite, uint8_t al)
{
	// this won't mean a damn thing --GM
	ite->PatternStorage = al;
}

void Music_InitMixTable(it_engine *ite)
{
	ite->d.DriverSetMixVolume(ite, ite->hdr.MV); // AL = 0->128
}

uint16_t Music_GetTempo(it_engine *ite)
{
	// returns in BX --GM
	return ite->Tempo;
}

uint16_t Music_GetLastChannel(it_engine *ite)
{
	// Returns AX
	uint16_t cx = 64;
	uint16_t ax = 0;
	uint16_t dx = 0;

	for(; cx != 0; cx--, dx++)
		if(((ite->hdr.Chnl_Vol[dx]>>7)^(ite->MuteChannelTable[dx])) == 0)
			ax = dx;

	return ax;
}

int Music_GetDriverScreen(it_engine *ite)
{
	return ite->d.DriverSoundCardScreen(ite);
}

int Music_GetDriverVariable(it_engine *ite, uint16_t Var)
{
	return ite->d.DriverGetVariable(ite, Var);
}

int Music_SetDriverVariable(it_engine *ite, uint16_t Var, uint16_t Thing)
{
	return ite->d.DriverSetVariable(ite, Var, Thing);
}

void Music_SetNextOrder(it_engine *ite, uint16_t order)
{
	ite->ProcessOrder = order-1;
}

uint16_t Music_GetDelay(it_engine *ite)
{
	// returns in DX --GM

	// in case you're wondering,
	// this generates SDx delay effect information for liveplay --GM

	if(ite->PlayMode == 0)
		return 0;

	uint16_t dx = ite->CurrentSpeed;

	if(dx == ite->CurrentRow)
	{
		dx -= ite->ProcessTick;
		if(dx == 0) return 0;

		if(dx >= 0x0F)
			dx = 0x0F;
	} else {
		dx--;

		if(dx >= 0x0F)
			dx = 0x0F;
	}

	dx <<= 8;
	dx |= 'S'-'@' + 0xD000;

	return dx;
}

int InternalTimer(it_engine *ite, uint16_t bx)
{
	// Ticks = (1193181/(2*0.4))/Tempo
	uint32_t eax = 0x16C214 / (uint32_t)bx;
	ite->TimerCounter = eax*2;

	return 0;
}

int Music_TimeSong(it_engine *ite)
{
	// Time song!

	S_SaveScreen(ite);

	S_SetDirectMode(ite, 1);
	S_DrawSmallBox(ite);

	S_DrawString(ite, 33, 26, PleaseWaitMsg, 0x20);

	Music_Stop(ite);

	/*
	Mov     CX, 0FFFFh

Music_TimeSong1:
	In      AL, 21h                 // Delay mechanism
	In      AL, 0A1h                 // Delay mechanism
	Loop    Music_TimeSong1
	*/

	ite->StopSong = 0;
	ite->TotalTimer = 0;
	ite->TotalTimerHigh = 0;

	// appears to disable interrupts --GM
	/*
	ClI
	In      AL, 0A1h
	Mov     AH, AL
	In      AL, 21h
	Push    AX

	Mov     AL, 0FFh
	Out     0A1h, AL
	Out     21h, AL
	*/

	int (*OldDriverSetTempo)(it_engine *ite, uint16_t Tempo) = ite->d.DriverSetTempo;
	ite->d.DriverSetTempo = InternalTimer;
	uint16_t OldNumChannels = ite->NumChannels;
	uint16_t OldFlags = ite->hdr.Flags;
	ite->hdr.Flags &= ~4;

	Music_PlaySong(ite, 0);

	for(;;)
	{
		uint16_t cx;
		it_slave *si;
		uint16_t ax;
		Update(ite, &cx, &si, &ax);

		if(ite->StopSong != 0) break;

		ite->TotalTimer += ite->TimerCounter;
		if(ite->TotalTimer < ite->TimerCounter) // cheeky way to do AdC 0 --GM
			ite->TotalTimerHigh++;
	}

	Music_Stop(ite);

	ite->hdr.Flags = OldFlags;
	ite->NumChannels = OldNumChannels;
	ite->d.DriverSetTempo = OldDriverSetTempo;

	/*
	Pop     AX
	Out     21h, AL
	Mov     AL, AH
	Out     0A1h, AL

	StI
	*/

	S_SetDirectMode(ite, 0);

	S_RestoreScreen(ite);

	M_Object1List(ite, &O1_ShowTime, -1);

	return 1;
}

void Music_ShowTime(it_engine *ite)
{
	S_GetDestination(ite); // TODO: work this out --GM

	uint32_t xtime = (ite->TotalTimerHigh<<16) | (ite->TotalTimer>>16);
	USED(xtime);

	D_ShowTime(ite, 43, 27, xtime);
}

uint16_t Music_GetPatternLength(it_engine *ite)
{
	return ite->NumberOfRows;
}

uint16_t Music_SaveMIDIConfig(it_engine *ite)
{
	USED(ite);
	D_GotoStartingDirectory(ite);

	// TODO!
	/*
	Mov     AH, 3Ch
	Xor     CX, CX
	Mov     DX, Offset MIDIConfigFileName
	Int     21h
	JC      Music_SaveMIDIConfig1

	Mov     BX, AX

	Mov     AH, 40h
	Mov     DS, CS:MIDIDataArea
	Xor     DX, DX
	Mov     CX, (128+16+9)*32
	Int     21h

	Mov     AH, 3Eh
	Int     21h

Music_SaveMIDIConfig1:
	Xor     AX, AX
	*/
	return 0;
}

uint8_t *Music_GetMIDIDataArea(it_engine *ite)
{
	USED(ite);
	// TODO!
	//Mov     DS, CS:MIDIDataArea
	return NULL;
}

void Music_ToggleOrderUpdate(it_engine *ite)
{
	const char *msg = OrderUpdateEnabledMsg;
	ite->OrderLockFlag ^= 1;
	if(ite->OrderLockFlag != 0)
		msg = OrderUpdateDisabledMsg;
	USED(msg);

	SetInfoLine(ite, msg);
}

uint16_t Music_ToggleSoloInstrument(it_engine *ite)
{
	return Music_ToggleSolo(ite, SoloInstrumentMsg, &ite->SoloInstrument, 1);
}

uint16_t Music_ToggleSoloSample(it_engine *ite)
{
	return Music_ToggleSolo(ite, SoloSampleMsg, &ite->SoloSample, 0);
}

uint16_t Music_ToggleSolo(it_engine *ite, const char *msg, uint8_t *v, uint16_t bp)
{
	uint16_t bx = PE_GetLastInstrument(ite);
	uint16_t ax = bx;
	bx += bp;

	if(*v != (uint8_t)bx)
	{
		ite->SoloSample = 0xFF;
		ite->SoloInstrument = 0xFF;
		ax++;
		USED(ax);
		*v = bx;
	} else {
		ite->SoloSample = 0xFF;
		ite->SoloInstrument = 0xFF;
		msg = UnsoloMsg;
		USED(msg);
	}

	SetInfoLine(ite, msg);

	uint16_t cx;
	it_slave *slave;

	for(cx = ite->NumChannels, slave = &ite->slave[0]; cx != 0; cx--, slave++)
	{
		slave->Flags |= 18;
		bx = slave->HCN; // BX = channel

		if((ite->hdr.Chnl_Vol[bx] & 0x80) == 0)
			slave->Flags &= ~0x800;
	}

	RecalculateAllVolumes(ite);

	return 1;
}