#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "kernel.h"
#include "swis.h"

#include "ISong.h"
#include "FSong.h"
#include "FSongCtrl.h"
#include "FSysLog.h"
#include "GlobHdr.h"
#include "Loaders.h"
#include "Mem.h"
#include "TimLib:Seq.h"
#ifndef MAKEABS
#include "FFX.h"
#endif

extern void ASM_Song_Changer(void);
extern void ASM_Song_Lister(void);

static const _kernel_oserror Err_StillHandler =
{ErrNum_PlayerError, "At least one TimPlayer song handler is still loaded."};
static const _kernel_oserror unknown_configure_code =
{ErrNum_ParamsError, "Unknown TimPlayer configuration code."};
static const _kernel_oserror Err_Bad_Song_Handle =
{ErrNum_ParamsError, "Invalid song handle."};
static const _kernel_oserror Err_TooManySongHandlers =
{ErrNum_PlayerError, "Too many registered TimPlayer song handlers."};
static const _kernel_oserror Err_InvalidSection =
{ErrNum_ParamsError, "Invalid section number."};
static const _kernel_oserror Err_InvalidChannelNr =
{ErrNum_ParamsError, "Invalid channel number."};
static const _kernel_oserror Err_InvalidMemoryFile =
{ErrNum_ParamsError, "Invalid 'Memory File' pointer or size."};
static const _kernel_oserror Err_NotASong =
{ErrNum_UnsupportedFormat, "Song format not supported."};
static const _kernel_oserror Err_NotACompressedSong =
{ErrNum_UnsupportedFormat, "Song is not compressed or compression format is not supported."};
static const _kernel_oserror Err_SongCannotBePlayed =
{ErrNum_InputError, "The song does not contain any pattern and cannot be played."};
static const _kernel_oserror Err_RangeCannotBePlayed =
{ErrNum_ParamsError, "The range specified does not contain any pattern and cannot be played."};

/**
 * Initializes the song handlers and configuration.
 */
const _kernel_oserror* Songs_Init(GlobHdr* g, void* pw)
{
	IGNORE(pw);

	memset(g->songsptr, 0, sizeof(g->songsptr));

	g->flags |= glb_flag_IgnoreMuteInfoOnLoading;

	return NULL;
}

/**
 * Closes all song handlers.
 */
void Songs_UnloadAll(GlobHdr* g)
{
	int i;

	for (i = 0; i < max_songs; i++)
	{
		if (g->songsptr[i])
		{
			_kernel_swi_regs r;

			r.r[0] = i;
			swi_Song_Unload(g, &r);
		}
	}
}

/**
 * Checks if we can finalizes the module with regards to songs.
 */
const const _kernel_oserror* Songs_Finalize(GlobHdr* g, void* pw)
{
	int i;

	IGNORE(pw);

	for (i = 0; i < max_songs; i++)
	{
		if (g->songsptr[i])
			return &Err_StillHandler;
	}

	return NULL;
}

/**
 * Configure volume ramping option.
 *
 * In:
 *   r[1] 0 = off, 1 = on, -1 to read
 * Out:
 *   r[1] 0 = off, 1 = on
 */
static const _kernel_oserror* swi_Configure_VolumeRamping(GlobHdr* g, _kernel_swi_regs* r)
{
	if (!(r->r[1] & ~1))
	{
		if (r->r[1] & 1)
			g->flags |= glb_flag_VolumeRamping;
		else
			g->flags &= ~glb_flag_VolumeRamping;
	}

	r->r[1] = ((g->flags & glb_flag_VolumeRamping) != 0);

	return NULL;
}

/**
 * Configure ignore sequence end loops.
 *
 * In:
 *   r[1] 0 = off, 1 = on, -1 to read
 * Out:
 *   r[1] 0 = off, 1 = on
 */
static const _kernel_oserror* swi_Configure_IgnoreSequenceEndLoops(GlobHdr* g, _kernel_swi_regs* r)
{
	if (!(r->r[1] & ~1))
	{
		if (r->r[1] & 1)
			g->flags |= glb_flag_IgnoreSeqEndLoop;
		else
			g->flags &= ~glb_flag_IgnoreSeqEndLoop;
	}

	r->r[1] = ((g->flags & glb_flag_IgnoreSeqEndLoop) != 0);

	return NULL;
}

/**
 * Configure ignore sequence end markers.
 *
 * In:
 *   r[1] 0 = off, 1 = on, -1 to read
 * Out:
 *   r[1] 0 = off, 1 = on
 */
static const _kernel_oserror* swi_Configure_IgnoreSequenceEndMarkers(GlobHdr* g, _kernel_swi_regs* r)
{
	if (!(r->r[1] & ~1))
	{
		if (r->r[1] & 1)
			g->flags |= glb_flag_IgnoreSeqEndMark;
		else
			g->flags &= ~glb_flag_IgnoreSeqEndMark;
	}

	r->r[1] = ((g->flags & glb_flag_IgnoreSeqEndMark) != 0);

	return NULL;
}

/**
 * Configure ignore sequence restart position.
 *
 * In:
 *   r[1] 0 = off, 1 = on, -1 to read
 * Out:
 *   r[1] 0 = off, 1 = on
 */
static const _kernel_oserror* swi_Configure_IgnoreSequenceRestartPos(GlobHdr* g, _kernel_swi_regs* r)
{
	if (!(r->r[1] & ~1))
	{
		if (r->r[1] & 1)
			g->flags |= glb_flag_IgnoreSeqRestartPos;
		else
			g->flags &= ~glb_flag_IgnoreSeqRestartPos;
	}

	r->r[1] = ((g->flags & glb_flag_IgnoreSeqRestartPos) != 0);

	return NULL;
}

/**
 * Configure mute info on loading.
 *
 * In:
 *   r[1] 0 = off, 1 = on, -1 to read
 * Out:
 *   r[1] 0 = off, 1 = on
 */
static const _kernel_oserror* swi_Configure_MuteInfoOnLoading(GlobHdr* g, _kernel_swi_regs* r)
{
	if (!(r->r[1] & ~1))
	{
		if (r->r[1] & 1)
			g->flags |= glb_flag_IgnoreMuteInfoOnLoading;
		else
			g->flags &= ~glb_flag_IgnoreMuteInfoOnLoading;
	}

	r->r[1] = ((g->flags & glb_flag_IgnoreMuteInfoOnLoading) != 0);

	return NULL;
}

/**
 * Configure songs related options.
 */
const _kernel_oserror* swi_Configure_Songs(GlobHdr* g, _kernel_swi_regs* r)
{
	switch(r->r[0])
	{
		case 0x100: return swi_Configure_VolumeRamping(g, r);
		case 0x101: return swi_Configure_IgnoreSequenceEndLoops(g, r);
		case 0x102: return swi_Configure_IgnoreSequenceEndMarkers(g, r);
		case 0x103: return swi_Configure_IgnoreSequenceRestartPos(g, r);
		case 0x104: return swi_Configure_MuteInfoOnLoading(g, r);
	}

	return &unknown_configure_code;
}

/**
 * Configure songs related options.
 *
 * In:
 *   r[0] Song handle
 *   r[1] id of song settings
 *   r[2], ... new values of song settings
 * Out:
 *   r[2], ... current values of song settings
 * codes 0   r[2] = Pre-amplification 1 On, 0 Off, <= 65536 use that value, -1 to read
 *       1   r[2] = ignore end of sequence loops. 0 = off, 1 = on, -1 to read
 *       2   r[2] = ignore end of sequence markers. 0 = off, 1 = on, -1 to read
 *       3   r[2] = ignore sequence restart pos. 0 = off, 1 = on, -1 to read
 *       4   r[2] = do not play mute channels. 0 = off, 1 = on, -1 to read
 */
const _kernel_oserror* swi_Song_Configure(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	switch(r->r[1])
	{
		case 0x00:
		{
			if ((r->r[2] >= 0)
			&&  (r->r[2] <= (1 << 16)))
				s->ConfigAmp = r->r[2];

			r->r[2] = s->ConfigAmp;
			Song_SetPreAmp(s);

			return NULL;
		}
		break;
		case 0x01:
		{
			if (!(r->r[2] & ~1))
			{
				if (r->r[2] & 1)
					s->Config |= glb_flag_IgnoreSeqEndLoop;
				else
					s->Config &= ~glb_flag_IgnoreSeqEndLoop;
			}

			r->r[2] = ((s->Config & glb_flag_IgnoreSeqEndLoop) != 0);

			return NULL;
		}
		break;
		case 0x02:
		{
			if (!(r->r[2] & ~1))
			{
				if (r->r[2] & 1)
					s->Config |= glb_flag_IgnoreSeqEndMark;
				else
					s->Config &= ~glb_flag_IgnoreSeqEndMark;
			}

			r->r[2] = ((s->Config & glb_flag_IgnoreSeqEndMark) != 0);

			return NULL;
		}
		break;
		case 0x03:
		{
			if (!(r->r[2] & ~1))
			{
				if (r->r[2] & 1)
					s->Config |= glb_flag_IgnoreSeqRestartPos;
				else
					s->Config &= ~glb_flag_IgnoreSeqRestartPos;
			}

			r->r[2] = ((s->Config & glb_flag_IgnoreSeqRestartPos) != 0);

			return NULL;
		}
		break;
		case 0x04:
		{
			if (!(r->r[2] & ~1))
			{
				if (r->r[2] & 1)
					s->Config &= ~glb_flag_IgnoreMuteInfoOnLoading;
				else
					s->Config |= glb_flag_IgnoreMuteInfoOnLoading;
			}

			r->r[2] = ((s->Config & glb_flag_IgnoreMuteInfoOnLoading) == 0);

			return NULL;
		}
		break;
		case 0x05:
		{
			if (!(r->r[2] & ~1))
			{
				if (r->r[2] & 1)
					s->Hdr.Compatibility |= song_compat_usescalevolume;
				else
					s->Hdr.Compatibility &= ~song_compat_usescalevolume;
			}

			r->r[2] = ((s->Hdr.Compatibility & song_compat_usescalevolume) != 0);
		}
		break;
	}

	return &unknown_configure_code;
}

/**
 * Finds the song corresponding to a given id.
 */
const _kernel_oserror* Song_GetSongHeader(const GlobHdr* g, uint32_t id, ISong** ppSong)
{
	if ((id >= max_songs)
	|| !g->songsptr[id])
		return &Err_Bad_Song_Handle;

	*ppSong = g->songsptr[id];

	return NULL;
}

/**
 * Clears most of the songs data, do not bother to reset pointers.
 */
static void SongHdr_Clear(GlobHdr* g, SongHdr* h)
{
	int i;

	if (h->pLoaderData != NULL)
	{
		CMem_Free(g, h->pLoaderData->pPattern);
		h->pLoaderData->pPattern = NULL;
	}

	CMem_Free(g, h->pName);
	CMem_Free(g, h->pAuthor);
	CMem_Free(g, h->pFullType);
	CMem_Free(g, h->pComments);

	// Free subsongs
	{
		SubSong* psub = h->BaseSong.pNextSubSong;
		while (psub)
		{
			SubSong* pnext = psub->pNextSubSong;
			CMem_Free(g, psub->pSeqs); // + pTimeIndexes + pSecIndexes + pSecTempos
			CMem_Free(g, psub);
			psub = pnext;
		}
		CMem_Free(g, h->BaseSong.pSeqs); // + pTimeIndexes + pSecIndexes + pSecTempos
	}

	// Free patterns
	for (i = 0; i < song_max_patterns; i++)
	{
		CMem_Free(g, h->pPatterns[i].Ptr);
	}

	// Free instruments
	for (i = 0; i < song_max_instruments; i++)
	{
		Instrument* p = &h->pInstruments[i];

		CMem_Free(g, p->pName);
		CMem_Free(g, p->pFilename);
		CMem_Free(g, p->pNotesMap);
		CMem_Free(g, p->VolumeEnvelope.pTable);
		CMem_Free(g, p->PanningEnvelope.pTable);
		CMem_Free(g, p->PitchEnvelope.pTable);
		CMem_Free(g, p->FilterEnvelope.pTable);
	}

	// Free samples
	for (i = 0; i < song_max_samples; i++)
	{
		Sample* p = &h->pSamples[i];

		if ((p->Type & Smp_Type_ExternalPtr) == 0)
		{
			CMem_Free(g, p->Ptr);
		}
		if ((p->Type & Smp_Type_ExternalName) == 0)
			CMem_Free(g, p->pName);
		if ((p->Type & Smp_Type_ExternalFile) == 0)
			CMem_Free(g, p->pFilename);
	}
}

