diff --git a/.gitignore b/.gitignore index 2970ca3a675..8f08a423db5 100644 --- a/.gitignore +++ b/.gitignore @@ -111,6 +111,8 @@ ipch/ #Ignore default Visual Studio build folders [Dd]ebug*/ [Rr]elease*/ +LLVM32/ +LLVM64/ #Ignore Qt Creator project files ResidualVM.config @@ -121,6 +123,9 @@ ResidualVM.includes #Ignore Komodo IDE/Edit project files *.komodoproject +#Ignore Mac DS_Store files +.DS_Store + # Emacs files *~ .#* diff --git a/audio/midiparser.cpp b/audio/midiparser.cpp index 9c144c24794..2454575413a 100644 --- a/audio/midiparser.cpp +++ b/audio/midiparser.cpp @@ -44,7 +44,8 @@ _centerPitchWheelOnUnload(false), _sendSustainOffOnNotesOff(false), _numTracks(0), _activeTrack(255), -_abortParse(0) { +_abortParse(false), +_jumpingToTick(false) { memset(_activeNotes, 0, sizeof(_activeNotes)); memset(_tracks, 0, sizeof(_tracks)); _nextEvent.start = NULL; @@ -204,49 +205,22 @@ void MidiParser::onTimer() { return; } - if (info.event == 0xF0) { - // SysEx event - // Check for trailing 0xF7 -- if present, remove it. - if (info.ext.data[info.length-1] == 0xF7) - _driver->sysEx(info.ext.data, (uint16)info.length-1); + if (info.command() == 0x8) { + activeNote(info.channel(), info.basic.param1, false); + } else if (info.command() == 0x9) { + if (info.length > 0) + hangingNote(info.channel(), info.basic.param1, info.length * _psecPerTick - (endTime - eventTime)); else - _driver->sysEx(info.ext.data, (uint16)info.length); - } else if (info.event == 0xFF) { - // META event - if (info.ext.type == 0x2F) { - // End of Track must be processed by us, - // as well as sending it to the output device. - if (_autoLoop) { - jumpToTick(0); - parseNextEvent(_nextEvent); - } else { - stopPlaying(); - _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length); - } - return; - } else if (info.ext.type == 0x51) { - if (info.length >= 3) { - setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); - } - } - _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length); - } else { - if (info.command() == 0x8) { - activeNote(info.channel(), info.basic.param1, false); - } else if (info.command() == 0x9) { - if (info.length > 0) - hangingNote(info.channel(), info.basic.param1, info.length * _psecPerTick - (endTime - eventTime)); - else - activeNote(info.channel(), info.basic.param1, true); - } - sendToDriver(info.event, info.basic.param1, info.basic.param2); + activeNote(info.channel(), info.basic.param1, true); } + processEvent(info); - if (!_abortParse) { - _position._lastEventTime = eventTime; - parseNextEvent(_nextEvent); - } + if (_abortParse) + break; + + _position._lastEventTime = eventTime; + parseNextEvent(_nextEvent); } if (!_abortParse) { @@ -255,6 +229,45 @@ void MidiParser::onTimer() { } } +void MidiParser::processEvent(const EventInfo &info, bool fireEvents) { + if (info.event == 0xF0) { + // SysEx event + // Check for trailing 0xF7 -- if present, remove it. + if (fireEvents) { + if (info.ext.data[info.length-1] == 0xF7) + _driver->sysEx(info.ext.data, (uint16)info.length-1); + else + _driver->sysEx(info.ext.data, (uint16)info.length); + } + } else if (info.event == 0xFF) { + // META event + if (info.ext.type == 0x2F) { + // End of Track must be processed by us, + // as well as sending it to the output device. + if (_autoLoop) { + jumpToTick(0); + parseNextEvent(_nextEvent); + } else { + stopPlaying(); + if (fireEvents) + _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length); + } + _abortParse = true; + return; + } else if (info.ext.type == 0x51) { + if (info.length >= 3) { + setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); + } + } + if (fireEvents) + _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length); + } else { + if (fireEvents) + sendToDriver(info.event, info.basic.param1, info.basic.param2); + } +} + + void MidiParser::allNotesOff() { if (!_driver) return; @@ -370,6 +383,9 @@ bool MidiParser::jumpToTick(uint32 tick, bool fireEvents, bool stopNotes, bool d if (_activeTrack >= _numTracks) return false; + assert(!_jumpingToTick); // This function is not re-entrant + _jumpingToTick = true; + Tracker currentPos(_position); EventInfo currentEvent(_nextEvent); @@ -390,34 +406,19 @@ bool MidiParser::jumpToTick(uint32 tick, bool fireEvents, bool stopNotes, bool d _position._playTick = _position._lastEventTick; _position._playTime = _position._lastEventTime; - if (info.event == 0xFF) { - if (info.ext.type == 0x2F) { // End of track - _position = currentPos; - _nextEvent = currentEvent; - return false; - } else { - if (info.ext.type == 0x51 && info.length >= 3) // Tempo - setTempo(info.ext.data[0] << 16 | info.ext.data[1] << 8 | info.ext.data[2]); - if (fireEvents) - _driver->metaEvent(info.ext.type, info.ext.data, (uint16) info.length); - } - } else if (fireEvents) { - if (info.event == 0xF0) { - if (info.ext.data[info.length-1] == 0xF7) - _driver->sysEx(info.ext.data, (uint16)info.length-1); - else - _driver->sysEx(info.ext.data, (uint16)info.length); - } else { - // The note on sending code is used by the SCUMM engine. Other engine using this code - // (such as SCI) have issues with this, as all the notes sent can be heard when a song - // is fast-forwarded. Thus, if the engine requests it, don't send note on events. - if (info.command() == 0x9 && dontSendNoteOn) { - // Don't send note on; doing so creates a "warble" with some instruments on the MT-32. - // Refer to patch #3117577 - } else { - sendToDriver(info.event, info.basic.param1, info.basic.param2); - } - } + // Some special processing for the fast-forward case + if (info.command() == 0x9 && dontSendNoteOn) { + // Don't send note on; doing so creates a "warble" with + // some instruments on the MT-32. Refer to patch #3117577 + } else if (info.event == 0xFF && info.ext.type == 0x2F) { + // End of track + // This means that we failed to find the right tick. + _position = currentPos; + _nextEvent = currentEvent; + _jumpingToTick = false; + return false; + } else { + processEvent(info, fireEvents); } parseNextEvent(_nextEvent); @@ -441,6 +442,7 @@ bool MidiParser::jumpToTick(uint32 tick, bool fireEvents, bool stopNotes, bool d } _abortParse = true; + _jumpingToTick = false; return true; } diff --git a/audio/midiparser.h b/audio/midiparser.h index bb9749b97fc..05d0cbe1db2 100644 --- a/audio/midiparser.h +++ b/audio/midiparser.h @@ -105,8 +105,8 @@ struct EventInfo { ///< will occur, and the MidiParser will have to generate one itself. ///< For all other events, this value should always be zero. - byte channel() { return event & 0x0F; } ///< Separates the MIDI channel from the event. - byte command() { return event >> 4; } ///< Separates the command code from the event. + byte channel() const { return event & 0x0F; } ///< Separates the MIDI channel from the event. + byte command() const { return event >> 4; } ///< Separates the command code from the event. }; /** @@ -287,12 +287,14 @@ protected: ///< so each event is parsed only once; this permits ///< simulated events in certain formats. bool _abortParse; ///< If a jump or other operation interrupts parsing, flag to abort. + bool _jumpingToTick; ///< True if currently inside jumpToTick protected: static uint32 readVLQ(byte * &data); virtual void resetTracking(); virtual void allNotesOff(); virtual void parseNextEvent(EventInfo &info) = 0; + virtual void processEvent(const EventInfo &info, bool fireEvents = true); void activeNote(byte channel, byte note, bool active); void hangingNote(byte channel, byte note, uint32 ticksLeft, bool recycle = true); diff --git a/audio/mixer.cpp b/audio/mixer.cpp index ab3ed9eb2de..9e6e4596e28 100644 --- a/audio/mixer.cpp +++ b/audio/mixer.cpp @@ -429,7 +429,11 @@ void MixerImpl::pauseHandle(SoundHandle handle, bool paused) { bool MixerImpl::isSoundIDActive(int id) { Common::StackLock lock(_mutex); + +#ifdef ENABLE_EVENTRECORDER g_eventRec.updateSubsystems(); +#endif + for (int i = 0; i != NUM_CHANNELS; i++) if (_channels[i] && _channels[i]->getId() == id) return true; @@ -446,7 +450,11 @@ int MixerImpl::getSoundID(SoundHandle handle) { bool MixerImpl::isSoundHandleActive(SoundHandle handle) { Common::StackLock lock(_mutex); + +#ifdef ENABLE_EVENTRECORDER g_eventRec.updateSubsystems(); +#endif + const int index = handle._val % NUM_CHANNELS; return _channels[index] && _channels[index]->getHandle()._val == handle._val; } diff --git a/audio/softsynth/mt32/AReverbModel.cpp b/audio/softsynth/mt32/AReverbModel.cpp deleted file mode 100644 index 1d638321575..00000000000 --- a/audio/softsynth/mt32/AReverbModel.cpp +++ /dev/null @@ -1,277 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -#include "mt32emu.h" - -#if MT32EMU_USE_REVERBMODEL == 1 - -#include "AReverbModel.h" - -// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that -// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF) -// and followed by three parallel comb filters - -namespace MT32Emu { - -// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay, -// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data. -// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level, -// so we can simply increase the input buffer size. -static const Bit32u PROCESS_DELAY = 1; - -// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different. -// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog). - -static const Bit32u NUM_ALLPASSES = 3; -static const Bit32u NUM_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be perfectly processed via a comb here. - -static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78}; -static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632}; -static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960}; -static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145}; -static const Bit32u MODE_0_COMB_FACTOR[] = {0x3C, 0x60, 0x60, 0x60}; -static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, - 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, - 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; -static const Bit32u MODE_0_LEVELS[] = {10*1, 10*3, 10*5, 10*7, 11*9, 11*12, 11*15, 13*15}; -static const Bit32u MODE_0_LPF_AMP = 6; - -static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176}; -static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519}; -static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518}; -static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274}; -static const Bit32u MODE_1_COMB_FACTOR[] = {0x30, 0x60, 0x60, 0x60}; -static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98, - 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, - 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; -static const Bit32u MODE_1_LEVELS[] = {10*1, 10*3, 11*5, 11*7, 11*9, 11*12, 11*15, 14*15}; -static const Bit32u MODE_1_LPF_AMP = 6; - -static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157}; -static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539}; -static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769}; -static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1}; -static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20}; -static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0, - 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0, - 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0}; -static const Bit32u MODE_2_LEVELS[] = {10*1, 10*3, 11*5, 11*7, 11*9, 11*12, 12*15, 14*15}; -static const Bit32u MODE_2_LPF_AMP = 8; - -static const AReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_LEVELS, MODE_0_LPF_AMP}; -static const AReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_LEVELS, MODE_1_LPF_AMP}; -static const AReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_LEVELS, MODE_2_LPF_AMP}; - -static const AReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_0_SETTINGS}; - -RingBuffer::RingBuffer(const Bit32u newsize) : size(newsize), index(0) { - buffer = new float[size]; -} - -RingBuffer::~RingBuffer() { - delete[] buffer; - buffer = NULL; -} - -float RingBuffer::next() { - if (++index >= size) { - index = 0; - } - return buffer[index]; -} - -bool RingBuffer::isEmpty() const { - if (buffer == NULL) return true; - - float *buf = buffer; - float max = 0.001f; - for (Bit32u i = 0; i < size; i++) { - if ((*buf < -max) || (*buf > max)) return false; - buf++; - } - return true; -} - -void RingBuffer::mute() { - float *buf = buffer; - for (Bit32u i = 0; i < size; i++) { - *buf++ = 0; - } -} - -AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {} - -float AllpassFilter::process(const float in) { - // This model corresponds to the allpass filter implementation of the real CM-32L device - // found from sample analysis - - const float bufferOut = next(); - - // store input - feedback / 2 - buffer[index] = in - 0.5f * bufferOut; - - // return buffer output + feedforward / 2 - return bufferOut + 0.5f * buffer[index]; -} - -CombFilter::CombFilter(const Bit32u useSize) : RingBuffer(useSize) {} - -void CombFilter::process(const float in) { - // This model corresponds to the comb filter implementation of the real CM-32L device - // found from sample analysis - - // the previously stored value - float last = buffer[index]; - - // prepare input + feedback - float filterIn = in + next() * feedbackFactor; - - // store input + feedback processed by a low-pass filter - buffer[index] = filterFactor * last - filterIn; -} - -float CombFilter::getOutputAt(const Bit32u outIndex) const { - return buffer[(size + index - outIndex) % size]; -} - -void CombFilter::setFeedbackFactor(const float useFeedbackFactor) { - feedbackFactor = useFeedbackFactor; -} - -void CombFilter::setFilterFactor(const float useFilterFactor) { - filterFactor = useFilterFactor; -} - -AReverbModel::AReverbModel(const ReverbMode mode) : allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]) {} - -AReverbModel::~AReverbModel() { - close(); -} - -void AReverbModel::open() { - allpasses = new AllpassFilter*[NUM_ALLPASSES]; - for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]); - } - combs = new CombFilter*[NUM_COMBS]; - for (Bit32u i = 0; i < NUM_COMBS; i++) { - combs[i] = new CombFilter(currentSettings.combSizes[i]); - combs[i]->setFilterFactor(currentSettings.filterFactor[i] / 256.0f); - } - lpfAmp = currentSettings.lpfAmp / 16.0f; - mute(); -} - -void AReverbModel::close() { - if (allpasses != NULL) { - for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - if (allpasses[i] != NULL) { - delete allpasses[i]; - allpasses[i] = NULL; - } - } - delete[] allpasses; - allpasses = NULL; - } - if (combs != NULL) { - for (Bit32u i = 0; i < NUM_COMBS; i++) { - if (combs[i] != NULL) { - delete combs[i]; - combs[i] = NULL; - } - } - delete[] combs; - combs = NULL; - } -} - -void AReverbModel::mute() { - if (allpasses == NULL || combs == NULL) return; - for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - allpasses[i]->mute(); - } - for (Bit32u i = 0; i < NUM_COMBS; i++) { - combs[i]->mute(); - } -} - -void AReverbModel::setParameters(Bit8u time, Bit8u level) { -// FIXME: wetLevel definitely needs ramping when changed -// Although, most games don't set reverb level during MIDI playback - if (combs == NULL) return; - level &= 7; - time &= 7; - for (Bit32u i = 0; i < NUM_COMBS; i++) { - combs[i]->setFeedbackFactor(currentSettings.decayTimes[(i << 3) + time] / 256.0f); - } - wetLevel = (level == 0 && time == 0) ? 0.0f : 0.5f * lpfAmp * currentSettings.wetLevels[level] / 256.0f; -} - -bool AReverbModel::isActive() const { - for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - if (!allpasses[i]->isEmpty()) return true; - } - for (Bit32u i = 0; i < NUM_COMBS; i++) { - if (!combs[i]->isEmpty()) return true; - } - return false; -} - -void AReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { - float dry, link, outL1; - - for (unsigned long i = 0; i < numSamples; i++) { - dry = wetLevel * (*inLeft + *inRight); - - // Get the last stored sample before processing in order not to loose it - link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1); - - combs[0]->process(-dry); - - link = allpasses[0]->process(link); - link = allpasses[1]->process(link); - link = allpasses[2]->process(link); - - // If the output position is equal to the comb size, get it now in order not to loose it - outL1 = 1.5f * combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1); - - combs[1]->process(link); - combs[2]->process(link); - combs[3]->process(link); - - link = outL1 + 1.5f * combs[2]->getOutputAt(currentSettings.outLPositions[1]); - link += combs[3]->getOutputAt(currentSettings.outLPositions[2]); - *outLeft = link; - - link = 1.5f * combs[1]->getOutputAt(currentSettings.outRPositions[0]); - link += 1.5f * combs[2]->getOutputAt(currentSettings.outRPositions[1]); - link += combs[3]->getOutputAt(currentSettings.outRPositions[2]); - *outRight = link; - - inLeft++; - inRight++; - outLeft++; - outRight++; - } -} - -} - -#endif diff --git a/audio/softsynth/mt32/AReverbModel.h b/audio/softsynth/mt32/AReverbModel.h deleted file mode 100644 index c992478907b..00000000000 --- a/audio/softsynth/mt32/AReverbModel.h +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -#ifndef MT32EMU_A_REVERB_MODEL_H -#define MT32EMU_A_REVERB_MODEL_H - -namespace MT32Emu { - -struct AReverbSettings { - const Bit32u * const allpassSizes; - const Bit32u * const combSizes; - const Bit32u * const outLPositions; - const Bit32u * const outRPositions; - const Bit32u * const filterFactor; - const Bit32u * const decayTimes; - const Bit32u * const wetLevels; - const Bit32u lpfAmp; -}; - -class RingBuffer { -protected: - float *buffer; - const Bit32u size; - Bit32u index; - -public: - RingBuffer(const Bit32u size); - virtual ~RingBuffer(); - float next(); - bool isEmpty() const; - void mute(); -}; - -class AllpassFilter : public RingBuffer { -public: - AllpassFilter(const Bit32u size); - float process(const float in); -}; - -class CombFilter : public RingBuffer { - float feedbackFactor; - float filterFactor; - -public: - CombFilter(const Bit32u size); - void process(const float in); - float getOutputAt(const Bit32u outIndex) const; - void setFeedbackFactor(const float useFeedbackFactor); - void setFilterFactor(const float useFilterFactor); -}; - -class AReverbModel : public ReverbModel { - AllpassFilter **allpasses; - CombFilter **combs; - - const AReverbSettings ¤tSettings; - float lpfAmp; - float wetLevel; - void mute(); - -public: - AReverbModel(const ReverbMode mode); - ~AReverbModel(); - void open(); - void close(); - void setParameters(Bit8u time, Bit8u level); - void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); - bool isActive() const; -}; - -} - -#endif diff --git a/audio/softsynth/mt32/BReverbModel.cpp b/audio/softsynth/mt32/BReverbModel.cpp index cc0219b741a..c16f7f17da4 100644 --- a/audio/softsynth/mt32/BReverbModel.cpp +++ b/audio/softsynth/mt32/BReverbModel.cpp @@ -15,10 +15,8 @@ * along with this program. If not, see . */ +//#include #include "mt32emu.h" - -#if MT32EMU_USE_REVERBMODEL == 2 - #include "BReverbModel.h" // Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that @@ -62,9 +60,9 @@ static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518}; static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274}; static const Bit32u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60}; static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98, - 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, - 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; + 0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; static const Bit32u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0}; static const Bit32u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0}; static const Bit32u MODE_1_LPF_AMP = 0x60; @@ -103,7 +101,11 @@ static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTING // This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines). // Also LA32 is suspected to use the similar one to perform PCM interpolation and ring modulation. -static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) { +static Sample weirdMul(Sample a, Bit8u addMask, Bit8u carryMask) { + (void)carryMask; +#if MT32EMU_USE_FLOAT_SAMPLES + return a * addMask / 256.0f; +#elif MT32EMU_BOSS_REVERB_PRECISE_MODE Bit8u mask = 0x80; Bit32s res = 0; for (int i = 0; i < 8; i++) { @@ -113,10 +115,13 @@ static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) { mask >>= 1; } return res; +#else + return Sample(((Bit32s)a * addMask) >> 8); +#endif } RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) { - buffer = new Bit16s[size]; + buffer = new Sample[size]; } RingBuffer::~RingBuffer() { @@ -124,7 +129,7 @@ RingBuffer::~RingBuffer() { buffer = NULL; } -Bit32s RingBuffer::next() { +Sample RingBuffer::next() { if (++index >= size) { index = 0; } @@ -134,52 +139,69 @@ Bit32s RingBuffer::next() { bool RingBuffer::isEmpty() const { if (buffer == NULL) return true; - Bit16s *buf = buffer; +#if MT32EMU_USE_FLOAT_SAMPLES + Sample max = 0.001f; +#else + Sample max = 8; +#endif + Sample *buf = buffer; for (Bit32u i = 0; i < size; i++) { - if (*buf < -8 || *buf > 8) return false; + if (*buf < -max || *buf > max) return false; buf++; } return true; } void RingBuffer::mute() { - Bit16s *buf = buffer; +#if MT32EMU_USE_FLOAT_SAMPLES + Sample *buf = buffer; for (Bit32u i = 0; i < size; i++) { *buf++ = 0; } +#else + memset(buffer, 0, size * sizeof(Sample)); +#endif } AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {} -Bit32s AllpassFilter::process(const Bit32s in) { +Sample AllpassFilter::process(const Sample in) { // This model corresponds to the allpass filter implementation of the real CM-32L device // found from sample analysis - Bit16s bufferOut = next(); + const Sample bufferOut = next(); +#if MT32EMU_USE_FLOAT_SAMPLES + // store input - feedback / 2 + buffer[index] = in - 0.5f * bufferOut; + + // return buffer output + feedforward / 2 + return bufferOut + 0.5f * buffer[index]; +#else // store input - feedback / 2 buffer[index] = in - (bufferOut >> 1); // return buffer output + feedforward / 2 return bufferOut + (buffer[index] >> 1); +#endif } CombFilter::CombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {} -void CombFilter::process(const Bit32s in) { +void CombFilter::process(const Sample in) { // This model corresponds to the comb filter implementation of the real CM-32L device // the previously stored value - Bit32s last = buffer[index]; + const Sample last = buffer[index]; // prepare input + feedback - Bit32s filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */); + const Sample filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */); // store input + feedback processed by a low-pass filter buffer[index] = weirdMul(last, filterFactor, 0x40) - filterIn; } -Bit32s CombFilter::getOutputAt(const Bit32u outIndex) const { +Sample CombFilter::getOutputAt(const Bit32u outIndex) const { return buffer[(size + index - outIndex) % size]; } @@ -190,15 +212,15 @@ void CombFilter::setFeedbackFactor(const Bit32u useFeedbackFactor) { DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp) : CombFilter(useSize, useFilterFactor), amp(useAmp) {} -void DelayWithLowPassFilter::process(const Bit32s in) { +void DelayWithLowPassFilter::process(const Sample in) { // the previously stored value - Bit32s last = buffer[index]; + const Sample last = buffer[index]; // move to the next index next(); // low-pass filter process - Bit32s lpfOut = weirdMul(last, filterFactor, 0xFF) + in; + Sample lpfOut = weirdMul(last, filterFactor, 0xFF) + in; // store lpfOut multiplied by LPF amp factor buffer[index] = weirdMul(lpfOut, amp, 0xFF); @@ -206,26 +228,26 @@ void DelayWithLowPassFilter::process(const Bit32s in) { TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : CombFilter(useSize, useFilterFactor) {} -void TapDelayCombFilter::process(const Bit32s in) { +void TapDelayCombFilter::process(const Sample in) { // the previously stored value - Bit32s last = buffer[index]; + const Sample last = buffer[index]; // move to the next index next(); // prepare input + feedback // Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output - Bit32s filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0); + const Sample filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0); // store input + feedback processed by a low-pass filter buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn; } -Bit32s TapDelayCombFilter::getLeftOutput() const { +Sample TapDelayCombFilter::getLeftOutput() const { return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); } -Bit32s TapDelayCombFilter::getRightOutput() const { +Sample TapDelayCombFilter::getRightOutput() const { return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); } @@ -327,14 +349,14 @@ bool BReverbModel::isActive() const { return false; } -void BReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { - Bit32s dry, link, outL1, outR1; +void BReverbModel::process(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, unsigned long numSamples) { + Sample dry; - for (unsigned long i = 0; i < numSamples; i++) { + while (numSamples > 0) { if (tapDelayMode) { - dry = Bit32s(*inLeft * 8192.0f) + Bit32s(*inRight * 8192.0f); + dry = *inLeft + *inRight; } else { - dry = Bit32s(*inLeft * 8192.0f) / 2 + Bit32s(*inRight * 8192.0f) / 2; + dry = *inLeft / 2 + *inRight / 2; } // Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I @@ -343,44 +365,53 @@ void BReverbModel::process(const float *inLeft, const float *inRight, float *out if (tapDelayMode) { TapDelayCombFilter *comb = static_cast (*combs); comb->process(dry); - *outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF) / 8192.0f; - *outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF) / 8192.0f; + *outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF); + *outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF); } else { - // Get the last stored sample before processing in order not to loose it - link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1); + // If the output position is equal to the comb size, get it now in order not to loose it + Sample link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1); // Entrance LPF. Note, comb.process() differs a bit here. combs[0]->process(dry); +#if !MT32EMU_USE_FLOAT_SAMPLES // This introduces reverb noise which actually makes output from the real Boss chip nondeterministic link = link - 1; +#endif link = allpasses[0]->process(link); link = allpasses[1]->process(link); link = allpasses[2]->process(link); // If the output position is equal to the comb size, get it now in order not to loose it - outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1); - outL1 += outL1 >> 1; + Sample outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1); combs[1]->process(link); combs[2]->process(link); combs[3]->process(link); - link = combs[2]->getOutputAt(currentSettings.outLPositions[1]); - link += link >> 1; - link += outL1; - link += combs[3]->getOutputAt(currentSettings.outLPositions[2]); - *outLeft = weirdMul(link, wetLevel, 0xFF) / 8192.0f; + Sample outL2 = combs[2]->getOutputAt(currentSettings.outLPositions[1]); + Sample outL3 = combs[3]->getOutputAt(currentSettings.outLPositions[2]); + Sample outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]); + Sample outR2 = combs[2]->getOutputAt(currentSettings.outRPositions[1]); + Sample outR3 = combs[3]->getOutputAt(currentSettings.outRPositions[2]); + +#if MT32EMU_USE_FLOAT_SAMPLES + *outLeft = 1.5f * (outL1 + outL2) + outL3; + *outRight = 1.5f * (outR1 + outR2) + outR3; +#else + outL1 += outL1 >> 1; + outL2 += outL2 >> 1; + *outLeft = outL1 + outL2 + outL3; - outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]); outR1 += outR1 >> 1; - link = combs[2]->getOutputAt(currentSettings.outRPositions[1]); - link += link >> 1; - link += outR1; - link += combs[3]->getOutputAt(currentSettings.outRPositions[2]); - *outRight = weirdMul(link, wetLevel, 0xFF) / 8192.0f; + outR2 += outR2 >> 1; + *outRight = outR1 + outR2 + outR3; +#endif + *outLeft = weirdMul(*outLeft, wetLevel, 0xFF); + *outRight = weirdMul(*outRight, wetLevel, 0xFF); } + numSamples--; inLeft++; inRight++; outLeft++; @@ -389,5 +420,3 @@ void BReverbModel::process(const float *inLeft, const float *inRight, float *out } } - -#endif diff --git a/audio/softsynth/mt32/BReverbModel.h b/audio/softsynth/mt32/BReverbModel.h index d6fcb73c139..7cf431db0dd 100644 --- a/audio/softsynth/mt32/BReverbModel.h +++ b/audio/softsynth/mt32/BReverbModel.h @@ -36,14 +36,14 @@ struct BReverbSettings { class RingBuffer { protected: - Bit16s *buffer; + Sample *buffer; const Bit32u size; Bit32u index; public: RingBuffer(const Bit32u size); virtual ~RingBuffer(); - Bit32s next(); + Sample next(); bool isEmpty() const; void mute(); }; @@ -51,7 +51,7 @@ public: class AllpassFilter : public RingBuffer { public: AllpassFilter(const Bit32u size); - Bit32s process(const Bit32s in); + Sample process(const Sample in); }; class CombFilter : public RingBuffer { @@ -61,8 +61,8 @@ protected: public: CombFilter(const Bit32u size, const Bit32u useFilterFactor); - virtual void process(const Bit32s in); // Actually, no need to make it virtual, but for sure - Bit32s getOutputAt(const Bit32u outIndex) const; + virtual void process(const Sample in); + Sample getOutputAt(const Bit32u outIndex) const; void setFeedbackFactor(const Bit32u useFeedbackFactor); }; @@ -71,7 +71,7 @@ class DelayWithLowPassFilter : public CombFilter { public: DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp); - void process(const Bit32s in); + void process(const Sample in); void setFeedbackFactor(const Bit32u) {} }; @@ -81,13 +81,13 @@ class TapDelayCombFilter : public CombFilter { public: TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor); - void process(const Bit32s in); - Bit32s getLeftOutput() const; - Bit32s getRightOutput() const; + void process(const Sample in); + Sample getLeftOutput() const; + Sample getRightOutput() const; void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR); }; -class BReverbModel : public ReverbModel { +class BReverbModel { AllpassFilter **allpasses; CombFilter **combs; @@ -100,10 +100,12 @@ class BReverbModel : public ReverbModel { public: BReverbModel(const ReverbMode mode); ~BReverbModel(); + // After construction or a close(), open() must be called at least once before any other call (with the exception of close()). void open(); + // May be called multiple times without an open() in between. void close(); void setParameters(Bit8u time, Bit8u level); - void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); + void process(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, unsigned long numSamples); bool isActive() const; }; diff --git a/audio/softsynth/mt32/DelayReverb.cpp b/audio/softsynth/mt32/DelayReverb.cpp deleted file mode 100644 index d80c98acbc2..00000000000 --- a/audio/softsynth/mt32/DelayReverb.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -//#include -//#include -#include "mt32emu.h" -#include "DelayReverb.h" - -namespace MT32Emu { - -// CONFIRMED: The values below are found via analysis of digital samples and tracing reverb RAM address / data lines. Checked with all time and level combinations. -// Obviously: -// rightDelay = (leftDelay - 2) * 2 + 2 -// echoDelay = rightDelay - 1 -// Leaving these separate in case it's useful for work on other reverb modes... -static const Bit32u REVERB_TIMINGS[8][3]= { - // {leftDelay, rightDelay, feedbackDelay} - {402, 802, 801}, - {626, 1250, 1249}, - {962, 1922, 1921}, - {1490, 2978, 2977}, - {2258, 4514, 4513}, - {3474, 6946, 6945}, - {5282, 10562, 10561}, - {8002, 16002, 16001} -}; - -// Reverb amp is found as dryAmp * wetAmp -static const Bit32u REVERB_AMP[8] = {0x20*0x18, 0x50*0x18, 0x50*0x28, 0x50*0x40, 0x50*0x60, 0x50*0x80, 0x50*0xA8, 0x50*0xF8}; -static const Bit32u REVERB_FEEDBACK67 = 0x60; -static const Bit32u REVERB_FEEDBACK = 0x68; -static const float LPF_VALUE = 0x68 / 256.0f; - -static const Bit32u BUFFER_SIZE = 16384; - -DelayReverb::DelayReverb() { - buf = NULL; - setParameters(0, 0); -} - -DelayReverb::~DelayReverb() { - delete[] buf; -} - -void DelayReverb::open() { - if (buf == NULL) { - delete[] buf; - - buf = new float[BUFFER_SIZE]; - - recalcParameters(); - - // mute buffer - bufIx = 0; - if (buf != NULL) { - for (unsigned int i = 0; i < BUFFER_SIZE; i++) { - buf[i] = 0.0f; - } - } - } -} - -void DelayReverb::close() { - delete[] buf; - buf = NULL; -} - -// This method will always trigger a flush of the buffer -void DelayReverb::setParameters(Bit8u newTime, Bit8u newLevel) { - time = newTime; - level = newLevel; - recalcParameters(); -} - -void DelayReverb::recalcParameters() { - // Number of samples between impulse and eventual appearance on the left channel - delayLeft = REVERB_TIMINGS[time][0]; - // Number of samples between impulse and eventual appearance on the right channel - delayRight = REVERB_TIMINGS[time][1]; - // Number of samples between a response and that response feeding back/echoing - delayFeedback = REVERB_TIMINGS[time][2]; - - if (level < 3 || time < 6) { - feedback = REVERB_FEEDBACK / 256.0f; - } else { - feedback = REVERB_FEEDBACK67 / 256.0f; - } - - // Overall output amp - amp = (level == 0 && time == 0) ? 0.0f : REVERB_AMP[level] / 65536.0f; -} - -void DelayReverb::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { - if (buf == NULL) return; - - for (unsigned int sampleIx = 0; sampleIx < numSamples; sampleIx++) { - // The ring buffer write index moves backwards; reads are all done with positive offsets. - Bit32u bufIxPrev = (bufIx + 1) % BUFFER_SIZE; - Bit32u bufIxLeft = (bufIx + delayLeft) % BUFFER_SIZE; - Bit32u bufIxRight = (bufIx + delayRight) % BUFFER_SIZE; - Bit32u bufIxFeedback = (bufIx + delayFeedback) % BUFFER_SIZE; - - // Attenuated input samples and feedback response are directly added to the current ring buffer location - float lpfIn = amp * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback]; - - // Single-pole IIR filter found on real devices - buf[bufIx] = buf[bufIxPrev] * LPF_VALUE - lpfIn; - - outLeft[sampleIx] = buf[bufIxLeft]; - outRight[sampleIx] = buf[bufIxRight]; - - bufIx = (BUFFER_SIZE + bufIx - 1) % BUFFER_SIZE; - } -} - -bool DelayReverb::isActive() const { - if (buf == NULL) return false; - - float *b = buf; - float max = 0.001f; - for (Bit32u i = 0; i < BUFFER_SIZE; i++) { - if ((*b < -max) || (*b > max)) return true; - b++; - } - return false; -} - -} diff --git a/audio/softsynth/mt32/DelayReverb.h b/audio/softsynth/mt32/DelayReverb.h deleted file mode 100644 index c8003832b58..00000000000 --- a/audio/softsynth/mt32/DelayReverb.h +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -#ifndef MT32EMU_DELAYREVERB_H -#define MT32EMU_DELAYREVERB_H - -namespace MT32Emu { - -class DelayReverb : public ReverbModel { -private: - Bit8u time; - Bit8u level; - - Bit32u bufIx; - float *buf; - - Bit32u delayLeft; - Bit32u delayRight; - Bit32u delayFeedback; - - float amp; - float feedback; - - void recalcParameters(); - -public: - DelayReverb(); - ~DelayReverb(); - void open(); - void close(); - void setParameters(Bit8u time, Bit8u level); - void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); - bool isActive() const; -}; -} -#endif diff --git a/audio/softsynth/mt32/FreeverbModel.cpp b/audio/softsynth/mt32/FreeverbModel.cpp deleted file mode 100644 index bd9c70b6f45..00000000000 --- a/audio/softsynth/mt32/FreeverbModel.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -#include "mt32emu.h" -#include "FreeverbModel.h" - -#include "freeverb.h" - -namespace MT32Emu { - -FreeverbModel::FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp) { - freeverb = NULL; - scaleTuning = useScaleTuning; - filtVal = useFiltVal; - wet = useWet; - room = useRoom; - damp = useDamp; -} - -FreeverbModel::~FreeverbModel() { - delete freeverb; -} - -void FreeverbModel::open() { - if (freeverb == NULL) { - freeverb = new revmodel(scaleTuning); - } - freeverb->mute(); - - // entrance Lowpass filter factor - freeverb->setfiltval(filtVal); - - // decay speed of high frequencies in the wet signal - freeverb->setdamp(damp); -} - -void FreeverbModel::close() { - delete freeverb; - freeverb = NULL; -} - -void FreeverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { - freeverb->process(inLeft, inRight, outLeft, outRight, numSamples); -} - -void FreeverbModel::setParameters(Bit8u time, Bit8u level) { - // wet signal level - // FIXME: need to implement some sort of reverb level ramping - freeverb->setwet((float)level / 7.0f * wet); - - // wet signal decay speed - static float roomTable[] = { - 0.25f, 0.37f, 0.54f, 0.71f, 0.78f, 0.86f, 0.93f, 1.00f, - -1.00f, -0.50f, 0.00f, 0.30f, 0.51f, 0.64f, 0.77f, 0.90f, - 0.50f, 0.57f, 0.70f, 0.77f, 0.85f, 0.93f, 0.96f, 1.01f}; - freeverb->setroomsize(roomTable[8 * room + time]); -} - -bool FreeverbModel::isActive() const { - // FIXME: Not bothering to do this properly since we'll be replacing Freeverb soon... - return false; -} - -} diff --git a/audio/softsynth/mt32/FreeverbModel.h b/audio/softsynth/mt32/FreeverbModel.h deleted file mode 100644 index 5ea11f1f40e..00000000000 --- a/audio/softsynth/mt32/FreeverbModel.h +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -#ifndef MT32EMU_FREEVERB_MODEL_H -#define MT32EMU_FREEVERB_MODEL_H - -class revmodel; - -namespace MT32Emu { - -class FreeverbModel : public ReverbModel { - revmodel *freeverb; - float scaleTuning; - float filtVal; - float wet; - Bit8u room; - float damp; -public: - FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp); - ~FreeverbModel(); - void open(); - void close(); - void setParameters(Bit8u time, Bit8u level); - void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); - bool isActive() const; -}; - -} - -#endif diff --git a/audio/softsynth/mt32/LA32WaveGenerator.cpp b/audio/softsynth/mt32/LA32WaveGenerator.cpp index 80650699fb2..1d115c12efd 100644 --- a/audio/softsynth/mt32/LA32WaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32WaveGenerator.cpp @@ -20,7 +20,9 @@ #include "mmath.h" #include "LA32WaveGenerator.h" -#if MT32EMU_ACCURATE_WG == 0 +#if MT32EMU_USE_FLOAT_SAMPLES +#include "LA32FloatWaveGenerator.cpp" +#else namespace MT32Emu { @@ -129,7 +131,8 @@ void LA32WaveGenerator::advancePosition() { computePositions(highLinearLength, lowLinearLength, resonanceWaveLengthFactor); // resonancePhase computation hack - *(int*)&resonancePhase = ((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3; + int *resonancePhaseAlias = (int *)&resonancePhase; + *resonancePhaseAlias = ((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3; } void LA32WaveGenerator::generateNextSquareWaveLogSample() { @@ -367,18 +370,12 @@ void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u } } -Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample) { - if (!wg.isActive() || ((ringModulatingLogSample != NULL) && (ringModulatingLogSample->logValue == SILENCE.logValue))) { +Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg) { + if (!wg.isActive()) { return 0; } - LogSample firstLogSample = wg.getOutputLogSample(true); - LogSample secondLogSample = wg.getOutputLogSample(false); - if (ringModulatingLogSample != NULL) { - LA32Utilites::addLogSamples(firstLogSample, *ringModulatingLogSample); - LA32Utilites::addLogSamples(secondLogSample, *ringModulatingLogSample); - } - Bit16s firstSample = LA32Utilites::unlog(firstLogSample); - Bit16s secondSample = LA32Utilites::unlog(secondLogSample); + Bit16s firstSample = LA32Utilites::unlog(wg.getOutputLogSample(true)); + Bit16s secondSample = LA32Utilites::unlog(wg.getOutputLogSample(false)); if (wg.isPCMWave()) { return Bit16s(firstSample + ((Bit32s(secondSample - firstSample) * wg.getPCMInterpolationFactor()) >> 7)); } @@ -386,19 +383,32 @@ Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg, const L } Bit16s LA32PartialPair::nextOutSample() { - if (ringModulated) { - LogSample slaveFirstLogSample = slave.getOutputLogSample(true); - LogSample slaveSecondLogSample = slave.getOutputLogSample(false); - Bit16s sample = unlogAndMixWGOutput(master, &slaveFirstLogSample); - if (!slave.isPCMWave()) { - sample += unlogAndMixWGOutput(master, &slaveSecondLogSample); - } - if (mixed) { - sample += unlogAndMixWGOutput(master, NULL); - } - return sample; + if (!ringModulated) { + return unlogAndMixWGOutput(master) + unlogAndMixWGOutput(slave); } - return unlogAndMixWGOutput(master, NULL) + unlogAndMixWGOutput(slave, NULL); + + /* + * SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion. + * LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191. + * This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case. + * As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space, + * it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication. + * Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning. + */ + Bit16s nonOverdrivenMasterSample = unlogAndMixWGOutput(master); // Store master partial sample for further mixing + Bit16s masterSample = nonOverdrivenMasterSample << 2; + masterSample >>= 2; + + /* SEMI-CONFIRMED from sample analysis: + * We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial. + * It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial + * is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair). + */ + Bit16s slaveSample = slave.isPCMWave() ? LA32Utilites::unlog(slave.getOutputLogSample(true)) : unlogAndMixWGOutput(slave); + slaveSample <<= 2; + slaveSample >>= 2; + Bit16s ringModulatedSample = Bit16s(((Bit32s)masterSample * (Bit32s)slaveSample) >> 13); + return mixed ? nonOverdrivenMasterSample + ringModulatedSample : ringModulatedSample; } void LA32PartialPair::deactivate(const PairType useMaster) { @@ -415,4 +425,4 @@ bool LA32PartialPair::isActive(const PairType useMaster) const { } -#endif // #if MT32EMU_ACCURATE_WG == 0 +#endif // #if MT32EMU_USE_FLOAT_SAMPLES diff --git a/audio/softsynth/mt32/LA32WaveGenerator.h b/audio/softsynth/mt32/LA32WaveGenerator.h index 37a4aead851..b5f4dedff43 100644 --- a/audio/softsynth/mt32/LA32WaveGenerator.h +++ b/audio/softsynth/mt32/LA32WaveGenerator.h @@ -15,7 +15,9 @@ * along with this program. If not, see . */ -#if MT32EMU_ACCURATE_WG == 0 +#if MT32EMU_USE_FLOAT_SAMPLES +#include "LA32FloatWaveGenerator.h" +#else #ifndef MT32EMU_LA32_WAVE_GENERATOR_H #define MT32EMU_LA32_WAVE_GENERATOR_H @@ -207,7 +209,7 @@ class LA32PartialPair { bool ringModulated; bool mixed; - static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample); + static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg); public: enum PairType { @@ -243,4 +245,4 @@ public: #endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H -#endif // #if MT32EMU_ACCURATE_WG == 0 +#endif // #if MT32EMU_USE_FLOAT_SAMPLES diff --git a/audio/softsynth/mt32/LegacyWaveGenerator.cpp b/audio/softsynth/mt32/LegacyWaveGenerator.cpp deleted file mode 100644 index 35ca9750189..00000000000 --- a/audio/softsynth/mt32/LegacyWaveGenerator.cpp +++ /dev/null @@ -1,347 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -//#include -#include "mt32emu.h" -#include "mmath.h" -#include "LegacyWaveGenerator.h" - -#if MT32EMU_ACCURATE_WG == 1 - -namespace MT32Emu { - -static const float MIDDLE_CUTOFF_VALUE = 128.0f; -static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f; -static const float MAX_CUTOFF_VALUE = 240.0f; - -float LA32WaveGenerator::getPCMSample(unsigned int position) { - if (position >= pcmWaveLength) { - if (!pcmWaveLooped) { - return 0; - } - position = position % pcmWaveLength; - } - Bit16s pcmSample = pcmWaveAddress[position]; - float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f); - return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue; -} - -void LA32WaveGenerator::initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { - this->sawtoothWaveform = sawtoothWaveform; - this->pulseWidth = pulseWidth; - this->resonance = resonance; - - wavePos = 0.0f; - lastFreq = 0.0f; - - pcmWaveAddress = NULL; - active = true; -} - -void LA32WaveGenerator::initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated) { - this->pcmWaveAddress = pcmWaveAddress; - this->pcmWaveLength = pcmWaveLength; - this->pcmWaveLooped = pcmWaveLooped; - this->pcmWaveInterpolated = pcmWaveInterpolated; - - pcmPosition = 0.0f; - active = true; -} - -float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) { - if (!active) { - return 0.0f; - } - - this->amp = amp; - this->pitch = pitch; - - float sample = 0.0f; - - // SEMI-CONFIRMED: From sample analysis: - // (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc. - // This gives results within +/- 2 at the output (before any DAC bitshifting) - // when sustaining at levels 156 - 255 with no modifiers. - // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255. - // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces - // positive amps, so negative still needs to be explored, as well as lower levels. - // - // Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing. - - float amp = EXP2F(ampVal / -1024.0f / 4096.0f); - float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE; - - if (isPCMWave()) { - // Render PCM waveform - int len = pcmWaveLength; - int intPCMPosition = (int)pcmPosition; - if (intPCMPosition >= len && !pcmWaveLooped) { - // We're now past the end of a non-looping PCM waveform so it's time to die. - deactivate(); - return 0.0f; - } - float positionDelta = freq * 2048.0f / SAMPLE_RATE; - - // Linear interpolation - float firstSample = getPCMSample(intPCMPosition); - // We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial. - // It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial - // is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair). - if (pcmWaveInterpolated) { - sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition); - } else { - sample = firstSample; - } - - float newPCMPosition = pcmPosition + positionDelta; - if (pcmWaveLooped) { - newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength); - } - pcmPosition = newPCMPosition; - } else { - // Render synthesised waveform - wavePos *= lastFreq / freq; - lastFreq = freq; - - float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f); - { - //static const float resAmpFactor = EXP2F(-7); - //resAmp = EXP2I(resonance << 10) * resAmpFactor; - } - - // The cutoffModifier may not be supposed to be directly added to the cutoff - - // it may for example need to be multiplied in some way. - // The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4). - // More research is needed to be sure that this is correct, however. - float cutoffVal = cutoffRampVal / 262144.0f; - if (cutoffVal > MAX_CUTOFF_VALUE) { - cutoffVal = MAX_CUTOFF_VALUE; - } - - // Wave length in samples - float waveLen = SAMPLE_RATE / freq; - - // Init cosineLen - float cosineLen = 0.5f * waveLen; - if (cutoffVal > MIDDLE_CUTOFF_VALUE) { - cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis - } - - // Start playing in center of first cosine segment - // relWavePos is shifted by a half of cosineLen - float relWavePos = wavePos + 0.5f * cosineLen; - if (relWavePos > waveLen) { - relWavePos -= waveLen; - } - - // Ratio of positive segment to wave length - float pulseLen = 0.5f; - if (pulseWidth > 128) { - pulseLen = EXP2F((64 - pulseWidth) / 64.0f); - //static const float pulseLenFactor = EXP2F(-192 / 64); - //pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor; - } - pulseLen *= waveLen; - - float hLen = pulseLen - cosineLen; - - // Ignore pulsewidths too high for given freq - if (hLen < 0.0f) { - hLen = 0.0f; - } - - // Ignore pulsewidths too high for given freq and cutoff - float lLen = waveLen - hLen - 2 * cosineLen; - if (lLen < 0.0f) { - lLen = 0.0f; - } - - // Correct resAmp for cutoff in range 50..66 - if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) { - resAmp *= sin(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f); - } - - // Produce filtered square wave with 2 cosine waves on slopes - - // 1st cosine segment - if (relWavePos < cosineLen) { - sample = -cos(FLOAT_PI * relWavePos / cosineLen); - } else - - // high linear segment - if (relWavePos < (cosineLen + hLen)) { - sample = 1.f; - } else - - // 2nd cosine segment - if (relWavePos < (2 * cosineLen + hLen)) { - sample = cos(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen); - } else { - - // low linear segment - sample = -1.f; - } - - if (cutoffVal < 128.0f) { - - // Attenuate samples below cutoff 50 - // Found by sample analysis - sample *= EXP2F(-0.125f * (128.0f - cutoffVal)); - } else { - - // Add resonance sine. Effective for cutoff > 50 only - float resSample = 1.0f; - - // Resonance decay speed factor - float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2]; - - // Now relWavePos counts from the middle of first cosine - relWavePos = wavePos; - - // negative segments - if (!(relWavePos < (cosineLen + hLen))) { - resSample = -resSample; - relWavePos -= cosineLen + hLen; - - // From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments - resAmpDecayFactor += 0.25f; - } - - // Resonance sine WG - resSample *= sin(FLOAT_PI * relWavePos / cosineLen); - - // Resonance sine amp - float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact - float resAmpFade = EXP2F(resAmpFadeLog2); - - // Now relWavePos set negative to the left from center of any cosine - relWavePos = wavePos; - - // negative segment - if (!(wavePos < (waveLen - 0.5f * cosineLen))) { - relWavePos -= waveLen; - } else - - // positive segment - if (!(wavePos < (hLen + 0.5f * cosineLen))) { - relWavePos -= cosineLen + hLen; - } - - // To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment - if (relWavePos < 0.5f * cosineLen) { - float syncSine = sin(FLOAT_PI * relWavePos / cosineLen); - if (relWavePos < 0.0f) { - // The window is synchronous square sine here - resAmpFade *= syncSine * syncSine; - } else { - // The window is synchronous sine here - resAmpFade *= syncSine; - } - } - - sample += resSample * resAmp * resAmpFade; - } - - // sawtooth waves - if (sawtoothWaveform) { - sample *= cos(FLOAT_2PI * wavePos / waveLen); - } - - wavePos++; - - // wavePos isn't supposed to be > waveLen - if (wavePos > waveLen) { - wavePos -= waveLen; - } - } - - // Multiply sample with current TVA value - sample *= amp; - return sample; -} - -void LA32WaveGenerator::deactivate() { - active = false; -} - -bool LA32WaveGenerator::isActive() const { - return active; -} - -bool LA32WaveGenerator::isPCMWave() const { - return pcmWaveAddress != NULL; -} - -void LA32PartialPair::init(const bool ringModulated, const bool mixed) { - this->ringModulated = ringModulated; - this->mixed = mixed; - masterOutputSample = 0.0f; - slaveOutputSample = 0.0f; -} - -void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { - if (useMaster == MASTER) { - master.initSynth(sawtoothWaveform, pulseWidth, resonance); - } else { - slave.initSynth(sawtoothWaveform, pulseWidth, resonance); - } -} - -void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) { - if (useMaster == MASTER) { - master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true); - } else { - slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated); - } -} - -void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) { - if (useMaster == MASTER) { - masterOutputSample = master.generateNextSample(amp, pitch, cutoff); - } else { - slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff); - } -} - -Bit16s LA32PartialPair::nextOutSample() { - float outputSample; - if (ringModulated) { - float ringModulatedSample = masterOutputSample * slaveOutputSample; - outputSample = mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample; - } else { - outputSample = masterOutputSample + slaveOutputSample; - } - return Bit16s(outputSample * 8192.0f); -} - -void LA32PartialPair::deactivate(const PairType useMaster) { - if (useMaster == MASTER) { - master.deactivate(); - masterOutputSample = 0.0f; - } else { - slave.deactivate(); - slaveOutputSample = 0.0f; - } -} - -bool LA32PartialPair::isActive(const PairType useMaster) const { - return useMaster == MASTER ? master.isActive() : slave.isActive(); -} - -} - -#endif // #if MT32EMU_ACCURATE_WG == 1 diff --git a/audio/softsynth/mt32/LegacyWaveGenerator.h b/audio/softsynth/mt32/LegacyWaveGenerator.h deleted file mode 100644 index 81c1b9c7137..00000000000 --- a/audio/softsynth/mt32/LegacyWaveGenerator.h +++ /dev/null @@ -1,146 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -#if MT32EMU_ACCURATE_WG == 1 - -#ifndef MT32EMU_LA32_WAVE_GENERATOR_H -#define MT32EMU_LA32_WAVE_GENERATOR_H - -namespace MT32Emu { - -/** - * LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator. - * The output square wave is created by adding high / low linear segments in-between - * the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis. - * Behaviour of a true resonance filter is emulated by adding decaying sine wave. - * The beginning and the ending of the resonant sine is multiplied by a cosine window. - * To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave. - */ -class LA32WaveGenerator { - //*************************************************************************** - // The local copy of partial parameters below - //*************************************************************************** - - bool active; - - // True means the resulting square wave is to be multiplied by the synchronous cosine - bool sawtoothWaveform; - - // Logarithmic amp of the wave generator - Bit32u amp; - - // Logarithmic frequency of the resulting wave - Bit16u pitch; - - // Values in range [1..31] - // Value 1 correspong to the minimum resonance - Bit8u resonance; - - // Processed value in range [0..255] - // Values in range [0..128] have no effect and the resulting wave remains symmetrical - // Value 255 corresponds to the maximum possible asymmetric of the resulting wave - Bit8u pulseWidth; - - // Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier - Bit32u cutoffVal; - - // Logarithmic PCM sample start address - const Bit16s *pcmWaveAddress; - - // Logarithmic PCM sample length - Bit32u pcmWaveLength; - - // true for looped logarithmic PCM samples - bool pcmWaveLooped; - - // false for slave PCM partials in the structures with the ring modulation - bool pcmWaveInterpolated; - - //*************************************************************************** - // Internal variables below - //*************************************************************************** - - float wavePos; - float lastFreq; - float pcmPosition; - - float getPCMSample(unsigned int position); - -public: - // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters - void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance); - - // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters - void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated); - - // Update parameters with respect to TVP, TVA and TVF, and generate next sample - float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); - - // Deactivate the WG engine - void deactivate(); - - // Return active state of the WG engine - bool isActive() const; - - // Return true if the WG engine generates PCM wave samples - bool isPCMWave() const; -}; - -// LA32PartialPair contains a structure of two partials being mixed / ring modulated -class LA32PartialPair { - LA32WaveGenerator master; - LA32WaveGenerator slave; - bool ringModulated; - bool mixed; - float masterOutputSample; - float slaveOutputSample; - -public: - enum PairType { - MASTER, - SLAVE - }; - - // ringModulated should be set to false for the structures with mixing or stereo output - // ringModulated should be set to true for the structures with ring modulation - // mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output - void init(const bool ringModulated, const bool mixed); - - // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters - void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance); - - // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters - void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped); - - // Update parameters with respect to TVP, TVA and TVF, and generate next sample - void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); - - // Perform mixing / ring modulation and return the result - Bit16s nextOutSample(); - - // Deactivate the WG engine - void deactivate(const PairType master); - - // Return active state of the WG engine - bool isActive(const PairType master) const; -}; - -} // namespace MT32Emu - -#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H - -#endif // #if MT32EMU_ACCURATE_WG == 1 diff --git a/audio/softsynth/mt32/Part.cpp b/audio/softsynth/mt32/Part.cpp index 88404316eb9..8a0daf3b38b 100644 --- a/audio/softsynth/mt32/Part.cpp +++ b/audio/softsynth/mt32/Part.cpp @@ -67,18 +67,12 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) { pitchBend = 0; activePartialCount = 0; memset(patchCache, 0, sizeof(patchCache)); - for (int i = 0; i < MT32EMU_MAX_POLY; i++) { - freePolys.prepend(new Poly(this)); - } } Part::~Part() { while (!activePolys.isEmpty()) { delete activePolys.takeFirst(); } - while (!freePolys.isEmpty()) { - delete freePolys.takeFirst(); - } } void Part::setDataEntryMSB(unsigned char midiDataEntryMSB) { @@ -431,23 +425,10 @@ void Part::noteOn(unsigned int midiKey, unsigned int velocity) { playPoly(patchCache, NULL, midiKey, key, velocity); } -void Part::abortPoly(Poly *poly) { - if (poly->startAbort()) { - while (poly->isActive()) { - if (!synth->prerender()) { - synth->printDebug("%s (%s): Ran out of prerender space to abort poly gracefully", name, currentInstr); - poly->terminate(); - break; - } - } - } -} - bool Part::abortFirstPoly(unsigned int key) { for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { if (poly->getKey() == key) { - abortPoly(poly); - return true; + return poly->startAbort(); } } return false; @@ -456,8 +437,7 @@ bool Part::abortFirstPoly(unsigned int key) { bool Part::abortFirstPoly(PolyState polyState) { for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { if (poly->getState() == polyState) { - abortPoly(poly); - return true; + return poly->startAbort(); } } return false; @@ -474,8 +454,7 @@ bool Part::abortFirstPoly() { if (activePolys.isEmpty()) { return false; } - abortPoly(activePolys.getFirst()); - return true; + return activePolys.getFirst()->startAbort(); } void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity) { @@ -489,6 +468,7 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt if ((patchTemp->patch.assignMode & 2) == 0) { // Single-assign mode abortFirstPoly(key); + if (synth->isAbortingPoly()) return; } if (!synth->partialManager->freePartials(needPartials, partNum)) { @@ -498,12 +478,13 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt #endif return; } + if (synth->isAbortingPoly()) return; - if (freePolys.isEmpty()) { + Poly *poly = synth->partialManager->assignPolyToPart(this); + if (poly == NULL) { synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity); return; } - Poly *poly = freePolys.takeFirst(); if (patchTemp->patch.assignMode & 1) { // Priority to data first received activePolys.prepend(poly); @@ -596,6 +577,10 @@ unsigned int Part::getActivePartialCount() const { return activePartialCount; } +const Poly *Part::getFirstActivePoly() const { + return activePolys.getFirst(); +} + unsigned int Part::getActiveNonReleasingPartialCount() const { unsigned int activeNonReleasingPartialCount = 0; for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { @@ -606,11 +591,15 @@ unsigned int Part::getActiveNonReleasingPartialCount() const { return activeNonReleasingPartialCount; } +Synth *Part::getSynth() const { + return synth; +} + void Part::partialDeactivated(Poly *poly) { activePartialCount--; if (!poly->isActive()) { activePolys.remove(poly); - freePolys.prepend(poly); + synth->partialManager->polyFreed(poly); synth->polyStateChanged(partNum); } } diff --git a/audio/softsynth/mt32/Part.h b/audio/softsynth/mt32/Part.h index b6585880fed..2aeeaf5bd70 100644 --- a/audio/softsynth/mt32/Part.h +++ b/audio/softsynth/mt32/Part.h @@ -51,13 +51,11 @@ private: unsigned int activePartialCount; PatchCache patchCache[4]; - PolyList freePolys; PolyList activePolys; void setPatch(const PatchParam *patch); unsigned int midiKeyToKey(unsigned int midiKey); - void abortPoly(Poly *poly); bool abortFirstPoly(unsigned int key); protected: @@ -110,8 +108,10 @@ public: virtual void setTimbre(TimbreParam *timbre); virtual unsigned int getAbsTimbreNum() const; const char *getCurrentInstr() const; + const Poly *getFirstActivePoly() const; unsigned int getActivePartialCount() const; unsigned int getActiveNonReleasingPartialCount() const; + Synth *getSynth() const; const MemParams::PatchTemp *getPatchTemp() const; diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp index b80a0285154..75e674074fa 100644 --- a/audio/softsynth/mt32/Partial.cpp +++ b/audio/softsynth/mt32/Partial.cpp @@ -24,15 +24,10 @@ namespace MT32Emu { -#ifdef INACCURATE_SMOOTH_PAN -// Mok wanted an option for smoother panning, and we love Mok. -static const float PAN_NUMERATOR_NORMAL[] = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 5.5f, 6.0f, 6.5f, 7.0f}; -#else -// CONFIRMED by Mok: These NUMERATOR values (as bytes, not floats, obviously) are sent exactly like this to the LA32. -static const float PAN_NUMERATOR_NORMAL[] = {0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f, 7.0f}; -#endif -static const float PAN_NUMERATOR_MASTER[] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; -static const float PAN_NUMERATOR_SLAVE[] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f}; +static const Bit8u PAN_NUMERATOR_MASTER[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7}; +static const Bit8u PAN_NUMERATOR_SLAVE[] = {0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7}; + +static const Bit32s PAN_FACTORS[] = {0, 18, 37, 55, 73, 91, 110, 128, 146, 165, 183, 201, 219, 238, 256}; Partial::Partial(Synth *useSynth, int useDebugPartialNum) : synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) { @@ -116,23 +111,30 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us structurePosition = patchCache->structurePosition; Bit8u panSetting = rhythmTemp != NULL ? rhythmTemp->panpot : part->getPatchTemp()->panpot; - float panVal; if (mixType == 3) { if (structurePosition == 0) { - panVal = PAN_NUMERATOR_MASTER[panSetting]; + panSetting = PAN_NUMERATOR_MASTER[panSetting] << 1; } else { - panVal = PAN_NUMERATOR_SLAVE[panSetting]; + panSetting = PAN_NUMERATOR_SLAVE[panSetting] << 1; } // Do a normal mix independent of any pair partial. mixType = 0; pairPartial = NULL; } else { - panVal = PAN_NUMERATOR_NORMAL[panSetting]; + // Mok wanted an option for smoother panning, and we love Mok. +#ifndef INACCURATE_SMOOTH_PAN + // CONFIRMED by Mok: exactly bytes like this (right shifted?) are sent to the LA32. + panSetting &= 0x0E; +#endif } - // FIXME: Sample analysis suggests that the use of panVal is linear, but there are some some quirks that still need to be resolved. - stereoVolume.leftVol = panVal / 7.0f; - stereoVolume.rightVol = 1.0f - stereoVolume.leftVol; + leftPanValue = synth->reversedStereoEnabled ? 14 - panSetting : panSetting; + rightPanValue = 14 - leftPanValue; + +#if !MT32EMU_USE_FLOAT_SAMPLES + leftPanValue = PAN_FACTORS[leftPanValue]; + rightPanValue = PAN_FACTORS[rightPanValue]; +#endif // SEMI-CONFIRMED: From sample analysis: // Found that timbres with 3 or 4 partials (i.e. one using two partial pairs) are mixed in two different ways. @@ -149,8 +151,8 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us // For my personal taste, this behaviour rather enriches the sounding and should be emulated. // Also, the current partial allocator model probably needs to be refined. if (debugPartialNum & 8) { - stereoVolume.leftVol = -stereoVolume.leftVol; - stereoVolume.rightVol = -stereoVolume.rightVol; + leftPanValue = -leftPanValue; + rightPanValue = -rightPanValue; } if (patchCache->PCMPartial) { @@ -198,9 +200,6 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us if (!hasRingModulatingSlave()) { la32Pair.deactivate(LA32PartialPair::SLAVE); } - // Temporary integration hack - stereoVolume.leftVol /= 8192.0f; - stereoVolume.rightVol /= 8192.0f; } Bit32u Partial::getAmpValue() { @@ -232,39 +231,6 @@ Bit32u Partial::getCutoffValue() { return (tvf->getBaseCutoff() << 18) + cutoffModifierRampVal; } -unsigned long Partial::generateSamples(Bit16s *partialBuf, unsigned long length) { - if (!isActive() || alreadyOutputed) { - return 0; - } - if (poly == NULL) { - synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::generateSamples()!", debugPartialNum); - return 0; - } - alreadyOutputed = true; - - for (sampleNum = 0; sampleNum < length; sampleNum++) { - if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) { - deactivate(); - break; - } - la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue()); - if (hasRingModulatingSlave()) { - la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue()); - if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) { - pair->deactivate(); - if (mixType == 2) { - deactivate(); - break; - } - } - } - *partialBuf++ = la32Pair.nextOutSample(); - } - unsigned long renderedSamples = sampleNum; - sampleNum = 0; - return renderedSamples; -} - bool Partial::hasRingModulatingSlave() const { return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2); } @@ -288,7 +254,18 @@ Synth *Partial::getSynth() const { return synth; } -bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long length) { +TVA *Partial::getTVA() const { + return tva; +} + +void Partial::backupCache(const PatchCache &cache) { + if (patchCache == &cache) { + cachebackup = cache; + patchCache = &cachebackup; + } +} + +bool Partial::produceOutput(Sample *leftBuf, Sample *rightBuf, unsigned long length) { if (!isActive() || alreadyOutputed || isRingModulatingSlave()) { return false; } @@ -296,15 +273,52 @@ bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long lengt synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum); return false; } - unsigned long numGenerated = generateSamples(myBuffer, length); - for (unsigned int i = 0; i < numGenerated; i++) { - *leftBuf++ = myBuffer[i] * stereoVolume.leftVol; - *rightBuf++ = myBuffer[i] * stereoVolume.rightVol; - } - for (; numGenerated < length; numGenerated++) { - *leftBuf++ = 0.0f; - *rightBuf++ = 0.0f; + alreadyOutputed = true; + + for (sampleNum = 0; sampleNum < length; sampleNum++) { + if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) { + deactivate(); + break; + } + la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue()); + if (hasRingModulatingSlave()) { + la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue()); + if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) { + pair->deactivate(); + if (mixType == 2) { + deactivate(); + break; + } + } + } + + // Although, LA32 applies panning itself, we assume here it is applied in the mixer, not within a pair. + // Applying the pan value in the log-space looks like a waste of unlog resources. Though, it needs clarification. + Sample sample = la32Pair.nextOutSample(); + + // FIXME: Sample analysis suggests that the use of panVal is linear, but there are some quirks that still need to be resolved. +#if MT32EMU_USE_FLOAT_SAMPLES + Sample leftOut = (sample * (float)leftPanValue) / 14.0f; + Sample rightOut = (sample * (float)rightPanValue) / 14.0f; + *(leftBuf++) += leftOut; + *(rightBuf++) += rightOut; +#else + // FIXME: Dividing by 7 (or by 14 in a Mok-friendly way) looks of course pointless. Need clarification. + // FIXME2: LA32 may produce distorted sound in case if the absolute value of maximal amplitude of the input exceeds 8191 + // when the panning value is non-zero. Most probably the distortion occurs in the same way it does with ring modulation, + // and it seems to be caused by limited precision of the common multiplication circuit. + // From analysis of this overflow, it is obvious that the right channel output is actually found + // by subtraction of the left channel output from the input. + // Though, it is unknown whether this overflow is exploited somewhere. + Sample leftOut = Sample((sample * leftPanValue) >> 8); + Sample rightOut = Sample((sample * rightPanValue) >> 8); + *leftBuf = Synth::clipBit16s((Bit32s)*leftBuf + (Bit32s)leftOut); + *rightBuf = Synth::clipBit16s((Bit32s)*rightBuf + (Bit32s)rightOut); + leftBuf++; + rightBuf++; +#endif } + sampleNum = 0; return true; } diff --git a/audio/softsynth/mt32/Partial.h b/audio/softsynth/mt32/Partial.h index 21b1bfe376d..05c1c740c48 100644 --- a/audio/softsynth/mt32/Partial.h +++ b/audio/softsynth/mt32/Partial.h @@ -25,26 +25,22 @@ class Part; class TVA; struct ControlROMPCMStruct; -struct StereoVolume { - float leftVol; - float rightVol; -}; - // A partial represents one of up to four waveform generators currently playing within a poly. class Partial { private: Synth *synth; const int debugPartialNum; // Only used for debugging - // Number of the sample currently being rendered by generateSamples(), or 0 if no run is in progress + // Number of the sample currently being rendered by produceOutput(), or 0 if no run is in progress // This is only kept available for debugging purposes. unsigned long sampleNum; + // Actually, this is a 4-bit register but we abuse this to emulate inverted mixing. + // Also we double the value to enable INACCURATE_SMOOTH_PAN, with respect to MoK. + Bit32s leftPanValue, rightPanValue; + int ownerPart; // -1 if unassigned int mixType; int structurePosition; // 0 or 1 of a structure pair - StereoVolume stereoVolume; - - Bit16s myBuffer[MAX_SAMPLES_PER_RUN]; // Only used for PCM partials int pcmNum; @@ -56,6 +52,11 @@ private: int pulseWidthVal; Poly *poly; + Partial *pair; + + TVA *tva; + TVP *tvp; + TVF *tvf; LA32Ramp ampRamp; LA32Ramp cutoffModifierRamp; @@ -63,18 +64,13 @@ private: // TODO: This should be owned by PartialPair LA32PartialPair la32Pair; + const PatchCache *patchCache; + PatchCache cachebackup; + Bit32u getAmpValue(); Bit32u getCutoffValue(); public: - const PatchCache *patchCache; - TVA *tva; - TVP *tvp; - TVF *tvf; - - PatchCache cachebackup; - - Partial *pair; bool alreadyOutputed; Partial(Synth *synth, int debugPartialNum); @@ -97,14 +93,14 @@ public: bool isPCM() const; const ControlROMPCMStruct *getControlROMPCMStruct() const; Synth *getSynth() const; + TVA *getTVA() const; + + void backupCache(const PatchCache &cache); // Returns true only if data written to buffer // This function (unlike the one below it) returns processed stereo samples // made from combining this single partial with its pair, if it has one. - bool produceOutput(float *leftBuf, float *rightBuf, unsigned long length); - - // This function writes mono sample output to the provided buffer, and returns the number of samples written - unsigned long generateSamples(Bit16s *partialBuf, unsigned long length); + bool produceOutput(Sample *leftBuf, Sample *rightBuf, unsigned long length); }; } diff --git a/audio/softsynth/mt32/PartialManager.cpp b/audio/softsynth/mt32/PartialManager.cpp index 436e7a353e7..905b5b8cf30 100644 --- a/audio/softsynth/mt32/PartialManager.cpp +++ b/audio/softsynth/mt32/PartialManager.cpp @@ -25,19 +25,26 @@ namespace MT32Emu { PartialManager::PartialManager(Synth *useSynth, Part **useParts) { synth = useSynth; parts = useParts; - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + partialTable = new Partial *[synth->getPartialCount()]; + freePolys = new Poly *[synth->getPartialCount()]; + firstFreePolyIndex = 0; + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { partialTable[i] = new Partial(synth, i); + freePolys[i] = new Poly(); } } PartialManager::~PartialManager(void) { - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { delete partialTable[i]; + if (freePolys[i] != NULL) delete freePolys[i]; } + delete[] partialTable; + delete[] freePolys; } void PartialManager::clearAlreadyOutputed() { - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { partialTable[i]->alreadyOutputed = false; } } @@ -46,12 +53,12 @@ bool PartialManager::shouldReverb(int i) { return partialTable[i]->shouldReverb(); } -bool PartialManager::produceOutput(int i, float *leftBuf, float *rightBuf, Bit32u bufferLength) { +bool PartialManager::produceOutput(int i, Sample *leftBuf, Sample *rightBuf, Bit32u bufferLength) { return partialTable[i]->produceOutput(leftBuf, rightBuf, bufferLength); } void PartialManager::deactivateAll() { - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { partialTable[i]->deactivate(); } } @@ -69,7 +76,7 @@ Partial *PartialManager::allocPartial(int partNum) { Partial *outPartial = NULL; // Get the first inactive partial - for (int partialNum = 0; partialNum < MT32EMU_MAX_PARTIALS; partialNum++) { + for (unsigned int partialNum = 0; partialNum < synth->getPartialCount(); partialNum++) { if (!partialTable[partialNum]->isActive()) { outPartial = partialTable[partialNum]; break; @@ -83,7 +90,7 @@ Partial *PartialManager::allocPartial(int partNum) { unsigned int PartialManager::getFreePartialCount(void) { int count = 0; - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { if (!partialTable[i]->isActive()) { count++; } @@ -94,7 +101,7 @@ unsigned int PartialManager::getFreePartialCount(void) { // This function is solely used to gather data for debug output at the moment. void PartialManager::getPerPartPartialUsage(unsigned int perPartPartialUsage[9]) { memset(perPartPartialUsage, 0, 9 * sizeof(unsigned int)); - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { if (partialTable[i]->isActive()) { perPartPartialUsage[partialTable[i]->getOwnerPart()]++; } @@ -189,7 +196,7 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { break; } #endif - if (getFreePartialCount() >= needed) { + if (synth->isAbortingPoly() || getFreePartialCount() >= needed) { return true; } } @@ -206,7 +213,7 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { if (!abortFirstPolyPreferHeldWhereReserveExceeded(partNum)) { break; } - if (getFreePartialCount() >= needed) { + if (synth->isAbortingPoly() || getFreePartialCount() >= needed) { return true; } } @@ -222,7 +229,7 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { if (!abortFirstPolyPreferHeldWhereReserveExceeded(-1)) { break; } - if (getFreePartialCount() >= needed) { + if (synth->isAbortingPoly() || getFreePartialCount() >= needed) { return true; } } @@ -233,7 +240,7 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { if (!parts[partNum]->abortFirstPolyPreferHeld()) { break; } - if (getFreePartialCount() >= needed) { + if (synth->isAbortingPoly() || getFreePartialCount() >= needed) { return true; } } @@ -243,10 +250,39 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { } const Partial *PartialManager::getPartial(unsigned int partialNum) const { - if (partialNum > MT32EMU_MAX_PARTIALS - 1) { + if (partialNum > synth->getPartialCount() - 1) { return NULL; } return partialTable[partialNum]; } +Poly *PartialManager::assignPolyToPart(Part *part) { + if (firstFreePolyIndex < synth->getPartialCount()) { + Poly *poly = freePolys[firstFreePolyIndex]; + freePolys[firstFreePolyIndex] = NULL; + firstFreePolyIndex++; + poly->setPart(part); + return poly; + } + return NULL; +} + +void PartialManager::polyFreed(Poly *poly) { + if (0 == firstFreePolyIndex) { + synth->printDebug("Cannot return freed poly, currently active polys:\n"); + for (Bit32u partNum = 0; partNum < 9; partNum++) { + const Poly *activePoly = synth->getPart(partNum)->getFirstActivePoly(); + Bit32u polyCount = 0; + while (activePoly != NULL) { + activePoly->getNext(); + polyCount++; + } + synth->printDebug("Part: %i, active poly count: %i\n", partNum, polyCount); + } + } + poly->setPart(NULL); + firstFreePolyIndex--; + freePolys[firstFreePolyIndex] = poly; +} + } diff --git a/audio/softsynth/mt32/PartialManager.h b/audio/softsynth/mt32/PartialManager.h index a1c9266ea1a..229b6e81219 100644 --- a/audio/softsynth/mt32/PartialManager.h +++ b/audio/softsynth/mt32/PartialManager.h @@ -24,17 +24,17 @@ class Synth; class PartialManager { private: - Synth *synth; // Only used for sending debug output + Synth *synth; Part **parts; - - Partial *partialTable[MT32EMU_MAX_PARTIALS]; + Poly **freePolys; + Partial **partialTable; Bit8u numReservedPartialsForPart[9]; + Bit32u firstFreePolyIndex; bool abortFirstReleasingPolyWhereReserveExceeded(int minPart); bool abortFirstPolyPreferHeldWhereReserveExceeded(int minPart); public: - PartialManager(Synth *synth, Part **parts); ~PartialManager(); Partial *allocPartial(int partNum); @@ -43,10 +43,12 @@ public: bool freePartials(unsigned int needed, int partNum); unsigned int setReserve(Bit8u *rset); void deactivateAll(); - bool produceOutput(int i, float *leftBuf, float *rightBuf, Bit32u bufferLength); + bool produceOutput(int i, Sample *leftBuf, Sample *rightBuf, Bit32u bufferLength); bool shouldReverb(int i); void clearAlreadyOutputed(); const Partial *getPartial(unsigned int partialNum) const; + Poly *assignPolyToPart(Part *part); + void polyFreed(Poly *poly); }; } diff --git a/audio/softsynth/mt32/Poly.cpp b/audio/softsynth/mt32/Poly.cpp index 46574f8967e..15548812701 100644 --- a/audio/softsynth/mt32/Poly.cpp +++ b/audio/softsynth/mt32/Poly.cpp @@ -19,8 +19,8 @@ namespace MT32Emu { -Poly::Poly(Part *usePart) { - part = usePart; +Poly::Poly() { + part = NULL; key = 255; velocity = 255; sustain = false; @@ -32,10 +32,21 @@ Poly::Poly(Part *usePart) { next = NULL; } +void Poly::setPart(Part *usePart) { + part = usePart; +} + void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) { if (isActive()) { - // FIXME: Throw out some big ugly debug output with a lot of exclamation marks - we should never get here - terminate(); + // This should never happen + part->getSynth()->printDebug("Resetting active poly. Active partial count: %i\n", activePartialCount); + for (int i = 0; i < 4; i++) { + if (partials[i] != NULL && partials[i]->isActive()) { + partials[i]->deactivate(); + activePartialCount--; + } + } + state = POLY_Inactive; } key = newKey; @@ -92,41 +103,24 @@ bool Poly::startDecay() { } bool Poly::startAbort() { - if (state == POLY_Inactive) { + if (state == POLY_Inactive || part->getSynth()->isAbortingPoly()) { return false; } for (int t = 0; t < 4; t++) { Partial *partial = partials[t]; if (partial != NULL) { partial->startAbort(); + part->getSynth()->abortingPoly = this; } } return true; } -void Poly::terminate() { - if (state == POLY_Inactive) { - return; - } - for (int t = 0; t < 4; t++) { - Partial *partial = partials[t]; - if (partial != NULL) { - partial->deactivate(); - } - } - if (state != POLY_Inactive) { - // FIXME: Throw out lots of debug output - this should never happen - // (Deactivating the partials above should've made them each call partialDeactivated(), ultimately changing the state to POLY_Inactive) - state = POLY_Inactive; - } -} - void Poly::backupCacheToPartials(PatchCache cache[4]) { for (int partialNum = 0; partialNum < 4; partialNum++) { Partial *partial = partials[partialNum]; - if (partial != NULL && partial->patchCache == &cache[partialNum]) { - partial->cachebackup = cache[partialNum]; - partial->patchCache = &partial->cachebackup; + if (partial != NULL) { + partial->backupCache(cache[partialNum]); } } } @@ -171,11 +165,14 @@ void Poly::partialDeactivated(Partial *partial) { } if (activePartialCount == 0) { state = POLY_Inactive; + if (part->getSynth()->abortingPoly == this) { + part->getSynth()->abortingPoly = NULL; + } } part->partialDeactivated(this); } -Poly *Poly::getNext() { +Poly *Poly::getNext() const { return next; } diff --git a/audio/softsynth/mt32/Poly.h b/audio/softsynth/mt32/Poly.h index 068cf73d35c..33abc35fdfc 100644 --- a/audio/softsynth/mt32/Poly.h +++ b/audio/softsynth/mt32/Poly.h @@ -44,13 +44,13 @@ private: Poly *next; public: - Poly(Part *part); + Poly(); + void setPart(Part *usePart); void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials); bool noteOff(bool pedalHeld); bool stopPedalHold(); bool startDecay(); bool startAbort(); - void terminate(); void backupCacheToPartials(PatchCache cache[4]); @@ -63,7 +63,7 @@ public: void partialDeactivated(Partial *partial); - Poly *getNext(); + Poly *getNext() const; void setNext(Poly *poly); }; diff --git a/audio/softsynth/mt32/Structures.h b/audio/softsynth/mt32/Structures.h index 43d2d1f2264..421e427fc01 100644 --- a/audio/softsynth/mt32/Structures.h +++ b/audio/softsynth/mt32/Structures.h @@ -38,6 +38,12 @@ typedef signed short int Bit16s; typedef unsigned char Bit8u; typedef signed char Bit8s; +#if MT32EMU_USE_FLOAT_SAMPLES +typedef float Sample; +#else +typedef Bit16s Sample; +#endif + // The following structures represent the MT-32's memory // Since sysex allows this memory to be written to in blocks of bytes, // we keep this packed so that we can copy data into the various diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp index 1e1be06bc96..efba9b75147 100644 --- a/audio/softsynth/mt32/Synth.cpp +++ b/audio/softsynth/mt32/Synth.cpp @@ -26,15 +26,7 @@ #include "mt32emu.h" #include "mmath.h" #include "PartialManager.h" - -#if MT32EMU_USE_REVERBMODEL == 1 -#include "AReverbModel.h" -#elif MT32EMU_USE_REVERBMODEL == 2 #include "BReverbModel.h" -#else -#include "FreeverbModel.h" -#endif -#include "DelayReverb.h" namespace MT32Emu { @@ -50,84 +42,22 @@ static const ControlROMMap ControlROMMaps[7] = { // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp) }; -static inline Bit16s *streamOffset(Bit16s *stream, Bit32u pos) { - return stream == NULL ? NULL : stream + pos; -} +static inline void muteStream(Sample *stream, Bit32u len) { + if (stream == NULL) return; -static inline void clearIfNonNull(Bit16s *stream, Bit32u len) { - if (stream != NULL) { - memset(stream, 0, len * sizeof(Bit16s)); - } -} - -static inline void mix(float *target, const float *stream, Bit32u len) { - while (len--) { - *target += *stream; - stream++; - target++; - } -} - -static inline void clearFloats(float *leftBuf, float *rightBuf, Bit32u len) { +#if MT32EMU_USE_FLOAT_SAMPLES // FIXME: Use memset() where compatibility is guaranteed (if this turns out to be a win) while (len--) { - *leftBuf++ = 0.0f; - *rightBuf++ = 0.0f; + *stream++ = 0.0f; } +#else + memset(stream, 0, len * sizeof(Sample)); +#endif } -static inline Bit16s clipBit16s(Bit32s a) { - // Clamp values above 32767 to 32767, and values below -32768 to -32768 - if ((a + 32768) & ~65535) { - return (a >> 31) ^ 32767; - } - return a; -} - -static void floatToBit16s_nice(Bit16s *target, const float *source, Bit32u len, float outputGain) { - float gain = outputGain * 16384.0f; - while (len--) { - // Since we're not shooting for accuracy here, don't worry about the rounding mode. - *target = clipBit16s((Bit32s)(*source * gain)); - source++; - target++; - } -} - -static void floatToBit16s_pure(Bit16s *target, const float *source, Bit32u len, float /*outputGain*/) { - while (len--) { - *target = clipBit16s((Bit32s)floor(*source * 8192.0f)); - source++; - target++; - } -} - -static void floatToBit16s_reverb(Bit16s *target, const float *source, Bit32u len, float outputGain) { - float gain = outputGain * 8192.0f; - while (len--) { - *target = clipBit16s((Bit32s)floor(*source * gain)); - source++; - target++; - } -} - -static void floatToBit16s_generation1(Bit16s *target, const float *source, Bit32u len, float outputGain) { - float gain = outputGain * 8192.0f; - while (len--) { - *target = clipBit16s((Bit32s)floor(*source * gain)); - *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE); - source++; - target++; - } -} - -static void floatToBit16s_generation2(Bit16s *target, const float *source, Bit32u len, float outputGain) { - float gain = outputGain * 8192.0f; - while (len--) { - *target = clipBit16s((Bit32s)floor(*source * gain)); - *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE) | ((*target >> 14) & 0x0001); - source++; - target++; +static inline void advanceStreamPosition(Sample *&stream, Bit32u posDelta) { + if (stream != NULL) { + stream += posDelta; } } @@ -146,6 +76,7 @@ Synth::Synth(ReportHandler *useReportHandler) { isOpen = false; reverbEnabled = true; reverbOverridden = false; + partialCount = DEFAULT_MAX_PARTIALS; if (useReportHandler == NULL) { reportHandler = new ReportHandler; @@ -155,28 +86,20 @@ Synth::Synth(ReportHandler *useReportHandler) { isDefaultReportHandler = false; } -#if MT32EMU_USE_REVERBMODEL == 1 - reverbModels[REVERB_MODE_ROOM] = new AReverbModel(REVERB_MODE_ROOM); - reverbModels[REVERB_MODE_HALL] = new AReverbModel(REVERB_MODE_HALL); - reverbModels[REVERB_MODE_PLATE] = new AReverbModel(REVERB_MODE_PLATE); - reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb(); -#elif MT32EMU_USE_REVERBMODEL == 2 reverbModels[REVERB_MODE_ROOM] = new BReverbModel(REVERB_MODE_ROOM); reverbModels[REVERB_MODE_HALL] = new BReverbModel(REVERB_MODE_HALL); reverbModels[REVERB_MODE_PLATE] = new BReverbModel(REVERB_MODE_PLATE); reverbModels[REVERB_MODE_TAP_DELAY] = new BReverbModel(REVERB_MODE_TAP_DELAY); -#else - reverbModels[REVERB_MODE_ROOM] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f); - reverbModels[REVERB_MODE_HALL] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f); - reverbModels[REVERB_MODE_PLATE] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f); - reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb(); -#endif reverbModel = NULL; setDACInputMode(DACInputMode_NICE); + setMIDIDelayMode(MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY); setOutputGain(1.0f); - setReverbOutputGain(0.68f); + setReverbOutputGain(1.0f); + setReversedStereoEnabled(false); partialManager = NULL; + midiQueue = NULL; + lastReceivedMIDIEventTimestamp = 0; memset(parts, 0, sizeof(parts)); renderedSampleCount = 0; } @@ -197,8 +120,8 @@ void ReportHandler::showLCDMessage(const char *data) { } void ReportHandler::printDebug(const char *fmt, va_list list) { - vprintf(fmt, list); - printf("\n"); + vprintf(fmt, list); + printf("\n"); } void Synth::polyStateChanged(int partNum) { @@ -236,35 +159,72 @@ bool Synth::isReverbOverridden() const { } void Synth::setDACInputMode(DACInputMode mode) { - switch(mode) { - case DACInputMode_GENERATION1: - la32FloatToBit16sFunc = floatToBit16s_generation1; - reverbFloatToBit16sFunc = floatToBit16s_reverb; - break; - case DACInputMode_GENERATION2: - la32FloatToBit16sFunc = floatToBit16s_generation2; - reverbFloatToBit16sFunc = floatToBit16s_reverb; - break; - case DACInputMode_PURE: - la32FloatToBit16sFunc = floatToBit16s_pure; - reverbFloatToBit16sFunc = floatToBit16s_pure; - break; - case DACInputMode_NICE: - default: - la32FloatToBit16sFunc = floatToBit16s_nice; - reverbFloatToBit16sFunc = floatToBit16s_reverb; - break; - } + dacInputMode = mode; } +DACInputMode Synth::getDACInputMode() const { + return dacInputMode; +} + +void Synth::setMIDIDelayMode(MIDIDelayMode mode) { + midiDelayMode = mode; +} + +MIDIDelayMode Synth::getMIDIDelayMode() const { + return midiDelayMode; +} + +#if MT32EMU_USE_FLOAT_SAMPLES + void Synth::setOutputGain(float newOutputGain) { outputGain = newOutputGain; } +float Synth::getOutputGain() const { + return outputGain; +} + void Synth::setReverbOutputGain(float newReverbOutputGain) { reverbOutputGain = newReverbOutputGain; } +float Synth::getReverbOutputGain() const { + return reverbOutputGain; +} + +#else // #if MT32EMU_USE_FLOAT_SAMPLES + +void Synth::setOutputGain(float newOutputGain) { + if (newOutputGain < 0.0f) newOutputGain = -newOutputGain; + if (256.0f < newOutputGain) newOutputGain = 256.0f; + outputGain = int(newOutputGain * 256.0f); +} + +float Synth::getOutputGain() const { + return outputGain / 256.0f; +} + +void Synth::setReverbOutputGain(float newReverbOutputGain) { + if (newReverbOutputGain < 0.0f) newReverbOutputGain = -newReverbOutputGain; + float maxValue = 256.0f / CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR; + if (maxValue < newReverbOutputGain) newReverbOutputGain = maxValue; + reverbOutputGain = int(newReverbOutputGain * 256.0f); +} + +float Synth::getReverbOutputGain() const { + return reverbOutputGain / 256.0f; +} + +#endif // #if MT32EMU_USE_FLOAT_SAMPLES + +void Synth::setReversedStereoEnabled(bool enabled) { + reversedStereoEnabled = enabled; +} + +bool Synth::isReversedStereoEnabled() { + return reversedStereoEnabled; +} + bool Synth::loadControlROM(const ROMImage &controlROMImage) { if (&controlROMImage == NULL) return false; Common::File *file = controlROMImage.getFile(); @@ -343,9 +303,9 @@ bool Synth::loadPCMROM(const ROMImage &pcmROMImage) { bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; for (int i = 0; i < count; i++) { - size_t rAddr = tps[i].pos * 0x800; - size_t rLenExp = (tps[i].len & 0x70) >> 4; - size_t rLen = 0x800 << rLenExp; + Bit32u rAddr = tps[i].pos * 0x800; + Bit32u rLenExp = (tps[i].len & 0x70) >> 4; + Bit32u rLen = 0x800 << rLenExp; if (rAddr + rLen > pcmROMSize) { printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen); return false; @@ -407,17 +367,18 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int count, int startTi return true; } -bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage) { +bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount) { if (isOpen) { return false; } - prerenderReadIx = prerenderWriteIx = 0; + partialCount = usePartialCount; + abortingPoly = NULL; #if MT32EMU_MONITOR_INIT printDebug("Initialising Constant Tables"); #endif #if !MT32EMU_REDUCE_REVERB_MEMORY - for (int i = 0; i < 4; i++) { - reverbModels[i]->open(useProp.sampleRate); + for (int i = REVERB_MODE_ROOM; i <= REVERB_MODE_TAP_DELAY; i++) { + reverbModels[i]->open(); } #endif @@ -554,6 +515,8 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage) { // For resetting mt32 mid-execution mt32default = mt32ram; + midiQueue = new MidiEventQueue(); + isOpen = true; isEnabled = false; @@ -568,6 +531,9 @@ void Synth::close() { return; } + delete midiQueue; + midiQueue = NULL; + delete partialManager; partialManager = NULL; @@ -588,12 +554,78 @@ void Synth::close() { isOpen = false; } -void Synth::playMsg(Bit32u msg) { +void Synth::flushMIDIQueue() { + if (midiQueue != NULL) { + for (;;) { + const MidiEvent *midiEvent = midiQueue->peekMidiEvent(); + if (midiEvent == NULL) break; + if (midiEvent->sysexData == NULL) { + playMsgNow(midiEvent->shortMessageData); + } else { + playSysexNow(midiEvent->sysexData, midiEvent->sysexLength); + } + midiQueue->dropMidiEvent(); + } + lastReceivedMIDIEventTimestamp = renderedSampleCount; + } +} + +void Synth::setMIDIEventQueueSize(Bit32u useSize) { + if (midiQueue != NULL) { + flushMIDIQueue(); + delete midiQueue; + midiQueue = new MidiEventQueue(useSize); + } +} + +Bit32u Synth::getShortMessageLength(Bit32u msg) { + if ((msg & 0xF0) == 0xF0) return 1; + // NOTE: This calculation isn't quite correct + // as it doesn't consider the running status byte + return ((msg & 0xE0) == 0xC0) ? 2 : 3; +} + +Bit32u Synth::addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp) { + Bit32u transferTime = Bit32u((double)len * MIDI_DATA_TRANSFER_RATE); + // Dealing with wrapping + if (Bit32s(timestamp - lastReceivedMIDIEventTimestamp) < 0) { + timestamp = lastReceivedMIDIEventTimestamp; + } + timestamp += transferTime; + lastReceivedMIDIEventTimestamp = timestamp; + return timestamp; +} + +bool Synth::playMsg(Bit32u msg) { + return playMsg(msg, renderedSampleCount); +} + +bool Synth::playMsg(Bit32u msg, Bit32u timestamp) { + if (midiQueue == NULL) return false; + if (midiDelayMode != MIDIDelayMode_IMMEDIATE) { + timestamp = addMIDIInterfaceDelay(getShortMessageLength(msg), timestamp); + } + return midiQueue->pushShortMessage(msg, timestamp); +} + +bool Synth::playSysex(const Bit8u *sysex, Bit32u len) { + return playSysex(sysex, len, renderedSampleCount); +} + +bool Synth::playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp) { + if (midiQueue == NULL) return false; + if (midiDelayMode == MIDIDelayMode_DELAY_ALL) { + timestamp = addMIDIInterfaceDelay(len, timestamp); + } + return midiQueue->pushSysex(sysex, len, timestamp); +} + +void Synth::playMsgNow(Bit32u msg) { // FIXME: Implement active sensing unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); unsigned char chan = (unsigned char)(msg & 0x00000F); - unsigned char note = (unsigned char)((msg & 0x00FF00) >> 8); - unsigned char velocity = (unsigned char)((msg & 0xFF0000) >> 16); + unsigned char note = (unsigned char)((msg & 0x007F00) >> 8); + unsigned char velocity = (unsigned char)((msg & 0x7F0000) >> 16); isEnabled = true; //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); @@ -606,11 +638,6 @@ void Synth::playMsg(Bit32u msg) { return; } playMsgOnPart(part, code, note, velocity); - - // This ensures minimum 1-sample delay between sequential MIDI events - // Without this, a sequence of NoteOn and immediately succeeding NoteOff messages is always silent - // Technically, it's also impossible to send events through the MIDI interface faster than about each millisecond - prerender(); } void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) { @@ -692,7 +719,7 @@ void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char #if MT32EMU_MONITOR_MIDI > 0 printDebug("Unknown MIDI Control code: 0x%02x - vel 0x%02x", note, velocity); #endif - break; + return; } break; @@ -709,13 +736,12 @@ void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char #if MT32EMU_MONITOR_MIDI > 0 printDebug("Unknown Midi code: 0x%01x - %02x - %02x", code, note, velocity); #endif - break; + return; } - - //midiOutShortMsg(m_out, msg); + reportHandler->onMIDIMessagePlayed(); } -void Synth::playSysex(const Bit8u *sysex, Bit32u len) { +void Synth::playSysexNow(const Bit8u *sysex, Bit32u len) { if (len < 2) { printDebug("playSysex: Message is too short for sysex (%d bytes)", len); } @@ -810,6 +836,7 @@ void Synth::readSysex(unsigned char /*device*/, const Bit8u * /*sysex*/, Bit32u } void Synth::writeSysex(unsigned char device, const Bit8u *sysex, Bit32u len) { + reportHandler->onMIDIMessagePlayed(); Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]); addr = MT32EMU_MEMADDR(addr); sysex += 3; @@ -1232,7 +1259,7 @@ void Synth::refreshSystemReverbParameters() { reportHandler->onNewReverbTime(mt32ram.system.reverbTime); reportHandler->onNewReverbLevel(mt32ram.system.reverbLevel); - ReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode]; + BReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode]; #if MT32EMU_REDUCE_REVERB_MEMORY if (reverbModel != newReverbModel) { if (reverbModel != NULL) { @@ -1308,179 +1335,229 @@ void Synth::reset() { isEnabled = false; } -void Synth::render(Bit16s *stream, Bit32u len) { - if (!isEnabled) { - memset(stream, 0, len * sizeof(Bit16s) * 2); - return; +MidiEvent::~MidiEvent() { + if (sysexData != NULL) { + delete[] sysexData; } +} + +void MidiEvent::setShortMessage(Bit32u useShortMessageData, Bit32u useTimestamp) { + if (sysexData != NULL) { + delete[] sysexData; + } + shortMessageData = useShortMessageData; + timestamp = useTimestamp; + sysexData = NULL; + sysexLength = 0; +} + +void MidiEvent::setSysex(const Bit8u *useSysexData, Bit32u useSysexLength, Bit32u useTimestamp) { + if (sysexData != NULL) { + delete[] sysexData; + } + shortMessageData = 0; + timestamp = useTimestamp; + sysexLength = useSysexLength; + Bit8u *dstSysexData = new Bit8u[sysexLength]; + sysexData = dstSysexData; + memcpy(dstSysexData, useSysexData, sysexLength); +} + +MidiEventQueue::MidiEventQueue(Bit32u useRingBufferSize) : ringBufferSize(useRingBufferSize) { + ringBuffer = new MidiEvent[ringBufferSize]; + memset(ringBuffer, 0, ringBufferSize * sizeof(MidiEvent)); + reset(); +} + +MidiEventQueue::~MidiEventQueue() { + delete[] ringBuffer; +} + +void MidiEventQueue::reset() { + startPosition = 0; + endPosition = 0; +} + +bool MidiEventQueue::pushShortMessage(Bit32u shortMessageData, Bit32u timestamp) { + unsigned int newEndPosition = (endPosition + 1) % ringBufferSize; + // Is ring buffer full? + if (startPosition == newEndPosition) return false; + ringBuffer[endPosition].setShortMessage(shortMessageData, timestamp); + endPosition = newEndPosition; + return true; +} + +bool MidiEventQueue::pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp) { + unsigned int newEndPosition = (endPosition + 1) % ringBufferSize; + // Is ring buffer full? + if (startPosition == newEndPosition) return false; + ringBuffer[endPosition].setSysex(sysexData, sysexLength, timestamp); + endPosition = newEndPosition; + return true; +} + +const MidiEvent *MidiEventQueue::peekMidiEvent() { + return (startPosition == endPosition) ? NULL : &ringBuffer[startPosition]; +} + +void MidiEventQueue::dropMidiEvent() { + // Is ring buffer empty? + if (startPosition != endPosition) { + startPosition = (startPosition + 1) % ringBufferSize; + } +} + +void Synth::render(Sample *stream, Bit32u len) { + Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN]; + Sample tmpNonReverbRight[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbDryRight[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbWetRight[MAX_SAMPLES_PER_RUN]; + while (len > 0) { Bit32u thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisLen); for (Bit32u i = 0; i < thisLen; i++) { - stream[0] = clipBit16s((Bit32s)tmpNonReverbLeft[i] + (Bit32s)tmpReverbDryLeft[i] + (Bit32s)tmpReverbWetLeft[i]); - stream[1] = clipBit16s((Bit32s)tmpNonReverbRight[i] + (Bit32s)tmpReverbDryRight[i] + (Bit32s)tmpReverbWetRight[i]); - stream += 2; +#if MT32EMU_USE_FLOAT_SAMPLES + *(stream++) = tmpNonReverbLeft[i] + tmpReverbDryLeft[i] + tmpReverbWetLeft[i]; + *(stream++) = tmpNonReverbRight[i] + tmpReverbDryRight[i] + tmpReverbWetRight[i]; +#else + *(stream++) = clipBit16s((Bit32s)tmpNonReverbLeft[i] + (Bit32s)tmpReverbDryLeft[i] + (Bit32s)tmpReverbWetLeft[i]); + *(stream++) = clipBit16s((Bit32s)tmpNonReverbRight[i] + (Bit32s)tmpReverbDryRight[i] + (Bit32s)tmpReverbWetRight[i]); +#endif } len -= thisLen; } } -bool Synth::prerender() { - int newPrerenderWriteIx = (prerenderWriteIx + 1) % MAX_PRERENDER_SAMPLES; - if (newPrerenderWriteIx == prerenderReadIx) { - // The prerender buffer is full - return false; - } - doRenderStreams( - prerenderNonReverbLeft + prerenderWriteIx, - prerenderNonReverbRight + prerenderWriteIx, - prerenderReverbDryLeft + prerenderWriteIx, - prerenderReverbDryRight + prerenderWriteIx, - prerenderReverbWetLeft + prerenderWriteIx, - prerenderReverbWetRight + prerenderWriteIx, - 1); - prerenderWriteIx = newPrerenderWriteIx; - return true; -} - -static inline void maybeCopy(Bit16s *out, Bit32u outPos, Bit16s *in, Bit32u inPos, Bit32u len) { - if (out == NULL) { - return; - } - memcpy(out + outPos, in + inPos, len * sizeof(Bit16s)); -} - -void Synth::copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len) { - maybeCopy(nonReverbLeft, pos, prerenderNonReverbLeft, prerenderReadIx, len); - maybeCopy(nonReverbRight, pos, prerenderNonReverbRight, prerenderReadIx, len); - maybeCopy(reverbDryLeft, pos, prerenderReverbDryLeft, prerenderReadIx, len); - maybeCopy(reverbDryRight, pos, prerenderReverbDryRight, prerenderReadIx, len); - maybeCopy(reverbWetLeft, pos, prerenderReverbWetLeft, prerenderReadIx, len); - maybeCopy(reverbWetRight, pos, prerenderReverbWetRight, prerenderReadIx, len); -} - -void Synth::checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len) { - if (prerenderReadIx > prerenderWriteIx) { - // There's data in the prerender buffer, and the write index has wrapped. - Bit32u prerenderCopyLen = MAX_PRERENDER_SAMPLES - prerenderReadIx; - if (prerenderCopyLen > len) { - prerenderCopyLen = len; - } - copyPrerender(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, pos, prerenderCopyLen); - len -= prerenderCopyLen; - pos += prerenderCopyLen; - prerenderReadIx = (prerenderReadIx + prerenderCopyLen) % MAX_PRERENDER_SAMPLES; - } - if (prerenderReadIx < prerenderWriteIx) { - // There's data in the prerender buffer, and the write index is ahead of the read index. - Bit32u prerenderCopyLen = prerenderWriteIx - prerenderReadIx; - if (prerenderCopyLen > len) { - prerenderCopyLen = len; - } - copyPrerender(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, pos, prerenderCopyLen); - len -= prerenderCopyLen; - pos += prerenderCopyLen; - prerenderReadIx += prerenderCopyLen; - } - if (prerenderReadIx == prerenderWriteIx) { - // If the ring buffer's empty, reset it to start at 0 to minimise wrapping, - // which requires two writes instead of one. - prerenderReadIx = prerenderWriteIx = 0; - } -} - -void Synth::renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len) { - if (!isEnabled) { - clearIfNonNull(nonReverbLeft, len); - clearIfNonNull(nonReverbRight, len); - clearIfNonNull(reverbDryLeft, len); - clearIfNonNull(reverbDryRight, len); - clearIfNonNull(reverbWetLeft, len); - clearIfNonNull(reverbWetRight, len); - return; - } - Bit32u pos = 0; - - // First, check for data in the prerender buffer and spit that out before generating anything new. - // Note that the prerender buffer is rarely used - see comments elsewhere for details. - checkPrerender(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, pos, len); - +void Synth::renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len) { while (len > 0) { - Bit32u thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; - doRenderStreams( - streamOffset(nonReverbLeft, pos), - streamOffset(nonReverbRight, pos), - streamOffset(reverbDryLeft, pos), - streamOffset(reverbDryRight, pos), - streamOffset(reverbWetLeft, pos), - streamOffset(reverbWetRight, pos), - thisLen); + // We need to ensure zero-duration notes will play so add minimum 1-sample delay. + Bit32u thisLen = 1; + if (!isAbortingPoly()) { + const MidiEvent *nextEvent = midiQueue->peekMidiEvent(); + Bit32s samplesToNextEvent = (nextEvent != NULL) ? Bit32s(nextEvent->timestamp - renderedSampleCount) : MAX_SAMPLES_PER_RUN; + if (samplesToNextEvent > 0) { + thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; + if (thisLen > (Bit32u)samplesToNextEvent) { + thisLen = samplesToNextEvent; + } + } else { + if (nextEvent->sysexData == NULL) { + playMsgNow(nextEvent->shortMessageData); + // If a poly is aborting we don't drop the event from the queue. + // Instead, we'll return to it again when the abortion is done. + if (!isAbortingPoly()) { + midiQueue->dropMidiEvent(); + } + } else { + playSysexNow(nextEvent->sysexData, nextEvent->sysexLength); + midiQueue->dropMidiEvent(); + } + } + } + doRenderStreams(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, thisLen); + advanceStreamPosition(nonReverbLeft, thisLen); + advanceStreamPosition(nonReverbRight, thisLen); + advanceStreamPosition(reverbDryLeft, thisLen); + advanceStreamPosition(reverbDryRight, thisLen); + advanceStreamPosition(reverbWetLeft, thisLen); + advanceStreamPosition(reverbWetRight, thisLen); len -= thisLen; - pos += thisLen; } } -// FIXME: Using more temporary buffers than we need to -void Synth::doRenderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len) { - clearFloats(&tmpBufMixLeft[0], &tmpBufMixRight[0], len); - if (!reverbEnabled) { - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialManager->produceOutput(i, &tmpBufPartialLeft[0], &tmpBufPartialRight[0], len)) { - mix(&tmpBufMixLeft[0], &tmpBufPartialLeft[0], len); - mix(&tmpBufMixRight[0], &tmpBufPartialRight[0], len); - } - } - if (nonReverbLeft != NULL) { - la32FloatToBit16sFunc(nonReverbLeft, &tmpBufMixLeft[0], len, outputGain); - } - if (nonReverbRight != NULL) { - la32FloatToBit16sFunc(nonReverbRight, &tmpBufMixRight[0], len, outputGain); - } - clearIfNonNull(reverbDryLeft, len); - clearIfNonNull(reverbDryRight, len); - clearIfNonNull(reverbWetLeft, len); - clearIfNonNull(reverbWetRight, len); +void Synth::convertSamplesToOutput(Sample *target, const Sample *source, Bit32u len, bool reverb) { + if (target == NULL) return; + + if (dacInputMode == DACInputMode_PURE) { + memcpy(target, source, len * sizeof(Sample)); + return; + } + +#if MT32EMU_USE_FLOAT_SAMPLES + float gain = reverb ? reverbOutputGain * CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR : 2.0f * outputGain; + while (len--) { + *(target++) = *(source++) * gain; + } +#else + int gain; + if (reverb) { + gain = int(reverbOutputGain * CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR); } else { - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (!partialManager->shouldReverb(i)) { - if (partialManager->produceOutput(i, &tmpBufPartialLeft[0], &tmpBufPartialRight[0], len)) { - mix(&tmpBufMixLeft[0], &tmpBufPartialLeft[0], len); - mix(&tmpBufMixRight[0], &tmpBufPartialRight[0], len); - } + gain = outputGain; + switch (dacInputMode) { + case DACInputMode_NICE: + // Since we're not shooting for accuracy here, don't worry about the rounding mode. + gain <<= 1; + break; + case DACInputMode_GENERATION1: + while (len--) { + *target = clipBit16s(Bit32s((*source * gain) >> 8)); + *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE); + source++; + target++; } - } - if (nonReverbLeft != NULL) { - la32FloatToBit16sFunc(nonReverbLeft, &tmpBufMixLeft[0], len, outputGain); - } - if (nonReverbRight != NULL) { - la32FloatToBit16sFunc(nonReverbRight, &tmpBufMixRight[0], len, outputGain); - } - - clearFloats(&tmpBufMixLeft[0], &tmpBufMixRight[0], len); - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialManager->shouldReverb(i)) { - if (partialManager->produceOutput(i, &tmpBufPartialLeft[0], &tmpBufPartialRight[0], len)) { - mix(&tmpBufMixLeft[0], &tmpBufPartialLeft[0], len); - mix(&tmpBufMixRight[0], &tmpBufPartialRight[0], len); - } + return; + case DACInputMode_GENERATION2: + while (len--) { + *target = clipBit16s(Bit32s((*source * gain) >> 8)); + *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE) | ((*target >> 14) & 0x0001); + source++; + target++; } - } - if (reverbDryLeft != NULL) { - la32FloatToBit16sFunc(reverbDryLeft, &tmpBufMixLeft[0], len, outputGain); - } - if (reverbDryRight != NULL) { - la32FloatToBit16sFunc(reverbDryRight, &tmpBufMixRight[0], len, outputGain); - } - - // FIXME: Note that on the real devices, reverb input and output are signed linear 16-bit (well, kinda, there's some fudging) PCM, not float. - reverbModel->process(&tmpBufMixLeft[0], &tmpBufMixRight[0], &tmpBufReverbOutLeft[0], &tmpBufReverbOutRight[0], len); - if (reverbWetLeft != NULL) { - reverbFloatToBit16sFunc(reverbWetLeft, &tmpBufReverbOutLeft[0], len, reverbOutputGain); - } - if (reverbWetRight != NULL) { - reverbFloatToBit16sFunc(reverbWetRight, &tmpBufReverbOutRight[0], len, reverbOutputGain); + return; + default: + break; } } + while (len--) { + *(target++) = clipBit16s(Bit32s((*(source++) * gain) >> 8)); + } +#endif +} + +void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len) { + if (isEnabled) { + Sample tmpBufMixLeft[MAX_SAMPLES_PER_RUN], tmpBufMixRight[MAX_SAMPLES_PER_RUN]; + muteStream(tmpBufMixLeft, len); + muteStream(tmpBufMixRight, len); + for (unsigned int i = 0; i < getPartialCount(); i++) { + if (!reverbEnabled || !partialManager->shouldReverb(i)) { + partialManager->produceOutput(i, tmpBufMixLeft, tmpBufMixRight, len); + } + } + convertSamplesToOutput(nonReverbLeft, tmpBufMixLeft, len, false); + convertSamplesToOutput(nonReverbRight, tmpBufMixRight, len, false); + } else { + muteStream(nonReverbLeft, len); + muteStream(nonReverbRight, len); + } + + if (isEnabled && reverbEnabled) { + Sample tmpBufMixLeft[MAX_SAMPLES_PER_RUN], tmpBufMixRight[MAX_SAMPLES_PER_RUN]; + muteStream(tmpBufMixLeft, len); + muteStream(tmpBufMixRight, len); + for (unsigned int i = 0; i < getPartialCount(); i++) { + if (partialManager->shouldReverb(i)) { + partialManager->produceOutput(i, tmpBufMixLeft, tmpBufMixRight, len); + } + } + convertSamplesToOutput(reverbDryLeft, tmpBufMixLeft, len, false); + convertSamplesToOutput(reverbDryRight, tmpBufMixRight, len, false); + + Sample tmpBufReverbOutLeft[MAX_SAMPLES_PER_RUN], tmpBufReverbOutRight[MAX_SAMPLES_PER_RUN]; + reverbModel->process(tmpBufMixLeft, tmpBufMixRight, tmpBufReverbOutLeft, tmpBufReverbOutRight, len); + convertSamplesToOutput(reverbWetLeft, tmpBufReverbOutLeft, len, true); + convertSamplesToOutput(reverbWetRight, tmpBufReverbOutRight, len, true); + } else { + muteStream(reverbDryLeft, len); + muteStream(reverbDryRight, len); + muteStream(reverbWetLeft, len); + muteStream(reverbWetRight, len); + } + partialManager->clearAlreadyOutputed(); renderedSampleCount += len; } @@ -1489,19 +1566,14 @@ void Synth::printPartialUsage(unsigned long sampleOffset) { unsigned int partialUsage[9]; partialManager->getPerPartPartialUsage(partialUsage); if (sampleOffset > 0) { - printDebug("[+%lu] Partial Usage: 1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d R: %02d TOTAL: %02d", sampleOffset, partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7], partialUsage[8], MT32EMU_MAX_PARTIALS - partialManager->getFreePartialCount()); + printDebug("[+%lu] Partial Usage: 1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d R: %02d TOTAL: %02d", sampleOffset, partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7], partialUsage[8], getPartialCount() - partialManager->getFreePartialCount()); } else { - printDebug("Partial Usage: 1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d R: %02d TOTAL: %02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7], partialUsage[8], MT32EMU_MAX_PARTIALS - partialManager->getFreePartialCount()); + printDebug("Partial Usage: 1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d R: %02d TOTAL: %02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7], partialUsage[8], getPartialCount() - partialManager->getFreePartialCount()); } } bool Synth::hasActivePartials() const { - if (prerenderReadIx != prerenderWriteIx) { - // Data in the prerender buffer means that the current isActive() states are "in the future". - // It also means that partials are definitely active at this render point. - return true; - } - for (int partialNum = 0; partialNum < MT32EMU_MAX_PARTIALS; partialNum++) { + for (unsigned int partialNum = 0; partialNum < getPartialCount(); partialNum++) { if (partialManager->getPartial(partialNum)->isActive()) { return true; } @@ -1509,6 +1581,10 @@ bool Synth::hasActivePartials() const { return false; } +bool Synth::isAbortingPoly() const { + return abortingPoly != NULL; +} + bool Synth::isActive() const { if (hasActivePartials()) { return true; @@ -1523,6 +1599,10 @@ const Partial *Synth::getPartial(unsigned int partialNum) const { return partialManager->getPartial(partialNum); } +unsigned int Synth::getPartialCount() const { + return partialCount; +} + const Part *Synth::getPart(unsigned int partNum) const { if (partNum > 8) { return NULL; diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h index b85e7ae507e..8816711bf31 100644 --- a/audio/softsynth/mt32/Synth.h +++ b/audio/softsynth/mt32/Synth.h @@ -27,6 +27,7 @@ class Partial; class PartialManager; class Part; class ROMImage; +class BReverbModel; /** * Methods for emulating the connection between the LA32 and the DAC, which involves @@ -44,6 +45,7 @@ enum DACInputMode { // * Much less likely to overdrive than any other mode. // * Half the volume of any of the other modes, meaning its volume relative to the reverb // output when mixed together directly will sound wrong. + // * Output gain is ignored for both LA32 and reverb output. // * Perfect for developers while debugging :) DACInputMode_PURE, @@ -58,7 +60,17 @@ enum DACInputMode { DACInputMode_GENERATION2 }; -typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain); +enum MIDIDelayMode { + // Process incoming MIDI events immediately. + MIDIDelayMode_IMMEDIATE, + + // Delay incoming short MIDI messages as if they where transferred via a MIDI cable to a real hardware unit and immediate sysex processing. + // This ensures more accurate timing of simultaneous NoteOn messages. + MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY, + + // Delay all incoming MIDI events as if they where transferred via a MIDI cable to a real hardware unit. + MIDIDelayMode_DELAY_ALL +}; const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; @@ -217,18 +229,6 @@ public: ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {} }; -class ReverbModel { -public: - virtual ~ReverbModel() {} - // After construction or a close(), open() will be called at least once before any other call (with the exception of close()). - virtual void open() = 0; - // May be called multiple times without an open() in between. - virtual void close() = 0; - virtual void setParameters(Bit8u time, Bit8u level) = 0; - virtual void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) = 0; - virtual bool isActive() const = 0; -}; - class ReportHandler { friend class Synth; @@ -244,22 +244,63 @@ protected: virtual void onErrorControlROM() {} virtual void onErrorPCMROM() {} virtual void showLCDMessage(const char *message); + virtual void onMIDIMessagePlayed() {} virtual void onDeviceReset() {} virtual void onDeviceReconfig() {} virtual void onNewReverbMode(Bit8u /* mode */) {} virtual void onNewReverbTime(Bit8u /* time */) {} virtual void onNewReverbLevel(Bit8u /* level */) {} - virtual void onPartStateChanged(int /* partNum */, bool /* hasActiveNonReleasingPolys */) {} virtual void onPolyStateChanged(int /* partNum */) {} - virtual void onPartialStateChanged(int /* partialNum */, int /* oldPartialPhase */, int /* newPartialPhase */) {} virtual void onProgramChanged(int /* partNum */, int /* bankNum */, const char * /* patchName */) {} }; +/** + * Used to safely store timestamped MIDI events in a local queue. + */ +struct MidiEvent { + Bit32u shortMessageData; + const Bit8u *sysexData; + Bit32u sysexLength; + Bit32u timestamp; + + ~MidiEvent(); + void setShortMessage(Bit32u shortMessageData, Bit32u timestamp); + void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); +}; + +/** + * Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it. + * It is intended to: + * - get rid of prerenderer while retaining graceful partial abortion + * - add fair emulation of the MIDI interface delays + * - extend the synth interface with the default implementation of a typical rendering loop. + * THREAD SAFETY: + * It is safe to use either in a single thread environment or when there are only two threads - one performs only reading + * and one performs only writing. More complicated usage requires external synchronisation. + */ +class MidiEventQueue { +private: + MidiEvent *ringBuffer; + Bit32u ringBufferSize; + volatile Bit32u startPosition; + volatile Bit32u endPosition; + +public: + MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE); + ~MidiEventQueue(); + void reset(); + bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp); + bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); + const MidiEvent *peekMidiEvent(); + void dropMidiEvent(); +}; + class Synth { friend class Part; friend class RhythmPart; friend class Poly; friend class Partial; +friend class PartialManager; friend class Tables; friend class MemoryRegion; friend class TVA; @@ -286,22 +327,32 @@ private: Bit16s *pcmROMData; size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM - Bit8s chantable[32]; - - Bit32u renderedSampleCount; + unsigned int partialCount; + Bit8s chantable[32]; // FIXME: Need explanation why 32 is set, obviously it should be 16 + MidiEventQueue *midiQueue; + volatile Bit32u lastReceivedMIDIEventTimestamp; + volatile Bit32u renderedSampleCount; MemParams mt32ram, mt32default; - ReverbModel *reverbModels[4]; - ReverbModel *reverbModel; + BReverbModel *reverbModels[4]; + BReverbModel *reverbModel; bool reverbEnabled; bool reverbOverridden; - FloatToBit16sFunc la32FloatToBit16sFunc; - FloatToBit16sFunc reverbFloatToBit16sFunc; + MIDIDelayMode midiDelayMode; + DACInputMode dacInputMode; + +#if MT32EMU_USE_FLOAT_SAMPLES float outputGain; float reverbOutputGain; +#else + int outputGain; + int reverbOutputGain; +#endif + + bool reversedStereoEnabled; bool isOpen; @@ -311,41 +362,18 @@ private: PartialManager *partialManager; Part *parts[9]; - // FIXME: We can reorganise things so that we don't need all these separate tmpBuf, tmp and prerender buffers. - // This should be rationalised when things have stabilised a bit (if prerender buffers don't die in the mean time). - - float tmpBufPartialLeft[MAX_SAMPLES_PER_RUN]; - float tmpBufPartialRight[MAX_SAMPLES_PER_RUN]; - float tmpBufMixLeft[MAX_SAMPLES_PER_RUN]; - float tmpBufMixRight[MAX_SAMPLES_PER_RUN]; - float tmpBufReverbOutLeft[MAX_SAMPLES_PER_RUN]; - float tmpBufReverbOutRight[MAX_SAMPLES_PER_RUN]; - - Bit16s tmpNonReverbLeft[MAX_SAMPLES_PER_RUN]; - Bit16s tmpNonReverbRight[MAX_SAMPLES_PER_RUN]; - Bit16s tmpReverbDryLeft[MAX_SAMPLES_PER_RUN]; - Bit16s tmpReverbDryRight[MAX_SAMPLES_PER_RUN]; - Bit16s tmpReverbWetLeft[MAX_SAMPLES_PER_RUN]; - Bit16s tmpReverbWetRight[MAX_SAMPLES_PER_RUN]; - - // These ring buffers are only used to simulate delays present on the real device. - // In particular, when a partial needs to be aborted to free it up for use by a new Poly, + // When a partial needs to be aborted to free it up for use by a new Poly, // the controller will busy-loop waiting for the sound to finish. - Bit16s prerenderNonReverbLeft[MAX_PRERENDER_SAMPLES]; - Bit16s prerenderNonReverbRight[MAX_PRERENDER_SAMPLES]; - Bit16s prerenderReverbDryLeft[MAX_PRERENDER_SAMPLES]; - Bit16s prerenderReverbDryRight[MAX_PRERENDER_SAMPLES]; - Bit16s prerenderReverbWetLeft[MAX_PRERENDER_SAMPLES]; - Bit16s prerenderReverbWetRight[MAX_PRERENDER_SAMPLES]; - int prerenderReadIx; - int prerenderWriteIx; + // We emulate this by delaying new MIDI events processing until abortion finishes. + Poly *abortingPoly; - bool prerender(); - void copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len); - void checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len); - void doRenderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len); + Bit32u getShortMessageLength(Bit32u msg); + Bit32u addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp); + + void convertSamplesToOutput(Sample *target, const Sample *source, Bit32u len, bool reverb); + bool isAbortingPoly() const; + void doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len); - void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len) const; void initMemoryRegions(); void deleteMemoryRegions(); @@ -375,6 +403,14 @@ private: void printDebug(const char *fmt, ...); public: + static inline Bit16s clipBit16s(Bit32s sample) { + // Clamp values above 32767 to 32767, and values below -32768 to -32768 + if ((sample + 32768) & ~65535) { + return (sample >> 31) ^ 32767; + } + return (Bit16s)sample; + } + static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); // Optionally sets callbacks for reporting various errors, information and debug messages @@ -384,18 +420,44 @@ public: // Used to initialise the MT-32. Must be called before any other function. // Returns true if initialization was sucessful, otherwise returns false. // controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth. - bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage); + // usePartialCount sets the maximum number of partials playing simultaneously for this session. + bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount = DEFAULT_MAX_PARTIALS); // Closes the MT-32 and deallocates any memory used by the synthesizer void close(void); - // Sends a 4-byte MIDI message to the MT-32 for immediate playback - void playMsg(Bit32u msg); + // All the enqueued events are processed by the synth immediately. + void flushMIDIQueue(); + + // Sets size of the internal MIDI event queue. + // The queue is flushed before reallocation. + void setMIDIEventQueueSize(Bit32u); + + // Enqueues a MIDI event for subsequent playback. + // The minimum delay involves the delay introduced while the event is transferred via MIDI interface + // and emulation of the MCU busy-loop while it frees partials for use by a new Poly. + // Calls from multiple threads must be synchronised, although, + // no synchronisation is required with the rendering thread. + + // The MIDI event will be processed not before the specified timestamp. + // The timestamp is measured as the global rendered sample count since the synth was created. + bool playMsg(Bit32u msg, Bit32u timestamp); + bool playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp); + // The MIDI event will be processed ASAP. + bool playMsg(Bit32u msg); + bool playSysex(const Bit8u *sysex, Bit32u len); + + // WARNING: + // The methods below don't ensure minimum 1-sample delay between sequential MIDI events, + // and a sequence of NoteOn and immediately succeeding NoteOff messages is always silent. + + // Sends a 4-byte MIDI message to the MT-32 for immediate playback. + void playMsgNow(Bit32u msg); void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); // Sends a string of Sysex commands to the MT-32 for immediate interpretation // The length is in bytes - void playSysex(const Bit8u *sysex, Bit32u len); + void playSysexNow(const Bit8u *sysex, Bit32u len); void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len); void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len); void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); @@ -405,20 +467,34 @@ public: void setReverbOverridden(bool reverbOverridden); bool isReverbOverridden() const; void setDACInputMode(DACInputMode mode); + DACInputMode getDACInputMode() const; + void setMIDIDelayMode(MIDIDelayMode mode); + MIDIDelayMode getMIDIDelayMode() const; // Sets output gain factor. Applied to all output samples and unrelated with the synth's Master volume. + // Ignored in DACInputMode_PURE void setOutputGain(float); + float getOutputGain() const; // Sets output gain factor for the reverb wet output. setOutputGain() doesn't change reverb output gain. + // Note: We're currently emulate CM-32L/CM-64 reverb quite accurately and the reverb output level closely + // corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic, + // there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68 + // of that for LA32 analogue output. This factor is applied to the reverb output gain. + // Ignored in DACInputMode_PURE void setReverbOutputGain(float); + float getReverbOutputGain() const; + + void setReversedStereoEnabled(bool enabled); + bool isReversedStereoEnabled(); // Renders samples to the specified output stream. // The length is in frames, not bytes (in 16-bit stereo, // one frame is 4 bytes). - void render(Bit16s *stream, Bit32u len); + void render(Sample *stream, Bit32u len); // Renders samples to the specified output streams (any or all of which may be NULL). - void renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len); + void renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len); // Returns true when there is at least one active partial, otherwise false. bool hasActivePartials() const; @@ -428,6 +504,9 @@ public: const Partial *getPartial(unsigned int partialNum) const; + // Returns the maximum number of partials playing simultaneously. + unsigned int getPartialCount() const; + void readMemory(Bit32u addr, Bit32u len, Bit8u *data); // partNum should be 0..7 for Part 1..8, or 8 for Rhythm diff --git a/audio/softsynth/mt32/TVP.cpp b/audio/softsynth/mt32/TVP.cpp index c3e64c18d0d..8f68245753c 100644 --- a/audio/softsynth/mt32/TVP.cpp +++ b/audio/softsynth/mt32/TVP.cpp @@ -181,7 +181,7 @@ void TVP::updatePitch() { pitch = (Bit16u)newPitch; // FIXME: We're doing this here because that's what the CM-32L does - we should probably move this somewhere more appropriate in future. - partial->tva->recalcSustain(); + partial->getTVA()->recalcSustain(); } void TVP::targetPitchOffsetReached() { diff --git a/audio/softsynth/mt32/Tables.h b/audio/softsynth/mt32/Tables.h index 8b4580df0ed..bfb80e121e1 100644 --- a/audio/softsynth/mt32/Tables.h +++ b/audio/softsynth/mt32/Tables.h @@ -25,6 +25,11 @@ namespace MT32Emu { // The output from the synth is supposed to be resampled to convert the sample rate. const unsigned int SAMPLE_RATE = 32000; +// MIDI interface data transfer rate in samples. Used to simulate the transfer delay. +const double MIDI_DATA_TRANSFER_RATE = (double)SAMPLE_RATE / 31250.0 * 8.0; + +const float CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR = 0.68f; + const int MIDDLEC = 60; class Synth; diff --git a/audio/softsynth/mt32/freeverb.cpp b/audio/softsynth/mt32/freeverb.cpp deleted file mode 100644 index 181b878596b..00000000000 --- a/audio/softsynth/mt32/freeverb.cpp +++ /dev/null @@ -1,324 +0,0 @@ -// Allpass filter implementation -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// This code is public domain - -#include "freeverb.h" - -allpass::allpass() -{ - bufidx = 0; -} - -void allpass::setbuffer(float *buf, int size) -{ - buffer = buf; - bufsize = size; -} - -void allpass::mute() -{ - for (int i=0; i= freezemode) - return; - - for (i=0;i 0) - { - int i; - - outL = outR = 0; - input = (*inputL + *inputR) * gain; - - // Implementation of 2-stage IIR single-pole low-pass filter - // found at the entrance of reverb processing on real devices - filtprev1 += (input - filtprev1) * filtval; - filtprev2 += (filtprev1 - filtprev2) * filtval; - input = filtprev2; - - int s = -1; - // Accumulate comb filters in parallel - for (i=0; i= freezemode) - { - roomsize1 = 1; - damp1 = 0; - gain = muted; - } - else - { - roomsize1 = roomsize; - damp1 = damp; - gain = fixedgain; - } - - for (i=0; i= freezemode) - return 1; - else - return 0; -} - -void revmodel::setfiltval(float value) -{ - filtval = value; -} diff --git a/audio/softsynth/mt32/freeverb.h b/audio/softsynth/mt32/freeverb.h deleted file mode 100644 index ae4d48169e2..00000000000 --- a/audio/softsynth/mt32/freeverb.h +++ /dev/null @@ -1,189 +0,0 @@ -#ifndef _freeverb_ -#define _freeverb_ - -// Reverb model tuning values -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// This code is public domain - -const int numcombs = 8; -const int numallpasses = 4; -const float muted = 0; -const float fixedgain = 0.015f; -const float scalewet = 3; -const float scaledry = 2; -const float scaledamp = 0.4f; -const float scaleroom = 0.28f; -const float offsetroom = 0.7f; -const float initialroom = 0.5f; -const float initialdamp = 0.5f; -const float initialwet = 1/scalewet; -const float initialdry = 0; -const float initialwidth = 1; -const float initialmode = 0; -const float freezemode = 0.5f; -const int stereospread = 23; - -const int combtuning[] = {1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617}; -const int allpasstuning[] = {556, 441, 341, 225}; - -// Macro for killing denormalled numbers -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// Based on IS_DENORMAL macro by Jon Watte -// This code is public domain - -static inline float undenormalise(float x) { - union { - float f; - unsigned int i; - } u; - u.f = x; - if ((u.i & 0x7f800000) == 0) { - return 0.0f; - } - return x; -} - -// Allpass filter declaration -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// This code is public domain - -class allpass -{ -public: - allpass(); - void setbuffer(float *buf, int size); - void deletebuffer(); - inline float process(float inp); - void mute(); - void setfeedback(float val); - float getfeedback(); -// private: - float feedback; - float *buffer; - int bufsize; - int bufidx; -}; - - -// Big to inline - but crucial for speed - -inline float allpass::process(float input) -{ - float output; - float bufout; - - bufout = undenormalise(buffer[bufidx]); - - output = -input + bufout; - buffer[bufidx] = input + (bufout*feedback); - - if (++bufidx>=bufsize) bufidx = 0; - - return output; -} - -// Comb filter class declaration -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// This code is public domain - -class comb -{ -public: - comb(); - void setbuffer(float *buf, int size); - void deletebuffer(); - inline float process(float inp); - void mute(); - void setdamp(float val); - float getdamp(); - void setfeedback(float val); - float getfeedback(); -private: - float feedback; - float filterstore; - float damp1; - float damp2; - float *buffer; - int bufsize; - int bufidx; -}; - - -// Big to inline - but crucial for speed - -inline float comb::process(float input) -{ - float output; - - output = undenormalise(buffer[bufidx]); - - filterstore = undenormalise((output*damp2) + (filterstore*damp1)); - - buffer[bufidx] = input + (filterstore*feedback); - - if (++bufidx>=bufsize) bufidx = 0; - - return output; -} - -// Reverb model declaration -// -// Written by Jezar at Dreampoint, June 2000 -// Modifications by Jerome Fisher, 2009 -// http://www.dreampoint.co.uk -// This code is public domain - -class revmodel -{ -public: - revmodel(float scaletuning); - ~revmodel(); - void mute(); - void process(const float *inputL, const float *inputR, float *outputL, float *outputR, long numsamples); - void setroomsize(float value); - float getroomsize(); - void setdamp(float value); - float getdamp(); - void setwet(float value); - float getwet(); - void setdry(float value); - float getdry(); - void setwidth(float value); - float getwidth(); - void setmode(float value); - float getmode(); - void setfiltval(float value); -private: - void update(); -private: - float gain; - float roomsize,roomsize1; - float damp,damp1; - float wet,wet1,wet2; - float dry; - float width; - float mode; - - // LPF stuff - float filtval; - float filtprev1; - float filtprev2; - - // Comb filters - comb combL[numcombs]; - comb combR[numcombs]; - - // Allpass filters - allpass allpassL[numallpasses]; - allpass allpassR[numallpasses]; -}; - -#endif//_freeverb_ diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk index e7afdfd2b4f..1c8aa125aba 100644 --- a/audio/softsynth/mt32/module.mk +++ b/audio/softsynth/mt32/module.mk @@ -1,24 +1,19 @@ MODULE := audio/softsynth/mt32 MODULE_OBJS := \ - AReverbModel.o \ BReverbModel.o \ - DelayReverb.o \ - FreeverbModel.o \ LA32Ramp.o \ LA32WaveGenerator.o \ - LegacyWaveGenerator.o \ Part.o \ Partial.o \ PartialManager.o \ Poly.o \ ROMInfo.o \ Synth.o \ + Tables.o \ TVA.o \ TVF.o \ - TVP.o \ - Tables.o \ - freeverb.o + TVP.o # Include common rules include $(srcdir)/rules.mk diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h index 971a0886d5e..ab963886ac7 100644 --- a/audio/softsynth/mt32/mt32emu.h +++ b/audio/softsynth/mt32/mt32emu.h @@ -60,27 +60,24 @@ #define MT32EMU_MONITOR_TVF 0 // Configuration -// The maximum number of partials playing simultaneously -#define MT32EMU_MAX_PARTIALS 32 -// The maximum number of notes playing simultaneously per part. -// No point making it more than MT32EMU_MAX_PARTIALS, since each note needs at least one partial. -#define MT32EMU_MAX_POLY 32 // If non-zero, deletes reverb buffers that are not in use to save memory. // If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path. #define MT32EMU_REDUCE_REVERB_MEMORY 1 -// 0: Use legacy Freeverb -// 1: Use Accurate Reverb model aka AReverb -// 2: Use Bit-perfect Boss Reverb model aka BReverb (for developers, not much practical use) -#define MT32EMU_USE_REVERBMODEL 1 +// 0: Maximum speed at the cost of a bit lower emulation accuracy. +// 1: Maximum achievable emulation accuracy. +#define MT32EMU_BOSS_REVERB_PRECISE_MODE 0 -// 0: Use refined wave generator based on logarithmic fixed-point computations and LUTs -// 1: Use legacy accurate wave generator based on float computations -#define MT32EMU_ACCURATE_WG 0 +// 0: Use 16-bit signed samples and refined wave generator based on logarithmic fixed-point computations and LUTs. Maximum emulation accuracy and speed. +// 1: Use float samples in the wave generator and renderer. Maximum output quality and minimum noise. +#define MT32EMU_USE_FLOAT_SAMPLES 0 namespace MT32Emu { +// The default value for the maximum number of partials playing simultaneously. +const unsigned int DEFAULT_MAX_PARTIALS = 32; + // The higher this number, the more memory will be used, but the more samples can be processed in one run - // various parts of sample generation can be processed more efficiently in a single run. // A run's maximum length is that given to Synth::render(), so giving a value here higher than render() is ever @@ -90,11 +87,14 @@ namespace MT32Emu // This value must be >= 1. const unsigned int MAX_SAMPLES_PER_RUN = 4096; -// This determines the amount of memory available for simulating delays. -// If set too low, partials aborted to allow other partials to play will not end gracefully, but will terminate -// abruptly and potentially cause a pop/crackle in the audio output. -// This value must be >= 1. -const unsigned int MAX_PRERENDER_SAMPLES = 1024; +// The default size of the internal MIDI event queue. +// It holds the incoming MIDI events before the rendering engine actually processes them. +// The main goal is to fairly emulate the real hardware behaviour which obviously +// uses an internal MIDI event queue to gather incoming data as well as the delays +// introduced by transferring data via the MIDI interface. +// This also facilitates building of an external rendering loop +// as the queue stores timestamped MIDI events. +const unsigned int DEFAULT_MIDI_EVENT_QUEUE_SIZE = 1024; } #include "Structures.h" @@ -103,7 +103,6 @@ const unsigned int MAX_PRERENDER_SAMPLES = 1024; #include "Poly.h" #include "LA32Ramp.h" #include "LA32WaveGenerator.h" -#include "LegacyWaveGenerator.h" #include "TVA.h" #include "TVP.h" #include "TVF.h" diff --git a/backends/base-backend.cpp b/backends/base-backend.cpp index 3e0005deddc..3e95c3e26ae 100644 --- a/backends/base-backend.cpp +++ b/backends/base-backend.cpp @@ -57,7 +57,7 @@ void BaseBackend::initBackend() { void BaseBackend::fillScreen(uint32 col) { Graphics::Surface *screen = lockScreen(); - if (screen && screen->pixels) - memset(screen->pixels, col, screen->h * screen->pitch); + if (screen && screen->getPixels()) + memset(screen->getPixels(), col, screen->h * screen->pitch); unlockScreen(); } diff --git a/backends/events/default/default-events.cpp b/backends/events/default/default-events.cpp index bf76bbc1cba..30f3b3790ca 100644 --- a/backends/events/default/default-events.cpp +++ b/backends/events/default/default-events.cpp @@ -54,6 +54,8 @@ DefaultEventManager::DefaultEventManager(Common::EventSource *boss) : _currentKeyDown.ascii = 0; _currentKeyDown.flags = 0; + _keyRepeatTime = 0; + #ifdef ENABLE_VKEYBD _vk = new Common::VirtualKeyboard(); #endif diff --git a/backends/midi/timidity.cpp b/backends/midi/timidity.cpp index b4e6e7f51a2..ddf1fc62020 100644 --- a/backends/midi/timidity.cpp +++ b/backends/midi/timidity.cpp @@ -148,7 +148,7 @@ MidiDriver_TIMIDITY::MidiDriver_TIMIDITY() { int MidiDriver_TIMIDITY::open() { char *res; - char timidity_host[MAXHOSTNAMELEN]; + char timidity_host[NI_MAXHOST]; int timidity_port, data_port, i; /* count ourselves open */ diff --git a/backends/modular-backend.cpp b/backends/modular-backend.cpp index 5b8e0e01dc8..dd76cf79d01 100644 --- a/backends/modular-backend.cpp +++ b/backends/modular-backend.cpp @@ -153,9 +153,15 @@ void ModularBackend::fillScreen(uint32 col) { } void ModularBackend::updateScreen() { +#ifdef ENABLE_EVENTRECORDER g_eventRec.preDrawOverlayGui(); +#endif + _graphicsManager->updateScreen(); + +#ifdef ENABLE_EVENTRECORDER g_eventRec.postDrawOverlayGui(); +#endif } void ModularBackend::setShakePos(int shakeOffset) { diff --git a/backends/platform/android/gfx.cpp b/backends/platform/android/gfx.cpp index be3e2e8812d..8f64ceb0760 100644 --- a/backends/platform/android/gfx.cpp +++ b/backends/platform/android/gfx.cpp @@ -583,7 +583,7 @@ Graphics::Surface *OSystem_Android::lockScreen() { GLTHREADCHECK; Graphics::Surface *surface = _game_texture->surface(); - assert(surface->pixels); + assert(surface->getPixels()); return surface; } @@ -676,7 +676,7 @@ void OSystem_Android::grabOverlay(void *buf, int pitch) { assert(surface->format.bytesPerPixel == sizeof(uint16)); byte *dst = (byte *)buf; - const byte *src = (const byte *)surface->pixels; + const byte *src = (const byte *)surface->getPixels(); uint h = surface->h; do { diff --git a/backends/platform/android/texture.cpp b/backends/platform/android/texture.cpp index 05d10c56594..f405ead9d23 100644 --- a/backends/platform/android/texture.cpp +++ b/backends/platform/android/texture.cpp @@ -247,7 +247,7 @@ void GLESTexture::allocBuffer(GLuint w, GLuint h) { _pixels = new byte[w * h * _surface.format.bytesPerPixel]; assert(_pixels); - _surface.pixels = _pixels; + _surface.setPixels(_pixels); fillBuffer(0); @@ -270,7 +270,7 @@ void GLESTexture::updateBuffer(GLuint x, GLuint y, GLuint w, GLuint h, } void GLESTexture::fillBuffer(uint32 color) { - assert(_surface.pixels); + assert(_surface.getPixels()); if (_pixelFormat.bytesPerPixel == 1 || ((color & 0xff) == ((color >> 8) & 0xff))) @@ -398,7 +398,7 @@ void GLESFakePaletteTexture::allocBuffer(GLuint w, GLuint h) { assert(_pixels); // fixup surface, for the outside this is a CLUT8 surface - _surface.pixels = _pixels; + _surface.setPixels(_pixels); fillBuffer(0); @@ -407,8 +407,8 @@ void GLESFakePaletteTexture::allocBuffer(GLuint w, GLuint h) { } void GLESFakePaletteTexture::fillBuffer(uint32 color) { - assert(_surface.pixels); - memset(_surface.pixels, color & 0xff, _surface.pitch * _surface.h); + assert(_surface.getPixels()); + memset(_surface.getPixels(), color & 0xff, _surface.pitch * _surface.h); setDirty(); } diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index 6be65a5337d..6a35f481311 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -88,7 +88,13 @@ OSystem_SDL::~OSystem_SDL() { delete _mixerManager; _mixerManager = 0; +#ifdef ENABLE_EVENTRECORDER + // HACK HACK HACK + // This is nasty. delete g_eventRec.getTimerManager(); +#else + delete _timerManager; +#endif _timerManager = 0; delete _mutexManager; @@ -151,9 +157,15 @@ void OSystem_SDL::initBackend() { // Setup and start mixer _mixerManager->init(); } + +#ifdef ENABLE_EVENTRECORDER g_eventRec.registerMixerManager(_mixerManager); g_eventRec.registerTimerManager(new SdlTimerManager()); +#else + if (_timerManager == 0) + _timerManager = new SdlTimerManager(); +#endif if (_audiocdManager == 0) { // Audio CD support was removed with SDL 1.3 @@ -423,12 +435,18 @@ void OSystem_SDL::setupIcon() { uint32 OSystem_SDL::getMillis(bool skipRecord) { uint32 millis = SDL_GetTicks(); + +#ifdef ENABLE_EVENTRECORDER g_eventRec.processMillis(millis, skipRecord); +#endif + return millis; } void OSystem_SDL::delayMillis(uint msecs) { +#ifdef ENABLE_EVENTRECORDER if (!g_eventRec.processDelayMillis()) +#endif SDL_Delay(msecs); } @@ -451,10 +469,19 @@ Audio::Mixer *OSystem_SDL::getMixer() { SdlMixerManager *OSystem_SDL::getMixerManager() { assert(_mixerManager); + +#ifdef ENABLE_EVENTRECORDER return g_eventRec.getMixerManager(); +#else + return _mixerManager; +#endif } Common::TimerManager *OSystem_SDL::getTimerManager() { +#ifdef ENABLE_EVENTRECORDER return g_eventRec.getTimerManager(); +#else + return _timerManager; +#endif } diff --git a/backends/vkeybd/virtual-keyboard-gui.cpp b/backends/vkeybd/virtual-keyboard-gui.cpp index 75de86472f0..ec4cbf1de24 100644 --- a/backends/vkeybd/virtual-keyboard-gui.cpp +++ b/backends/vkeybd/virtual-keyboard-gui.cpp @@ -32,11 +32,9 @@ namespace Common { -static void blit(Graphics::Surface *surf_dst, Graphics::Surface *surf_src, int16 x, int16 y, OverlayColor transparent) { - if (surf_dst->format.bytesPerPixel != sizeof(OverlayColor) || surf_src->format.bytesPerPixel != sizeof(OverlayColor)) - return; - - const OverlayColor *src = (const OverlayColor *)surf_src->pixels; +template +static void blitImplementation(Graphics::Surface *surf_dst, Graphics::Surface *surf_src, int16 x, int16 y, ColorType transparent) { + const ColorType *src = (const ColorType *)surf_src->getPixels(); int blitW = surf_src->w; int blitH = surf_src->h; @@ -58,13 +56,13 @@ static void blit(Graphics::Surface *surf_dst, Graphics::Surface *surf_src, int16 if (blitW <= 0 || blitH <= 0) return; - OverlayColor *dst = (OverlayColor *)surf_dst->getBasePtr(x, y); + ColorType *dst = (ColorType *)surf_dst->getBasePtr(x, y); int dstAdd = surf_dst->w - blitW; int srcAdd = surf_src->w - blitW; for (int i = 0; i < blitH; ++i) { for (int j = 0; j < blitW; ++j, ++dst, ++src) { - OverlayColor col = *src; + ColorType col = *src; if (col != transparent) *dst = col; } @@ -73,6 +71,16 @@ static void blit(Graphics::Surface *surf_dst, Graphics::Surface *surf_src, int16 } } +static void blit(Graphics::Surface *surf_dst, Graphics::Surface *surf_src, int16 x, int16 y, uint32 transparent) { + if (surf_dst->format.bytesPerPixel != surf_src->format.bytesPerPixel) + return; + + if (surf_dst->format.bytesPerPixel == 2) + blitImplementation(surf_dst, surf_src, x, y, transparent); + else if (surf_dst->format.bytesPerPixel == 4) + blitImplementation(surf_dst, surf_src, x, y, transparent); +} + VirtualKeyboardGUI::VirtualKeyboardGUI(VirtualKeyboard *kbd) : _kbd(kbd), _displaying(false), _drag(false), _drawCaret(false), _displayEnabled(false), _firstRun(true), @@ -111,7 +119,7 @@ void VirtualKeyboardGUI::initMode(VirtualKeyboard::Mode *mode) { } } -void VirtualKeyboardGUI::setupDisplayArea(Rect &r, OverlayColor forecolor) { +void VirtualKeyboardGUI::setupDisplayArea(Rect &r, uint32 forecolor) { _dispFont = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont); if (!fontIsSuitable(_dispFont, r)) { @@ -161,7 +169,7 @@ void VirtualKeyboardGUI::run() { _system->clearOverlay(); } _overlayBackup.create(_screenW, _screenH, _system->getOverlayFormat()); - _system->grabOverlay(_overlayBackup.pixels, _overlayBackup.pitch); + _system->grabOverlay(_overlayBackup.getPixels(), _overlayBackup.pitch); setupCursor(); @@ -171,7 +179,7 @@ void VirtualKeyboardGUI::run() { removeCursor(); - _system->copyRectToOverlay(_overlayBackup.pixels, _overlayBackup.pitch, 0, 0, _overlayBackup.w, _overlayBackup.h); + _system->copyRectToOverlay(_overlayBackup.getPixels(), _overlayBackup.pitch, 0, 0, _overlayBackup.w, _overlayBackup.h); if (!g_gui.isActive()) _system->hideOverlay(); _overlayBackup.free(); @@ -262,7 +270,7 @@ void VirtualKeyboardGUI::screenChanged() { _screenH = newScreenH; _overlayBackup.create(_screenW, _screenH, _system->getOverlayFormat()); - _system->grabOverlay(_overlayBackup.pixels, _overlayBackup.pitch); + _system->grabOverlay(_overlayBackup.getPixels(), _overlayBackup.pitch); if (!_kbd->checkModeResolutions()) { _displaying = false; @@ -356,13 +364,13 @@ void VirtualKeyboardGUI::redraw() { Graphics::Surface surf; surf.create(w, h, _system->getOverlayFormat()); - OverlayColor *dst = (OverlayColor *)surf.pixels; - const OverlayColor *src = (OverlayColor *) _overlayBackup.getBasePtr(_dirtyRect.left, _dirtyRect.top); + byte *dst = (byte *)surf.getPixels(); + const byte *src = (const byte *)_overlayBackup.getBasePtr(_dirtyRect.left, _dirtyRect.top); while (h--) { - memcpy(dst, src, surf.w * sizeof(OverlayColor)); - dst += surf.w; - src += _overlayBackup.w; + memcpy(dst, src, surf.pitch); + dst += surf.pitch; + src += _overlayBackup.pitch; } blit(&surf, _kbdSurface, _kbdBound.left - _dirtyRect.left, @@ -371,7 +379,7 @@ void VirtualKeyboardGUI::redraw() { blit(&surf, &_dispSurface, _dispX - _dirtyRect.left, _dispY - _dirtyRect.top, _dispBackColor); } - _system->copyRectToOverlay(surf.pixels, surf.pitch, + _system->copyRectToOverlay(surf.getPixels(), surf.pitch, _dirtyRect.left, _dirtyRect.top, surf.w, surf.h); surf.free(); diff --git a/backends/vkeybd/virtual-keyboard-gui.h b/backends/vkeybd/virtual-keyboard-gui.h index d0f9c884edc..a2000adea02 100644 --- a/backends/vkeybd/virtual-keyboard-gui.h +++ b/backends/vkeybd/virtual-keyboard-gui.h @@ -99,7 +99,7 @@ private: VirtualKeyboard *_kbd; Rect _kbdBound; Graphics::Surface *_kbdSurface; - OverlayColor _kbdTransparentColor; + uint32 _kbdTransparentColor; Point _dragPoint; bool _drag; @@ -113,7 +113,7 @@ private: const Graphics::Font *_dispFont; int16 _dispX, _dispY; uint _dispI; - OverlayColor _dispForeColor, _dispBackColor; + uint32 _dispForeColor, _dispBackColor; int _lastScreenChanged; int16 _screenW, _screenH; @@ -121,7 +121,7 @@ private: bool _displaying; bool _firstRun; - void setupDisplayArea(Rect &r, OverlayColor forecolor); + void setupDisplayArea(Rect &r, uint32 forecolor); void move(int16 x, int16 y); void moveToDefaultPosition(); void screenChanged(); diff --git a/backends/vkeybd/virtual-keyboard.h b/backends/vkeybd/virtual-keyboard.h index 4ab5ad446d4..3b2b2196bd1 100644 --- a/backends/vkeybd/virtual-keyboard.h +++ b/backends/vkeybd/virtual-keyboard.h @@ -112,11 +112,11 @@ protected: String resolution; String bitmapName; Graphics::Surface *image; - OverlayColor transparentColor; + uint32 transparentColor; ImageMap imageMap; VKEventMap events; Rect displayArea; - OverlayColor displayFontColor; + uint32 displayFontColor; Mode() : image(0) {} ~Mode() { diff --git a/base/commandLine.cpp b/base/commandLine.cpp index e6a3d605650..0de3aa794ff 100644 --- a/base/commandLine.cpp +++ b/base/commandLine.cpp @@ -783,9 +783,8 @@ void upgradeTargets() { printf("Upgrading all your existing targets\n"); - Common::ConfigManager::DomainMap &domains = ConfMan.getGameDomains(); - Common::ConfigManager::DomainMap::iterator iter = domains.begin(); - for (iter = domains.begin(); iter != domains.end(); ++iter) { + Common::ConfigManager::DomainMap::iterator iter = ConfMan.beginGameDomains(); + for (; iter != ConfMan.endGameDomains(); ++iter) { Common::ConfigManager::Domain &dom = iter->_value; Common::String name(iter->_key); Common::String gameid(dom.getVal("gameid")); diff --git a/base/main.cpp b/base/main.cpp index d67c57b11e3..6edf5bd6757 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -428,6 +428,7 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) { // take place after the backend is initiated and the screen has been setup system.getEventManager()->init(); +#ifdef ENABLE_EVENTRECORDER // Directly after initializing the event manager, we will initialize our // event recorder. // @@ -435,6 +436,7 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) { // our event recorder, we might do this at another place. Or even change // the whole API for that ;-). g_eventRec.RegisterEventSource(); +#endif // Now as the event manager is created, setup the keymapper setupKeymapper(system); @@ -472,9 +474,11 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) { // Try to run the game Common::Error result = runGame(plugin, system, specialDebug); +#ifdef ENABLE_EVENTRECORDER // Flush Event recorder file. The recorder does not get reinitialized for next game // which is intentional. Only single game per session is allowed. g_eventRec.deinit(); +#endif #if defined(UNCACHED_PLUGINS) && defined(DYNAMIC_MODULES) // do our best to prevent fragmentation by unloading as soon as we can @@ -527,7 +531,9 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) { GUI::GuiManager::destroy(); Common::ConfigManager::destroy(); Common::DebugManager::destroy(); +#ifdef ENABLE_EVENTRECORDER GUI::EventRecorder::destroy(); +#endif Common::SearchManager::destroy(); #ifdef USE_TRANSLATION Common::TranslationManager::destroy(); diff --git a/base/plugins.h b/base/plugins.h index cb93653d2c2..86ce66f224c 100644 --- a/base/plugins.h +++ b/base/plugins.h @@ -169,7 +169,7 @@ protected: PluginType _type; public: - Plugin() : _pluginObject(0) {} + Plugin() : _pluginObject(0), _type(PLUGIN_TYPE_MAX) {} virtual ~Plugin() { //if (isLoaded()) //unloadPlugin(); diff --git a/base/version.cpp b/base/version.cpp index 75f39fa5840..68ce0a0e1ef 100644 --- a/base/version.cpp +++ b/base/version.cpp @@ -129,4 +129,12 @@ const char *gScummVMFeatures = "" #ifdef USE_FREETYPE2 "FreeType2 " #endif + +#ifdef USE_JPEG + "JPEG " +#endif + +#ifdef USE_PNG + "PNG " +#endif ; diff --git a/common/config-file.cpp b/common/config-file.cpp deleted file mode 100644 index 0ce6dcf0c88..00000000000 --- a/common/config-file.cpp +++ /dev/null @@ -1,392 +0,0 @@ -/* 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. - * - */ - -#include "common/config-file.h" -#include "common/file.h" -#include "common/savefile.h" -#include "common/system.h" -#include "common/textconsole.h" - -namespace Common { - -bool ConfigFile::isValidName(const String &name) { - const char *p = name.c_str(); - while (*p && (isAlnum(*p) || *p == '-' || *p == '_' || *p == '.')) - p++; - return *p == 0; -} - -ConfigFile::ConfigFile() { -} - -ConfigFile::~ConfigFile() { -} - -void ConfigFile::clear() { - _sections.clear(); -} - -bool ConfigFile::loadFromFile(const String &filename) { - File file; - if (file.open(filename)) - return loadFromStream(file); - else - return false; -} - -bool ConfigFile::loadFromSaveFile(const char *filename) { - assert(g_system); - SaveFileManager *saveFileMan = g_system->getSavefileManager(); - SeekableReadStream *loadFile; - - assert(saveFileMan); - if (!(loadFile = saveFileMan->openForLoading(filename))) - return false; - - bool status = loadFromStream(*loadFile); - delete loadFile; - return status; -} - -bool ConfigFile::loadFromStream(SeekableReadStream &stream) { - Section section; - KeyValue kv; - String comment; - int lineno = 0; - - // TODO: Detect if a section occurs multiple times (or likewise, if - // a key occurs multiple times inside one section). - - while (!stream.eos() && !stream.err()) { - lineno++; - - // Read a line - String line = stream.readLine(); - - if (line.size() == 0) { - // Do nothing - } else if (line[0] == '#' || line[0] == ';' || line.hasPrefix("//")) { - // Accumulate comments here. Once we encounter either the start - // of a new section, or a key-value-pair, we associate the value - // of the 'comment' variable with that entity. The semicolon and - // C++-style comments are used for Living Books games in Mohawk. - comment += line; - comment += "\n"; - } else if (line[0] == '(') { - // HACK: The following is a hack added by Kirben to support the - // "map.ini" used in the HE SCUMM game "SPY Fox in Hold the Mustard". - // - // It would be nice if this hack could be restricted to that game, - // but the current design of this class doesn't allow to do that - // in a nice fashion (a "isMustard" parameter is *not* a nice - // solution). - comment += line; - comment += "\n"; - } else if (line[0] == '[') { - // It's a new section which begins here. - const char *p = line.c_str() + 1; - // Get the section name, and check whether it's valid (that - // is, verify that it only consists of alphanumerics, - // periods, dashes and underscores). Mohawk Living Books games - // can have periods in their section names. - while (*p && (isAlnum(*p) || *p == '-' || *p == '_' || *p == '.')) - p++; - - if (*p == '\0') - error("ConfigFile::loadFromStream: missing ] in line %d", lineno); - else if (*p != ']') - error("ConfigFile::loadFromStream: Invalid character '%c' occurred in section name in line %d", *p, lineno); - - // Previous section is finished now, store it. - if (!section.name.empty()) - _sections.push_back(section); - - section.name = String(line.c_str() + 1, p); - section.keys.clear(); - section.comment = comment; - comment.clear(); - - assert(isValidName(section.name)); - } else { - // This line should be a line with a 'key=value' pair, or an empty one. - - // Skip leading whitespaces - const char *t = line.c_str(); - while (isSpace(*t)) - t++; - - // Skip empty lines / lines with only whitespace - if (*t == 0) - continue; - - // If no section has been set, this config file is invalid! - if (section.name.empty()) { - error("ConfigFile::loadFromStream: Key/value pair found outside a section in line %d", lineno); - } - - // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter. - const char *p = strchr(t, '='); - if (!p) - error("Config file buggy: Junk found in line line %d: '%s'", lineno, t); - - // Extract the key/value pair - kv.key = String(t, p); - kv.value = String(p + 1); - - // Trim of spaces - kv.key.trim(); - kv.value.trim(); - - // Store comment - kv.comment = comment; - comment.clear(); - - assert(isValidName(kv.key)); - - section.keys.push_back(kv); - } - } - - // Save last section - if (!section.name.empty()) - _sections.push_back(section); - - return (!stream.err() || stream.eos()); -} - -bool ConfigFile::saveToFile(const String &filename) { - DumpFile file; - if (file.open(filename)) - return saveToStream(file); - else - return false; -} - -bool ConfigFile::saveToSaveFile(const char *filename) { - assert(g_system); - SaveFileManager *saveFileMan = g_system->getSavefileManager(); - WriteStream *saveFile; - - assert(saveFileMan); - if (!(saveFile = saveFileMan->openForSaving(filename))) - return false; - - bool status = saveToStream(*saveFile); - delete saveFile; - return status; -} - -bool ConfigFile::saveToStream(WriteStream &stream) { - for (List
::iterator i = _sections.begin(); i != _sections.end(); ++i) { - // Write out the section comment, if any - if (! i->comment.empty()) { - stream.writeString(i->comment); - } - - // Write out the section name - stream.writeByte('['); - stream.writeString(i->name); - stream.writeByte(']'); - stream.writeByte('\n'); - - // Write out the key/value pairs - for (List::iterator kv = i->keys.begin(); kv != i->keys.end(); ++kv) { - // Write out the comment, if any - if (! kv->comment.empty()) { - stream.writeString(kv->comment); - } - // Write out the key/value pair - stream.writeString(kv->key); - stream.writeByte('='); - stream.writeString(kv->value); - stream.writeByte('\n'); - } - } - - stream.flush(); - return !stream.err(); -} - -void ConfigFile::addSection(const String §ion) { - Section *s = getSection(section); - if (s) - return; - - Section newSection; - newSection.name = section; - _sections.push_back(newSection); -} - -void ConfigFile::removeSection(const String §ion) { - assert(isValidName(section)); - for (List
::iterator i = _sections.begin(); i != _sections.end(); ++i) { - if (section.equalsIgnoreCase(i->name)) { - _sections.erase(i); - return; - } - } -} - -bool ConfigFile::hasSection(const String §ion) const { - assert(isValidName(section)); - const Section *s = getSection(section); - return s != 0; -} - -void ConfigFile::renameSection(const String &oldName, const String &newName) { - assert(isValidName(oldName)); - assert(isValidName(newName)); - - Section *os = getSection(oldName); - const Section *ns = getSection(newName); - if (os) { - // HACK: For now we just print a warning, for more info see the TODO - // below. - if (ns) - warning("ConfigFile::renameSection: Section name \"%s\" already used", newName.c_str()); - else - os->name = newName; - } - // TODO: Check here whether there already is a section with the - // new name. Not sure how to cope with that case, we could: - // - simply remove the existing "newName" section - // - error out - // - merge the two sections "oldName" and "newName" -} - - -bool ConfigFile::hasKey(const String &key, const String §ion) const { - assert(isValidName(key)); - assert(isValidName(section)); - - const Section *s = getSection(section); - if (!s) - return false; - return s->hasKey(key); -} - -void ConfigFile::removeKey(const String &key, const String §ion) { - assert(isValidName(key)); - assert(isValidName(section)); - - Section *s = getSection(section); - if (s) - s->removeKey(key); -} - -bool ConfigFile::getKey(const String &key, const String §ion, String &value) const { - assert(isValidName(key)); - assert(isValidName(section)); - - const Section *s = getSection(section); - if (!s) - return false; - const KeyValue *kv = s->getKey(key); - if (!kv) - return false; - value = kv->value; - return true; -} - -void ConfigFile::setKey(const String &key, const String §ion, const String &value) { - assert(isValidName(key)); - assert(isValidName(section)); - // TODO: Verify that value is valid, too. In particular, it shouldn't - // contain CR or LF... - - Section *s = getSection(section); - if (!s) { - KeyValue newKV; - newKV.key = key; - newKV.value = value; - - Section newSection; - newSection.name = section; - newSection.keys.push_back(newKV); - - _sections.push_back(newSection); - } else { - s->setKey(key, value); - } -} - -const ConfigFile::SectionKeyList ConfigFile::getKeys(const String §ion) const { - const Section *s = getSection(section); - - return s->getKeys(); -} - -ConfigFile::Section *ConfigFile::getSection(const String §ion) { - for (List
::iterator i = _sections.begin(); i != _sections.end(); ++i) { - if (section.equalsIgnoreCase(i->name)) { - return &(*i); - } - } - return 0; -} - -const ConfigFile::Section *ConfigFile::getSection(const String §ion) const { - for (List
::const_iterator i = _sections.begin(); i != _sections.end(); ++i) { - if (section.equalsIgnoreCase(i->name)) { - return &(*i); - } - } - return 0; -} - -bool ConfigFile::Section::hasKey(const String &key) const { - return getKey(key) != 0; -} - -const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const { - for (List::const_iterator i = keys.begin(); i != keys.end(); ++i) { - if (key.equalsIgnoreCase(i->key)) { - return &(*i); - } - } - return 0; -} - -void ConfigFile::Section::setKey(const String &key, const String &value) { - for (List::iterator i = keys.begin(); i != keys.end(); ++i) { - if (key.equalsIgnoreCase(i->key)) { - i->value = value; - return; - } - } - - KeyValue newKV; - newKV.key = key; - newKV.value = value; - keys.push_back(newKV); -} - -void ConfigFile::Section::removeKey(const String &key) { - for (List::iterator i = keys.begin(); i != keys.end(); ++i) { - if (key.equalsIgnoreCase(i->key)) { - keys.erase(i); - return; - } - } -} - -} // End of namespace Common diff --git a/common/config-file.h b/common/config-file.h deleted file mode 100644 index 8bba851110b..00000000000 --- a/common/config-file.h +++ /dev/null @@ -1,140 +0,0 @@ -/* 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. - * - */ - -#ifndef COMMON_CONFIG_FILE_H -#define COMMON_CONFIG_FILE_H - -#include "common/hash-str.h" -#include "common/list.h" -#include "common/str.h" - -namespace Common { - -class SeekableReadStream; -class WriteStream; - -/** - * This class allows reading/writing INI style config files. - * It is used by the ConfigManager for storage, but can also - * be used by other code if it needs to read/write custom INI - * files. - * - * Lines starting with a '#' are ignored (i.e. treated as comments). - * Some effort is made to preserve comments, though. - * - * This class makes no attempts to provide fast access to key/value pairs. - * In particular, it stores all sections and k/v pairs in lists, not - * in dictionaries/maps. This makes it very easy to read/write the data - * from/to files, but of course is not appropriate for fast access. - * The main reason is that this class is indeed geared toward doing precisely - * that! - * If you need fast access to the game config, use higher level APIs, like the - * one provided by ConfigManager. - */ -class ConfigFile { -public: - struct KeyValue { - String key; - String value; - String comment; - }; - - typedef List SectionKeyList; - - /** A section in a config file. I.e. corresponds to something like this: - * [mySection] - * key=value - * - * Comments are also stored, to keep users happy who like editing their - * config files manually. - */ - struct Section { - String name; - List keys; - String comment; - - bool hasKey(const String &key) const; - const KeyValue* getKey(const String &key) const; - void setKey(const String &key, const String &value); - void removeKey(const String &key); - const SectionKeyList getKeys() const { return keys; } - }; - - typedef List
SectionList; - -public: - ConfigFile(); - ~ConfigFile(); - - // TODO: Maybe add a copy constructor etc.? - - /** - * Check whether the given string is a valid section or key name. - * For that, it must only consist of letters, numbers, dashes and - * underscores. In particular, white space and "#", "=", "[", "]" - * are not valid! - */ - static bool isValidName(const String &name); - - /** Reset everything stored in this config file. */ - void clear(); - - bool loadFromFile(const String &filename); - bool loadFromSaveFile(const char *filename); - bool loadFromStream(SeekableReadStream &stream); - bool saveToFile(const String &filename); - bool saveToSaveFile(const char *filename); - bool saveToStream(WriteStream &stream); - - bool hasSection(const String §ion) const; - void addSection(const String §ion); - void removeSection(const String §ion); - void renameSection(const String &oldName, const String &newName); - - bool hasKey(const String &key, const String §ion) const; - bool getKey(const String &key, const String §ion, String &value) const; - void setKey(const String &key, const String §ion, const String &value); - void removeKey(const String &key, const String §ion); - - const SectionList getSections() const { return _sections; } - const SectionKeyList getKeys(const String §ion) const; - - void listKeyValues(StringMap &kv); - -private: - SectionList _sections; - - Section *getSection(const String §ion); - const Section *getSection(const String §ion) const; -}; - -/* -- ConfigMan owns a config file -- allow direct access to that config file (for the launcher) -- simplify and unify the regular ConfigMan API in exchange - - -*/ - -} // End of namespace Common - -#endif diff --git a/common/config-manager.h b/common/config-manager.h index d43a7bec517..6bf56749c54 100644 --- a/common/config-manager.h +++ b/common/config-manager.h @@ -24,7 +24,6 @@ #define COMMON_CONFIG_MANAGER_H #include "common/array.h" -//#include "common/config-file.h" #include "common/hashmap.h" #include "common/singleton.h" #include "common/str.h" @@ -47,12 +46,33 @@ class ConfigManager : public Singleton { public: - class Domain : public StringMap { + class Domain { private: + StringMap _entries; StringMap _keyValueComments; String _domainComment; public: + typedef StringMap::const_iterator const_iterator; + const_iterator begin() const { return _entries.begin(); } + const_iterator end() const { return _entries.end(); } + + bool empty() const { return _entries.empty(); } + + bool contains(const String &key) const { return _entries.contains(key); } + + String &operator[](const String &key) { return _entries[key]; } + const String &operator[](const String &key) const { return _entries[key]; } + + void setVal(const String &key, const String &value) { _entries.setVal(key, value); } + + String &getVal(const String &key) { return _entries.getVal(key); } + const String &getVal(const String &key) const { return _entries.getVal(key); } + + void clear() { _entries.clear(); } + + void erase(const String &key) { _entries.erase(key); } + void setDomainComment(const String &comment); const String &getDomainComment() const; @@ -143,7 +163,8 @@ public: bool hasMiscDomain(const String &domName) const; const DomainMap & getGameDomains() const { return _gameDomains; } - DomainMap & getGameDomains() { return _gameDomains; } + DomainMap::iterator beginGameDomains() { return _gameDomains.begin(); } + DomainMap::iterator endGameDomains() { return _gameDomains.end(); } static void defragment(); // move in memory to reduce fragmentation void copyFrom(ConfigManager &source); diff --git a/common/math.h b/common/math.h index b85ec0d22a9..ba137101e4a 100644 --- a/common/math.h +++ b/common/math.h @@ -52,14 +52,6 @@ #endif #endif -#ifndef M_SQRT1_2 - #define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ -#endif - -#ifndef M_PI - #define M_PI 3.14159265358979323846 -#endif - #ifndef FLT_MIN #define FLT_MIN 1E-37 #endif diff --git a/common/module.mk b/common/module.mk index 41db8b98140..11212e9081e 100644 --- a/common/module.mk +++ b/common/module.mk @@ -2,7 +2,6 @@ MODULE := common MODULE_OBJS := \ archive.o \ - config-file.o \ config-manager.o \ debug.o \ streamdebug.o \ @@ -13,6 +12,7 @@ MODULE_OBJS := \ fs.o \ gui_options.o \ hashmap.o \ + ini-file.o \ language.o \ macresman.o \ memorypool.o \ diff --git a/common/random.cpp b/common/random.cpp index a74ab831ea7..de1269b4856 100644 --- a/common/random.cpp +++ b/common/random.cpp @@ -30,8 +30,12 @@ RandomSource::RandomSource(const String &name) { // Use system time as RNG seed. Normally not a good idea, if you are using // a RNG for security purposes, but good enough for our purposes. assert(g_system); - uint32 seed = g_eventRec.getRandomSeed(name); - setSeed(seed); + +#ifdef ENABLE_EVENTRECORDER + setSeed(g_eventRec.getRandomSeed(name)); +#else + setSeed(g_system->getMillis()); +#endif } void RandomSource::setSeed(uint32 seed) { diff --git a/common/scummsys.h b/common/scummsys.h index 3fdabe324dd..1afeed48334 100644 --- a/common/scummsys.h +++ b/common/scummsys.h @@ -23,6 +23,10 @@ #ifndef COMMON_SCUMMSYS_H #define COMMON_SCUMMSYS_H +#ifndef __has_feature // Optional of course. + #define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + // This is a convenience macro to test whether the compiler used is a GCC // version, which is at least major.minor. #define GCC_ATLEAST(major, minor) (defined(__GNUC__) && (__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor)))) @@ -144,6 +148,63 @@ #endif #endif +// The following math constants are usually defined by the system math.h header, but +// they are not part of the ANSI C++ standards and so can NOT be relied upon to be +// present i.e. when -std=c++11 is passed to GCC, enabling strict ANSI compliance. +// As we rely on these being present, we define them if they are not set. + +#ifndef M_E + #define M_E 2.7182818284590452354 /* e */ +#endif + +#ifndef M_LOG2E + #define M_LOG2E 1.4426950408889634074 /* log_2 e */ +#endif + +#ifndef M_LOG10E + #define M_LOG10E 0.43429448190325182765 /* log_10 e */ +#endif + +#ifndef M_LN2 + #define M_LN2 0.69314718055994530942 /* log_e 2 */ +#endif + +#ifndef M_LN10 + #define M_LN10 2.30258509299404568402 /* log_e 10 */ +#endif + +#ifndef M_PI + #define M_PI 3.14159265358979323846 /* pi */ +#endif + +#ifndef M_PI_2 + #define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif + +#ifndef M_PI_4 + #define M_PI_4 0.78539816339744830962 /* pi/4 */ +#endif + +#ifndef M_1_PI + #define M_1_PI 0.31830988618379067154 /* 1/pi */ +#endif + +#ifndef M_2_PI + #define M_2_PI 0.63661977236758134308 /* 2/pi */ +#endif + +#ifndef M_2_SQRTPI + #define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */ +#endif + +#ifndef M_SQRT2 + #define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif + +#ifndef M_SQRT1_2 + #define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ +#endif + // Include our C++11 compatability header for pre-C++11 compilers. #if __cplusplus < 201103L #include "common/c++11-compat.h" diff --git a/common/util.h b/common/util.h index 4ca1c429299..392ced1ffe3 100644 --- a/common/util.h +++ b/common/util.h @@ -41,10 +41,10 @@ #undef MAX #endif -template inline T ABS (T x) { return (x>=0) ? x : -x; } -template inline T MIN (T a, T b) { return (a inline T MAX (T a, T b) { return (a>b) ? a : b; } -template inline T CLIP (T v, T amin, T amax) +template inline T ABS(T x) { return (x >= 0) ? x : -x; } +template inline T MIN(T a, T b) { return (a < b) ? a : b; } +template inline T MAX(T a, T b) { return (a > b) ? a : b; } +template inline T CLIP(T v, T amin, T amax) { if (v < amin) return amin; else if (v > amax) return amax; else return v; } /** diff --git a/configure b/configure index 119aab5684f..fe007443e8b 100755 --- a/configure +++ b/configure @@ -104,7 +104,7 @@ _srcdir=`dirname $0` # #ResidualVM defaults: mpeg2=auto, faad=no, opengles=no, vorbis=no, tremor=no # mt32emu=no, translation=no, flac=no, seq_midi=no, snd_io=no, timidity=no, png=no -# theoradec=no, fluidsynth=no, opengles2=no, eventrec=no +# theoradec=no, fluidsynth=no, opengles2=no, eventrec=no, jpeg=no # # Default lib behaviour yes/no/auto _vorbis=no @@ -117,10 +117,9 @@ _seq_midi=no _sndio=no _timidity=no _zlib=auto -_sparkle=auto -_png=no _mpeg2=auto _sparkle=auto +_jpeg=no _png=no _theoradec=no _faad=no @@ -174,7 +173,7 @@ _windres=windres _stagingpath="staging" _win32path="C:/residualvm" _aos4path="Games:ResidualVM" -_staticlibpath=/opt/local +_staticlibpath= _sdlconfig=sdl-config _freetypeconfig=freetype-config _sdlpath="$PATH" @@ -198,6 +197,7 @@ add_feature faad "libfaad" "_faad" add_feature flac "FLAC" "_flac" add_feature freetype2 "FreeType2" "_freetype2" add_feature mad "MAD" "_mad" +add_feature jpeg "JPEG" "_jpeg" add_feature png "PNG" "_png" add_feature theoradec "libtheoradec" "_theoradec" add_feature vorbis "Vorbis file support" "_vorbis _tremor" @@ -919,6 +919,9 @@ Optional Libraries: --disable-opengl disable OpenGL (ES) support [autodetect] + --with-jpeg-prefix=DIR Prefix where libjpeg is installed (optional) + --disable-jpeg disable JPEG decoder [autodetect] + --with-png-prefix=DIR Prefix where libpng is installed (optional) --disable-png disable PNG decoder [autodetect] @@ -998,6 +1001,8 @@ for ac_option in $@; do --disable-nasm) _nasm=no ;; --enable-mpeg2) _mpeg2=yes ;; --disable-mpeg2) _mpeg2=no ;; + --disable-jpeg) _jpeg=no ;; + --enable-jpeg) _jpeg=yes ;; --disable-png) _png=no ;; --enable-png) _png=yes ;; --disable-theoradec) _theoradec=no ;; @@ -1082,6 +1087,11 @@ for ac_option in $@; do MAD_CFLAGS="-I$arg/include" MAD_LIBS="-L$arg/lib" ;; + --with-jpeg-prefix=*) + arg=`echo $ac_option | cut -d '=' -f 2` + JPEG_CFLAGS="-I$arg/include" + JPEG_LIBS="-L$arg/lib" + ;; --with-png-prefix=*) arg=`echo $ac_option | cut -d '=' -f 2` PNG_CFLAGS="-I$arg/include" @@ -1243,7 +1253,7 @@ get_system_exe_extension $guessed_host NATIVEEXEEXT=$_exeext case $_host in -android | android-v7a) +android | android-v7a | ouya) _host_os=android _host_cpu=arm _host_alias=arm-linux-androideabi @@ -2035,6 +2045,12 @@ case $_host_os in CXXFLAGS="$CXXFLAGS -mfpu=vfp" LDFLAGS="$LDFLAGS -Wl,--fix-cortex-a8" ;; + ouya) + CXXFLAGS="$CXXFLAGS -march=armv7-a" + CXXFLAGS="$CXXFLAGS -mtune=cortex-a9" + CXXFLAGS="$CXXFLAGS -mfloat-abi=softfp" + CXXFLAGS="$CXXFLAGS -mfpu=neon" + ;; esac # ResidualVM use newer NDK CXXFLAGS="$CXXFLAGS --sysroot=$ANDROID_NDK/platforms/android-8/arch-arm" @@ -2102,6 +2118,24 @@ case $_host_os in LDFLAGS="-L${macport_prefix}/lib $LDFLAGS" CXXFLAGS="-I${macport_prefix}/include $CXXFLAGS" + + if test -z "$_staticlibpath"; then + _staticlibpath=${macport_prefix} + echo "Set staticlib-prefix to ${_staticlibpath}" + fi + fi + # If _staticlibpath is not set yet try first /sw (fink) then /usr/local + # (the macports case is handled above). + if test -z "$_staticlibpath"; then + if test -d "/sw"; then + _staticlibpath=/sw + echo "Set staticlib-prefix to ${_staticlibpath}" + elif test -d "/usr/local"; then + _staticlibpath=/usr/local + echo "Set staticlib-prefix to ${_staticlibpath}" + else + echo "Could not determine prefix for static libraries" + fi fi ;; dreamcast) @@ -2307,7 +2341,7 @@ if test -n "$_host"; then # Cross-compiling mode - add your target here if needed echo "Cross-compiling to $_host" case "$_host" in - android | android-v7a) + android | android-v7a | ouya) # we link a .so as default LDFLAGS="$LDFLAGS -shared" LDFLAGS="$LDFLAGS -Wl,-Bsymbolic,--no-undefined" @@ -3340,6 +3374,32 @@ fi define_in_config_h_if_yes "$_alsa" 'USE_ALSA' echo "$_alsa" +# +# Check for libjpeg +# +echocheck "libjpeg >= v6b" +if test "$_jpeg" = auto ; then + _jpeg=no + cat > $TMPC << EOF +#include +#include +int main(void) { +#if JPEG_LIB_VERSION >= 62 +#else + syntax error +#endif + return 0; +} +EOF + cc_check $JPEG_CFLAGS $JPEG_LIBS -ljpeg && _jpeg=yes +fi +if test "$_jpeg" = yes ; then + LIBS="$LIBS $JPEG_LIBS -ljpeg" + INCLUDES="$INCLUDES $JPEG_CFLAGS" +fi +define_in_config_if_yes "$_jpeg" 'USE_JPEG' +echo "$_jpeg" + # # Check for PNG # @@ -3356,10 +3416,10 @@ int main(void) { return 0; } EOF - cc_check $PNG_CFLAGS $PNG_LIBS -lpng && _png=yes + cc_check $PNG_CFLAGS $PNG_LIBS -lpng -lz && _png=yes fi if test "$_png" = yes ; then - LIBS="$LIBS $PNG_LIBS -lpng" + LIBS="$LIBS $PNG_LIBS -lpng -lz" INCLUDES="$INCLUDES $PNG_CFLAGS" fi define_in_config_if_yes "$_png" 'USE_PNG' diff --git a/devtools/create_project/codeblocks.cpp b/devtools/create_project/codeblocks.cpp index 3458ca5a192..ec003df2d5e 100644 --- a/devtools/create_project/codeblocks.cpp +++ b/devtools/create_project/codeblocks.cpp @@ -64,6 +64,11 @@ std::string processLibraryName(std::string name) { if (pos != std::string::npos) return name.replace(pos, 7, ""); + // Remove "-static" in lib name + pos = name.find("-static"); + if (pos != std::string::npos) + return name.replace(pos, 7, ""); + // Replace "zlib" by "libz" if (name == "zlib") return "libz"; diff --git a/devtools/create_project/create_project.cpp b/devtools/create_project/create_project.cpp index 2c0da107bdf..195ef25d228 100644 --- a/devtools/create_project/create_project.cpp +++ b/devtools/create_project/create_project.cpp @@ -39,7 +39,7 @@ #include #include - +#include #include #include #include @@ -845,6 +845,7 @@ const Feature s_features[] = { { "mpeg2", "USE_MPEG2", "libmpeg2", true, "MPEG-2 support" }, { "theora", "USE_THEORADEC", "libtheora_static", false, "Theora decoding support" }, {"freetype", "USE_FREETYPE2", "freetype", true, "FreeType support" }, + { "jpeg", "USE_JPEG", "jpeg-static", true, "libjpeg support" }, // Feature flags { "bink", "USE_BINK", "", true, "Bink video support" }, @@ -966,6 +967,10 @@ bool producesObjectFile(const std::string &fileName) { return false; } +std::string toString(int num) { + return static_cast(&(std::ostringstream() << num))->str(); +} + /** * Checks whether the give file in the specified directory is present in the given * file list. diff --git a/devtools/create_project/create_project.h b/devtools/create_project/create_project.h index d0f2db364c2..2f27cc2f611 100644 --- a/devtools/create_project/create_project.h +++ b/devtools/create_project/create_project.h @@ -23,6 +23,10 @@ #ifndef TOOLS_CREATE_PROJECT_H #define TOOLS_CREATE_PROJECT_H +#ifndef __has_feature // Optional of course. + #define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + #include #include #include @@ -302,6 +306,14 @@ void splitFilename(const std::string &fileName, std::string &name, std::string & */ bool producesObjectFile(const std::string &fileName); +/** +* Convert an integer to string +* +* @param num the integer to convert +* @return string representation of the number +*/ +std::string toString(int num); + /** * Structure representing a file tree. This contains two * members: name and children. "name" holds the name of diff --git a/devtools/create_project/msbuild.cpp b/devtools/create_project/msbuild.cpp index 23bf1bc28a0..0d68b2e9c9f 100644 --- a/devtools/create_project/msbuild.cpp +++ b/devtools/create_project/msbuild.cpp @@ -67,10 +67,10 @@ inline void outputConfiguration(std::ostream &project, const std::string &config "\t\t\n"; } -inline void outputConfigurationType(const BuildSetup &setup, std::ostream &project, const std::string &name, const std::string &config, int version) { +inline void outputConfigurationType(const BuildSetup &setup, std::ostream &project, const std::string &name, const std::string &config, std::string toolset) { project << "\t\n" "\t\t" << ((name == setup.projectName || setup.devTools || setup.tests) ? "Application" : "StaticLibrary") << "\n" - "\t\tv" << version << "0\n" + "\t\t" << toolset << "\n" "\t\n"; } @@ -98,6 +98,8 @@ void MSBuildProvider::createProjectFile(const std::string &name, const std::stri outputConfiguration(project, "Debug", "x64"); outputConfiguration(project, "Analysis", "Win32"); outputConfiguration(project, "Analysis", "x64"); + outputConfiguration(project, "LLVM", "Win32"); + outputConfiguration(project, "LLVM", "x64"); outputConfiguration(project, "Release", "Win32"); outputConfiguration(project, "Release", "x64"); @@ -108,18 +110,23 @@ void MSBuildProvider::createProjectFile(const std::string &name, const std::stri "\t\t{" << uuid << "}\n" "\t\t" << name << "\n" "\t\tWin32Proj\n" - "\t\t$(VCTargetsPath" << _version << ")\n" + "\t\t$(VCTargetsPath" << _version << ")\n" "\t\n"; // Shared configuration project << "\t\n"; - outputConfigurationType(setup, project, name, "Release|Win32", _version); - outputConfigurationType(setup, project, name, "Analysis|Win32", _version); - outputConfigurationType(setup, project, name, "Debug|Win32", _version); - outputConfigurationType(setup, project, name, "Release|x64", _version); - outputConfigurationType(setup, project, name, "Analysis|x64", _version); - outputConfigurationType(setup, project, name, "Debug|x64", _version); + std::string version = "v" + toString(_version) + "0"; + std::string llvm = "LLVM-vs" + toString(getVisualStudioVersion()); + + outputConfigurationType(setup, project, name, "Release|Win32", version); + outputConfigurationType(setup, project, name, "Analysis|Win32", version); + outputConfigurationType(setup, project, name, "LLVM|Win32", llvm); + outputConfigurationType(setup, project, name, "Debug|Win32", version); + outputConfigurationType(setup, project, name, "Release|x64", version); + outputConfigurationType(setup, project, name, "LLVM|x64", llvm); + outputConfigurationType(setup, project, name, "Analysis|x64", version); + outputConfigurationType(setup, project, name, "Debug|x64", version); project << "\t\n" "\t\n" @@ -127,20 +134,24 @@ void MSBuildProvider::createProjectFile(const std::string &name, const std::stri outputProperties(project, "Release|Win32", setup.projectDescription + "_Release.props"); outputProperties(project, "Analysis|Win32", setup.projectDescription + "_Analysis.props"); + outputProperties(project, "LLVM|Win32", setup.projectDescription + "_LLVM.props"); outputProperties(project, "Debug|Win32", setup.projectDescription + "_Debug.props"); outputProperties(project, "Release|x64", setup.projectDescription + "_Release64.props"); outputProperties(project, "Analysis|x64", setup.projectDescription + "_Analysis64.props"); + outputProperties(project, "LLVM|x64", setup.projectDescription + "_LLVM64.props"); outputProperties(project, "Debug|x64", setup.projectDescription + "_Debug64.props"); project << "\t\n"; // Project-specific settings (analysis uses debug properties) - outputProjectSettings(project, name, setup, false, true, false); - outputProjectSettings(project, name, setup, false, true, true); - outputProjectSettings(project, name, setup, true, true, false); - outputProjectSettings(project, name, setup, false, false, false); - outputProjectSettings(project, name, setup, false, false, true); - outputProjectSettings(project, name, setup, true, false, false); + outputProjectSettings(project, name, setup, false, true, "Debug"); + outputProjectSettings(project, name, setup, false, true, "Analysis"); + outputProjectSettings(project, name, setup, false, true, "LLVM"); + outputProjectSettings(project, name, setup, true, true, "Release"); + outputProjectSettings(project, name, setup, false, false, "Debug"); + outputProjectSettings(project, name, setup, false, false, "Analysis"); + outputProjectSettings(project, name, setup, false, false, "LLVM"); + outputProjectSettings(project, name, setup, true, false, "Release"); // Files std::string modulePath; @@ -255,9 +266,7 @@ void MSBuildProvider::writeReferences(const BuildSetup &setup, std::ofstream &ou output << "\t\n"; } -void MSBuildProvider::outputProjectSettings(std::ofstream &project, const std::string &name, const BuildSetup &setup, bool isRelease, bool isWin32, bool enableAnalysis) { - const std::string configuration = (enableAnalysis ? "Analysis" : (isRelease ? "Release" : "Debug")); - +void MSBuildProvider::outputProjectSettings(std::ofstream &project, const std::string &name, const BuildSetup &setup, bool isRelease, bool isWin32, std::string configuration) { // Check for project-specific warnings: std::map::iterator warningsIterator = _projectWarnings.find(name); bool enableLanguageExtensions = find(_enableLanguageExtensions.begin(), _enableLanguageExtensions.end(), name) != _enableLanguageExtensions.end(); @@ -382,13 +391,12 @@ void MSBuildProvider::outputGlobalPropFile(const BuildSetup &setup, std::ofstrea properties.flush(); } -void MSBuildProvider::createBuildProp(const BuildSetup &setup, bool isRelease, bool isWin32, bool enableAnalysis) { - const std::string outputType = (enableAnalysis ? "Analysis" : (isRelease ? "Release" : "Debug")); +void MSBuildProvider::createBuildProp(const BuildSetup &setup, bool isRelease, bool isWin32, std::string configuration) { const std::string outputBitness = (isWin32 ? "32" : "64"); - std::ofstream properties((setup.outputDir + '/' + setup.projectDescription + "_" + outputType + (isWin32 ? "" : "64") + getPropertiesExtension()).c_str()); + std::ofstream properties((setup.outputDir + '/' + setup.projectDescription + "_" + configuration + (isWin32 ? "" : "64") + getPropertiesExtension()).c_str()); if (!properties) - error("Could not open \"" + setup.outputDir + '/' + setup.projectDescription + "_" + outputType + (isWin32 ? "" : "64") + getPropertiesExtension() + "\" for writing"); + error("Could not open \"" + setup.outputDir + '/' + setup.projectDescription + "_" + configuration + (isWin32 ? "" : "64") + getPropertiesExtension() + "\" for writing"); properties << "\n" "= 12 ? _version : 4) << ".0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n" @@ -396,7 +404,7 @@ void MSBuildProvider::createBuildProp(const BuildSetup &setup, bool isRelease, b "\t\t\n" "\t\n" "\t\n" - "\t\t<_PropertySheetDisplayName>" << setup.projectDescription << "_" << outputType << outputBitness << "\n" + "\t\t<_PropertySheetDisplayName>" << setup.projectDescription << "_" << configuration << outputBitness << "\n" "\t\t" << (isRelease ? "false" : "true") << "\n" "\t\n" "\t\n" @@ -405,27 +413,31 @@ void MSBuildProvider::createBuildProp(const BuildSetup &setup, bool isRelease, b if (isRelease) { properties << "\t\t\ttrue\n" "\t\t\ttrue\n" - "\t\t\tWIN32;DISABLE_GUI_BUILTIN_THEME;RELEASE_BUILD;%(PreprocessorDefinitions)\n" + "\t\t\tWIN32;RELEASE_BUILD;%(PreprocessorDefinitions)\n" "\t\t\ttrue\n" "\t\t\tfalse\n" "\t\t\t\n" "\t\t\tMultiThreaded\n" - "\t\t\t" << (enableAnalysis ? "true" : "false") << "\n" + "\t\t\t" << (configuration == "Analysis" ? "true" : "false") << "\n" "\t\t\n" "\t\t\n" "\t\t\t%(IgnoreSpecificDefaultLibraries)\n" "\t\t\ttrue\n"; } else { properties << "\t\t\tDisabled\n" - "\t\t\tWIN32;DISABLE_GUI_BUILTIN_THEME;%(PreprocessorDefinitions)\n" + "\t\t\tWIN32;" << (configuration == "LLVM" ? "_CRT_SECURE_NO_WARNINGS;" : "") << "%(PreprocessorDefinitions)\n" "\t\t\ttrue\n" "\t\t\tEnableFastChecks\n" "\t\t\tMultiThreadedDebug\n" "\t\t\ttrue\n" "\t\t\tfalse\n" "\t\t\t" << (isWin32 ? "EditAndContinue" : "ProgramDatabase") << "\n" // For x64 format Edit and continue is not supported, thus we default to Program Database - "\t\t\t" << (enableAnalysis ? "true" : "false") << "\n" - "\t\t\n" + "\t\t\t" << (configuration == "Analysis" ? "true" : "false") << "\n"; + + if (configuration == "LLVM") + properties << "\t\t\t-Wno-microsoft -Wno-long-long -Wno-multichar -Wno-unknown-pragmas -Wno-reorder -Wpointer-arith -Wcast-qual -Wshadow -Wnon-virtual-dtor -Wwrite-strings -Wno-conversion -Wno-shorten-64-to-32 -Wno-sign-compare -Wno-four-char-constants -Wno-nested-anon-types %(AdditionalOptions)\n"; + + properties << "\t\t\n" "\t\t\n" "\t\t\ttrue\n" "\t\t\tfalse\n" @@ -481,7 +493,7 @@ void MSBuildProvider::writeFileListToProject(const FileNode &dir, std::ofstream // Deal with duplicated file names if (isDuplicate) { projectFile << "\t\t\n" - "\t\t\t$(IntDir)" << (*entry).prefix << "%(Filename).obj\n"; + "\t\t\t$(IntDir)" << (*entry).prefix << "%(Filename).obj\n"; projectFile << "\t\t\n"; } else { diff --git a/devtools/create_project/msbuild.h b/devtools/create_project/msbuild.h index fa6667741a7..829657beffd 100644 --- a/devtools/create_project/msbuild.h +++ b/devtools/create_project/msbuild.h @@ -35,7 +35,7 @@ protected: void createProjectFile(const std::string &name, const std::string &uuid, const BuildSetup &setup, const std::string &moduleDir, const StringList &includeList, const StringList &excludeList); - void outputProjectSettings(std::ofstream &project, const std::string &name, const BuildSetup &setup, bool isRelease, bool isWin32, bool enableAnalysis); + void outputProjectSettings(std::ofstream &project, const std::string &name, const BuildSetup &setup, bool isRelease, bool isWin32, std::string configuration); void writeFileListToProject(const FileNode &dir, std::ofstream &projectFile, const int indentation, const StringList &duplicate, const std::string &objPrefix, const std::string &filePrefix); @@ -44,7 +44,7 @@ protected: void outputGlobalPropFile(const BuildSetup &setup, std::ofstream &properties, int bits, const StringList &defines, const std::string &prefix, bool runBuildEvents); - void createBuildProp(const BuildSetup &setup, bool isRelease, bool isWin32, bool enableAnalysis); + void createBuildProp(const BuildSetup &setup, bool isRelease, bool isWin32, std::string configuration); const char *getProjectExtension(); const char *getPropertiesExtension(); diff --git a/devtools/create_project/msvc.cpp b/devtools/create_project/msvc.cpp index 2fedadcba55..cdd2d8a7c16 100644 --- a/devtools/create_project/msvc.cpp +++ b/devtools/create_project/msvc.cpp @@ -79,9 +79,11 @@ void MSVCProvider::createWorkspace(const BuildSetup &setup) { "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n" "\t\tDebug|Win32 = Debug|Win32\n" "\t\tAnalysis|Win32 = Analysis|Win32\n" + "\t\tLLVM|Win32 = LLVM|Win32\n" "\t\tRelease|Win32 = Release|Win32\n" "\t\tDebug|x64 = Debug|x64\n" "\t\tAnalysis|x64 = Analysis|x64\n" + "\t\tLLVM|x64 = LLVM|x64\n" "\t\tRelease|x64 = Release|x64\n" "\tEndGlobalSection\n" "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n"; @@ -91,12 +93,16 @@ void MSVCProvider::createWorkspace(const BuildSetup &setup) { "\t\t{" << i->second << "}.Debug|Win32.Build.0 = Debug|Win32\n" "\t\t{" << i->second << "}.Analysis|Win32.ActiveCfg = Analysis|Win32\n" "\t\t{" << i->second << "}.Analysis|Win32.Build.0 = Analysis|Win32\n" + "\t\t{" << i->second << "}.LLVM|Win32.ActiveCfg = LLVM|Win32\n" + "\t\t{" << i->second << "}.LLVM|Win32.Build.0 = LLVM|Win32\n" "\t\t{" << i->second << "}.Release|Win32.ActiveCfg = Release|Win32\n" "\t\t{" << i->second << "}.Release|Win32.Build.0 = Release|Win32\n" "\t\t{" << i->second << "}.Debug|x64.ActiveCfg = Debug|x64\n" "\t\t{" << i->second << "}.Debug|x64.Build.0 = Debug|x64\n" "\t\t{" << i->second << "}.Analysis|x64.ActiveCfg = Analysis|x64\n" "\t\t{" << i->second << "}.Analysis|x64.Build.0 = Analysis|x64\n" + "\t\t{" << i->second << "}.LLVM|x64.ActiveCfg = LLVM|x64\n" + "\t\t{" << i->second << "}.LLVM|x64.Build.0 = LLVM|x64\n" "\t\t{" << i->second << "}.Release|x64.ActiveCfg = Release|x64\n" "\t\t{" << i->second << "}.Release|x64.Build.0 = Release|x64\n"; } @@ -114,12 +120,14 @@ void MSVCProvider::createOtherBuildFiles(const BuildSetup &setup) { // Create the configuration property files (for Debug and Release with 32 and 64bits versions) // Note: we use the debug properties for the analysis configuration - createBuildProp(setup, true, false, false); - createBuildProp(setup, true, true, false); - createBuildProp(setup, false, false, false); - createBuildProp(setup, false, false, true); - createBuildProp(setup, false, true, false); - createBuildProp(setup, false, true, true); + createBuildProp(setup, true, false, "Release"); + createBuildProp(setup, true, true, "Release"); + createBuildProp(setup, false, false, "Debug"); + createBuildProp(setup, false, true, "Debug"); + createBuildProp(setup, false, false, "Analysis"); + createBuildProp(setup, false, true, "Analysis"); + createBuildProp(setup, false, false, "LLVM"); + createBuildProp(setup, false, true, "LLVM"); } void MSVCProvider::createGlobalProp(const BuildSetup &setup) { diff --git a/devtools/create_project/msvc.h b/devtools/create_project/msvc.h index b9b93fe1098..3a3eb980343 100644 --- a/devtools/create_project/msvc.h +++ b/devtools/create_project/msvc.h @@ -70,7 +70,7 @@ protected: * @param isWin32 Bitness of property file * @param enableAnalysis PREfast support */ - virtual void createBuildProp(const BuildSetup &setup, bool isRelease, bool isWin32, bool enableAnalysis) = 0; + virtual void createBuildProp(const BuildSetup &setup, bool isRelease, bool isWin32, std::string configuration) = 0; /** * Get the file extension for property files diff --git a/devtools/create_project/visualstudio.cpp b/devtools/create_project/visualstudio.cpp index 23225d34356..438e0772f9d 100644 --- a/devtools/create_project/visualstudio.cpp +++ b/devtools/create_project/visualstudio.cpp @@ -92,6 +92,7 @@ void VisualStudioProvider::createProjectFile(const std::string &name, const std: // Win32 outputConfiguration(project, setup, libraries, "Debug", "Win32", "", true); outputConfiguration(project, setup, libraries, "Analysis", "Win32", "", true); + outputConfiguration(project, setup, libraries, "LLVM", "Win32", "", true); outputConfiguration(project, setup, libraries, "Release", "Win32", "", true); // x64 @@ -100,6 +101,7 @@ void VisualStudioProvider::createProjectFile(const std::string &name, const std: // libraries list created for IA-32. If that changes in the future, we need to adjust this part! outputConfiguration(project, setup, libraries, "Debug", "x64", "64", false); outputConfiguration(project, setup, libraries, "Analysis", "x64", "64", false); + outputConfiguration(project, setup, libraries, "LLVM", "Win32", "64", false); outputConfiguration(project, setup, libraries, "Release", "x64", "64", false); } else { @@ -119,9 +121,11 @@ void VisualStudioProvider::createProjectFile(const std::string &name, const std: // Win32 outputConfiguration(setup, project, toolConfig, "Debug", "Win32", ""); outputConfiguration(setup, project, toolConfig, "Analysis", "Win32", ""); + outputConfiguration(setup, project, toolConfig, "LLVM", "Win32", ""); outputConfiguration(setup, project, toolConfig, "Release", "Win32", ""); outputConfiguration(setup, project, toolConfig, "Debug", "x64", "64"); outputConfiguration(setup, project, toolConfig, "Analysis", "x64", "64"); + outputConfiguration(setup, project, toolConfig, "LLVM", "x64", "64"); outputConfiguration(setup, project, toolConfig, "Release", "x64", "64"); } @@ -265,19 +269,18 @@ void VisualStudioProvider::outputGlobalPropFile(const BuildSetup &setup, std::of properties.flush(); } -void VisualStudioProvider::createBuildProp(const BuildSetup &setup, bool isRelease, bool isWin32, bool enableAnalysis) { - const std::string outputType = (enableAnalysis ? "Analysis" : (isRelease ? "Release" : "Debug")); +void VisualStudioProvider::createBuildProp(const BuildSetup &setup, bool isRelease, bool isWin32, std::string configuration) { const std::string outputBitness = (isWin32 ? "32" : "64"); - std::ofstream properties((setup.outputDir + '/' + setup.projectDescription + "_" + outputType + (isWin32 ? "" : "64") + getPropertiesExtension()).c_str()); + std::ofstream properties((setup.outputDir + '/' + setup.projectDescription + "_" + configuration + (isWin32 ? "" : "64") + getPropertiesExtension()).c_str()); if (!properties) - error("Could not open \"" + setup.outputDir + '/' + setup.projectDescription + "_" + outputType + (isWin32 ? "" : "64") + getPropertiesExtension() + "\" for writing"); + error("Could not open \"" + setup.outputDir + '/' + setup.projectDescription + "_" + configuration + (isWin32 ? "" : "64") + getPropertiesExtension() + "\" for writing"); properties << "\n" "\n" "\t\n" "\t\n" "\t\n" << toolLine << indentString << "\t\n" - << indentString << "\t\n" - << toolLine - << indentString << "\t\n" + << indentString << "\t\n" + << toolLine + << indentString << "\t\n" << indentString << "\t\n" << toolLine << indentString << "\t\n" @@ -369,18 +372,18 @@ void VisualStudioProvider::writeFileListToProject(const FileNode &dir, std::ofst << indentString << "\t\n" << toolLine << indentString << "\t\n" - << indentString << "\t\n" - << toolLine - << indentString << "\t\n" + << indentString << "\t\n" + << toolLine + << indentString << "\t\n" << indentString << "\t\n" << toolLine << indentString << "\t\n" - << indentString << "\t\n" - << toolLine - << indentString << "\t\n" - << indentString << "\t\n" - << toolLine - << indentString << "\t\n" + << indentString << "\t\n" + << toolLine + << indentString << "\t\n" + << indentString << "\t\n" + << toolLine + << indentString << "\t\n" << indentString << "\t\n" << toolLine << indentString << "\t\n" diff --git a/devtools/create_project/visualstudio.h b/devtools/create_project/visualstudio.h index 845550139c2..7eb66c4f2d1 100644 --- a/devtools/create_project/visualstudio.h +++ b/devtools/create_project/visualstudio.h @@ -42,7 +42,7 @@ protected: void outputGlobalPropFile(const BuildSetup &setup, std::ofstream &properties, int bits, const StringList &defines, const std::string &prefix, bool runBuildEvents); - void createBuildProp(const BuildSetup &setup, bool isRelease, bool isWin32, bool enableAnalysis); + void createBuildProp(const BuildSetup &setup, bool isRelease, bool isWin32, std::string configuration); const char *getProjectExtension(); const char *getPropertiesExtension(); diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp index ce5b1d94873..b572f0f78c4 100644 --- a/engines/advancedDetector.cpp +++ b/engines/advancedDetector.cpp @@ -78,7 +78,7 @@ static Common::String generatePreferredTarget(const Common::String &id, const AD res = res + "-cd"; } - if (desc->platform != Common::kPlatformDOS && desc->platform != Common::kPlatformUnknown) { + if (desc->platform != Common::kPlatformDOS && desc->platform != Common::kPlatformUnknown && !(desc->flags & ADGF_DROPPLATFORM)) { res = res + "-" + getPlatformAbbrev(desc->platform); } @@ -609,7 +609,9 @@ AdvancedMetaEngine::AdvancedMetaEngine(const void *descs, uint descItemSize, con } void AdvancedMetaEngine::initSubSystems(const ADGameDescription *gameDesc) const { +#ifdef ENABLE_EVENTRECORDER if (gameDesc) { g_eventRec.processGameDescription(gameDesc); } +#endif } diff --git a/engines/advancedDetector.h b/engines/advancedDetector.h index 71d2c4a446c..376a59e471a 100644 --- a/engines/advancedDetector.h +++ b/engines/advancedDetector.h @@ -87,7 +87,8 @@ enum ADGameFlags { ADGF_ADDENGLISH = (1 << 24), ///< always add English as language option ADGF_MACRESFORK = (1 << 25), ///< the md5 for this entry will be calculated from the resource fork ADGF_USEEXTRAASTITLE = (1 << 26), ///< Extra field value will be used as main game title, not gameid - ADGF_DROPLANGUAGE = (1 << 28), ///< don't add language to gameid + ADGF_DROPLANGUAGE = (1 << 27), ///< don't add language to gameid + ADGF_DROPPLATFORM = (1 << 28), ///< don't add platform to gameid ADGF_CD = (1 << 29), ///< add "-cd" to gameid ADGF_DEMO = (1 << 30) ///< add "-demo" to gameid }; diff --git a/engines/grim/bitmap.cpp b/engines/grim/bitmap.cpp index 9179df09ec4..391e770b8b4 100644 --- a/engines/grim/bitmap.cpp +++ b/engines/grim/bitmap.cpp @@ -248,7 +248,7 @@ bool BitmapData::loadTGA(Common::SeekableReadStream *data) { _colorFormat = BM_RGBA; _numImages = 1; _data = new Graphics::PixelBuffer[1]; - _data[0].set(pixelFormat, (unsigned char *)surf->pixels); + _data[0].set(pixelFormat, (unsigned char *)surf->getPixels()); g_driver->createBitmap(this); diff --git a/engines/grim/gfx_opengl.cpp b/engines/grim/gfx_opengl.cpp index b2a28e0251f..8ac67cc3732 100644 --- a/engines/grim/gfx_opengl.cpp +++ b/engines/grim/gfx_opengl.cpp @@ -1274,7 +1274,7 @@ void GfxOpenGL::drawDepthBitmap(int x, int y, int w, int h, char *data) { void GfxOpenGL::prepareMovieFrame(Graphics::Surface *frame) { int height = frame->h; int width = frame->w; - byte *bitmap = (byte *)frame->pixels; + byte *bitmap = (byte *)frame->getPixels(); // remove if already exist if (_smushNumTex > 0) { diff --git a/engines/grim/gfx_tinygl.cpp b/engines/grim/gfx_tinygl.cpp index 169d2454a23..8cff0012803 100644 --- a/engines/grim/gfx_tinygl.cpp +++ b/engines/grim/gfx_tinygl.cpp @@ -1203,7 +1203,7 @@ void GfxTinyGL::prepareMovieFrame(Graphics::Surface *frame) { _smushWidth = frame->w; _smushHeight = frame->h; - Graphics::PixelBuffer srcBuf(frame->format, (byte *)frame->pixels); + Graphics::PixelBuffer srcBuf(frame->format, (byte *)frame->getPixels()); _smushBitmap.create(_pixelFormat, frame->w * frame->h, DisposeAfterUse::YES); _smushBitmap.copyBuffer(0, frame->w * frame->h, srcBuf); } diff --git a/engines/grim/material.cpp b/engines/grim/material.cpp index 147c1011cbb..70d746ef401 100644 --- a/engines/grim/material.cpp +++ b/engines/grim/material.cpp @@ -112,7 +112,7 @@ void loadTGA(Common::SeekableReadStream *data, Texture *t) { t->_data = new char[t->_width * t->_height * (bpp)]; // Copy the texture data, as the decoder owns the current copy. - memcpy(t->_data, tgaSurface->pixels, t->_width * t->_height * (bpp)); + memcpy(t->_data, tgaSurface->getPixels(), t->_width * t->_height * (bpp)); delete tgaDecoder; } diff --git a/engines/grim/movie/codecs/smush_decoder.cpp b/engines/grim/movie/codecs/smush_decoder.cpp index 8a1c1a9ad05..d1a24898caf 100644 --- a/engines/grim/movie/codecs/smush_decoder.cpp +++ b/engines/grim/movie/codecs/smush_decoder.cpp @@ -496,9 +496,9 @@ void SmushDecoder::SmushVideoTrack::convertDemoFrame() { conversion.create(0, 0, _format); // Avoid issues with copyFrom, by creating an empty surface. conversion.copyFrom(_surface); - uint16 *d = (uint16 *)_surface.pixels; + uint16 *d = (uint16 *)_surface.getPixels(); for (int l = 0; l < _width * _height; l++) { - int index = ((byte *)conversion.pixels)[l]; + int index = ((byte *)conversion.getPixels())[l]; d[l] = ((_pal[(index * 3) + 0] & 0xF8) << 8) | ((_pal[(index * 3) + 1] & 0xFC) << 3) | (_pal[(index * 3) + 2] >> 3); } conversion.free(); @@ -513,7 +513,7 @@ void SmushDecoder::SmushVideoTrack::handleBlocky16(Common::SeekableReadStream *s byte *ptr = new byte[size]; stream->read(ptr, size); - _blocky16->decode((byte *)_surface.pixels, ptr); + _blocky16->decode((byte *)_surface.getPixels(), ptr); delete[] ptr; } @@ -543,7 +543,7 @@ void SmushDecoder::SmushVideoTrack::handleFrameObject(Common::SeekableReadStream size -= 14; byte *ptr = new byte[size]; stream->read(ptr, size); - _blocky8->decode((byte *)_surface.pixels, ptr); + _blocky8->decode((byte *)_surface.getPixels(), ptr); delete[] ptr; } diff --git a/engines/myst3/detection.cpp b/engines/myst3/detection.cpp index 3fbaaacd562..ed32e39735b 100644 --- a/engines/myst3/detection.cpp +++ b/engines/myst3/detection.cpp @@ -205,7 +205,7 @@ public: assert(big->format.bytesPerPixel == 4 && small->format.bytesPerPixel == 4); - uint32 *dst = (uint32 *)small->pixels; + uint32 *dst = (uint32 *)small->getPixels(); for (uint i = 0; i < small->h; i++) { for (uint j = 0; j < small->w; j++) { uint32 srcX = big->w * j / small->w; diff --git a/engines/myst3/gfx.cpp b/engines/myst3/gfx.cpp index 3cc9e2dc693..f1f1aa75312 100644 --- a/engines/myst3/gfx.cpp +++ b/engines/myst3/gfx.cpp @@ -115,7 +115,7 @@ OpenGLTexture::~OpenGLTexture() { void OpenGLTexture::update(const Graphics::Surface *surface) { glBindTexture(GL_TEXTURE_2D, id); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, surface->w, surface->h, internalFormat, sourceFormat, surface->pixels); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, surface->w, surface->h, internalFormat, sourceFormat, surface->getPixels()); } Renderer::Renderer(OSystem *system) : @@ -427,7 +427,7 @@ Graphics::Surface *Renderer::getScreenshot() { Graphics::Surface *s = new Graphics::Surface(); s->create(kOriginalWidth, kOriginalHeight, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)); - glReadPixels(0, 0, kOriginalWidth, kOriginalHeight, GL_RGBA, GL_UNSIGNED_BYTE, s->pixels); + glReadPixels(0, 0, kOriginalWidth, kOriginalHeight, GL_RGBA, GL_UNSIGNED_BYTE, s->getPixels()); return s; } diff --git a/engines/myst3/menu.cpp b/engines/myst3/menu.cpp index 2b47facc4eb..a29f4469fde 100644 --- a/engines/myst3/menu.cpp +++ b/engines/myst3/menu.cpp @@ -567,7 +567,7 @@ Graphics::Surface *Menu::createThumbnail(Graphics::Surface *big) { uint bigHeight = big->h - Renderer::kTopBorderHeight - Renderer::kBottomBorderHeight; uint bigYOffset = Renderer::kBottomBorderHeight; - uint32 *dst = (uint32 *)small->pixels; + uint32 *dst = (uint32 *)small->getPixels(); for (uint i = 0; i < small->h; i++) { for (uint j = 0; j < small->w; j++) { uint32 srcX = big->w * j / small->w; diff --git a/engines/myst3/myst3.cpp b/engines/myst3/myst3.cpp index 201c9ec2a71..afc160993e4 100644 --- a/engines/myst3/myst3.cpp +++ b/engines/myst3/myst3.cpp @@ -1023,7 +1023,7 @@ Graphics::Surface *Myst3Engine::loadTexture(uint16 id) { Graphics::Surface *s = new Graphics::Surface(); s->create(width, height, Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0)); - data->read(s->pixels, height * s->pitch); + data->read(s->getPixels(), height * s->pitch); delete data; // ARGB => RGBA diff --git a/engines/myst3/node.cpp b/engines/myst3/node.cpp index f28b3a83cc0..03f98430bcd 100644 --- a/engines/myst3/node.cpp +++ b/engines/myst3/node.cpp @@ -298,7 +298,7 @@ void SpotItemFace::updateData(const Graphics::Surface *surface) { } void SpotItemFace::clear() { - memset(_bitmap->pixels, 0, _bitmap->pitch * _bitmap->h); + memset(_bitmap->getPixels(), 0, _bitmap->pitch * _bitmap->h); _drawn = false; } diff --git a/engines/myst3/state.cpp b/engines/myst3/state.cpp index ee9bd227480..97b52c00847 100644 --- a/engines/myst3/state.cpp +++ b/engines/myst3/state.cpp @@ -354,7 +354,7 @@ void GameState::StateData::syncWithSaveGame(Common::Serializer &s) { } assert(thumbnail && thumbnail->w == kThumbnailWidth && thumbnail->h == kThumbnailHeight); - s.syncBytes((byte *)thumbnail->pixels, kThumbnailWidth * kThumbnailHeight * 4); + s.syncBytes((byte *)thumbnail->getPixels(), kThumbnailWidth * kThumbnailHeight * 4); } void GameState::newGame() { diff --git a/engines/myst3/subtitles.cpp b/engines/myst3/subtitles.cpp index c46429e82ab..9272f2ff2d4 100644 --- a/engines/myst3/subtitles.cpp +++ b/engines/myst3/subtitles.cpp @@ -197,7 +197,7 @@ void Subtitles::setFrame(int32 frame) { error("No available font"); // Draw the new text - memset(_surface->pixels, 0, _surface->pitch * _surface->h); + memset(_surface->getPixels(), 0, _surface->pitch * _surface->h); font->drawString(_surface, phrase->string, 0, _singleLineTop, _surface->w, 0xFFFFFFFF, Graphics::kTextAlignCenter); // Update the texture diff --git a/graphics/VectorRenderer.h b/graphics/VectorRenderer.h index 0467cac946e..8e1c5e91e19 100644 --- a/graphics/VectorRenderer.h +++ b/graphics/VectorRenderer.h @@ -73,6 +73,8 @@ struct DrawStep { uint8 shadow, stroke, factor, radius, bevel; /**< Misc options... */ uint8 fillMode; /**< active fill mode */ + uint8 shadowFillMode; /**< fill mode of the shadow used */ + uint32 extraData; /**< Generic parameter for extra options (orientation/bevel) */ uint32 scale; /**< scale of all the coordinates in FIXED POINT with 16 bits mantissa */ @@ -103,7 +105,7 @@ VectorRenderer *createRenderer(int mode); */ class VectorRenderer { public: - VectorRenderer() : _activeSurface(NULL), _fillMode(kFillDisabled), _shadowOffset(0), + VectorRenderer() : _activeSurface(NULL), _fillMode(kFillDisabled), _shadowOffset(0), _shadowFillMode(kShadowExponential), _disableShadows(false), _strokeWidth(1), _gradientFactor(1) { } @@ -126,6 +128,11 @@ public: kTriangleRight }; + enum ShadowFillMode { + kShadowLinear = 0, + kShadowExponential = 1 + }; + /** * Draws a line by considering the special cases for optimization. * @@ -278,7 +285,7 @@ public: * Clears the active surface. */ virtual void clearSurface() { - byte *src = (byte *)_activeSurface->pixels; + byte *src = (byte *)_activeSurface->getPixels(); memset(src, 0, _activeSurface->pitch * _activeSurface->h); } @@ -292,6 +299,10 @@ public: _fillMode = mode; } + virtual void setShadowFillMode(ShadowFillMode mode) { + _shadowFillMode = mode; + } + /** * Sets the stroke width. All shapes drawn with a stroke will * have that width. Pass 0 to disable shape stroking. @@ -466,7 +477,7 @@ public: */ virtual void drawString(const Graphics::Font *font, const Common::String &text, const Common::Rect &area, Graphics::TextAlign alignH, - GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool useEllipsis) = 0; + GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool useEllipsis, const Common::Rect &textDrawableArea) = 0; /** * Allows to temporarily enable/disable all shadows drawing. @@ -485,6 +496,7 @@ protected: Surface *_activeSurface; /**< Pointer to the surface currently being drawn */ FillMode _fillMode; /**< Defines in which way (if any) are filled the drawn shapes */ + ShadowFillMode _shadowFillMode; int _shadowOffset; /**< offset for drawn shadows */ int _bevel; /**< amount of fake bevel */ diff --git a/graphics/VectorRendererSpec.cpp b/graphics/VectorRendererSpec.cpp index 6a3ee306a53..491a9d7f42d 100644 --- a/graphics/VectorRendererSpec.cpp +++ b/graphics/VectorRendererSpec.cpp @@ -119,6 +119,38 @@ inline frac_t fp_sqroot(uint32 x) { *(ptr4 + (y) + (px)) = color2; \ } while (0) +#define BE_DRAWCIRCLE_BCOLOR_TR_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (y) - (px), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_TR_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (x) - (py), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_TL_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (x) - (py), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_TL_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (y) - (px), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_BL_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (y) + (px), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_BL_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (x) + (py), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_BR_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (x) + (py), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_BR_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (y) + (px), color, a); \ +} while (0) + #define BE_DRAWCIRCLE_XCOLOR_TOP(ptr1,ptr2,x,y,px,py) do { \ *(ptr1 + (y) - (px)) = color1; \ *(ptr1 + (x) - (py)) = color2; \ @@ -222,6 +254,37 @@ inline frac_t fp_sqroot(uint32 x) { this->blendPixelPtr(ptr4 + (y) + (px), color2, a); \ } while (0) +#define WU_DRAWCIRCLE_BCOLOR_TR_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (y) - (px), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_TR_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (x) - (py), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_TL_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (x) - (py), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_TL_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (y) - (px), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_BL_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (y) + (px), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_BL_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (x) + (py), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_BR_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (x) + (py), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_BR_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (y) + (px), color, a); \ +} while (0) // optimized Wu's algorithm #define WU_ALGORITHM() do { \ @@ -277,16 +340,24 @@ void colorFill(PixelType *first, PixelType *last, PixelType color) { VectorRenderer *createRenderer(int mode) { #ifdef DISABLE_FANCY_THEMES - assert(mode == GUI::ThemeEngine::kGfxStandard16bit); + assert(mode == GUI::ThemeEngine::kGfxStandard); #endif PixelFormat format = g_system->getOverlayFormat(); switch (mode) { - case GUI::ThemeEngine::kGfxStandard16bit: - return new VectorRendererSpec(format); + case GUI::ThemeEngine::kGfxStandard: + if (g_system->getOverlayFormat().bytesPerPixel == 4) + return new VectorRendererSpec(format); + else if (g_system->getOverlayFormat().bytesPerPixel == 2) + return new VectorRendererSpec(format); + break; #ifndef DISABLE_FANCY_THEMES - case GUI::ThemeEngine::kGfxAntialias16bit: - return new VectorRendererAA(format); + case GUI::ThemeEngine::kGfxAntialias: + if (g_system->getOverlayFormat().bytesPerPixel == 4) + return new VectorRendererAA(format); + else if (g_system->getOverlayFormat().bytesPerPixel == 2) + return new VectorRendererAA(format); + break; #endif default: break; @@ -317,9 +388,15 @@ setGradientColors(uint8 r1, uint8 g1, uint8 b1, uint8 r2, uint8 g2, uint8 b2) { _gradientEnd = _format.RGBToColor(r2, g2, b2); _gradientStart = _format.RGBToColor(r1, g1, b1); - _gradientBytes[0] = (_gradientEnd & _redMask) - (_gradientStart & _redMask); - _gradientBytes[1] = (_gradientEnd & _greenMask) - (_gradientStart & _greenMask); - _gradientBytes[2] = (_gradientEnd & _blueMask) - (_gradientStart & _blueMask); + if (sizeof(PixelType) == 4) { + _gradientBytes[0] = ((_gradientEnd & _redMask) >> _format.rShift) - ((_gradientStart & _redMask) >> _format.rShift); + _gradientBytes[1] = ((_gradientEnd & _greenMask) >> _format.gShift) - ((_gradientStart & _greenMask) >> _format.gShift); + _gradientBytes[2] = ((_gradientEnd & _blueMask) >> _format.bShift) - ((_gradientStart & _blueMask) >> _format.bShift); + } else { + _gradientBytes[0] = (_gradientEnd & _redMask) - (_gradientStart & _redMask); + _gradientBytes[1] = (_gradientEnd & _greenMask) - (_gradientStart & _greenMask); + _gradientBytes[2] = (_gradientEnd & _blueMask) - (_gradientStart & _blueMask); + } } template @@ -328,9 +405,15 @@ calcGradient(uint32 pos, uint32 max) { PixelType output = 0; pos = (MIN(pos * Base::_gradientFactor, max) << 12) / max; - output |= ((_gradientStart & _redMask) + ((_gradientBytes[0] * pos) >> 12)) & _redMask; - output |= ((_gradientStart & _greenMask) + ((_gradientBytes[1] * pos) >> 12)) & _greenMask; - output |= ((_gradientStart & _blueMask) + ((_gradientBytes[2] * pos) >> 12)) & _blueMask; + if (sizeof(PixelType) == 4) { + output |= ((_gradientStart & _redMask) + (((_gradientBytes[0] * pos) >> 12) << _format.rShift)) & _redMask; + output |= ((_gradientStart & _greenMask) + (((_gradientBytes[1] * pos) >> 12) << _format.gShift)) & _greenMask; + output |= ((_gradientStart & _blueMask) + (((_gradientBytes[2] * pos) >> 12) << _format.bShift)) & _blueMask; + } else { + output |= ((_gradientStart & _redMask) + ((_gradientBytes[0] * pos) >> 12)) & _redMask; + output |= ((_gradientStart & _greenMask) + ((_gradientBytes[1] * pos) >> 12)) & _greenMask; + output |= ((_gradientStart & _blueMask) + ((_gradientBytes[2] * pos) >> 12)) & _blueMask; + } output |= _alphaMask; return output; @@ -397,7 +480,7 @@ gradientFill(PixelType *ptr, int width, int x, int y) { template void VectorRendererSpec:: fillSurface() { - byte *ptr = (byte *)_activeSurface->getBasePtr(0, 0); + byte *ptr = (byte *)_activeSurface->getPixels(); int h = _activeSurface->h; int pitch = _activeSurface->pitch; @@ -453,7 +536,7 @@ template void VectorRendererSpec:: blitSubSurface(const Graphics::Surface *source, const Common::Rect &r) { byte *dst_ptr = (byte *)_activeSurface->getBasePtr(r.left, r.top); - const byte *src_ptr = (const byte *)source->getBasePtr(0, 0); + const byte *src_ptr = (const byte *)source->getPixels(); const int dst_pitch = _activeSurface->pitch; const int src_pitch = source->pitch; @@ -481,7 +564,7 @@ blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) { y = y + (r.height() >> 1) - (source->h >> 1); PixelType *dst_ptr = (PixelType *)_activeSurface->getBasePtr(x, y); - const PixelType *src_ptr = (const PixelType *)source->getBasePtr(0, 0); + const PixelType *src_ptr = (const PixelType *)source->getPixels(); int dst_pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; int src_pitch = source->pitch / source->format.bytesPerPixel; @@ -508,7 +591,7 @@ template void VectorRendererSpec:: applyScreenShading(GUI::ThemeEngine::ShadingStyle shadingStyle) { int pixels = _activeSurface->w * _activeSurface->h; - PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(0, 0); + PixelType *ptr = (PixelType *)_activeSurface->getPixels(); uint8 r, g, b; uint lum; @@ -537,20 +620,41 @@ applyScreenShading(GUI::ThemeEngine::ShadingStyle shadingStyle) { template inline void VectorRendererSpec:: blendPixelPtr(PixelType *ptr, PixelType color, uint8 alpha) { - int idst = *ptr; - int isrc = color; + if (sizeof(PixelType) == 4) { + const byte sR = (color & _redMask) >> _format.rShift; + const byte sG = (color & _greenMask) >> _format.gShift; + const byte sB = (color & _blueMask) >> _format.bShift; - *ptr = (PixelType)( - (_redMask & ((idst & _redMask) + - ((int)(((int)(isrc & _redMask) - - (int)(idst & _redMask)) * alpha) >> 8))) | - (_greenMask & ((idst & _greenMask) + - ((int)(((int)(isrc & _greenMask) - - (int)(idst & _greenMask)) * alpha) >> 8))) | - (_blueMask & ((idst & _blueMask) + - ((int)(((int)(isrc & _blueMask) - - (int)(idst & _blueMask)) * alpha) >> 8))) | - (idst & _alphaMask)); + byte dR = (*ptr & _redMask) >> _format.rShift; + byte dG = (*ptr & _greenMask) >> _format.gShift; + byte dB = (*ptr & _blueMask) >> _format.bShift; + + dR += ((sR - dR) * alpha) >> 8; + dG += ((sG - dG) * alpha) >> 8; + dB += ((sB - dB) * alpha) >> 8; + + *ptr = ((dR << _format.rShift) & _redMask) + | ((dG << _format.gShift) & _greenMask) + | ((dB << _format.bShift) & _blueMask) + | (*ptr & _alphaMask); + } else if (sizeof(PixelType) == 2) { + int idst = *ptr; + int isrc = color; + + *ptr = (PixelType)( + (_redMask & ((idst & _redMask) + + ((int)(((int)(isrc & _redMask) - + (int)(idst & _redMask)) * alpha) >> 8))) | + (_greenMask & ((idst & _greenMask) + + ((int)(((int)(isrc & _greenMask) - + (int)(idst & _greenMask)) * alpha) >> 8))) | + (_blueMask & ((idst & _blueMask) + + ((int)(((int)(isrc & _blueMask) - + (int)(idst & _blueMask)) * alpha) >> 8))) | + (idst & _alphaMask)); + } else { + error("Unsupported BPP format: %u", (uint)sizeof(PixelType)); + } } template @@ -607,24 +711,45 @@ darkenFill(PixelType *ptr, PixelType *end) { template void VectorRendererSpec:: drawString(const Graphics::Font *font, const Common::String &text, const Common::Rect &area, - Graphics::TextAlign alignH, GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool ellipsis) { + Graphics::TextAlign alignH, GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool ellipsis, const Common::Rect &textDrawableArea) { int offset = area.top; if (font->getFontHeight() < area.height()) { switch (alignV) { - case GUI::ThemeEngine::kTextAlignVCenter: - offset = area.top + ((area.height() - font->getFontHeight()) >> 1); - break; - case GUI::ThemeEngine::kTextAlignVBottom: - offset = area.bottom - font->getFontHeight(); - break; - default: - break; + case GUI::ThemeEngine::kTextAlignVCenter: + offset = area.top + ((area.height() - font->getFontHeight()) >> 1); + break; + case GUI::ThemeEngine::kTextAlignVBottom: + offset = area.bottom - font->getFontHeight(); + break; + default: + break; } } - font->drawString(_activeSurface, text, area.left, offset, area.width() - deltax, _fgColor, alignH, deltax, ellipsis); + Common::Rect drawArea; + if (textDrawableArea.isEmpty()) { + // In case no special area to draw to is given we only draw in the + // area specified by the user. + drawArea = area; + // warning("there is no text drawable area. Please set this area for clipping"); + } else { + // The area we can draw to is the intersection between the allowed + // drawing area (textDrawableArea) and the area where we try to draw + // the text (area). + drawArea = textDrawableArea.findIntersectingRect(area); + } + + // Better safe than sorry. We intersect with the actual surface boundaries + // to avoid any ugly clipping in _activeSurface->getSubArea which messes + // up the calculation of the x and y coordinates where to draw the string. + drawArea = drawArea.findIntersectingRect(Common::Rect(0, 0, _activeSurface->w, _activeSurface->h)); + + if (!drawArea.isEmpty()) { + Surface textAreaSurface = _activeSurface->getSubArea(drawArea); + font->drawString(&textAreaSurface, text, area.left - drawArea.left, offset - drawArea.top, area.width() - deltax, _fgColor, alignH, deltax, ellipsis); + } } /** LINES **/ @@ -778,7 +903,8 @@ drawRoundedSquare(int x, int y, int r, int w, int h) { if (Base::_fillMode != kFillDisabled && Base::_shadowOffset && x + w + Base::_shadowOffset + 1 < Base::_activeSurface->w - && y + h + Base::_shadowOffset + 1 < Base::_activeSurface->h) { + && y + h + Base::_shadowOffset + 1 < Base::_activeSurface->h + && h > (Base::_shadowOffset + 1) * 2) { drawRoundedSquareShadow(x, y, r, w, h, Base::_shadowOffset); } @@ -809,13 +935,14 @@ drawTab(int x, int y, int r, int w, int h) { // FIXME: This is broken for the AA renderer. // See the rounded rect alg for how to fix it. (The border should // be drawn before the interior, both inside drawTabAlg.) - drawTabAlg(x, y, w, h, r, (Base::_fillMode == kFillBackground) ? _bgColor : _fgColor, Base::_fillMode); + drawTabShadow(x, y, w - 2, h, r); + drawTabAlg(x, y, w - 2, h, r, _bgColor, Base::_fillMode); if (Base::_strokeWidth) drawTabAlg(x, y, w, h, r, _fgColor, kFillDisabled, (Base::_dynamicData >> 16), (Base::_dynamicData & 0xFFFF)); break; case kFillForeground: - drawTabAlg(x, y, w, h, r, (Base::_fillMode == kFillBackground) ? _bgColor : _fgColor, Base::_fillMode); + drawTabAlg(x, y, w, h, r, _fgColor, Base::_fillMode); break; } } @@ -996,6 +1123,67 @@ drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer: } +template +void VectorRendererSpec:: +drawTabShadow(int x1, int y1, int w, int h, int r) { + int offset = 3; + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + + // "Harder" shadows when having lower BPP, since we will have artifacts (greenish tint on the modern theme) + uint8 expFactor = 3; + uint16 alpha = (_activeSurface->format.bytesPerPixel > 2) ? 4 : 8; + + int xstart = x1; + int ystart = y1; + int width = w; + int height = h + offset + 1; + + for (int i = offset; i >= 0; i--) { + int f, ddF_x, ddF_y; + int x, y, px, py; + + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(xstart + r, ystart + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(xstart + width - r, ystart + r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(xstart, ystart); + + int short_h = height - (2 * r) + 2; + PixelType color = _format.RGBToColor(0, 0, 0); + + BE_RESET(); + + // HACK: As we are drawing circles exploting 8-axis symmetry, + // there are 4 pixels on each circle which are drawn twice. + // this is ok on filled circles, but when blending on surfaces, + // we cannot let it blend twice. awful. + uint32 hb = 0; + + while (x++ < y) { + BE_ALGORITHM(); + + if (((1 << x) & hb) == 0) { + blendFill(ptr_tl - y - px, ptr_tr + y - px, color, (uint8)alpha); + hb |= (1 << x); + } + + if (((1 << y) & hb) == 0) { + blendFill(ptr_tl - x - py, ptr_tr + x - py, color, (uint8)alpha); + hb |= (1 << y); + } + } + + ptr_fill += pitch * r; + while (short_h--) { + blendFill(ptr_fill, ptr_fill + width + 1, color, (uint8)alpha); + ptr_fill += pitch; + } + + // Move shadow one pixel upward each iteration + xstart += 1; + // Multiply with expfactor + alpha = (alpha * (expFactor << 8)) >> 9; + } +} + /** BEVELED TABS FOR CLASSIC THEME **/ template void VectorRendererSpec:: @@ -1429,118 +1617,160 @@ drawTriangleFast(int x1, int y1, int size, bool inverted, PixelType color, Vecto /** ROUNDED SQUARE ALGORITHM **/ template void VectorRendererSpec:: -drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { +drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m, uint8 alpha_t, uint8 alpha_r, uint8 alpha_b, uint8 alpha_l) { + int f, ddF_x, ddF_y; + int x, y, px, py; + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int sw = 0, sp = 0, hp = h * pitch; + + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); + + int real_radius = r; + int short_h = h - (2 * r) + 2; + + PixelType color1 = color; + PixelType color2 = color; + + while (sw++ < Base::_strokeWidth) { + blendFill(ptr_fill + sp + r, ptr_fill + w + 1 + sp - r, color1, alpha_t); // top + blendFill(ptr_fill + hp - sp + r, ptr_fill + w + hp + 1 - sp - r, color2, alpha_b); // bottom + sp += pitch; + + BE_RESET(); + r--; + + int alphaStep_tr = ((alpha_t - alpha_r)/(y+1)); + int alphaStep_br = ((alpha_r - alpha_b)/(y+1)); + int alphaStep_bl = ((alpha_b - alpha_l)/(y+1)); + int alphaStep_tl = ((alpha_l - alpha_t)/(y+1)); + + // Avoid blending the last pixels twice, since we have an alpha + while (x++ < (y - 2)) { + BE_ALGORITHM(); + + BE_DRAWCIRCLE_BCOLOR_TR_CW(ptr_tr, x, y, px, py, (uint8)(alpha_r + (alphaStep_tr * x))); + BE_DRAWCIRCLE_BCOLOR_BR_CW(ptr_br, x, y, px, py, (uint8)(alpha_b + (alphaStep_br * x))); + BE_DRAWCIRCLE_BCOLOR_BL_CW(ptr_bl, x, y, px, py, (uint8)(alpha_l + (alphaStep_bl * x))); + BE_DRAWCIRCLE_BCOLOR_TL_CW(ptr_tl, x, y, px, py, (uint8)(alpha_t + (alphaStep_tl * x))); + + BE_DRAWCIRCLE_BCOLOR_TR_CCW(ptr_tr, x, y, px, py, (uint8)(alpha_t - (alphaStep_tr * x))); + BE_DRAWCIRCLE_BCOLOR_BR_CCW(ptr_br, x, y, px, py, (uint8)(alpha_r - (alphaStep_br * x))); + BE_DRAWCIRCLE_BCOLOR_BL_CCW(ptr_bl, x, y, px, py, (uint8)(alpha_b - (alphaStep_bl * x))); + BE_DRAWCIRCLE_BCOLOR_TL_CCW(ptr_tl, x, y, px, py, (uint8)(alpha_l - (alphaStep_tl * x))); + + if (Base::_strokeWidth > 1) { + BE_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x - 1, y, px, py); + BE_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px - pitch, py); + } + } + } + + ptr_fill += pitch * real_radius; + while (short_h--) { + blendFill(ptr_fill, ptr_fill + Base::_strokeWidth, color1, alpha_l); // left + blendFill(ptr_fill + w - Base::_strokeWidth + 1, ptr_fill + w + 1, color2, alpha_r); // right + ptr_fill += pitch; + } +} + +template +void VectorRendererSpec:: +drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { int f, ddF_x, ddF_y; int x, y, px, py; int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; - // TODO: Split this up into border, bevel and interior functions + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); - if (fill_m != kFillDisabled) { - PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); + int real_radius = r; + int short_h = h - (2 * r) + 2; + int long_h = h; - int real_radius = r; - int short_h = h - (2 * r) + 2; - int long_h = h; + BE_RESET(); - BE_RESET(); + PixelType color1 = color; - PixelType color1 = color; - if (fill_m == kFillBackground) - color1 = _bgColor; + if (fill_m == kFillGradient) { + PixelType color2, color3, color4; + precalcGradient(long_h); - if (fill_m == kFillGradient) { - PixelType color2, color3, color4; - precalcGradient(long_h); + while (x++ < y) { + BE_ALGORITHM(); - while (x++ < y) { - BE_ALGORITHM(); + color1 = calcGradient(real_radius - x, long_h); + color2 = calcGradient(real_radius - y, long_h); + color3 = calcGradient(long_h - r + x, long_h); + color4 = calcGradient(long_h - r + y, long_h); - color1 = calcGradient(real_radius - x, long_h); - color2 = calcGradient(real_radius - y, long_h); - color3 = calcGradient(long_h - r + x, long_h); - color4 = calcGradient(long_h - r + y, long_h); + gradientFill(ptr_tl - x - py, w - 2 * r + 2 * x, x1 + r - x - y, real_radius - y); + gradientFill(ptr_tl - y - px, w - 2 * r + 2 * y, x1 + r - y - x, real_radius - x); - gradientFill(ptr_tl - x - py, w - 2 * r + 2 * x, x1 + r - x - y, real_radius - y); - gradientFill(ptr_tl - y - px, w - 2 * r + 2 * y, x1 + r - y - x, real_radius - x); + gradientFill(ptr_bl - x + py, w - 2 * r + 2 * x, x1 + r - x - y, long_h - r + y); + gradientFill(ptr_bl - y + px, w - 2 * r + 2 * y, x1 + r - y - x, long_h - r + x); - gradientFill(ptr_bl - x + py, w - 2 * r + 2 * x, x1 + r - x - y, long_h - r + y); - gradientFill(ptr_bl - y + px, w - 2 * r + 2 * y, x1 + r - y - x, long_h - r + x); - - BE_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py); - } - } else { - while (x++ < y) { - BE_ALGORITHM(); - - colorFill(ptr_tl - x - py, ptr_tr + x - py, color1); - colorFill(ptr_tl - y - px, ptr_tr + y - px, color1); - - colorFill(ptr_bl - x + py, ptr_br + x + py, color1); - colorFill(ptr_bl - y + px, ptr_br + y + px, color1); - - // do not remove - messes up the drawing at lower resolutions - BE_DRAWCIRCLE(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py); - } + BE_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py); } + } else { + while (x++ < y) { + BE_ALGORITHM(); - ptr_fill += pitch * r; - while (short_h--) { - if (fill_m == kFillGradient) { - gradientFill(ptr_fill, w + 1, x1, real_radius++); - } else { - colorFill(ptr_fill, ptr_fill + w + 1, color1); - } - ptr_fill += pitch; + colorFill(ptr_tl - x - py, ptr_tr + x - py, color1); + colorFill(ptr_tl - y - px, ptr_tr + y - px, color1); + + colorFill(ptr_bl - x + py, ptr_br + x + py, color1); + colorFill(ptr_bl - y + px, ptr_br + y + px, color1); + + // do not remove - messes up the drawing at lower resolutions + BE_DRAWCIRCLE(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py); } } + ptr_fill += pitch * r; + while (short_h--) { + if (fill_m == kFillGradient) { + gradientFill(ptr_fill, w + 1, x1, real_radius++); + } else { + colorFill(ptr_fill, ptr_fill + w + 1, color1); + } + ptr_fill += pitch; + } +} + +template +void VectorRendererSpec:: +drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + const uint8 borderAlpha_t = 0; + const uint8 borderAlpha_r = 127; + const uint8 borderAlpha_b = 255; + const uint8 borderAlpha_l = 63; + + const uint8 bevelAlpha_t = 255; + const uint8 bevelAlpha_r = 31; + const uint8 bevelAlpha_b = 0; + const uint8 bevelAlpha_l = 127; + + // If only border is visible + if ((!(w <= 0 || h <= 0)) && (fill_m != Base::kFillDisabled)) { + if (fill_m == Base::kFillBackground) + drawInteriorRoundedSquareAlg(x1, y1, r, w, h, _bgColor, fill_m); + else + drawInteriorRoundedSquareAlg(x1, y1, r, w, h, color, fill_m); + } if (Base::_strokeWidth) { - int sw = 0, sp = 0, hp = h * pitch; - - PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); - - int real_radius = r; - int short_h = h - (2 * r) + 2; - - // TODO: A gradient effect on the bevel - PixelType color1, color2; - color1 = Base::_bevel ? _bevelColor : color; - color2 = color; - - while (sw++ < Base::_strokeWidth) { - colorFill(ptr_fill + sp + r, ptr_fill + w + 1 + sp - r, color1); - colorFill(ptr_fill + hp - sp + r, ptr_fill + w + hp + 1 - sp - r, color2); - sp += pitch; - - BE_RESET(); - r--; - - while (x++ < y) { - BE_ALGORITHM(); - BE_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py); - - if (Base::_strokeWidth > 1) { - BE_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x - 1, y, px, py); - BE_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px - pitch, py); - } - } - } - - ptr_fill += pitch * real_radius; - while (short_h--) { - colorFill(ptr_fill, ptr_fill + Base::_strokeWidth, color1); - colorFill(ptr_fill + w - Base::_strokeWidth + 1, ptr_fill + w + 1, color2); - ptr_fill += pitch; + if (r != 0 && _bevel > 0) { + drawBorderRoundedSquareAlg(x1, y1, r, w, h, color, fill_m, borderAlpha_t, borderAlpha_r, borderAlpha_b, borderAlpha_l); + drawBorderRoundedSquareAlg(x1, y1, r, w, h, _bevelColor, fill_m, bevelAlpha_t, bevelAlpha_r, bevelAlpha_b, bevelAlpha_l); + } else { + drawBorderRoundedSquareAlg(x1, y1, r, w, h, color, fill_m, 255, 255, 255, 255); } } } @@ -1599,85 +1829,112 @@ drawCircleAlg(int x1, int y1, int r, PixelType color, VectorRenderer::FillMode f ********************************************************************/ template void VectorRendererSpec:: -drawSquareShadow(int x, int y, int w, int h, int blur) { - PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x + w - 1, y + blur); +drawSquareShadow(int x, int y, int w, int h, int offset) { + PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x + w - 1, y + offset); int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; int i, j; - i = h - blur; + i = h - offset; while (i--) { - j = blur; + j = offset; while (j--) - blendPixelPtr(ptr + j, 0, ((blur - j) << 8) / blur); + blendPixelPtr(ptr + j, 0, ((offset - j) << 8) / offset); ptr += pitch; } - ptr = (PixelType *)_activeSurface->getBasePtr(x + blur, y + h - 1); + ptr = (PixelType *)_activeSurface->getBasePtr(x + offset, y + h - 1); - while (i++ < blur) { - j = w - blur; + while (i++ < offset) { + j = w - offset; while (j--) - blendPixelPtr(ptr + j, 0, ((blur - i) << 8) / blur); + blendPixelPtr(ptr + j, 0, ((offset - i) << 8) / offset); ptr += pitch; } ptr = (PixelType *)_activeSurface->getBasePtr(x + w, y + h); i = 0; - while (i++ < blur) { - j = blur - 1; + while (i++ < offset) { + j = offset - 1; while (j--) - blendPixelPtr(ptr + j, 0, (((blur - j) * (blur - i)) << 8) / (blur * blur)); + blendPixelPtr(ptr + j, 0, (((offset - j) * (offset - i)) << 8) / (offset * offset)); ptr += pitch; } } template void VectorRendererSpec:: -drawRoundedSquareShadow(int x1, int y1, int r, int w, int h, int blur) { - int f, ddF_x, ddF_y; - int x, y, px, py; +drawRoundedSquareShadow(int x1, int y1, int r, int w, int h, int offset) { int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; - int alpha = 102; - x1 += blur; - y1 += blur; + // "Harder" shadows when having lower BPP, since we will have artifacts (greenish tint on the modern theme) + uint8 expFactor = 3; + uint16 alpha = (_activeSurface->format.bytesPerPixel > 2) ? 4 : 8; + + // These constants ensure a border of 2px on the left and of each rounded square + int xstart = (x1 > 2) ? x1 - 2 : x1; + int ystart = y1; + int width = w + offset + 2; + int height = h + offset + 1; - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - blur, y1 + r); + for (int i = offset; i >= 0; i--) { + int f, ddF_x, ddF_y; + int x, y, px, py; + + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(xstart + r, ystart + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(xstart + width - r, ystart + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(xstart + r, ystart + height - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(xstart + width - r, ystart + height - r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(xstart, ystart); - int short_h = h - (2 * r) + 1; + int short_h = height - (2 * r) + 2; + PixelType color = _format.RGBToColor(0, 0, 0); - BE_RESET(); + BE_RESET(); - // HACK: As we are drawing circles exploting 8-axis symmetry, - // there are 4 pixels on each circle which are drawn twice. - // this is ok on filled circles, but when blending on surfaces, - // we cannot let it blend twice. awful. - uint32 hb = 0; + // HACK: As we are drawing circles exploting 8-axis symmetry, + // there are 4 pixels on each circle which are drawn twice. + // this is ok on filled circles, but when blending on surfaces, + // we cannot let it blend twice. awful. + uint32 hb = 0; + + while (x++ < y) { + BE_ALGORITHM(); - while (x++ < y) { - BE_ALGORITHM(); - if (((1 << x) & hb) == 0) { - blendFill(ptr_tr - px - r, ptr_tr + y - px, 0, alpha); - blendFill(ptr_bl - y + px, ptr_br + y + px, 0, alpha); - hb |= (1 << x); + if (((1 << x) & hb) == 0) { + blendFill(ptr_tl - y - px, ptr_tr + y - px, color, (uint8)alpha); + + // Will create a dark line of pixles if left out + if (hb > 0) { + blendFill(ptr_bl - y + px, ptr_br + y + px, color, (uint8)alpha); + } + hb |= (1 << x); + } + + if (((1 << y) & hb) == 0) { + blendFill(ptr_tl - x - py, ptr_tr + x - py, color, (uint8)alpha); + blendFill(ptr_bl - x + py, ptr_br + x + py, color, (uint8)alpha); + hb |= (1 << y); + } + } + + ptr_fill += pitch * r; + while (short_h--) { + blendFill(ptr_fill, ptr_fill + width + 1, color, (uint8)alpha); + ptr_fill += pitch; } - if (((1 << y) & hb) == 0) { - blendFill(ptr_tr - r - py, ptr_tr + x - py, 0, alpha); - blendFill(ptr_bl - x + py, ptr_br + x + py, 0, alpha); - hb |= (1 << y); - } - } - - while (short_h--) { - blendFill(ptr_fill - r, ptr_fill + blur, 0, alpha); - ptr_fill += pitch; + // Make shadow smaller each iteration, and move it one pixel inward + xstart += 1; + ystart += 1; + width -= 2; + height -= 2; + + if (_shadowFillMode == kShadowExponential) + // Multiply with expfactor + alpha = (alpha * (expFactor << 8)) >> 9; } } @@ -1870,7 +2127,7 @@ drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer: /** ROUNDED SQUARES **/ template void VectorRendererAA:: -drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { +drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m, uint8 alpha_t, uint8 alpha_r, uint8 alpha_b, uint8 alpha_l) { int x, y; const int pitch = Base::_activeSurface->pitch / Base::_activeSurface->format.bytesPerPixel; int px, py; @@ -1879,65 +2136,89 @@ drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, Vecto frac_t T = 0, oldT; uint8 a1, a2; - // TODO: Split this up into border, bevel and interior functions + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); - if (Base::_strokeWidth) { - PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); + int sw = 0, sp = 0; + int short_h = h - 2 * r; + int hp = h * pitch; - int sw = 0, sp = 0; - int short_h = h - 2 * r; - int hp = h * pitch; + int strokeWidth = Base::_strokeWidth; - int strokeWidth = Base::_strokeWidth; - // If we're going to fill the inside, draw a slightly thicker border - // so we can blend the inside on top of it. - if (fill_m != Base::kFillDisabled) strokeWidth++; + while (sw++ < strokeWidth) { + this->blendFill(ptr_fill + hp - sp + r, ptr_fill + w + hp + 1 - sp - r, color, alpha_b); // bottom + this->blendFill(ptr_fill + sp + r, ptr_fill + w + 1 + sp - r, color, alpha_t); // top - // TODO: A gradient effect on the bevel - PixelType color1, color2; - color1 = Base::_bevel ? Base::_bevelColor : color; - color2 = color; + sp += pitch; + x = r - (sw - 1); + y = 0; + T = 0; + px = pitch * x; + py = 0; - while (sw++ < strokeWidth) { - colorFill(ptr_fill + sp + r, ptr_fill + w + 1 + sp - r, color1); - colorFill(ptr_fill + hp - sp + r, ptr_fill + w + hp + 1 - sp - r, color2); - sp += pitch; + int alphaStep_tr = ((alpha_t - alpha_r)/(x+1)); + int alphaStep_br = ((alpha_r - alpha_b)/(x+1)); + int alphaStep_bl = ((alpha_b - alpha_l)/(x+1)); + int alphaStep_tl = ((alpha_l - alpha_t)/(x+1)); - x = r - (sw - 1); - y = 0; - T = 0; - px = pitch * x; - py = 0; + while (x > y++) { + WU_ALGORITHM(); - while (x > y++) { - WU_ALGORITHM(); + // sw == 1: outside, sw = _strokeWidth: inside + // We always draw the outer edge AAed, but the inner edge + // only when the inside isn't filled + if (sw != strokeWidth || fill_m != Base::kFillDisabled) + a2 = 255; + + // inner arc + WU_DRAWCIRCLE_BCOLOR_TR_CW(ptr_tr, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_t - (alphaStep_tr * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BR_CW(ptr_br, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_r - (alphaStep_br * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BL_CW(ptr_bl, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_b - (alphaStep_bl * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_TL_CW(ptr_tl, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_l - (alphaStep_tl * y)) << 8) * a2) >> 16)); + + WU_DRAWCIRCLE_BCOLOR_TR_CCW(ptr_tr, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_r + (alphaStep_tr * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BR_CCW(ptr_br, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_b + (alphaStep_br * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BL_CCW(ptr_bl, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_l + (alphaStep_bl * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_TL_CCW(ptr_tl, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_t + (alphaStep_tl * y)) << 8) * a2) >> 16)); - // sw == 1: outside, sw = _strokeWidth: inside - // We always draw the outer edge AAed, but the inner edge - // only when the inside isn't filled - if (sw != strokeWidth || fill_m != Base::kFillDisabled) - a2 = 255; - - // inner arc - WU_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, (x - 1), y, (px - pitch), py, a2); - - if (sw == 1) // outer arc - WU_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1); + // outer arc + if (sw == 1) { + WU_DRAWCIRCLE_BCOLOR_TR_CW(ptr_tr, x, y, px, py, (uint8)((uint32)(((alpha_t - (alphaStep_tr * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BR_CW(ptr_br, x, y, px, py, (uint8)((uint32)(((alpha_r - (alphaStep_br * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BL_CW(ptr_bl, x, y, px, py, (uint8)((uint32)(((alpha_b - (alphaStep_bl * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_TL_CW(ptr_tl, x, y, px, py, (uint8)((uint32)(((alpha_l - (alphaStep_tl * y)) << 8) * a1) >> 16)); + + WU_DRAWCIRCLE_BCOLOR_TR_CCW(ptr_tr, x, y, px, py, (uint8)((uint32)(((alpha_r + (alphaStep_tr * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BR_CCW(ptr_br, x, y, px, py, (uint8)((uint32)(((alpha_b + (alphaStep_br * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BL_CCW(ptr_bl, x, y, px, py, (uint8)((uint32)(((alpha_l + (alphaStep_bl * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_TL_CCW(ptr_tl, x, y, px, py, (uint8)((uint32)(((alpha_t + (alphaStep_tl * y)) << 8) * a1) >> 16)); + } } - } ptr_fill += pitch * r; - while (short_h-- >= 0) { - colorFill(ptr_fill, ptr_fill + Base::_strokeWidth, color1); - colorFill(ptr_fill + w - Base::_strokeWidth + 1, ptr_fill + w + 1, color2); + + while (short_h-- >= -2) { + this->blendFill(ptr_fill, ptr_fill + Base::_strokeWidth, color, alpha_l); // left + this->blendFill(ptr_fill + w - Base::_strokeWidth + 1, ptr_fill + w + 1, color, alpha_r); // right ptr_fill += pitch; } } +} + +template +void VectorRendererAA:: +drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + int x, y; + const int pitch = Base::_activeSurface->pitch / Base::_activeSurface->format.bytesPerPixel; + int px, py; + + uint32 rsq = r*r; + frac_t T = 0, oldT; + uint8 a1, a2; r -= Base::_strokeWidth; x1 += Base::_strokeWidth; @@ -1946,92 +2227,115 @@ drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, Vecto h -= 2*Base::_strokeWidth; rsq = r*r; - if (w <= 0 || h <= 0) - return; // Only border is visible + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); - if (fill_m != Base::kFillDisabled) { - if (fill_m == Base::kFillBackground) - color = Base::_bgColor; + int short_h = h - 2 * r; + x = r; + y = 0; + T = 0; + px = pitch * x; + py = 0; - PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); + if (fill_m == Base::kFillGradient) { - int short_h = h - 2 * r; - x = r; - y = 0; - T = 0; - px = pitch * x; - py = 0; + Base::precalcGradient(h); - if (fill_m == Base::kFillGradient) { + PixelType color1, color2, color3, color4; + while (x > y++) { + WU_ALGORITHM(); - Base::precalcGradient(h); + color1 = Base::calcGradient(r - x, h); + color2 = Base::calcGradient(r - y, h); + color3 = Base::calcGradient(h - r + x, h); + color4 = Base::calcGradient(h - r + y, h); - PixelType color1, color2, color3, color4; - while (x > y++) { - WU_ALGORITHM(); + Base::gradientFill(ptr_tl - x - py + 1, w - 2 * r + 2 * x - 1, x1 + r - x - y + 1, r - y); - color1 = Base::calcGradient(r - x, h); - color2 = Base::calcGradient(r - y, h); - color3 = Base::calcGradient(h - r + x, h); - color4 = Base::calcGradient(h - r + y, h); + // Only fill each horizontal line once (or we destroy + // the gradient effect at the edges) + if (T < oldT || y == 1) + Base::gradientFill(ptr_tl - y - px + 1, w - 2 * r + 2 * y - 1, x1 + r - y - x + 1, r - x); - Base::gradientFill(ptr_tl - x - py + 1, w - 2 * r + 2 * x - 1, x1 + r - x - y + 1, r - y); + Base::gradientFill(ptr_bl - x + py + 1, w - 2 * r + 2 * x - 1, x1 + r - x - y + 1, h - r + y); - // Only fill each horizontal line once (or we destroy - // the gradient effect at the edges) - if (T < oldT || y == 1) - Base::gradientFill(ptr_tl - y - px + 1, w - 2 * r + 2 * y - 1, x1 + r - y - x + 1, r - x); + // Only fill each horizontal line once (or we destroy + // the gradient effect at the edges) + if (T < oldT || y == 1) + Base::gradientFill(ptr_bl - y + px + 1, w - 2 * r + 2 * y - 1, x1 + r - y - x + 1, h - r + x); - Base::gradientFill(ptr_bl - x + py + 1, w - 2 * r + 2 * x - 1, x1 + r - x - y + 1, h - r + y); - - // Only fill each horizontal line once (or we destroy - // the gradient effect at the edges) - if (T < oldT || y == 1) - Base::gradientFill(ptr_bl - y + px + 1, w - 2 * r + 2 * y - 1, x1 + r - y - x + 1, h - r + x); - - // This shape is used for dialog backgrounds. - // If we're drawing on top of an empty overlay background, - // and the overlay supports alpha, we have to do AA by - // setting the dest alpha channel, instead of blending with - // dest color channels. - if (!g_system->hasFeature(OSystem::kFeatureOverlaySupportsAlpha)) - WU_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1, blendPixelPtr); - else - WU_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1, blendPixelDestAlphaPtr); - } - - ptr_fill += pitch * r; - while (short_h-- >= 0) { - Base::gradientFill(ptr_fill, w + 1, x1, r++); - ptr_fill += pitch; - } - - } else { - - while (x > 1 + y++) { - WU_ALGORITHM(); - - colorFill(ptr_tl - x - py + 1, ptr_tr + x - py, color); - if (T < oldT || y == 1) - colorFill(ptr_tl - y - px + 1, ptr_tr + y - px, color); - - colorFill(ptr_bl - x + py + 1, ptr_br + x + py, color); - if (T < oldT || y == 1) - colorFill(ptr_bl - y + px + 1, ptr_br + y + px, color); - - WU_DRAWCIRCLE(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1); - } - - ptr_fill += pitch * r; - while (short_h-- >= 0) { - colorFill(ptr_fill, ptr_fill + w + 1, color); - ptr_fill += pitch; - } + // This shape is used for dialog backgrounds. + // If we're drawing on top of an empty overlay background, + // and the overlay supports alpha, we have to do AA by + // setting the dest alpha channel, instead of blending with + // dest color channels. + if (!g_system->hasFeature(OSystem::kFeatureOverlaySupportsAlpha)) + WU_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1, blendPixelPtr); + else + WU_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1, blendPixelDestAlphaPtr); } + + ptr_fill += pitch * r; + while (short_h-- >= 0) { + Base::gradientFill(ptr_fill, w + 1, x1, r++); + ptr_fill += pitch; + } + + } else { + + while (x > 1 + y++) { + WU_ALGORITHM(); + + colorFill(ptr_tl - x - py + 1, ptr_tr + x - py, color); + if (T < oldT || y == 1) + colorFill(ptr_tl - y - px + 1, ptr_tr + y - px, color); + + colorFill(ptr_bl - x + py + 1, ptr_br + x + py, color); + if (T < oldT || y == 1) + colorFill(ptr_bl - y + px + 1, ptr_br + y + px, color); + + WU_DRAWCIRCLE(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1); + } + + ptr_fill += pitch * r; + while (short_h-- >= 0) { + colorFill(ptr_fill, ptr_fill + w + 1, color); + ptr_fill += pitch; + } + } +} + +template +void VectorRendererAA:: +drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + const uint8 borderAlpha_t = 0; + const uint8 borderAlpha_r = 127; + const uint8 borderAlpha_b = 255; + const uint8 borderAlpha_l = 63; + + const uint8 bevelAlpha_t = 255; + const uint8 bevelAlpha_r = 31; + const uint8 bevelAlpha_b = 0; + const uint8 bevelAlpha_l = 127; + + if (Base::_strokeWidth) { + if (r != 0 && Base::_bevel > 0) { + drawBorderRoundedSquareAlg(x1, y1, r, w, h, color, fill_m, borderAlpha_t, borderAlpha_r, borderAlpha_b, borderAlpha_l); + drawBorderRoundedSquareAlg(x1, y1, r, w, h, Base::_bevelColor, fill_m, bevelAlpha_t, bevelAlpha_r, bevelAlpha_b, bevelAlpha_l); + } else { + drawBorderRoundedSquareAlg(x1, y1, r, w, h, color, fill_m, 255, 255, 255, 255); + } + } + + // If only border is visible + if ((!(w <= 0 || h <= 0)) && (fill_m != Base::kFillDisabled)) { + if (fill_m == Base::kFillBackground) + drawInteriorRoundedSquareAlg(x1, y1, r, w, h, Base::_bgColor, fill_m); + else + drawInteriorRoundedSquareAlg(x1, y1, r, w, h, color, fill_m); } } diff --git a/graphics/VectorRendererSpec.h b/graphics/VectorRendererSpec.h index 4ed80cb55fa..c035ca0e192 100644 --- a/graphics/VectorRendererSpec.h +++ b/graphics/VectorRendererSpec.h @@ -61,7 +61,7 @@ public: } void drawString(const Graphics::Font *font, const Common::String &text, const Common::Rect &area, Graphics::TextAlign alignH, - GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool elipsis); + GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool elipsis, const Common::Rect &textDrawableArea = Common::Rect(0, 0, 0, 0)); void setFgColor(uint8 r, uint8 g, uint8 b) { _fgColor = _format.RGBToColor(r, g, b); } void setBgColor(uint8 r, uint8 g, uint8 b) { _bgColor = _format.RGBToColor(r, g, b); } @@ -158,6 +158,12 @@ protected: virtual void drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, FillMode fill_m); + virtual void drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, + PixelType color, FillMode fill_m, uint8 alpha_t, uint8 alpha_r, uint8 alpha_b, uint8 alpha_l); + + virtual void drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, + PixelType color, FillMode fill_m); + virtual void drawSquareAlg(int x, int y, int w, int h, PixelType color, FillMode fill_m); @@ -174,6 +180,8 @@ protected: PixelType color, VectorRenderer::FillMode fill_m, int baseLeft = 0, int baseRight = 0); + virtual void drawTabShadow(int x, int y, int w, int h, int r); + virtual void drawBevelTabAlg(int x, int y, int w, int h, int bevel, PixelType topColor, PixelType bottomColor, int baseLeft = 0, int baseRight = 0); @@ -186,10 +194,10 @@ protected: * There functions may be overloaded in inheriting classes to improve performance * in the slowest platforms where pixel alpha blending just doesn't cut it. * - * @param blur Intensity/size of the shadow. + * @param offset Intensity/size of the shadow. */ - virtual void drawSquareShadow(int x, int y, int w, int h, int blur); - virtual void drawRoundedSquareShadow(int x, int y, int r, int w, int h, int blur); + virtual void drawSquareShadow(int x, int y, int w, int h, int offset); + virtual void drawRoundedSquareShadow(int x, int y, int r, int w, int h, int offset); /** * Calculates the color gradient on a given point. @@ -292,10 +300,12 @@ protected: */ virtual void drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m); - virtual void drawRoundedSquareShadow(int x, int y, int r, int w, int h, int blur) { - Base::drawRoundedSquareShadow(x, y, r, w, h, blur); -// VectorRenderer::applyConvolutionMatrix(VectorRenderer::kConvolutionHardBlur, -// Common::Rect(x, y, x + w + blur * 2, y + h + blur * 2)); + virtual void drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m, uint8 alpha_t, uint8 alpha_l, uint8 alpha_r, uint8 alpha_b); + + virtual void drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m); + + virtual void drawRoundedSquareShadow(int x, int y, int r, int w, int h, int offset) { + Base::drawRoundedSquareShadow(x, y, r, w, h, offset); } virtual void drawTabAlg(int x, int y, int w, int h, int r, diff --git a/graphics/cursorman.cpp b/graphics/cursorman.cpp index 6825767dfdc..133ee5fd71e 100644 --- a/graphics/cursorman.cpp +++ b/graphics/cursorman.cpp @@ -253,6 +253,7 @@ CursorManager::Cursor::Cursor(const void *data, uint w, uint h, int hotspotX, in _hotspotX = hotspotX; _hotspotY = hotspotY; _dontScale = dontScale; + _visible = false; } CursorManager::Cursor::~Cursor() { diff --git a/graphics/decoders/bmp.cpp b/graphics/decoders/bmp.cpp index bcfd0abbda0..2eabbb7631d 100644 --- a/graphics/decoders/bmp.cpp +++ b/graphics/decoders/bmp.cpp @@ -130,14 +130,14 @@ bool BitmapDecoder::loadStream(Common::SeekableReadStream &stream) { const int extraDataLength = (srcPitch % 4) ? 4 - (srcPitch % 4) : 0; if (bitsPerPixel == 8) { - byte *dst = (byte *)_surface->pixels; + byte *dst = (byte *)_surface->getPixels(); for (int32 i = 0; i < height; i++) { stream.read(dst + (height - i - 1) * width, width); stream.skip(extraDataLength); } } else if (bitsPerPixel == 24) { - byte *dst = (byte *)_surface->pixels + (height - 1) * _surface->pitch; + byte *dst = (byte *)_surface->getBasePtr(0, height - 1); for (int32 i = 0; i < height; i++) { for (uint32 j = 0; j < width; j++) { @@ -154,7 +154,7 @@ bool BitmapDecoder::loadStream(Common::SeekableReadStream &stream) { dst -= _surface->pitch * 2; } } else { // 32 bpp - byte *dst = (byte *)_surface->pixels + (height - 1) * _surface->pitch; + byte *dst = (byte *)_surface->getBasePtr(0, height - 1); for (int32 i = 0; i < height; i++) { for (uint32 j = 0; j < width; j++) { diff --git a/graphics/decoders/jpeg.cpp b/graphics/decoders/jpeg.cpp index 75fdcd6e5ac..c8588840959 100644 --- a/graphics/decoders/jpeg.cpp +++ b/graphics/decoders/jpeg.cpp @@ -20,8 +20,11 @@ * */ +// libjpeg uses forbidden symbols in its header. Thus, we need to allow them +// here. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + #include "graphics/pixelformat.h" -#include "graphics/yuv_to_rgb.h" #include "graphics/decoders/jpeg.h" #include "common/debug.h" @@ -29,35 +32,19 @@ #include "common/stream.h" #include "common/textconsole.h" +#ifdef USE_JPEG +// The original release of libjpeg v6b did not contain any extern "C" in case +// its header files are included in a C++ environment. To avoid any linking +// issues we need to add it on our own. +extern "C" { +#include +#include +} +#endif + namespace Graphics { -// Order used to traverse the quantization tables -static const uint8 _zigZagOrder[64] = { - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63 -}; - -JPEGDecoder::JPEGDecoder() : ImageDecoder(), - _stream(NULL), _w(0), _h(0), _numComp(0), _components(NULL), _numScanComp(0), - _scanComp(NULL), _currentComp(NULL), _rgbSurface(0) { - - // Initialize the quantization tables - for (int i = 0; i < JPEG_MAX_QUANT_TABLES; i++) - _quant[i] = NULL; - - // Initialize the Huffman tables - for (int i = 0; i < 2 * JPEG_MAX_HUFF_TABLES; i++) { - _huff[i].count = 0; - _huff[i].values = NULL; - _huff[i].sizes = NULL; - _huff[i].codes = NULL; - } +JPEGDecoder::JPEGDecoder() : ImageDecoder(), _surface(), _colorSpace(kColorSpaceRGBA) { } JPEGDecoder::~JPEGDecoder() { @@ -65,723 +52,215 @@ JPEGDecoder::~JPEGDecoder() { } const Surface *JPEGDecoder::getSurface() const { - // Make sure we have loaded data - if (!isLoaded()) - return 0; - - if (_rgbSurface) - return _rgbSurface; - - // Create an RGBA8888 surface - _rgbSurface = new Graphics::Surface(); - _rgbSurface->create(_w, _h, Graphics::PixelFormat(4, 8, 8, 8, 0, 24, 16, 8, 0)); - - // Get our component surfaces - const Graphics::Surface *yComponent = getComponent(1); - const Graphics::Surface *uComponent = getComponent(2); - const Graphics::Surface *vComponent = getComponent(3); - - YUVToRGBMan.convert444(_rgbSurface, Graphics::YUVToRGBManager::kScaleFull, (byte *)yComponent->pixels, (byte *)uComponent->pixels, (byte *)vComponent->pixels, yComponent->w, yComponent->h, yComponent->pitch, uComponent->pitch); - - return _rgbSurface; + return &_surface; } void JPEGDecoder::destroy() { - // Reset member variables - _stream = NULL; - _w = _h = 0; - _restartInterval = 0; + _surface.free(); +} - // Free the components - for (int c = 0; c < _numComp; c++) - _components[c].surface.free(); - delete[] _components; _components = NULL; - _numComp = 0; +#ifdef USE_JPEG +namespace { - // Free the scan components - delete[] _scanComp; _scanComp = NULL; - _numScanComp = 0; - _currentComp = NULL; +#define JPEG_BUFFER_SIZE 4096 - // Free the quantization tables - for (int i = 0; i < JPEG_MAX_QUANT_TABLES; i++) { - delete[] _quant[i]; - _quant[i] = NULL; +struct StreamSource : public jpeg_source_mgr { + Common::SeekableReadStream *stream; + bool startOfFile; + JOCTET buffer[JPEG_BUFFER_SIZE]; +}; + +void initSource(j_decompress_ptr cinfo) { + StreamSource *source = (StreamSource *)cinfo->src; + source->startOfFile = true; +} + +boolean fillInputBuffer(j_decompress_ptr cinfo) { + StreamSource *source = (StreamSource *)cinfo->src; + + uint32 bufferSize = source->stream->read((byte *)source->buffer, sizeof(source->buffer)); + if (bufferSize == 0) { + if (source->startOfFile) { + // An empty file is a fatal error + ERREXIT(cinfo, JERR_INPUT_EMPTY); + } else { + // Otherwise we insert an EOF marker + WARNMS(cinfo, JWRN_JPEG_EOF); + source->buffer[0] = (JOCTET)0xFF; + source->buffer[1] = (JOCTET)JPEG_EOI; + bufferSize = 2; + } } - // Free the Huffman tables - for (int i = 0; i < 2 * JPEG_MAX_HUFF_TABLES; i++) { - _huff[i].count = 0; - delete[] _huff[i].values; _huff[i].values = NULL; - delete[] _huff[i].sizes; _huff[i].sizes = NULL; - delete[] _huff[i].codes; _huff[i].codes = NULL; - } + source->next_input_byte = source->buffer; + source->bytes_in_buffer = bufferSize; + source->startOfFile = false; + + return TRUE; +} + +void skipInputData(j_decompress_ptr cinfo, long numBytes) { + StreamSource *source = (StreamSource *)cinfo->src; + + if (numBytes > 0) { + if (numBytes > (long)source->bytes_in_buffer) { + // In case we need to skip more bytes than there are in the buffer + // we will skip the remaining data and fill the buffer again + numBytes -= (long)source->bytes_in_buffer; + + // Skip the remaining bytes + source->stream->skip(numBytes); + + // Fill up the buffer again + (*source->fill_input_buffer)(cinfo); + } else { + source->next_input_byte += (size_t)numBytes; + source->bytes_in_buffer -= (size_t)numBytes; + } - if (_rgbSurface) { - _rgbSurface->free(); - delete _rgbSurface; } } +void termSource(j_decompress_ptr cinfo) { +} + +void jpeg_scummvm_src(j_decompress_ptr cinfo, Common::SeekableReadStream *stream) { + StreamSource *source; + + // Initialize the source in case it has not been done yet. + if (cinfo->src == NULL) { + cinfo->src = (jpeg_source_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(StreamSource)); + } + + source = (StreamSource *)cinfo->src; + source->init_source = &initSource; + source->fill_input_buffer = &fillInputBuffer; + source->skip_input_data = &skipInputData; + source->resync_to_restart = &jpeg_resync_to_restart; + source->term_source = &termSource; + source->bytes_in_buffer = 0; + source->next_input_byte = NULL; + + source->stream = stream; +} + +void errorExit(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + // This function is not allowed to return to the caller, thus we simply + // error out with our error handling here. + error("%s", buffer); +} + +void outputMessage(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + // Is using debug here a good idea? Or do we want to ignore all libjpeg + // messages? + debug(3, "libjpeg: %s", buffer); +} + +} // End of anonymous namespace +#endif + bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) { - // Reset member variables and tables from previous reads +#ifdef USE_JPEG + // Reset member variables from previous decodings destroy(); - // Save the input stream - _stream = &stream; + jpeg_decompress_struct cinfo; + jpeg_error_mgr jerr; - bool ok = true; - bool done = false; - while (!_stream->eos() && ok && !done) { - // Read the marker + // Initialize error handling callbacks + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = &errorExit; + cinfo.err->output_message = &outputMessage; - // WORKAROUND: While each and every JPEG file should end with - // an EOI (end of image) tag, in reality this may not be the - // case. For instance, at least one image in the Masterpiece - // edition of Myst doesn't, yet other programs are able to read - // the image without complaining. - // - // Apparently, the customary workaround is to insert a fake - // EOI tag. + // Initialize the decompression structure + jpeg_create_decompress(&cinfo); - uint16 marker = _stream->readByte(); - bool fakeEOI = false; + // Initialize our buffer handling + jpeg_scummvm_src(&cinfo, &stream); - if (_stream->eos()) { - fakeEOI = true; - marker = 0xFF; - } + // Read the file header + jpeg_read_header(&cinfo, TRUE); - if (marker != 0xFF) { - error("JPEG: Invalid marker[0]: 0x%02X", marker); - ok = false; + // We can request YUV output because Groovie requires it + switch (_colorSpace) { + case kColorSpaceRGBA: + cinfo.out_color_space = JCS_RGB; + break; + + case kColorSpaceYUV: + cinfo.out_color_space = JCS_YCbCr; + break; + } + + // Actually start decompressing the image + jpeg_start_decompress(&cinfo); + + // Allocate buffers for the output data + switch (_colorSpace) { + case kColorSpaceRGBA: + // We use RGBA8888 in this scenario + _surface.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat(4, 8, 8, 8, 0, 24, 16, 8, 0)); + break; + + case kColorSpaceYUV: + // We use YUV with 3 bytes per pixel otherwise. + // This is pretty ugly since our PixelFormat cannot express YUV... + _surface.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat(3, 0, 0, 0, 0, 0, 0, 0, 0)); + break; + } + + // Allocate buffer for one scanline + assert(cinfo.output_components == 3); + JDIMENSION pitch = cinfo.output_width * cinfo.output_components; + assert(_surface.pitch >= pitch); + JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, pitch, 1); + + // Go through the image data scanline by scanline + while (cinfo.output_scanline < cinfo.output_height) { + byte *dst = (byte *)_surface.getBasePtr(0, cinfo.output_scanline); + + jpeg_read_scanlines(&cinfo, buffer, 1); + + const byte *src = buffer[0]; + switch (_colorSpace) { + case kColorSpaceRGBA: { + for (int remaining = cinfo.output_width; remaining > 0; --remaining) { + byte r = *src++; + byte g = *src++; + byte b = *src++; + // We need to insert a alpha value of 255 (opaque) here. +#ifdef SCUMM_BIG_ENDIAN + *dst++ = r; + *dst++ = g; + *dst++ = b; + *dst++ = 0xFF; +#else + *dst++ = 0xFF; + *dst++ = b; + *dst++ = g; + *dst++ = r; +#endif + } + } break; + + case kColorSpaceYUV: + memcpy(dst, src, pitch); break; } - - while (marker == 0xFF && !_stream->eos()) - marker = _stream->readByte(); - - if (_stream->eos()) { - fakeEOI = true; - marker = 0xD9; - } - - if (fakeEOI) - warning("JPEG: Inserted fake EOI"); - - // Process the marker data - switch (marker) { - case 0xC0: // Start Of Frame - ok = readSOF0(); - break; - case 0xC4: // Define Huffman Tables - ok = readDHT(); - break; - case 0xD8: // Start Of Image - break; - case 0xD9: // End Of Image - done = true; - break; - case 0xDA: // Start Of Scan - ok = readSOS(); - break; - case 0xDB: // Define Quantization Tables - ok = readDQT(); - break; - case 0xE0: // JFIF/JFXX segment - ok = readJFIF(); - break; - case 0xDD: // Define Restart Interval - ok = readDRI(); - break; - case 0xFE: // Comment - _stream->seek(_stream->readUint16BE() - 2, SEEK_CUR); - break; - default: { // Unknown marker - uint16 size = _stream->readUint16BE(); - - if ((marker & 0xE0) != 0xE0) - warning("JPEG: Unknown marker %02X, skipping %d bytes", marker, size - 2); - - _stream->seek(size - 2, SEEK_CUR); - } - } } - _stream = 0; - return ok; -} - -bool JPEGDecoder::readJFIF() { - uint16 length = _stream->readUint16BE(); - uint32 tag = _stream->readUint32BE(); - - if (tag != MKTAG('J', 'F', 'I', 'F')) { - warning("JPEGDecoder::readJFIF() tag mismatch"); - return false; - } - - if (_stream->readByte() != 0) { // NULL - warning("JPEGDecoder::readJFIF() NULL mismatch"); - return false; - } - - byte majorVersion = _stream->readByte(); - byte minorVersion = _stream->readByte(); - if (majorVersion != 1 || minorVersion > 2) - warning("JPEGDecoder::readJFIF(): v%d.%02d JPEGs may not be handled correctly", majorVersion, minorVersion); - - /* byte densityUnits = */_stream->readByte(); - /* uint16 xDensity = */_stream->readUint16BE(); - /* uint16 yDensity = */_stream->readUint16BE(); - byte thumbW = _stream->readByte(); - byte thumbH = _stream->readByte(); - - _stream->seek(thumbW * thumbH * 3, SEEK_CUR); // Ignore thumbnail - if (length != (thumbW * thumbH * 3) + 16) { - warning("JPEGDecoder::readJFIF() length mismatch"); - return false; - } + // We are done with decompressing, thus free all the data + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); return true; -} - -// Marker 0xC0 (Start Of Frame, Baseline DCT) -bool JPEGDecoder::readSOF0() { - debug(5, "JPEG: readSOF0"); - uint16 size = _stream->readUint16BE(); - - // Read the sample precision - uint8 precision = _stream->readByte(); - if (precision != 8) { - warning("JPEG: Just 8 bit precision supported at the moment"); - return false; - } - - // Image size - _h = _stream->readUint16BE(); - _w = _stream->readUint16BE(); - - // Number of components - _numComp = _stream->readByte(); - if (size != 8 + 3 * _numComp) { - warning("JPEG: Invalid number of components"); - return false; - } - - // Allocate the new components - delete[] _components; - _components = new Component[_numComp]; - - // Read the components details - for (int c = 0; c < _numComp; c++) { - _components[c].id = _stream->readByte(); - _components[c].factorH = _stream->readByte(); - _components[c].factorV = _components[c].factorH & 0xF; - _components[c].factorH >>= 4; - _components[c].quantTableSelector = _stream->readByte(); - } - - return true; -} - -// Marker 0xC4 (Define Huffman Tables) -bool JPEGDecoder::readDHT() { - debug(5, "JPEG: readDHT"); - uint16 size = _stream->readUint16BE() - 2; - uint32 pos = _stream->pos(); - - while ((uint32)_stream->pos() < (size + pos)) { - // Read the table type and id - uint8 tableId = _stream->readByte(); - uint8 tableType = tableId >> 4; // type 0: DC, 1: AC - tableId &= 0xF; - uint8 tableNum = (tableId << 1) + tableType; - - // Free the Huffman table - delete[] _huff[tableNum].values; _huff[tableNum].values = NULL; - delete[] _huff[tableNum].sizes; _huff[tableNum].sizes = NULL; - delete[] _huff[tableNum].codes; _huff[tableNum].codes = NULL; - - // Read the number of values for each length - uint8 numValues[16]; - _huff[tableNum].count = 0; - for (int len = 0; len < 16; len++) { - numValues[len] = _stream->readByte(); - _huff[tableNum].count += numValues[len]; - } - - // Allocate memory for the current table - _huff[tableNum].values = new uint8[_huff[tableNum].count]; - _huff[tableNum].sizes = new uint8[_huff[tableNum].count]; - _huff[tableNum].codes = new uint16[_huff[tableNum].count]; - - // Read the table contents - int cur = 0; - for (int len = 0; len < 16; len++) { - for (int i = 0; i < numValues[len]; i++) { - _huff[tableNum].values[cur] = _stream->readByte(); - _huff[tableNum].sizes[cur] = len + 1; - cur++; - } - } - - // Fill the table of Huffman codes - cur = 0; - uint16 curCode = 0; - uint8 curCodeSize = _huff[tableNum].sizes[0]; - while (cur < _huff[tableNum].count) { - // Increase the code size to fit the request - while (_huff[tableNum].sizes[cur] != curCodeSize) { - curCode <<= 1; - curCodeSize++; - } - - // Assign the current code - _huff[tableNum].codes[cur] = curCode; - curCode++; - cur++; - } - } - - return true; -} - -// Marker 0xDA (Start Of Scan) -bool JPEGDecoder::readSOS() { - debug(5, "JPEG: readSOS"); - uint16 size = _stream->readUint16BE(); - - // Number of scan components - _numScanComp = _stream->readByte(); - if (size != 6 + 2 * _numScanComp) { - warning("JPEG: Invalid number of components"); - return false; - } - - // Allocate the new scan components - delete[] _scanComp; - _scanComp = new Component *[_numScanComp]; - - // Reset the maximum sampling factors - _maxFactorV = 0; - _maxFactorH = 0; - - // Component-specification parameters - for (int c = 0; c < _numScanComp; c++) { - // Read the desired component id - uint8 id = _stream->readByte(); - - // Search the component with the specified id - bool found = false; - for (int i = 0; !found && i < _numComp; i++) { - if (_components[i].id == id) { - // We found the desired component - found = true; - - // Assign the found component to the c'th scan component - _scanComp[c] = &_components[i]; - } - } - - if (!found) { - warning("JPEG: Invalid component"); - return false; - } - - // Read the entropy table selectors - _scanComp[c]->DCentropyTableSelector = _stream->readByte(); - _scanComp[c]->ACentropyTableSelector = _scanComp[c]->DCentropyTableSelector & 0xF; - _scanComp[c]->DCentropyTableSelector >>= 4; - - // Calculate the maximum sampling factors - if (_scanComp[c]->factorV > _maxFactorV) - _maxFactorV = _scanComp[c]->factorV; - - if (_scanComp[c]->factorH > _maxFactorH) - _maxFactorH = _scanComp[c]->factorH; - - // Initialize the DC predictor - _scanComp[c]->DCpredictor = 0; - } - - // Start of spectral selection - if (_stream->readByte() != 0) { - warning("JPEG: Progressive scanning not supported"); - return false; - } - - // End of spectral selection - if (_stream->readByte() != 63) { - warning("JPEG: Progressive scanning not supported"); - return false; - } - - // Successive approximation parameters - if (_stream->readByte() != 0) { - warning("JPEG: Progressive scanning not supported"); - return false; - } - - // Entropy coded sequence starts, initialize Huffman decoder - _bitsNumber = 0; - - // Read all the scan MCUs - uint16 xMCU = _w / (_maxFactorH * 8); - uint16 yMCU = _h / (_maxFactorV * 8); - - // Check for non- multiple-of-8 dimensions - if (_w % (_maxFactorH * 8) != 0) - xMCU++; - if (_h % (_maxFactorV * 8) != 0) - yMCU++; - - // Initialize the scan surfaces - for (uint16 c = 0; c < _numScanComp; c++) { - _scanComp[c]->surface.create(xMCU * _maxFactorH * 8, yMCU * _maxFactorV * 8, PixelFormat::createFormatCLUT8()); - } - - bool ok = true; - uint16 interval = _restartInterval; - - for (int y = 0; ok && (y < yMCU); y++) { - for (int x = 0; ok && (x < xMCU); x++) { - ok = readMCU(x, y); - - // If we have a restart interval, we'll need to reset a couple - // variables - if (_restartInterval != 0) { - interval--; - - if (interval == 0) { - interval = _restartInterval; - _bitsNumber = 0; - - for (byte i = 0; i < _numScanComp; i++) - _scanComp[i]->DCpredictor = 0; - } - } - } - } - - // Trim Component surfaces back to image height and width - // Note: Code using jpeg must use surface.pitch correctly... - for (uint16 c = 0; c < _numScanComp; c++) { - _scanComp[c]->surface.w = _w; - _scanComp[c]->surface.h = _h; - } - - return ok; -} - -// Marker 0xDB (Define Quantization Tables) -bool JPEGDecoder::readDQT() { - debug(5, "JPEG: readDQT"); - uint16 size = _stream->readUint16BE() - 2; - uint32 pos = _stream->pos(); - - while ((uint32)_stream->pos() < (pos + size)) { - // Read the table precision and id - uint8 tableId = _stream->readByte(); - bool highPrecision = (tableId & 0xF0) != 0; - - // Validate the table id - tableId &= 0xF; - if (tableId >= JPEG_MAX_QUANT_TABLES) { - warning("JPEG: Invalid quantization table"); - return false; - } - - // Create the new table if necessary - if (!_quant[tableId]) - _quant[tableId] = new uint16[64]; - - // Read the table (stored in Zig-Zag order) - for (int i = 0; i < 64; i++) - _quant[tableId][i] = highPrecision ? _stream->readUint16BE() : _stream->readByte(); - } - - return true; -} - -// Marker 0xDD (Define Restart Interval) -bool JPEGDecoder::readDRI() { - debug(5, "JPEG: readDRI"); - uint16 size = _stream->readUint16BE() - 2; - - if (size != 2) { - warning("JPEG: Invalid DRI size %d", size); - return false; - } - - _restartInterval = _stream->readUint16BE(); - debug(5, "Restart interval: %d", _restartInterval); - return true; -} - -bool JPEGDecoder::readMCU(uint16 xMCU, uint16 yMCU) { - bool ok = true; - for (int c = 0; ok && (c < _numComp); c++) { - // Set the current component - _currentComp = _scanComp[c]; - - // Read the data units of the current component - for (int y = 0; ok && (y < _scanComp[c]->factorV); y++) - for (int x = 0; ok && (x < _scanComp[c]->factorH); x++) - ok = readDataUnit(xMCU * _scanComp[c]->factorH + x, yMCU * _scanComp[c]->factorV + y); - } - - return ok; -} - -// triple-butterfly-add (and possible rounding) -#define xadd3(xa, xb, xc, xd, h) \ - p = xa + xb; \ - n = xa - xb; \ - xa = p + xc + h; \ - xb = n + xd + h; \ - xc = p - xc + h; \ - xd = n - xd + h; - -// butterfly-mul -#define xmul(xa, xb, k1, k2, sh) \ - n = k1 * (xa + xb); \ - p = xa; \ - xa = (n + (k2 - k1) * xb) >> sh; \ - xb = (n - (k2 + k1) * p) >> sh; - -// IDCT based on public domain code from http://halicery.com/jpeg/idct.html -void JPEGDecoder::idct1D8x8(int32 src[8], int32 dest[64], int32 ps, int32 half) { - int p, n; - - src[0] <<= 9; - src[1] <<= 7; - src[3] *= 181; - src[4] <<= 9; - src[5] *= 181; - src[7] <<= 7; - - // Even part - xmul(src[6], src[2], 277, 669, 0) - xadd3(src[0], src[4], src[6], src[2], half) - - // Odd part - xadd3(src[1], src[7], src[3], src[5], 0) - xmul(src[5], src[3], 251, 50, 6) - xmul(src[1], src[7], 213, 142, 6) - - dest[0 * 8] = (src[0] + src[1]) >> ps; - dest[1 * 8] = (src[4] + src[5]) >> ps; - dest[2 * 8] = (src[2] + src[3]) >> ps; - dest[3 * 8] = (src[6] + src[7]) >> ps; - dest[4 * 8] = (src[6] - src[7]) >> ps; - dest[5 * 8] = (src[2] - src[3]) >> ps; - dest[6 * 8] = (src[4] - src[5]) >> ps; - dest[7 * 8] = (src[0] - src[1]) >> ps; -} - -void JPEGDecoder::idct2D8x8(int32 block[64]) { - int32 tmp[64]; - - // Apply 1D IDCT to rows - for (int i = 0; i < 8; i++) - idct1D8x8(&block[i * 8], &tmp[i], 9, 1 << 8); - - // Apply 1D IDCT to columns - for (int i = 0; i < 8; i++) - idct1D8x8(&tmp[i * 8], &block[i], 12, 1 << 11); - } - -bool JPEGDecoder::readDataUnit(uint16 x, uint16 y) { - // Prepare an empty data array - int16 readData[64]; - for (int i = 1; i < 64; i++) - readData[i] = 0; - - // Read the DC component - readData[0] = _currentComp->DCpredictor + readDC(); - _currentComp->DCpredictor = readData[0]; - - // Read the AC components (stored in Zig-Zag) - readAC(readData); - - // Calculate the DCT coefficients from the input sequence - int32 block[64]; - for (uint8 i = 0; i < 64; i++) { - // Dequantize - int32 val = readData[i]; - int16 quant = _quant[_currentComp->quantTableSelector][i]; - val *= quant; - - // Store the normalized coefficients, undoing the Zig-Zag - block[_zigZagOrder[i]] = val; - } - - // Apply the IDCT - idct2D8x8(block); - - // Level shift to make the values unsigned - for (int i = 0; i < 64; i++) { - block[i] = block[i] + 128; - - if (block[i] < 0) - block[i] = 0; - - if (block[i] > 255) - block[i] = 255; - } - - // Paint the component surface - uint8 scalingV = _maxFactorV / _currentComp->factorV; - uint8 scalingH = _maxFactorH / _currentComp->factorH; - - // Convert coordinates from MCU blocks to pixels - x <<= 3; - y <<= 3; - - for (uint8 j = 0; j < 8; j++) { - for (uint16 sV = 0; sV < scalingV; sV++) { - // Get the beginning of the block line - byte *ptr = (byte *)_currentComp->surface.getBasePtr(x * scalingH, (y + j) * scalingV + sV); - - for (uint8 i = 0; i < 8; i++) { - for (uint16 sH = 0; sH < scalingH; sH++) { - *ptr = (byte)(block[j * 8 + i]); - ptr++; - } - } - } - } - - return true; -} - -int16 JPEGDecoder::readDC() { - // DC is type 0 - uint8 tableNum = _currentComp->DCentropyTableSelector << 1; - - // Get the number of bits to read - uint8 numBits = readHuff(tableNum); - - // Read the requested bits - return readSignedBits(numBits); -} - -void JPEGDecoder::readAC(int16 *out) { - // AC is type 1 - uint8 tableNum = (_currentComp->ACentropyTableSelector << 1) + 1; - - // Start reading AC element 1 - uint8 cur = 1; - while (cur < 64) { - uint8 s = readHuff(tableNum); - uint8 r = s >> 4; - s &= 0xF; - - if (s == 0) { - if (r == 15) { - // Skip 16 values - cur += 16; - } else { - // EOB: end of block - cur = 64; - } - } else { - // Skip r values - cur += r; - - // Read the next value - out[cur] = readSignedBits(s); - cur++; - } - } -} - -int16 JPEGDecoder::readSignedBits(uint8 numBits) { - uint16 ret = 0; - if (numBits > 16) - error("requested %d bits", numBits); //XXX - - // MSB=0 for negatives, 1 for positives - for (int i = 0; i < numBits; i++) - ret = (ret << 1) + readBit(); - - // Extend sign bits (PAG109) - if (!(ret >> (numBits - 1))) { - uint16 tmp = ((uint16)-1 << numBits) + 1; - ret = ret + tmp; - } - return ret; -} - -// TODO: optimize? -uint8 JPEGDecoder::readHuff(uint8 table) { - bool foundCode = false; - uint8 val = 0; - - uint8 cur = 0; - uint8 codeSize = 1; - uint16 code = readBit(); - while (!foundCode) { - // Prepare a code of the current size - while (codeSize < _huff[table].sizes[cur]) { - code = (code << 1) + readBit(); - codeSize++; - } - - // Compare the codes of the current size - while (!foundCode && (codeSize == _huff[table].sizes[cur])) { - if (code == _huff[table].codes[cur]) { - // Found the code - val = _huff[table].values[cur]; - foundCode = true; - } else { - // Continue reading - cur++; - } - } - } - - return val; -} - -uint8 JPEGDecoder::readBit() { - // Read a whole byte if necessary - if (_bitsNumber == 0) { - _bitsData = _stream->readByte(); - _bitsNumber = 8; - - // Detect markers - if (_bitsData == 0xFF) { - uint8 byte2 = _stream->readByte(); - - // A stuffed 0 validates the previous byte - if (byte2 != 0) { - if (byte2 == 0xDC) { - // DNL marker: Define Number of Lines - // TODO: terminate scan - warning("DNL marker detected: terminate scan"); - } else if (byte2 >= 0xD0 && byte2 <= 0xD7) { - debug(7, "RST%d marker detected", byte2 & 7); - _bitsData = _stream->readByte(); - } else { - warning("Error: marker 0x%02X read in entropy data", byte2); - } - } - } - } - _bitsNumber--; - - return (_bitsData & (1 << _bitsNumber)) ? 1 : 0; -} - -const Surface *JPEGDecoder::getComponent(uint c) const { - for (int i = 0; i < _numComp; i++) - if (_components[i].id == c) // We found the desired component - return &_components[i].surface; - - error("JPEGDecoder::getComponent: No component %d present", c); - return NULL; +#else + return false; +#endif } } // End of Graphics namespace diff --git a/graphics/decoders/jpeg.h b/graphics/decoders/jpeg.h index d59b72adf40..8460bc26983 100644 --- a/graphics/decoders/jpeg.h +++ b/graphics/decoders/jpeg.h @@ -40,100 +40,54 @@ class SeekableReadStream; namespace Graphics { -struct PixelFormat; - -#define JPEG_MAX_QUANT_TABLES 4 -#define JPEG_MAX_HUFF_TABLES 2 - class JPEGDecoder : public ImageDecoder { public: JPEGDecoder(); ~JPEGDecoder(); // ImageDecoder API - void destroy(); - bool loadStream(Common::SeekableReadStream &str); - const Surface *getSurface() const; + virtual void destroy(); + virtual bool loadStream(Common::SeekableReadStream &str); + virtual const Surface *getSurface() const; - bool isLoaded() const { return _numComp && _w && _h; } - uint16 getWidth() const { return _w; } - uint16 getHeight() const { return _h; } - const Surface *getComponent(uint c) const; + // Special API for JPEG + enum ColorSpace { + /** + * Output 32bit RGBA data. + * + * This is the default output. + */ + kColorSpaceRGBA, -private: - Common::SeekableReadStream *_stream; - uint16 _w, _h; - uint16 _restartInterval; - - // mutable so that we can convert to RGB only during - // a getSurface() call while still upholding the - // const requirement in other ImageDecoders - mutable Graphics::Surface *_rgbSurface; - - // Image components - uint8 _numComp; - struct Component { - // Global values - uint8 id; - uint8 factorH; - uint8 factorV; - uint8 quantTableSelector; - - // Scan specific values - uint8 DCentropyTableSelector; - uint8 ACentropyTableSelector; - int16 DCpredictor; - - // Result image for this component - Surface surface; + /** + * Output (interleaved) YUV data. + * + * Be aware that some images cannot be output in YUV mode. + * These are (non-standard) JPEG images which are in RGB colorspace. + * + * The resulting Surface will have a PixelFormat with 3 bytes per + * pixel and the remaining entries are completely zeroed. This works + * around the fact that PixelFormat can only describe RGB formats. + * + * You should only use this when you are really aware of what you are + * doing! + */ + kColorSpaceYUV }; - Component *_components; + /** + * Request the output color space. This can be used to obtain raw YUV + * data from the JPEG file. But this might not work for all files! + * + * The decoder itself defaults to RGBA. + * + * @param outSpace The color space to output. + */ + void setOutputColorSpace(ColorSpace outSpace) { _colorSpace = outSpace; } - // Scan components - uint8 _numScanComp; - Component **_scanComp; - Component *_currentComp; - - // Maximum sampling factors, used to calculate the interleaving of the MCU - uint8 _maxFactorV; - uint8 _maxFactorH; - - // Quantization tables - uint16 *_quant[JPEG_MAX_QUANT_TABLES]; - - // Huffman tables - struct HuffmanTable { - uint8 count; - uint8 *values; - uint8 *sizes; - uint16 *codes; - } _huff[2 * JPEG_MAX_HUFF_TABLES]; - - // Marker read functions - bool readJFIF(); - bool readSOF0(); - bool readDHT(); - bool readSOS(); - bool readDQT(); - bool readDRI(); - - // Helper functions - bool readMCU(uint16 xMCU, uint16 yMCU); - bool readDataUnit(uint16 x, uint16 y); - int16 readDC(); - void readAC(int16 *out); - int16 readSignedBits(uint8 numBits); - - // Huffman decoding - uint8 readHuff(uint8 table); - uint8 readBit(); - uint8 _bitsData; - uint8 _bitsNumber; - - // Inverse Discrete Cosine Transformation - static void idct1D8x8(int32 src[8], int32 dest[64], int32 ps, int32 half); - static void idct2D8x8(int32 block[64]); +private: + Graphics::Surface _surface; + ColorSpace _colorSpace; }; } // End of Graphics namespace diff --git a/graphics/decoders/tga.cpp b/graphics/decoders/tga.cpp index c3b9d840554..a9f136d238d 100644 --- a/graphics/decoders/tga.cpp +++ b/graphics/decoders/tga.cpp @@ -272,7 +272,7 @@ bool TGADecoder::readData(Common::SeekableReadStream &tga, byte imageType, byte } else if (imageType == TYPE_BW) { _surface.create(_surface.w, _surface.h, _format); - byte *data = (byte *)_surface.pixels; + byte *data = (byte *)_surface.getPixels(); uint32 count = _surface.w * _surface.h; while (count-- > 0) { @@ -318,7 +318,7 @@ bool TGADecoder::readDataRLE(Common::SeekableReadStream &tga, byte imageType, by if (imageType == TYPE_RLE_TRUECOLOR || imageType == TYPE_RLE_BW || imageType == TYPE_RLE_CMAP) { _surface.create(_surface.w, _surface.h, _format); uint32 count = _surface.w * _surface.h; - byte *data = (byte *)_surface.pixels; + byte *data = (byte *)_surface.getPixels(); while (count > 0) { uint32 header = tga.readByte(); diff --git a/graphics/font.cpp b/graphics/font.cpp index 3b00cd85686..a852274b068 100644 --- a/graphics/font.cpp +++ b/graphics/font.cpp @@ -128,7 +128,7 @@ void Font::drawString(Surface *dst, const Common::String &sOld, int x, int y, in w = getCharWidth(cur); if (x+w > rightX) break; - if (x >= leftX) + if (x+w >= leftX) drawChar(dst, str[i], x, y, color); x += w; } diff --git a/graphics/fonts/bdf.cpp b/graphics/fonts/bdf.cpp index 6d4befa37c8..e523a36ad57 100644 --- a/graphics/fonts/bdf.cpp +++ b/graphics/fonts/bdf.cpp @@ -102,7 +102,7 @@ void BdfFont::drawChar(Surface *dst, byte chr, const int tx, const int ty, const // equal to 50 and the decision of the theme designer? // asserting _data.maxAdvance <= 50: let the theme designer decide what looks best assert(_data.maxAdvance <= 50); - assert(dst->format.bytesPerPixel == 1 || dst->format.bytesPerPixel == 2); + assert(dst->format.bytesPerPixel == 1 || dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); const int idx = mapToIndex(chr); if (idx < 0) @@ -165,6 +165,8 @@ void BdfFont::drawChar(Surface *dst, byte chr, const int tx, const int ty, const drawCharIntern(ptr, dst->pitch, src, height, originalWidth, xStart, xEnd, color); else if (dst->format.bytesPerPixel == 2) drawCharIntern(ptr, dst->pitch, src, height, originalWidth, xStart, xEnd, color); + else if (dst->format.bytesPerPixel == 4) + drawCharIntern(ptr, dst->pitch, src, height, originalWidth, xStart, xEnd, color); } namespace { diff --git a/graphics/fonts/ttf.cpp b/graphics/fonts/ttf.cpp index 2b1dca1eaee..b9e9610d77c 100644 --- a/graphics/fonts/ttf.cpp +++ b/graphics/fonts/ttf.cpp @@ -322,7 +322,7 @@ void TTFFont::drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const int w = glyph.image.w; int h = glyph.image.h; - const uint8 *srcPos = (const uint8 *)glyph.image.getBasePtr(0, 0); + const uint8 *srcPos = (const uint8 *)glyph.image.getPixels(); // Make sure we are not drawing outside the screen bounds if (x < 0) { @@ -422,7 +422,7 @@ bool TTFFont::cacheGlyph(Glyph &glyph, FT_UInt &slot, uint chr) { srcPitch = -srcPitch; } - uint8 *dst = (uint8 *)glyph.image.getBasePtr(0, 0); + uint8 *dst = (uint8 *)glyph.image.getPixels(); memset(dst, 0, glyph.image.h * glyph.image.pitch); switch (bitmap.pixel_mode) { diff --git a/graphics/scaler.h b/graphics/scaler.h index 0abe6a3cc1a..242d2924346 100644 --- a/graphics/scaler.h +++ b/graphics/scaler.h @@ -90,10 +90,4 @@ inline bool createThumbnailFromScreen(Graphics::Surface *surf) { return true; } */ extern bool createThumbnail(Graphics::Surface *surf, const uint8 *pixels, int w, int h, const uint8 *palette); -/** - * Downscale screenshot to thumbnale size. - * - */ -extern bool createThumbnail(Graphics::Surface &out, Graphics::Surface &in); - #endif diff --git a/graphics/surface.cpp b/graphics/surface.cpp index 41ae8dcebb6..929157203e1 100644 --- a/graphics/surface.cpp +++ b/graphics/surface.cpp @@ -82,9 +82,55 @@ void Surface::free() { format = PixelFormat(); } +void Surface::init(uint16 width, uint16 height, uint16 newPitch, void *newPixels, const PixelFormat &f) { + w = width; + h = height; + pitch = newPitch; + pixels = newPixels; + format = f; +} + void Surface::copyFrom(const Surface &surf) { create(surf.w, surf.h, surf.format); - memcpy(pixels, surf.pixels, h * pitch); + if (surf.pitch == pitch) { + memcpy(pixels, surf.pixels, h * pitch); + } else { + const byte *src = (const byte *)surf.pixels; + byte *dst = (byte *)pixels; + for (int y = h; y > 0; --y) { + memcpy(dst, src, w * format.bytesPerPixel); + src += surf.pitch; + dst += pitch; + } + } +} + +Surface Surface::getSubArea(const Common::Rect &area) { + Common::Rect effectiveArea(area); + effectiveArea.clip(w, h); + + Surface subSurface; + subSurface.w = effectiveArea.width(); + subSurface.h = effectiveArea.height(); + subSurface.pitch = pitch; + subSurface.pixels = getBasePtr(area.left, area.top); + subSurface.format = format; + return subSurface; +} + +const Surface Surface::getSubArea(const Common::Rect &area) const { + Common::Rect effectiveArea(area); + effectiveArea.clip(w, h); + + Surface subSurface; + subSurface.w = effectiveArea.width(); + subSurface.h = effectiveArea.height(); + subSurface.pitch = pitch; + // We need to cast the const away here because a Surface always has a + // pointer to modifiable pixel data. + subSurface.pixels = const_cast(getBasePtr(area.left, area.top)); + subSurface.format = format; + return subSurface; } void Surface::hLine(int x, int y, int x2, uint32 color) { diff --git a/graphics/surface.h b/graphics/surface.h index 6c9e4646572..b08d4a5cb71 100644 --- a/graphics/surface.h +++ b/graphics/surface.h @@ -61,11 +61,13 @@ struct Surface { */ uint16 pitch; +protected: /** * The surface's pixel data. */ void *pixels; +public: /** * The pixel format of the surface. */ @@ -77,6 +79,33 @@ struct Surface { Surface() : w(0), h(0), pitch(0), pixels(0), format() { } + /** + * Return a pointer to the pixel data. + * + * @return Pointer to the pixel data. + */ + inline const void *getPixels() const { + return pixels; + } + + /** + * Return a pointer to the pixel data. + * + * @return Pointer to the pixel data. + */ + inline void *getPixels() { + return pixels; + } + + /** + * Sets the pixel data. + * + * Note that this is a simply a setter. Be careful what you are doing! + * + * @param newPixels The new pixel data. + */ + void setPixels(void *newPixels) { pixels = newPixels; } + /** * Return a pointer to the pixel at the specified point. * @@ -121,6 +150,20 @@ struct Surface { */ void free(); + /** + * Set up the Surface with user specified data. + * + * Note that this simply sets the 'internal' attributes of the Surface. It + * will not take care of freeing old data via free or similar! + * + * @param width Width of the pixel data. + * @param height Height of the pixel data. + * @param pitch The pitch of the pixel data. + * @param pixels The pixel data itself. + * @param format The pixel format of the pixel data. + */ + void init(uint16 width, uint16 height, uint16 pitch, void *pixels, const PixelFormat &format); + /** * Copy the data from another Surface. * @@ -134,11 +177,43 @@ struct Surface { */ void copyFrom(const Surface &surf); + /** + * Creates a Surface which represents a sub-area of this Surface object. + * + * The pixel (0, 0) of the returned Surface will be the same as Pixel + * (area.x, area.y) of this Surface. Changes to any of the Surface objects + * will change the shared pixel data. + * + * Note that the Surface returned is only valid as long as this Surface + * object is still alive (i.e. its pixel data is not destroyed or + * reallocated). Do *never* try to free the returned Surface. + * + * @param area The area which should be represented. Note that the area + * will get clipped in case it does not fit! + */ + Surface getSubArea(const Common::Rect &area); + + /** + * Creates a Surface which represents a sub-area of this Surface object. + * + * The pixel (0, 0) of the returned Surface will be the same as Pixel + * (area.x, area.y) of this Surface. + * + * Note that the Surface returned is only valid as long as this Surface + * object is still alive (i.e. its pixel data is not destroyed or + * reallocated). Do *never* try to free the returned Surface. + * + * @param area The area which should be represented. Note that the area + * will get clipped in case it does not fit! + */ + const Surface getSubArea(const Common::Rect &area) const; + /** * Convert the data to another pixel format. * * This works in-place. This means it will not create an additional buffer - * for the conversion process. The value of pixels might change though. + * for the conversion process. The value of 'pixels' might change though + * (that means it might realloc the pixel data). * * Note that you should only use this, when you created the Surface data via * create! Otherwise this function has undefined behavior. diff --git a/graphics/yuv_to_rgb.cpp b/graphics/yuv_to_rgb.cpp index 6043315a13b..2a485fa6647 100644 --- a/graphics/yuv_to_rgb.cpp +++ b/graphics/yuv_to_rgb.cpp @@ -229,7 +229,7 @@ void convertYUV444ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup void YUVToRGBManager::convert444(Graphics::Surface *dst, YUVToRGBManager::LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Sanity checks - assert(dst && dst->pixels); + assert(dst && dst->getPixels()); assert(dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); assert(ySrc && uSrc && vSrc); @@ -237,9 +237,9 @@ void YUVToRGBManager::convert444(Graphics::Surface *dst, YUVToRGBManager::Lumina // Use a templated function to avoid an if check on every pixel if (dst->format.bytesPerPixel == 2) - convertYUV444ToRGB((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV444ToRGB((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); else - convertYUV444ToRGB((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV444ToRGB((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); } template @@ -283,7 +283,7 @@ void convertYUV420ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup void YUVToRGBManager::convert420(Graphics::Surface *dst, YUVToRGBManager::LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Sanity checks - assert(dst && dst->pixels); + assert(dst && dst->getPixels()); assert(dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); assert(ySrc && uSrc && vSrc); assert((yWidth & 1) == 0); @@ -293,9 +293,9 @@ void YUVToRGBManager::convert420(Graphics::Surface *dst, YUVToRGBManager::Lumina // Use a templated function to avoid an if check on every pixel if (dst->format.bytesPerPixel == 2) - convertYUV420ToRGB((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV420ToRGB((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); else - convertYUV420ToRGB((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV420ToRGB((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); } #define READ_QUAD(ptr, prefix) \ @@ -368,7 +368,7 @@ void convertYUV410ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup void YUVToRGBManager::convert410(Graphics::Surface *dst, YUVToRGBManager::LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Sanity checks - assert(dst && dst->pixels); + assert(dst && dst->getPixels()); assert(dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); assert(ySrc && uSrc && vSrc); assert((yWidth & 3) == 0); @@ -378,9 +378,9 @@ void YUVToRGBManager::convert410(Graphics::Surface *dst, YUVToRGBManager::Lumina // Use a templated function to avoid an if check on every pixel if (dst->format.bytesPerPixel == 2) - convertYUV410ToRGB((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV410ToRGB((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); else - convertYUV410ToRGB((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV410ToRGB((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); } } // End of namespace Graphics diff --git a/graphics/yuva_to_rgba.cpp b/graphics/yuva_to_rgba.cpp index a2d09d2f1e8..163f23990f5 100644 --- a/graphics/yuva_to_rgba.cpp +++ b/graphics/yuva_to_rgba.cpp @@ -248,7 +248,7 @@ void convertYUVA420ToRGBA(byte *dstPtr, int dstPitch, const YUVAToRGBALookup *lo void YUVAToRGBAManager::convert420(Graphics::Surface *dst, YUVAToRGBAManager::LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, const byte *aSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Sanity checks - assert(dst && dst->pixels); + assert(dst && dst->getPixels()); assert(dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); assert(ySrc && uSrc && vSrc); assert((yWidth & 1) == 0); @@ -258,9 +258,9 @@ void YUVAToRGBAManager::convert420(Graphics::Surface *dst, YUVAToRGBAManager::Lu // Use a templated function to avoid an if check on every pixel if (dst->format.bytesPerPixel == 2) - convertYUVA420ToRGBA((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, aSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUVA420ToRGBA((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, aSrc, yWidth, yHeight, yPitch, uvPitch); else - convertYUVA420ToRGBA((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, aSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUVA420ToRGBA((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, aSrc, yWidth, yHeight, yPitch, uvPitch); } } // End of namespace Graphics diff --git a/gui/EventRecorder.cpp b/gui/EventRecorder.cpp index 89a226922a9..71f66911e94 100644 --- a/gui/EventRecorder.cpp +++ b/gui/EventRecorder.cpp @@ -23,12 +23,12 @@ #include "gui/EventRecorder.h" +#ifdef ENABLE_EVENTRECORDER + namespace Common { DECLARE_SINGLETON(GUI::EventRecorder); } -#ifdef ENABLE_EVENTRECORDER - #include "common/debug-channels.h" #include "backends/timer/sdl/sdl-timer.h" #include "backends/mixer/sdl/sdl-mixer.h" @@ -372,8 +372,8 @@ SdlMixerManager *EventRecorder::getMixerManager() { } } -void EventRecorder::getConfigFromDomain(Common::ConfigManager::Domain *domain) { - for (Common::ConfigManager::Domain::iterator entry = domain->begin(); entry!= domain->end(); ++entry) { +void EventRecorder::getConfigFromDomain(const Common::ConfigManager::Domain *domain) { + for (Common::ConfigManager::Domain::const_iterator entry = domain->begin(); entry!= domain->end(); ++entry) { _playbackFile->getHeader().settingsRecords[entry->_key] = entry->_value; } } @@ -386,7 +386,7 @@ void EventRecorder::getConfig() { void EventRecorder::applyPlaybackSettings() { - for (Common::StringMap::iterator i = _playbackFile->getHeader().settingsRecords.begin(); i != _playbackFile->getHeader().settingsRecords.end(); ++i) { + for (Common::StringMap::const_iterator i = _playbackFile->getHeader().settingsRecords.begin(); i != _playbackFile->getHeader().settingsRecords.end(); ++i) { Common::String currentValue = ConfMan.get(i->_key); if (currentValue != i->_value) { ConfMan.set(i->_key, i->_value, ConfMan.kTransientDomain); @@ -400,7 +400,7 @@ void EventRecorder::applyPlaybackSettings() { } void EventRecorder::removeDifferentEntriesInDomain(Common::ConfigManager::Domain *domain) { - for (Common::ConfigManager::Domain::iterator entry = domain->begin(); entry!= domain->end(); ++entry) { + for (Common::ConfigManager::Domain::const_iterator entry = domain->begin(); entry!= domain->end(); ++entry) { if (_playbackFile->getHeader().settingsRecords.find(entry->_key) == _playbackFile->getHeader().settingsRecords.end()) { debugC(1, kDebugLevelEventRec, "playback:action=\"Apply settings\" checksettings:key=%s storedvalue=%s currentvalue="" result=different", entry->_key.c_str(), entry->_value.c_str()); domain->erase(entry->_key); @@ -481,6 +481,8 @@ Common::List EventRecorder::mapEvent(const Common::Event &ev, Com default: return Common::DefaultEventMapper::mapEvent(ev, source); } + + return Common::DefaultEventMapper::mapEvent(ev, source); } void EventRecorder::setGameMd5(const ADGameDescription *gameDesc) { @@ -522,7 +524,7 @@ bool EventRecorder::grabScreenAndComputeMD5(Graphics::Surface &screen, uint8 md5 warning("Can't save screenshot"); return false; } - Common::MemoryReadStream bitmapStream((const byte*)screen.pixels, screen.w * screen.h * screen.format.bytesPerPixel); + Common::MemoryReadStream bitmapStream((const byte*)screen.getPixels(), screen.w * screen.h * screen.format.bytesPerPixel); computeStreamMD5(bitmapStream, md5); return true; } diff --git a/gui/EventRecorder.h b/gui/EventRecorder.h index 60fe07fafc0..b2a549ece80 100644 --- a/gui/EventRecorder.h +++ b/gui/EventRecorder.h @@ -199,7 +199,7 @@ private: void setFileHeader(); void setGameMd5(const ADGameDescription *gameDesc); void getConfig(); - void getConfigFromDomain(Common::ConfigManager::Domain *domain); + void getConfigFromDomain(const Common::ConfigManager::Domain *domain); void removeDifferentEntriesInDomain(Common::ConfigManager::Domain *domain); void applyPlaybackSettings(); @@ -233,61 +233,6 @@ private: } // End of namespace GUI -#else - -#ifdef SDL_BACKEND -#include "backends/timer/default/default-timer.h" -#include "backends/mixer/sdl/sdl-mixer.h" -#endif - -#define g_eventRec (GUI::EventRecorder::instance()) - -namespace GUI { - -class EventRecorder : private Common::EventSource, public Common::Singleton, private Common::DefaultEventMapper { - friend class Common::Singleton; - - public: - EventRecorder() { -#ifdef SDL_BACKEND - _timerManager = NULL; - _realMixerManager = NULL; -#endif - } - ~EventRecorder() {} - - bool pollEvent(Common::Event &ev) { return false; } - void RegisterEventSource() {} - void deinit() {} - void suspendRecording() {} - void resumeRecording() {} - void preDrawOverlayGui() {} - void postDrawOverlayGui() {} - void processGameDescription(const ADGameDescription *desc) {} - void updateSubsystems() {} - uint32 getRandomSeed(const Common::String &name) { return g_system->getMillis(); } - Common::SaveFileManager *getSaveManager(Common::SaveFileManager *realSaveManager) { return realSaveManager; } - -#ifdef SDL_BACKEND - private: - DefaultTimerManager *_timerManager; - SdlMixerManager *_realMixerManager; - - public: - DefaultTimerManager *getTimerManager() { return _timerManager; } - void registerTimerManager(DefaultTimerManager *timerManager) { _timerManager = timerManager; } - - SdlMixerManager *getMixerManager() { return _realMixerManager; } - void registerMixerManager(SdlMixerManager *mixerManager) { _realMixerManager = mixerManager; } - - void processMillis(uint32 &millis, bool skipRecord) {} - bool processDelayMillis() { return false; } -#endif - -}; - -} // namespace GUI - #endif // ENABLE_EVENTRECORDER #endif diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp index 996e8f24248..ebfe709826d 100644 --- a/gui/ThemeEngine.cpp +++ b/gui/ThemeEngine.cpp @@ -122,15 +122,16 @@ protected: class ThemeItemTextData : public ThemeItem { public: - ThemeItemTextData(ThemeEngine *engine, const TextDrawData *data, const TextColorData *color, const Common::Rect &area, const Common::String &text, - Graphics::TextAlign alignH, GUI::ThemeEngine::TextAlignVertical alignV, + ThemeItemTextData(ThemeEngine *engine, const TextDrawData *data, const TextColorData *color, const Common::Rect &area, const Common::Rect &textDrawableArea, + const Common::String &text, Graphics::TextAlign alignH, GUI::ThemeEngine::TextAlignVertical alignV, bool ellipsis, bool restoreBg, int deltaX) : ThemeItem(engine, area), _data(data), _color(color), _text(text), _alignH(alignH), _alignV(alignV), - _ellipsis(ellipsis), _restoreBg(restoreBg), _deltax(deltaX) {} + _ellipsis(ellipsis), _restoreBg(restoreBg), _deltax(deltaX), _textDrawableArea(textDrawableArea) {} void drawSelf(bool draw, bool restore); protected: + Common::Rect _textDrawableArea; const TextDrawData *_data; const TextColorData *_color; Common::String _text; @@ -246,7 +247,7 @@ void ThemeItemTextData::drawSelf(bool draw, bool restore) { if (draw) { _engine->renderer()->setFgColor(_color->r, _color->g, _color->b); - _engine->renderer()->drawString(_data->_fontPtr, _text, _area, _alignH, _alignV, _deltax, _ellipsis); + _engine->renderer()->drawString(_data->_fontPtr, _text, _area, _alignH, _alignV, _deltax, _ellipsis, _textDrawableArea); } _engine->addDirtyRect(_area); @@ -343,9 +344,9 @@ ThemeEngine::~ThemeEngine() { *********************************************************/ const ThemeEngine::Renderer ThemeEngine::_rendererModes[] = { { _s("Disabled GFX"), _sc("Disabled GFX", "lowres"), "none", kGfxDisabled }, - { _s("Standard Renderer (16bpp)"), _s("Standard (16bpp)"), "normal_16bpp", kGfxStandard16bit }, + { _s("Standard Renderer"), _s("Standard"), "normal", kGfxStandard }, #ifndef DISABLE_FANCY_THEMES - { _s("Antialiased Renderer (16bpp)"), _s("Antialiased (16bpp)"), "aa_16bpp", kGfxAntialias16bit } + { _s("Antialiased Renderer"), _s("Antialiased"), "antialias", kGfxAntialias } #endif }; @@ -353,9 +354,9 @@ const uint ThemeEngine::_rendererModesSize = ARRAYSIZE(ThemeEngine::_rendererMod const ThemeEngine::GraphicsMode ThemeEngine::_defaultRendererMode = #ifndef DISABLE_FANCY_THEMES - ThemeEngine::kGfxAntialias16bit; + ThemeEngine::kGfxAntialias; #else - ThemeEngine::kGfxStandard16bit; + ThemeEngine::kGfxStandard; #endif ThemeEngine::GraphicsMode ThemeEngine::findMode(const Common::String &cfg) { @@ -389,7 +390,7 @@ bool ThemeEngine::init() { _overlayFormat = _system->getOverlayFormat(); setGraphicsMode(_graphicsMode); - if (_screen.pixels && _backBuffer.pixels) { + if (_screen.getPixels() && _backBuffer.getPixels()) { _initOk = true; } @@ -439,7 +440,7 @@ bool ThemeEngine::init() { void ThemeEngine::clearAll() { if (_initOk) { _system->clearOverlay(); - _system->grabOverlay(_screen.pixels, _screen.pitch); + _system->grabOverlay(_screen.getPixels(), _screen.pitch); } } @@ -494,13 +495,17 @@ void ThemeEngine::disable() { void ThemeEngine::setGraphicsMode(GraphicsMode mode) { switch (mode) { - case kGfxStandard16bit: + case kGfxStandard: #ifndef DISABLE_FANCY_THEMES - case kGfxAntialias16bit: + case kGfxAntialias: #endif - _bytesPerPixel = sizeof(uint16); - break; - + if (g_system->getOverlayFormat().bytesPerPixel == 4) { + _bytesPerPixel = sizeof(uint32); + break; + } else if (g_system->getOverlayFormat().bytesPerPixel == 2) { + _bytesPerPixel = sizeof(uint16); + break; + } default: error("Invalid graphics mode"); } @@ -517,6 +522,12 @@ void ThemeEngine::setGraphicsMode(GraphicsMode mode) { delete _vectorRenderer; _vectorRenderer = Graphics::createRenderer(mode); _vectorRenderer->setSurface(&_screen); + + // Since we reinitialized our screen surfaces we know nothing has been + // drawn so far. Sometimes we still end up with dirty screen bits in the + // list. Clearing it avoids invalid overlay writes when the backend + // resizes the overlay. + _dirtyScreen.clear(); } void WidgetDrawData::calcBackgroundOffset() { @@ -832,7 +843,7 @@ void ThemeEngine::queueDD(DrawData type, const Common::Rect &r, uint32 dynamic, } void ThemeEngine::queueDDText(TextData type, TextColor color, const Common::Rect &r, const Common::String &text, bool restoreBg, - bool ellipsis, Graphics::TextAlign alignH, TextAlignVertical alignV, int deltax) { + bool ellipsis, Graphics::TextAlign alignH, TextAlignVertical alignV, int deltax, const Common::Rect &drawableTextArea) { if (_texts[type] == 0) return; @@ -840,7 +851,7 @@ void ThemeEngine::queueDDText(TextData type, TextColor color, const Common::Rect Common::Rect area = r; area.clip(_screen.w, _screen.h); - ThemeItemTextData *q = new ThemeItemTextData(this, _texts[type], _textColors[color], area, text, alignH, alignV, ellipsis, restoreBg, deltax); + ThemeItemTextData *q = new ThemeItemTextData(this, _texts[type], _textColors[color], area, drawableTextArea, text, alignH, alignV, ellipsis, restoreBg, deltax); if (_buffering) { _screenQueue.push_back(q); @@ -1111,7 +1122,7 @@ void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, int tabWidth, co } } -void ThemeEngine::drawText(const Common::Rect &r, const Common::String &str, WidgetStateInfo state, Graphics::TextAlign align, TextInversionState inverted, int deltax, bool useEllipsis, FontStyle font, FontColor color, bool restore) { +void ThemeEngine::drawText(const Common::Rect &r, const Common::String &str, WidgetStateInfo state, Graphics::TextAlign align, TextInversionState inverted, int deltax, bool useEllipsis, FontStyle font, FontColor color, bool restore, const Common::Rect &drawableTextArea) { if (!ready()) return; @@ -1181,7 +1192,7 @@ void ThemeEngine::drawText(const Common::Rect &r, const Common::String &str, Wid break; } - queueDDText(textId, colorId, r, str, restore, useEllipsis, align, kTextAlignVCenter, deltax); + queueDDText(textId, colorId, r, str, restore, useEllipsis, align, kTextAlignVCenter, deltax, drawableTextArea); } void ThemeEngine::drawChar(const Common::Rect &r, byte ch, const Graphics::Font *font, WidgetStateInfo state, FontColor color) { @@ -1219,7 +1230,7 @@ void ThemeEngine::updateScreen(bool render) { } _vectorRenderer->setSurface(&_screen); - memcpy(_screen.getBasePtr(0, 0), _backBuffer.getBasePtr(0, 0), _screen.pitch * _screen.h); + memcpy(_screen.getPixels(), _backBuffer.getPixels(), _screen.pitch * _screen.h); _bufferQueue.clear(); } @@ -1287,7 +1298,7 @@ void ThemeEngine::openDialog(bool doBuffer, ShadingStyle style) { addDirtyRect(Common::Rect(0, 0, _screen.w, _screen.h)); } - memcpy(_backBuffer.getBasePtr(0, 0), _screen.getBasePtr(0, 0), _screen.pitch * _screen.h); + memcpy(_backBuffer.getPixels(), _screen.getPixels(), _screen.pitch * _screen.h); _vectorRenderer->setSurface(&_screen); } @@ -1320,22 +1331,31 @@ bool ThemeEngine::createCursor(const Common::String &filename, int hotspotX, int memset(_cursor, 0xFF, sizeof(byte) * _cursorWidth * _cursorHeight); // the transparent color is 0xFF00FF - const int colTransparent = _overlayFormat.RGBToColor(0xFF, 0, 0xFF); + const uint32 colTransparent = _overlayFormat.RGBToColor(0xFF, 0, 0xFF); // Now, scan the bitmap. We have to convert it from 16 bit color mode // to 8 bit mode, and have to create a suitable palette on the fly. uint colorsFound = 0; Common::HashMap colorToIndex; - const OverlayColor *src = (const OverlayColor *)cursor->pixels; + const byte *src = (const byte *)cursor->getPixels(); for (uint y = 0; y < _cursorHeight; ++y) { for (uint x = 0; x < _cursorWidth; ++x) { + uint32 color = colTransparent; byte r, g, b; + if (cursor->format.bytesPerPixel == 2) { + color = READ_UINT16(src); + } else if (cursor->format.bytesPerPixel == 4) { + color = READ_UINT32(src); + } + + src += cursor->format.bytesPerPixel; + // Skip transparency - if (src[x] == colTransparent) + if (color == colTransparent) continue; - _overlayFormat.colorToRGB(src[x], r, g, b); + cursor->format.colorToRGB(color, r, g, b); const int col = (r << 16) | (g << 8) | b; // If there is no entry yet for this color in the palette: Add one @@ -1357,7 +1377,6 @@ bool ThemeEngine::createCursor(const Common::String &filename, int hotspotX, int const int index = colorToIndex[col]; _cursor[y * _cursorWidth + x] = index; } - src += _cursorWidth; } _useCursor = true; diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h index 160ceb3259c..4dffb13e712 100644 --- a/gui/ThemeEngine.h +++ b/gui/ThemeEngine.h @@ -29,6 +29,7 @@ #include "common/hashmap.h" #include "common/list.h" #include "common/str.h" +#include "common/rect.h" #include "graphics/surface.h" #include "graphics/font.h" @@ -39,10 +40,6 @@ class OSystem; -namespace Common { -struct Rect; -} - namespace Graphics { struct DrawStep; class VectorRenderer; @@ -250,8 +247,8 @@ public: */ enum GraphicsMode { kGfxDisabled = 0, ///< No GFX - kGfxStandard16bit, ///< 2BPP with the standard (aliased) renderer. - kGfxAntialias16bit ///< 2BPP with the optimized AA renderer. + kGfxStandard, ///< Standard (aliased) renderer. + kGfxAntialias ///< Optimized AA renderer. }; /** Constant value to expand dirty rectangles, to make sure they are fully copied */ @@ -376,7 +373,7 @@ public: void drawDialogBackground(const Common::Rect &r, DialogBackground type, WidgetStateInfo state = kStateEnabled); - void drawText(const Common::Rect &r, const Common::String &str, WidgetStateInfo state = kStateEnabled, Graphics::TextAlign align = Graphics::kTextAlignCenter, TextInversionState inverted = kTextInversionNone, int deltax = 0, bool useEllipsis = true, FontStyle font = kFontStyleBold, FontColor color = kFontColorNormal, bool restore = true); + void drawText(const Common::Rect &r, const Common::String &str, WidgetStateInfo state = kStateEnabled, Graphics::TextAlign align = Graphics::kTextAlignCenter, TextInversionState inverted = kTextInversionNone, int deltax = 0, bool useEllipsis = true, FontStyle font = kFontStyleBold, FontColor color = kFontColorNormal, bool restore = true, const Common::Rect &drawableTextArea = Common::Rect(0, 0, 0, 0)); void drawChar(const Common::Rect &r, byte ch, const Graphics::Font *font, WidgetStateInfo state = kStateEnabled, FontColor color = kFontColorNormal); @@ -588,7 +585,7 @@ protected: */ void queueDD(DrawData type, const Common::Rect &r, uint32 dynamic = 0, bool restore = false); void queueDDText(TextData type, TextColor color, const Common::Rect &r, const Common::String &text, bool restoreBg, - bool elipsis, Graphics::TextAlign alignH = Graphics::kTextAlignLeft, TextAlignVertical alignV = kTextAlignVTop, int deltax = 0); + bool elipsis, Graphics::TextAlign alignH = Graphics::kTextAlignLeft, TextAlignVertical alignV = kTextAlignVTop, int deltax = 0, const Common::Rect &drawableTextArea = Common::Rect(0, 0, 0, 0)); void queueBitmap(const Graphics::Surface *bitmap, const Common::Rect &r, bool alpha); /** diff --git a/gui/about.cpp b/gui/about.cpp index 33fa963f2f2..62869117dd2 100644 --- a/gui/about.cpp +++ b/gui/about.cpp @@ -180,9 +180,10 @@ void AboutDialog::close() { } void AboutDialog::drawDialog() { -// g_gui.theme()->setDrawArea(Common::Rect(_x, _y, _x+_w, _y+_h)); Dialog::drawDialog(); + setTextDrawableArea(Common::Rect(_x, _y, _x + _w, _y + _h)); + // Draw text // TODO: Add a "fade" effect for the top/bottom text lines // TODO: Maybe prerender all of the text into another surface, @@ -239,8 +240,8 @@ void AboutDialog::drawDialog() { while (*str && *str == ' ') str++; - if (*str && y > _y && y + g_gui.theme()->getFontHeight() < _y + _h) - g_gui.theme()->drawText(Common::Rect(_x + _xOff, y, _x + _w - _xOff, y + g_gui.theme()->getFontHeight()), str, state, align, ThemeEngine::kTextInversionNone, 0, false); + if (*str) + g_gui.theme()->drawText(Common::Rect(_x + _xOff, y, _x + _w - _xOff, y + g_gui.theme()->getFontHeight()), str, state, align, ThemeEngine::kTextInversionNone, 0, false, ThemeEngine::kFontStyleBold, ThemeEngine::kFontColorNormal, true, _textDrawableArea); y += _lineHeight; } } diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp index 163aeebc92a..2abc11832bd 100644 --- a/gui/gui-manager.cpp +++ b/gui/gui-manager.cpp @@ -258,8 +258,10 @@ void GuiManager::runLoop() { if (activeDialog == 0) return; +#ifdef ENABLE_EVENTRECORDER // Suspend recording while GUI is shown g_eventRec.suspendRecording(); +#endif if (!_stateIsSaved) { saveState(); @@ -361,8 +363,10 @@ void GuiManager::runLoop() { _useStdCursor = false; } +#ifdef ENABLE_EVENTRECORDER // Resume recording once GUI is shown g_eventRec.resumeRecording(); +#endif } #pragma mark - diff --git a/gui/object.cpp b/gui/object.cpp index 73c4f74d6c2..189a286eadd 100644 --- a/gui/object.cpp +++ b/gui/object.cpp @@ -29,7 +29,7 @@ namespace GUI { GuiObject::GuiObject(const Common::String &name) - : _x(-1000), _y(-1000), _w(0), _h(0), _name(name), _firstWidget(0) { + : _x(-1000), _y(-1000), _w(0), _h(0), _name(name), _firstWidget(0), _textDrawableArea(Common::Rect(0, 0, 0, 0)) { reflowLayout(); } diff --git a/gui/object.h b/gui/object.h index bce3cd7846b..dac3341b5a5 100644 --- a/gui/object.h +++ b/gui/object.h @@ -24,6 +24,7 @@ #include "common/scummsys.h" #include "common/str.h" +#include "common/rect.h" namespace GUI { @@ -59,6 +60,8 @@ class Widget; class GuiObject : public CommandReceiver { friend class Widget; protected: + Common::Rect _textDrawableArea; + int16 _x, _y; uint16 _w, _h; const Common::String _name; @@ -66,10 +69,12 @@ protected: Widget *_firstWidget; public: - GuiObject(int x, int y, int w, int h) : _x(x), _y(y), _w(w), _h(h), _firstWidget(0) { } + GuiObject(int x, int y, int w, int h) : _x(x), _y(y), _w(w), _h(h), _firstWidget(0), _textDrawableArea(Common::Rect(0, 0, 0, 0)) { } GuiObject(const Common::String &name); ~GuiObject(); + virtual void setTextDrawableArea(const Common::Rect &r) { _textDrawableArea = r; } + virtual int16 getAbsX() const { return _x; } virtual int16 getAbsY() const { return _y; } virtual int16 getChildX() const { return getAbsX(); } diff --git a/gui/predictivedialog.cpp b/gui/predictivedialog.cpp index 5ce093e054f..ef94ec6d501 100644 --- a/gui/predictivedialog.cpp +++ b/gui/predictivedialog.cpp @@ -69,7 +69,7 @@ enum { PredictiveDialog::PredictiveDialog() : Dialog("Predictive") { new StaticTextWidget(this, "Predictive.Headline", "Enter Text"); - _btns = (ButtonWidget **)calloc(1, sizeof(ButtonWidget *) * 16); + _btns = (ButtonWidget **)calloc(16, sizeof(ButtonWidget *)); _btns[kCancelAct] = new ButtonWidget(this, "Predictive.Cancel", _("Cancel") , 0, kCancelCmd); _btns[kOkAct] = new ButtonWidget(this, "Predictive.OK", _("Ok") , 0, kOkCmd); @@ -144,6 +144,7 @@ PredictiveDialog::PredictiveDialog() : Dialog("Predictive") { _currentWord.clear(); _wordNumber = 0; _numMatchingWords = 0; + memset(_predictiveResult, 0, sizeof(_predictiveResult)); _lastbutton = kNoAct; _mode = kModePre; @@ -420,6 +421,9 @@ void PredictiveDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 d case kCancelCmd: saveUserDictToFile(); close(); + // When we cancel the dialog no result should be returned. Thus, we + // will invalidate any result here. + _predictiveResult[0] = 0; return; case kOkCmd: _currBtn = kOkAct; @@ -614,7 +618,7 @@ void PredictiveDialog::handleTickle() { void PredictiveDialog::mergeDicts() { _unitedDict.dictLineCount = _predictiveDict.dictLineCount + _userDict.dictLineCount; - _unitedDict.dictLine = (char **)calloc(1, sizeof(char *) * _unitedDict.dictLineCount); + _unitedDict.dictLine = (char **)calloc(_unitedDict.dictLineCount, sizeof(char *)); if (!_unitedDict.dictLine) { debug("Predictive Dialog: cannot allocate memory for united dic"); @@ -853,7 +857,7 @@ void PredictiveDialog::addWord(Dict &dict, const Common::String &word, const Com } // start from here are INSERTING new line to dictionaty ( dict ) - char **newDictLine = (char **)calloc(1, sizeof(char *) * (dict.dictLineCount + 1)); + char **newDictLine = (char **)calloc(dict.dictLineCount + 1, sizeof(char *)); if (!newDictLine) { warning("Predictive Dialog: cannot allocate memory for index buffer"); @@ -861,7 +865,6 @@ void PredictiveDialog::addWord(Dict &dict, const Common::String &word, const Com return; } - newDictLine[dict.dictLineCount] = '\0'; int k = 0; bool inserted = false; @@ -883,7 +886,7 @@ void PredictiveDialog::addWord(Dict &dict, const Common::String &word, const Com free(dict.dictLine); dict.dictLineCount += 1; - dict.dictLine = (char **)calloc(1, sizeof(char *) * dict.dictLineCount); + dict.dictLine = (char **)calloc(dict.dictLineCount, sizeof(char *)); if (!dict.dictLine) { warning("Predictive Dialog: cannot allocate memory for index buffer"); free(newDictLine); @@ -935,7 +938,7 @@ void PredictiveDialog::loadDictionary(Common::SeekableReadStream *in, Dict &dict ptr++; } - dict.dictLine = (char **)calloc(1, sizeof(char *) * lines); + dict.dictLine = (char **)calloc(lines, sizeof(char *)); if (dict.dictLine == NULL) { warning("Predictive Dialog: Cannot allocate memory for line index buffer"); return; diff --git a/gui/recorderdialog.h b/gui/recorderdialog.h index eb690a4f38c..9c5965f56dc 100644 --- a/gui/recorderdialog.h +++ b/gui/recorderdialog.h @@ -34,6 +34,8 @@ class ContainerWidget; class StaticTextWidget; class RecorderDialog : public GUI::Dialog { + using GUI::Dialog::runModal; + private: bool _firstScreenshotUpdate; Common::PlaybackFile _playbackFile; diff --git a/gui/themes/default.inc b/gui/themes/default.inc index 665bb18ad14..5740e86664d 100644 --- a/gui/themes/default.inc +++ b/gui/themes/default.inc @@ -1,142 +1,142 @@ "" -" " -" " +"" +"" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" " " -" " -" " +"" +"" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " -" " +"/>" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " +"/>" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " +"/>" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " +"/>" +"" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " -" " +"/>" +"" " " -" " -" " +"/>" +"" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " -" " +"/>" +"" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " +"/>" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " -" " +"/>" +"" " " -" " +"/>" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " -" " +"/>" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " +"/>" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" " " -" " -" " -" " +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " -" " +"/>" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " +"/>" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " +"/>" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " +"/>" +"" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " -" " +"/>" +"" " " -" " -" " +"/>" +"" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " +"/>" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " -" " -" " +"/>" +"" +"" " " -" " +"/>" +"" " " -" " -" " +"/>" +"" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " +"/>" +"" " " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" " " +"/>" " " +"/>" " " +"/>" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " +"/>" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" +"" +"" " " +"/>" " " -" " +"/>" +"" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" " " -" " +"/>" +"" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " +"/>" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " +"/>" +"" +"" +"" " " +"/>" " " +"/>" " " -" " -" " -" " -" " +"/>" +"" +"" +"" +"" diff --git a/gui/themes/modern.zip b/gui/themes/modern.zip index e4c3aaa2f85..82e3bbf1814 100644 Binary files a/gui/themes/modern.zip and b/gui/themes/modern.zip differ diff --git a/gui/themes/modern/modern_gfx.stx b/gui/themes/modern/modern_gfx.stx index b28974d5a21..39627e2e3c1 100644 --- a/gui/themes/modern/modern_gfx.stx +++ b/gui/themes/modern/modern_gfx.stx @@ -38,6 +38,14 @@ rgb = '124, 203, 109' /> + + + + + + @@ -186,10 +198,6 @@ color = 'white' /> - - @@ -232,7 +240,7 @@ stroke = '0' gradient_start = 'darkorange' gradient_end = 'brightorange' - shadow = '3' + shadow = '7' gradient_factor = '3' /> @@ -466,7 +474,7 @@ fg_color = 'lightgray2' fill = 'background' bg_color = 'xtrabrightred' - shadow = '2' + shadow = '1' /> @@ -737,7 +745,7 @@ gradient_start = 'blandyellow' gradient_end = 'xtrabrightred' gradient_factor = '4' - shadow = '3' + shadow = '7' /> @@ -783,18 +791,18 @@ stroke = '1' fill = 'gradient' shadow = '0' - fg_color = 'shadowcolor' + fg_color = 'darkredborder' gradient_start = 'brightred' gradient_end = 'darkred' bevel = '1' - bevel_color = '170, 222, 162' + bevel_color = 'brightredborder' /> @@ -803,11 +811,11 @@ stroke = '1' fill = 'gradient' shadow = '0' - fg_color = 'shadowcolor' + fg_color = 'darkredborder' gradient_start = 'brightpink' gradient_end = 'darkpink' bevel = '1' - bevel_color = 'xtrabrightred' + bevel_color = 'brightredborder' /> @@ -915,7 +923,7 @@ gradient_factor = '6' fill = 'gradient' bg_color = 'xtrabrightred' - shadow = '3' + shadow = '7' /> diff --git a/gui/widget.cpp b/gui/widget.cpp index c3f10a861f0..e96b62e359a 100644 --- a/gui/widget.cpp +++ b/gui/widget.cpp @@ -287,7 +287,7 @@ ButtonWidget::ButtonWidget(GuiObject *boss, int x, int y, int w, int h, const Co ButtonWidget::ButtonWidget(GuiObject *boss, const Common::String &name, const Common::String &label, const char *tooltip, uint32 cmd, uint8 hotkey) : StaticTextWidget(boss, name, cleanupHotkey(label), tooltip), CommandSender(boss), - _cmd(cmd), _lastTime(0) { + _cmd(cmd), _hotkey(hotkey), _lastTime(0) { if (hotkey == 0) _hotkey = parseHotkey(label); setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG); @@ -396,7 +396,7 @@ PicButtonWidget::~PicButtonWidget() { void PicButtonWidget::setGfx(const Graphics::Surface *gfx) { _gfx.free(); - if (!gfx || !gfx->pixels) + if (!gfx || !gfx->getPixels()) return; if (gfx->format.bytesPerPixel == 1) { @@ -429,7 +429,7 @@ void PicButtonWidget::setGfx(int w, int h, int r, int g, int b) { void PicButtonWidget::drawWidget() { g_gui.theme()->drawButton(Common::Rect(_x, _y, _x+_w, _y+_h), "", _state, getFlags()); - if (_gfx.pixels) { + if (_gfx.getPixels()) { // Check whether the set up surface needs to be converted to the GUI // color format. const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat(); @@ -646,7 +646,7 @@ GraphicsWidget::~GraphicsWidget() { void GraphicsWidget::setGfx(const Graphics::Surface *gfx) { _gfx.free(); - if (!gfx || !gfx->pixels) + if (!gfx || !gfx->getPixels()) return; if (gfx->format.bytesPerPixel == 1) { @@ -676,7 +676,7 @@ void GraphicsWidget::setGfx(int w, int h, int r, int g, int b) { } void GraphicsWidget::drawWidget() { - if (_gfx.pixels) { + if (_gfx.getPixels()) { // Check whether the set up surface needs to be converted to the GUI // color format. const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat(); diff --git a/gui/widgets/editable.cpp b/gui/widgets/editable.cpp index 6fae9346b27..667850d6cc1 100644 --- a/gui/widgets/editable.cpp +++ b/gui/widgets/editable.cpp @@ -277,7 +277,7 @@ void EditableWidget::drawCaret(bool erase) { int chrWidth = g_gui.getCharWidth(_editString[_caretPos], _font); const uint last = (_caretPos > 0) ? _editString[_caretPos - 1] : 0; x += g_gui.getKerningOffset(last, _editString[_caretPos], _font); - g_gui.theme()->drawText(Common::Rect(x, y, x + chrWidth, y + editRect.height() - 2), chr, _state, Graphics::kTextAlignLeft, _inversion, 0, false, _font); + g_gui.theme()->drawText(Common::Rect(x, y, x + chrWidth, y + editRect.height() - 2), chr, _state, Graphics::kTextAlignLeft, _inversion, 0, false, _font, ThemeEngine::kFontColorNormal, true, _textDrawableArea); } } diff --git a/gui/widgets/edittext.cpp b/gui/widgets/edittext.cpp index 3677f02e471..52527effd8a 100644 --- a/gui/widgets/edittext.cpp +++ b/gui/widgets/edittext.cpp @@ -93,11 +93,15 @@ void EditTextWidget::drawWidget() { // Draw the text adjustOffset(); - g_gui.theme()->drawText(Common::Rect(_x+2+ _leftPadding,_y+2, _x+_leftPadding+getEditRect().width()+2, _y+_h-2), _editString, _state, Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, -_editScrollOffset, false, _font); + + const Common::Rect &r = Common::Rect(_x + 2 + _leftPadding, _y + 2, _x + _leftPadding + getEditRect().width() + 8, _y + _h); + setTextDrawableArea(r); + + g_gui.theme()->drawText(Common::Rect(_x + 2 + _leftPadding, _y + 2, _x + _leftPadding + getEditRect().width() + 2, _y + _h), _editString, _state, Graphics::kTextAlignLeft, ThemeEngine::kTextInversionNone, -_editScrollOffset, false, _font, ThemeEngine::kFontColorNormal, true, _textDrawableArea); } Common::Rect EditTextWidget::getEditRect() const { - Common::Rect r(2 + _leftPadding, 2, _w - 2 - _leftPadding - _rightPadding, _h-1); + Common::Rect r(2 + _leftPadding, 2, _w - 2 - _leftPadding - _rightPadding, _h - 1); return r; } diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp index 473d5f04df9..8ecb31311f2 100644 --- a/gui/widgets/list.cpp +++ b/gui/widgets/list.cpp @@ -91,6 +91,9 @@ ListWidget::ListWidget(Dialog *boss, int x, int y, int w, int h, const char *too // FIXME: This flag should come from widget definition _editable = true; + + _quickSelect = true; + _editColor = ThemeEngine::kFontColorNormal; } ListWidget::~ListWidget() { diff --git a/po/module.mk b/po/module.mk index 4c627191bf2..dc8aabab80b 100644 --- a/po/module.mk +++ b/po/module.mk @@ -2,8 +2,10 @@ POTFILE := $(srcdir)/po/residualvm.pot POFILES := $(wildcard $(srcdir)/po/*.po) CPFILES := $(wildcard $(srcdir)/po/*.cp) +ENGINE_INPUT_POTFILES := $(wildcard $(srcdir)/engines/*/POTFILES) updatepot: - xgettext -f $(srcdir)/po/POTFILES -D $(srcdir) -d scummvm --c++ -k_ -k_s -k_c:1,2c -k_sc:1,2c --add-comments=I18N\ + cat $(srcdir)/po/POTFILES $(ENGINE_INPUT_POTFILES) | \ + xgettext -f - -D $(srcdir) -d residualvm --c++ -k_ -k_s -k_c:1,2c -k_sc:1,2c --add-comments=I18N\ -kDECLARE_TRANSLATION_ADDITIONAL_CONTEXT:1,2c -o $(POTFILE) \ --copyright-holder="ResidualVM Team" --package-name=ResidualVM \ --package-version=$(VERSION) --msgid-bugs-address=residualvm-devel@lists.sf.net -o $(POTFILE)_ diff --git a/ports.mk b/ports.mk index f5719feca43..e30a9c168e8 100644 --- a/ports.mk +++ b/ports.mk @@ -150,6 +150,10 @@ ifdef USE_MPEG2 OSX_STATIC_LIBS += $(STATICLIBPATH)/lib/libmpeg2.a endif +ifdef USE_JPEG +OSX_STATIC_LIBS += $(STATICLIBPATH)/lib/libjpeg.a +endif + ifdef USE_ZLIB OSX_ZLIB ?= $(STATICLIBPATH)/lib/libz.a endif diff --git a/test/audio/raw.h b/test/audio/raw.h index e7cb42ac449..3792f82674e 100644 --- a/test/audio/raw.h +++ b/test/audio/raw.h @@ -1,6 +1,7 @@ #include #include "audio/decoders/raw.h" +#include "audio/audiostream.h" #include "helper.h" diff --git a/video/smk_decoder.cpp b/video/smk_decoder.cpp index b622a0ab61f..3dbcebcde44 100644 --- a/video/smk_decoder.cpp +++ b/video/smk_decoder.cpp @@ -580,7 +580,7 @@ void SmackerDecoder::SmackerVideoTrack::decodeFrame(Common::BitStream &bs) { while (run-- && block < blocks) { clr = _MClrTree->getCode(bs); map = _MMapTree->getCode(bs); - out = (byte *)_surface->pixels + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; + out = (byte *)_surface->getPixels() + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; hi = clr >> 8; lo = clr & 0xff; for (i = 0; i < 4; i++) { @@ -613,7 +613,7 @@ void SmackerDecoder::SmackerVideoTrack::decodeFrame(Common::BitStream &bs) { } while (run-- && block < blocks) { - out = (byte *)_surface->pixels + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; + out = (byte *)_surface->getPixels() + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; switch (mode) { case 0: for (i = 0; i < 4; ++i) { @@ -679,7 +679,7 @@ void SmackerDecoder::SmackerVideoTrack::decodeFrame(Common::BitStream &bs) { uint32 col; mode = type >> 8; while (run-- && block < blocks) { - out = (byte *)_surface->pixels + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; + out = (byte *)_surface->getPixels() + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; col = mode * 0x01010101; for (i = 0; i < 4 * doubleY; ++i) { out[0] = out[1] = out[2] = out[3] = col; diff --git a/video/video_decoder.cpp b/video/video_decoder.cpp index 5df811008c6..0ab1478727c 100644 --- a/video/video_decoder.cpp +++ b/video/video_decoder.cpp @@ -62,6 +62,8 @@ void VideoDecoder::close() { delete *it; _tracks.clear(); + _internalTracks.clear(); + _externalTracks.clear(); _dirtyPalette = false; _palette = 0; _startTime = 0; @@ -336,7 +338,12 @@ bool VideoDecoder::seek(const Audio::Timestamp &time) { if (isPlaying()) stopAudio(); - for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) + // Do the actual seeking + if (!seekIntern(time)) + return false; + + // Seek any external track too + for (TrackListIterator it = _externalTracks.begin(); it != _externalTracks.end(); it++) if (!(*it)->seek(time)) return false; @@ -356,12 +363,12 @@ bool VideoDecoder::seek(const Audio::Timestamp &time) { } bool VideoDecoder::seekToFrame(uint frame) { + if (!isSeekable()) + return false; + VideoTrack *track = 0; for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) { - if (!(*it)->isSeekable()) - return false; - if ((*it)->getTrackType() == Track::kTrackTypeVideo) { // We only allow seeking by frame when one video track // is present @@ -471,6 +478,14 @@ Audio::Timestamp VideoDecoder::getDuration() const { return maxDuration; } +bool VideoDecoder::seekIntern(const Audio::Timestamp &time) { + for (TrackList::iterator it = _internalTracks.begin(); it != _internalTracks.end(); it++) + if (!(*it)->seek(time)) + return false; + + return true; +} + VideoDecoder::Track::Track() { _paused = false; } @@ -516,10 +531,9 @@ Audio::Timestamp VideoDecoder::FixedRateVideoTrack::getFrameTime(uint frame) con if (frameRate == frameRate.toInt()) // The nice case (a whole number) return Audio::Timestamp(0, frame, frameRate.toInt()); - // Just convert to milliseconds. - Common::Rational time = frame * 1000; - time /= frameRate; - return Audio::Timestamp(time.toInt(), 1000); + // Convert as best as possible + Common::Rational time = frameRate.getInverse() * frame; + return Audio::Timestamp(0, time.getNumerator(), time.getDenominator()); } uint VideoDecoder::FixedRateVideoTrack::getFrameAtTime(const Audio::Timestamp &time) const { @@ -529,8 +543,10 @@ uint VideoDecoder::FixedRateVideoTrack::getFrameAtTime(const Audio::Timestamp &t if (frameRate == time.framerate()) return time.totalNumberOfFrames(); - // Default case - return (time.totalNumberOfFrames() * frameRate / time.framerate()).toInt(); + // Create the rational based on the time first to hopefully cancel out + // *something* when multiplying by the frameRate (which can be large in + // some AVI videos). + return (Common::Rational(time.totalNumberOfFrames(), time.framerate()) * frameRate).toInt(); } Audio::Timestamp VideoDecoder::FixedRateVideoTrack::getDuration() const { @@ -641,9 +657,14 @@ bool VideoDecoder::StreamFileAudioTrack::loadFromFile(const Common::String &base return _stream != 0; } -void VideoDecoder::addTrack(Track *track) { +void VideoDecoder::addTrack(Track *track, bool isExternal) { _tracks.push_back(track); + if (isExternal) + _externalTracks.push_back(track); + else + _internalTracks.push_back(track); + if (track->getTrackType() == Track::kTrackTypeAudio) { // Update volume settings if it's an audio track ((AudioTrack *)track)->setVolume(_audioVolume); @@ -673,7 +694,7 @@ bool VideoDecoder::addStreamFileTrack(const Common::String &baseName) { bool result = track->loadFromFile(baseName); if (result) - addTrack(track); + addTrack(track, true); else delete track; @@ -704,17 +725,17 @@ void VideoDecoder::setEndTime(const Audio::Timestamp &endTime) { } VideoDecoder::Track *VideoDecoder::getTrack(uint track) { - if (track > _tracks.size()) + if (track > _internalTracks.size()) return 0; - return _tracks[track]; + return _internalTracks[track]; } const VideoDecoder::Track *VideoDecoder::getTrack(uint track) const { - if (track > _tracks.size()) + if (track > _internalTracks.size()) return 0; - return _tracks[track]; + return _internalTracks[track]; } bool VideoDecoder::endOfVideoTracks() const { diff --git a/video/video_decoder.h b/video/video_decoder.h index d0a6e08005e..ac6586d8dde 100644 --- a/video/video_decoder.h +++ b/video/video_decoder.h @@ -168,14 +168,15 @@ public: /** * Seek to a given time in the video. * - * If the video is playing, it will continue to play. The default - * implementation will seek each track and must still be called - * from any other implementation. + * If the video is playing, it will continue to play. This calls + * seekIntern(), which can be overriden. By default, seekIntern() + * will call Track::seek() on all tracks with the time passed to + * this function. * * @param time The time to seek to * @return true on success, false otherwise */ - virtual bool seek(const Audio::Timestamp &time); + bool seek(const Audio::Timestamp &time); /** * Seek to a given frame. @@ -593,17 +594,17 @@ protected: virtual Audio::Timestamp getDuration() const; Audio::Timestamp getFrameTime(uint frame) const; - protected: - /** - * Get the rate at which this track is played. - */ - virtual Common::Rational getFrameRate() const = 0; - /** * Get the frame that should be displaying at the given time. This is * helpful for someone implementing seek(). */ uint getFrameAtTime(const Audio::Timestamp &time) const; + + protected: + /** + * Get the rate at which this track is played. + */ + virtual Common::Rational getFrameRate() const = 0; }; /** @@ -760,8 +761,11 @@ protected: * Define a track to be used by this class. * * The pointer is then owned by this base class. + * + * @param track The track to add + * @param isExternal Is this an external track not found by loadStream()? */ - void addTrack(Track *track); + void addTrack(Track *track, bool isExternal = false); /** * Whether or not getTime() will sync with a playing audio track. @@ -813,16 +817,27 @@ protected: /** * Get the begin iterator of the tracks */ - TrackListIterator getTrackListBegin() { return _tracks.begin(); } + TrackListIterator getTrackListBegin() { return _internalTracks.begin(); } /** * Get the end iterator of the tracks */ - TrackListIterator getTrackListEnd() { return _tracks.end(); } + TrackListIterator getTrackListEnd() { return _internalTracks.end(); } + + /** + * The internal seek function that does the actual seeking. + * + * @see seek() + * + * @return true on success, false otherwise + */ + virtual bool seekIntern(const Audio::Timestamp &time); private: // Tracks owned by this VideoDecoder TrackList _tracks; + TrackList _internalTracks; + TrackList _externalTracks; // Current playback status bool _needsUpdate;