Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raw output not going STDOUT? #123

Closed
DejitaruJin opened this issue Dec 22, 2016 · 18 comments
Closed

Raw output not going STDOUT? #123

DejitaruJin opened this issue Dec 22, 2016 · 18 comments

Comments

@DejitaruJin
Copy link

I have the latest version of CAVA, set to raw output method. And indeed, if the nonsense in the terminal is to be believed, it is spitting out 8-bit raw data. (The way they all seem to be boxes is kind of odd, but I can't verify what I can't parse.)

The problem is, while it's printing in the terminal, it is not technically STDOUT - it can not be piped nor redirected. I've tried piping it to a Python script, dd, and hexdump, and even tried redirecting it (along with STDERR) to /dev/null... but those boxes just keep printing! Testing in ASCII mode, it also produces unconsumable output. Myself and my coworkers can't even figure out how it's doing that, nothing should be that resistant to redirection.

Looking at the code, it seems almost like it's trying to output to a file, /dev/stdout? Which technically is file descriptor 1 for the current process (that being the Bash window), but it can't be piped, so it's a ways away from actual "STDOUT".

Currently running on a Raspberry Pi B+, all updates applied. Got so frustrated with this I reformatted, so all's clean.

@karlstav
Copy link
Owner

@DejitaruJin first of all I'm sorry for causing you so much frustration with my program 😟

You probably know more about the STDOUT and redirecting then I do, the raw output module is actually not really tested that well. It was kind of a feature request and when I had it ready for testing the people that came with the request went sort of quiet.

You are right about cava printing to the file /dev/stdout, which might be why it can't be redirected in any ordinary sense.

If you do want to output it to a different file you can always specify a different file in the config:

# raw output target defaults to stdout, a fifo will be created if target does not exist
; raw_target = /dev/stdout

I'm sure if you create a fifo for output, you can pipe that fifo into whatever.

@DejitaruJin
Copy link
Author

The issue with using a file is the way some programs or languages treat it. For example, a Python script (useful for the LED strips people like to use this for) will gobble up the file's current contents, reach the end, and then say "Okay, done!" Which you'd swear is some kind of bug when dealing with a FIFO file, but oh no, Python is "supposed to do that." And then I died a little inside :P

But when dealing with redirectable STDIO, things are generally geared toward streaming; a program will usually just block until it has input to consume, and a lot of the time the source will block until it has something to consume the output.

I'll mess around with a few work-arounds, and may even tinker with the CAVA code; it is written very clearly, and as best I can tell the actual raw output is working fine beside that.

@karlstav
Copy link
Owner

ok cool, I think it's possbile to rewrite it so that it uses a normal printf when writing to STDOUT. As a work around maybe you can use a FIFO and then cat the fifo to STDOUT?

@MWWorks
Copy link

MWWorks commented Jan 30, 2017

Many thanks for adding the raw output - I will soon be doing something similar to DejitaruJin by way of using Cava to drive LED strips. Quick question - is the output range of raw (either binary or ascii) a linear bar height, or a logarithmic audio intensity? I ask so as to use the right variation of the driver chip.

@karlstav
Copy link
Owner

Not sure if I understand the question, but the raw output values are the same values that are normally used to draw the bar height in the "spectrum mode". Just linearly scaled up or down to whatever range specified in the config file.

Does that answer your question?

@MWWorks
Copy link

MWWorks commented Jan 30, 2017

It does, thank you for the reply :)

To add more context - I plan to use a driver chip that takes an input voltage - up to a certain reference/maximum - and convert that into how many bars are lit. There are a couple of versions of the chip that differ in how they map the input voltage to bars - one does it linearly, ie 0.5 the voltage lights up half the bars. The other responds on a log scale, I think making the bottom end more sensitive to provide more useful feedback.

If I were feeding the audio level straight into the chip, I'd want the log version; but I'm figuring that Cava is already doing whatever log conversion is needed to convert the audio level into a more responsive bar - so the raw output value is a linear value of the bar height. Solved! Many thanks :)

@karlstav
Copy link
Owner

cool, good luck with your project!

Don't hesitate to raise an issue if you find anything strange. I don't know how well tested the raw output module actually is.

@worron
Copy link
Contributor

worron commented Feb 4, 2017

I want to speak in defense of binary output as it is for today. Seems like using FIFO pretty flexible way for third party extensions. Here is my simple GUI app built over CAVA and I would personally prefer that the output way remained unchanged.

By the way @karlstav thanks a lot for your work.

@gtoal
Copy link

gtoal commented May 7, 2017

this workaround works to redirect the /dev/stdout output.

this captures 1 second worth of data:

              unbuffer timeout 1 cava > CAVA-SAMPLES.txt

(unbuffer is in the 'expect' package. Or on some systems expect-dev)

@chrisg-waymark
Copy link

Trying to capture the output here is killing me, 2 days I've been on. Pretty noobish with python I admit, but what is going on?

@worron
Copy link
Contributor