static const uint32_t ChPanning[4] = {64, 192, 192, 64};

/**
 *
 */
static void Song_ReInit(ISong* s)
{
	int i;
	GlobHdr* pGlb = s->Hdr.pGlb;
	LoaderData* pLoaderData = s->Hdr.pLoaderData;
	uint8_t* pFilename = s->Hdr.pFilename;
	SubSong* pSubSong = &s->Hdr.BaseSong;

	memset(&s->Hdr, 0, sizeof(s->Hdr));
	s->Hdr.pGlb = pGlb;
	s->Hdr.pLoaderData = pLoaderData;
	s->Hdr.pFilename = pFilename;

	s->Tag =  (*(const uint32_t*) "SWRK");
	s->Status = 0;
	// Normal play
	s->PlayRange.Start = -1;
	s->PlayRange.Last = -1;
	s->PlayRange.Pattern = -1;
	s->Seq.volume = pSubSong->Defaults.Volume = s->Hdr.VolumeShift = 256;
	s->Seq.lister = ASM_Song_Lister;
	s->Seq.changer = ASM_Song_Changer;
	pSubSong->Defaults.Tempo = 125;
	pSubSong->Defaults.Speed = 6;
	pSubSong->Defaults.VibratoStrength =
	pSubSong->Defaults.TremoloStrength =
	pSubSong->Defaults.PanbrelloStrength = 1;
	s->Hdr.TempoBase = 0;
	s->Hdr.MinTempo = 32;
	s->Hdr.MaxTempo = 255;
	s->Hdr.MinSpeed = 1;
	s->Hdr.MaxSpeed = 31;

	s->Hdr.pPatterns = s->patterns;
	memset(s->patterns, 0, sizeof(s->patterns));

	s->Hdr.pInstruments = s->instruments;
	memset(s->instruments, 0, sizeof(s->instruments));
	for (i = 0; i < song_max_instruments; i++)
	{
		Instrument* p = &s->instruments[i];
		p->Panning = 128;
	}

	s->Hdr.pSamples = s->samples;
	memset(s->samples, 0, sizeof(s->samples));
	for (i = 0; i < song_max_samples; i++)
	{
		Sample* p = &s->samples[i];
		p->Frequency = 8363;
		p->ScaleVolume = p->DefaultVolume = 256;
		p->Panning = 128;
	}
	// Most trackers treat samples nr >= s->Hdr.Samples
	// as empty sample with volume 0
	{
		Sample* p = &s->samples[song_max_samples];

		s->Hdr.pSampleDef = p;
		p->Frequency = 8363;
		p->ScaleVolume = 256;
		p->DefaultVolume = 0;
		p->Panning = 128;

		p++;
		s->Hdr.pSampleNoMap = p;
		p->Frequency = 8363;
		p->ScaleVolume = 256;
		p->DefaultVolume = 0;
		p->Panning = 128;
		p->Type = Smp_Type_Undefined;
	}

	for (i = 0; i < song_max_channels; i++)
	{
		ChannelDefault* p = &pSubSong->Defaults.ChDef[i];
		p->Volume = 256;
		p->Panning = ChPanning[i & 3];
		p->Mute = 0;
	}

	s->ChannelsPtr = s->channels;
	s->LoopsInfoPtr = s->loops;
}

static void Song_FixString(uint8_t* pString, bool bAllowReturn)
{
	uint8_t* p = pString;
	if (p == NULL) return;

	while(*p)
	{
		if (bAllowReturn && (*p == '\r') && (p[1] != '\n'))
			*p = '\n';
		if (iscntrl(*p) && (!bAllowReturn || (*p != '\n')))
			*p = ' ';
		p++;
	}

	// strip trailing blanks
	if (!bAllowReturn)
	{
		p--;
		while (p >= pString)
		{
			if (*p != ' ')
				break;
			*p = 0;
			p--;
		}
	}
}

/**
 * Normalizes stuff after a loader as successfully loaded a song.
 */
static const _kernel_oserror* Song_CheckIntegrity(GlobHdr* g, ISong* s)
{
	const _kernel_oserror* e;
	char* p;
	int i, j;

	if (  !s->Hdr.Patterns
	   || !s->Hdr.Channels
	   || !s->Hdr.Samples
	   || ((s->Hdr.Flags & Song_Flag_Instruments) && !s->Hdr.Instruments)
	   )
	{
		e = Loaders_Error(&s->Hdr, 0, "Base information for a %s missing", s->Hdr.pType);
		return e;
	}

	if (s->Hdr.MinTempo < 1)
	{
		SysLog_Log(SysLog_Low, "Min tempo increased from %d to %d", s->Hdr.MinTempo, 1);
		s->Hdr.MinTempo = 1;
	}
	if (s->Hdr.MaxTempo > 0xffff)
	{
		SysLog_Log(SysLog_Low, "Max tempo decreased from %d to %d", s->Hdr.MaxTempo, 0xffff);
		s->Hdr.MaxTempo = 0xffff;
	}
	if (s->Hdr.MinSpeed < 1)
		s->Hdr.MinSpeed = 1;
	if (s->Hdr.MaxSpeed > 512)
	{
		SysLog_Log(SysLog_Low, "Max speed decreased from %d to %d", s->Hdr.MaxSpeed, 512);
		s->Hdr.MaxSpeed = 512;
	}
//	if (s->Hdr.MinPitch < 1)
//	{
//		SysLog_Log(SysLog_Low, "Min pitch increased from %d to %d", s->Hdr.MinPitch, 1);
//		s->Hdr.MinPitch = 1;
//	}
	if (s->Hdr.MaxPitch && (s->Hdr.MaxPitch > 232))
	{
		SysLog_Log(SysLog_Low, "Max pitch decreased from %d to %d", s->Hdr.MaxPitch, 232);
		s->Hdr.MaxPitch = 232;
	}

	Song_FixString(s->Hdr.pName, false);
	Song_FixString(s->Hdr.pAuthor, false);
	if (s->Hdr.pComments != NULL)
	{
		Song_FixString(s->Hdr.pComments, true);
		for (i = 0; i < s->Hdr.CommentsLen; i++)
		{
			if (!s->Hdr.pComments[i])
				break;
		}
		s->Hdr.CommentsLen = i;
	}
	else s->Hdr.CommentsLen = 0;

	// Build full type name
	e = CMem_Alloc(g, (void**) &s->Hdr.pFullType, 50);
	if (e) return e;

	p = (char*) s->Hdr.pFullType;
	strcpy(p, (const char*) s->Hdr.pType);
	p += strlen(p);

	if (s->Hdr.Version)
		p += sprintf(p, " %d.%02d", s->Hdr.Version / 100, s->Hdr.Version % 100);
	*p++ = ' ';
	if (s->Hdr.Flags & Song_Flag_Linear)
		*p++ = 'l';
	if (s->Hdr.Flags & Song_Flag_Instruments)
		*p++ = 'i';
	if (s->Hdr.Flags & Song_Flag_Virtual)
		*p++ = 'v';
	if (s->Hdr.Flags & Song_Flag_Filters)
		*p++ = 'f';
	if (p[-1] == ' ') p--;
	*p = '\0';

	// Check subsongs
	{
		SubSong* pSubSong = &s->Hdr.BaseSong;
		for (; pSubSong; pSubSong = pSubSong->pNextSubSong)
		{
			if (!pSubSong->Defaults.Speed)
				pSubSong->Defaults.Speed = 6;
			if (!pSubSong->Defaults.Tempo)
				pSubSong->Defaults.Tempo = 125;

			if (pSubSong->pSeqs == NULL)
				pSubSong->SeqLen = 0;

			// Check sequence, must contain valid pattern nr, -1 (end of section), -2 (invalid)
			{
				int seq_nr = 0;
				int32_t pat_nr;

				for (i = 0; i < pSubSong->SeqLen; i++)
				{
					pat_nr = pSubSong->pSeqs[i];

					// Mark invalid patterns
					if ((pat_nr < -2) || (pat_nr >= (int32_t) s->Hdr.Patterns))
					{
						pat_nr = -2;
						pSubSong->pSeqs[i] = pat_nr;
					}

					if (pat_nr >= 0)
						seq_nr = i + 1;
				}

				// Remove anything beyond last valid pattern
				pSubSong->SeqLen = seq_nr;
			}
		}

		// Merge subssongs for SWI SongInfo
		uint32_t size = 0;
		for (pSubSong = &s->Hdr.BaseSong; pSubSong; pSubSong = pSubSong->pNextSubSong)
			size += pSubSong->SeqLen;

		e = Loaders_Alloc(&s->Hdr, (void**) &s->pMergedSeqs, size * sizeof(uint32_t));
		if (e) return e;
		uint32_t* p = s->pMergedSeqs;
		for (pSubSong = &s->Hdr.BaseSong; pSubSong; pSubSong = pSubSong->pNextSubSong)
		{
			for (i = 0; i < pSubSong->SeqLen; i++)
				*p++ = pSubSong->pSeqs[i];
		}
	}

	// Check patterns integrity
	for (i = 0; i < s->Hdr.Patterns; i++)
	{
		Pattern* pPattern = s->Hdr.pPatterns + i;
		if ((pPattern->Ptr == NULL) || !pPattern->Rows)
		{
			e = Loaders_Error(&s->Hdr, 0, "Invalid song, pattern %d is missing.", i);
			return e;
		}

		e = Loaders_CheckPattern(&s->Hdr, pPattern);
		if (e) return e;
	}

	// Check samples
	for (i = 0; i < s->Hdr.Samples; i++)
	{
		Sample* pSample = &s->Hdr.pSamples[i];

		Song_FixString(pSample->pName, false);
		Song_FixString(pSample->pFilename, false);

		if (!pSample->Ptr)
			pSample->Size = 0;

		// Check loop
		if (pSample->LoopStart > pSample->Size)
			pSample->LoopStart = pSample->Size;

		if (pSample->LoopEnd < pSample->LoopStart)
			pSample->LoopEnd = pSample->LoopStart;
		else if (pSample->LoopEnd > pSample->Size)
			pSample->LoopEnd = pSample->Size;

		if ((pSample->LoopStart == pSample->Size)
		||  (pSample->LoopStart == pSample->LoopEnd)
		||  (!(pSample->Type & Smp_Type_Loop)))
			pSample->Type &= ~(Smp_Type_Loop | Smp_Type_Loop_Bidi);

		// Check sustain loop
		if (pSample->SustainStart > pSample->Size)
			pSample->SustainStart = pSample->Size;

		if (pSample->SustainEnd < pSample->SustainStart)
			pSample->SustainEnd = pSample->SustainStart;
		else if (pSample->SustainEnd > pSample->Size)
			pSample->SustainEnd = pSample->Size;

		if ((pSample->SustainStart == pSample->Size)
		||  (pSample->SustainStart == pSample->SustainEnd)
		||  (!(pSample->Type & Smp_Type_Sustain)))
			pSample->Type &= ~(Smp_Type_Sustain | Smp_Type_Sustain_Bidi);

		if (pSample->Ptr)
		{
			uint32_t* p;
			uint32_t* pstart = (uint32_t*) pSample->Ptr;
			uint32_t* pend;
			int shift;

			// Convert unsigned to signed samples
			// Add trailing null samples because sequencer may sligtly read past end of samples
			if (pSample->Type & Smp_Type_16bit)
			{
				if (pSample->Type & Smp_Type_Stereo)
					pend = pstart + pSample->Size;
				else
					pend = pstart + ((pSample->Size + 1) >> 1);

				if (pSample->Type & Smp_Type_BigEndian)
				{
					uint32_t mask = 0xff00ff00;
					uint32_t val;

					for (p = pstart; p < pend; p++)
					{
						val = *p;
						*p = ((val & mask) >> 8) | ((val << 8) & mask);
					}
				}

				if (pSample->Type & Smp_Type_Unsigned)
				{
					uint32_t mask = 0x80008000;

					for (p = pstart; p < pend; p++)
						*p ^= mask;
				}

				if (pSample->Type & Smp_Type_Delta)
				{
					int16_t val = 0;
					int16_t* ps;

					for (ps = (int16_t*) pstart; ps < (int16_t*) pend; ps++)
						*ps = (val += *ps);
				}

				if (!(pSample->Type & Smp_Type_Stereo)
				&&  (pSample->Size & 1))
				{
					pend--;
					*pend = (*pend << 16) >> 16;
				}
				else
					*pend = 0;
			}
			else
			{
				if (pSample->Type & Smp_Type_Stereo)
					pend = pstart + ((pSample->Size + 1) >> 1);
				else
					pend = pstart + ((pSample->Size + 3) >> 2);

				if (pSample->Type & Smp_Type_Unsigned)
				{
					uint32_t mask = 0x80808080;

					for (p = pstart; p < pend; p++)
						*p ^= mask;
				}

				if (pSample->Type & Smp_Type_Delta)
				{
					int8_t val = 0;
					int8_t* ps;

					for (ps = (int8_t*) pstart; ps < (int8_t*) pend; ps++)
						*ps = (val += *ps);
				}

				if (pSample->Type & Smp_Type_Stereo)
					shift = 16*(pSample->Size & 1);
				else
					shift = 8*(pSample->Size & 3);

				if (shift)
				{
					pend--;
					shift = 32 - shift;
					*pend = (*pend << shift) >> shift;
				}
				else
					*pend = 0;
			}
		}
	}

	// Check instruments
	for (i = 0; i < s->Hdr.Instruments; i++)
	{
		Instrument* pInst = &s->Hdr.pInstruments[i];

		Song_FixString(pInst->pName, false);
		Song_FixString(pInst->pFilename, false);

		if (pInst->pNotesMap)
		{
			Envelope* pEnv;
			int prev;
			int32_t pos, val;

			// Check volume envelope
			pEnv = &pInst->VolumeEnvelope;
			if (pEnv->Points)
			{
				for (prev = -1, j = 0; j < pEnv->Points; j++)
				{
					pos = pEnv->pTable[j];
					val = pos >> 16;
					pos = (pos << 16) >> 16;
					if (pos == prev)
						pos ++;
					while (pos <= prev)
						pos += 0x100; // Some files have only the last byte stored
					pEnv->pTable[j] = pos + (val << 16);
					prev = pos;
				}
				if (pEnv->LoopEnd >= pEnv->Points) pEnv->LoopEnd = pEnv->Points - 1;
				if (pEnv->LoopStart > pEnv->LoopEnd) pEnv->LoopStart = pEnv->LoopEnd;
				if (pEnv->SustainEnd >= pEnv->Points) pEnv->SustainEnd = pEnv->Points - 1;
				if (pEnv->SustainStart > pEnv->SustainEnd) pEnv->SustainStart = pEnv->SustainEnd;
			}

			// Check panning envelope
			if (pEnv->Points)
			{
				pEnv = &pInst->PanningEnvelope;
				for (prev = -1, j = 0; j < pEnv->Points; j++)
				{
					pos = pEnv->pTable[j];
					val = pos >> 16;
					pos = (pos << 16) >> 16;
					while (pos <= prev)
						pos += 0x100; // Some files have only the last byte stored
					pEnv->pTable[j] = pos + (val << 16);
					prev = pos;
				}
				if (pEnv->LoopEnd >= pEnv->Points) pEnv->LoopEnd = pEnv->Points - 1;
				if (pEnv->LoopStart > pEnv->LoopEnd) pEnv->LoopStart = pEnv->LoopEnd;
				if (pEnv->SustainEnd >= pEnv->Points) pEnv->SustainEnd = pEnv->Points - 1;
				if (pEnv->SustainStart > pEnv->SustainEnd) pEnv->SustainStart = pEnv->SustainEnd;
			}

			// Check pitch envelope
			pEnv = &pInst->PitchEnvelope;
			if (pEnv->Points)
			{
				for (prev = -1, j = 0; j < pEnv->Points; j++)
				{
					pos = pEnv->pTable[j];
					val = pos >> 16;
					pos = (pos << 16) >> 16;
					while (pos <= prev)
						pos += 0x100; // Some files have only the last byte stored
					pEnv->pTable[j] = pos + (val << 16);
					prev = pos;
				}
				if (pEnv->LoopEnd >= pEnv->Points) pEnv->LoopEnd = pEnv->Points - 1;
				if (pEnv->LoopStart > pEnv->LoopEnd) pEnv->LoopStart = pEnv->LoopEnd;
				if (pEnv->SustainEnd >= pEnv->Points) pEnv->SustainEnd = pEnv->Points - 1;
				if (pEnv->SustainStart > pEnv->SustainEnd) pEnv->SustainStart = pEnv->SustainEnd;
			}

			// Check filter envelope
			pEnv = &pInst->FilterEnvelope;
			if (pEnv->Points)
			{
				for (prev = -1, j = 0; j < pEnv->Points; j++)
				{
					pos = pEnv->pTable[j];
					val = pos >> 16;
					pos = (pos << 16) >> 16;
					while (pos <= prev)
						pos += 0x100; // Some files have only the last byte stored
					pEnv->pTable[j] = pos + (val << 16);
					prev = pos;
				}
				if (pEnv->LoopEnd >= pEnv->Points) pEnv->LoopEnd = pEnv->Points - 1;
				if (pEnv->LoopStart > pEnv->LoopEnd) pEnv->LoopStart = pEnv->LoopEnd;
				if (pEnv->SustainEnd >= pEnv->Points) pEnv->SustainEnd = pEnv->Points - 1;
				if (pEnv->SustainStart > pEnv->SustainEnd) pEnv->SustainStart = pEnv->SustainEnd;
			}
		}
	}

	return NULL;
}

