/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-2-1/engines/sci/sound/soundcmd.cpp $
 * $Id: soundcmd.cpp 52606 2010-09-06 19:03:55Z m_kiewitz $
 *
 */

#include "common/config-manager.h"
#include "sci/sound/audio.h"
#include "sci/sound/music.h"
#include "sci/sound/soundcmd.h"

#include "sci/engine/kernel.h"
#include "sci/engine/selector.h"

namespace Sci {

SoundCommandParser::SoundCommandParser(ResourceManager *resMan, SegManager *segMan, Kernel *kernel, AudioPlayer *audio, SciVersion soundVersion) :
	_resMan(resMan), _segMan(segMan), _kernel(kernel), _audio(audio), _soundVersion(soundVersion) {

	_music = new SciMusic(_soundVersion);
	_music->init();
}

SoundCommandParser::~SoundCommandParser() {
	delete _music;
}

reg_t SoundCommandParser::kDoSoundInit(int argc, reg_t *argv, reg_t acc) {
	debugC(2, kDebugLevelSound, "kDoSound(init): %04x:%04x", PRINT_REG(argv[0]));
	processInitSound(argv[0]);
	return acc;
}

void SoundCommandParser::processInitSound(reg_t obj) {
	int resourceId = readSelectorValue(_segMan, obj, SELECTOR(number));

	// Check if a track with the same sound object is already playing
	MusicEntry *oldSound = _music->getSlot(obj);
	if (oldSound)
		processDisposeSound(obj);

	MusicEntry *newSound = new MusicEntry();
	newSound->resourceId = resourceId;
	if (resourceId && _resMan->testResource(ResourceId(kResourceTypeSound, resourceId)))
		newSound->soundRes = new SoundResource(resourceId, _resMan, _soundVersion);
	else
		newSound->soundRes = 0;

	newSound->soundObj = obj;
	newSound->loop = readSelectorValue(_segMan, obj, SELECTOR(loop));
	newSound->priority = readSelectorValue(_segMan, obj, SELECTOR(pri)) & 0xFF;
	if (_soundVersion >= SCI_VERSION_1_EARLY)
		newSound->volume = CLIP<int>(readSelectorValue(_segMan, obj, SELECTOR(vol)), 0, MUSIC_VOLUME_MAX);

	debugC(2, kDebugLevelSound, "kDoSound(init): %04x:%04x number %d, loop %d, prio %d, vol %d", PRINT_REG(obj),
			resourceId,	newSound->loop, newSound->priority, newSound->volume);

	// In SCI1.1 games, sound effects are started from here. If we can find
	// a relevant audio resource, play it, otherwise switch to synthesized
	// effects. If the resource exists, play it using map 65535 (sound
	// effects map)
	bool checkAudioResource = getSciVersion() >= SCI_VERSION_1_1;
	if (g_sci->getGameId() == GID_HOYLE4)
		checkAudioResource = false; // hoyle 4 has garbled audio resources in place of the sound resources
	// if we play those, we will only make the user deaf and break speakers. Sierra SCI doesn't play anything
	// on soundblaster. FIXME: check, why this is

	if (checkAudioResource && _resMan->testResource(ResourceId(kResourceTypeAudio, resourceId))) {
		// Found a relevant audio resource, play it
		int sampleLen;
		newSound->pStreamAud = _audio->getAudioStream(resourceId, 65535, &sampleLen);
		newSound->soundType = Audio::Mixer::kSpeechSoundType;
	} else {
		if (newSound->soundRes)
			_music->soundInitSnd(newSound);
	}

	_music->pushBackSlot(newSound);

	if (newSound->soundRes || newSound->pStreamAud) {
		// Notify the engine
		if (_soundVersion <= SCI_VERSION_0_LATE)
			writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundInitialized);
		else
			writeSelector(_segMan, obj, SELECTOR(nodePtr), obj);

		writeSelector(_segMan, obj, SELECTOR(handle), obj);
	}
}

