<commit_before>#include "Chan.h"
#include "znc.h"
#include "User.h"
#include "Utils.h"

void CChan::Reset() {
	m_bWhoDone = false;
	m_bIsOn = false;
	m_bIsOp = false;
	m_bIsVoice = false;
	m_uOpCount = 0;
	m_uVoiceCount =	0;
	m_uModes = 0;
	m_uLimit = 0;
	m_uClientRequests = 0;
	ClearNicks();
}

void CChan::Joined() {
}

void CChan::Cycle() const {
	if (AutoCycle()) {
		m_pUser->PutIRC("PART " + GetName() + "\r\nJOIN " + GetName() + " " + GetKey());
	}
}

void CChan::JoinUser() {
	if (!IsOn()) {
		IncClientRequests();
		m_pUser->PutIRC("JOIN " + GetName());
		return;
	}

	m_pUser->PutUser(":" + m_pUser->GetIRCNick().GetNickMask() + " JOIN :" + GetName());

	if (!GetTopic().empty()) {
		m_pUser->PutUser(":" + m_pUser->GetIRCServer() + " 332 " + m_pUser->GetIRCNick().GetNick() + " " + GetName() + " :" + GetTopic());
		m_pUser->PutUser(":" + m_pUser->GetIRCServer() + " 333 " + m_pUser->GetIRCNick().GetNick() + " " + GetName() + " " + GetTopicOwner() + " " + CUtils::ToString(GetTopicDate()));
	}

	string sPre = ":" + m_pUser->GetIRCServer() + " 353 " + m_pUser->GetIRCNick().GetNick() + " = " + GetName() + " :";
	string sLine = sPre;

	for (map<string,CNick*>::iterator a = m_msNicks.begin(); a != m_msNicks.end(); a++) {
		if (a->second->IsOp()) {
			sLine += "@";
		} else if (a->second->IsVoice()) {
			sLine += "+";
		}

		sLine += a->first;

		if (sLine.size() >= 490 || a == (--m_msNicks.end())) {
			m_pUser->PutUser(sLine);
			sLine = sPre;
		} else {
			sLine += " ";
		}
	}

	m_pUser->PutUser(":" + m_pUser->GetIRCServer() + " 366 " + m_pUser->GetIRCNick().GetNick() + " " + GetName() + " :End of /NAMES list.");

	//m_pUser->PutIRC("NAMES " + GetName());
	//m_pUser->PutIRC("TOPIC " + GetName());

	SendBuffer();

	m_bDetached = false;
}

void CChan::SendBuffer() {
	if (m_pUser->IsUserAttached()) {
		const vector<string>& vsBuffer = GetBuffer();

		if (vsBuffer.size()) {
			m_pUser->PutUser(":***!znc@znc.com PRIVMSG " + GetName() + " :Buffer Playback...");

			for (unsigned int a = 0; a < vsBuffer.size(); a++) {
				m_pUser->PutUser(vsBuffer[a]);
			}

			if (!KeepBuffer()) {
				ClearBuffer();
			}

			m_pUser->PutUser(":***!znc@znc.com PRIVMSG " + GetName() + " :Playback Complete.");
		}
	}
}

void CChan::DetachUser() {
	m_pUser->PutUser(":" + m_pUser->GetIRCNick().GetNickMask() + " PART " + GetName());
	m_bDetached = true;
}

string CChan::GetModeString() const {
	string sRet;

	if (m_uModes & Secret) { sRet += "s"; }
	if (m_uModes & Private) { sRet += "p"; }
	if (m_uModes & OpTopic) { sRet += "t"; }
	if (m_uModes & InviteOnly) { sRet += "i"; }
	if (m_uModes & NoMessages) { sRet += "n"; }
	if (m_uModes & Moderated) { sRet += "m"; }
	if (m_uLimit) { sRet += "l"; }
	if (m_uModes & Key) { sRet += "k"; }

	return (sRet.empty()) ? sRet : ("+" + sRet);
}

void CChan::SetModes(const string& sModes) {
	m_uModes = 0;
	m_uLimit = 0;
	m_sKey = "";
	ModeChange(sModes);
}

void CChan::IncClientRequests() {
	m_uClientRequests++;
}

bool CChan::DecClientRequests() {
	if (!m_uClientRequests) {
		return false;
	}

	m_uClientRequests--;
	return true;
}

bool CChan::Who() {
	if (m_bWhoDone) {
		return false;
	}

	m_pUser->PutIRC("WHO " + GetName());
	return true;
}

void CChan::OnWho(const string& sNick, const string& sIdent, const string& sHost) {
	CNick* pNick = FindNick(sNick);

	if (pNick) {
		pNick->SetIdent(sIdent);
		pNick->SetHost(sHost);
	}
}

void CChan::ModeChange(const string& sModes, const string& sOpNick) {
	string sModeArg = CUtils::Token(sModes, 0);
	string sArgs = CUtils::Token(sModes, 1, true);
	bool bAdd = true;

#ifdef _MODULES
	CNick* pNick = FindNick(sOpNick);
	if (pNick) {
		m_pUser->GetModules().OnRawMode(*pNick, *this, sModeArg, sArgs);
	}
#endif

	for (unsigned int a = 0; a < sModeArg.size(); a++) {
		switch (sModeArg[a]) {
			case '+': bAdd = true; break;
			case '-': bAdd = false; break;
			case 's': if (bAdd) { m_uModes |= Secret; } else { m_uModes &= ~Secret; } break;
			case 'p': if (bAdd) { m_uModes |= Private; } else { m_uModes &= ~Private; }  break;
			case 'm': if (bAdd) { m_uModes |= Moderated; } else { m_uModes &= ~Moderated; }  break;
			case 'i': if (bAdd) { m_uModes |= InviteOnly; } else { m_uModes &= ~InviteOnly; }  break;
			case 'n': if (bAdd) { m_uModes |= NoMessages; } else { m_uModes &= ~NoMessages; }  break;
			case 't': if (bAdd) { m_uModes |= OpTopic; } else { m_uModes &= ~OpTopic; }  break;
			case 'l': if (bAdd) { m_uLimit = strtoul(GetModeArg(sArgs).c_str(), NULL, 10); } else { m_uLimit = 0; }  break;
			case 'k': if (bAdd) { m_uModes |= Key; SetKey(GetModeArg(sArgs)); } else { m_uModes &= ~Key; GetModeArg(sArgs); }  break;
			case 'o': OnOp(sOpNick, GetModeArg(sArgs), bAdd); break;
			case 'v': OnVoice(sOpNick, GetModeArg(sArgs), bAdd); break;
			case 'b': // Don't do anything with bans yet
			case 'e': // Don't do anything with excepts yet
			default: GetModeArg(sArgs); // Pop off an arg, assume new modes will have an argument
		}
	}
}

string CChan::GetModeArg(string& sArgs) const {
	string sRet = sArgs.substr(0, sArgs.find(' '));
	sArgs = (sRet.size() < sArgs.size()) ? sArgs.substr(sRet.size() +1) : "";
	return sRet;
}

void CChan::ClearNicks() {
	for (map<string,CNick*>::iterator a = m_msNicks.begin(); a != m_msNicks.end(); a++) {
		delete a->second;
	}

	m_msNicks.clear();
}

int CChan::AddNicks(const string& sNicks) {
	if (IsOn()) {
		return 0;
	}

	int iRet = 0;
	string sCurNick;

	for (unsigned int a = 0; a < sNicks.size(); a++) {
		switch (sNicks[a]) {
			case ' ':
				if (AddNick(sCurNick)) {
					iRet++;
				}

				sCurNick = "";

				break;
			default:
				sCurNick += sNicks[a];
				break;
		}
	}

	if (!sCurNick.empty()) {
		if (AddNick(sCurNick)) {
			iRet++;
		}
	}

	return iRet;
}

bool CChan::AddNick(const string& sNick) {
	const char* p = sNick.c_str();
	bool bIsOp = false;
	bool bIsVoice = false;

	switch (*p) {
		case '\0':
			return false;
		case '@':
			bIsOp = true;
		case '+':
			if (!*++p) {
				return false;
			}

			bIsVoice = !bIsOp;
	}

	CNick* pNick = FindNick(p);
	if (!pNick) {
		pNick = new CNick(p);
	}

	if ((bIsOp) && (!pNick->IsOp())) {
		IncOpCount();
		pNick->SetOp(true);

		if (strcasecmp(pNick->GetNick().c_str(), m_pUser->GetCurNick().c_str()) == 0) {
			SetOpped(true);
		}
	} else if ((bIsVoice) && (!pNick->IsVoice())) {
		IncVoiceCount();
		pNick->SetVoice(true);

		if (strcasecmp(pNick->GetNick().c_str(), m_pUser->GetCurNick().c_str()) == 0) {
			SetVoiced(true);
		}
	}

	m_msNicks[pNick->GetNick()] = pNick;

	return true;
}

bool CChan::RemNick(const string& sNick) {
	map<string,CNick*>::iterator it = m_msNicks.find(sNick);
	if (it == m_msNicks.end()) {
		return false;
	}

	if (it->second->IsOp()) {
		DecOpCount();
	}

	if (it->second->IsVoice()) {
		DecVoiceCount();
	}

	delete it->second;
	m_msNicks.erase(it);
	CNick* pNick = m_msNicks.begin()->second;

	if ((m_msNicks.size() == 1) && (!pNick->IsOp()) && (strcasecmp(pNick->GetNick().c_str(), m_pUser->GetCurNick().c_str()) == 0)) {
		Cycle();
	}

	return true;
}