/**
 * Creates a new song and return its id.
 * In:
 *  -
 * Out:
 *  r[0] = song id.
 */
const _kernel_oserror* swi_Song_New(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s = NULL;
	int id;

	// Find free slot
	for (id = 0; id < max_songs; id++)
	{
		if (!g->songsptr[id])
			break;
	}

	if (id >= max_songs)
		return &Err_TooManySongHandlers;

	e = CMem_Alloc(g, (void**) &s, sizeof(*s));
	if (e) return e;

	g->songsptr[id] = s;
	memset(s, 0, sizeof(*s));
	s->Hdr.pGlb = g;
	s->Config = g->flags;
	s->ConfigAmp = 1;

	Song_ReInit(s);
	s->Hdr.Samples = 255;

	r->r[0] = id;

	return NULL;
}

typedef struct
{
	const FNLoader  FN;
	int             TagSize;
	const char*     pTag;
} Tagged_Loader;

// Decompressors with TAG at beginning of file (tags must be in alphabetic order)
static const Tagged_Loader Tagged_Decomps[] =
{ {Decomp_ICE,  4, "ICE!"}
, {Decomp_PP20, 4, "PP20"}
, {Decomp_S404, 4, "S404"}
, {Decomp_XPKF, 4, "XPKF"}
, NULL
};

static const FNLoader Decomps[] =
{ NULL
};

/**
 * Decompress a song.
 * In:
 *  r[0] = output filename.
 *  r[1] = input filename ptr or 0.
 *  r[2] = pointer to song in memory if r[1] is 0.
 *  r[3] = size of song in memory if r[1] is 0.
 * Out:
 */
const _kernel_oserror* swi_Song_Decompress(GlobHdr* g, _kernel_swi_regs* r)
{
	const const _kernel_oserror* e;
	ISong* s;
	LoaderData* pLoaderData;
	const FNLoader* pDecomp;
	const Tagged_Loader* pTaggedDecomp;
	uint32_t outFile = 0;
	const char* outFilename = (const char*) r->r[0];
	uint8_t Tag[20];
	int val;

	e = swi_Song_New(g, r);
	if (e) return e;

	s = g->songsptr[r->r[0]];

	e = CMem_Alloc(g, (void**) &s->Hdr.pLoaderData, sizeof(*s->Hdr.pLoaderData));
	if (e) goto load_error;
	memset(s->Hdr.pLoaderData, 0, sizeof(*s->Hdr.pLoaderData));
	pLoaderData = s->Hdr.pLoaderData;

	if (r->r[1] == 0)
	{
		if ((r->r[2] == 0) || (r->r[3] < 0))
			return &Err_InvalidMemoryFile;

		pLoaderData->File = 0;
		pLoaderData->pMemory = (uint8_t*) r->r[2];
		pLoaderData->MemSize = r->r[3];
		pLoaderData->MemPos = 0;
	}
	else
	{
		pLoaderData->pMemory = NULL;

		e = _swix(OS_Find, _INR(0,1)|_OUT(0), 0x4f, (const char*) r->r[1], &pLoaderData->File);
		if (e) goto load_error;
	}

	// First look for decompressors with a tag at start of file
	e = FileLoad_SetPos(&s->Hdr, 0);
	if (!e) FileLoad_Read(&s->Hdr, Tag, sizeof(Tag));
	if (e) goto load_error;

	for (pTaggedDecomp = Tagged_Decomps; pTaggedDecomp->FN; pTaggedDecomp++)
	{
		// Matching tag?
		val = Loaders_strncmp(Tag, (const uint8_t*)pTaggedDecomp->pTag, pTaggedDecomp->TagSize);
		if (val < 0) continue;
		if (val > 0) break;

		// Move back to start of file
		e = FileLoad_SetPos(&s->Hdr, 0);
		if (e) goto decomp_found;

		// Try with this loader
		e = (*pTaggedDecomp->FN)(&s->Hdr);

		// 0 OK, 1 not supported, other real error
		if (e == Loaders_NotThisType)
			continue;

		// Found, possibly with errors
		goto decomp_found;
	}

	// Now check the rest of the decompressors
	for (pDecomp = Decomps; *pDecomp; pDecomp++)
	{
		// Move back to start of file
		e = FileLoad_SetPos(&s->Hdr, 0);
		if (e) goto decomp_found;

		// Try with this loader
		e = (*pDecomp)(&s->Hdr);

		// 0 OK, 1 not supported, other real error
		if (e == Loaders_NotThisType)
			continue;

		// Found, possibly with errors
		goto decomp_found;
	}

	// No loader found
	e = &Err_NotACompressedSong;

decomp_found:
	if (e == NULL)
	{
		// Write file
		e = _swix(OS_Find, _INR(0,1)|_OUT(0), 0x80, outFilename, &outFile);
		if (!e) e = _swix(OS_GBPB, _INR(0,3), 2, outFile
				, pLoaderData->Decomp.pMemory, pLoaderData->Decomp.MemSize);
	}

	// Close output file
	if (outFile)
	{
		const const _kernel_oserror* ef;
		ef = _swix(OS_Find, _INR(0, 1), 0, outFile);
		if (ef) e = ef;
		// Give it type MOD
		_swix(8, _INR(0,2), 18, outFilename, 0xcb6);
	}

load_error:
	if (s->Hdr.pLoaderData)
	{
		// Close input file
		if (s->Hdr.pLoaderData->File)
		{
			const const _kernel_oserror* ef;
			ef = _swix(OS_Find, _INR(0, 1), 0, s->Hdr.pLoaderData->File);
			if (ef) e = ef;
		}
		CMem_Free(g, s->Hdr.pLoaderData->Decomp.pMemory);
		CMem_Free(g, s->Hdr.pLoaderData);
		s->Hdr.pLoaderData = NULL;
	}

	swi_Song_Unload(g, r);

	return e;
}

