garblefart said: ive been fantasizing about raytraced audio and its potential brilliance for ages. i imagine youre not going down the realtime route?
Nah, I can’t think of a ray of tracing enough rays fast enough. (I could do it on the GPU, but then it wouldn’t be very portable.) At the moment each impulse response takes between a few seconds to a few minutes to render (depending on the implementation and the complexity of the scene). Although I’m still using dumb linear search for collision testing rather than something clever like octrees or bounding interval hierarchies, so maybe that would help.
Also doing it in realtime requires stuff like an interface for defining paths/movements of microphones and sources, and a custom convolution of some kind, which I don’t really feel up to writing.
Thought I’d try making my audio raytracer spit out some data that I could visualise… Looking OK so far, I think.
I mean, Python is a nice enough language that I’m willing to use it voluntarily, but on the other hand what if my brain turns into a potato?
So having written versions of my raytracing impulse response generator in C++ (twice, one cmd-line and one GUI) and Haskell (also twice because I didn’t know Haskell the first time), I decided that the fifth time would be the charm, and I’m rewriting in Python. The new repo is over here if you’re interested.
Why Python? Mainly because I thought a more interactive language would be conducive to easier debugging and faster mocking-up of ideas. I do miss my compile-time type-checking though.
I managed to get the actual raytracing part up and running in a day. It exports json in the same format as the last Haskell version, so the screenshot above is a comparison of the same scene, rendered in Python (left) and Haskell (right), both flattened with the Haskell flattener. Everything looks pretty good so far, but the files look like they have opposite phase, so there’s obviously a mistake in one of the versions somewhere… (note: the files shouldn’t be identical, because the ray directions used are randomly generated on each run.)
My immediate goals are to try to improve the memory usage and performance (the inner loop is probably going to be written in Cython eventually), and to get some kind of interactive visualiser up and running so that you can actually watch the rays bounce around as they’re rendered out.
So I just managed to install hsndfile on OS X Mavericks and it was a bit complicated so I thought I’d write a thing about it just in case anyone else ever decides that they really need extensive audio format support in Haskell.
- First I installed libsndfile. Downloaded it,
cd'd to the downloaded dir, ran
sudo make install. Install failed looking for Carbon.h.
- I followed the advice here, modifying libsndfile_src/programs/Makefile so that the
CFLAGSdeclaration included the flag
-I/Applications/Xcode.app/Contents /Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk /System/Library/Frameworks/Carbon.framework/Versions/A/Headers/.
- After making this change and re-running sudo make install, the library installed properly (in /usr/local/lib and /usr/local/include).
- Then I ran
cabal install hsndfile. There was an error about needing c2hs.
cabal install c2hs. The c2hs sources were downloaded and built, and the executable installed in ~/Library/Haskell/ghc-7.6.3/lib/c2hs-0.17.2/bin with a symlink in ~/Library/Haskell/bin.
cabal install hsndfilestill failed. Apparently the c2hs install location is non-standard, and after mucking around with my PATH environment variable I ended up creating the directory ~/.cabal/bin and copying the c2hs executable into it, then running
- Now running cabal install hsndfile got a bit further, but c2hs itself failed processing stdio.h. This is a bug with c2hs (more info here), but luckily there’s a workaround:
- As root, I added the following definitions to /usr/local/include/sndfile.h before the #include <stdio.h>:
#define __AVAILABILITY__ #define __OSX_AVAILABLE_STARTING(a,b) #define __OSX_AVAILABLE_BUT_DEPRECATED(a,b,c,d) #define __OSX_AVAILABLE_BUT_DEPRECATED_MSG(a,b,c,d,e)
cabal install hsndfileagain, and everything worked this time. Hooray!
- At this point I felt some emotional turmoil due to the harsh contrast between Haskell’s functional purity and the sheer ugliness of this hacked-together precarious house-of-cards ‘solution’. Maybe I’ll go and write some poetry or drink to forget.
Disclaimer: Reuben does not condone the use of alcohol as an inhibitor for emotions or memories. Reuben also doesn’t drink so the drinking to forget probably won’t happen. He also doesn’t write poetry. He’s basically just a machine that struggles with programming languages and plays that ‘2048’ game far too much.
Anonymous said: cyan sucks
I’m Reuben Thomas of functor.co, this is my submission for the Giphoscope Award 2014.
version420 said: So as long as the function ends with a call (a single, direct call) to some other "local function" then that function will be optimized for tail-recursion?
Tail-recursion is a way of turning recursion into looping by using a single stack frame. The function modifies its parameters in-place and, at the end of the function, either returns an expression of its parameters or starts the function over using the newly-modified parameters.
So the tail-recursive function(define (last list) (if (null? (cdr list)) (car list) (last (cdr list))))
essentially gets compiled into the pseudo-assembly# car at %stackpointer; cdr at %stackpointer enter-last-func: compare null %stackpointer jump-if-equal exit-last-func # copy the next cons into the old parameters copy (%stackpointer) %stackpointer copy (%stackpointer)+4 %stackpointer unconditional-jump enter-last-func exit-last-func: return %stackpointer
which does not invoke any further stack frames.
Functions which cannot normally be tail-call optimized can be rewritten to use an internal tail-recursive function.
(Forgive the bad pseudo-assembly. It’s been a while since I’ve written x86 and I’m pretending that the indirect access syntax is (address).)
I don’t really know that much about Clojure, metalncode, but I do know about tail call optimisation (TCO) in Clojure thanks to Seven Languages in Seven Weeks. (Worth a read btw.)
crypsys' advice about TCO is true for Schemes, which require TCO as part of the standard. (I think ‘local function’ would normally mean any function whose scope completely captures the recursive call. The recursive call must refer to a function that’s already on the stack.)
However, Clojure uses the Java VM which doesn’t tail-call optimise. Instead, you have to use the 'recur' special operator as the last expression in your function definition. ‘Recur’ evaluates its arguments in order, then rebinds in parallel the bindings of the recursion point to the values of its arguments. The recursion point is the immediately enclosing ‘loop’ or ‘fn’ declaration - the loop bindings or parameters will be rebound to the values of the recur’s arguments. There are some examples here.
It’s a shame that this stuff isn’t optimised automatically, but at the same time it’s helpful to have the optimisation stated explicitly in your code. The code shouldn’t compile if the ‘recur’ statement is used incorrectly, so you don’t have to guess about whether the compiler is optimising under the hood or not.
I am 21 in two days but I still can’t really do facial hair and it’s really annoying.
Also annoying: when people say my gifs have looping issues. The only issues they have are floating-point round-off issues and if you can see those then you’re magic probably. More likely: your computer can’t handle a 500x500 gif. Sorry.
I went to the Palais de Tokyo yesterday and took some photos while I wandered around. It was fun!
Today was apparently a national holiday, but no-one told me. I got to work, nobody was there, so I took the opportunity to get lots of work done in peace and quiet. Then I got stuck, sent some emails, and came home.
I’m going home a week today, and I’ll have my first stretch of holiday in six months or so. Normal creative terrible-musican budding-gif-artist Reuben will be resumed shortly (hopefully).