bool CChan::ChangeNick(const string& sOldNick, const string& sNewNick) {
	map<string,CNick*>::iterator it = m_msNicks.find(sOldNick);

	if (it == m_msNicks.end()) {
		return false;
	}

	// Rename this nick
	it->second->SetNick(sNewNick);

	// Insert a new element into the map then erase the old one, do this to change the key
	m_msNicks[sNewNick] = it->second;
	m_msNicks.erase(it);

	return true;
}

void CChan::OnOp(const string& sOpNick, const string& sNick, bool bOpped) {
	CNick* pNick = FindNick(sNick);

	if (pNick) {
		bool bNoChange = (pNick->IsOp() == bOpped);
#ifdef _MODULES
		CNick* pOpNick = FindNick(sOpNick);

		if (pOpNick) {
			if (bOpped) {
				m_pUser->GetModules().OnOp(*pOpNick, *pNick, *this, bNoChange);
			} else {
				m_pUser->GetModules().OnDeop(*pOpNick, *pNick, *this, bNoChange);
			}
		}
#endif

		if (strcasecmp(sNick.c_str(), m_pUser->GetCurNick().c_str()) == 0) {
			SetOpped(bOpped);
		}

		if (bNoChange) {
			// If no change, return
			return;
		}

		pNick->SetOp(bOpped);
		(bOpped) ? IncOpCount() : DecOpCount();
	}
}

void CChan::OnVoice(const string& sOpNick, const string& sNick, bool bVoiced) {
	CNick* pNick = FindNick(sNick);

	if (pNick) {
		bool bNoChange = (pNick->IsVoice() == bVoiced);

#ifdef _MODULES
		CNick* pOpNick = FindNick(sOpNick);

		if (pOpNick) {
			if (bVoiced) {
				m_pUser->GetModules().OnVoice(*pOpNick, *pNick, *this, bNoChange);
			} else {
				m_pUser->GetModules().OnDevoice(*pOpNick, *pNick, *this, bNoChange);
			}
		}
#endif

		if (strcasecmp(sNick.c_str(), m_pUser->GetCurNick().c_str()) == 0) {
			SetVoiced(bVoiced);
		}

		if (bNoChange) {
			// If no change, return
			return;
		}

		pNick->SetVoice(bVoiced);
		(bVoiced) ? IncVoiceCount() : DecVoiceCount();
	}
}

CNick* CChan::FindNick(const string& sNick) const {
	map<string,CNick*>::const_iterator it = m_msNicks.find(sNick);
	return (it != m_msNicks.end()) ? it->second : NULL;
}

int CChan::AddBuffer(const string& sLine) {
	// Todo: revisit the buffering
	if (!m_uBufferCount) {
		return 0;
	}

	if (m_vsBuffer.size() >= m_uBufferCount) {
		m_vsBuffer.erase(m_vsBuffer.begin());
	}

	m_vsBuffer.push_back(sLine);
	return m_vsBuffer.size();
}

void CChan::ClearBuffer() {
	m_vsBuffer.clear();
}

<commit_msg>Moved AutoCycle() check to the caller instead of inside of Cycle()<commit_after>#include "Chan.h"
#include "znc.h"
#include "User.h"
#include "Utils.h"

void CChan::Reset() {
	m_bWhoDone = false;
	m_bIsOn = false;
	m_bIsOp = false;
	m_bIsVoice = false;
	m_uOpCount = 0;
	m_uVoiceCount =	0;
	m_uModes = 0;
	m_uLimit = 0;
	m_uClientRequests = 0;
	ClearNicks();
}

void CChan::Joined() {
}

void CChan::Cycle() const {
	m_pUser->PutIRC("PART " + GetName() + "\r\nJOIN " + GetName() + " " + GetKey());
}

void CChan::JoinUser() {
	if (!IsOn()) {
		IncClientRequests();
		m_pUser->PutIRC("JOIN " + GetName());
		return;
	}

	m_pUser->PutUser(":" + m_pUser->GetIRCNick().GetNickMask() + " JOIN :" + GetName());

	if (!GetTopic().empty()) {
		m_pUser->PutUser(":" + m_pUser->GetIRCServer() + " 332 " + m_pUser->GetIRCNick().GetNick() + " " + GetName() + " :" + GetTopic());
		m_pUser->PutUser(":" + m_pUser->GetIRCServer() + " 333 " + m_pUser->GetIRCNick().GetNick() + " " + GetName() + " " + GetTopicOwner() + " " + CUtils::ToString(GetTopicDate()));
	}

	string sPre = ":" + m_pUser->GetIRCServer() + " 353 " + m_pUser->GetIRCNick().GetNick() + " = " + GetName() + " :";
	string sLine = sPre;

	for (map<string,CNick*>::iterator a = m_msNicks.begin(); a != m_msNicks.end(); a++) {
		if (a->second->IsOp()) {
			sLine += "@";
		} else if (a->second->IsVoice()) {
			sLine += "+";
		}

		sLine += a->first;

		if (sLine.size() >= 490 || a == (--m_msNicks.end())) {
			m_pUser->PutUser(sLine);
			sLine = sPre;
		} else {
			sLine += " ";
		}
	}

	m_pUser->PutUser(":" + m_pUser->GetIRCServer() + " 366 " + m_pUser->GetIRCNick().GetNick() + " " + GetName() + " :End of /NAMES list.");

	//m_pUser->PutIRC("NAMES " + GetName());
	//m_pUser->PutIRC("TOPIC " + GetName());

	SendBuffer();

	m_bDetached = false;
}

void CChan::SendBuffer() {
	if (m_pUser->IsUserAttached()) {
		const vector<string>& vsBuffer = GetBuffer();

		if (vsBuffer.size()) {
			m_pUser->PutUser(":***!znc@znc.com PRIVMSG " + GetName() + " :Buffer Playback...");

			for (unsigned int a = 0; a < vsBuffer.size(); a++) {
				m_pUser->PutUser(vsBuffer[a]);
			}

			if (!KeepBuffer()) {
				ClearBuffer();
			}

			m_pUser->PutUser(":***!znc@znc.com PRIVMSG " + GetName() + " :Playback Complete.");
		}
	}
}

void CChan::DetachUser() {
	m_pUser->PutUser(":" + m_pUser->GetIRCNick().GetNickMask() + " PART " + GetName());
	m_bDetached = true;
}

string CChan::GetModeString() const {
	string sRet;

	if (m_uModes & Secret) { sRet += "s"; }
	if (m_uModes & Private) { sRet += "p"; }
	if (m_uModes & OpTopic) { sRet += "t"; }
	if (m_uModes & InviteOnly) { sRet += "i"; }
	if (m_uModes & NoMessages) { sRet += "n"; }
	if (m_uModes & Moderated) { sRet += "m"; }
	if (m_uLimit) { sRet += "l"; }
	if (m_uModes & Key) { sRet += "k"; }

	return (sRet.empty()) ? sRet : ("+" + sRet);
}

void CChan::SetModes(const string& sModes) {
	m_uModes = 0;
	m_uLimit = 0;
	m_sKey = "";
	ModeChange(sModes);
}

void CChan::IncClientRequests() {
	m_uClientRequests++;
}

bool CChan::DecClientRequests() {
	if (!m_uClientRequests) {
		return false;
	}

	m_uClientRequests--;
	return true;
}

bool CChan::Who() {
	if (m_bWhoDone) {
		return false;
	}

	m_pUser->PutIRC("WHO " + GetName());
	return true;
}

void CChan::OnWho(const string& sNick, const string& sIdent, const string& sHost) {
	CNick* pNick = FindNick(sNick);

	if (pNick) {
		pNick->SetIdent(sIdent);
		pNick->SetHost(sHost);
	}
}

void CChan::ModeChange(const string& sModes, const string& sOpNick) {
	string sModeArg = CUtils::Token(sModes, 0);
	string sArgs = CUtils::Token(sModes, 1, true);
	bool bAdd = true;

#ifdef _MODULES
	CNick* pNick = FindNick(sOpNick);
	if (pNick) {
		m_pUser->GetModules().OnRawMode(*pNick, *this, sModeArg, sArgs);
	}
#endif

	for (unsigned int a = 0; a < sModeArg.size(); a++) {
		switch (sModeArg[a]) {
			case '+': bAdd = true; break;
			case '-': bAdd = false; break;
			case 's': if (bAdd) { m_uModes |= Secret; } else { m_uModes &= ~Secret; } break;
			case 'p': if (bAdd) { m_uModes |= Private; } else { m_uModes &= ~Private; }  break;
			case 'm': if (bAdd) { m_uModes |= Moderated; } else { m_uModes &= ~Moderated; }  break;
			case 'i': if (bAdd) { m_uModes |= InviteOnly; } else { m_uModes &= ~InviteOnly; }  break;
			case 'n': if (bAdd) { m_uModes |= NoMessages; } else { m_uModes &= ~NoMessages; }  break;
			case 't': if (bAdd) { m_uModes |= OpTopic; } else { m_uModes &= ~OpTopic; }  break;
			case 'l': if (bAdd) { m_uLimit = strtoul(GetModeArg(sArgs).c_str(), NULL, 10); } else { m_uLimit = 0; }  break;
			case 'k': if (bAdd) { m_uModes |= Key; SetKey(GetModeArg(sArgs)); } else { m_uModes &= ~Key; GetModeArg(sArgs); }  break;
			case 'o': OnOp(sOpNick, GetModeArg(sArgs), bAdd); break;
			case 'v': OnVoice(sOpNick, GetModeArg(sArgs), bAdd); break;
			case 'b': // Don't do anything with bans yet
			case 'e': // Don't do anything with excepts yet
			default: GetModeArg(sArgs); // Pop off an arg, assume new modes will have an argument
		}
	}
}

string CChan::GetModeArg(string& sArgs) const {
	string sRet = sArgs.substr(0, sArgs.find(' '));
	sArgs = (sRet.size() < sArgs.size()) ? sArgs.substr(sRet.size() +1) : "";
	return sRet;
}