// Loaders with TAG at beginning of file (tags must be in alphabetic order)
static const Tagged_Loader Tagged_Loaders[] =
{ {Loader_DSym     ,  8, "\x02\x01\x13\x13\x14\x12\x01\x0B"}
, {Loader_AMF      ,  3, "AMF"}
, {Loader_AON      ,  3, "AON"}
, {Loader_Asylum   , 19, "ASYLUM Music Format"}
, {Loader_DT       ,  4, "D.T."}
, {Loader_DBM      ,  4, "DBM0"}
, {Loader_DMF      ,  4, "DDMF"}
, {Loader_Digi     , 20, "DIGI Booster module\x00"}
, {Loader_MDL      ,  4, "DMDL"}
, {Loader_DSM      ,  3, "DSM"}
, {Loader_DskT     ,  4, "DskT"}
, {Loader_DskT     ,  4, "EskT"}
, {Loader_XM       , 17, "Extended Module: "}
, {Loader_FAR      ,  4, "FAR\xfe"}
, {Loader_FC       ,  4, "FC14"}
, {Loader_GT2      ,  3, "GT2"}
, {Loader_GTK      ,  3, "GTK"}
, {Loader_IT       ,  4, "IMPM"}
, {Loader_ULT      , 14, "MAS_UTrack_V00"}
, {Loader_MMD      ,  3, "MMD"}
, {Loader_MT2      ,  4, "MT20"}
, {Loader_MTM      ,  3, "MTM"}
, {Loader_MTX      ,  4, "MTRX"}
, {Loader_MUSX     ,  4, "MUSX"}
//, {Loader_Maestro  ,  8, "Maestro\x0a"}
, {Loader_Oktalyzer,  8, "OKTASONG"}
, {Loader_PSM      ,  4, "PSM "}
, {Loader_PSM254   ,  4, "PSM"}
, {Loader_DSMF     ,  4, "RIFF"}
, {Loader_FC       ,  4, "SMOD"}
, NULL
};

static const FNLoader Loaders[] =
{ Loader_S3M
, Loader_PTM
, Loader_STM
, Loader_IMF
// No easy recognised header
, Loader_669
, Loader_Coconizer
, Loader_MOD
, NULL
};

/**
 * Loads a song and return its id.
 * In:
 *  r[1] = filename ptr or 0.
 *  r[2] = pointer to song in memory if r[1] is 0.
 *  r[3] = size of song in memory if r[1] is 0.
 * Out:
 *  r[0] = song id.
 */
const _kernel_oserror* swi_Song_Load(GlobHdr* g, _kernel_swi_regs* r)
{
	r->r[0] = 0;
	return swi_Song_Load2(g, r);
}

/**
 * Loads a song and return its id.
 * In:
 *  r[0] = compatibility flags.
 *  r[1] = filename ptr or 0.
 *  r[2] = pointer to song in memory if r[1] is 0.
 *  r[3] = size of song in memory if r[1] is 0.
 *  r[4] = configurations flags (if r[0] & 8).
 * Out:
 *  r[0] = song id.
 */
const _kernel_oserror* swi_Song_Load2(GlobHdr* g, _kernel_swi_regs* r)
{
	const const _kernel_oserror* e;
	ISong* s;
	LoaderData* pLoaderData;
	const FNLoader* pDecomp;
	const FNLoader* pLoader;
	const Tagged_Loader* pTaggedDecomp;
	const Tagged_Loader* pTaggedLoader;
	uint32_t compat = r->r[0];
	uint8_t Tag[20];
	int val;

	// Beware, r->r[0] modified on exit of this call
	e = swi_Song_New(g, r);
	if (e) return e;

	s = g->songsptr[r->r[0]];

	e = CMem_Alloc(g, (void**) &s->Hdr.pLoaderData, sizeof(*s->Hdr.pLoaderData));
	if (e) goto load_error;
	memset(s->Hdr.pLoaderData, 0, sizeof(*s->Hdr.pLoaderData));
	pLoaderData = s->Hdr.pLoaderData;

	if (compat & song_compat_override_cfg)
	{
		int mask = glb_flag_IgnoreSeqEndMark
		         | glb_flag_IgnoreSeqEndLoop
		         | glb_flag_IgnoreSeqRestartPos
		         | glb_flag_IgnoreMuteInfoOnLoading;

		s->Config &= ~mask;
		s->Config |= r->r[4] & mask;
		s->Config ^= glb_flag_IgnoreMuteInfoOnLoading; // R4 has this flag inverted on entry
	}

	if (r->r[1] == 0)
	{
		if ((r->r[2] == 0) || (r->r[3] < 0))
			return &Err_InvalidMemoryFile;

		pLoaderData->File = 0;
		pLoaderData->pMemory = (uint8_t*) r->r[2];
		pLoaderData->MemSize = r->r[3];
		pLoaderData->MemPos = 0;
	}
	else
	{
		pLoaderData->pMemory = NULL;
		e = CMem_AllocString(g, (char**) &s->Hdr.pFilename, (const char*) r->r[1]);
		if (e) goto load_error;

		e = _swix(OS_Find, _INR(0,1)|_OUT(0), 0x4f, s->Hdr.pFilename, &pLoaderData->File);
		if (e) goto load_error;
	}

	// First look for decompressors with a tag at start of file
	e = FileLoad_SetPos(&s->Hdr, 0);
	if (!e) FileLoad_Read(&s->Hdr, Tag, sizeof(Tag));
	if (e)
	{
		if (e->errnum == 0xdf) // End of file
			e = &Err_NotASong;
		goto load_error;
	}

	for (pTaggedDecomp = Tagged_Decomps; pTaggedDecomp->FN; pTaggedDecomp++)
	{
		// Matching tag?
		val = Loaders_strncmp(Tag, (const uint8_t*)pTaggedDecomp->pTag, pTaggedDecomp->TagSize);
		if (val < 0) continue;
		if (val > 0) break;

		// Move back to start of file
		e = FileLoad_SetPos(&s->Hdr, 0);
		if (e) goto decomp_found;

		// Try with this loader
		e = (*pTaggedDecomp->FN)(&s->Hdr);

		// 0 OK, 1 not supported, other real error
		if (e == Loaders_NotThisType)
			continue;

		// Found, possibly with errors
		goto decomp_found;
	}

	// Now check the rest of the decompressors
	for (pDecomp = Decomps; *pDecomp; pDecomp++)
	{
		// Move back to start of file
		e = FileLoad_SetPos(&s->Hdr, 0);
		if (e) goto decomp_found;

		// Try with this loader
		e = (*pDecomp)(&s->Hdr);

		// 0 OK, 1 not supported, other real error
		if (e == Loaders_NotThisType)
			continue;

		// Found, possibly with errors
		goto decomp_found;
	}

	// No loader found
	e = &Err_NotACompressedSong;

decomp_found:
	if (e == NULL)
	{
		// Close file and point to decompressed data
		if (pLoaderData->pMemory == NULL)
		{
			_swix(OS_Find, _INR(0, 1), 0, pLoaderData->File);
			pLoaderData->File = 0;
		}
		pLoaderData->pMemory = pLoaderData->Decomp.pMemory;
		pLoaderData->MemSize = pLoaderData->Decomp.MemSize;
		pLoaderData->MemPos = 0;

		// Re-read tag at start from decompressed data
		e = FileLoad_SetPos(&s->Hdr, 0);
		if (!e) FileLoad_Read(&s->Hdr, Tag, sizeof(Tag));
		if (e) goto load_error;
	}
	else if (e != &Err_NotACompressedSong)
		goto load_error;

	// First look for loaders with a tag at start of file

	for (pTaggedLoader = Tagged_Loaders; pTaggedLoader->FN; pTaggedLoader++)
	{
		SongHdr_Clear(g, &s->Hdr);
		Song_ReInit(s);
		s->Hdr.Compatibility = compat;

		// Matching tag?
		val = Loaders_strncmp(Tag, (const uint8_t*)pTaggedLoader->pTag, pTaggedLoader->TagSize);
		if (val < 0) continue;
		if (val > 0) break;

		// Move back to start of file
		e = FileLoad_SetPos(&s->Hdr, 0);
		if (e) goto loader_found;

		// Prepare to fill the patterns
		e = Loaders_PreparePatterns(&s->Hdr);
		if (e) goto loader_found;

		// Try with this loader
		e = (*pTaggedLoader->FN)(&s->Hdr);

		// 0 OK, 1 not supported, other real error
		if (e == Loaders_NotThisType)
			continue;

		// Found, possibly with errors
		goto loader_found;
	}

	// Now check the rest of the loaders
	for (pLoader = Loaders; *pLoader; pLoader++)
	{
		SongHdr_Clear(g, &s->Hdr);
		Song_ReInit(s);
		s->Hdr.Compatibility = compat;

		// Move back to start of file
		e = FileLoad_SetPos(&s->Hdr, 0);
		if (e) goto loader_found;

		// Prepare to fill the patterns
		e = Loaders_PreparePatterns(&s->Hdr);
		if (e) goto loader_found;

		// Try with this loader
		e = (*pLoader)(&s->Hdr);

		// 0 OK, 1 not supported, other real error
		if (e == Loaders_NotThisType)
			continue;

		// Found, possibly with errors
		goto loader_found;
	}

	// No loader found
	e = &Err_NotASong;

loader_found:
	// Fix little things
	if (!e) e = Loaders_CompletePatterns(&s->Hdr);
	if (!e) e = Song_CheckIntegrity(g, s);
	if (!e)
	{
		// Song loaded
		if ((s->Hdr.Flags & Song_Flag_Corrupted) && (SysLog_LogLevel() < SysLog_High))
			SysLog_FileError(&s->Hdr, 0, "File is somewhat corrupted, increase logging level for details");

		e = Song_InitForPlay(s);
		if (!e) CSeq_ResetMaxWavePercentage(g);
	}

load_error:
	if (s->Hdr.pLoaderData)
	{
		// Close input file
		if (s->Hdr.pLoaderData->File)
		{
			const const _kernel_oserror* ef;
			ef = _swix(OS_Find, _INR(0, 1), 0, s->Hdr.pLoaderData->File);
			if (ef) e = ef;
		}
		CMem_Free(g, s->Hdr.pLoaderData->Decomp.pMemory);
		CMem_Free(g, s->Hdr.pLoaderData->pPattern);
		CMem_Free(g, s->Hdr.pLoaderData);
		s->Hdr.pLoaderData = NULL;
	}

	if (e) swi_Song_Unload(g, r);

	return e;
}

/**
 * Removes a song from memory.
 * In:
 *  r[0] = song id.
 * Out:
 *  -
 */
const _kernel_oserror* swi_Song_Unload(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;
	int index;

	index = r->r[0];
	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	s = g->songsptr[index];

	// Remove handle
	g->songsptr[index] = NULL;

	// Unlink from mixer
	if (s->Status & swrk_status_linked)
	{
		s->Status &= ~swrk_status_linked;
		CSeq_UnregisterHandler(s->Hdr.pGlb, &s->Seq);
	}

	CSeq_ResetMaxWavePercentage(g);

	// Clear memory
	SongHdr_Clear(g, &s->Hdr);
	CMem_Free(g, s->Hdr.pLoaderData);
	CMem_Free(g, s->Hdr.pFilename);
	CMem_Free(g, s->pMergedSeqs);
	CMem_Free(g, s->SectionsPtr);
	CMem_Free(g, s);
	CMem_Shrink(g);

	return NULL;
}

/**
 * Starts or continues (after a pause) to play a loaded song.
 * In:
 *  r[0] = song id.
 * Out:
 *  -
 */
const _kernel_oserror* swi_Song_Play(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	// Don't do anything if already playing
	if (s->Status & swrk_status_playing)
		return NULL;

	// Sanity check
	if (!s->SectionsPtr)
	{
		e = &Err_SongCannotBePlayed;
		return e;
	}

#ifndef MAKEABS
	int ioff = _kernel_irqs_disabled();
	if (!ioff) _kernel_irqs_off();
#endif

	// Mark file as playing
	s->Status |= swrk_status_playing;
	s->Status &= ~swrk_status_paused;

	if (!(s->Status & swrk_status_linked))
	{
		s->Status |= swrk_status_linked;
		// Determine number of soft & hard channels
		s->Seq.R12 = s;
		CSeq_RegisterHandler(g, &s->Seq);
	}

#ifndef MAKEABS
	if (!ioff) _kernel_irqs_on();
#endif

	return NULL;
}

