summaryrefslogtreecommitdiffstats
path: root/soundserver/simplesoundserver_impl.cc
diff options
context:
space:
mode:
Diffstat (limited to 'soundserver/simplesoundserver_impl.cc')
-rw-r--r--soundserver/simplesoundserver_impl.cc453
1 files changed, 453 insertions, 0 deletions
diff --git a/soundserver/simplesoundserver_impl.cc b/soundserver/simplesoundserver_impl.cc
new file mode 100644
index 0000000..608f146
--- /dev/null
+++ b/soundserver/simplesoundserver_impl.cc
@@ -0,0 +1,453 @@
+ /*
+
+ Copyright (C) 1999-2001 Stefan Westerfeld
+ 2001 Matthias Kretz
+ 2003 Allan Sandfeld Jensen
+
+ 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ Permission is also granted to link this program with the Qt
+ library, treating Qt like a library that normally accompanies the
+ operating system kernel, whether or not that is in fact the case.
+
+ */
+
+#include "simplesoundserver_impl.h"
+#include "artsflow.h"
+#include "flowsystem.h"
+#include "audiosubsys.h"
+#include "connect.h"
+#include "debug.h"
+#include "reference.h"
+
+#include <stdio.h>
+#include <iostream>
+
+using namespace std;
+using namespace Arts;
+
+void SoundServerJob::detach(const Object&)
+{
+ // nothing by default
+}
+
+SoundServerJob::~SoundServerJob()
+{
+ // virtual destructor, since we've got virtual functions
+}
+
+PlayWavJob::PlayWavJob(const string& filename) :terminated(false)
+{
+ arts_debug("play '%s'!",filename.c_str());
+
+ connect(wav,out);
+ wav.filename(filename);
+ wav.start();
+ out.start();
+}
+
+void PlayWavJob::terminate()
+{
+ terminated = true;
+}
+
+bool PlayWavJob::done()
+{
+ return terminated || wav.finished();
+}
+
+PlayObjectJob::PlayObjectJob(PlayObject newPlob)
+ : plob(newPlob), terminated(false)
+{
+ plob.play();
+}
+
+void PlayObjectJob::terminate()
+{
+ terminated = true;
+ plob.halt();
+}
+
+bool PlayObjectJob::done()
+{
+ return terminated || (plob.state() == posIdle);
+}
+
+PlayStreamJob::PlayStreamJob(ByteSoundProducer bsp) : sender(bsp)
+{
+ int samplingRate = bsp.samplingRate();
+ int channels = bsp.channels();
+ int bits = bsp.bits();
+
+ arts_debug("incoming stream, parameters: rate=%d, %d bit, %d channels",
+ samplingRate, bits, channels);
+
+ if((samplingRate < 500 || samplingRate > 2000000)
+ || (channels != 1 && channels != 2) || (bits != 8 && bits != 16))
+ {
+ arts_warning("invalid stream parameters: rate=%d, %d bit, %d channels",
+ samplingRate, bits, channels);
+ terminate();
+ return;
+ }
+
+ convert.samplingRate(samplingRate);
+ convert.channels(channels);
+ convert.bits(bits);
+ ByteSoundProducerV2 senderv2 = DynamicCast(sender);
+ if(!senderv2.isNull())
+ out.title(senderv2.title());
+
+ connect(sender,"outdata",convert,"indata");
+ connect(convert,out);
+
+ convert.start();
+ out.start();
+}
+
+void PlayStreamJob::detach(const Object& object)
+{
+ if(object._isEqual(sender))
+ terminate();
+}
+
+void PlayStreamJob::terminate()
+{
+ sender = ByteSoundProducer::null();
+}
+
+bool PlayStreamJob::done()
+{
+ // when the sender is not alive any longer, assign a null object
+ if(!sender.isNull() && sender.error())
+ sender = ByteSoundProducer::null();
+
+ return sender.isNull() && !convert.running();
+}
+
+RecordStreamJob::RecordStreamJob(ByteSoundReceiver bsr) : receiver(bsr)
+{
+ int samplingRate = bsr.samplingRate();
+ int channels = bsr.channels();
+ int bits = bsr.bits();
+
+ arts_debug("outgoing stream, parameters: rate=%d, %d bit, %d channels",
+ samplingRate, bits, channels);
+
+ if((samplingRate < 500 || samplingRate > 2000000)
+ || (channels != 1 && channels != 2) || (bits != 8 && bits != 16))
+ {
+ arts_warning("invalid stream parameters: rate=%d, %d bit, %d channels",
+ samplingRate, bits, channels);
+ terminate();
+ return;
+ }
+
+ convert.samplingRate(samplingRate);
+ convert.channels(channels);
+ convert.bits(bits);
+ in.title(receiver.title());
+
+ connect(in,convert);
+ connect(convert,"outdata",receiver,"indata");
+
+ in.start();
+ convert.start();
+}
+
+void RecordStreamJob::detach(const Object& object)
+{
+ if(object._isEqual(receiver))
+ terminate();
+}
+
+void RecordStreamJob::terminate()
+{
+ receiver = ByteSoundReceiver::null();
+ convert.stop();
+ in.stop();
+}
+
+bool RecordStreamJob::done()
+{
+ // when the sender is not alive any longer, assign a null object
+ if(!receiver.isNull() && receiver.error())
+ receiver = ByteSoundReceiver::null();
+
+ return receiver.isNull();
+}
+
+/*
+ * ( This place where other objects for playing wave files and such will
+ * be connected, to get their output mixed with the other clients ).
+ * _____________________
+ * | soundcardBus | (a Synth_BUS_DOWNLINK for "out_soundcard")
+ * ~~~~~~~~~~~~~~~~~~~~~
+ * | |
+ * ___V____________V____
+ * | outstack | (here the user can plugin various effects)
+ * ~~~~~~~~~~~~~~~~~~~~~
+ * | |
+ * ___V____________V____
+ * | outVolume | (global output volume for the soundserver)
+ * ~~~~~~~~~~~~~~~~~~~~~
+ * | |
+ * ___V____________V____
+ * | playSound | (output to soundcard)
+ * ~~~~~~~~~~~~~~~~~~~~~
+ */
+SimpleSoundServer_impl::SimpleSoundServer_impl() : autoSuspendTime(0), bufferMultiplier(1)
+{
+ soundcardBus.busname("out_soundcard");
+ connect(soundcardBus,_outstack);
+ connect(_outstack,_outVolume);
+ connect(_outVolume,playSound);
+
+ if(AudioSubSystem::the()->fullDuplex())
+ {
+ recordBus.busname("in_soundcard");
+ connect(recordSound,recordBus);
+
+ recordBus.start();
+ recordSound.start();
+ }
+
+ soundcardBus.start();
+ _outVolume.start();
+ playSound.start();
+
+ asCount = 0; // AutoSuspend
+ Dispatcher::the()->ioManager()->addTimer(200,this);
+
+ // load Arts::MidiManager when installed
+ TraderQuery query;
+ query.supports("InterfaceName","Arts::MidiManager");
+
+ vector<TraderOffer> *offers = query.query();
+ if(offers->size())
+ _addChild(Arts::SubClass("Arts::MidiManager"),
+ "Extension_Arts::MidiManager");
+ delete offers;
+}
+
+SimpleSoundServer_impl::~SimpleSoundServer_impl()
+{
+ /*
+ * we don't need to care about the flow system nodes we started, since
+ * we have put them into Object_var's, which means that they will be
+ * freed automatically here
+ */
+ Dispatcher::the()->ioManager()->removeTimer(this);
+}
+
+long SimpleSoundServer_impl::play(const string& filename)
+{
+ PlayObject tmp( createPlayObject( filename ) );
+ if ( tmp.isNull() )
+ return 0; // No success creating the PlayObject and playing the file...
+ jobs.push_back(new PlayObjectJob(tmp));
+ return 1;
+}
+
+float SimpleSoundServer_impl::serverBufferTime()
+{
+ float hardwareBuffer = AudioSubSystem::the()->fragmentSize()
+ * AudioSubSystem::the()->fragmentCount();
+
+ float playSpeed = AudioSubSystem::the()->channels()
+ * AudioSubSystem::the()->samplingRate()
+ * (AudioSubSystem::the()->bits() / 8);
+
+ return 1000.0 * hardwareBuffer / playSpeed;
+}
+
+float SimpleSoundServer_impl::minStreamBufferTime()
+{
+ /*
+ * It is sane to assume that client side stream buffers must be >= server
+ * side hardware buffers (or it can come to dropouts during hardware
+ * buffer refill). The buffer size can be increased using the multiplier.
+ */
+ return bufferMultiplier * serverBufferTime();
+}
+
+void SimpleSoundServer_impl::attach(ByteSoundProducer bsp)
+{
+ arts_return_if_fail(!bsp.isNull());
+
+ jobs.push_back(new PlayStreamJob(bsp));
+}
+
+void SimpleSoundServer_impl::detach(ByteSoundProducer bsp)
+{
+ arts_return_if_fail(!bsp.isNull());
+ arts_debug("detach incoming stream");
+
+ list<SoundServerJob *>::iterator j;
+
+ for(j = jobs.begin();j != jobs.end();j++)
+ (*j)->detach(bsp);
+}
+
+void SimpleSoundServer_impl::attachRecorder(ByteSoundReceiver bsr)
+{
+ arts_return_if_fail(!bsr.isNull());
+ jobs.push_back(new RecordStreamJob(bsr));
+}
+
+void SimpleSoundServer_impl::detachRecorder(ByteSoundReceiver bsr)
+{
+ arts_return_if_fail(!bsr.isNull());
+ arts_debug("detach outgoing stream");
+
+ list<SoundServerJob *>::iterator j;
+
+ for(j = jobs.begin();j != jobs.end();j++)
+ (*j)->detach(bsr);
+}
+
+StereoEffectStack SimpleSoundServer_impl::outstack()
+{
+ return _outstack;
+}
+
+Object SimpleSoundServer_impl::createObject(const string& name)
+{
+ // don't use SubClass(name) as this will abort if the object is not found
+ return Object::_from_base(ObjectManager::the()->create(name));
+}
+
+void SimpleSoundServer_impl::notifyTime()
+{
+ static long lock = 0;
+ assert(!lock); // paranoid reentrancy check (remove me later)
+ lock++;
+ /*
+ * Three times the same game: look if a certain object is still
+ * active - if yes, keep, if no, remove
+ */
+
+ /* look for jobs which may have terminated by now */
+ list<SoundServerJob *>::iterator i;
+
+ i = jobs.begin();
+ while(i != jobs.end())
+ {
+ SoundServerJob *job = *i;
+
+ if(job->done())
+ {
+ delete job;
+ jobs.erase(i);
+ arts_debug("job finished");
+ i = jobs.begin();
+ }
+ else i++;
+ }
+
+/*
+ * AutoSuspend
+ */
+ if(Dispatcher::the()->flowSystem()->suspendable() &&
+ !Dispatcher::the()->flowSystem()->suspended() &&
+ autoSuspendTime != 0)
+ {
+ asCount++;
+ if(asCount > autoSuspendTime*5)
+ {
+ Dispatcher::the()->flowSystem()->suspend();
+ arts_info("sound server suspended");
+ }
+ }
+ else
+ asCount = 0;
+ lock--;
+}
+
+PlayObject SimpleSoundServer_impl::createPlayObject(const string& filename)
+{
+ string objectType = "";
+
+ /*
+ * figure out extension (as lowercased letters)
+ */
+ string extension = "";
+ bool extensionok = false;
+ string::const_reverse_iterator i;
+ for(i = filename.rbegin(); i != filename.rend() && !extensionok; i++)
+ {
+ if(*i == '.')
+ extensionok = true;
+ else
+ extension.insert(extension.begin(), (char)tolower(*i));
+ }
+
+ /*
+ * query trader for PlayObjects which support this
+ */
+ if(extensionok)
+ {
+ arts_debug("search playobject, extension = %s",extension.c_str());
+
+ TraderQuery query;
+ query.supports("Interface","Arts::PlayObject");
+ query.supports("Extension",extension);
+
+ vector<TraderOffer> *offers = query.query();
+ if(!offers->empty())
+ objectType = offers->front().interfaceName(); // first offer
+
+ delete offers;
+ }
+
+ /*
+ * create a PlayObject and connect it
+ */
+ if(!objectType.empty())
+ {
+ arts_debug("creating %s to play file %s", objectType.c_str(), filename.c_str() );
+
+ PlayObject result = SubClass(objectType);
+ if(result.loadMedia(filename))
+ {
+ // TODO: check for existence of left & right streams
+ Synth_BUS_UPLINK uplink;
+ uplink.busname("out_soundcard");
+ connect(result,"left",uplink,"left");
+ connect(result,"right",uplink,"right");
+ uplink.start();
+ result._node()->start();
+ result._addChild(uplink,"uplink");
+ return result;
+ }
+ else arts_warning("couldn't load file %s", filename.c_str());
+ }
+ else arts_warning("file format extension %s unsupported",extension.c_str());
+
+ return PlayObject::null();
+}
+
+#ifndef __SUNPRO_CC
+/* SunPRO CC has problems finding the SimpleSoundServer_impl constructor
+ implementation from a template instantiation file, if this is here,
+ although I verified that the requested and provided symbols do indeed match,
+ and the latter is global. Wonderfully this problem goes away, if we don't
+ register the implementation here, but in another file. I bet this is
+ because of the static var that is created by the macro. */
+REGISTER_IMPLEMENTATION(SimpleSoundServer_impl);
+#endif