void CChan::ClearNicks() {
	for (map<string,CNick*>::iterator a = m_msNicks.begin(); a != m_msNicks.end(); a++) {
		delete a->second;
	}

	m_msNicks.clear();
}

int CChan::AddNicks(const string& sNicks) {
	if (IsOn()) {
		return 0;
	}

	int iRet = 0;
	string sCurNick;

	for (unsigned int a = 0; a < sNicks.size(); a++) {
		switch (sNicks[a]) {
			case ' ':
				if (AddNick(sCurNick)) {
					iRet++;
				}

				sCurNick = "";

				break;
			default:
				sCurNick += sNicks[a];
				break;
		}
	}

	if (!sCurNick.empty()) {
		if (AddNick(sCurNick)) {
			iRet++;
		}
	}

	return iRet;
}

bool CChan::AddNick(const string& sNick) {
	const char* p = sNick.c_str();
	bool bIsOp = false;
	bool bIsVoice = false;

	switch (*p) {
		case '\0':
			return false;
		case '@':
			bIsOp = true;
		case '+':
			if (!*++p) {
				return false;
			}

			bIsVoice = !bIsOp;
	}

	CNick* pNick = FindNick(p);
	if (!pNick) {
		pNick = new CNick(p);
	}

	if ((bIsOp) && (!pNick->IsOp())) {
		IncOpCount();
		pNick->SetOp(true);

		if (strcasecmp(pNick->GetNick().c_str(), m_pUser->GetCurNick().c_str()) == 0) {
			SetOpped(true);
		}
	} else if ((bIsVoice) && (!pNick->IsVoice())) {
		IncVoiceCount();
		pNick->SetVoice(true);

		if (strcasecmp(pNick->GetNick().c_str(), m_pUser->GetCurNick().c_str()) == 0) {
			SetVoiced(true);
		}
	}

	m_msNicks[pNick->GetNick()] = pNick;

	return true;
}

bool CChan::RemNick(const string& sNick) {
	map<string,CNick*>::iterator it = m_msNicks.find(sNick);
	if (it == m_msNicks.end()) {
		return false;
	}

	if (it->second->IsOp()) {
		DecOpCount();
	}

	if (it->second->IsVoice()) {
		DecVoiceCount();
	}

	delete it->second;
	m_msNicks.erase(it);
	CNick* pNick = m_msNicks.begin()->second;

	if ((m_msNicks.size() == 1) && (!pNick->IsOp()) && (strcasecmp(pNick->GetNick().c_str(), m_pUser->GetCurNick().c_str()) == 0)) {
		if (AutoCycle()) {
			Cycle();
		}
	}

	return true;
}

bool CChan::ChangeNick(const string& sOldNick, const string& sNewNick) {
	map<string,CNick*>::iterator it = m_msNicks.find(sOldNick);

	if (it == m_msNicks.end()) {
		return false;
	}

	// Rename this nick
	it->second->SetNick(sNewNick);

	// Insert a new element into the map then erase the old one, do this to change the key
	m_msNicks[sNewNick] = it->second;
	m_msNicks.erase(it);

	return true;
}

void CChan::OnOp(const string& sOpNick, const string& sNick, bool bOpped) {
	CNick* pNick = FindNick(sNick);

	if (pNick) {
		bool bNoChange = (pNick->IsOp() == bOpped);
#ifdef _MODULES
		CNick* pOpNick = FindNick(sOpNick);

		if (pOpNick) {
			if (bOpped) {
				m_pUser->GetModules().OnOp(*pOpNick, *pNick, *this, bNoChange);
			} else {
				m_pUser->GetModules().OnDeop(*pOpNick, *pNick, *this, bNoChange);
			}
		}
#endif

		if (strcasecmp(sNick.c_str(), m_pUser->GetCurNick().c_str()) == 0) {
			SetOpped(bOpped);
		}

		if (bNoChange) {
			// If no change, return
			return;
		}

		pNick->SetOp(bOpped);
		(bOpped) ? IncOpCount() : DecOpCount();
	}
}

void CChan::OnVoice(const string& sOpNick, const string& sNick, bool bVoiced) {
	CNick* pNick = FindNick(sNick);

	if (pNick) {
		bool bNoChange = (pNick->IsVoice() == bVoiced);

#ifdef _MODULES
		CNick* pOpNick = FindNick(sOpNick);

		if (pOpNick) {
			if (bVoiced) {
				m_pUser->GetModules().OnVoice(*pOpNick, *pNick, *this, bNoChange);
			} else {
				m_pUser->GetModules().OnDevoice(*pOpNick, *pNick, *this, bNoChange);
			}
		}
#endif

		if (strcasecmp(sNick.c_str(), m_pUser->GetCurNick().c_str()) == 0) {
			SetVoiced(bVoiced);
		}

		if (bNoChange) {
			// If no change, return
			return;
		}

		pNick->SetVoice(bVoiced);
		(bVoiced) ? IncVoiceCount() : DecVoiceCount();
	}
}

CNick* CChan::FindNick(const string& sNick) const {
	map<string,CNick*>::const_iterator it = m_msNicks.find(sNick);
	return (it != m_msNicks.end()) ? it->second : NULL;
}

int CChan::AddBuffer(const string& sLine) {
	// Todo: revisit the buffering
	if (!m_uBufferCount) {
		return 0;
	}

	if (m_vsBuffer.size() >= m_uBufferCount) {
		m_vsBuffer.erase(m_vsBuffer.begin());
	}

	m_vsBuffer.push_back(sLine);
	return m_vsBuffer.size();
}

void CChan::ClearBuffer() {
	m_vsBuffer.clear();
}

<|endoftext|>


<commit_before>/*
 * Copyright (C) 2006-2020  Music Technology Group - Universitat Pompeu Fabra
 *
 * This file is part of Essentia
 *
 * Essentia is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation (FSF), 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 Affero GNU General Public License
 * version 3 along with this program.  If not, see http://www.gnu.org/licenses/
 */

#include "algorithmfactory.h"
#include "essentiamath.h"
#include "noveltycurve.h"

using namespace std;

