Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

sound_pcjr.cpp

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

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

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-2-0/engines/agi/sound_pcjr.cpp $
 * $Id: sound_pcjr.cpp 52741 2010-09-16 04:43:49Z eriktorbjorn $
 *
 */

/* Heavily based on code from NAGI
 *
 * COPYRIGHT AND PERMISSION NOTICE
 *
 * Copyright (c) 2001, 2001, 2002 Nick Sonneveld
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons
 * to whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
 * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Except as contained in this notice, the name of a copyright holder
 * shall not be used in advertising or otherwise to promote the sale, use
 * or other dealings in this Software without prior written authorization
 *
 */

#include "agi/agi.h"
#include "agi/sound.h"
#include "agi/sound_pcjr.h"

namespace Agi {

// "fade out" or possibly "dissolve"
// v2.9xx
const int8 dissolveDataV2[] = {
      -2, -3, -2, -1,
      0x00, 0x00,
      0x01, 0x01, 0x01, 0x01,
      0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
      0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
      0x04, 0x04, 0x04, 0x04,
      0x05, 0x05, 0x05, 0x05,
      0x06, 0x06, 0x06, 0x06, 0x06,
      0x07, 0x07, 0x07, 0x07,
      0x08, 0x08, 0x08, 0x08,
      0x09, 0x09, 0x09, 0x09,
      0x0A, 0x0A, 0x0A, 0x0A,
      0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
      0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
      0x0D,
      -100
};

// v3
const int8 dissolveDataV3[] = {
      -2, -3, -2, -1,
      0x00, 0x00, 0x00, 0x00, 0x00,
      0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
      0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
      0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
      0x04, 0x04, 0x04, 0x04, 0x04,
      0x05, 0x05, 0x05, 0x05, 0x05,
      0x06, 0x06, 0x06, 0x06, 0x06,
      0x07, 0x07, 0x07, 0x07,
      0x08, 0x08, 0x08, 0x08,
      0x09, 0x09, 0x09, 0x09,
      0x0A, 0x0A, 0x0A, 0x0A,
      0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
      0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
      0x0D,
      -100
};


SoundGenPCJr::SoundGenPCJr(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer) {
      _chanAllocated = 10240; // preallocate something which will most likely fit
      _chanData = (int16 *)malloc(_chanAllocated << 1);

      // Pick dissolve method
      //
      // 0 = no dissolve.. just play for as long as it's meant to be played
      // this was used in older v2.4 and under games i THINK
      // 1 = not used
      // 2 = v2.9+ games used a shorter dissolve
      // 3 (default) = v3 games used this dissolve pattern.. slightly longer
      if (_vm->getVersion() >= 0x3000)
            _dissolveMethod = 3;
      else if (_vm->getVersion() >= 0x2900)
            _dissolveMethod = 2;
      else
            _dissolveMethod = 0;

      _dissolveMethod = 3;

      memset(_channel, 0, sizeof(_channel));
      memset(_tchannel, 0, sizeof(_tchannel));

      _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}

SoundGenPCJr::~SoundGenPCJr() {
      free(_chanData);

      _mixer->stopHandle(_soundHandle);
}

void SoundGenPCJr::play(int resnum) {
      PCjrSound *pcjrSound = (PCjrSound *)_vm->_game.sounds[resnum];

      for (int i = 0; i < CHAN_MAX; i++) {
            _channel[i].data = pcjrSound->getVoicePointer(i % 4);
            _channel[i].duration = 0;
            _channel[i].avail = 0xffff;
            _channel[i].dissolveCount = 0xFFFF;
            _channel[i].attenuation = 0;
            _channel[i].attenuationCopy = 0;

            _tchannel[i].avail = 1;
            _tchannel[i].noteCount = 0;
            _tchannel[i].freqCount = 250;
            _tchannel[i].freqCountPrev = -1;
            _tchannel[i].atten = 0xF;     // silence
            _tchannel[i].genType = kGenTone;
            _tchannel[i].genTypePrev = -1;
      }
}

void SoundGenPCJr::stop(void) {
      int i;

      for (i = 0; i < CHAN_MAX; i++) {
            _channel[i].avail = 0;
            _tchannel[i].avail = 0;
      }
}

int SoundGenPCJr::volumeCalc(SndGenChan *chan) {
      int8 attenuation, dissolveValue;

      const int8 *dissolveData;

      switch (_dissolveMethod) {
      case 2:
            dissolveData = dissolveDataV2;
            break;
      case 3:
      default:
            dissolveData = dissolveDataV3;
            break;
      }

      assert(chan);

      attenuation = chan->attenuation;
      if (attenuation != 0x0F) {    // != silence
            if (chan->dissolveCount != 0xFFFF) {
                  dissolveValue = dissolveData[chan->dissolveCount];
                  if (dissolveValue == -100) {  // if at end of list
                        chan->dissolveCount = 0xFFFF;
                        chan->attenuation = chan->attenuationCopy;
                        attenuation = chan->attenuation;
                  } else {
                        chan->dissolveCount++;

                        attenuation += dissolveValue;
                        if (attenuation < 0)
                              attenuation = 0;
                        if (attenuation > 0x0F)
                              attenuation = 0x0F;

                        chan->attenuationCopy = attenuation;

                        attenuation &= 0x0F;
                        attenuation += _vm->getvar(vVolume);
                        if (attenuation > 0x0F)
                              attenuation = 0x0F;
                  }
            }
            //if (computer_type == 2) && (attenuation < 8)
            if (attenuation < 8)
                  attenuation += 2;
      }

      return attenuation;
}

// read the next channel data.. fill it in *tone
// if tone isn't touched.. it should be inited so it just plays silence
// return 0 if it's passing more data
// return -1 if it's passing nothing (end of data)
int SoundGenPCJr::getNextNote(int ch, Tone *tone) {
      SndGenChan *chan;
      const byte *data;

      assert(tone);
      assert(ch < CHAN_MAX);

      if (!_vm->getflag(fSoundOn))
            return -1;

      chan = &_channel[ch];
      if (!chan->avail)
            return -1;

      while ((chan->duration == 0) && (chan->duration != 0xFFFF)) {
            data = chan->data;

            // read the duration of the note
            chan->duration = READ_LE_UINT16(data);    // duration

            // if it's 0 then it's not going to be played
            // if it's 0xFFFF then the channel data has finished.
            if ((chan->duration != 0) && (chan->duration != 0xFFFF)) {
                  // only tone channels dissolve
                  if ((ch != 3) && (_dissolveMethod != 0))  // != noise??
                        chan->dissolveCount = 0;

                  // attenuation (volume)
                  chan->attenuation = data[4] & 0xF;

                  // frequency
                  if (ch < (CHAN_MAX - 1)) {
                        chan->freqCount = (uint16)data[2] & 0x3F;
                        chan->freqCount <<= 4;
                        chan->freqCount |= data[3] & 0x0F;

                        chan->genType = kGenTone;
                  } else {
                        int noiseFreq;

                        // check for white noise (1) or periodic (0)
                        chan->genType = (data[3] & 0x04) ? kGenWhite : kGenPeriod;

                        noiseFreq = data[3] & 0x03;

                        switch (noiseFreq) {
                        case 0:
                              chan->freqCount = 32;
                              break;
                        case 1:
                              chan->freqCount = 64;
                              break;
                        case 2:
                              chan->freqCount = 128;
                              break;
                        case 3:
                              chan->freqCount = _channel[2].freqCount * 2;
                              break;
                        }
                  }
            }
            // data now points to the next data seg-a-ment
            chan->data += 5;
      }

      if (chan->duration != 0xFFFF) {
            tone->freqCount = chan->freqCount;
            tone->atten = volumeCalc(chan);     // calc volume, sent vol is different from saved vol
            tone->type = chan->genType;
            chan->duration--;
      } else {
            // kill channel
            chan->avail = 0;
            chan->attenuation = 0x0F;     // silent
            chan->attenuationCopy = 0x0F; // dunno really

            return -1;
      }

      return 0;
}

// Formulas for noise generator
// bit0 = output

// noise feedback for white noise mode
#define FB_WNOISE 0x12000     // bit15.d(16bits) = bit0(out) ^ bit2
//#define FB_WNOISE 0x14000   // bit15.d(16bits) = bit0(out) ^ bit1
//#define FB_WNOISE 0x28000   // bit16.d(17bits) = bit0(out) ^ bit2 (same to AY-3-8910)
//#define FB_WNOISE 0x50000   // bit17.d(18bits) = bit0(out) ^ bit2

// noise feedback for periodic noise mode
// it is correct maybe (it was in the Megadrive sound manual)
//#define FB_PNOISE 0x10000   // 16bit rorate
#define FB_PNOISE 0x08000

// noise generator start preset (for periodic noise)
#define NG_PRESET 0x0f35

//#define WAVE_HEIGHT (0x7FFF)

// Volume table.
//
// 2dB = 20*log(a/b)
// 10^(2/20)*b = a;
// value = 0x7fff;
// value /= 1.258925411794;
const int16 volTable[16] = {
      32767, 26027, 20674, 16422, 13044, 10361, 8230, 6537, 5193, 4125, 3276, 2602, 2067, 1642, 1304, 0
};

#define FREQ_DIV 111844
#define MULT FREQ_DIV

// fill buff
int SoundGenPCJr::chanGen(int chan, int16 *stream, int len) {
      ToneChan *tpcm;
      Tone toneNew;
      int fillSize;
      int retVal;

      tpcm = &_tchannel[chan];

      retVal = -1;

      while (len > 0) {
            if (tpcm->noteCount <= 0) {
                  // get new tone data
                  toneNew.freqCount = 0;
                  toneNew.atten = 0xF;
                  toneNew.type = kGenTone;
                  if ((tpcm->avail) && (getNextNote(chan, &toneNew) == 0)) {
                        tpcm->atten = toneNew.atten;
                        tpcm->freqCount = toneNew.freqCount;
                        tpcm->genType = toneNew.type;

                        // setup counters 'n stuff
                        // SAMPLE_RATE samples per sec.. tone changes 60 times per sec
                        tpcm->noteCount = SAMPLE_RATE / 60;
                        retVal = 0;
                  } else {
                        // if it doesn't return an
                        tpcm->genType = kGenSilence;
                        tpcm->noteCount = len;
                        tpcm->avail = 0;
                  }
            }

            // write nothing
            if ((tpcm->freqCount == 0) || (tpcm->atten == 0xf)) {
                  tpcm->genType = kGenSilence;
            }

            // find which is smaller.. the buffer or the
            fillSize = (tpcm->noteCount <= len) ? tpcm->noteCount : len;

            switch (tpcm->genType) {
                  case kGenTone:
                        fillSize = fillSquare(tpcm, stream, fillSize);
                        break;
                  case kGenPeriod:
                  case kGenWhite:
                        fillSize = fillNoise(tpcm, stream, fillSize);
                        break;
                  case kGenSilence:
                  default:
                        // fill with whitespace
                        memset(stream, 0, fillSize * sizeof(int16));
                        break;
            }

            tpcm->noteCount -= fillSize;
            stream += fillSize;
            len -= fillSize;
      }

      return retVal;
}

int SoundGenPCJr::fillSquare(ToneChan *t, int16 *buf, int len) {
      int count;

      if (t->genType != t->genTypePrev) {
            // make sure the freqCount is checked
            t->freqCountPrev = -1;
            t->sign = 1;
            t->genTypePrev = t->genType;
      }

      if (t->freqCount != t->freqCountPrev) {
            //t->scale = (int)( (double)t->samp->freq*t->freqCount/FREQ_DIV * MULT + 0.5);
            t->scale = (SAMPLE_RATE / 2) * t->freqCount;
            t->count = t->scale;
            t->freqCountPrev = t->freqCount;
      }

      count = len;

      while (count > 0) {
            *(buf++) = t->sign ? volTable[t->atten] : -volTable[t->atten];
            count--;

            // get next sample
            t->count -= MULT;
            while (t->count <= 0) {
                  t->sign ^= 1;
                  t->count += t->scale;
            }
      }

      return len;
}

int SoundGenPCJr::fillNoise(ToneChan *t, int16 *buf, int len) {
      int count;

      if (t->genType != t->genTypePrev) {
            // make sure the freqCount is checked
            t->freqCountPrev = -1;
            t->genTypePrev = t->genType;
      }

      if (t->freqCount != t->freqCountPrev) {
            //t->scale = (int)( (double)t->samp->freq*t->freqCount/FREQ_DIV * MULT + 0.5);
            t->scale = (SAMPLE_RATE / 2) * t->freqCount;
            t->count = t->scale;
            t->freqCountPrev = t->freqCount;

            t->feedback = (t->genType == kGenWhite) ? FB_WNOISE : FB_PNOISE;
            // reset noise shifter
            t->noiseState = NG_PRESET;
            t->sign = t->noiseState & 1;
      }

      count = len;

      while (count > 0) {
            *(buf++) = t->sign ? volTable[t->atten] : -volTable[t->atten];
            count--;

            // get next sample
            t->count -= MULT;
            while (t->count <= 0) {
                  if (t->noiseState & 1)
                        t->noiseState ^= t->feedback;

                  t->noiseState >>= 1;
                  t->sign = t->noiseState & 1;
                  t->count += t->scale;
            }
      }

      return len;
}

int SoundGenPCJr::readBuffer(int16 *stream, const int len) {
      int streamCount;
      int16 *sPtr, *cPtr;

      if (_chanAllocated < len) {
            free(_chanData);
            _chanData = (int16 *)malloc(len << 1);
            _chanAllocated = len;
      }
      memset(stream, 0, len << 1);

      assert(stream);

      bool finished = true;

      for (int i = 0; i < CHAN_MAX; i++) {
            // get channel data(chan.userdata)
            if (chanGen(i, _chanData, len) == 0) {
                  // divide by number of channels then add to stream
                  streamCount = len;
                  sPtr = stream;
                  cPtr = _chanData;

                  while (streamCount--)
                        *(sPtr++) += *(cPtr++) / CHAN_MAX;

                  finished = false;
            }
      }

      if (finished)
            _vm->_sound->soundIsFinished();

      return len;
}

} // End of namespace Agi

Generated by  Doxygen 1.6.0   Back to index