reg_t SoundCommandParser::kDoSoundPlay(int argc, reg_t *argv, reg_t acc) {
	debugC(2, kDebugLevelSound, "kDoSound(play): %04x:%04x", PRINT_REG(argv[0]));
	processPlaySound(argv[0]);
	return acc;
}

void SoundCommandParser::processPlaySound(reg_t obj) {
	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		warning("kDoSound(play): Slot not found (%04x:%04x)", PRINT_REG(obj));
		return;
	}

	int resourceId = obj.segment ? readSelectorValue(_segMan, obj, SELECTOR(number)) : -1;

	if (musicSlot->resourceId != resourceId) { // another sound loaded into struct
		processDisposeSound(obj);
		processInitSound(obj);
		// Find slot again :)
		musicSlot = _music->getSlot(obj);
	}

	writeSelector(_segMan, obj, SELECTOR(handle), obj);

	if (_soundVersion >= SCI_VERSION_1_EARLY) {
		writeSelector(_segMan, obj, SELECTOR(nodePtr), obj);
		writeSelectorValue(_segMan, obj, SELECTOR(min), 0);
		writeSelectorValue(_segMan, obj, SELECTOR(sec), 0);
		writeSelectorValue(_segMan, obj, SELECTOR(frame), 0);
		writeSelectorValue(_segMan, obj, SELECTOR(signal), 0);
	} else {
		writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundPlaying);
	}

	musicSlot->loop = readSelectorValue(_segMan, obj, SELECTOR(loop));
	musicSlot->priority = readSelectorValue(_segMan, obj, SELECTOR(priority));
	if (_soundVersion >= SCI_VERSION_1_EARLY)
		musicSlot->volume = readSelectorValue(_segMan, obj, SELECTOR(vol));

	debugC(2, kDebugLevelSound, "kDoSound(play): %04x:%04x number %d, loop %d, prio %d, vol %d", PRINT_REG(obj),
			resourceId, musicSlot->loop, musicSlot->priority, musicSlot->volume);

	_music->soundPlay(musicSlot);
}

reg_t SoundCommandParser::kDoSoundRestore(int argc, reg_t *argv, reg_t acc) {
	// Called after loading, to restore the playlist
	// We don't really use or need this
	return acc;
}

reg_t SoundCommandParser::kDoSoundDummy(int argc, reg_t *argv, reg_t acc) {
	warning("cmdDummy invoked");	// not supposed to occur
	return acc;
}

reg_t SoundCommandParser::kDoSoundDispose(int argc, reg_t *argv, reg_t acc) {
	debugC(2, kDebugLevelSound, "kDoSound(dispose): %04x:%04x", PRINT_REG(argv[0]));
	processDisposeSound(argv[0]);
	return acc;
}

void SoundCommandParser::processDisposeSound(reg_t obj) {
	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		warning("kDoSound(dispose): Slot not found (%04x:%04x)", PRINT_REG(obj));
		return;
	}

	processStopSound(obj, false);

	_music->soundKill(musicSlot);
	writeSelectorValue(_segMan, obj, SELECTOR(handle), 0);
	if (_soundVersion >= SCI_VERSION_1_EARLY)
		writeSelector(_segMan, obj, SELECTOR(nodePtr), NULL_REG);
	else
		writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundStopped);
}

reg_t SoundCommandParser::kDoSoundStop(int argc, reg_t *argv, reg_t acc) {
	debugC(2, kDebugLevelSound, "kDoSound(stop): %04x:%04x", PRINT_REG(argv[0]));
	processStopSound(argv[0], false);
	return acc;
}