namespace essentia {
namespace standard {

const char* NoveltyCurve::name = "NoveltyCurve";
const char* NoveltyCurve::category = "Rhythm";
const char* NoveltyCurve::description = DOC("This algorithm computes the \"novelty curve\" (Grosche & Müller, 2009) onset detection function. The algorithm expects as an input a frame-wise sequence of frequency-bands energies or spectrum magnitudes as originally proposed in [1] (see FrequencyBands and Spectrum algorithms). Novelty in each band (or frequency bin) is computed as a derivative between log-compressed energy (magnitude) values in consequent frames. The overall novelty value is then computed as a weighted sum that can be configured using 'weightCurve' parameter. The resulting novelty curve can be used for beat tracking and onset detection (see BpmHistogram and Onsets).\n"
"\n"
"Notes:\n"
"- Recommended frame/hop size for spectrum computation is 2048/1024 samples (44.1 kHz sampling rate) [2].\n"
"- Log compression is applied with C=1000 as in [1].\n"
"- Frequency bands energies (see FrequencyBands) as well as bin magnitudes for the whole spectrum can be used as an input. The implementation for the original algorithm [2] works with spectrum bin magnitudes for which novelty functions are computed separately and are then summarized into bands.\n"
"- In the case if 'weightCurve' is set to 'hybrid' a complex combination of flat, quadratic, linear and inverse quadratic weight curves is used. It was reported to improve performance of beat tracking in some informal in-house experiments (Note: this information is probably outdated).\n"
"\n"
"References:\n"
"  [1] P. Grosche and M. Müller, \"A mid-level representation for capturing\n"
"  dominant tempo and pulse information in music recordings,\" in\n"
"  International Society for Music Information Retrieval Conference\n"
"  (ISMIR’09), 2009, pp. 189–194.\n"
"  [2] Tempogram Toolbox (Matlab implementation),\n"
"  http://resources.mpi-inf.mpg.de/MIR/tempogramtoolbox\n");

vector<Real> NoveltyCurve::weightCurve(int size, WeightType type) {
  vector<Real> result(size, 0.0);
  int halfSize = size/2;
  int sqrHalfSize = halfSize*halfSize;
  int sqrSize = size*size;

  // NB:some of these curves have a +1 so we don't reach zero!!
  switch(type) {
    case FLAT:
      fill(result.begin(), result.end(), Real(1.0));
      break;
    case TRIANGLE:
      for (int i=0; i<halfSize; i++) {
        result[i] = result[size-1-i] = i+1;
      }
      if ((size&1) == 1) result[halfSize] = size/2; // for odd sizes
      break;
    case INVERSE_TRIANGLE:
      for (int i=0; i<halfSize; i++) {
        result[i] = result[size-1-i] = halfSize-i;
      }
      break;
    case PARABOLA:
      for (int i=0; i<halfSize; i++) {
        result[i] = result[size-1-i] = (halfSize-i)*(halfSize-i);
      }
      break;
    case INVERSE_PARABOLA:
      for (int i=0; i<halfSize; i++) {
        result[i] = sqrHalfSize - (halfSize-i)*(halfSize-i)+1;
        result[size-1-i] =  result[i];
      }
      if ((size&1) == 1) result[halfSize] = halfSize; // for odd sizes
      break;
    case LINEAR:
      for (int i=0; i<size; i++) result[i] = i+1;
      break;
    case QUADRATIC:
      for (int i=0; i<size; i++) result[i] = i*i+1;
      break;
    case INVERSE_QUADRATIC:
      for (int i=0; i<size; i++) result[i] = sqrSize - i*i;
      break;
    case SUPPLIED:
      result = parameter("weightCurve").toVectorReal();
      if (int(result.size()) != size) {
        throw EssentiaException("NoveltyCurve::weightCurve, the size of the supplied weights must be the same as the number of the frequency bands", size);
      }
      break;
    default:
      throw EssentiaException("Weighting Curve type not known");
    }

  //Real max = *max_element(result.begin(), result.end());
  //if (max == 0) throw EssentiaException("Weighting curves has null maximum");
  //for (int i=0; i<size; i++) result[i] /= max;

  return result;
}

/**
 * Compute the novelty curve for a single variable (energy band, spectrum bin, ...).
 * Resulting output vector size is equal to the input vector. The first value is always set
 * to 0 because for it the derivative cannot be defined.
 */
vector<Real> NoveltyCurve::noveltyFunction(const vector<Real>& spec, Real C, int meanSize) {
  int size = spec.size();
  int dsize = size - 1;

  vector<Real> logSpec(size, 0.0), novelty(dsize, 0.0);
  for (int i=0; i<size; i++) logSpec[i] = log10(1 + C*spec[i]);

  // differentiate log spec and keep only positive variations
  for (int i=1; i<size; i++) {
    Real d = logSpec[i] - logSpec[i-1];
    if (d>0) novelty[i-1] = d;
  }

  // subtract local mean
  for (int i=0; i<dsize; i++) {
    int start = i - meanSize/2, end = i + meanSize/2;
    // TODO: decide on which option to choose

    // Nico adjust
    //start = max(start, 0);
    //end = min(end, size-1);

    // Edu adjust
    if (start<0 && end>=dsize) {start=0; end=dsize;}
    else {
      if (start<0) { start=0; end=meanSize;}
      if (end>=dsize) { end=dsize; start=dsize-meanSize;}
    }

    Real m = essentia::mean(novelty, start, end);
    if (novelty[i] < m) novelty[i]=0.0;
    else novelty[i] -= m;
  }
  if (_normalize) {
    Real maxValue = *max_element(novelty.begin(), novelty.end());
    if (maxValue != 0) {
      vector<Real>::iterator it = novelty.begin();
      for (;it!=novelty.end(); ++it) *it /= maxValue;
    }
  }
  Algorithm * mavg = AlgorithmFactory::create("MovingAverage", "size", meanSize);
  vector<Real> novelty_ma;
  mavg->input("signal").set(novelty);
  mavg->output("signal").set(novelty_ma);
  mavg->compute();
  delete mavg;
  return novelty_ma;
}

void NoveltyCurve::configure() {
  string type = parameter("weightCurveType").toString();
  if (type == "flat") _type = FLAT;
  else if (type == "triangle") _type = TRIANGLE;
  else if (type == "inverse_triangle") _type = INVERSE_TRIANGLE;
  else if (type == "parabola") _type = PARABOLA;
  else if (type == "inverse_parabola") _type = INVERSE_PARABOLA;
  else if (type == "linear") _type = LINEAR;
  else if (type == "quadratic") _type = QUADRATIC;
  else if (type == "inverse_quadratic") _type = INVERSE_QUADRATIC;
  else if (type == "supplied") _type = SUPPLIED;
  else if (type == "hybrid") _type = HYBRID;
  _frameRate = parameter("frameRate").toReal();
  _normalize = parameter("normalize").toBool();
}

void NoveltyCurve::compute() {
  const vector<vector<Real> >& frequencyBands = _frequencyBands.get();
  vector<Real>& novelty = _novelty.get();
  if (frequencyBands.empty())
    throw EssentiaException("NoveltyCurve::compute, cannot compute from an empty input matrix");

  int nFrames = frequencyBands.size();
  int nBands = (int)frequencyBands[0].size();
  //vector<Real> weights = weightCurve(nBands);
  novelty.resize(nFrames-1);
  fill(novelty.begin(), novelty.end(), Real(0.0));

  vector<vector<Real> > t_frequencyBands = essentia::transpose(frequencyBands); // [bands x frames]
  vector<vector<Real> > noveltyBands(nBands);

  int meanSize = int(0.1 * _frameRate); // integral number of frames in 2*0.05 second

  // compute novelty for each sub-band
  meanSize += (meanSize % 2); // force even size // TODO: why?
  for (int bandIdx=0; bandIdx<nBands; bandIdx++) {
    noveltyBands[bandIdx] = noveltyFunction(t_frequencyBands[bandIdx], 1000, meanSize);
  }

  //sum novelty on all bands (weighted) to get a single novelty value per frame
  noveltyBands = essentia::transpose(noveltyBands); // back to [frames x bands]

  // TODO: weight curves should be pre-computed in configure() method
  if (_type == HYBRID) {
    // EAylon: By trial-&-error I found that combining weightings (flat, quadratic,
    // linear and inverse quadratic) was giving better results.
    vector<Real> aweights = weightCurve(nBands, FLAT);
    vector<Real> bweights = weightCurve(nBands, QUADRATIC);
    vector<Real> cweights = weightCurve(nBands, LINEAR);
    vector<Real> dweights = weightCurve(nBands, INVERSE_QUADRATIC);

    vector<Real> bnovelty(nFrames-1, 0.0);
    vector<Real> cnovelty(nFrames-1, 0.0);
    vector<Real> dnovelty(nFrames-1, 0.0);

    for (int frameIdx=0; frameIdx<nFrames-1; frameIdx++) { // noveltyBands is a derivative whose size is nframes-1
      for (int bandIdx=0; bandIdx<nBands; bandIdx++) {
        novelty[frameIdx] += aweights[bandIdx] * noveltyBands[frameIdx][bandIdx];
        bnovelty[frameIdx] += bweights[bandIdx] * noveltyBands[frameIdx][bandIdx];
        cnovelty[frameIdx] += cweights[bandIdx] * noveltyBands[frameIdx][bandIdx];
        dnovelty[frameIdx] += dweights[bandIdx] * noveltyBands[frameIdx][bandIdx];
      }
    }
    for (int frameIdx=0; frameIdx<nFrames-1; frameIdx++) {
      // TODO why multiplication instead of sum (or mean)?
      novelty[frameIdx] *= bnovelty[frameIdx];
      novelty[frameIdx] *= cnovelty[frameIdx];
      novelty[frameIdx] *= dnovelty[frameIdx];
    }
  }
  else {
    // TODO weight curve should be pre-computed in configure() method
    vector<Real> weights = weightCurve(nBands, _type);

    for (int frameIdx=0; frameIdx<nFrames-1; frameIdx++) {
      for (int bandIdx=0; bandIdx<nBands; bandIdx++) {
        novelty[frameIdx] += weights[bandIdx] * noveltyBands[frameIdx][bandIdx];
      }
    }
  }

  // smoothing
  Algorithm * mavg = AlgorithmFactory::create("MovingAverage", "size", meanSize);
  vector<Real> novelty_ma;
  mavg->input("signal").set(novelty);
  mavg->output("signal").set(novelty_ma);
  mavg->compute();
  delete mavg;
  novelty.assign(novelty_ma.begin(), novelty_ma.end());
}

void NoveltyCurve::reset() {
  Algorithm::reset();
}

} // namespace standard
} // namespace essentia

#include "poolstorage.h"
#include "algorithmfactory.h"

namespace essentia {
namespace streaming {

const char* NoveltyCurve::name = standard::NoveltyCurve::name;
const char* NoveltyCurve::description = standard::NoveltyCurve::description;

NoveltyCurve::NoveltyCurve() : AlgorithmComposite() {

  _noveltyCurve = standard::AlgorithmFactory::create("NoveltyCurve");
  _poolStorage = new PoolStorage<vector<Real> >(&_pool, "internal.frequencyBands");

  declareInput(_frequencyBands, 1, "frequencyBands", "the frequency bands");
  declareOutput(_novelty, 0, "novelty", "the novelty curve as a single vector");

  _frequencyBands >> _poolStorage->input("data"); // attach input proxy

  // Need to set the buffer type to multiple frames as all the values
  // are output all at once
  _novelty.setBufferType(BufferUsage::forMultipleFrames);
}

NoveltyCurve::~NoveltyCurve() {
  delete _noveltyCurve;
  delete _poolStorage;
}

void NoveltyCurve::reset() {
  AlgorithmComposite::reset();
  _noveltyCurve->reset();
}

AlgorithmStatus NoveltyCurve::process() {
  if (!shouldStop()) return PASS;

  vector<Real> novelty;
  _noveltyCurve->input("frequencyBands").set(_pool.value<vector<vector<Real> > >("internal.frequencyBands"));
  _noveltyCurve->output("novelty").set(novelty);
  _noveltyCurve->compute();

  for (size_t i=0; i<novelty.size(); ++i) {
    _novelty.push(novelty[i]);
  }
  return FINISHED;
}

} // namespace streaming
} // namespace essentia<commit_msg>Fix DOC formatting for NoveltyCurve<commit_after>/*
 * Copyright (C) 2006-2020  Music Technology Group - Universitat Pompeu Fabra
 *
 * This file is part of Essentia
 *
 * Essentia is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation (FSF), 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 Affero GNU General Public License
 * version 3 along with this program.  If not, see http://www.gnu.org/licenses/
 */

#include "algorithmfactory.h"
#include "essentiamath.h"
#include "noveltycurve.h"

using namespace std;