/**
 * Pauses a playing song (stop emitting sound but keep the current position).
 * In:
 *  r[0] = song id.
 * Out:
 *  -
 */
const _kernel_oserror* swi_Song_Pause(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	// Is file loaded and playing ?
	if (s->Status & swrk_status_playing)
	{
#ifndef MAKEABS
		int ioff = _kernel_irqs_disabled();
		if (!ioff) _kernel_irqs_off();
#endif

		s->Status &= ~swrk_status_playing;
		s->Status |= swrk_status_paused;

		if (!(s->Status & swrk_status_alwayslink))
		{
			s->Status &= ~swrk_status_linked;
			CSeq_UnregisterHandler(g, &s->Seq);
		}

#ifndef MAKEABS
		if (!ioff) _kernel_irqs_on();
#endif
	}

	return NULL;
}

/**
 *
 */
static void Song_StopCallback(ISong* s)
{
	if (s->Status & (swrk_status_playing|swrk_status_paused))
	{
#ifndef MAKEABS
		int ioff = _kernel_irqs_disabled();
		if (!ioff) _kernel_irqs_off();
#endif

		s->Status &= ~(swrk_status_playing|swrk_status_paused);

		// Unlink if required
		if (!(s->Status & swrk_status_alwayslink)
		&&  (s->Status & swrk_status_linked))
		{
			s->Status &= ~swrk_status_linked;
			CSeq_UnregisterHandler(s->Hdr.pGlb, &s->Seq);
		}

#ifndef MAKEABS
		if (!ioff) _kernel_irqs_on();
#endif

		if (s->PlayRange.Pattern >= 0)
			Song_SetPosition(s, NULL, s->PlayRange.Pattern);
		else if (s->PlayRange.Start >= 0)
			Song_SetPosition(s, s->SubSongPtr, s->PlayRange.Start);
		else
			Song_SetPosition(s, &s->Hdr.BaseSong, 0);
	}
}

/**
 * Stops a playing song (stop emitting sounds and move back to the beginning of the song).
 * In:
 *  r[0] = song id.
 * Out:
 *  -
 */
const _kernel_oserror* swi_Song_Stop(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	Song_StopCallback(s);

	return NULL;
}

/**
 * Converts current subsong sequence position into global sequence position.
 */
static uint32_t Song_GetGlobalPosition(ISong* s)
{
	const SubSong* pSubSong;
	uint32_t pos= s->SeqPos;

	for (pSubSong = &s->Hdr.BaseSong; pSubSong; pSubSong = pSubSong->pNextSubSong)
	{
		if (pSubSong == s->SubSongPtr)
			break;
		pos += pSubSong->SeqLen;
	}

	return pos;
}

/**
 * Reads or alter the position in the song.
 * Handle sub-songs by joining all sequences together.
 * In:
 *  r[0] = song id.
 *  r[1] = new sequence position [0, nr of orders[, -1 to read, -2 to read frame info
 * Out:
 * if r[1] = -2 on entry:
 *  r[1] = frame index in row
 *  r[2] = frame index in row, repeats included
 *  r[3] = nr of frames in row, repeats included
 *  r[4] = repeat index in row
 *  r[5] = nr of repeats in row
 *  r[6] = speed
 * else
 *  r[1] = sequence position [0, nr of orders[
 *  r[2] = current row in pattern [0, pattern length[
 *  r[3] = current pattern [0, nr of patterns[
 *  r[4] = current pattern length [0, 255]
 *  r[5] = time index (in ms)
 */
const _kernel_oserror* swi_Song_Position(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	const SubSong* pSubSong;
	ISong* s;
	uint32_t pos;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	// Sanity check
	if (!s->SectionsPtr)
	{
		e = &Err_SongCannotBePlayed;
		return e;
	}

	// Find sub-song position belomgs to
	if (r->r[1] >= 0)
	{
		pos = r->r[1];
		for (pSubSong = &s->Hdr.BaseSong; pSubSong; pSubSong = pSubSong->pNextSubSong)
		{
			if (pos < pSubSong->SeqLen)
			{
				if (s->PlayRange.Start >= 0)
				{
					if ((s->SubSongPtr != pSubSong)
					||  (pos < s->PlayRange.Start)
					||  (pos > s->PlayRange.Last))
					{
						pSubSong = s->SubSongPtr;
						pos = s->PlayRange.Start;
					}
				}
				Song_SetPosition(s, pSubSong, pos);
				break;
			}
			pos -= pSubSong->SeqLen;
		}
	}

#ifndef MAKEABS
	int ioff = _kernel_irqs_disabled();
	if (!ioff) _kernel_irqs_off();
#endif

	if (r->r[1] == -2)
	{
		r->r[1] = s->FrameCount;
		r->r[2] = s->FrameExtCount;
		r->r[3] = s->Frames * s->Repeats;
		r->r[4] = s->RepeatCount;
		r->r[5] = s->Repeats;
		r->r[6] = s->CurrentSpeed;
	}
	else
	{
		// Read (new) current position
		if ((s->PlayRange.Pattern >= 0))
		{
			// Playing pattern
			r->r[1] = 0;
			r->r[2] = s->RowPos;
			r->r[3] = s->PlayRange.Pattern;
		}
		else
		{
			// Playing sequence (normal/range)
			r->r[1] = Song_GetGlobalPosition(s);
			r->r[2] = s->RowPos;
			r->r[3] = s->SubSongPtr->pSeqs[s->SeqPos];
		}

		r->r[4] = s->Hdr.pPatterns[r->r[3]].Rows;

		pos = s->TimeIndex + (s->FrameTime >> 10);
		if (pos > s->SongTime)
			pos = s->SongTime;

		r->r[5] = pos / 250;
	}

#ifndef MAKEABS
	if (!ioff) _kernel_irqs_on();
#endif

	return NULL;
}

/**
 * Reads or alters the volume of a song.
 * In:
 *  r[0] = song id.
 *  r[1] = new volume [0...256...1024], -1 to read
 * Out:
 *  r[1] = current volume [0...256...1024]
 */
const _kernel_oserror* swi_Song_Volume(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;
	unsigned int volume = r->r[1];

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	if (volume <= 1024)
	{
		s->Seq.volume = volume;
		CSeq_SetMixScale(g);
	}

	r->r[1] = s->Seq.volume;

	return NULL;
}

/**
 * Reads or alter the status of a song.
 * In:
 *  r[0] = song id.
 *  r[1] = new status.
 *  r[2] = status mask.
 * Out:
 *  r[1] = current status
 *          bit  meaning when set
 *          0    song is playing
 *          1    song is paused
 *          2    mix even when stopped
 *          4    pause when song ends
 *          5    pause when pattern ends
 *          6    pause when row ends
 *          7    pause when frame ends
 */
const _kernel_oserror* swi_Song_Status(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;
	unsigned int value = r->r[1];
	unsigned int mask = r->r[2];

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

#ifndef MAKEABS
	int ioff = _kernel_irqs_disabled();
	if (!ioff) _kernel_irqs_off();
#endif

	// Only some flags may be altered
	mask &= swrk_status_altermask;
	s->Status = (value & mask) | (s->Status & ~mask);

	if (s->Status & swrk_status_alwayslink)
	{
		// Link to mixer?
        if (!(s->Status & swrk_status_linked))
        {
			s->Status |= swrk_status_linked;
			s->Seq.R12 = s;
			CSeq_RegisterHandler(g, &s->Seq);
        }
	}
	else
	{
		// Unlink if required
		if (!(s->Status & swrk_status_playing)
		&&  (s->Status & swrk_status_linked))
		{
			s->Status &= ~swrk_status_linked;
			CSeq_UnregisterHandler(g, &s->Seq);
		}
	}

#ifndef MAKEABS
	if (!ioff) _kernel_irqs_on();
#endif

	r->r[1] = s->Status & swrk_status_readmask;

	return NULL;
}

/**
 * Plays a pattern or loop on a range of patterns withn the  sequence.
 * In:
 *  r[0] = song id.
 * if r[1] == -1
 *  r[2] = pattern number [0, nr of patterns[, -1 for normal play
 * else
 *  r[1] = start position in the sequence [0, nr of orders[
 *  r[2] = end position in the sequence [0, nr of orders[
 * Out:
 *  -
 */
const _kernel_oserror* swi_Song_PlayRange(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;
	const SubSong* pSubSong;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	if (r->r[2] >= 0)
	{
		if (r->r[1] >= 0)
		{
			// Find sub-song start position belomgs to and restrict to it
			int countValid = 0;

			uint32_t spos = r->r[1];
			uint32_t epos = r->r[2];
			if (epos < spos)
				epos = spos;
			for (pSubSong = &s->Hdr.BaseSong; pSubSong; pSubSong = pSubSong->pNextSubSong)
			{
				if (spos < pSubSong->SeqLen)
				{
					if (epos >= pSubSong->SeqLen)
						epos = pSubSong->SeqLen - 1;

					// Protect against range containing only dummy entries
					for (int i = spos; i <= epos; i++)
					{
						if (pSubSong->pSeqs[i] >= 0)
							countValid ++;
					}
					if (countValid == 0)
						return &Err_RangeCannotBePlayed;

					bool fixPos = ((s->SubSongPtr != pSubSong)
					||  (s->SeqPos < spos)
					||  (s->SeqPos > epos)
					||  (s->PlayRange.Pattern >= 0));

					s->PlayRange.Start = spos;
					s->PlayRange.Last = epos;
					s->PlayRange.Pattern = -1;

					// Update position if doesn't fit in new range
					if (fixPos)
						Song_SetPosition(s, pSubSong, spos);
					break;
				}
				spos -= pSubSong->SeqLen;
				epos -= pSubSong->SeqLen;
			}

			if (countValid == 0)
				return &Err_RangeCannotBePlayed;
		}
		else
		{
			// Check if valid pattern to play
			if ((r->r[2] >= s->Hdr.Patterns)
			||  !s->Hdr.pPatterns[r->r[2]].Rows)
				return &Err_RangeCannotBePlayed;

			if (s->PlayRange.Pattern != r->r[2])
			{
				s->PlayRange.Start = -1;
				s->PlayRange.Last = -1;
				s->PlayRange.Pattern = r->r[2];
				Song_SetPosition(s, NULL, s->PlayRange.Pattern);
			}
		}
	}
	// Normal play
	else
	{
		s->PlayRange.Start = -1;
		s->PlayRange.Last = -1;
		// Only move position if we were in pattern mode
		if (s->PlayRange.Pattern >= 0)
		{
			s->PlayRange.Pattern = -1;
			Song_SetPosition(s, &s->Hdr.BaseSong, 0);
		}
	}

	return NULL;
}

/**
 * Provides general information on a song.
 * In:
 *  r[0] = song id.
 * Out:
 *  r[1] = number of channels [0, 64]
 *  r[2] = number of orders in sequence [0, ]
 *  r[3] = pointer to sequence table of first SubSong (4 bytes values)
 *  r[4] = number of patterns [0, ]
 *  r[5] = number of instruments [0, 255]
 *  r[6] = number of samples [0, 255]
 *  r[7] = info flags:
 *          Bit Meaning when set
 *          0   uses linear pitch slides
 *          1   uses instruments
 *          2   may generate virtual notes (loading info)
 *          3   linked pitch Slide and Portamento memories
 *          4   file was corrupted (loading info)
 *          5   use full envelope loops
 *          6   file has unsupported effects (loading info)
 *          7   file uses filters (loading info)
 */
