#+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)