namespace essentia {
namespace standard {

const char* NoveltyCurve::name = "NoveltyCurve";
const char* NoveltyCurve::category = "Rhythm";
const char* NoveltyCurve::description = DOC("This algorithm computes the \"novelty curve\" (Grosche & Müller, 2009) onset detection function. The algorithm expects as an input a frame-wise sequence of frequency-bands energies or spectrum magnitudes as originally proposed in [1] (see FrequencyBands and Spectrum algorithms). Novelty in each band (or frequency bin) is computed as a derivative between log-compressed energy (magnitude) values in consequent frames. The overall novelty value is then computed as a weighted sum that can be configured using 'weightCurve' parameter. The resulting novelty curve can be used for beat tracking and onset detection (see BpmHistogram and Onsets).\n"
"\n"
"Notes:\n"
"\n"
"- Recommended frame/hop size for spectrum computation is 2048/1024 samples (44.1 kHz sampling rate) [2].\n"
"- Log compression is applied with C=1000 as in [1].\n"
"- Frequency bands energies (see FrequencyBands) as well as bin magnitudes for the whole spectrum can be used as an input. The implementation for the original algorithm [2] works with spectrum bin magnitudes for which novelty functions are computed separately and are then summarized into bands.\n"
"- In the case if 'weightCurve' is set to 'hybrid' a complex combination of flat, quadratic, linear and inverse quadratic weight curves is used. It was reported to improve performance of beat tracking in some informal in-house experiments (Note: this information is probably outdated).\n"
"\n"
"References:\n"
"\n"
"1. Grosche, P. & Müller, M. (2009). A mid-level representation for capturing "
"dominant tempo and pulse information in music recordings. "
"International Society for Music Information Retrieval Conference (ISMIR 2009).\n"
"\n"
"2. Tempogram Toolbox (Matlab implementation), "
"http://resources.mpi%2Dinf.mpg.de/MIR/tempogramtoolbox\n\n");

vector<Real> NoveltyCurve::weightCurve(int size, WeightType type) {
  vector<Real> result(size, 0.0);
  int halfSize = size/2;
  int sqrHalfSize = halfSize*halfSize;
  int sqrSize = size*size;

  // NB:some of these curves have a +1 so we don't reach zero!!
  switch(type) {
    case FLAT:
      fill(result.begin(), result.end(), Real(1.0));
      break;
    case TRIANGLE:
      for (int i=0; i<halfSize; i++) {
        result[i] = result[size-1-i] = i+1;
      }
      if ((size&1) == 1) result[halfSize] = size/2; // for odd sizes
      break;
    case INVERSE_TRIANGLE:
      for (int i=0; i<halfSize; i++) {
        result[i] = result[size-1-i] = halfSize-i;
      }
      break;
    case PARABOLA:
      for (int i=0; i<halfSize; i++) {
        result[i] = result[size-1-i] = (halfSize-i)*(halfSize-i);
      }
      break;
    case INVERSE_PARABOLA:
      for (int i=0; i<halfSize; i++) {
        result[i] = sqrHalfSize - (halfSize-i)*(halfSize-i)+1;
        result[size-1-i] =  result[i];
      }
      if ((size&1) == 1) result[halfSize] = halfSize; // for odd sizes
      break;
    case LINEAR:
      for (int i=0; i<size; i++) result[i] = i+1;
      break;
    case QUADRATIC:
      for (int i=0; i<size; i++) result[i] = i*i+1;
      break;
    case INVERSE_QUADRATIC:
      for (int i=0; i<size; i++) result[i] = sqrSize - i*i;
      break;
    case SUPPLIED:
      result = parameter("weightCurve").toVectorReal();
      if (int(result.size()) != size) {
        throw EssentiaException("NoveltyCurve::weightCurve, the size of the supplied weights must be the same as the number of the frequency bands", size);
      }
      break;
    default:
      throw EssentiaException("Weighting Curve type not known");
    }

  //Real max = *max_element(result.begin(), result.end());
  //if (max == 0) throw EssentiaException("Weighting curves has null maximum");
  //for (int i=0; i<size; i++) result[i] /= max;

  return result;
}

/**
 * Compute the novelty curve for a single variable (energy band, spectrum bin, ...).
 * Resulting output vector size is equal to the input vector. The first value is always set
 * to 0 because for it the derivative cannot be defined.
 */
vector<Real> NoveltyCurve::noveltyFunction(const vector<Real>& spec, Real C, int meanSize) {
  int size = spec.size();
  int dsize = size - 1;

  vector<Real> logSpec(size, 0.0), novelty(dsize, 0.0);
  for (int i=0; i<size; i++) logSpec[i] = log10(1 + C*spec[i]);

  // differentiate log spec and keep only positive variations
  for (int i=1; i<size; i++) {
    Real d = logSpec[i] - logSpec[i-1];
    if (d>0) novelty[i-1] = d;
  }

  // subtract local mean
  for (int i=0; i<dsize; i++) {
    int start = i - meanSize/2, end = i + meanSize/2;
    // TODO: decide on which option to choose

    // Nico adjust
    //start = max(start, 0);
    //end = min(end, size-1);

    // Edu adjust
    if (start<0 && end>=dsize) {start=0; end=dsize;}
    else {
      if (start<0) { start=0; end=meanSize;}
      if (end>=dsize) { end=dsize; start=dsize-meanSize;}
    }

    Real m = essentia::mean(novelty, start, end);
    if (novelty[i] < m) novelty[i]=0.0;
    else novelty[i] -= m;
  }
  if (_normalize) {
    Real maxValue = *max_element(novelty.begin(), novelty.end());
    if (maxValue != 0) {
      vector<Real>::iterator it = novelty.begin();
      for (;it!=novelty.end(); ++it) *it /= maxValue;
    }
  }
  Algorithm * mavg = AlgorithmFactory::create("MovingAverage", "size", meanSize);
  vector<Real> novelty_ma;
  mavg->input("signal").set(novelty);
  mavg->output("signal").set(novelty_ma);
  mavg->compute();
  delete mavg;
  return novelty_ma;
}

void NoveltyCurve::configure() {
  string type = parameter("weightCurveType").toString();
  if (type == "flat") _type = FLAT;
  else if (type == "triangle") _type = TRIANGLE;
  else if (type == "inverse_triangle") _type = INVERSE_TRIANGLE;
  else if (type == "parabola") _type = PARABOLA;
  else if (type == "inverse_parabola") _type = INVERSE_PARABOLA;
  else if (type == "linear") _type = LINEAR;
  else if (type == "quadratic") _type = QUADRATIC;
  else if (type == "inverse_quadratic") _type = INVERSE_QUADRATIC;
  else if (type == "supplied") _type = SUPPLIED;
  else if (type == "hybrid") _type = HYBRID;
  _frameRate = parameter("frameRate").toReal();
  _normalize = parameter("normalize").toBool();
}

void NoveltyCurve::compute() {
  const vector<vector<Real> >& frequencyBands = _frequencyBands.get();
  vector<Real>& novelty = _novelty.get();
  if (frequencyBands.empty())
    throw EssentiaException("NoveltyCurve::compute, cannot compute from an empty input matrix");

  int nFrames = frequencyBands.size();
  int nBands = (int)frequencyBands[0].size();
  //vector<Real> weights = weightCurve(nBands);
  novelty.resize(nFrames-1);
  fill(novelty.begin(), novelty.end(), Real(0.0));

  vector<vector<Real> > t_frequencyBands = essentia::transpose(frequencyBands); // [bands x frames]
  vector<vector<Real> > noveltyBands(nBands);

  int meanSize = int(0.1 * _frameRate); // integral number of frames in 2*0.05 second

  // compute novelty for each sub-band
  meanSize += (meanSize % 2); // force even size // TODO: why?
  for (int bandIdx=0; bandIdx<nBands; bandIdx++) {
    noveltyBands[bandIdx] = noveltyFunction(t_frequencyBands[bandIdx], 1000, meanSize);
  }

  //sum novelty on all bands (weighted) to get a single novelty value per frame
  noveltyBands = essentia::transpose(noveltyBands); // back to [frames x bands]

  // TODO: weight curves should be pre-computed in configure() method
  if (_type == HYBRID) {
    // EAylon: By trial-&-error I found that combining weightings (flat, quadratic,
    // linear and inverse quadratic) was giving better results.
    vector<Real> aweights = weightCurve(nBands, FLAT);
    vector<Real> bweights = weightCurve(nBands, QUADRATIC);
    vector<Real> cweights = weightCurve(nBands, LINEAR);
    vector<Real> dweights = weightCurve(nBands, INVERSE_QUADRATIC);

    vector<Real> bnovelty(nFrames-1, 0.0);
    vector<Real> cnovelty(nFrames-1, 0.0);
    vector<Real> dnovelty(nFrames-1, 0.0);

    for (int frameIdx=0; frameIdx<nFrames-1; frameIdx++) { // noveltyBands is a derivative whose size is nframes-1
      for (int bandIdx=0; bandIdx<nBands; bandIdx++) {
        novelty[frameIdx] += aweights[bandIdx] * noveltyBands[frameIdx][bandIdx];
        bnovelty[frameIdx] += bweights[bandIdx] * noveltyBands[frameIdx][bandIdx];
        cnovelty[frameIdx] += cweights[bandIdx] * noveltyBands[frameIdx][bandIdx];
        dnovelty[frameIdx] += dweights[bandIdx] * noveltyBands[frameIdx][bandIdx];
      }
    }
    for (int frameIdx=0; frameIdx<nFrames-1; frameIdx++) {
      // TODO why multiplication instead of sum (or mean)?
      novelty[frameIdx] *= bnovelty[frameIdx];
      novelty[frameIdx] *= cnovelty[frameIdx];
      novelty[frameIdx] *= dnovelty[frameIdx];
    }
  }
  else {
    // TODO weight curve should be pre-computed in configure() method
    vector<Real> weights = weightCurve(nBands, _type);

    for (int frameIdx=0; frameIdx<nFrames-1; frameIdx++) {
      for (int bandIdx=0; bandIdx<nBands; bandIdx++) {
        novelty[frameIdx] += weights[bandIdx] * noveltyBands[frameIdx][bandIdx];
      }
    }
  }

  // smoothing
  Algorithm * mavg = AlgorithmFactory::create("MovingAverage", "size", meanSize);
  vector<Real> novelty_ma;
  mavg->input("signal").set(novelty);
  mavg->output("signal").set(novelty_ma);
  mavg->compute();
  delete mavg;
  novelty.assign(novelty_ma.begin(), novelty_ma.end());
}

void NoveltyCurve::reset() {
  Algorithm::reset();
}

} // namespace standard
} // namespace essentia