void SoundCommandParser::processStopSound(reg_t obj, bool sampleFinishedPlaying) {
	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		warning("kDoSound(stop): Slot not found (%04x:%04x)", PRINT_REG(obj));
		return;
	}

	if (_soundVersion <= SCI_VERSION_0_LATE) {
		writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundStopped);
	} else {
		writeSelectorValue(_segMan, obj, SELECTOR(handle), 0);
	}

	// Set signal selector in sound SCI0 games only, when the sample has
	// finished playing. If we don't set it at all, we get a problem when using
	// vaporizer on the 2 guys. If we set it all the time, we get no music in
	// sq3new and kq1.
	// FIXME: This *may* be wrong, it's impossible to find out in Sierra DOS
	//        SCI, because SCI0 under DOS didn't have sfx drivers included.
	// We need to set signal in sound SCI1+ games all the time.
	if ((_soundVersion > SCI_VERSION_0_LATE) || sampleFinishedPlaying)
		writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET);

	musicSlot->dataInc = 0;
	musicSlot->signal = 0;
	_music->soundStop(musicSlot);
}

reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {
	if (argc == 1)
		debugC(2, kDebugLevelSound, "kDoSound(pause): %04x:%04x", PRINT_REG(argv[0]));
	else
		debugC(2, kDebugLevelSound, "kDoSound(pause): %04x:%04x, %04x:%04x", PRINT_REG(argv[0]), PRINT_REG(argv[1]));

	if (_soundVersion <= SCI_VERSION_0_LATE) {
		// SCI0 games give us 0/1 for either resuming or pausing the current music
		//  this one doesn't count, so pausing 2 times and resuming once means here that we are supposed to resume
		uint16 value = argv[0].toUint16();
		MusicEntry *musicSlot = _music->getActiveSci0MusicSlot();
		switch (value) {
		case 1:
			if ((musicSlot) && (musicSlot->status == kSoundPlaying)) {
				_music->soundPause(musicSlot);
				writeSelectorValue(_segMan, musicSlot->soundObj, SELECTOR(state), kSoundPaused);
			}
			return make_reg(0, 0);
		case 0:
			if ((musicSlot) && (musicSlot->status == kSoundPaused)) {
				_music->soundResume(musicSlot);
				writeSelectorValue(_segMan, musicSlot->soundObj, SELECTOR(state), kSoundPlaying);
				return make_reg(0, 1);
			}
			return make_reg(0, 0);
		default:
			error("kDoSound(pause): parameter 0 is invalid for sound-sci0");
		}
	}

	reg_t obj = argv[0];
	uint16 value = argc > 1 ? argv[1].toUint16() : 0;
	if (!obj.segment) {		// pause the whole playlist
		_music->pauseAll(value);
	} else {	// pause a playlist slot
		MusicEntry *musicSlot = _music->getSlot(obj);
		if (!musicSlot) {
			// This happens quite frequently
			debugC(2, kDebugLevelSound, "kDoSound(pause): Slot not found (%04x:%04x)", PRINT_REG(obj));
			return acc;
		}

		_music->soundToggle(musicSlot, value);
	}
	return acc;
}

// SCI0 only command
//  It's called right after restoring a game - it's responsible to kick off playing music again
//  we don't need this at all, so we don't do anything here
reg_t SoundCommandParser::kDoSoundResumeAfterRestore(int argc, reg_t *argv, reg_t acc) {
	return acc;
}

reg_t SoundCommandParser::kDoSoundMute(int argc, reg_t *argv, reg_t acc) {
	uint16 previousState = _music->soundGetSoundOn();
	if (argc > 0) {
		debugC(2, kDebugLevelSound, "kDoSound(mute): %d", argv[0].toUint16());
		_music->soundSetSoundOn(argv[0].toUint16());
	}

	return make_reg(0, previousState);
}

reg_t SoundCommandParser::kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc) {
	acc = make_reg(0, _music->soundGetMasterVolume());

	if (argc > 0) {
		debugC(2, kDebugLevelSound, "kDoSound(masterVolume): %d", argv[0].toSint16());
		int vol = CLIP<int16>(argv[0].toSint16(), 0, MUSIC_MASTERVOLUME_MAX);
		vol = vol * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX;
		ConfMan.setInt("music_volume", vol);
		ConfMan.setInt("sfx_volume", vol);
		g_engine->syncSoundSettings();
	}
	return acc;
}

reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
	reg_t obj = argv[0];

	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		debugC(2, kDebugLevelSound, "kDoSound(fade): Slot not found (%04x:%04x)", PRINT_REG(obj));
		return acc;
	}

	int volume = musicSlot->volume;

	// If sound is not playing currently, set signal directly
	if (musicSlot->status != kSoundPlaying) {
		debugC(2, kDebugLevelSound, "kDoSound(fade): %04x:%04x fading requested, but sound is currently not playing", PRINT_REG(obj));
		writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET);
		return acc;
	}

	switch (argc) {
	case 1: // SCI0
		// SCI0 fades out all the time and when fadeout is done it will also
		// stop the music from playing
		musicSlot->fadeTo = 0;
		musicSlot->fadeStep = -5;
		musicSlot->fadeTickerStep = 10 * 16667 / _music->soundGetTempo();
		musicSlot->fadeTicker = 0;
		break;

	case 4: // SCI01+
	case 5: // SCI1+ (SCI1 late sound scheme), with fade and continue
		musicSlot->fadeTo = CLIP<uint16>(argv[1].toUint16(), 0, MUSIC_VOLUME_MAX);
		// sometimes we get objects in that position, fix it up (ffs. workarounds)
		if (!argv[1].segment)
			musicSlot->fadeStep = volume > musicSlot->fadeTo ? -argv[3].toUint16() : argv[3].toUint16();
		else
			musicSlot->fadeStep = volume > musicSlot->fadeTo ? -5 : 5;
		musicSlot->fadeTickerStep = argv[2].toUint16() * 16667 / _music->soundGetTempo();
		musicSlot->fadeTicker = 0;
		musicSlot->stopAfterFading = (argc == 5) ? (argv[4].toUint16() != 0) : false;
		break;

	default:
		error("kDoSound(fade): unsupported argc %d", argc);
	}

	debugC(2, kDebugLevelSound, "kDoSound(fade): %04x:%04x to %d, step %d, ticker %d", PRINT_REG(obj), musicSlot->fadeTo, musicSlot->fadeStep, musicSlot->fadeTickerStep);
	return acc;
}

reg_t SoundCommandParser::kDoSoundGetPolyphony(int argc, reg_t *argv, reg_t acc) {
	return make_reg(0, _music->soundGetVoices());	// Get the number of voices
}

reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) {
	reg_t obj = argv[0];

	debugC(2, kDebugLevelSound, "kDoSound(update): %04x:%04x", PRINT_REG(argv[0]));

	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		warning("kDoSound(update): Slot not found (%04x:%04x)", PRINT_REG(obj));
		return acc;
	}

	musicSlot->loop = readSelectorValue(_segMan, obj, SELECTOR(loop));
	int16 objVol = CLIP<int>(readSelectorValue(_segMan, obj, SELECTOR(vol)), 0, 255);
	if (objVol != musicSlot->volume)
		_music->soundSetVolume(musicSlot, objVol);
	uint32 objPrio = readSelectorValue(_segMan, obj, SELECTOR(pri));
	if (objPrio != musicSlot->priority)
		_music->soundSetPriority(musicSlot, objPrio);
	return acc;
}

reg_t SoundCommandParser::kDoSoundUpdateCues(int argc, reg_t *argv, reg_t acc) {
	processUpdateCues(argv[0]);
	return acc;
}