const _kernel_oserror* swi_Song_Info(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	r->r[1] = s->Hdr.Channels;
	r->r[2] = 0;
	for (SubSong* pSubSong = &s->Hdr.BaseSong; pSubSong; pSubSong = pSubSong->pNextSubSong)
		r->r[2] += pSubSong->SeqLen;
	r->r[3] = (int) s->pMergedSeqs;
	r->r[4] = s->Hdr.Patterns;
	r->r[5] = s->Hdr.Instruments;
	r->r[6] = s->Hdr.Samples;
	r->r[7] = s->Hdr.Flags & Song_Flag_VisibleFlags;

	return NULL;
}

static const char nil = 0;

/**
 * Provides general textual information on of a song.
 * In:
 *  r[0] = song id.
 * Out:
 *  r[1] = song title, pointer to a 0 terminated string.
 *  r[2] = song author, pointer to a 0 terminated string.
 *  r[3] = song type, pointer to a 0 terminated string.
 *  r[4] = length of song comments.
 *  r[5] = song comments, pointer to a 0 terminated string.
 *         May contain control characters to split the comments into several lines.
 */
const _kernel_oserror* swi_Song_Texts(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;

	// Avoid null pointer for caller not check for errors
	r->r[1] = r->r[2] = r->r[3] = r->r[4] = r->r[5] = (int) &nil;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	r->r[1] = s->Hdr.pName ? (int) s->Hdr.pName : (int) &nil;
	r->r[2] = s->Hdr.pAuthor ? (int) s->Hdr.pAuthor : (int) &nil;
	r->r[3] = s->Hdr.pFullType ? (int) s->Hdr.pFullType : (int) &nil;
	r->r[4] = s->Hdr.CommentsLen;
	r->r[5] = s->Hdr.pComments ? (int) s->Hdr.pComments : (int) &nil;

	return NULL;
}

/**
 * Reads the initial replay parameters of first sub-song a song.
 * In:
 *  r[0] = song id.
 * Out:
 *  r[1] = initial speed [1, 63].
 *  r[2] = initial tempo (in 8/20 Hz).
 *  r[3] = initial global volume [0, 256].
 *  r[4] = restart position [0, nr of orders[.
 */
const _kernel_oserror* swi_Song_InitialSettings(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	r->r[1] = s->Hdr.BaseSong.Defaults.Speed;
	switch(s->Hdr.TempoBase)
	{
		case Tempo_Base_20Hz: r->r[2] = s->Hdr.BaseSong.Defaults.Tempo >> 3; break;
		case Tempo_Base_4Hz: r->r[2] = (5*s->Hdr.BaseSong.Defaults.Tempo) >> 3; break;
		default: r->r[2] = s->Hdr.BaseSong.Defaults.Tempo;
	}
	r->r[3] = s->Hdr.BaseSong.Defaults.Volume;
	r->r[4] = s->Hdr.BaseSong.RestartPos;

	return NULL;
}

/**
 * Reads the initial setting of first sub-song of a song channel.
 * In:
 *  r[0] = song id.
 *  r[1] = channel number [0, nr of channels[
 * Out:
 *  r[2] = volume [0, 256].
 *  r[3] = panning [0,255], 384 is surround.
 */
const _kernel_oserror* swi_Channel_InitialSettings(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;
	unsigned int channel = r->r[1];

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	if (channel >= s->Hdr.Channels)
		return &Err_InvalidChannelNr;

	r->r[2] = s->Hdr.BaseSong.Defaults.ChDef[channel].Volume;
	r->r[3] = s->Hdr.BaseSong.Defaults.ChDef[channel].Panning;

	return NULL;
}

/**
 * Reads the playing information of a song.
 * In:
 *  r[0] = song id.
 * Out:
 *  r[1] = minimal tone (in semitones) below which the pitch cannot slide
 *  r[2] = maximal tone (in semitones), the note is cut if the pitch slides above it
 *  r[3] = corrupted
 *  r[4] = song duration (in ms)
 *  r[5] = Current maximal number of channels/notes seen.
 *         This can be greater than nr of channels for ITs using
 *         instruments with New Note Action != 'note cut'.
 */
const _kernel_oserror* swi_Song_PlayInfo(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	r->r[1] = (s->Hdr.MinPitch) ? s->Hdr.MinPitch : 1;
	r->r[2] = (s->Hdr.MaxPitch) ? s->Hdr.MaxPitch : 232;
	r->r[3] = 208;
	r->r[4] = s->SongTime / 250;
	r->r[5] = s->Seq.maxstreams;

	return NULL;
}

/**
 * Reads information on a song section.
 * In:
 *  r[0] = song id.
 *  r[1] = section number [0, nr of sections[,
 *         or -1 for highest section nr,
 *         or -2 for currently played section.
 * Out:
 *  r[1] = section number [0, nr of sections[.
 *  r[2] = start position of section in the sequence [0, nr of orders[
 *  r[3] = start time index (ms)
 *  r[4] = end time index (ms)
 */
const _kernel_oserror* swi_Song_SectionInfo(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;
	int section = r->r[1];

	r->r[1] = r->r[2] = r->r[3] = r->r[4] = 0;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	// Sanity check
	if (!s->SectionsPtr)
	{
		e = &Err_SongCannotBePlayed;
		return e;
	}

	if (section == -1)
		section = s->Hdr.Sections - 1;
	else if (section == -2)
	{
		if (s->PlayRange.Pattern >= 0)
			section = 0;
		else
			section = s->SubSongPtr->pSecIndexes[s->SeqPos] - 1;
	}

	if ((section < 0) || (section >= s->Hdr.Sections))
		return &Err_InvalidSection;

	const ISection* pSection = s->SectionsPtr + section;
	const SubSong* pSubSong;
	r->r[1] = section;
	r->r[2] = pSection->StartPos;
	for (pSubSong = &s->Hdr.BaseSong; pSubSong; pSubSong = pSubSong->pNextSubSong)
	{
		if (pSubSong == pSection->pSubSong)
			break;
		r->r[2] += pSubSong->SeqLen;
	}
	r->r[3] = pSection->pSubSong->pTimeIndexes[pSection->StartPos] / 250;

	// Find start time of next section
	if (section == (s->Hdr.Sections - 1))
		r->r[4] = s->SongTime / 250;
	else
	{
		pSection = s->SectionsPtr + section + 1;
		r->r[4] = pSection->pSubSong->pTimeIndexes[pSection->StartPos] / 250;
	}

	return NULL;
}

/**
 * Reads the current replay parameters of a song.
 * In:
 *  r[0] = song id.
 * Out:
 *  r[1] = current speed [1, 63].
 *  r[2] = current tempo (in 8/20 Hz).
 *  r[3] = current global volume [0, 256].
 */
const _kernel_oserror* swi_Song_SongParams(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	r->r[1] = s->CurrentSpeed;
	switch(s->Hdr.TempoBase)
	{
		case Tempo_Base_20Hz: r->r[2] = s->CurrentTempo >> 3; break;
		case Tempo_Base_4Hz: r->r[2] = (5*s->CurrentTempo) >> 3; break;
		default: r->r[2] = s->CurrentTempo;
	}
	r->r[3] = s->Notes.Volume;

	return NULL;
}

/**
 * Reads playback parameters on a given channel of a song.
 * In:
 *  r[0] = song id.
 *  r[1] = channel nr [0, nr of channels[ + flags
 *         bit  meaning
 *         24   add instrument/note nr in r[2]
 *  r[2] = 16-bit buffer ptr where to copy sound output on this channel or 0 if not used
 *  r[3] = length of buffer (in bytes)
 * Out:
 *  r[2] = sample nr (0 if nothing played)
 *         if (bit 24 of flags)
 *         + instrument nr << 8
 *         + inst note nr << 16
 *  r[3] = pitch in 1/65536 of default pitch (0 if nothing played)
 *  r[4] = if r[2] != 0 on input
 *             buffer peak value [0, &7fff]
 *          else
 *             note volume [0, &10000]
 *  r[5] = if r[2] != 0 on input
 *             buffer mean value [0, &7fff]
 *          else
 *             channel volume [0, &10000]
 *  r[6] = panning [0 (left), 256 (right)], 384 for surround
 */
const _kernel_oserror* swi_Song_ChannelParams(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;
	const Channel* pChannel;
	const Note* pNote;
	uint32_t channel = r->r[1] & 0xffffff;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	if (channel >= s->Hdr.Channels)
		return &Err_InvalidChannelNr;

	pChannel = &s->channels[channel];

#ifndef MAKEABS
	int ioff = _kernel_irqs_disabled();
	if (!ioff) _kernel_irqs_off();
#endif

	pNote = pChannel->pnote;

	if (pNote && (pNote->stream.pos != -1))
	{
		if (r->r[2] && (r->r[3] > 0))
		{
			Note_Monitor nm = {g, pNote, (void*) r->r[2], r->r[3]};

			CNote_FillMonitorBuffer(&nm);
			r->r[4] = nm.peekVolume;
			r->r[5] = nm.meanVolume;
		}
		else
		{
			r->r[4] = pNote->volume;
			r->r[5] = pChannel->GVolume << 8;
		}
		r->r[2] = pNote->smp_id;
		if (r->r[1] & 0x1000000)
		{
			r->r[2] += (pNote->inst_id << 8)
			        +  (pNote->inst_note << 16);
		}
		r->r[3] = pNote->pitch;
		r->r[6] = pNote->panning;
	}
	else
	{
		if (r->r[2]) memset((void*) r->r[2], 0, r->r[3]);
		r->r[2] = 0;
		r->r[3] = 0;
		r->r[4] = 0;
		r->r[5] = pChannel->GVolume << 8;
		r->r[6] = pChannel->GPanning;
	}

#ifndef MAKEABS
	if (!ioff) _kernel_irqs_on();
#endif

	return NULL;
}

/**
 * Reads playback parameters on a given note of a song.
 * In:
 *  r[0] = song id.
 *  r[1] = note nr [0, nr of notes currently playing[ + flags
 *         bit  meaning
 *         24   add instrument/note nr in r[2]
 *  r[2] = 16-bit buffer ptr where to copy sound output for this note or 0 if not used
 *  r[3] = length of buffer (in bytes)
 * Out:
 *  r[7] = -1 if no note playing for note nr >= r[1] else:
 *
 *  r[2] = sample nr (0 if nothing played)
 *         if (bit 24 of flags)
 *         + instrument nr << 8
 *         + inst note nr << 16
 *  r[3] = pitch in 1/65536 of default pitch (0 if nothing played)
 *  r[4] = if r[2] != 0 on input
 *             buffer peak value [0, &7fff]
 *          else
 *             note volume [0, &10000]
 *  r[5] = if r[2] != 0 on input
 *             buffer mean value [0, &7fff]
 *          else
 *             channel volume [0, &10000]
 *  r[6] = panning [0 (left), 256 (right)], 384 for surround
 *  r[7] = bits 0-7  channel nr on which the note was started
 *         bit 31    set if it is a 'past note'
 *         bit 30    set if filter is in use
 */
const _kernel_oserror* swi_Song_VChannelParams(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;
	const Channel* pChannel;
	const Note* pNote;
	uint32_t note = r->r[1] & 0xffffff;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

#ifndef MAKEABS
	int ioff = _kernel_irqs_disabled();
	if (!ioff) _kernel_irqs_off();
#endif

	if (note < s->Notes.ActiveNotes)
	{
		pNote = s->Notes.ActiveNotesArray[note];
		pChannel = pNote->pchannel;

		if (r->r[2] && (r->r[3] > 0))
		{
			Note_Monitor nm = {g, pNote, (void*) r->r[2], r->r[3]};

			CNote_FillMonitorBuffer(&nm);
			r->r[4] = nm.peekVolume;
			r->r[5] = nm.meanVolume;
		}
		else
		{
			r->r[4] = pNote->volume;
			r->r[5] = pChannel->GVolume << 8;
		}
		r->r[2] = pNote->smp_id;
		if (r->r[1] & 0x1000000)
		{
			r->r[2] += (pNote->inst_id << 8)
			        +  (pNote->inst_note << 16);
		}
		r->r[3] = pNote->pitch;
		r->r[6] = pNote->panning;
		r->r[7] = pChannel->id;
		if (pChannel->pnote != pNote)
			r->r[7] |= ~(0x7fffffff);
		if (pNote->stream.flags & mixstream_flag_filter)
			r->r[7] |= 0x40000000;
	}
	else
	{
		r->r[2] = 0;
		r->r[3] = 0;
		r->r[4] = 0;
		r->r[5] = 0;
		r->r[6] = 0;
		r->r[7] = -1;
	}

#ifndef MAKEABS
	if (!ioff) _kernel_irqs_on();
#endif

	return NULL;
}