#include "poolstorage.h"
#include "algorithmfactory.h"

namespace essentia {
namespace streaming {

const char* NoveltyCurve::name = standard::NoveltyCurve::name;
const char* NoveltyCurve::description = standard::NoveltyCurve::description;

NoveltyCurve::NoveltyCurve() : AlgorithmComposite() {

  _noveltyCurve = standard::AlgorithmFactory::create("NoveltyCurve");
  _poolStorage = new PoolStorage<vector<Real> >(&_pool, "internal.frequencyBands");

  declareInput(_frequencyBands, 1, "frequencyBands", "the frequency bands");
  declareOutput(_novelty, 0, "novelty", "the novelty curve as a single vector");

  _frequencyBands >> _poolStorage->input("data"); // attach input proxy

  // Need to set the buffer type to multiple frames as all the values
  // are output all at once
  _novelty.setBufferType(BufferUsage::forMultipleFrames);
}

NoveltyCurve::~NoveltyCurve() {
  delete _noveltyCurve;
  delete _poolStorage;
}

void NoveltyCurve::reset() {
  AlgorithmComposite::reset();
  _noveltyCurve->reset();
}

AlgorithmStatus NoveltyCurve::process() {
  if (!shouldStop()) return PASS;

  vector<Real> novelty;
  _noveltyCurve->input("frequencyBands").set(_pool.value<vector<vector<Real> > >("internal.frequencyBands"));
  _noveltyCurve->output("novelty").set(novelty);
  _noveltyCurve->compute();

  for (size_t i=0; i<novelty.size(); ++i) {
    _novelty.push(novelty[i]);
  }
  return FINISHED;
}

} // namespace streaming
} // namespace essentia
<|endoftext|>


<commit_before>// This file is part of the AliceVision project.
// Copyright (c) 2016 AliceVision contributors.
// Copyright (c) 2012 openMVG contributors.
// This Source Code Form is subject to the terms of the Mozilla Public License,
// v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

#include "Datasheet.hpp"

#include <string>
#include <algorithm>

#include <boost/algorithm/string/predicate.hpp>

namespace aliceVision {
namespace sensorDB {

bool Datasheet::operator==(const Datasheet& other) const
{
  std::string brandA = _brand;
  std::string brandB = other._brand;

  std::transform(brandA.begin(), brandA.end(), brandA.begin(), ::tolower); //tolower
  std::transform(brandB.begin(), brandB.end(), brandB.begin(), ::tolower); //tolower

  brandA.erase(std::remove_if(brandA.begin(), brandA.end(), ::ispunct), brandA.end()); //remove punctuation
  brandB.erase(std::remove_if(brandB.begin(), brandB.end(), ::ispunct), brandB.end()); //remove punctuation

  if(brandA == brandB)
  {
    std::string modelA = _model;
    std::string modelB = other._model;

    std::transform(modelA.begin(), modelA.end(), modelA.begin(), ::tolower); //tolower
    std::transform(modelB.begin(), modelB.end(), modelB.begin(), ::tolower); //tolower

    modelA.erase(std::remove_if(modelA.begin(), modelA.end(), ::ispunct), modelA.end()); //remove punctuation
    modelB.erase(std::remove_if(modelB.begin(), modelB.end(), ::ispunct), modelB.end()); //remove punctuation

    if((modelA == modelB) ||
       (boost::algorithm::ends_with(modelA, modelB)) ||
       (boost::algorithm::ends_with(modelB, modelA)))
      return true;
  }

  return false;
}

} // namespace sensorDB
} // namespace aliceVision
<commit_msg>[sensorDB] Use boost `to_lower` instead of `transform`<commit_after>// This file is part of the AliceVision project.
// Copyright (c) 2016 AliceVision contributors.
// Copyright (c) 2012 openMVG contributors.
// This Source Code Form is subject to the terms of the Mozilla Public License,
// v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

#include "Datasheet.hpp"

#include <string>
#include <algorithm>

#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>

namespace aliceVision {
namespace sensorDB {

bool Datasheet::operator==(const Datasheet& other) const
{
  std::string brandA = _brand;
  std::string brandB = other._brand;

  boost::algorithm::to_lower(brandA);
  boost::algorithm::to_lower(brandB);

  brandA.erase(std::remove_if(brandA.begin(), brandA.end(), ::ispunct), brandA.end()); //remove punctuation
  brandB.erase(std::remove_if(brandB.begin(), brandB.end(), ::ispunct), brandB.end()); //remove punctuation

  if(brandA == brandB)
  {
    std::string modelA = _model;
    std::string modelB = other._model;

    boost::algorithm::to_lower(modelA);
    boost::algorithm::to_lower(modelB);

    modelA.erase(std::remove_if(modelA.begin(), modelA.end(), ::ispunct), modelA.end()); //remove punctuation
    modelB.erase(std::remove_if(modelB.begin(), modelB.end(), ::ispunct), modelB.end()); //remove punctuation

    if((modelA == modelB) ||
       (boost::algorithm::ends_with(modelA, modelB)) ||
       (boost::algorithm::ends_with(modelB, modelA)))
      return true;
  }

  return false;
}

} // namespace sensorDB
} // namespace aliceVision
<|endoftext|>


<commit_before>#include "espresso_core.h"

int main(int argc, char** argv) {

  handle_cmd_args(argc, argv);

  load_persistent_metadata(argv[METADATA_FILE]);

  // TODO: open filesystem data for when you need it
  // filepath is argv[FILESYSTEM]

  printf("persistent metadata on %s has been loaded, ", argv[NAME_ID]);
  printf("opening espresso node to network connections\n");

  // register espresso callback functions in network layer
  register_read_data_callback(read_data);
  register_write_data_callback(write_data);
  register_delete_data_callback(delete_data);

  // the svc_main_loop function lives in network core and
  // calls registered espresso functions as it receives the rpcs
  svc_main_loop(argc, argv);

  return 0;
}

ssize_t read_data (int fd, int file_id, int stripe_id, int chunk_num, int offset, void *buf, int count) {

  printf("read_data called, espresso_core\n");
  return read_chunk(fd, file_id, stripe_id, chunk_num, offset, buf, count);
}

ssize_t write_data (int fd, int file_id, int stripe_id, int chunk_num, int offset, void *buf, int count) {

  return write_chunk(fd, file_id, stripe_id, chunk_num, offset, buf, count);
}

int delete_data (int fd, int file_id, int stripe_id, int chunk_num) {

   printf("delete_data called, espresso_core\n");
  return delete_chunk(fd, file_id, stripe_id, chunk_num);
}

void load_persistent_metadata(char* metadata_path) {

   printf("loading metadata from: %s\n", metadata_path);

   // TODO: load metadata
}

void handle_cmd_args(int argc, char** argv) {

  if (argc == NUM_EXPECTED_ARGS) {

  }
  else {
     fprintf(stderr, "%s exiting: invalid command line arguments provided", argv[0]);
     fprintf(stderr, "\nUsage: %s <id> <persistent metadata path> <data path>\n", argv[0]);
     exit(-1);
  }
}
<commit_msg>changed #defined names in espresso_core<commit_after>#include "espresso_core.h"

int main(int argc, char** argv) {

  handle_cmd_args(argc, argv);

  load_persistent_metadata(argv[METADATA]);

  // TODO: open filesystem data for when you need it
  // filepath is argv[FILESYSTEM]

  printf("persistent metadata on %s has been loaded, ", argv[NAME]);
  printf("opening espresso node to network connections\n");

  // register espresso callback functions in network layer
  register_read_data_callback(read_data);
  register_write_data_callback(write_data);
  register_delete_data_callback(delete_data);

  // the svc_main_loop function lives in network core and
  // calls registered espresso functions as it receives the rpcs
  svc_main_loop(argc, argv);

  return 0;
}

ssize_t read_data (int fd, int file_id, int stripe_id, int chunk_num, int offset, void *buf, int count) {

  printf("read_data called, espresso_core\n");
  return read_chunk(fd, file_id, stripe_id, chunk_num, offset, buf, count);
}

ssize_t write_data (int fd, int file_id, int stripe_id, int chunk_num, int offset, void *buf, int count) {

  return write_chunk(fd, file_id, stripe_id, chunk_num, offset, buf, count);
}

int delete_data (int fd, int file_id, int stripe_id, int chunk_num) {

   printf("delete_data called, espresso_core\n");
  return delete_chunk(fd, file_id, stripe_id, chunk_num);
}

void load_persistent_metadata(char* metadata_path) {

   printf("loading metadata from: %s\n", metadata_path);

   // TODO: load metadata
}

void handle_cmd_args(int argc, char** argv) {

  if (argc == NUM_EXPECTED_ARGS) {

  }
  else {
     fprintf(stderr, "%s exiting: invalid command line arguments provided", argv[0]);
     fprintf(stderr, "\nUsage: %s <id> <persistent metadata path> <data path>\n", argv[0]);
     exit(-1);
  }
}
<|endoftext|>


