#+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 [email protected].
 (You could also donate to me so my boxen don't become homeless again)