void SoundCommandParser::processUpdateCues(reg_t obj) {
	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		warning("kDoSound(updateCues): Slot not found (%04x:%04x)", PRINT_REG(obj));
		return;
	}

	if (musicSlot->pStreamAud) {
		// Update digital sound effect slots
		uint currentLoopCounter = 0;

		if (musicSlot->pLoopStream)
			currentLoopCounter = musicSlot->pLoopStream->getCompleteIterations();

		if (currentLoopCounter != musicSlot->sampleLoopCounter) {
			// during last time we looped at least one time, update loop accordingly
			musicSlot->loop -= currentLoopCounter - musicSlot->sampleLoopCounter;
			musicSlot->sampleLoopCounter = currentLoopCounter;
		}
		if (musicSlot->status == kSoundPlaying) {
			if (!_music->soundIsActive(musicSlot)) {
				processStopSound(obj, true);
			} else {
				_music->updateAudioStreamTicker(musicSlot);
			}
		} else if (musicSlot->status == kSoundPaused) {
			_music->updateAudioStreamTicker(musicSlot);
		}
		// We get a flag from MusicEntry::doFade() here to set volume for the stream
		if (musicSlot->fadeSetVolume) {
			_music->soundSetSampleVolume(musicSlot, musicSlot->volume);
			musicSlot->fadeSetVolume = false;
		}
	} else if (musicSlot->pMidiParser) {
		// Update MIDI slots
		if (musicSlot->signal == 0) {
			if (musicSlot->dataInc != readSelectorValue(_segMan, obj, SELECTOR(dataInc))) {
				if (SELECTOR(dataInc) > -1)
					writeSelectorValue(_segMan, obj, SELECTOR(dataInc), musicSlot->dataInc);
				writeSelectorValue(_segMan, obj, SELECTOR(signal), musicSlot->dataInc + 127);
			}
		} else {
			// Sync the signal of the sound object
			writeSelectorValue(_segMan, obj, SELECTOR(signal), musicSlot->signal);
			// We need to do this especially because state selector needs to get updated
			if (musicSlot->signal == SIGNAL_OFFSET)
				processStopSound(obj, false);
		}
	} else {
		// Slot actually has no data (which would mean that a sound-resource w/
		// unsupported data is used.
		//  (example lsl5 - sound resource 744 - it's roland exclusive
		writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET);
		// If we don't set signal here, at least the switch to the mud wrestling
		// room in lsl5 will not work.
	}

	if (musicSlot->fadeCompleted) {
		musicSlot->fadeCompleted = false;
		// We need signal for sci0 at least in iceman as well (room 14, fireworks)
		writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET);
		if (_soundVersion <= SCI_VERSION_0_LATE) {
			processStopSound(obj, false);
		} else {
			if (musicSlot->stopAfterFading)
				processStopSound(obj, false);
		}
	}

	// Sync loop selector for SCI0
	if (_soundVersion <= SCI_VERSION_0_LATE)
		writeSelectorValue(_segMan, obj, SELECTOR(loop), musicSlot->loop);

	musicSlot->signal = 0;

	if (_soundVersion >= SCI_VERSION_1_EARLY) {
		writeSelectorValue(_segMan, obj, SELECTOR(min), musicSlot->ticker / 3600);
		writeSelectorValue(_segMan, obj, SELECTOR(sec), musicSlot->ticker % 3600 / 60);
		writeSelectorValue(_segMan, obj, SELECTOR(frame), musicSlot->ticker);
	}
}

reg_t SoundCommandParser::kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc) {
	reg_t obj = argv[0];
	byte channel = argv[1].toUint16() & 0xf;
	byte midiCmd = argv[2].toUint16() & 0xff;

	// TODO: first there is a 4-parameter variant of this call which needs to get reversed
	//        second the current code isn't 100% accurate, sierra sci does checks on the 4th parameter
	if (argc == 4)
		return acc;

	uint16 controller = argv[3].toUint16();
	uint16 param = argv[4].toUint16();

	debugC(2, kDebugLevelSound, "kDoSound(sendMidi): %04x:%04x, %d, %d, %d, %d", PRINT_REG(obj), channel, midiCmd, controller, param);
	if (channel)
		channel--; // channel is given 1-based, we are using 0-based

	uint32 midiCommand = (channel | midiCmd) | ((uint32)controller << 8) | ((uint32)param << 16);

	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		// TODO: maybe it's possible to call this with obj == 0:0 and send directly?!
		// if so, allow it
		//_music->sendMidiCommand(_midiCommand);
		warning("kDoSound(sendMidi): Slot not found (%04x:%04x)", PRINT_REG(obj));
		return acc;
	}
	_music->sendMidiCommand(musicSlot, midiCommand);
	return acc;
}