/**
 * SWI ChannelStatus
 *
 * In:
 *   r[0] song handle
 *   r[1] channel nr [0, nr of channels[
 *   r[2] actions
 *            channel described in r[1]
 *              bit 0: alter status: 1 yes, 0 no
 *              bit 1: new status: 1 play, 0 mute
 *            channels other than the one discribed in R1
 *              bit 2: alter status: 1 yes, 0 no
 *              bit 3: new status: 1 play, 0 mute
 * Out:
 *   r[3] 0 mute, 1 play
 */
const _kernel_oserror* swi_Channel_Status(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	ISong* s;
	Channel* pChannel;
	int i;

	e = Song_GetSongHeader(g, r->r[0], &s);
	if (e) return e;

	if ((r->r[1] < 0) || (r->r[1] >= s->Hdr.Channels))
		return &Err_InvalidChannelNr;

	// Treat the other channels
	if (r->r[2] & 4)
	{
		for (i = 0, pChannel = s->channels; i < s->Hdr.Channels; i++, pChannel++)
		{
			if (i != r->r[1])
			{
				if (r->r[2] & 8)
					pChannel->flags &= ~ch_flags_mute;
				else
					pChannel->flags |= ch_flags_mute;
			}
		}
	}

	// Treat the mentionned channel
	pChannel = &s->channels[r->r[1]];
	if (r->r[2] & 1)
	{
		if (r->r[2] & 2)
			pChannel->flags &= ~ch_flags_mute;
		else
			pChannel->flags |= ch_flags_mute;
	}
	if (pChannel->flags & ch_flags_mute)
		r->r[3] = 0;
	else
		r->r[3] = 1;

	return NULL;
}

/*
 * variables:
 *        00 Frame count
 *        01 Delay
 *        02 Note final volume
 *        03 Note final panning
 *        04 Note final pitch/period
 *        05 Note frequency
 *        06 Note vibrato -> running depth
 *        07 Note vibrato -> position
 *        08 Note flags
 *        09 Note finetune
 *        10 Note fadeout volume
 *        11 Note NNA
 *        12 Note Filter -> resonance
 *        13 Note Filter -> cutoff
 *        14 Note Filter -> inalcutoff
 *        15 Note Filter -> envelope flags
 *        16 Note Filter -> envelope position
 *        17 Ch Volume -> current
 *        18 Ch Volume -> start
 *        19 Ch Volume -> Mem portamento speed
 *        20 Ch Volume -> Mem slide speed 1
 *        21 Ch Volume -> Mem slide speed 2
 *        22 Ch Volume -> Mem slowslide speed
 *        23 Ch Volume -> Mem vibrato position
 *        24 Ch Volume -> Mem vibrato speed
 *        25 Ch Volume -> Mem vibrato depth
 *        26 Ch Volume -> Mem last slide effect
 *        27 Smp Volume -> current
 *        28 Smp Volume -> start
 *        29 Smp Volume -> envelope flags
 *        30 Smp Volume -> envelope position
 *        31 Smp Volume -> Mem portamento speed
 *        32 Smp Volume -> Mem slide speed 1
 *        33 Smp Volume -> Mem slide speed 2
 *        34 Smp Volume -> Mem slowslide speed
 *        35 Smp Volume -> Mem vibrato position
 *        36 Smp Volume -> Mem vibrato speed
 *        37 Smp Volume -> Mem vibrato depth
 *        38 Smp Volume -> Mem last slide effect
 *        39 Smp Volume -> Mem tremor on time
 *        40 Smp Volume -> Mem tremor off time
 *        41 Smp Volume -> Mem tremor position
 *        42 Smp Panning -> current
 *        43 Smp Panning -> start
 *        44 Smp Panning -> envelope flags
 *        45 Smp Panning -> envelope position
 *        46 Smp Panning -> Mem portamento speed
 *        47 Smp Panning -> Mem slide speed 1
 *        48 Smp Panning -> Mem slide speed 2
 *        49 Smp Panning -> Mem slowslide speed
 *        50 Smp Panning -> Mem vibrato position
 *        51 Smp Panning -> Mem vibrato speed
 *        52 Smp Panning -> Mem vibrato depth
 *        53 Smp Panning -> Mem last slide effect
 *        54 Smp Pitch -> current
 *        55 Smp Pitch -> start
 *        56 Smp Pitch -> envelope flags
 *        57 Smp Pitch -> envelope position
 *        58 Smp Pitch -> Mem slide speed 1
 *        59 Smp Pitch -> Mem slide speed 2
 *        60 Smp Pitch -> Mem slide speed 3
 *        61 Smp Pitch -> Mem slide speed 4
 *        62 Smp Pitch -> Mem slide speed 5
 *        63 Smp Pitch -> Mem slide speed 6
 *        64 Smp Pitch -> Mem slowslide speed
 *        65 Smp Pitch -> Mem vibrato position
 *        66 Smp Pitch -> Mem vibrato speed
 *        67 Smp Pitch -> Mem vibrato depth
 *        68 Smp Pitch -> Mem last slide effect
 *        69 Smp Pitch -> Mem arpeggio
 *        70 Ch -> instrument nr
 *        71 Ch -> instrument note
 *        72 Ch -> sample nr
 *        73 Ch -> sample note
 *        74 Ch -> flags
 *        75 Ch -> next instrument nr
 *        76 Ch -> next instrument note
 *        77 Smp -> Mem set offset
 *        78 Smp -> Mem retrig
 *        79 Smp -> Mem retrig position
 *        80 Smp -> Mem retrig position XM
 *        81 Smp -> Mem retrig position S3M
 *        82 Smp -> Position
 *        83 Ch Volume -> Autoslide
 *        84 Smp Volume -> Autoslide
 *        85 Smp Panning -> Autoslide
 *        86 Pitch -> Autoslide
 *        87 Stream -> Flags
 *        88 Stream -> Loop Start
 *        89 Stream -> Loop End
 *        90 Smp -> Mem gapper on time
 *        91 Smp -> Mem gapper off time
 *        92 Smp -> Mem gapper position
 */