<commit_before>/*  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
 *
 *  libtest
 *
 *  Copyright (C) 2012 Data Differential, http://datadifferential.com/
 *
 *  This library 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 3 of the License, or (at your option) any later version.
 *
 *  This library 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 library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#pragma once

#include <stdexcept>

#ifndef __PRETTY_FUNCTION__
#define __PRETTY_FUNCTION__ __func__
#endif

#define LIBYATL_DEFAULT_PARAM __FILE__, __LINE__, __PRETTY_FUNCTION__

namespace libtest {

class fatal : std::runtime_error
{
public:
  fatal(const char *file, int line, const char *func, const char *format, ...);

  const char* what() const throw()
  {
    return _error_message;
  }

  // The following are just for unittesting the exception class
  static bool is_disabled();
  static bool disable();
  static bool enable();
  static uint32_t disabled_counter();
  static void increment_disabled_counter();

private:
  char _error_message[BUFSIZ];
};

} // namespace libtest

#define fatal_message(__mesg) libtest::fatal(LIBYATL_DEFAULT_PARAM, __mesg)
<commit_msg>Update of libtest.<commit_after>/*  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
 *
 *  libtest
 *
 *  Copyright (C) 2012 Data Differential, http://datadifferential.com/
 *
 *  This library 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 3 of the License, or (at your option) any later version.
 *
 *  This library 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 library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#pragma once

#include <stdexcept>

#ifndef __PRETTY_FUNCTION__
#define __PRETTY_FUNCTION__ __func__
#endif

#define LIBYATL_DEFAULT_PARAM __FILE__, __LINE__, __PRETTY_FUNCTION__

namespace libtest {

class fatal : std::runtime_error
{
public:
  fatal(const char *file, int line, const char *func, const char *format, ...);

  const char* what() const throw()
  {
    return _error_message;
  }

  // The following are just for unittesting the exception class
  static bool is_disabled();
  static bool disable();
  static bool enable();
  static uint32_t disabled_counter();
  static void increment_disabled_counter();

private:
  char _error_message[BUFSIZ];
};

} // namespace libtest

#define fatal_message(__mesg) libtest::fatal(LIBYATL_DEFAULT_PARAM, __mesg)
#define fatal_assert(__assert) if((__assert)) {} else { libtest::fatal(LIBYATL_DEFAULT_PARAM, #__assert); }
<|endoftext|>


<commit_before>/** \copyright
 * Copyright (c) 2013, Balazs Racz
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are  permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \file main.cxx
 *
 * Main file for the io board application on the Tiva Launchpad board.
 *
 * @author Balazs Racz
 * @date 5 Jun 2015
 */

#define LOGLEVEL INFO

#include <algorithm>

#include "os/os.h"
#include "nmranet_config.h"

#include "openlcb/SimpleStack.hxx"
#include "openlcb/ConfiguredConsumer.hxx"
#include "openlcb/MultiConfiguredConsumer.hxx"
#include "openlcb/ConfiguredProducer.hxx"
#include "utils/gc_format.h"

#include "freertos_drivers/ti/TivaGPIO.hxx"
#include "freertos_drivers/ti/TivaCpuLoad.hxx"
#include "freertos_drivers/common/BlinkerGPIO.hxx"
#include "freertos_drivers/common/PersistentGPIO.hxx"
#include "freertos_drivers/common/BenchmarkCan.hxx"
#include "config.hxx"
#include "hardware.hxx"

// Writes all log data to stderr.
#include "utils/TcpLogging.hxx"

BenchmarkCan benchmark_can("/dev/fakecan0");

// These preprocessor symbols are used to select which physical connections
// will be enabled in the main(). See @ref appl_main below.
//#define SNIFF_ON_SERIAL
//#define SNIFF_ON_USB
//#define HAVE_PHYSICAL_CAN_PORT

// Changes the default behavior by adding a newline after each gridconnect
// packet. Makes it easier for debugging the raw device.
OVERRIDE_CONST(gc_generate_newlines, 1);
// Specifies how much RAM (in bytes) we allocate to the stack of the main
// thread. Useful tuning parameter in case the application runs out of memory.
OVERRIDE_CONST(main_thread_stack_size, 2500);

// Specifies the 48-bit OpenLCB node identifier. This must be unique for every
// hardware manufactured, so in production this should be replaced by some
// easily incrementable method.
extern const openlcb::NodeID NODE_ID = 0x050101011804ULL;

// Sets up a comprehensive OpenLCB stack for a single virtual node. This stack
// contains everything needed for a usual peripheral node -- all
// CAN-bus-specific components, a virtual node, PIP, SNIP, Memory configuration
// protocol, ACDI, CDI, a bunch of memory spaces, etc.
openlcb::SimpleCanStack stack(NODE_ID);

SerialLoggingServer log_server(stack.service(), "/dev/ser0");

TivaCpuLoad<TivaCpuLoadDefHw> load_monitor;

#include "freertos_drivers/common/cpu_profile.hxx"

DEFINE_CPU_PROFILE_INTERRUPT_HANDLER(timer4a_interrupt_handler);

class BenchmarkDriver : public StateFlowBase
{
public:
    BenchmarkDriver()
        : StateFlowBase(stack.service())
    {
        start_flow(STATE(log_and_wait));
    }

private:
    Action log_and_wait() {
        if (!SW1_Pin::get()) {
            return call_immediately(STATE(do_benchmark));
        }
        return sleep_and_call(&timer_, MSEC_TO_NSEC(100), STATE(log_and_wait));
    }

    Action do_benchmark() {
        struct can_frame frame;
        LOG(INFO, "Starting benchmark.");
        HHASSERT(0 == gc_format_parse("X195B4123N0501010118FF0123", &frame));
        startTime_ = OSTime::get_monotonic();
        benchmark_can.start_benchmark(&frame, COUNT);
        enable_profiling = 1;
        return sleep_and_call(
            &timer_, MSEC_TO_NSEC(100), STATE(check_benchmark_state));
    }

    Action check_benchmark_state() {
        unsigned count = 1;
        auto end_time = benchmark_can.get_timestamp(&count);
        if (!count) {
            enable_profiling = 0;
            float spd = COUNT;
            float time = (end_time - startTime_);
            time /= 1e9;
            spd /= time;
            LOG(INFO, "Benchmark done. Time %d msec, speed %d pkt/sec",
                static_cast<int>((end_time - startTime_ + 500000) / 1000000),
                static_cast<int>(spd));
            return call_immediately(STATE(log_and_wait));
        } else {
            LOG(INFO, "benchmark: %u pkt left", count);
        }
        return sleep_and_call(
            &timer_, MSEC_TO_NSEC(100), STATE(check_benchmark_state));

    }

    static constexpr unsigned COUNT = 10000;

    long long startTime_;
    StateFlowTimer timer_{this};
} benchmark_driver;

class CpuLoadLog : public StateFlowBase
{
public:
    CpuLoadLog()
        : StateFlowBase(stack.service())
    {
        start_flow(STATE(log_and_wait));
    }

private:
    Action log_and_wait()
    {
        auto *l = CpuLoad::instance();
        LOG(INFO, "Load: avg %3d max streak %d max of 16 %d", l->get_load(),
            l->get_max_consecutive(), l->get_peak_over_16_counts());
        l->clear_max_consecutive();
        l->clear_peak_over_16_counts();
        vector<pair<unsigned, string*> > per_task_ticks;
        unsigned c = l->get_utilization_delta(&per_task_ticks);
        std::sort(per_task_ticks.begin(), per_task_ticks.end(), std::greater<>());
        string details;
        for (volatile auto it : per_task_ticks) {
            int perc = it.first * 1000 / c;
            details += StringPrintf(" | %d.%d:", perc / 10, perc % 10);
            details += *it.second;
        }
        log_output((char*)details.data(), details.size());
        auto k = l->new_key();
        if (k > 300) {
            char* name = pcTaskGetName((TaskHandle_t)k);
            l->set_key_description(k, name);
        } else {
            l->set_key_description(k, StringPrintf("irq-%u", k));
        }
        return sleep_and_call(&timer_, MSEC_TO_NSEC(2000), STATE(log_and_wait));
    }

    StateFlowTimer timer_{this};
} load_logger;

// ConfigDef comes from config.hxx and is specific to the particular device and
// target. It defines the layout of the configuration memory space and is also
// used to generate the cdi.xml file. Here we instantiate the configuration
// layout. The argument of offset zero is ignored and will be removed later.
openlcb::ConfigDef cfg(0);
// Defines weak constants used by the stack to tell it which device contains
// the volatile configuration information. This device name appears in
// HwInit.cxx that creates the device drivers.
extern const char *const openlcb::CONFIG_FILENAME = "/dev/eeprom";
// The size of the memory space to export over the above device.
extern const size_t openlcb::CONFIG_FILE_SIZE =
    cfg.seg().size() + cfg.seg().offset();
static_assert(openlcb::CONFIG_FILE_SIZE <= 300, "Need to adjust eeprom size");
// The SNIP user-changeable information in also stored in the above eeprom
// device. In general this could come from different eeprom segments, but it is
// simpler to keep them together.
extern const char *const openlcb::SNIP_DYNAMIC_FILENAME =
    openlcb::CONFIG_FILENAME;

// Defines the GPIO ports used for the producers and the consumers.
// Defines the GPIO ports used for the producers and the consumers.

// The first LED is driven by the blinker device from BlinkerGPIO.hxx. WE just
// create an alias for symmetry.
typedef BLINKER_Pin XLED_B1_Pin;

// Instantiates the actual producer and consumer objects for the given GPIO
// pins from above. The ConfiguredConsumer class takes care of most of the
// complicated setup and operation requirements. We need to give it the virtual
// node pointer, the configuration configuration from the CDI definition, and
// the hardware pin definition. The virtual node pointer comes from the stack
// object. The configuration structure comes from the CDI definition object,
// segment 'seg', in which there is a repeated group 'consumers', and we assign
// the individual entries to the individual consumers. Each consumer gets its
// own GPIO pin.
openlcb::ConfiguredConsumer consumer_1(
    stack.node(), cfg.seg().consumers().entry<0>(), LED_B1_Pin());
openlcb::ConfiguredConsumer consumer_2(
    stack.node(), cfg.seg().consumers().entry<1>(), LED_B2_Pin());
