multithreaded app
This commit is contained in:
315
RpiLedBars/res/src/artnet/ArtnetnodeWifi.cpp
Normal file
315
RpiLedBars/res/src/artnet/ArtnetnodeWifi.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
|
||||
Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015
|
||||
|
||||
Copyright (c) 2016-2020 Stephan Ruloff
|
||||
https://github.com/rstephan/ArtnetnodeWifi
|
||||
|
||||
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, under version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#include <ArtnetnodeWifi.h>
|
||||
|
||||
|
||||
const char ArtnetnodeWifi::artnetId[] = ARTNET_ID;
|
||||
|
||||
ArtnetnodeWifi::ArtnetnodeWifi()
|
||||
{
|
||||
// Initalise DMXOutput array
|
||||
for (int i = 0; i < DMX_MAX_OUTPUTS; i++) {
|
||||
DMXOutputs[i][0] = 0xFF;
|
||||
DMXOutputs[i][1] = 0xFF;
|
||||
DMXOutputs[i][2] = 0;
|
||||
}
|
||||
|
||||
// Start DMX tick clock
|
||||
msSinceDMXSend = 0;
|
||||
|
||||
// Init DMX buffers
|
||||
for (int i = 0; i < DMX_MAX_OUTPUTS; i++) {
|
||||
memset(DMXBuffer[i], 0, sizeof(DMXBuffer[i]));
|
||||
}
|
||||
|
||||
sequence = 1;
|
||||
physical = 0;
|
||||
outgoingUniverse = 0;
|
||||
dmxDataLength = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@retval 0 Ok
|
||||
*/
|
||||
uint8_t ArtnetnodeWifi::begin(String hostname)
|
||||
{
|
||||
byte mac[6];
|
||||
|
||||
Udp.begin(ARTNET_PORT);
|
||||
localIP = WiFi.localIP();
|
||||
localMask = WiFi.subnetMask();
|
||||
localBroadcast = IPAddress((uint32_t)localIP | ~(uint32_t)localMask);
|
||||
|
||||
WiFi.macAddress(mac);
|
||||
PollReplyPacket.setMac(mac);
|
||||
PollReplyPacket.setIP(localIP);
|
||||
PollReplyPacket.canDHCP(true);
|
||||
PollReplyPacket.isDHCP(true);
|
||||
|
||||
host = hostname;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::setShortName(const char name[])
|
||||
{
|
||||
PollReplyPacket.setShortName(name);
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::setLongName(const char name[])
|
||||
{
|
||||
PollReplyPacket.setLongName(name);
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::setName(const char name[])
|
||||
{
|
||||
PollReplyPacket.setShortName(name);
|
||||
PollReplyPacket.setLongName(name);
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::setNumPorts(uint8_t num)
|
||||
{
|
||||
PollReplyPacket.setNumPorts(num);
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::setStartingUniverse(uint16_t startingUniverse)
|
||||
{
|
||||
PollReplyPacket.setStartingUniverse(startingUniverse);
|
||||
}
|
||||
|
||||
uint16_t ArtnetnodeWifi::read()
|
||||
{
|
||||
uint8_t startcode;
|
||||
|
||||
packetSize = Udp.parsePacket();
|
||||
|
||||
if (packetSize <= ARTNET_MAX_BUFFER && packetSize > 0) {
|
||||
Udp.read(artnetPacket, ARTNET_MAX_BUFFER);
|
||||
|
||||
// Check that packetID is "Art-Net" else ignore
|
||||
if (memcmp(artnetPacket, artnetId, sizeof(artnetId)) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
opcode = artnetPacket[8] | artnetPacket[9] << 8;
|
||||
|
||||
switch (opcode) {
|
||||
case OpDmx:
|
||||
return handleDMX(0);
|
||||
case OpPoll:
|
||||
return handlePollRequest();
|
||||
case OpNzs:
|
||||
startcode = artnetPacket[13];
|
||||
if (startcode != 0 && startcode != DMX_RDM_STARTCODE) {
|
||||
return handleDMX(startcode);
|
||||
}
|
||||
}
|
||||
|
||||
return opcode;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t ArtnetnodeWifi::makePacket(void)
|
||||
{
|
||||
uint16_t len;
|
||||
uint16_t version;
|
||||
|
||||
memcpy(artnetPacket, artnetId, sizeof(artnetId));
|
||||
opcode = OpDmx;
|
||||
artnetPacket[8] = opcode;
|
||||
artnetPacket[9] = opcode >> 8;
|
||||
version = 14;
|
||||
artnetPacket[10] = version >> 8;
|
||||
artnetPacket[11] = version;
|
||||
artnetPacket[12] = sequence;
|
||||
sequence++;
|
||||
if (!sequence) {
|
||||
sequence = 1;
|
||||
}
|
||||
artnetPacket[13] = physical;
|
||||
artnetPacket[14] = outgoingUniverse;
|
||||
artnetPacket[15] = outgoingUniverse >> 8;
|
||||
len = dmxDataLength + (dmxDataLength % 2); // make an even number
|
||||
artnetPacket[16] = len >> 8;
|
||||
artnetPacket[17] = len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int ArtnetnodeWifi::write(void)
|
||||
{
|
||||
uint16_t len;
|
||||
|
||||
len = makePacket();
|
||||
Udp.beginPacket(host.c_str(), ARTNET_PORT);
|
||||
Udp.write(artnetPacket, ARTNET_DMX_START_LOC + len);
|
||||
|
||||
return Udp.endPacket();
|
||||
}
|
||||
|
||||
int ArtnetnodeWifi::write(IPAddress ip)
|
||||
{
|
||||
uint16_t len;
|
||||
|
||||
len = makePacket();
|
||||
Udp.beginPacket(ip, ARTNET_PORT);
|
||||
Udp.write(artnetPacket, ARTNET_DMX_START_LOC + len);
|
||||
|
||||
return Udp.endPacket();
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::setByte(uint16_t pos, uint8_t value)
|
||||
{
|
||||
if (pos > 512) {
|
||||
return;
|
||||
}
|
||||
artnetPacket[ARTNET_DMX_START_LOC + pos] = value;
|
||||
}
|
||||
|
||||
|
||||
bool ArtnetnodeWifi::isBroadcast()
|
||||
{
|
||||
if (Udp.remoteIP() == localBroadcast){
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t ArtnetnodeWifi::handleDMX(uint8_t nzs)
|
||||
{
|
||||
if (isBroadcast()) {
|
||||
return 0;
|
||||
} else {
|
||||
// Get universe
|
||||
uint16_t universe = artnetPacket[14] | artnetPacket[15] << 8;
|
||||
|
||||
// Get DMX frame length
|
||||
uint16_t dmxDataLength = artnetPacket[17] | artnetPacket[16] << 8;
|
||||
|
||||
// Sequence
|
||||
uint8_t sequence = artnetPacket[12];
|
||||
|
||||
if (artDmxCallback) {
|
||||
(*artDmxCallback)(universe, dmxDataLength, sequence, artnetPacket + ARTNET_DMX_START_LOC);
|
||||
}
|
||||
|
||||
for(int a = 0; a < DMX_MAX_OUTPUTS; a++){
|
||||
if(DMXOutputs[a][1] == universe){
|
||||
for (int i = 0 ; i < DMX_MAX_BUFFER ; i++){
|
||||
if(i < dmxDataLength){
|
||||
DMXBuffer[a][i] = artnetPacket[i+ARTNET_DMX_START_LOC];
|
||||
}
|
||||
else{
|
||||
DMXBuffer[a][i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nzs) {
|
||||
return OpNzs;
|
||||
} else {
|
||||
return OpDmx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t ArtnetnodeWifi::handlePollRequest()
|
||||
{
|
||||
if (1 || isBroadcast()) {
|
||||
Udp.beginPacket(localBroadcast, ARTNET_PORT);
|
||||
Udp.write(PollReplyPacket.printPacket(), sizeof(PollReplyPacket.packet));
|
||||
Udp.endPacket();
|
||||
|
||||
return OpPoll;
|
||||
} else{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::enableDMX()
|
||||
{
|
||||
DMXOutputStatus = true;
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::disableDMX()
|
||||
{
|
||||
DMXOutputStatus = false;
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::enableDMXOutput(uint8_t outputID)
|
||||
{
|
||||
DMXOutputs[outputID][2] = 1;
|
||||
|
||||
int numEnabled = 0;
|
||||
for(int i = 0; i < DMX_MAX_OUTPUTS; i++){
|
||||
if(DMXOutputs[i][2]==1){
|
||||
if(numEnabled<4){
|
||||
numEnabled++;
|
||||
}
|
||||
}
|
||||
}
|
||||
PollReplyPacket.setNumPorts(numEnabled);
|
||||
PollReplyPacket.setOutputEnabled(outputID);
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::disableDMXOutput(uint8_t outputID)
|
||||
{
|
||||
DMXOutputs[outputID][2] = 0;
|
||||
|
||||
int numEnabled = 0;
|
||||
for(int i = 0; i < DMX_MAX_OUTPUTS; i++){
|
||||
if(DMXOutputs[i][2]==1){
|
||||
if(numEnabled<4){
|
||||
numEnabled++;
|
||||
}
|
||||
}
|
||||
}
|
||||
PollReplyPacket.setNumPorts(numEnabled);
|
||||
PollReplyPacket.setOutputDisabled(outputID);
|
||||
}
|
||||
|
||||
uint8_t ArtnetnodeWifi::setDMXOutput(uint8_t outputID, uint8_t uartNum, uint16_t attachedUniverse)
|
||||
{
|
||||
// Validate input
|
||||
if(outputID < DMX_MAX_OUTPUTS && uartNum != 0xFF && attachedUniverse != 0xFF){
|
||||
DMXOutputs[outputID][0] = uartNum;
|
||||
DMXOutputs[outputID][1] = attachedUniverse;
|
||||
DMXOutputs[outputID][2] = 0;
|
||||
PollReplyPacket.setSwOut(outputID, attachedUniverse);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ArtnetnodeWifi::tickDMX(uint32_t time)
|
||||
{
|
||||
msSinceDMXSend += time;
|
||||
if(msSinceDMXSend > DMX_MS_BETWEEN_TICKS){
|
||||
sendDMX();
|
||||
msSinceDMXSend = 0;
|
||||
}
|
||||
}
|
151
RpiLedBars/res/src/artnet/ArtnetnodeWifi.h
Normal file
151
RpiLedBars/res/src/artnet/ArtnetnodeWifi.h
Normal file
@@ -0,0 +1,151 @@
|
||||
#ifndef ARTNETNODEWIFI_H
|
||||
#define ARTNETNODEWIFI_H
|
||||
/*
|
||||
|
||||
Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015
|
||||
|
||||
Copyright (c) 2016 Stephan Ruloff
|
||||
https://github.com/rstephan
|
||||
|
||||
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, under version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#if defined(ARDUINO_SAMD_MKR1000)
|
||||
#include <WiFi101.h>
|
||||
#else
|
||||
#include <WiFiNINA.h>
|
||||
#endif
|
||||
#else
|
||||
#error "Architecture not supported!"
|
||||
#endif
|
||||
#include <WiFiUdp.h>
|
||||
#include "OpCodes.h"
|
||||
#include "NodeReportCodes.h"
|
||||
#include "StyleCodes.h"
|
||||
#include "PriorityCodes.h"
|
||||
#include "ProtocolSettings.h"
|
||||
#include "PollReply.h"
|
||||
|
||||
|
||||
class ArtnetnodeWifi
|
||||
{
|
||||
public:
|
||||
ArtnetnodeWifi();
|
||||
|
||||
uint8_t begin(String hostname = "");
|
||||
uint16_t read();
|
||||
|
||||
// Node identity
|
||||
void setShortName(const char name[]);
|
||||
void setLongName(const char name[]);
|
||||
void setName(const char name[]);
|
||||
void setNumPorts(uint8_t num);
|
||||
|
||||
void setStartingUniverse(uint16_t startingUniverse);
|
||||
|
||||
// Transmit
|
||||
int write(void);
|
||||
int write(IPAddress ip);
|
||||
void setByte(uint16_t pos, uint8_t value);
|
||||
|
||||
inline void setUniverse(uint16_t universe)
|
||||
{
|
||||
outgoingUniverse = universe;
|
||||
}
|
||||
|
||||
inline void setPhysical(uint8_t port)
|
||||
{
|
||||
physical = port;
|
||||
}
|
||||
|
||||
inline void setLength(uint16_t len)
|
||||
{
|
||||
dmxDataLength = len;
|
||||
}
|
||||
|
||||
inline void setPortType(uint8_t port, uint8_t type)
|
||||
{
|
||||
PollReplyPacket.setPortType(port, type);
|
||||
}
|
||||
|
||||
// DMX controls
|
||||
void enableDMX();
|
||||
void disableDMX();
|
||||
void enableDMXOutput(uint8_t outputID);
|
||||
void disableDMXOutput(uint8_t outputID);
|
||||
|
||||
uint8_t setDMXOutput(uint8_t outputID, uint8_t uartNum, uint16_t attachedUniverse);
|
||||
|
||||
// DMX tick
|
||||
void tickDMX(uint32_t time);
|
||||
|
||||
// Return a pointer to the start of the DMX data
|
||||
inline uint8_t* getDmxFrame(void)
|
||||
{
|
||||
return artnetPacket + ARTNET_DMX_START_LOC;
|
||||
}
|
||||
|
||||
inline void setArtDmxCallback(void (*fptr)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data))
|
||||
{
|
||||
artDmxCallback = fptr;
|
||||
}
|
||||
|
||||
private:
|
||||
WiFiUDP Udp;
|
||||
PollReply PollReplyPacket;
|
||||
String host;
|
||||
|
||||
// Packet handlers
|
||||
uint16_t handleDMX(uint8_t nzs);
|
||||
uint16_t handlePollRequest();
|
||||
|
||||
// Packet vars
|
||||
uint8_t artnetPacket[ARTNET_MAX_BUFFER];
|
||||
uint16_t packetSize;
|
||||
uint16_t opcode;
|
||||
uint8_t sequence;
|
||||
uint8_t physical;
|
||||
uint16_t outgoingUniverse;
|
||||
uint16_t dmxDataLength;
|
||||
IPAddress localIP;
|
||||
IPAddress localMask;
|
||||
IPAddress localBroadcast;
|
||||
|
||||
// Packet functions
|
||||
bool isBroadcast();
|
||||
uint16_t makePacket(void);
|
||||
|
||||
// DMX settings
|
||||
bool DMXOutputStatus;
|
||||
uint16_t DMXOutputs[DMX_MAX_OUTPUTS][3];
|
||||
uint8_t DMXBuffer[DMX_MAX_OUTPUTS][DMX_MAX_BUFFER];
|
||||
|
||||
uint16_t startingUniverse;
|
||||
|
||||
// DMX tick
|
||||
void sendDMX();
|
||||
uint8_t* getDmxFrame(uint8_t outputID);
|
||||
uint8_t msSinceDMXSend;
|
||||
|
||||
void (*artDmxCallback)(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data);
|
||||
static const char artnetId[];
|
||||
};
|
||||
|
||||
#endif
|
22
RpiLedBars/res/src/artnet/NodeReportCodes.h
Normal file
22
RpiLedBars/res/src/artnet/NodeReportCodes.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef NODEREPORTCODES_H
|
||||
#define NODEREPORTCODES_H
|
||||
// List of hex values and discriptions of Node Report Codes
|
||||
|
||||
#define RcDebug 0x0000 // Booted in debug mode (Only used in development)
|
||||
#define RcPowerOk 0x0001 // Power On Tests successful
|
||||
#define RcPowerFail 0x0002 // Hardware tests failed at Power On
|
||||
#define RcSocketWr1 0x0003 // Last UDP from Node failed due to truncated length, Most likely caused by a collision.
|
||||
#define RcParseFail 0x0004 // Unable to identify last UDP transmission. Check OpCode and \packet length.
|
||||
#define RcUdpFail 0x0005 // Unable to open Udp Socket in last transmission attempt
|
||||
#define RcShNameOk 0x0006 // Confirms that Short Name programming via ArtAddress, was successful.
|
||||
#define RcLoNameOk 0x0007 // Confirms that Long Name programming via ArtAddress, was successful.
|
||||
#define RcDmxError 0x0008 // DMX512 receive errors detected.
|
||||
#define RcDmxUdpFull 0x0009 // Ran out of internal DMX transmit buffers.
|
||||
#define RcDmxRxFull 0x000a // Ran out of internal DMX Rx buffers.
|
||||
#define RcSwitchErr 0x000b // Rx Universe switches conflict.
|
||||
#define RcConfigErr 0x000c // Product configuration does not match firmware.
|
||||
#define RcDmxShort 0x000d // DMX output short detected. See GoodOutput field.
|
||||
#define RcFirmwareFail 0x000e // Last attempt to upload new firmware failed.
|
||||
#define RcUserFail 0x000f // User changed switch settings when address locked by remote programming. User changes ignored.
|
||||
|
||||
#endif
|
136
RpiLedBars/res/src/artnet/PollReply.cpp
Normal file
136
RpiLedBars/res/src/artnet/PollReply.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
|
||||
Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015
|
||||
|
||||
Copyright (c) 2016 Stephan Ruloff
|
||||
https://github.com/rstephan
|
||||
|
||||
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, under version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
// Class for saving details to and for constructing pollreply packets
|
||||
|
||||
#include <PollReply.h>
|
||||
|
||||
PollReply::PollReply(){
|
||||
// Zero out vars
|
||||
memset(packet.IPAddr, 0, sizeof(packet.IPAddr));
|
||||
memset(packet.NodeReport, 0, sizeof(packet.NodeReport));
|
||||
memset(packet.PortTypes, 0, sizeof(packet.PortTypes));
|
||||
memset(packet.GoodInput, 0, sizeof(packet.GoodInput));
|
||||
memset(packet.GoodOutput, 0, sizeof(packet.GoodOutput));
|
||||
memset(packet.SwIn, 0, sizeof(packet.SwIn));
|
||||
memset(packet.SwOut, 0, sizeof(packet.SwOut));
|
||||
memset(packet.Filler, 0, sizeof(packet.Filler));
|
||||
setStartingUniverse(0);
|
||||
}
|
||||
|
||||
void PollReply::setMac(byte mac[]){
|
||||
packet.Mac[0] = mac[0];
|
||||
packet.Mac[1] = mac[1];
|
||||
packet.Mac[2] = mac[2];
|
||||
packet.Mac[3] = mac[3];
|
||||
packet.Mac[4] = mac[4];
|
||||
packet.Mac[5] = mac[5];
|
||||
}
|
||||
|
||||
void PollReply::setIP(IPAddress IP){
|
||||
packet.IPAddr[0] = IP[0];
|
||||
packet.IPAddr[1] = IP[1];
|
||||
packet.IPAddr[2] = IP[2];
|
||||
packet.IPAddr[3] = IP[3];
|
||||
}
|
||||
|
||||
void PollReply::setShortName(const char name[]){
|
||||
int shortNameLen = sizeof(packet.ShortName);
|
||||
|
||||
memset(packet.ShortName, 0, shortNameLen);
|
||||
|
||||
shortNameLen--;
|
||||
for(int i = 0; i < shortNameLen && name[i] != 0; i++){
|
||||
packet.ShortName[i] = name[i];
|
||||
}
|
||||
}
|
||||
|
||||
void PollReply::setLongName(const char name[]){
|
||||
int longNameLen = sizeof(packet.LongName);
|
||||
|
||||
memset(packet.LongName, 0, longNameLen);
|
||||
|
||||
longNameLen--;
|
||||
for(int i = 0; i < longNameLen && name[i] != 0; i++){
|
||||
packet.LongName[i] = name[i];
|
||||
}
|
||||
}
|
||||
|
||||
void PollReply::canDHCP(bool can){
|
||||
if(can){
|
||||
packet.Status2 = packet.Status2 | 0b00100000;
|
||||
}
|
||||
else{
|
||||
packet.Status2 = packet.Status2 & (~0b00100000);
|
||||
}
|
||||
}
|
||||
|
||||
void PollReply::isDHCP(bool is){
|
||||
if(is){
|
||||
packet.Status2 = packet.Status2 | 0b01000000;
|
||||
}
|
||||
else{
|
||||
packet.Status2 = packet.Status2 & (~0b01000000);
|
||||
}
|
||||
}
|
||||
|
||||
void PollReply::setNumPorts(uint8_t num){
|
||||
packet.NumPortsLo = num;
|
||||
}
|
||||
|
||||
void PollReply::setPortType(uint8_t port, uint8_t type)
|
||||
{
|
||||
if (port < 4) {
|
||||
packet.PortTypes[port] = type;
|
||||
}
|
||||
}
|
||||
|
||||
void PollReply::setSwOut(uint8_t port, uint16_t universe){
|
||||
if(port < 4){
|
||||
packet.SwOut[port] = universe & 0b0000000000001111;
|
||||
}
|
||||
}
|
||||
|
||||
void PollReply::setOutputEnabled(uint8_t port){
|
||||
if(port < 4){
|
||||
packet.PortTypes[port] = packet.PortTypes[port] | 0b10000000;
|
||||
packet.GoodOutput[port] = packet.GoodOutput[port] | 0b10000000;
|
||||
setSwOut(port, startingUniverse + port);
|
||||
}
|
||||
}
|
||||
|
||||
void PollReply::setOutputDisabled(uint8_t port){
|
||||
if(port < 4){
|
||||
packet.PortTypes[port] = packet.PortTypes[port] & (~0b10000000);
|
||||
packet.GoodOutput[port] = packet.GoodOutput[port] & (~0b10000000);
|
||||
setSwOut(port, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void PollReply::setStartingUniverse(uint16_t startUniverse){
|
||||
startingUniverse = startUniverse;
|
||||
packet.NetSwitch = startUniverse >> 8;
|
||||
packet.SubSwitch = (startUniverse & 0b000000011111111) >> 4;
|
||||
}
|
||||
|
||||
uint8_t* PollReply::printPacket(){
|
||||
return (uint8_t *)&packet;
|
||||
}
|
188
RpiLedBars/res/src/artnet/PollReply.h
Normal file
188
RpiLedBars/res/src/artnet/PollReply.h
Normal file
@@ -0,0 +1,188 @@
|
||||
#ifndef POLLREPLY_H
|
||||
#define POLLREPLY_H
|
||||
/*
|
||||
|
||||
Copyright (c) Charles Yarnold charlesyarnold@gmail.com 2015
|
||||
|
||||
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, under version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
// Class for saving details to and for constructing pollreply packets
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include "OpCodes.h"
|
||||
#include "NodeReportCodes.h"
|
||||
#include "StyleCodes.h"
|
||||
#include "PriorityCodes.h"
|
||||
#include "ProtocolSettings.h"
|
||||
|
||||
struct replyPollPacket{
|
||||
char ID[8] = "Art-Net"; // protocol ID = "Art-Net"
|
||||
//char ID[8]; // protocol ID = "Art-Net"
|
||||
uint16_t OpCode = OpPollReply; // == OpPollReply
|
||||
uint8_t IPAddr[4]; // 0 if not yet configured
|
||||
uint16_t Port = 0x1936;
|
||||
|
||||
uint8_t VersionInfoHi = 0; // The node's current FIRMWARE VERS hi
|
||||
uint8_t VersionInfoLo = 1; // The node's current FIRMWARE VERS lo
|
||||
|
||||
uint8_t NetSwitch = 0; // Bits 14-8 of the 15 bit universe number are encoded into the bottom 7 bits of this field.
|
||||
// This is used in combination with SubSwitch and Swin[] or Swout[] to produce the full universe address.
|
||||
uint8_t SubSwitch = 0; // Bits 7-4 of the 15 bit universe number are encoded into the bottom 4 bits of this field.
|
||||
// This is used in combination with NetSwitch and Swin[] or Swout[] to produce the full universe address.
|
||||
|
||||
uint16_t Oem = 0x0190; // Manufacturer code, bit 15 set if
|
||||
// extended features avail
|
||||
|
||||
uint8_t UbeaVersion = 0; // Firmware version of UBEA
|
||||
|
||||
uint8_t Status = 0;
|
||||
// bit 0 = 0 UBEA not present
|
||||
// bit 0 = 1 UBEA present
|
||||
// bit 1 = 0 Not capable of RDM (Uni-directional DMX)
|
||||
// bit 1 = 1 Capable of RDM (Bi-directional DMX)
|
||||
// bit 2 = 0 Booted from flash (normal boot)
|
||||
// bit 2 = 1 Booted from ROM (possible error condition)
|
||||
// bit 3 = Not used
|
||||
// bit 54 = 00 Universe programming authority unknown
|
||||
// bit 54 = 01 Universe programming authority set by front panel controls
|
||||
// bit 54 = 10 Universe programming authority set by network
|
||||
// bit 76 = 00 Indicators Normal
|
||||
// bit 76 = 01 Indicators Locate
|
||||
// bit 76 = 10 Indicators Mute
|
||||
|
||||
uint8_t EstaMan[2] = {0, 0}; // ESTA manufacturer id, lo byte
|
||||
|
||||
char ShortName[18] = "ElLab Artnetnode\0"; // short name defaults to IP
|
||||
char LongName[64] = "Electric Laboratory Artnetnode\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // long name
|
||||
char NodeReport[64]; // Text feedback of Node status or errors
|
||||
// also used for debug info
|
||||
|
||||
uint8_t NumPortsHi = 0; // 0
|
||||
uint8_t NumPortsLo = 0; // 4 If num i/p ports is dif to output ports, return biggest
|
||||
|
||||
uint8_t PortTypes[4];
|
||||
// bit 7 is output
|
||||
// bit 6 is input
|
||||
// bits 0-5 are protocol number (0= DMX, 1=MIDI)
|
||||
// for DMX-Hub ={0xc0,0xc0,0xc0,0xc0};
|
||||
|
||||
|
||||
uint8_t GoodInput[4];
|
||||
// bit 7 is data received
|
||||
// bit 6 is data includes test packets
|
||||
// bit 5 is data includes SIP's
|
||||
// bit 4 is data includes text
|
||||
// bit 3 set is input is disabled
|
||||
// bit 2 is receive errors
|
||||
// bit 1-0 not used, transmitted as zero.
|
||||
// Don't test for zero!
|
||||
|
||||
uint8_t GoodOutput[4];
|
||||
// bit 7 is data is transmitting
|
||||
// bit 6 is data includes test packets
|
||||
// bit 5 is data includes SIP's
|
||||
// bit 4 is data includes text
|
||||
// bit 3 output is merging data.
|
||||
// bit 2 set if DMX output short detected on power up
|
||||
// bit 1 set if DMX output merge mode is LTP
|
||||
// bit 0 not used, transmitted as zero.
|
||||
|
||||
uint8_t SwIn[4];
|
||||
// Bits 3-0 of the 15 bit universe number are encoded into the low nibble
|
||||
// This is used in combination with SubSwitch and NetSwitch to produce the full universe address.
|
||||
// THIS IS FOR INPUT - ART-NET or DMX
|
||||
// NB ON ART-NET II THESE 4 UNIVERSES WILL BE UNICAST TO.
|
||||
|
||||
|
||||
uint8_t SwOut[4];
|
||||
// Bits 3-0 of the 15 bit universe number are encoded into the low nibble
|
||||
// This is used in combination with SubSwitch and NetSwitch to produce the full universe address.
|
||||
// data belongs
|
||||
// THIS IS FOR OUTPUT - ART-NET or DMX.
|
||||
// NB ON ART-NET II THESE 4 UNIVERSES WILL BE UNICAST TO.
|
||||
|
||||
uint8_t SwVideo = 0;
|
||||
// Low nibble is the value of the video
|
||||
// output channel
|
||||
|
||||
uint8_t SwMacro = 0;
|
||||
// Bit 0 is Macro input 1
|
||||
// Bit 7 is Macro input 8
|
||||
|
||||
|
||||
uint8_t SwRemote = 0;
|
||||
// Bit 0 is Remote input 1
|
||||
// Bit 7 is Remote input 8
|
||||
|
||||
|
||||
uint8_t Spare1 = 0; // Spare, currently zero
|
||||
uint8_t Spare2 = 0; // Spare, currently zero
|
||||
uint8_t Spare3 = 0; // Spare, currently zero
|
||||
uint8_t Style = 0; // Set to Style code to describe type of equipment
|
||||
|
||||
uint8_t Mac[6]; // Mac Address, zero if info not available
|
||||
|
||||
uint8_t BindIp[4]; // If this unit is part of a larger or modular product, this is the IP of the root device.
|
||||
uint8_t BindIndex = 0; // Set to zero if no binding, otherwise this number represents the order of bound devices. A lower number means closer to root device.
|
||||
|
||||
uint8_t Status2 = 0b00000000;
|
||||
// bit 0 = 0 Node does not support web browser
|
||||
// bit 0 = 1 Node supports web browser configuration
|
||||
|
||||
// bit 1 = 0 Node's IP address is manually configured
|
||||
// bit 1 = 1 Node's IP address is DHCP configured
|
||||
|
||||
// bit 2 = 0 Node is not DHCP capable
|
||||
// bit 2 = 1 Node is DHCP capable
|
||||
|
||||
// bit 2-7 not implemented, transmit as zero
|
||||
|
||||
|
||||
|
||||
uint8_t Filler[26]; // Filler bytes, currently zero.
|
||||
};
|
||||
|
||||
class PollReply{
|
||||
public:
|
||||
PollReply();
|
||||
|
||||
void setMac(byte mac[]);
|
||||
void setIP(IPAddress IP);
|
||||
void setShortName(const char name[]);
|
||||
void setLongName(const char name[]);
|
||||
|
||||
void setNumPorts(uint8_t num);
|
||||
|
||||
void setSwOut(uint8_t id, uint16_t universe);
|
||||
void setPortType(uint8_t port, uint8_t type);
|
||||
|
||||
void setOutputEnabled(uint8_t port);
|
||||
void setOutputDisabled(uint8_t port);
|
||||
|
||||
void canDHCP(bool can);
|
||||
void isDHCP(bool is);
|
||||
|
||||
void setStartingUniverse(uint16_t startUniverse);
|
||||
|
||||
uint8_t* printPacket();
|
||||
|
||||
struct replyPollPacket packet;
|
||||
|
||||
private:
|
||||
uint16_t startingUniverse;
|
||||
};
|
||||
|
||||
#endif
|
128
RpiLedBars/res/src/artnet/artnet_op_codes.h
Normal file
128
RpiLedBars/res/src/artnet/artnet_op_codes.h
Normal file
@@ -0,0 +1,128 @@
|
||||
#if !defined(__ARTNET_OP_CODES_H__)
|
||||
#define __ARTNET_OP_CODES_H__
|
||||
|
||||
/* List of hex values and discriptions of Opcodes */
|
||||
|
||||
/* This is an ArtPoll packet, no other data is contained in this UDP packet */
|
||||
#define OpPoll 0x2000
|
||||
|
||||
/* This is an ArtPollReply Packet. It contains device status information. */
|
||||
#define OpPollReply 0x2100
|
||||
|
||||
/* Diagnostics and data logging packet. */
|
||||
#define OpDiagData 0x2300
|
||||
|
||||
/* Used to send text based parameter commands. */
|
||||
#define OpCommand 0x2400
|
||||
|
||||
/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single
|
||||
* Universe. */
|
||||
#define OpOutput 0x5000
|
||||
|
||||
/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single
|
||||
* Universe. */
|
||||
#define OpDmx 0x5000
|
||||
|
||||
/* This is an ArtNzs data packet. It contains non-zero start code (except RDM) DMX512 information
|
||||
* for a single Universe. */
|
||||
#define OpNzs 0x5100
|
||||
|
||||
/* This is an ArtAddress packet. It contains remote programming information for a Node. */
|
||||
#define OpAddress 0x6000
|
||||
|
||||
/* This is an ArtInput packet. It contains enable – disable data for DMX inputs. */
|
||||
#define OpInput 0x7000
|
||||
|
||||
/* This is an ArtTodRequest packet. It is used to request a Table of Devices (ToD) for RDM
|
||||
* discovery. */
|
||||
#define OpTodRequest 0x8000
|
||||
|
||||
/* This is an ArtTodData packet. It is used to send a Table of Devices (ToD) for RDM discovery. */
|
||||
#define OpTodData 0x8100
|
||||
|
||||
/* This is an ArtTodControl packet. It is used to send RDM discovery control messages. */
|
||||
#define OpTodControl 0x8200
|
||||
|
||||
/* This is an ArtRdm packet. It is used to send all non discovery RDM messages. */
|
||||
#define OpRdm 0x8300
|
||||
|
||||
/* This is an ArtRdmSub packet. It is used to send compressed, RDM Sub-Device data. */
|
||||
#define OpRdmSub 0x8400
|
||||
|
||||
/* This is an ArtVideoSetup packet. It contains video screen setup information for nodes that
|
||||
* implement the extended video features. */
|
||||
#define OpVideoSetup 0xa010
|
||||
|
||||
/* This is an ArtVideoPalette packet. It contains colour palette setup information for nodes that
|
||||
* implement the extended video features. */
|
||||
#define OpVideoPalette 0xa020
|
||||
|
||||
/* This is an ArtVideoData packet. It contains display data for nodes that implement the extended
|
||||
* video features. */
|
||||
#define OpVideoData 0xa040
|
||||
|
||||
/* This is an ArtMacMaster packet. It is used to program the Node’s MAC address, Oem device type and
|
||||
* ESTA manufacturer code. This is for factory initialisation of a Node. It is not to be used by
|
||||
* applications. */
|
||||
#define OpMacMaster 0xf000
|
||||
|
||||
/* This is an ArtMacSlave packet. It is returned by the node to acknowledge receipt of an
|
||||
* ArtMacMaster packet. */
|
||||
#define OpMacSlave 0xf100
|
||||
|
||||
/* This is an ArtFirmwareMaster packet. It is used to upload new firmware or firmware extensions to
|
||||
* the Node. */
|
||||
#define OpFirmwareMaster 0xf200
|
||||
|
||||
/* This is an ArtFirmwareReply packet. It is returned by the node to acknowledge receipt of an
|
||||
* ArtFirmwareMaster packet or ArtFileTnMaster packet. */
|
||||
#define OpFirmwareReply 0xf300
|
||||
|
||||
/* Uploads user file to node. */
|
||||
#define OpFileTnMaster 0xf400
|
||||
|
||||
/* Downloads user file from node. */
|
||||
#define OpFileFnMaster 0xf500
|
||||
|
||||
/* Node acknowledge for downloads. */
|
||||
#define OpFileFnReply 0xf600
|
||||
|
||||
/* This is an ArtIpProg packet. It is used to reprogramme the IP, Mask and Port address of the Node.
|
||||
*/
|
||||
#define OpIpProg 0xf800
|
||||
|
||||
/* This is an ArtIpProgReply packet. It is returned by the node to acknowledge receipt of an
|
||||
* ArtIpProg packet. */
|
||||
#define OpIpProgReply 0xf900
|
||||
|
||||
/* This is an ArtMedia packet. It is Unicast by a Media Server and acted upon by a Controller. */
|
||||
#define OpMedia 0x9000
|
||||
|
||||
/* This is an ArtMediaPatch packet. It is Unicast by a Controller and acted upon by a Media Server.
|
||||
*/
|
||||
#define OpMediaPatch 0x9100
|
||||
|
||||
/* This is an ArtMediaControl packet. It is Unicast by a Controller and acted upon by a Media
|
||||
* Server. */
|
||||
#define OpMediaControl 0x9200
|
||||
|
||||
/* This is an ArtMediaControlReply packet. It is Unicast by a Media Server and acted upon by a
|
||||
* Controller. */
|
||||
#define OpMediaContrlReply 0x9300
|
||||
|
||||
/* This is an ArtTimeCode packet. It is used to transport time code over the network. */
|
||||
#define OpTimeCode 0x9700
|
||||
|
||||
/* Used to synchronise real time date and clock */
|
||||
#define OpTimeSync 0x9800
|
||||
|
||||
/* Used to send trigger macros */
|
||||
#define OpTrigger 0x9900
|
||||
|
||||
/* Requests a node's file list */
|
||||
#define OpDirectory 0x9a00
|
||||
|
||||
/* Replies to OpDirectory with file list */
|
||||
#define OpDirectoryReply 0x9b00
|
||||
|
||||
#endif // __ARTNET_OP_CODES_H__
|
26
RpiLedBars/res/src/artnet/artnet_protocol_settings.h
Normal file
26
RpiLedBars/res/src/artnet/artnet_protocol_settings.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#if !defined(__PROTOCOL_SETTINGS_H__)
|
||||
#define __PROTOCOL_SETTINGS_H__
|
||||
|
||||
// UDP port
|
||||
#define ARTNET_PORT 6454
|
||||
|
||||
// Buffers
|
||||
#define ARTNET_MAX_BUFFER 530
|
||||
#define DMX_MAX_BUFFER 512
|
||||
|
||||
// Packet constants
|
||||
#define ARTNET_ID "Art-Net"
|
||||
#define ARTNET_DMX_START_LOC 18
|
||||
|
||||
// Packet confines
|
||||
#define ARTNET_SHORT_NAME_MAX_LENGTH 17
|
||||
#define ARTNET_LONG_NAME_MAX_LENGTH 63
|
||||
|
||||
// DMX settings
|
||||
#define DMX_MAX_OUTPUTS 4
|
||||
#define DMX_MS_BETWEEN_TICKS 25
|
||||
|
||||
// RDM
|
||||
#define DMX_RDM_STARTCODE 0xCC
|
||||
|
||||
#endif // __PROTOCOL_SETTINGS_H__
|
159
RpiLedBars/res/src/cava/rpi_microphone.c
Normal file
159
RpiLedBars/res/src/cava/rpi_microphone.c
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "rpi_microphone.h"
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define CHANNELS_COUNT 1
|
||||
#define SAMPLE_RATE 44100
|
||||
|
||||
snd_pcm_t *capture_handle;
|
||||
|
||||
int write_to_fftw_input_buffers(int16_t frames, int16_t buf[frames * 2], audio_data_t *audio);
|
||||
|
||||
void *microphone_listen(void *arg) {
|
||||
audio_data_t *audio = (audio_data_t *)arg;
|
||||
microphone_setup(audio);
|
||||
while (1) {
|
||||
microphone_exec(audio);
|
||||
}
|
||||
}
|
||||
|
||||
void microphone_setup(audio_data_t *audio) {
|
||||
int err;
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
snd_pcm_uframes_t frames = audio->FFTtreblebufferSize;
|
||||
unsigned int sampleRate = SAMPLE_RATE;
|
||||
|
||||
if ((err = snd_pcm_open(&capture_handle, audio->source, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
||||
fprintf(stderr, "cannot open audio device %s (%s)\n", audio->source, snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
|
||||
fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
|
||||
fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
||||
fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
|
||||
fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, CHANNELS_COUNT)) < 0) {
|
||||
fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &sampleRate, NULL)) < 0) {
|
||||
fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, &frames, NULL)) <
|
||||
0) {
|
||||
fprintf(stderr, "cannot set period size (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
|
||||
fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
snd_pcm_hw_params_get_rate(hw_params, &audio->rate, NULL);
|
||||
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
|
||||
if ((err = snd_pcm_prepare(capture_handle)) < 0) {
|
||||
fprintf(stderr, "cannot prepare audio interface for use (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void microphone_exec(audio_data_t *audio) {
|
||||
int err;
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
snd_pcm_uframes_t period_size;
|
||||
snd_pcm_uframes_t frames = audio->FFTtreblebufferSize;
|
||||
|
||||
snd_pcm_get_params(capture_handle, &buffer_size, &period_size);
|
||||
int16_t buf[period_size];
|
||||
frames = period_size / 2;
|
||||
|
||||
err = snd_pcm_readi(capture_handle, buf, frames);
|
||||
|
||||
if (err == -EPIPE) {
|
||||
/* EPIPE means overrun */
|
||||
fprintf(stderr, "overrun occurred\n");
|
||||
snd_pcm_prepare(capture_handle);
|
||||
} else if (err < 0) {
|
||||
fprintf(stderr, "error from read: %s\n", snd_strerror(err));
|
||||
} else if (err != (int)frames) {
|
||||
fprintf(stderr, "short read, read %d %d frames\n", err, (int)frames);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&audio->lock);
|
||||
write_to_fftw_input_buffers(frames, buf, audio);
|
||||
pthread_mutex_unlock(&audio->lock);
|
||||
}
|
||||
|
||||
void reset_output_buffers(audio_data_t *data) {
|
||||
memset(data->in_bass_l, 0, sizeof(double) * data->FFTbassbufferSize);
|
||||
memset(data->in_mid_l, 0, sizeof(double) * data->FFTmidbufferSize);
|
||||
memset(data->in_treble_l, 0, sizeof(double) * data->FFTtreblebufferSize);
|
||||
memset(data->in_bass_l_raw, 0, sizeof(double) * data->FFTbassbufferSize);
|
||||
memset(data->in_mid_l_raw, 0, sizeof(double) * data->FFTmidbufferSize);
|
||||
memset(data->in_treble_l_raw, 0, sizeof(double) * data->FFTtreblebufferSize);
|
||||
}
|
||||
|
||||
int write_to_fftw_input_buffers(int16_t frames, int16_t buf[frames * 2], audio_data_t *audio) {
|
||||
if (frames == 0)
|
||||
return 0;
|
||||
|
||||
for (uint16_t n = audio->FFTbassbufferSize; n > frames; n = n - frames) {
|
||||
for (uint16_t i = 1; i <= frames; i++) {
|
||||
audio->in_bass_l_raw[n - i] = audio->in_bass_l_raw[n - i - frames];
|
||||
}
|
||||
}
|
||||
for (uint16_t n = audio->FFTmidbufferSize; n > frames; n = n - frames) {
|
||||
for (uint16_t i = 1; i <= frames; i++) {
|
||||
audio->in_mid_l_raw[n - i] = audio->in_mid_l_raw[n - i - frames];
|
||||
}
|
||||
}
|
||||
for (uint16_t n = audio->FFTtreblebufferSize; n > frames; n = n - frames) {
|
||||
for (uint16_t i = 1; i <= frames; i++) {
|
||||
audio->in_treble_l_raw[n - i] = audio->in_treble_l_raw[n - i - frames];
|
||||
}
|
||||
}
|
||||
uint16_t n = frames - 1;
|
||||
|
||||
for (uint16_t i = 0; i < frames; i++) {
|
||||
audio->in_bass_l_raw[n] = buf[i * 2];
|
||||
audio->in_mid_l_raw[n] = audio->in_bass_l_raw[n];
|
||||
audio->in_treble_l_raw[n] = audio->in_bass_l_raw[n];
|
||||
n--;
|
||||
}
|
||||
|
||||
// Hann Window
|
||||
for (int i = 0; i < audio->FFTbassbufferSize; i++) {
|
||||
audio->in_bass_l[i] = audio->bass_multiplier[i] * audio->in_bass_l_raw[i];
|
||||
}
|
||||
for (int i = 0; i < audio->FFTmidbufferSize; i++) {
|
||||
audio->in_mid_l[i] = audio->mid_multiplier[i] * audio->in_mid_l_raw[i];
|
||||
}
|
||||
for (int i = 0; i < audio->FFTtreblebufferSize; i++) {
|
||||
audio->in_treble_l[i] = audio->treble_multiplier[i] * audio->in_treble_l_raw[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
42
RpiLedBars/res/src/cava/rpi_microphone.h
Normal file
42
RpiLedBars/res/src/cava/rpi_microphone.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#if !defined(__RPI_MICROPHONE_H__)
|
||||
#define __RPI_MICROPHONE_H__
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
int FFTbassbufferSize;
|
||||
int FFTmidbufferSize;
|
||||
int FFTtreblebufferSize;
|
||||
int bass_index;
|
||||
int mid_index;
|
||||
int treble_index;
|
||||
double *bass_multiplier;
|
||||
double *mid_multiplier;
|
||||
double *treble_multiplier;
|
||||
double *in_bass_l_raw;
|
||||
double *in_mid_l_raw;
|
||||
double *in_treble_l_raw;
|
||||
double *in_bass_l;
|
||||
double *in_mid_l;
|
||||
double *in_treble_l;
|
||||
int format;
|
||||
unsigned int rate;
|
||||
char *source; // alsa device, fifo path or pulse source
|
||||
int im; // input mode alsa, fifo or pulse
|
||||
unsigned int channels;
|
||||
bool left, right, average;
|
||||
int terminate; // shared variable used to terminate audio thread
|
||||
char error_message[1024];
|
||||
pthread_mutex_t lock;
|
||||
} audio_data_t;
|
||||
|
||||
void *microphone_listen(void *arg);
|
||||
|
||||
void microphone_setup(audio_data_t *audio);
|
||||
|
||||
void microphone_exec(audio_data_t *audio);
|
||||
|
||||
void reset_output_buffers(audio_data_t *data);
|
||||
|
||||
#endif /* __RPI_MICROPHONE_H__ */
|
447
RpiLedBars/res/src/cava/rpi_spectrum.c
Normal file
447
RpiLedBars/res/src/cava/rpi_spectrum.c
Normal file
@@ -0,0 +1,447 @@
|
||||
#include "rpi_spectrum.h"
|
||||
|
||||
#include <fftw3.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "rpi_microphone.h"
|
||||
|
||||
typedef struct {
|
||||
int framerate;
|
||||
int number_of_bars;
|
||||
int lower_cut_off, upper_cut_off;
|
||||
} param_t;
|
||||
|
||||
typedef struct {
|
||||
double sens;
|
||||
double gravity;
|
||||
double integral;
|
||||
double userEQ[128];
|
||||
} config_t;
|
||||
|
||||
param_t param = {
|
||||
.framerate = 60, .number_of_bars = 128, .lower_cut_off = 50, .upper_cut_off = 10000};
|
||||
config_t conf = {.sens = 1, .gravity = 0, .integral = 0, .userEQ = {1}};
|
||||
|
||||
pthread_t microphoneListener;
|
||||
|
||||
audio_data_t audio;
|
||||
|
||||
fftw_complex *out_bass_l;
|
||||
fftw_plan p_bass_l;
|
||||
fftw_complex *out_mid_l;
|
||||
fftw_plan p_mid_l;
|
||||
fftw_complex *out_treble_l;
|
||||
fftw_plan p_treble_l;
|
||||
|
||||
// input: init
|
||||
int *bars_left;
|
||||
double *temp_l;
|
||||
|
||||
int bass_cut_off = 150;
|
||||
int treble_cut_off = 2500;
|
||||
|
||||
void init_plan(int bufferSize, double **in, double **in_raw, fftw_complex **out, fftw_plan *p);
|
||||
|
||||
void spectrum_start_bg_worker();
|
||||
|
||||
void spectrum_stop_bg_worker();
|
||||
|
||||
void spectrum_setup(char *audio_source) {
|
||||
for (size_t i = 0; i < 128; i++) {
|
||||
conf.userEQ[i] = 1;
|
||||
}
|
||||
|
||||
memset(&audio, 0, sizeof(audio));
|
||||
|
||||
audio.source = malloc(1 + strlen(audio_source));
|
||||
strcpy(audio.source, audio_source);
|
||||
|
||||
audio.format = -1;
|
||||
audio.rate = 0;
|
||||
audio.FFTbassbufferSize = 4096;
|
||||
audio.FFTmidbufferSize = 2048;
|
||||
audio.FFTtreblebufferSize = 1024;
|
||||
audio.terminate = 0;
|
||||
audio.channels = 1;
|
||||
audio.average = false;
|
||||
audio.left = true;
|
||||
audio.right = false;
|
||||
audio.bass_index = 0;
|
||||
audio.mid_index = 0;
|
||||
audio.treble_index = 0;
|
||||
audio.bass_multiplier = (double *)malloc(audio.FFTbassbufferSize * sizeof(double));
|
||||
audio.mid_multiplier = (double *)malloc(audio.FFTmidbufferSize * sizeof(double));
|
||||
audio.treble_multiplier = (double *)malloc(audio.FFTtreblebufferSize * sizeof(double));
|
||||
|
||||
temp_l = (double *)malloc((audio.FFTbassbufferSize / 2 + 1) * sizeof(double));
|
||||
|
||||
bars_left = (int *)malloc(256 * sizeof(int));
|
||||
|
||||
for (int i = 0; i < audio.FFTbassbufferSize; i++) {
|
||||
audio.bass_multiplier[i] = 0.5 * (1 - cos(2 * M_PI * i / (audio.FFTbassbufferSize - 1)));
|
||||
}
|
||||
for (int i = 0; i < audio.FFTmidbufferSize; i++) {
|
||||
audio.mid_multiplier[i] = 0.5 * (1 - cos(2 * M_PI * i / (audio.FFTmidbufferSize - 1)));
|
||||
}
|
||||
for (int i = 0; i < audio.FFTtreblebufferSize; i++) {
|
||||
audio.treble_multiplier[i] = 0.5 * (1 - cos(2 * M_PI * i / (audio.FFTtreblebufferSize - 1)));
|
||||
}
|
||||
|
||||
// BASS
|
||||
// audio.FFTbassbufferSize = audio.rate / 20; // audio.FFTbassbufferSize;
|
||||
// audio.in_bass_l = fftw_alloc_real(audio.FFTbassbufferSize);
|
||||
// audio.in_bass_l_raw = fftw_alloc_real(audio.FFTbassbufferSize);
|
||||
|
||||
// out_bass_l = fftw_alloc_complex(audio.FFTbassbufferSize / 2 + 1);
|
||||
// memset(out_bass_l, 0, (audio.FFTbassbufferSize / 2 + 1) * sizeof(fftw_complex));
|
||||
|
||||
// p_bass_l =
|
||||
// fftw_plan_dft_r2c_1d(audio.FFTbassbufferSize, audio.in_bass_l, out_bass_l, FFTW_MEASURE);
|
||||
|
||||
init_plan(audio.FFTbassbufferSize, &audio.in_bass_l, &audio.in_bass_l_raw, &out_bass_l,
|
||||
&p_bass_l);
|
||||
|
||||
// MID
|
||||
// audio.FFTmidbufferSize = audio.rate / bass_cut_off; // audio.FFTbassbufferSize;
|
||||
// audio.in_mid_l = fftw_alloc_real(audio.FFTmidbufferSize);
|
||||
// audio.in_mid_l_raw = fftw_alloc_real(audio.FFTmidbufferSize);
|
||||
|
||||
// out_mid_l = fftw_alloc_complex(audio.FFTmidbufferSize / 2 + 1);
|
||||
// memset(out_mid_l, 0, (audio.FFTmidbufferSize / 2 + 1) * sizeof(fftw_complex));
|
||||
|
||||
// p_mid_l = fftw_plan_dft_r2c_1d(audio.FFTmidbufferSize, audio.in_mid_l, out_mid_l,
|
||||
// FFTW_MEASURE);
|
||||
|
||||
init_plan(audio.FFTmidbufferSize, &audio.in_mid_l, &audio.in_mid_l_raw, &out_mid_l, &p_mid_l);
|
||||
|
||||
// TRIEBLE
|
||||
// audio.FFTtreblebufferSize = audio.rate / treble_cut_off; // audio.FFTbassbufferSize;
|
||||
// audio.in_treble_l = fftw_alloc_real(audio.FFTtreblebufferSize);
|
||||
// audio.in_treble_l_raw = fftw_alloc_real(audio.FFTtreblebufferSize);
|
||||
|
||||
// out_treble_l = fftw_alloc_complex(audio.FFTtreblebufferSize / 2 + 1);
|
||||
// memset(out_treble_l, 0, (audio.FFTtreblebufferSize / 2 + 1) * sizeof(fftw_complex));
|
||||
|
||||
// p_treble_l = fftw_plan_dft_r2c_1d(audio.FFTtreblebufferSize, audio.in_treble_l,
|
||||
// out_treble_l,
|
||||
// FFTW_MEASURE);
|
||||
|
||||
init_plan(audio.FFTtreblebufferSize, &audio.in_treble_l, &audio.in_treble_l_raw, &out_treble_l,
|
||||
&p_treble_l);
|
||||
|
||||
reset_output_buffers(&audio);
|
||||
|
||||
spectrum_start_bg_worker();
|
||||
}
|
||||
|
||||
void spectrum_start_bg_worker() {
|
||||
pthread_mutex_init(&audio.lock, NULL);
|
||||
if (pthread_create(µphoneListener, NULL, microphone_listen, (void *)&audio) < 0) {
|
||||
perror("pthread_create");
|
||||
}
|
||||
|
||||
int timeout_counter = 0;
|
||||
while (audio.rate == 0) {
|
||||
usleep(2000);
|
||||
++timeout_counter;
|
||||
if (timeout_counter > 2000) {
|
||||
fprintf(stderr, "could not get rate and/or format, problems with audio thread? "
|
||||
"quiting...\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void spectrum_stop_bg_worker() {
|
||||
if (pthread_cancel(microphoneListener) != 0) {
|
||||
perror("pthread_cancel");
|
||||
}
|
||||
if (pthread_join(microphoneListener, NULL) != 0) {
|
||||
perror("pthread_join");
|
||||
}
|
||||
}
|
||||
|
||||
void spectrum_execute() {
|
||||
int bars[256];
|
||||
int bars_mem[256];
|
||||
int bars_last[256];
|
||||
int previous_frame[256];
|
||||
int fall[256];
|
||||
float bars_peak[256];
|
||||
|
||||
int height, lines, width, remainder, fp;
|
||||
|
||||
bool reloadConf = false;
|
||||
|
||||
for (int n = 0; n < 256; n++) {
|
||||
bars_last[n] = 0;
|
||||
previous_frame[n] = 0;
|
||||
fall[n] = 0;
|
||||
bars_peak[n] = 0;
|
||||
bars_mem[n] = 0;
|
||||
bars[n] = 0;
|
||||
}
|
||||
|
||||
width = 256;
|
||||
height = UINT16_MAX;
|
||||
|
||||
// getting numbers of bars
|
||||
int number_of_bars = param.number_of_bars;
|
||||
|
||||
if (number_of_bars > 256)
|
||||
number_of_bars = 256; // cant have more than 256 bars
|
||||
|
||||
// process [smoothing]: calculate gravity
|
||||
float g = conf.gravity * ((float)height / 2160) * pow((60 / (float)param.framerate), 2.5);
|
||||
|
||||
// calculate integral value, must be reduced with height
|
||||
double integral = conf.integral;
|
||||
if (height > 320)
|
||||
integral = conf.integral * 1 / sqrt((log10((float)height / 10)));
|
||||
|
||||
// process: calculate cutoff frequencies and eq
|
||||
|
||||
double userEQ_keys_to_bars_ratio;
|
||||
|
||||
if (number_of_bars > 0) {
|
||||
userEQ_keys_to_bars_ratio = (double)(((double)(number_of_bars < 128 ? number_of_bars : 128)) /
|
||||
((double)number_of_bars));
|
||||
}
|
||||
|
||||
// calculate frequency constant (used to distribute bars across the frequency band)
|
||||
double frequency_constant = log10((float)param.lower_cut_off / (float)param.upper_cut_off) /
|
||||
(1 / ((float)number_of_bars + 1) - 1);
|
||||
|
||||
float cut_off_frequency[256];
|
||||
float upper_cut_off_frequency[256];
|
||||
float relative_cut_off[256];
|
||||
double center_frequencies[256];
|
||||
int FFTbuffer_lower_cut_off[256];
|
||||
int FFTbuffer_upper_cut_off[256];
|
||||
double eq[256];
|
||||
|
||||
int bass_cut_off_bar = -1;
|
||||
int treble_cut_off_bar = -1;
|
||||
bool first_bar = true;
|
||||
int first_treble_bar = 0;
|
||||
int bar_buffer[number_of_bars + 1];
|
||||
|
||||
for (int n = 0; n < number_of_bars + 1; n++) {
|
||||
double bar_distribution_coefficient = frequency_constant * (-1);
|
||||
bar_distribution_coefficient +=
|
||||
((float)n + 1) / ((float)number_of_bars + 1) * frequency_constant;
|
||||
cut_off_frequency[n] = param.upper_cut_off * pow(10, bar_distribution_coefficient);
|
||||
|
||||
if (n > 0) {
|
||||
if (cut_off_frequency[n - 1] >= cut_off_frequency[n] &&
|
||||
cut_off_frequency[n - 1] > bass_cut_off)
|
||||
cut_off_frequency[n] =
|
||||
cut_off_frequency[n - 1] + (cut_off_frequency[n - 1] - cut_off_frequency[n - 2]);
|
||||
}
|
||||
|
||||
relative_cut_off[n] = cut_off_frequency[n] / (audio.rate / 2);
|
||||
// remember nyquist!, per my calculations this should be rate/2
|
||||
// and nyquist freq in M/2 but testing shows it is not...
|
||||
// or maybe the nq freq is in M/4
|
||||
|
||||
eq[n] = pow(cut_off_frequency[n], 1);
|
||||
|
||||
// the numbers that come out of the FFT are very high
|
||||
// the EQ is used to "normalize" them by dividing with this very huge number
|
||||
eq[n] *= (float)height / pow(2, 28);
|
||||
|
||||
eq[n] *= conf.userEQ[(int)floor(((double)n) * userEQ_keys_to_bars_ratio)];
|
||||
|
||||
eq[n] /= log2(audio.FFTbassbufferSize);
|
||||
|
||||
if (cut_off_frequency[n] < bass_cut_off) {
|
||||
// BASS
|
||||
bar_buffer[n] = 1;
|
||||
FFTbuffer_lower_cut_off[n] = relative_cut_off[n] * (audio.FFTbassbufferSize / 2);
|
||||
bass_cut_off_bar++;
|
||||
treble_cut_off_bar++;
|
||||
if (bass_cut_off_bar > 0)
|
||||
first_bar = false;
|
||||
|
||||
eq[n] *= log2(audio.FFTbassbufferSize);
|
||||
} else if (cut_off_frequency[n] > bass_cut_off && cut_off_frequency[n] < treble_cut_off) {
|
||||
// MID
|
||||
bar_buffer[n] = 2;
|
||||
FFTbuffer_lower_cut_off[n] = relative_cut_off[n] * (audio.FFTmidbufferSize / 2);
|
||||
treble_cut_off_bar++;
|
||||
if ((treble_cut_off_bar - bass_cut_off_bar) == 1) {
|
||||
first_bar = true;
|
||||
FFTbuffer_upper_cut_off[n - 1] = relative_cut_off[n] * (audio.FFTbassbufferSize / 2);
|
||||
} else {
|
||||
first_bar = false;
|
||||
}
|
||||
|
||||
eq[n] *= log2(audio.FFTmidbufferSize);
|
||||
} else {
|
||||
// TREBLE
|
||||
bar_buffer[n] = 3;
|
||||
FFTbuffer_lower_cut_off[n] = relative_cut_off[n] * (audio.FFTtreblebufferSize / 2);
|
||||
first_treble_bar++;
|
||||
if (first_treble_bar == 1) {
|
||||
first_bar = true;
|
||||
FFTbuffer_upper_cut_off[n - 1] = relative_cut_off[n] * (audio.FFTmidbufferSize / 2);
|
||||
} else {
|
||||
first_bar = false;
|
||||
}
|
||||
|
||||
eq[n] *= log2(audio.FFTtreblebufferSize);
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
if (!first_bar) {
|
||||
FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n] - 1;
|
||||
|
||||
// pushing the spectrum up if the exponential function gets "clumped" in the
|
||||
// bass and caluclating new cut off frequencies
|
||||
if (FFTbuffer_lower_cut_off[n] <= FFTbuffer_lower_cut_off[n - 1]) {
|
||||
|
||||
FFTbuffer_lower_cut_off[n] = FFTbuffer_lower_cut_off[n - 1] + 1;
|
||||
FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n] - 1;
|
||||
|
||||
if (bar_buffer[n] == 1)
|
||||
relative_cut_off[n] =
|
||||
(float)(FFTbuffer_lower_cut_off[n]) / ((float)audio.FFTbassbufferSize / 2);
|
||||
else if (bar_buffer[n] == 2)
|
||||
relative_cut_off[n] =
|
||||
(float)(FFTbuffer_lower_cut_off[n]) / ((float)audio.FFTmidbufferSize / 2);
|
||||
else if (bar_buffer[n] == 3)
|
||||
relative_cut_off[n] =
|
||||
(float)(FFTbuffer_lower_cut_off[n]) / ((float)audio.FFTtreblebufferSize / 2);
|
||||
|
||||
cut_off_frequency[n] = relative_cut_off[n] * ((float)audio.rate / 2);
|
||||
}
|
||||
} else {
|
||||
if (FFTbuffer_upper_cut_off[n - 1] <= FFTbuffer_lower_cut_off[n - 1])
|
||||
FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n - 1] + 1;
|
||||
}
|
||||
upper_cut_off_frequency[n - 1] =
|
||||
cut_off_frequency[n]; // high_relative_cut_off * ((float)audio.rate / 2);
|
||||
center_frequencies[n - 1] =
|
||||
pow((cut_off_frequency[n - 1] * upper_cut_off_frequency[n - 1]), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// process: execute FFT and sort frequency bands
|
||||
pthread_mutex_lock(&audio.lock);
|
||||
fftw_execute(p_bass_l);
|
||||
fftw_execute(p_mid_l);
|
||||
fftw_execute(p_treble_l);
|
||||
pthread_mutex_unlock(&audio.lock);
|
||||
|
||||
// process: separate frequency bands
|
||||
for (int n = 0; n < number_of_bars; n++) {
|
||||
|
||||
temp_l[n] = 0;
|
||||
|
||||
// process: add upp FFT values within bands
|
||||
for (int i = FFTbuffer_lower_cut_off[n]; i <= FFTbuffer_upper_cut_off[n]; i++) {
|
||||
if (n <= bass_cut_off_bar) {
|
||||
|
||||
temp_l[n] += hypot(out_bass_l[i][0], out_bass_l[i][1]);
|
||||
|
||||
} else if (n > bass_cut_off_bar && n <= treble_cut_off_bar) {
|
||||
|
||||
temp_l[n] += hypot(out_mid_l[i][0], out_mid_l[i][1]);
|
||||
|
||||
} else if (n > treble_cut_off_bar) {
|
||||
|
||||
temp_l[n] += hypot(out_treble_l[i][0], out_treble_l[i][1]);
|
||||
}
|
||||
}
|
||||
|
||||
// getting average multiply with sens and eq
|
||||
temp_l[n] /= FFTbuffer_upper_cut_off[n] - FFTbuffer_lower_cut_off[n] + 1;
|
||||
temp_l[n] *= conf.sens * eq[n];
|
||||
|
||||
bars_left[n] = temp_l[n];
|
||||
}
|
||||
|
||||
// process [filter]
|
||||
|
||||
// if (p.monstercat) {
|
||||
// if (p.stereo) {
|
||||
// bars_left = monstercat_filter(bars_left, number_of_bars / 2, p.waves, p.monstercat);
|
||||
// bars_right = monstercat_filter(bars_right, number_of_bars / 2, p.waves, p.monstercat);
|
||||
// } else {
|
||||
// bars_left = monstercat_filter(bars_left, number_of_bars, p.waves, p.monstercat);
|
||||
// }
|
||||
// }
|
||||
|
||||
// processing signal
|
||||
|
||||
// bool senselow = true;
|
||||
|
||||
for (int n = 0; n < number_of_bars; n++) {
|
||||
// // mirroring stereo channels
|
||||
// if (p.stereo) {
|
||||
// if (n < number_of_bars / 2) {
|
||||
// bars[n] = bars_left[number_of_bars / 2 - n - 1];
|
||||
// } else {
|
||||
// bars[n] = bars_right[n - number_of_bars / 2];
|
||||
// }
|
||||
|
||||
// } else {
|
||||
bars[n] = bars_left[n];
|
||||
}
|
||||
|
||||
// process [smoothing]: falloff
|
||||
// if (g > 0) {
|
||||
// if (bars[n] < bars_last[n]) {
|
||||
// bars[n] = bars_peak[n] - (g * fall[n] * fall[n]);
|
||||
// if (bars[n] < 0)
|
||||
// bars[n] = 0;
|
||||
// fall[n]++;
|
||||
// } else {
|
||||
// bars_peak[n] = bars[n];
|
||||
// fall[n] = 0;
|
||||
// }
|
||||
|
||||
// bars_last[n] = bars[n];
|
||||
// }
|
||||
|
||||
// process [smoothing]: integral
|
||||
// if (p.integral > 0) {
|
||||
// bars[n] = bars_mem[n] * integral + bars[n];
|
||||
// bars_mem[n] = bars[n];
|
||||
|
||||
// int diff = height - bars[n];
|
||||
// if (diff < 0)
|
||||
// diff = 0;
|
||||
// double div = 1 / (diff + 1);
|
||||
// // bars[n] = bars[n] - pow(div, 10) * (height + 1);
|
||||
// bars_mem[n] = bars_mem[n] * (1 - div / 20);
|
||||
// }
|
||||
|
||||
memcpy(previous_frame, bars, 256 * sizeof(int));
|
||||
|
||||
for (size_t i = 0; i < number_of_bars; i++) {
|
||||
printf("%5d, ", bars[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int *spectrum_display_bars() { return bars_left; }
|
||||
|
||||
void init_plan(int bufferSize, double **in, double **in_raw, fftw_complex **out, fftw_plan *p) {
|
||||
*in = fftw_alloc_real(bufferSize);
|
||||
*in_raw = fftw_alloc_real(bufferSize);
|
||||
|
||||
*out = fftw_alloc_complex(bufferSize / 2 + 1);
|
||||
memset(*out, 0, (bufferSize / 2 + 1) * sizeof(fftw_complex));
|
||||
|
||||
*p = fftw_plan_dft_r2c_1d(bufferSize, *in, *out, FFTW_MEASURE);
|
||||
}
|
||||
|
||||
void compute_param() {}
|
||||
|
||||
void compute_config() {}
|
14
RpiLedBars/res/src/cava/rpi_spectrum.h
Normal file
14
RpiLedBars/res/src/cava/rpi_spectrum.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#if !defined(__RPI_SPECTRUM_H__)
|
||||
#define __RPI_SPECTRUM_H__
|
||||
|
||||
void spectrum_setup(char *audio_source);
|
||||
|
||||
void spectrum_start_bg_worker();
|
||||
|
||||
void spectrum_stop_bg_worker();
|
||||
|
||||
void spectrum_execute();
|
||||
|
||||
int *spectrum_get_bars();
|
||||
|
||||
#endif /* __RPI_SPECTRUM_H__ */
|
15
RpiLedBars/res/src/output/raw.c
Normal file
15
RpiLedBars/res/src/output/raw.c
Normal file
@@ -0,0 +1,15 @@
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int16_t buf_16;
|
||||
int8_t buf_8;
|
||||
|
||||
int buffer[200];
|
||||
|
||||
int print_raw_out(int bars_count, int fd, int is_binary, int bit_format, int ascii_range,
|
||||
char bar_delim, char frame_delim, int const f[200]) {
|
||||
memcpy(buffer, f, sizeof(int) * bars_count);
|
||||
return 0;
|
||||
}
|
2
RpiLedBars/res/src/output/raw.h
Normal file
2
RpiLedBars/res/src/output/raw.h
Normal file
@@ -0,0 +1,2 @@
|
||||
int print_raw_out(int bars_count, int fd, int is_binary, int bit_format, int ascii_range,
|
||||
char bar_delim, char frame_delim, int const f[200]);
|
37
RpiLedBars/res/src/smi/VitualMemory.cpp
Normal file
37
RpiLedBars/res/src/smi/VitualMemory.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "VitualMemory.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "utils.h"
|
||||
#include <fcntl.h>
|
||||
// #include <sys/ioctl.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
VitualMemory::VitualMemory(void *addr, int size) {
|
||||
int fd;
|
||||
|
||||
roundSize = PAGE_ROUNDUP(size);
|
||||
|
||||
if ((fd = open("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC)) < 0) {
|
||||
throw std::runtime_error("Error: can't open /dev/mem, run using sudo");
|
||||
}
|
||||
|
||||
mem = mmap(0, roundSize, PROT_WRITE | PROT_READ, MAP_SHARED, fd, (uint32_t)addr);
|
||||
close(fd);
|
||||
|
||||
#if _DEBUG
|
||||
printf("Map %p -> %p\n", (void *)addr, mem);
|
||||
#endif // _DEBUG
|
||||
|
||||
if (mem == MAP_FAILED) {
|
||||
throw std::runtime_error("Error: can't map memory");
|
||||
}
|
||||
}
|
||||
|
||||
VitualMemory::~VitualMemory() {
|
||||
if (mem) {
|
||||
munmap(mem, roundSize);
|
||||
}
|
||||
}
|
14
RpiLedBars/res/src/smi/VitualMemory.hpp
Normal file
14
RpiLedBars/res/src/smi/VitualMemory.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#if !defined(__VIRTUAL_MEMORY_H__)
|
||||
#define __VIRTUAL_MEMORY_H__
|
||||
|
||||
class VitualMemory {
|
||||
public:
|
||||
VitualMemory(void *addr, int size);
|
||||
~VitualMemory();
|
||||
|
||||
private:
|
||||
int roundSize;
|
||||
void *mem;
|
||||
};
|
||||
|
||||
#endif // __VIRTUAL_MEMORY_H__
|
4
RpiLedBars/res/src/smi/utils.h
Normal file
4
RpiLedBars/res/src/smi/utils.h
Normal file
@@ -0,0 +1,4 @@
|
||||
// Size of memory page
|
||||
#define PAGE_SIZE 0x1000
|
||||
// Round up to nearest page
|
||||
#define PAGE_ROUNDUP(n) ((n) % PAGE_SIZE == 0 ? (n) : ((n) + PAGE_SIZE) & ~(PAGE_SIZE - 1))
|
8
RpiLedBars/res/src/tmp/main.c
Normal file
8
RpiLedBars/res/src/tmp/main.c
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
|
||||
int main(int argc, char const *argv[])
|
||||
{
|
||||
|
||||
return 0;
|
||||
}
|
93
RpiLedBars/res/src/tmp/rpi_cava.c
Normal file
93
RpiLedBars/res/src/tmp/rpi_cava.c
Normal file
@@ -0,0 +1,93 @@
|
||||
#include "rpi_cava.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
pid_t cavaPid;
|
||||
pthread_t fifoReader;
|
||||
int cavaFifo;
|
||||
uint16_t buffer[20];
|
||||
|
||||
static void *fifo_to_buffer(void *arg);
|
||||
|
||||
void setup_cava() {}
|
||||
|
||||
void close_cava() { stop_cava_bg_worker(); }
|
||||
|
||||
void start_cava_bg_worker() {
|
||||
if ((cavaPid = fork()) == -1) {
|
||||
perror("fork");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (cavaPid == 0) {
|
||||
/* Child process*/
|
||||
char *args[] = {"/usr/local/bin/cava", "-p", "/home/pi/LedBars/RpiLedBars/cava_config", NULL};
|
||||
|
||||
if (execv(args[0], args) != 0) {
|
||||
perror("execv");
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
sleep(1);
|
||||
if ((cavaFifo = open("/tmp/cava_output", O_RDONLY)) < 0) {
|
||||
perror("open");
|
||||
close_cava();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
pthread_create(&fifoReader, NULL, fifo_to_buffer, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void stop_cava_bg_worker() {
|
||||
if (pthread_cancel(fifoReader) != 0) {
|
||||
perror("pthread_cancel");
|
||||
}
|
||||
if (pthread_join(fifoReader, NULL) != 0) {
|
||||
perror("pthread_join");
|
||||
}
|
||||
close(cavaFifo);
|
||||
kill(cavaPid, SIGTERM);
|
||||
}
|
||||
|
||||
int get_cava_buffer(uint16_t buffer_dst[20]) {
|
||||
memcpy(buffer_dst, buffer, 40);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *fifo_to_buffer(void *arg) {
|
||||
while (1) {
|
||||
int totalRead = 0;
|
||||
while (totalRead < 40) {
|
||||
int nread;
|
||||
nread = read(cavaFifo, ((uint8_t *)buffer) + totalRead, 40 - totalRead);
|
||||
if (nread >= 0) {
|
||||
totalRead += nread;
|
||||
} else {
|
||||
if (errno != EAGAIN) {
|
||||
perror("read");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("data[%d] : ", totalRead);
|
||||
for (size_t i = 0; i < 20; i++) {
|
||||
printf("%5u;", buffer[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
return NULL;
|
||||
}
|
16
RpiLedBars/res/src/tmp/rpi_cava.h
Normal file
16
RpiLedBars/res/src/tmp/rpi_cava.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#if !defined(__RPI_CAVA_H__)
|
||||
#define __RPI_CAVA_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void setup_cava();
|
||||
|
||||
int get_cava_buffer(uint16_t buffer_dst[20]);
|
||||
|
||||
void close_cava();
|
||||
|
||||
void start_cava_bg_worker();
|
||||
|
||||
void stop_cava_bg_worker();
|
||||
|
||||
#endif /* __RPI_CAVA_H__ */
|
103
RpiLedBars/res/src/tmp/rpi_microphone.c
Normal file
103
RpiLedBars/res/src/tmp/rpi_microphone.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "rpi_microphone.h"
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <fftw3.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define CHANNELS_COUNT 1
|
||||
#define SAMPLE_RATE 44100
|
||||
|
||||
snd_pcm_t *capture_handle;
|
||||
char const *deviceName = "plughw:1";
|
||||
|
||||
void setup_microphone() {
|
||||
int err;
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
unsigned int rate = 44100;
|
||||
|
||||
if ((err = snd_pcm_open(&capture_handle, deviceName, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
||||
fprintf(stderr, "cannot open audio device %s (%s)\n", deviceName, snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
|
||||
fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
|
||||
fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
||||
fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
|
||||
fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &rate, 0)) < 0) {
|
||||
fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, 2)) < 0) {
|
||||
fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
|
||||
fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
|
||||
if ((err = snd_pcm_prepare(capture_handle)) < 0) {
|
||||
fprintf(stderr, "cannot prepare audio interface for use (%s)\n", snd_strerror(err));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#define BUFFERSIZE 1024
|
||||
short buf[BUFFERSIZE];
|
||||
|
||||
void read_microphone() {
|
||||
int err;
|
||||
short max = SHRT_MIN;
|
||||
|
||||
if ((err = snd_pcm_readi(capture_handle, buf, BUFFERSIZE)) != BUFFERSIZE) {
|
||||
fprintf(stderr, "read from audio interface failed (%s)\n", snd_strerror(err));
|
||||
} else {
|
||||
for (size_t i = 0; i < BUFFERSIZE; ++i) {
|
||||
if (buf[i] > max) {
|
||||
max = buf[i];
|
||||
}
|
||||
}
|
||||
printf("%d, ", max);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
void fft() {
|
||||
// fftw_complex *in, *out;
|
||||
// fftw_plan my_plan;
|
||||
// in = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
|
||||
// out = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
|
||||
// my_plan = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
// fftw_execute(my_plan); /* repeat as needed */
|
||||
// fftw_destroy_plan(my_plan);
|
||||
// fftw_free(in);
|
||||
// fftw_free(out);
|
||||
// return;
|
||||
}
|
||||
|
||||
void close_microphone() { snd_pcm_close(capture_handle); }
|
10
RpiLedBars/res/src/tmp/rpi_microphone.h
Normal file
10
RpiLedBars/res/src/tmp/rpi_microphone.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#if !defined(__RPI_MICROPHONE_H__)
|
||||
#define __RPI_MICROPHONE_H__
|
||||
|
||||
void setup_microphone();
|
||||
|
||||
void read_microphone();
|
||||
|
||||
void close_microphone();
|
||||
|
||||
#endif /* __RPI_MICROPHONE_H__ */
|
15
RpiLedBars/res/src/tmp/rpi_pixleds.h
Normal file
15
RpiLedBars/res/src/tmp/rpi_pixleds.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#if !defined(__RPI_PIXLEDS_H__)
|
||||
#define __RPI_PIXLEDS_H__
|
||||
|
||||
void rgb_txdata(int *rgbs, TXDATA_T *txd);
|
||||
int str_rgb(char *s, int rgbs[][LED_NCHANS], int chan);
|
||||
void swap_bytes(void *data, int len);
|
||||
int hexdig(char c);
|
||||
void map_devices(void);
|
||||
void fail(char *s);
|
||||
void terminate(int sig);
|
||||
void init_smi(int width, int ns, int setup, int hold, int strobe);
|
||||
void setup_smi_dma(MEM_MAP *mp, int nsamp, TXDATA_T *txdata);
|
||||
void start_smi(MEM_MAP *mp);
|
||||
|
||||
#endif // __RPI_PIXLEDS_H__
|
Reference in New Issue
Block a user