reg_t SoundCommandParser::kDoSoundReverb(int argc, reg_t *argv, reg_t acc) {
	debugC(2, kDebugLevelSound, "doSoundReverb: %d", argv[0].toUint16() & 0xF);
	_music->setReverb(argv[0].toUint16() & 0xF);
	return acc;
}

reg_t SoundCommandParser::kDoSoundSetHold(int argc, reg_t *argv, reg_t acc) {
	reg_t obj = argv[0];

	debugC(2, kDebugLevelSound, "doSoundSetHold: %04x:%04x, %d", PRINT_REG(argv[0]), argv[1].toUint16());

	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		warning("kDoSound(setHold): Slot not found (%04x:%04x)", PRINT_REG(obj));
		return acc;
	}

	// Set the special hold marker ID where the song should be looped at.
	musicSlot->hold = argv[1].toSint16();
	return acc;
}

reg_t SoundCommandParser::kDoSoundGetAudioCapability(int argc, reg_t *argv, reg_t acc) {
	// Tests for digital audio support
	return make_reg(0, 1);
}

reg_t SoundCommandParser::kDoSoundStopAll(int argc, reg_t *argv, reg_t acc) {
	// TODO: this can't be right, this gets called in kq1 - e.g. being in witch house, getting the note
	//  now the point jingle plays and after a messagebox they call this - and would stop the background effects with it
	//  this doesn't make sense, so i disable it for now
	return acc;

	Common::StackLock(_music->_mutex);

	const MusicList::iterator end = _music->getPlayListEnd();
	for (MusicList::iterator i = _music->getPlayListStart(); i != end; ++i) {
		if (_soundVersion <= SCI_VERSION_0_LATE) {
			writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(state), kSoundStopped);
		} else {
			writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(handle), 0);
			writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(signal), SIGNAL_OFFSET);
		}

		(*i)->dataInc = 0;
		_music->soundStop(*i);
	}
	return acc;
}

reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) {
	reg_t obj = argv[0];
	int16 value = argv[1].toSint16();

	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		// Do not throw a warning if the sound can't be found, as in some games
		// this is called before the actual sound is loaded (e.g. SQ4CD, with
		// the drum sounds of the energizer bunny at the beginning), so this is
		// normal behavior.
		//warning("cmdSetSoundVolume: Slot not found (%04x:%04x)", PRINT_REG(obj));
		return acc;
	}

	debugC(2, kDebugLevelSound, "kDoSound(setVolume): %d", value);

	value = CLIP<int>(value, 0, MUSIC_VOLUME_MAX);

	if (musicSlot->volume != value) {
		musicSlot->volume = value;
		_music->soundSetVolume(musicSlot, value);
		writeSelectorValue(_segMan, obj, SELECTOR(vol), value);
	}
	return acc;
}

reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc) {
	reg_t obj = argv[0];
	int16 value = argv[1].toSint16();

	debugC(2, kDebugLevelSound, "kDoSound(setPriority): %04x:%04x, %d", PRINT_REG(obj), value);

	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		debugC(2, kDebugLevelSound, "kDoSound(setPriority): Slot not found (%04x:%04x)", PRINT_REG(obj));
		return acc;
	}

	if (value == -1) {
		// Set priority from the song data
		Resource *song = _resMan->findResource(ResourceId(kResourceTypeSound, musicSlot->resourceId), 0);
		if (song->data[0] == 0xf0)
			_music->soundSetPriority(musicSlot, song->data[1]);
		else
			warning("kDoSound(setPriority): Attempt to unset song priority when there is no built-in value");

		//pSnd->prio=0;field_15B=0
		writeSelectorValue(_segMan, obj, SELECTOR(flags), readSelectorValue(_segMan, obj, SELECTOR(flags)) & 0xFD);
	} else {
		// Scripted priority

		//pSnd->field_15B=1;
		writeSelectorValue(_segMan, obj, SELECTOR(flags), readSelectorValue(_segMan, obj, SELECTOR(flags)) | 2);
		//DoSOund(0xF,hobj,w)
	}
	return acc;
}

reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {
	reg_t obj = argv[0];
	int16 value = argv[1].toSint16();

	debugC(2, kDebugLevelSound, "kDoSound(setLoop): %04x:%04x, %d", PRINT_REG(obj), value);

	MusicEntry *musicSlot = _music->getSlot(obj);
	if (!musicSlot) {
		// Apparently, it's perfectly normal for a game to call cmdSetSoundLoop
		// before actually initializing the sound and adding it to the playlist
		// with cmdInitSound. Usually, it doesn't matter if the game doesn't
		// request to loop the sound, so in this case, don't throw any warning,
		// otherwise do, because the sound won't be looped.
		if (value == -1) {
			warning("kDoSound(setLoop): Slot not found (%04x:%04x) and the song was requested to be looped", PRINT_REG(obj));
		} else {
			// Doesn't really matter
		}
		return acc;
	}
	if (value == -1) {
		musicSlot->loop = 0xFFFF;
	} else {
		musicSlot->loop = 1; // actually plays the music once
	}

	writeSelectorValue(_segMan, obj, SELECTOR(loop), musicSlot->loop);
	return acc;
}

reg_t SoundCommandParser::kDoSoundSuspend(int argc, reg_t *argv, reg_t acc) {
	// TODO
	warning("kDoSound(suspend): STUB");
	return acc;
}

void SoundCommandParser::updateSci0Cues() {
	bool noOnePlaying = true;
	MusicEntry *pWaitingForPlay = NULL;

	const MusicList::iterator end = _music->getPlayListEnd();
	for (MusicList::iterator i = _music->getPlayListStart(); i != end; ++i) {
		// Is the sound stopped, and the sound object updated too? If yes, skip
		// this sound, as SCI0 only allows one active song.
		if  ((*i)->isQueued) {
			pWaitingForPlay = (*i);
			// FIXME(?): In iceman 2 songs are queued when playing the door
			// sound - if we use the first song for resuming then it's the wrong
			// one. Both songs have same priority. Maybe the new sound function
			// in sci0 is somehow responsible.
			continue;
		}
		if ((*i)->signal == 0 && (*i)->status != kSoundPlaying)
			continue;

		processUpdateCues((*i)->soundObj);
		noOnePlaying = false;
	}

	if (noOnePlaying && pWaitingForPlay) {
		// If there is a queued entry, play it now ffs: SciMusic::soundPlay()
		pWaitingForPlay->isQueued = false;
		_music->soundPlay(pWaitingForPlay);
	}
}

void SoundCommandParser::clearPlayList() {
	_music->clearPlayList();
}

void SoundCommandParser::printPlayList(Console *con) {
	_music->printPlayList(con);
}

void SoundCommandParser::printSongInfo(reg_t obj, Console *con) {
	_music->printSongInfo(obj, con);
}

void SoundCommandParser::stopAllSounds() {
	_music->stopAll();
}

void SoundCommandParser::startNewSound(int number) {
	Common::StackLock lock(_music->_mutex);

	// Overwrite the first sound in the playlist
	MusicEntry *song = *_music->getPlayListStart();
	reg_t soundObj = song->soundObj;
	processDisposeSound(soundObj);
	writeSelectorValue(_segMan, soundObj, SELECTOR(number), number);
	processInitSound(soundObj);
	processPlaySound(soundObj);
}

void SoundCommandParser::setMasterVolume(int vol) {
	// 0...15
	_music->soundSetMasterVolume(vol);
}

void SoundCommandParser::pauseAll(bool pause) {
	_music->pauseAll(pause);
}

MusicType SoundCommandParser::getMusicType() const {
	assert(_music);
	return _music->soundGetMusicType();
}

} // End of namespace Sci