openlcb::ConfiguredConsumer consumer_3(
    stack.node(), cfg.seg().consumers().entry<2>(), LED_B3_Pin());
openlcb::ConfiguredConsumer consumer_4(
    stack.node(), cfg.seg().consumers().entry<3>(), LED_B4_Pin());

// Similar syntax for the producers.
openlcb::ConfiguredProducer producer_sw1(
    stack.node(), cfg.seg().producers().entry<0>(), SW1_Pin());
openlcb::ConfiguredProducer producer_sw2(
    stack.node(), cfg.seg().producers().entry<1>(), SW2_Pin());

// The producers need to be polled repeatedly for changes and to execute the
// debouncing algorithm. This class instantiates a refreshloop and adds the two
// producers to it.
openlcb::RefreshLoop loop(
    stack.node(), {producer_sw1.polling(), producer_sw2.polling()});

/** Entry point to application.
 * @param argc number of command line arguments
 * @param argv array of command line arguments
 * @return 0, should never return
 */
int appl_main(int argc, char *argv[])
{
    stack.check_version_and_factory_reset(
        cfg.seg().internal_config(), openlcb::CANONICAL_VERSION, false);

    stack.add_can_port_select("/dev/fakecan0");

    // This command donates the main thread to the operation of the
    // stack. Alternatively the stack could be started in a separate stack and
    // then application-specific business logic could be executed ion a busy
    // loop in the main thread.
    stack.loop_executor();
    return 0;
}
<commit_msg>Fixes compilation for the 129 performance benchmark.<commit_after>/** \copyright
 * Copyright (c) 2013, Balazs Racz
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are  permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \file main.cxx
 *
 * Main file for the io board application on the Tiva Launchpad board.
 *
 * @author Balazs Racz
 * @date 5 Jun 2015
 */

#define LOGLEVEL INFO

#include <algorithm>

#include "os/os.h"
#include "nmranet_config.h"

#include "openlcb/SimpleStack.hxx"
#include "openlcb/ConfiguredConsumer.hxx"
#include "openlcb/MultiConfiguredConsumer.hxx"
#include "openlcb/ConfiguredProducer.hxx"
#include "utils/gc_format.h"

#include "freertos_drivers/ti/TivaGPIO.hxx"
#include "freertos_drivers/ti/TivaCpuLoad.hxx"
#include "freertos_drivers/common/BlinkerGPIO.hxx"
#include "freertos_drivers/common/PersistentGPIO.hxx"
#include "freertos_drivers/common/BenchmarkCan.hxx"
#include "config.hxx"
#include "hardware.hxx"

// Writes all log data to stderr.
#include "utils/TcpLogging.hxx"

BenchmarkCan benchmark_can("/dev/fakecan0");

// These preprocessor symbols are used to select which physical connections
// will be enabled in the main(). See @ref appl_main below.
//#define SNIFF_ON_SERIAL
//#define SNIFF_ON_USB
//#define HAVE_PHYSICAL_CAN_PORT

// Changes the default behavior by adding a newline after each gridconnect
// packet. Makes it easier for debugging the raw device.
OVERRIDE_CONST(gc_generate_newlines, 1);
// Specifies how much RAM (in bytes) we allocate to the stack of the main
// thread. Useful tuning parameter in case the application runs out of memory.
OVERRIDE_CONST(main_thread_stack_size, 2500);

// Specifies the 48-bit OpenLCB node identifier. This must be unique for every
// hardware manufactured, so in production this should be replaced by some
// easily incrementable method.
extern const openlcb::NodeID NODE_ID = 0x050101011804ULL;

// Sets up a comprehensive OpenLCB stack for a single virtual node. This stack
// contains everything needed for a usual peripheral node -- all
// CAN-bus-specific components, a virtual node, PIP, SNIP, Memory configuration
// protocol, ACDI, CDI, a bunch of memory spaces, etc.
openlcb::SimpleCanStack stack(NODE_ID);

SerialLoggingServer log_server(stack.service(), "/dev/ser0");

TivaCpuLoad<TivaCpuLoadDefHw> load_monitor;

#include "freertos_drivers/common/cpu_profile.hxx"

DEFINE_CPU_PROFILE_INTERRUPT_HANDLER(timer4a_interrupt_handler,
    MAP_TimerIntClear(TIMER4_BASE, TIMER_TIMA_TIMEOUT));

class BenchmarkDriver : public StateFlowBase
{
public:
    BenchmarkDriver()
        : StateFlowBase(stack.service())
    {
        start_flow(STATE(log_and_wait));
    }

private:
    Action log_and_wait() {
        if (!SW1_Pin::get()) {
            return call_immediately(STATE(do_benchmark));
        }
        return sleep_and_call(&timer_, MSEC_TO_NSEC(100), STATE(log_and_wait));
    }

    Action do_benchmark() {
        struct can_frame frame;
        LOG(INFO, "Starting benchmark.");
        auto ret = gc_format_parse("X195B4123N0501010118FF0123", &frame);
        HASSERT(0 == ret);
        startTime_ = OSTime::get_monotonic();
        benchmark_can.start_benchmark(&frame, COUNT);
        enable_profiling = 1;
        return sleep_and_call(
            &timer_, MSEC_TO_NSEC(100), STATE(check_benchmark_state));
    }

    Action check_benchmark_state() {
        unsigned count = 1;
        auto end_time = benchmark_can.get_timestamp(&count);
        if (!count) {
            enable_profiling = 0;
            float spd = COUNT;
            float time = (end_time - startTime_);
            time /= 1e9;
            spd /= time;
            LOG(INFO, "Benchmark done. Time %d msec, speed %d pkt/sec",
                static_cast<int>((end_time - startTime_ + 500000) / 1000000),
                static_cast<int>(spd));
            return call_immediately(STATE(log_and_wait));
        } else {
            LOG(INFO, "benchmark: %u pkt left", count);
        }
        return sleep_and_call(
            &timer_, MSEC_TO_NSEC(100), STATE(check_benchmark_state));

    }

    static constexpr unsigned COUNT = 10000;

    long long startTime_;
    StateFlowTimer timer_{this};
} benchmark_driver;

CpuLoadLog load_logger(stack.service());

// ConfigDef comes from config.hxx and is specific to the particular device and
// target. It defines the layout of the configuration memory space and is also
// used to generate the cdi.xml file. Here we instantiate the configuration
// layout. The argument of offset zero is ignored and will be removed later.
openlcb::ConfigDef cfg(0);
// Defines weak constants used by the stack to tell it which device contains
// the volatile configuration information. This device name appears in
// HwInit.cxx that creates the device drivers.
extern const char *const openlcb::CONFIG_FILENAME = "/dev/eeprom";
// The size of the memory space to export over the above device.
extern const size_t openlcb::CONFIG_FILE_SIZE =
    cfg.seg().size() + cfg.seg().offset();
static_assert(openlcb::CONFIG_FILE_SIZE <= 300, "Need to adjust eeprom size");
// The SNIP user-changeable information in also stored in the above eeprom
// device. In general this could come from different eeprom segments, but it is
// simpler to keep them together.
extern const char *const openlcb::SNIP_DYNAMIC_FILENAME =
    openlcb::CONFIG_FILENAME;

// Defines the GPIO ports used for the producers and the consumers.
// Defines the GPIO ports used for the producers and the consumers.

// The first LED is driven by the blinker device from BlinkerGPIO.hxx. WE just
// create an alias for symmetry.
typedef BLINKER_Pin XLED_B1_Pin;

// Instantiates the actual producer and consumer objects for the given GPIO
// pins from above. The ConfiguredConsumer class takes care of most of the
// complicated setup and operation requirements. We need to give it the virtual
// node pointer, the configuration configuration from the CDI definition, and
// the hardware pin definition. The virtual node pointer comes from the stack
// object. The configuration structure comes from the CDI definition object,
// segment 'seg', in which there is a repeated group 'consumers', and we assign
// the individual entries to the individual consumers. Each consumer gets its
// own GPIO pin.
openlcb::ConfiguredConsumer consumer_1(
    stack.node(), cfg.seg().consumers().entry<0>(), LED_B1_Pin());
openlcb::ConfiguredConsumer consumer_2(
    stack.node(), cfg.seg().consumers().entry<1>(), LED_B2_Pin());
openlcb::ConfiguredConsumer consumer_3(
    stack.node(), cfg.seg().consumers().entry<2>(), LED_B3_Pin());
openlcb::ConfiguredConsumer consumer_4(
    stack.node(), cfg.seg().consumers().entry<3>(), LED_B4_Pin());

// Similar syntax for the producers.
openlcb::ConfiguredProducer producer_sw1(
    stack.node(), cfg.seg().producers().entry<0>(), SW1_Pin());
openlcb::ConfiguredProducer producer_sw2(
    stack.node(), cfg.seg().producers().entry<1>(), SW2_Pin());

// The producers need to be polled repeatedly for changes and to execute the
// debouncing algorithm. This class instantiates a refreshloop and adds the two
// producers to it.
openlcb::RefreshLoop loop(
    stack.node(), {producer_sw1.polling(), producer_sw2.polling()});

/** Entry point to application.
 * @param argc number of command line arguments
 * @param argv array of command line arguments
 * @return 0, should never return
 */
int appl_main(int argc, char *argv[])
{
    stack.check_version_and_factory_reset(
        cfg.seg().internal_config(), openlcb::CANONICAL_VERSION, false);

    stack.add_can_port_select("/dev/fakecan0");

    // This command donates the main thread to the operation of the
    // stack. Alternatively the stack could be started in a separate stack and
    // then application-specific business logic could be executed ion a busy
    // loop in the main thread.
    stack.loop_executor();
    return 0;
}
<|endoftext|>