/* 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 3 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, see . * */ #include "backends/mixer/atari/atari-mixer.h" #include #include #include #include #include // https://github.com/mikrosk/usound #include "backends/platform/atari/atari-debug.h" #include "common/config-manager.h" #define DEFAULT_OUTPUT_RATE 24585 #define DEFAULT_OUTPUT_CHANNELS 2 #define DEFAULT_SAMPLES 2048 // 83ms void AtariAudioShutdown() { Jdisint(MFP_TIMERA); AtariSoundSetupDeinitXbios(); } static volatile bool muted; static volatile bool endOfPlayback; static void __attribute__((interrupt)) timerA(void) { if (endOfPlayback && !muted) { *((volatile unsigned char *)0xFFFF8901L) &= 0xFC; // disable playback/repeat (and triggers another interrupt) muted = true; } endOfPlayback = true; *((volatile byte *)0xFFFFFA0FL) &= ~(1<<5); // clear in service bit } AtariMixerManager::AtariMixerManager() : MixerManager() { atari_debug("AtariMixerManager()"); suspendAudio(); ConfMan.registerDefault("output_rate", DEFAULT_OUTPUT_RATE); _outputRate = ConfMan.getInt("output_rate"); if (_outputRate <= 0) _outputRate = DEFAULT_OUTPUT_RATE; ConfMan.registerDefault("output_channels", DEFAULT_OUTPUT_CHANNELS); _outputChannels = ConfMan.getInt("output_channels"); if (_outputChannels <= 0 || _outputChannels > 2) _outputChannels = DEFAULT_OUTPUT_CHANNELS; ConfMan.registerDefault("audio_buffer_size", DEFAULT_SAMPLES); _samples = ConfMan.getInt("audio_buffer_size"); if (_samples <= 0) _samples = DEFAULT_SAMPLES; g_system->getEventManager()->getEventDispatcher()->registerObserver(this, 10, false); } AtariMixerManager::~AtariMixerManager() { atari_debug("~AtariMixerManager()"); g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this); AtariAudioShutdown(); Mfree(_atariSampleBuffer); _atariSampleBuffer = _atariPhysicalSampleBuffer = _atariLogicalSampleBuffer = nullptr; delete[] _samplesBuf; _samplesBuf = nullptr; } void AtariMixerManager::init() { AudioSpec desired, obtained; desired.frequency = _outputRate; desired.channels = _outputChannels; desired.format = AudioFormatSigned16MSB; desired.samples = _samples; if (!AtariSoundSetupInitXbios(&desired, &obtained)) { error("Sound system is not available"); } if (obtained.format != AudioFormatSigned8 && obtained.format != AudioFormatSigned16MSB) { error("Sound system currently supports only 8/16-bit signed big endian samples"); } // don't use the recommended number of samples obtained.size = obtained.size * desired.samples / obtained.samples; obtained.samples = desired.samples; _outputRate = obtained.frequency; _outputChannels = obtained.channels; _samples = obtained.samples; _downsample = (obtained.format == AudioFormatSigned8); ConfMan.setInt("output_rate", _outputRate); ConfMan.setInt("output_channels", _outputChannels); ConfMan.setInt("audio_buffer_size", _samples); atari_debug("setting %d Hz mixing frequency (%d-bit, %s)", _outputRate, obtained.format == AudioFormatSigned8 ? 8 : 16, _outputChannels == 1 ? "mono" : "stereo"); atari_debug("sample buffer size: %d", _samples); ConfMan.flushToDisk(); _atariSampleBuffer = (byte*)Mxalloc(obtained.size * 2, MX_STRAM); if (!_atariSampleBuffer) error("Failed to allocate memory in ST RAM"); _atariPhysicalSampleBuffer = _atariSampleBuffer; _atariLogicalSampleBuffer = _atariSampleBuffer + obtained.size; Setinterrupt(SI_TIMERA, SI_PLAY); Xbtimer(XB_TIMERA, 1<<3, 1, timerA); // event count mode, count to '1' Jenabint(MFP_TIMERA); _samplesBuf = new uint8[_samples * _outputChannels * 2]; // always 16-bit _mixer = new Audio::MixerImpl(_outputRate, _outputChannels == 2, _samples); _mixer->setReady(true); resumeAudio(); } void AtariMixerManager::suspendAudio() { atari_debug("suspendAudio"); Buffoper(0x00); muted = true; _audioSuspended = true; } int AtariMixerManager::resumeAudio() { atari_debug("resumeAudio"); _audioSuspended = false; update(); return 0; } bool AtariMixerManager::notifyEvent(const Common::Event &event) { switch (event.type) { case Common::EVENT_QUIT: case Common::EVENT_RETURN_TO_LAUNCHER: if (!muted) { Buffoper(0x00); muted = true; atari_debug("silencing the mixer"); } return false; default: break; } return false; } void AtariMixerManager::update() { if (_audioSuspended) { return; } assert(_mixer); int processed = -1; if (muted || endOfPlayback) { endOfPlayback = false; processed = _mixer->mixCallback(_samplesBuf, _samples * _outputChannels * 2); } if (processed > 0) { byte* tmp = _atariPhysicalSampleBuffer; _atariPhysicalSampleBuffer = _atariLogicalSampleBuffer; _atariLogicalSampleBuffer = tmp; if (_downsample) { // use the trick with move.b (a7)+,dx which skips two bytes at once // basically supplying move.w (src)+,dx; asr.w #8,dx; move.b dx,(dst)+ __asm__ volatile( " move.l %%a7,%%d0\n" " move.l %0,%%a7\n" " moveq #0x0f,%%d1\n" " and.l %2,%%d1\n" " neg.l %%d1\n" " lsr.l #4,%2\n" " jmp (2f,%%pc,%%d1.l*2)\n" "1: move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" " move.b (%%a7)+,(%1)+\n" "2: dbra %2,1b\n" " move.l %%d0,%%a7\n" : // outputs : "g"(_samplesBuf), "a"(_atariPhysicalSampleBuffer), "d"(processed * _outputChannels * 2/2) // inputs : "d0", "d1", "cc" AND_MEMORY ); memset(_atariPhysicalSampleBuffer + processed * _outputChannels * 2/2, 0, (_samples - processed) * _outputChannels * 2/2); Setbuffer(SR_PLAY, _atariPhysicalSampleBuffer, _atariPhysicalSampleBuffer + _samples * _outputChannels * 2/2); } else { memcpy(_atariPhysicalSampleBuffer, _samplesBuf, processed * _outputChannels * 2); memset(_atariPhysicalSampleBuffer + processed * _outputChannels * 2, 0, (_samples - processed) * _outputChannels * 2); Setbuffer(SR_PLAY, _atariPhysicalSampleBuffer, _atariPhysicalSampleBuffer + _samples * _outputChannels * 2); } if (muted) { Buffoper(SB_PLA_ENA | SB_PLA_RPT); endOfPlayback = true; muted = false; } } else if (processed == 0 && !muted) { Buffoper(0x00); muted = true; } if (processed > 0 && processed != _samples) { atari_warning("processed: %d, _samples: %d", processed, _samples); } }