worron commented Jun 5, 2017

@chrisg-waymark
Here is code sample I made

#!/usr/bin/python3
import struct

"""
cava config for testing

[general]
bars = 30

[output]
method = raw
raw_target = /tmp/cava.fifo
bit_format = 16bit
"""

# this is data  from cava config
RAW_TARGERT = "/tmp/cava.fifo"
BARS_NUMBER = 30
OUTPUT_BIT_FORMAT = "16bit"

bytetype, bytesize, bytenorm = ("H", 2, 65535) if OUTPUT_BIT_FORMAT == "16bit" else ("B", 1, 255)


def run():
    fifo = open(RAW_TARGERT, "rb")
    chunk = bytesize * BARS_NUMBER
    fmt = bytetype * BARS_NUMBER
    while True:
        data = fifo.read(chunk)
        if len(data) < chunk:
            break
        sample = [i / bytenorm for i in struct.unpack(fmt, data)]
        print(sample)
    fifo.close()

if __name__ == "__main__":
    run()

@chrisg-waymark
Copy link

chrisg-waymark commented Jun 6, 2017

Thanks Worron, I managed to change the output from Cava to printf statements like Karl mentioned, then I can pickup the stdout straight away.

I'll have a look at this though as it might solve some of the lag I'm experiencing. I'm currently just outputting Ascii to 256, and I'm also voice controlling the LEDs and this introduces bad lag. I'm killing and restarting the process but its only a temporary fix. I think tonight I'll try and read through the whole buffer doing nothing until I get to the last line, which should allow it to catch up!

Much appreciated code sample though, hopefully this helps others to work more easily with CAVA.

FYI : I'm running on a raspberry pi 3, and framerate = 30 seems very unresponsive. I've tested and it seems 120-240 is the best option. I didn't want to use the FIFO option as I wasn't sure if it used a physical file, and I dont want to excessively write to the SD card

@worron
Copy link
Contributor

worron commented Jun 6, 2017

Sorry, seems like i messed up with file extension in description. Anyway the only important thing is using tmpfs, not regular disk. And set bit_format = 8bit may be good idea for performance.

karlstav added a commit that referenced this issue Jun 6, 2017
turns out the raw target was never opened if the target was /dev/stdout
the file descriptor was opened within an if (target != /dev/stdout) statement
@karlstav
Copy link
Owner

karlstav commented Jun 6, 2017

@worron do you want to test the redirect /dev/stdout output now? With the latest commit, without the work around?

It turns out that the whole "opening of the file descriptor" was within an if (target != "/dev/stdout") statement. So if the target was stdout the whole output was simply written to an uninitialized file descriptor.

@worron
Copy link
Contributor

worron commented Jun 12, 2017

@karlstav, sorry for delay I was absent for a while.

Not sure what "work around" was mentioned, but I check last commit and can confirm that now raw binary output works ok with /dev/stdout and user fifo both.

May be interesting for someone - dirty example how to grab cava output data in python:

#!/usr/bin/python3
import os
import struct
import subprocess
import tempfile

BARS_NUMBER = 30
# OUTPUT_BIT_FORMAT = "8bit"
OUTPUT_BIT_FORMAT = "16bit"
# RAW_TARGET = "/tmp/cava.fifo"
RAW_TARGET = "/dev/stdout"

conpat = """
[general]
bars = %d
[output]
method = raw
raw_target = %s
bit_format = %s
"""

config = conpat % (BARS_NUMBER, RAW_TARGET, OUTPUT_BIT_FORMAT)
bytetype, bytesize, bytenorm = ("H", 2, 65535) if OUTPUT_BIT_FORMAT == "16bit" else ("B", 1, 255)


def run():
    with tempfile.NamedTemporaryFile() as config_file:
        config_file.write(config.encode())
        config_file.flush()

        process = subprocess.Popen(["cava", "-p", config_file.name], stdout=subprocess.PIPE)
        chunk = bytesize * BARS_NUMBER
        fmt = bytetype * BARS_NUMBER

        if RAW_TARGET != "/dev/stdout":
            if not os.path.exists(RAW_TARGET):
                os.mkfifo(RAW_TARGET)
            source = open(RAW_TARGET, "rb")
        else:
            source = process.stdout

        while True:
            data = source.read(chunk)
            if len(data) < chunk:
                break
            # sample = [i for i in struct.unpack(fmt, data)]  # raw values without norming
            sample = [i / bytenorm for i in struct.unpack(fmt, data)]
            print(sample)

if __name__ == "__main__":
    run()

@karlstav
Copy link
Owner

this workaround was necessary before to make redirecting work:

unbuffer timeout 1 cava > CAVA-SAMPLES.txt

it should not be necessary anymore. I will close this issue.

@chrisdruta
Copy link
Contributor

@worron just wanted to say thanks for the code, integrated/works perfectly with my controller. Gonna put in a pr adding a link for others to find this more easily.

@LeoDog896
Copy link

I just made a deno library for bindings to cava too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants