#+TITLE: Minimal libsndfile in ecl lisp #+AUTHOR: screwtape * I better write a little C lisp this week Not to be confused with GNU clisp. I *really* like ECL's sffi. Let's turn #+begin_src sh ffmpeg -f lavfi -i \"sine=frequency=2000:duration=10\" -y beep.wav #+end_src into #+begin_src 3e+08 +------------------------------------------------------------------+ | A A A A+AA AA A A A +A A A A A A A A A +A A A A | | A A A A A A A A A A A A AA A"oAt.AatA A A A | 2e+08 |-A A A A A A A A A A A A A +-| | A AAA AA AA AA A A A | |A A A A A A A A A A A A | | A A A A A A | 1e+08 |-+ A A A A A A A A +-| | A A A A A A A A A A A| | A A AA AA A A A| 0 |-A A A A A A A A A +-| | A A AA AA A A | | A A A A A A A A | -1e+08 |A+ A A A A A A A A A A-| | A A A A A A A | |A A A A A A A A A A A | | A A AA AA AA AAA A | -2e+08 |-+A A A A A A A A A A A A A +A| | A A A A A A A A A A A A A A A A A A A A A| |A A A A A A A A A +A A A A A+ A AA A A A+ A A A A | -3e+08 +------------------------------------------------------------------+ 0 50 100 150 200 250 #+end_src * First, let's (re)do it #+begin_src sh pkg_add ecl libsndfile gnuplot ffmpeg #+end_src Reimagine that for your package manager. I'm also assuming nc is openbsd-netcat. Let's grab source using our nc gopher browser #+begin_src sh printf "/users/screwtape/common-lisp/lcwav.lisp\n" | nc sdf.org 70 > lcwav.lisp printf "/users/screwtape/common-lisp/lcwav-make.lisp\n" | nc sdf.org 70 > make.lisp printf "/users/screwtape/common-lisp/lcwav-try.lisp\n" | nc sdf.org 70 > try.lisp #+end_src And since we both trust me, let's just make an ffmpeg beep, mark try.lisp executable and run it. (For reasons I cannot fathom, in my test run carriage returns show up at the end of try.lisp lines. You might need to remove them manually (?!).) #+begin_src sh chmod +x try.lisp ##ooh, I hope your /usr/local/bin/ecl is #!/usr/local/bin/ecl ffmpeg -f lavfi -i "sine=frequency=2000:duration=10" -y beep.wav ./try.lisp #+end_src Now let's check what was in those lisp files. * cat try.lisp #+begin_src lisp #!/usr/local/bin/ecl --load (ext:system "ecl --load make.lisp") (ext:system "./lcwav > out.dat") (ext:system "gnuplot -e 'set term dumb; plot \"out.dat\"' | tee graph.txt") (si:quit) #+end_src Shebang, three system(3) calls and quit, great. Clearly - make.lisp - makes - lcwav - Is the maked executably linked C binary - That we are dumping into out.dat - A gnuplot dumb terminal plot The make is going to be short so let's look there first. We need to 'install' our c compiler as C can't happen on the fly like ECL's internal byte-compilation. No big deal. #+begin_src lisp (ext:install-c-compiler) (setf c:*USER-LD-FLAGS* "-lsndfile") (compile-file "lcwav.lisp" :system-p t) (c:build-program "lcwav" :lisp-files '("lcwav.o")) (si:quit) #+end_src I mean, I could just read what the lines say out to you. - I do have an app for that. We have to get the REPL to install C compilation into itself - (No big deal; may have already been done on startup) Let the $CC know we're linking libsndfile Compile the c-containing lcwav.lisp into an object file Build an executably linked program from that object. * But what does sffi C inside ECL actually look like? Basically non-interactive stuff goes in ffi:clines as a string of C source, And interactive stuff goes in (ffi:c-inline (args) (arg-types) (returns) "C source"). Args, multiple returns and callbacks are important but this is basically it. You'll notice that I'm explicitly handling some memory that libsndfile is taking care of (sf_open() and sf_close()) ECL can also get the Boehm garbage collector it's using to watch memory for you, but that's not relevant here. (I just jammed this code asap but it is fine except for me forgetting what exactly libsndfile wants to give me making the graph weirdly scaled). #+begin_src lisp (defpackage lcwav (:use cl cl-user)) (in-package lcwav) ;;; Headers & define for quick libsndfile example (ffi:clines " #include <inttypes.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sndfile.h> #define length 1000 ") ;;; sndfile example variables (ffi:clines " SNDFILE *file; SF_INFO info; sf_count_t count, items; short *result[length]; const char *filename; int err, i; ") (defun test-read () " (test-read) A minimal C program to read shorts from a wav and print them to standard out inside of (ffi:c-inline () () nil \"..\") " (ffi:c-inline () () nil " filename = \"beep.wav\"; memset(&info, 0, sizeof(info)); file = sf_open(filename, SFM_READ, &info); if (file==NULL) { printf(\"Failed to open file\n\"); exit(1); } err = sf_error(file); if (err != SF_ERR_NO_ERROR) { printf(\"File opened with error\n\"); exit(1); } items = length; count = sf_read_short(file, result, items); if (count != items) { printf(\"sf_read returned wrong count.\"); exit(1); } if ( 0!=sf_close(file)) { printf(\"Failed to close file\"); exit(1); } for (i=0; i<length/4; i++) printf(\"%d\n\", result[i]); return; ")) ;;;Call (test-read) and quit. (test-read) (si:quit) #+end_src * By the way If you want some libre software to start to exist, especially by working on it with me please email me at screwtape@sdf.org. (You could also donate to me so my boxen don't become homeless again)