--- linuxsampler/trunk/src/network/lscpserver.cpp 2007/11/14 23:42:15 1481 +++ linuxsampler/trunk/src/network/lscpserver.cpp 2008/02/14 14:58:50 1686 @@ -3,7 +3,7 @@ * LinuxSampler - modular, streaming capable sampler * * * * Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck * - * Copyright (C) 2005 - 2007 Christian Schoenebeck * + * Copyright (C) 2005 - 2008 Christian Schoenebeck * * * * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -26,6 +26,7 @@ #include "lscpevent.h" #if defined(WIN32) +#include #else #include #endif @@ -100,7 +101,7 @@ Mutex LSCPServer::SubscriptionMutex = Mutex(); Mutex LSCPServer::RTNotifyMutex = Mutex(); -LSCPServer::LSCPServer(Sampler* pSampler, long int addr, short int port) : Thread(true, false, 0, -4) { +LSCPServer::LSCPServer(Sampler* pSampler, long int addr, short int port) : Thread(true, false, 0, -4), eventHandler(this) { SocketAddress.sin_family = AF_INET; SocketAddress.sin_addr.s_addr = addr; SocketAddress.sin_port = port; @@ -126,8 +127,10 @@ LSCPEvent::RegisterEvent(LSCPEvent::event_db_instr_info, "DB_INSTRUMENT_INFO"); LSCPEvent::RegisterEvent(LSCPEvent::event_db_instrs_job_info, "DB_INSTRUMENTS_JOB_INFO"); LSCPEvent::RegisterEvent(LSCPEvent::event_misc, "MISCELLANEOUS"); + LSCPEvent::RegisterEvent(LSCPEvent::event_total_stream_count, "TOTAL_STREAM_COUNT"); LSCPEvent::RegisterEvent(LSCPEvent::event_total_voice_count, "TOTAL_VOICE_COUNT"); LSCPEvent::RegisterEvent(LSCPEvent::event_global_info, "GLOBAL_INFO"); + LSCPEvent::RegisterEvent(LSCPEvent::event_channel_midi, "CHANNEL_MIDI"); hSocket = -1; } @@ -139,10 +142,63 @@ #endif } +LSCPServer::EventHandler::EventHandler(LSCPServer* pParent) { + this->pParent = pParent; +} + +LSCPServer::EventHandler::~EventHandler() { + std::vector l = channelMidiListeners; + channelMidiListeners.clear(); + for (int i = 0; i < l.size(); i++) + delete l[i].pMidiListener; +} + void LSCPServer::EventHandler::ChannelCountChanged(int NewCount) { LSCPServer::SendLSCPNotify(LSCPEvent(LSCPEvent::event_channel_count, NewCount)); } +void LSCPServer::EventHandler::ChannelAdded(SamplerChannel* pChannel) { + pChannel->AddEngineChangeListener(this); +} + +void LSCPServer::EventHandler::ChannelToBeRemoved(SamplerChannel* pChannel) { + if (!pChannel->GetEngineChannel()) return; + EngineToBeChanged(pChannel->Index()); +} + +void LSCPServer::EventHandler::EngineToBeChanged(int ChannelId) { + SamplerChannel* pSamplerChannel = + pParent->pSampler->GetSamplerChannel(ChannelId); + if (!pSamplerChannel) return; + EngineChannel* pEngineChannel = + pSamplerChannel->GetEngineChannel(); + if (!pEngineChannel) return; + for (std::vector::iterator iter = channelMidiListeners.begin(); iter != channelMidiListeners.end(); ++iter) { + if ((*iter).pEngineChannel == pEngineChannel) { + VirtualMidiDevice* pMidiListener = (*iter).pMidiListener; + pEngineChannel->Disconnect(pMidiListener); + channelMidiListeners.erase(iter); + delete pMidiListener; + return; + } + } +} + +void LSCPServer::EventHandler::EngineChanged(int ChannelId) { + SamplerChannel* pSamplerChannel = + pParent->pSampler->GetSamplerChannel(ChannelId); + if (!pSamplerChannel) return; + EngineChannel* pEngineChannel = + pSamplerChannel->GetEngineChannel(); + if (!pEngineChannel) return; + VirtualMidiDevice* pMidiListener = new VirtualMidiDevice; + pEngineChannel->Connect(pMidiListener); + midi_listener_entry entry = { + pSamplerChannel, pEngineChannel, pMidiListener + }; + channelMidiListeners.push_back(entry); +} + void LSCPServer::EventHandler::AudioDeviceCountChanged(int NewCount) { LSCPServer::SendLSCPNotify(LSCPEvent(LSCPEvent::event_audio_device_count, NewCount)); } @@ -187,6 +243,10 @@ LSCPServer::SendLSCPNotify(LSCPEvent(LSCPEvent::event_total_voice_count, NewCount)); } +void LSCPServer::EventHandler::TotalStreamCountChanged(int NewCount) { + LSCPServer::SendLSCPNotify(LSCPEvent(LSCPEvent::event_total_stream_count, NewCount)); +} + #if HAVE_SQLITE3 void LSCPServer::DbInstrumentsEventHandler::DirectoryCountChanged(String Dir) { LSCPServer::SendLSCPNotify(LSCPEvent(LSCPEvent::event_db_instr_dir_count, InstrumentsDb::toEscapedPath(Dir))); @@ -283,6 +343,7 @@ pSampler->AddVoiceCountListener(&eventHandler); pSampler->AddStreamCountListener(&eventHandler); pSampler->AddBufferFillListener(&eventHandler); + pSampler->AddTotalStreamCountListener(&eventHandler); pSampler->AddTotalVoiceCountListener(&eventHandler); pSampler->AddFxSendCountListener(&eventHandler); MidiInstrumentMapper::AddMidiInstrumentCountListener(&eventHandler); @@ -302,6 +363,9 @@ timeval timeout; while (true) { + #if CONFIG_PTHREAD_TESTCANCEL + TestCancel(); + #endif // check if some engine channel's parameter / status changed, if so notify the respective LSCP event subscribers { std::set engineChannels = EngineChannelFactory::EngineChannelInstances(); @@ -323,6 +387,30 @@ } } + // check if MIDI data arrived on some engine channel + for (int i = 0; i < eventHandler.channelMidiListeners.size(); ++i) { + const EventHandler::midi_listener_entry entry = + eventHandler.channelMidiListeners[i]; + VirtualMidiDevice* pMidiListener = entry.pMidiListener; + if (pMidiListener->NotesChanged()) { + for (int iNote = 0; iNote < 128; iNote++) { + if (pMidiListener->NoteChanged(iNote)) { + const bool bActive = pMidiListener->NoteIsActive(iNote); + LSCPServer::SendLSCPNotify( + LSCPEvent( + LSCPEvent::event_channel_midi, + entry.pSamplerChannel->Index(), + std::string(bActive ? "NOTE_ON" : "NOTE_OFF"), + iNote, + bActive ? pMidiListener->NoteOnVelocity(iNote) + : pMidiListener->NoteOffVelocity(iNote) + ) + ); + } + } + } + } + //Now let's deliver late notifies (if any) NotifyBufferMutex.Lock(); for (std::map::iterator iterNotify = bufferedNotifies.begin(); iterNotify != bufferedNotifies.end(); iterNotify++) { @@ -367,7 +455,7 @@ std::cerr << "LSCPServer: ioctlsocket: set FIONBIO failed. Error " << WSAGetLastError() << std::endl; exit(EXIT_FAILURE); } - #else + #else if (fcntl(socket, F_SETFL, O_NONBLOCK)) { std::cerr << "LSCPServer: F_SETFL O_NONBLOCK failed." << std::endl; exit(EXIT_FAILURE); @@ -439,6 +527,14 @@ NotifyMutex.Unlock(); } +void LSCPServer::LockRTNotify() { + RTNotifyMutex.Lock(); +} + +void LSCPServer::UnlockRTNotify() { + RTNotifyMutex.Unlock(); +} + int LSCPServer::EventSubscribers( std::list events ) { int subs = 0; SubscriptionMutex.Lock(); @@ -538,7 +634,7 @@ int wsa_lasterror = WSAGetLastError(); if (wsa_lasterror == WSAEWOULDBLOCK) //Would block, try again later. return false; - dmsg(2,("LSCPScanner: Socket error after recv() Error %d.\n", wsa_lasterror)); + dmsg(2,("LSCPScanner: Socket error after recv() Error %d.\n", wsa_lasterror)); CloseConnection(iter); break; } @@ -2346,6 +2442,16 @@ } /** + * Will be called by the parser to return the current number of all active streams. + */ +String LSCPServer::GetTotalStreamCount() { + dmsg(2,("LSCPServer: GetTotalStreamCount()\n")); + LSCPResultSet result; + result.Add(pSampler->GetDiskStreamCount()); + return result.Produce(); +} + +/** * Will be called by the parser to return the current number of all active voices. */ String LSCPServer::GetTotalVoiceCount() { @@ -2383,6 +2489,166 @@ return result.Produce(); } +String LSCPServer::GetFileInstruments(String Filename) { + dmsg(2,("LSCPServer: GetFileInstruments(String Filename=%s)\n",Filename.c_str())); + LSCPResultSet result; + try { + VerifyFile(Filename); + } catch (Exception e) { + result.Error(e); + return result.Produce(); + } + // try to find a sampler engine that can handle the file + bool bFound = false; + std::vector engineTypes = EngineFactory::AvailableEngineTypes(); + for (int i = 0; !bFound && i < engineTypes.size(); i++) { + Engine* pEngine = NULL; + try { + pEngine = EngineFactory::Create(engineTypes[i]); + if (!pEngine) throw Exception("Internal error: could not create '" + engineTypes[i] + "' engine"); + InstrumentManager* pManager = pEngine->GetInstrumentManager(); + if (pManager) { + std::vector IDs = + pManager->GetInstrumentFileContent(Filename); + // return the amount of instruments in the file + result.Add(IDs.size()); + // no more need to ask other engine types + bFound = true; + } else dmsg(1,("Warning: engine '%s' does not provide an instrument manager\n", engineTypes[i].c_str())); + } catch (Exception e) { + // NOOP, as exception is thrown if engine doesn't support file + } + if (pEngine) EngineFactory::Destroy(pEngine); + } + + if (!bFound) result.Error("Unknown file format"); + return result.Produce(); +} + +String LSCPServer::ListFileInstruments(String Filename) { + dmsg(2,("LSCPServer: ListFileInstruments(String Filename=%s)\n",Filename.c_str())); + LSCPResultSet result; + try { + VerifyFile(Filename); + } catch (Exception e) { + result.Error(e); + return result.Produce(); + } + // try to find a sampler engine that can handle the file + bool bFound = false; + std::vector engineTypes = EngineFactory::AvailableEngineTypes(); + for (int i = 0; !bFound && i < engineTypes.size(); i++) { + Engine* pEngine = NULL; + try { + pEngine = EngineFactory::Create(engineTypes[i]); + if (!pEngine) throw Exception("Internal error: could not create '" + engineTypes[i] + "' engine"); + InstrumentManager* pManager = pEngine->GetInstrumentManager(); + if (pManager) { + std::vector IDs = + pManager->GetInstrumentFileContent(Filename); + // return a list of IDs of the instruments in the file + String s; + for (int j = 0; j < IDs.size(); j++) { + if (s.size()) s += ","; + s += ToString(IDs[j].Index); + } + result.Add(s); + // no more need to ask other engine types + bFound = true; + } else dmsg(1,("Warning: engine '%s' does not provide an instrument manager\n", engineTypes[i].c_str())); + } catch (Exception e) { + // NOOP, as exception is thrown if engine doesn't support file + } + if (pEngine) EngineFactory::Destroy(pEngine); + } + + if (!bFound) result.Error("Unknown file format"); + return result.Produce(); +} + +String LSCPServer::GetFileInstrumentInfo(String Filename, uint InstrumentID) { + dmsg(2,("LSCPServer: GetFileInstrumentInfo(String Filename=%s, InstrumentID=%d)\n",Filename.c_str(),InstrumentID)); + LSCPResultSet result; + try { + VerifyFile(Filename); + } catch (Exception e) { + result.Error(e); + return result.Produce(); + } + InstrumentManager::instrument_id_t id; + id.FileName = Filename; + id.Index = InstrumentID; + // try to find a sampler engine that can handle the file + bool bFound = false; + bool bFatalErr = false; + std::vector engineTypes = EngineFactory::AvailableEngineTypes(); + for (int i = 0; !bFound && !bFatalErr && i < engineTypes.size(); i++) { + Engine* pEngine = NULL; + try { + pEngine = EngineFactory::Create(engineTypes[i]); + if (!pEngine) throw Exception("Internal error: could not create '" + engineTypes[i] + "' engine"); + InstrumentManager* pManager = pEngine->GetInstrumentManager(); + if (pManager) { + // check if the instrument index is valid + // FIXME: this won't work if an engine only supports parts of the instrument file + std::vector IDs = + pManager->GetInstrumentFileContent(Filename); + if (std::find(IDs.begin(), IDs.end(), id) == IDs.end()) { + std::stringstream ss; + ss << "Invalid instrument index " << InstrumentID << " for instrument file '" << Filename << "'"; + bFatalErr = true; + throw Exception(ss.str()); + } + // get the info of the requested instrument + InstrumentManager::instrument_info_t info = + pManager->GetInstrumentInfo(id); + // return detailed informations about the file + result.Add("NAME", info.InstrumentName); + result.Add("FORMAT_FAMILY", engineTypes[i]); + result.Add("FORMAT_VERSION", info.FormatVersion); + result.Add("PRODUCT", info.Product); + result.Add("ARTISTS", info.Artists); + // no more need to ask other engine types + bFound = true; + } else dmsg(1,("Warning: engine '%s' does not provide an instrument manager\n", engineTypes[i].c_str())); + } catch (Exception e) { + // usually NOOP, as exception is thrown if engine doesn't support file + if (bFatalErr) result.Error(e); + } + if (pEngine) EngineFactory::Destroy(pEngine); + } + + if (!bFound && !bFatalErr) result.Error("Unknown file format"); + return result.Produce(); +} + +void LSCPServer::VerifyFile(String Filename) { + #if WIN32 + WIN32_FIND_DATA win32FileAttributeData; + BOOL res = GetFileAttributesEx( Filename.c_str(), GetFileExInfoStandard, &win32FileAttributeData ); + if (!res) { + std::stringstream ss; + ss << "File does not exist, GetFileAttributesEx failed `" << Filename << "`: Error " << GetLastError(); + throw Exception(ss.str()); + } + if ( win32FileAttributeData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { + throw Exception("Directory is specified"); + } + #else + struct stat statBuf; + int res = stat(Filename.c_str(), &statBuf); + if (res) { + std::stringstream ss; + ss << "Fail to stat `" << Filename << "`: " << strerror(errno); + throw Exception(ss.str()); + } + + if (S_ISDIR(statBuf.st_mode)) { + throw Exception("Directory is specified"); + } + #endif +} + /** * Will be called by the parser to subscribe a client (frontend) on the * server for receiving event messages.