static void channel_variable(const Channel* pChannel, int code, int* exists, int* val)
{
	if (pChannel == NULL)
	{
		*exists = 0;
		return;
	}

	Note* pNote = pChannel->pnote;

	*exists = 1;
	switch(code)
	{
		case 0: // Frame count
		{
			*val = pChannel->frame_count;
		}
		break;
		case 1: // Delay
		{
			*val = pChannel->delay;
		}
		break;
		case 2: // Note final volume
		{
			if (pNote)
				*val = pNote->volume;
			else
				*exists = 0;
		}
		break;
		case 3: // Note final panning
		{
			if (pNote)
				*val = pNote->panning;
			else
				*exists = 0;
		}
		break;
		case 4: // Note final pitch/period
		{
			if (pNote)
				*val = pNote->pitch;
			else
				*exists = 0;
		}
		break;
		case 5: // Note frequency
		{
			if (pNote)
				*val = pNote->frequency;
			else
				*exists = 0;
		}
		break;
		case 6: // Note vibrato -> running depth
		{
			if (pNote)
				*val = pNote->smp_vibr_rdepth[0]
				     + (pNote->smp_vibr_rdepth[1] << 8)
				     + (pNote->smp_vibr_rdepth[2] << 8);
			else
				*exists = 0;
		}
		break;
		case 7: // Note vibrato -> position
		{
			if (pNote)
				*val = pNote->smp_vibr_pos;
			else
				*exists = 0;
		}
		break;
		case 8: // Note flags
		{
			if (pNote)
				*val = pNote->inst_flags | (pNote->envelope_flags << 8);
			else
				*exists = 0;
		}
		break;
		case 9: // Note finetune
		{
			if (pNote)
				*val = pNote->finetune;
			else
				*exists = 0;
		}
		break;
		case 10: // Note fadeout volume
		{
			if (pNote)
				*val = pNote->fadeout_volume;
			else
				*exists = 0;
		}
		break;
		case 11: // Note NNA
		{
			if (pNote)
				*val = pNote->newnoteaction;
			else
				*exists = 0;
		}
		break;
		case 12: // Note Filter -> resonance
		{
			if (pNote && ((pNote->cutoff != 127) || pNote->resonance))
				*val = pNote->resonance;
			else
				*exists = 0;
		}
		break;
		case 13: // Note Filter -> cutoff
		{
			if (pNote && ((pNote->cutoff != 127) || pNote->resonance))
				*val = pNote->cutoff;
			else
				*exists = 0;
		}
		break;
		case 14: // Note Filter -> finalcutoff
		{
			if (pNote && ((pNote->cutoff != 127) || pNote->resonance))
				*val = pNote->realcutoff;
			else
				*exists = 0;
		}
		break;
		case 15: // Note Filter -> envelope flags
		{
			if (pNote
			&&  pNote->pinstrument
			&&  pNote->pinstrument->FilterEnvelope.Points
			   )
				*val = pNote->pinstrument->FilterEnvelope.Flags;
			else
				*exists = 0;
		}
		break;
		case 16: // Note Filter -> envelope position
		{
			if (pNote
			&&  pNote->pinstrument
			&&  pNote->pinstrument->FilterEnvelope.Points
			   )
				*val = pNote->filter_envelope_pos;
			else
				*exists = 0;
		}
		break;
		case 17: // Ch Volume -> current
		{
			*val = pChannel->GVolume;
		}
		break;
		case 18: // Ch Volume -> start
		{
			*val = pChannel->channel_volume.value.start;
		}
		break;
		case 19: // Ch Volume -> Mem portamento speed
		{
			*val = pChannel->channel_volume.portamento_speed;
		}
		break;
		case 20: // Ch Volume -> Mem slide speed 1
		{
			*val = pChannel->channel_volume.slide_speed;
		}
		break;
		case 21: // Ch Volume -> Mem slide speed 2
		{
			*val = pChannel->channel_volume.slide_speed2;
		}
		break;
		case 22: // Ch Volume -> Mem slowslide speed
		{
			*val = pChannel->channel_volume.slowslide_speed;
		}
		break;
		case 23: // Ch Volume -> Mem vibrato position
		{
			*val = pChannel->channel_volume.vibrato_pos;
		}
		break;
		case 24: // Ch Volume -> Mem vibrato speed
		{
			*val = pChannel->channel_volume.vibrato_speed;
		}
		break;
		case 25: // Ch Volume -> Mem vibrato depth
		{
			*val = pChannel->channel_volume.vibrato_depth * pChannel->channel_volume.vibrato_strength;
		}
		break;
		case 26: // Ch Volume -> Mem last slide effect
		{
			*val = pChannel->channel_volume.last_slide_effect;
		}
		break;
		case 27: // Smp Volume -> current
		{
			*val = pChannel->NVolume;
		}
		break;
		case 28: // Smp Volume -> start
		{
			*val = pChannel->note_volume.value.start;
		}
		break;
		case 29: // Smp Volume -> envelope flags
		{
			if (pNote
			&&  pNote->pinstrument
			&&  pNote->pinstrument->VolumeEnvelope.Points
			   )
				*val = pNote->pinstrument->VolumeEnvelope.Flags;
			else
				*exists = 0;
		}
		break;
		case 30: // Smp Volume -> envelope position
		{
			if (pNote
			&&  pNote->pinstrument
			&&  pNote->pinstrument->VolumeEnvelope.Points
			   )
				*val = pNote->vol_envelope_pos;
			else
				*exists = 0;
		}
		break;
		case 31: // Smp Volume -> Mem portamento speed
		{
			*val = pChannel->note_volume.portamento_speed;
		}
		break;
		case 32: // Smp Volume -> Mem slide speed 1
		{
			*val = pChannel->note_volume.slide_speed;
		}
		break;
		case 33: // Smp Volume -> Mem slide speed 2
		{
			*val = pChannel->note_volume.slide_speed2;
		}
		break;
		case 34: // Smp Volume -> Mem slowslide speed
		{
			*val = pChannel->note_volume.slowslide_speed;
		}
		break;
		case 35: // Smp Volume -> Mem vibrato position
		{
			*val = pChannel->note_volume.vibrato_pos;
		}
		break;
		case 36: // Smp Volume -> Mem vibrato speed
		{
			*val = pChannel->note_volume.vibrato_speed * pChannel->note_volume.vibrato_strength;
		}
		break;
		case 37: // Smp Volume -> Mem vibrato depth
		{
			*val = pChannel->note_volume.vibrato_depth;
		}
		break;
		case 38: // Smp Volume -> Mem last slide effect
		{
			*val = pChannel->note_volume.last_slide_effect;
		}
		break;
		case 39: // Smp Volume -> Mem tremor on time
		{
			*val = pChannel->tremor_ontime;
		}
		break;
		case 40: // Smp Volume -> Mem tremor off time
		{
			*val = pChannel->tremor_offtime;
		}
		break;
		case 41: // Smp Volume -> Mem tremor position
		{
			*val = pChannel->tremor_pos;
		}
		break;
		case 42: // Smp Panning -> current
		{
			*val = pChannel->GPanning;
		}
		break;
		case 43: // Smp Panning -> start
		{
			*val = pChannel->panning.value.start;
		}
		break;
		case 44: // Smp Panning -> envelope flags
		{
			if (pNote
			&&  pNote->pinstrument
			&&  pNote->pinstrument->PanningEnvelope.Points
			   )
				*val = pNote->pinstrument->PanningEnvelope.Flags;
			else
				*exists = 0;
		}
		break;
		case 45: // Smp Panning -> envelope position
		{
			if (pNote
			&&  pNote->pinstrument
			&&  pNote->pinstrument->PanningEnvelope.Points
			   )
				*val = pNote->pan_envelope_pos;
			else
				*exists = 0;
		}
		break;
		case 46: // Smp Panning -> Mem portamento speed
		{
			*val = pChannel->panning.portamento_speed;
		}
		break;
		case 47: // Smp Panning -> Mem slide speed 1
		{
			*val = pChannel->panning.slide_speed;
		}
		break;
		case 48: // Smp Panning -> Mem slide speed 2
		{
			*val = pChannel->panning.slide_speed2;
		}
		break;
		case 49: // Smp Panning -> Mem slowslide speed
		{
			*val = pChannel->panning.slowslide_speed;
		}
		break;
		case 50: // Smp Panning -> Mem vibrato position
		{
			*val = pChannel->panning.vibrato_pos;
		}
		break;
		case 51: // Smp Panning -> Mem vibrato speed
		{
			*val = pChannel->panning.vibrato_speed * pChannel->panning.vibrato_strength;
		}
		break;
		case 52: // Smp Panning -> Mem vibrato depth
		{
			*val = pChannel->panning.vibrato_depth;
		}
		break;
		case 53: // Smp Panning -> Mem last slide effect
		{
			*val = pChannel->panning.last_slide_effect;
		}
		break;
		case 54: // Smp Pitch -> current
		{
			*val = pChannel->note_pitch.value.final;
		}
		break;
		case 55: // Smp Pitch -> start
		{
			*val = pChannel->note_pitch.value.start;
		}
		break;
		case 56: // Smp Pitch -> envelope flags
		{
			if (pNote
			&&  pNote->pinstrument
			&&  pNote->pinstrument->PitchEnvelope.Points
			)
				*val = pNote->pinstrument->PitchEnvelope.Flags;
			else
				*exists = 0;
		}
		break;
		case 57: // Smp Pitch -> envelope position
		{
			if (pNote
			&&  pNote->pinstrument
			&&  pNote->pinstrument->PitchEnvelope.Points
			)
				*val = pNote->pitch_envelope_pos;
			else
				*exists = 0;
		}
		break;
		case 58: // Smp Pitch -> Mem slide speed 1
		{
			*val = pChannel->note_pitch.slide_speed;
		}
		break;
		case 59: // Smp Pitch -> Mem slide speed 2
		{
			*val = pChannel->note_pitch.slide_speed2;
		}
		break;
		case 60: // Smp Pitch -> Mem slide speed 3
		{
			*val = pChannel->pitch_mem3;
		}
		break;
		case 61: // Smp Pitch -> Mem slide speed 4
		{
			*val = pChannel->pitch_mem4;
		}
		break;
		case 62: // Smp Pitch -> Mem slide speed 5
		{
			*val = pChannel->pitch_mem5;
		}
		break;
		case 63: // Smp Pitch -> Mem slide speed 6
		{
			*val = pChannel->pitch_mem6;
		}
		break;
		case 64: // Smp Pitch -> Mem slowslide speed
		{
			*val = pChannel->note_pitch.slowslide_speed;
		}
		break;
		case 65: // Smp Pitch -> Mem vibrato position
		{
			*val = pChannel->note_pitch.vibrato_pos;
		}
		break;
		case 66: // Smp Pitch -> Mem vibrato speed
		{
			*val = pChannel->note_pitch.vibrato_speed * pChannel->note_pitch.vibrato_strength;
		}
		break;
		case 67: // Smp Pitch -> Mem vibrato depth
		{
			*val = pChannel->note_pitch.vibrato_depth;
		}
		break;
		case 68: // Smp Pitch -> Mem last slide effect
		{
			*val = pChannel->note_pitch.last_slide_effect;
		}
		break;
		case 69: // Smp Pitch -> Mem arpeggio
		{
			*val = pChannel->last_arpeggio;
		}
		break;
		case 70: // Ch -> instrument nr
		{
			*val = pChannel->inst_id;
		}
		break;
		case 71: // Ch -> instrument note
		{
			*val = pChannel->inst_note;
		}
		break;
		case 72: // Ch -> sample nr
		{
			*val = pChannel->smp_id;
		}
		break;
		case 73: // Ch -> sample note
		{
			*val = pChannel->smp_note;
		}
		break;
		case 74: // Ch -> flags
		{
			*val = pChannel->flags;
		}
		break;
		case 75: // Ch -> next instrument nr
		{
			*val = pChannel->new_inst_id;
		}
		break;
		case 76: // Ch -> next instrument note
		{
			*val = (pChannel->new_note_id <= Note_Max) ? pChannel->new_note_id : 0;
		}
		break;
		case 77: // Smp -> Mem set offset
		{
			*val = pChannel->last_set_offset;
		}
		break;
		case 78: // Smp -> Mem retrig
		{
			*val = pChannel->last_retrig;
		}
		break;
		case 79: // Smp -> Mem retrig position
		{
			*val = pChannel->retrig_pos;
		}
		break;
		case 80: // Smp -> Mem retrig position XM
		{
			*val = pChannel->retrig_pos_XM;
		}
		break;
		case 81: // Smp -> Mem retrig position S3M
		{
			*val = pChannel->retrig_pos_S3M;
		}
		break;
		case 82: // Smp -> Position
		{
			if (pNote && (pNote->stream.pos != -1))
				*val = pNote->stream.pos;
			else
				*exists = 0;
		}
		break;
		case 83: // Ch Volume -> Autoslide
		{
			*val = pChannel->channel_volume.autoslide;
		}
		break;
		case 84: // Smp Volume -> Autoslide
		{
			*val = pChannel->note_volume.autoslide;
		}
		break;
		case 85: // Smp Panning -> Autoslide
		{
			*val = pChannel->panning.autoslide;
		}
		break;
		case 86: // Pitch -> Autoslide
		{
			*val = pChannel->note_pitch.autoslide;
		}
		break;
		case 87: // Stream -> Flags
		{
			if (pNote && (pNote->stream.pos != -1))
				*val = pNote->stream.flags;
			else
				*exists = 0;
		}
		break;
		case 88: // Stream -> Loop Start
		{
			if (pNote && (pNote->stream.pos != -1))
				*val = pNote->stream.smp_lstart;
			else
				*exists = 0;
		}
		break;
		case 89: // Stream -> Loop End
		{
			if (pNote && (pNote->stream.pos != -1))
				*val = pNote->stream.smp_lend;
			else
				*exists = 0;
		}
		break;
		case 90: // Smp -> Mem gapper on time
		{
			*val = pChannel->gapper_ontime;
		}
		break;
		case 91: // Smp -> Mem gapper off time
		{
			*val = pChannel->gapper_offtime;
		}
		break;
		case 92: // Smp -> Mem gapper position
		{
			*val = pChannel->gapper_pos;
		}
		break;
		case 93: // Upcall param
		{
			*val = pChannel->upcall_param;
			((Channel*) pChannel)->upcall_param = 0; // reset once read
		}
		break;
		default:
			*exists = 0;
	}
}

/**
 * SWI ChannelVariable
 *
 * In:
 *   r[0] song handle or (Fx Handle + 256)
 *   r[1] if r[2] is code
 *        channel nr [0, nr of channels[
 *        if r[2] is ptr to codes list
 *        amount of channels to list
 *   r[2] < 0x8000
 *        code of variable
 *        else
 *        ptr to -1 terminated list of codes
 *   r[3] if r[2] is code list
 *        array to store results (8 bytes * amount of codes in list * r[1])
 *
 * Out:
 *  if r[2] is code variable
 *   r[3] 1 if variable is defined, 0 otherwise
 *   r[4] variable value
 *  if r[2] is code list
 *   array pointed by r3 is filled as follow:
 *   for each (channels) { for each (code) { variable existance (4 bytes), value (4 bytes) } }
 */
const _kernel_oserror* swi_Channel_Variable(GlobHdr* g, _kernel_swi_regs* r)
{
	const _kernel_oserror* e;
	unsigned int r2 = r->r[2];
	const Channel* pChannel;
	int ch, chmin, chmax;
	int* pcodes;
	int* pvalues;
	ISong* s;

	if (r2 < 0x8000)
	{
		chmin = chmax = r->r[1];
		pvalues = &r->r[3];
	}
	else
	{
		chmin = 0;
		chmax = r->r[1] - 1;
		pvalues = (int*) r->r[3];
	}

	// Parameters checks
	if ((chmin < 0) || (chmin > chmax))
		return &Err_InvalidChannelNr;

#ifndef MAKEABS
	// Just check min channel,
	// for extra channels we will just ignore invalid channels
	if (r->r[0] < 256)
	{
#endif
		e = Song_GetSongHeader(g, r->r[0], &s);
		if (e) return e;

		if (chmin >= s->Hdr.Channels)
			return &Err_InvalidChannelNr;
#ifndef MAKEABS
	}
	else
	{
		e = FX_GetFXChannel(g, r->r[0] - 256, chmin, &pChannel);
		if (e) return e;
	}
#endif

#ifndef MAKEABS
	int ioff = _kernel_irqs_disabled();
	if (!ioff) _kernel_irqs_off();
#endif

	// Loop on channels
	for (ch = chmin; ch <= chmax; ch++)
	{
		pChannel = NULL;
#ifndef MAKEABS
		if (r->r[0] < 256)
		{
#endif
			if (ch < s->Hdr.Channels)
				pChannel = &s->channels[ch];
#ifndef MAKEABS
		}
		else
		{
			FX_GetFXChannel(g, r->r[0] - 256, ch, &pChannel);
		}
#endif

		if (r2 < 0x8000)
		{
			channel_variable(pChannel, r2, pvalues, pvalues + 1);
			pvalues += 2;
		}
		else
		{
			for (pcodes = (int*) r2; *pcodes >= 0; pcodes++)
			{
				channel_variable(pChannel, *pcodes, pvalues, pvalues + 1);
				pvalues += 2;
			}
		}
	}

#ifndef MAKEABS
	if (!ioff) _kernel_irqs_on();
#endif

	return NULL;
}
