changeset 690:84811c6dd32d

Added Auval with removed Lua dependancy, using dmeval only.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 16 Apr 2013 16:54:42 +0300
parents 686c23d69aa2
children e7663dd15eea
files Makefile.gen tools/auval.c
diffstat 2 files changed, 1160 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.gen	Tue Apr 16 16:38:25 2013 +0300
+++ b/Makefile.gen	Tue Apr 16 16:54:42 2013 +0300
@@ -122,6 +122,12 @@
 DM_CFLAGS += -DDM_GFX_TTF_TEXT
 DMLIB_OBJS += dmtext_ttf.o
 
+ifeq ($(DM_BUILD_TOOLS),yes)
+ifeq ($(DM_GFX_BLITS),yes)
+TOOL_BINARIES += auval
+endif
+endif
+
 ifeq ($(DM_BUILD_TESTS),yes)
 ifeq ($(DM_GFX_BLITS),yes)
 ifeq ($(DM_USE_STDIO),yes)
@@ -468,6 +474,10 @@
 	@echo " LINK $+"
 	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) -lm
 
+$(BINPATH)auval$(EXEEXT): $(OBJPATH)auval.o $(OBJPATH)dmeval.o $(DMLIB_A)
+	@echo " LINK $+"
+	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(SDL_LDFLAGS) -lSDL_ttf
+
 
 ###
 ### Demo binary
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/auval.c	Tue Apr 16 16:54:42 2013 +0300
@@ -0,0 +1,1150 @@
+#include "dmlib.h"
+#include "dmargs.h"
+#include "dmeval.h"
+#include "dmtext.h"
+#include <math.h>
+
+#define AUVAL_NAME           "AuVal"
+#define AUVAL_VERSION        "0.6"
+#define AUVAL_TMPBUF_SIZE    (4096)
+#define AUVAL_HISTORY_FILE   "history.txt"
+#define AUVAL_HISTORY_USER   "formulas.txt"
+#define SDL_NCOLORS           256
+
+
+enum
+{
+    REDRAW_VISUALIZER = 0x00000001,
+    REDRAW_EDITOR     = 0x00000002,
+    REDRAW_INFO       = 0x00000004,
+    REDRAW_ALL        = 0xffffffff,
+};
+
+
+typedef struct
+{
+    DMEvalNode *expr;
+    DMEvalContext *engine;
+
+    char *errStr;
+    int err;
+    
+    double varTime, varFreq, varKeyTime,
+           varKeyTimeRev, varKeyFreq, varUnit;
+
+    int avail, bufsize, pos, oldpos;
+    Uint8 *buf;
+
+    SDL_mutex *mutex;
+} AUAudioData;
+
+
+char *optFontFile = "font.ttf";
+
+int optVFlags      = SDL_SWSURFACE | SDL_HWPALETTE,
+    optScrWidth    = 640,
+    optScrHeight   = 480,
+    optScrDepth    = 32,
+    optFontSize    = 20,
+    optAudioFreq   = 44100,
+    optBMPSize     = 32,
+    optHistoryLen  = 64;
+
+BOOL optClipping = TRUE,
+     optScale = TRUE,
+     optAutoReturn = TRUE;
+
+
+DMOptArg optList[] = {
+    { 0, '?', "help",       "Show this help", OPT_NONE },
+    { 2, 'v', "verbose",    "Be more verbose", OPT_NONE },
+    { 3,   0, "fs",         "Fullscreen", OPT_NONE },
+    { 5, 's', "size",       "Initial window size/resolution -s 640x480", OPT_ARGREQ },
+    { 6, 'd', "depth",      "Color depth of mode/window in bits (8/15/16/32)", OPT_ARGREQ },
+    { 7, 'f', "freq",       "Audio output frequency", OPT_ARGREQ },
+    { 8, 'b', "bmpsize",    "Bitmap size", OPT_ARGREQ },
+
+    {10, 'n', "noclip",     "Disable clipping by default", OPT_NONE },
+    {11, 'r', "range",      "Use range [0, 255] instead of [0.0, 1.0]", OPT_NONE },
+};
+
+const int optListN = sizeof(optList) / sizeof(optList[0]);
+
+
+void argShowHelp()
+{
+    printf("%s v%s\n(C) Copyright 2011 ccr/TNSP\n", AUVAL_NAME, AUVAL_VERSION);
+    dmArgsPrintHelp(stdout, optList, optListN);
+}
+
+
+BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
+{
+    switch (optN) {
+    case 0:
+        argShowHelp();
+        exit(0);
+        break;
+
+    case 2:
+        dmVerbosity++;
+        break;
+    
+    case 3:
+        optVFlags |= SDL_FULLSCREEN;
+        break;
+
+    case 5:
+        if (optArg)
+        {
+            int w, h;
+            if (sscanf(optArg, "%dx%d", &w, &h) == 2)
+            {
+                if (w < 320 || h < 200 || w > 3200 || h > 3200)
+                {
+                    dmError("Invalid width or height: %d x %d\n", w, h);
+                    return FALSE;
+                }
+                optScrWidth = w;
+                optScrHeight = h;
+            }
+            else 
+            {
+                dmError("Invalid size argument '%s'.\n", optArg);
+                return FALSE;
+            }
+        }
+        else
+        {
+            dmError("Dimensions option %s requires an argument.\n", currArg);
+        }
+        break;
+
+    case 6:
+        optScrDepth = atoi(optArg);
+        break;
+
+    case 7:
+        {
+            int tmp = atoi(optArg);
+            if (tmp < 4000 || tmp > 96000)
+            {
+                dmError("Invalid audio frequency '%s'.\n", optArg);
+                return FALSE;
+            }
+            optAudioFreq = tmp;
+        }
+        break;
+
+    case 8:
+        {
+            int tmp = atoi(optArg);
+            if (tmp < 32 || tmp > 512)
+            {
+                dmError("Invalid bitmap size '%s'.\n", optArg);
+                return FALSE;
+            }
+            optBMPSize = tmp;
+        }
+        break;
+
+    case 10:
+        optClipping = FALSE;
+        break;
+    
+    case 11:
+        optScale = FALSE;
+        break;
+
+    default:
+        dmError("Unknown option '%s'.\n", currArg);
+        return FALSE;
+    }
+    
+    return TRUE;
+}
+
+
+typedef struct
+{
+    ssize_t pos, len, size;
+    char *data;
+    BOOL dirty;
+} AUEditBuf;
+
+
+int au_editbuf_write(AUEditBuf *buf, ssize_t pos, int ch)
+{
+    if (buf->len+1 >= buf->size) return -3;
+    
+    if (pos < 0)
+        return -1;
+    else if (pos >= buf->len) {
+        buf->data[buf->len++] = ch;
+    } else {
+        buf->data[pos] = ch;
+    }
+    buf->dirty = TRUE;
+    return 0;
+}
+
+
+int au_editbuf_insert(AUEditBuf *buf, ssize_t pos, int ch)
+{
+    if (buf->len+1 >= buf->size) return -3;
+    
+    if (pos < 0)
+        return -1;
+    else if (pos >= buf->len) {
+        buf->data[buf->len] = ch;
+    } else {
+        memmove(&(buf->data[pos+1]), &(buf->data[pos]), buf->len - pos + 1);
+        buf->data[pos] = ch;
+    }
+    buf->len++;
+    buf->dirty = TRUE;
+    return 0;
+}
+
+
+int au_editbuf_delete(AUEditBuf *buf, ssize_t pos)
+{
+    if (pos < 0)
+        return -1;
+    else if (pos < buf->len) {
+        memmove(&(buf->data[pos]), &(buf->data[pos+1]), buf->len - pos);
+        buf->len--;
+        buf->dirty = TRUE;
+        return 0;
+    } else
+        return -2;
+}
+
+
+void au_editbuf_clear(AUEditBuf *buf)
+{
+    buf->len = 0;
+    buf->pos = 0;
+    buf->dirty = TRUE;
+}
+
+
+AUEditBuf * au_editbuf_new(ssize_t n)
+{
+    AUEditBuf *res = dmCalloc(1, sizeof(AUEditBuf));
+    
+    res->data = (char *) dmMalloc(n);
+    res->size = n;
+    res->dirty = TRUE;
+    
+    return res;
+}
+
+AUEditBuf * au_editbuf_new_str(ssize_t n, const char *str)
+{
+    AUEditBuf *res = au_editbuf_new(n);
+    strncpy(res->data, str, res->size);
+    res->data[res->size - 1] = 0;
+    res->pos = res->len = strlen(res->data);
+    return res;
+}
+
+
+void au_editbuf_free(AUEditBuf *buf)
+{
+    if (buf != NULL)
+    {
+        dmFree(buf->data);
+        dmFree(buf);
+    }
+}
+
+
+AUEditBuf * au_editbuf_copy(AUEditBuf *src)
+{
+    AUEditBuf *res;
+    
+    if (src == NULL) return NULL;
+    
+    if ((res = au_editbuf_new(src->size)) == NULL)
+        return NULL;
+    
+    memcpy(res->data, src->data, src->size);
+    res->pos = res->len = src->len;
+    res->dirty = TRUE;
+    
+    return res;
+}
+
+
+char * au_editbuf_get_string(AUEditBuf *buf, ssize_t start, ssize_t end)
+{
+    char *str;
+    ssize_t siz;
+    
+    if (buf == NULL)
+        return NULL;
+
+    if (start < 0 || end > buf->len || start >= buf->len)
+        return NULL;
+
+    if (end < 0) {
+        siz = buf->len - start + 1;
+    } else if (start <= end) {
+        siz = end - start + 1;
+    } else
+        return NULL;
+
+    if ((str = dmMalloc(siz + 1)) == NULL)
+        return NULL;
+
+    memcpy(str, buf->data + start, siz);
+    str[siz] = 0;
+
+    return str;
+}
+
+
+void au_editbuf_setpos(AUEditBuf *buf, ssize_t pos)
+{
+    if (pos < 0)
+        buf->pos = 0;
+    else if (pos >= buf->len)
+        buf->pos = buf->len;
+    else
+        buf->pos = pos;
+}
+
+
+BOOL au_init_video(SDL_Surface **screen)
+{
+    *screen = SDL_SetVideoMode(optScrWidth, optScrHeight, optScrDepth, optVFlags | SDL_RESIZABLE);
+    if (*screen == NULL)
+    {
+        dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+void au_draw_editbuf(SDL_Surface *screen, TTF_Font *font, SDL_Color col,
+    int xc, int yc, int w, int h, AUEditBuf *buf, int curcol)
+{
+    ssize_t left, maxlen, pos;
+    int strw, strh, y1 = yc + h;
+    char *line = NULL, *ptr;
+
+    if (buf == NULL)
+        return;
+
+    if (TTF_SizeText(font, "X", &strw, &strh) != 0)
+        goto error;
+
+    maxlen = w / strw;
+    line = dmMalloc(maxlen + 2);
+    pos = 0;
+    left = buf->len;
+    ptr = buf->data;
+
+    do
+    {
+        ssize_t ppos = buf->pos - pos;
+        if (ppos >= 0 && ppos < maxlen)
+        {
+            int x0 = xc + ppos * strw;
+            dmFillRect(screen, x0, yc, x0 + strw, yc + strh, curcol);
+        }
+        else
+        if ((buf->pos % maxlen) == 0)
+        {
+            dmFillRect(screen, xc, yc + strh, xc + strw, yc + strh + strh, curcol);
+        }
+
+        ssize_t len = left > maxlen ? maxlen : left;
+        strncpy(line, ptr, len);
+        line[len] = 0;
+
+        dmDrawTTFTextConst(screen, font, col, xc, yc, line);
+
+        left -= len;
+        ptr += len;
+        pos += len;
+
+        yc += strh;
+    }
+    while (left > 0 && yc < y1);
+
+
+error:
+    dmFree(line);
+}
+
+
+void au_vis_wave(SDL_Surface *screen, int x0, int y0, int x1, int y1, AUAudioData *data)
+{
+    int x, offs;
+    const int height = y1 - y0, center = y0 + (height / 2);
+    const int pitch = screen->pitch, bpp = screen->format->BytesPerPixel;
+    const int xend = x1 * bpp;
+    const float scale = 128.0f / (float) height;
+
+    SDL_LockMutex(data->mutex);
+
+    Uint8 *dp = data->buf,
+          *sp = screen->pixels;
+
+    for (offs = 0, x = x0 * bpp; x < xend && offs < data->avail; x += bpp, offs++)
+    {
+        const int curr = center + ((dp[offs] - 128) * scale);
+        sp[x + curr * pitch] = 255;
+    }
+
+    SDL_UnlockMutex(data->mutex);
+}
+
+
+void au_vis_image(SDL_Surface *screen, AUAudioData *data)
+{
+    int y;
+    Uint8 *p = (Uint8 *) screen->pixels;
+
+    SDL_LockMutex(data->mutex);
+
+    Uint8 *dp = data->buf;
+
+    for (y = 0; y < screen->h; y++)
+    {
+        int x;
+        for (x = 0; x < screen->w; x++)
+            *p++ = *dp++;
+
+        p += screen->pitch - screen->w;
+    }
+
+    SDL_UnlockMutex(data->mutex);
+}
+
+
+int au_get_note_from_key(SDL_keysym *key, int octave)
+{
+    int note;
+
+    switch (key->sym)
+    {
+        case SDLK_z: note = 1; break;
+        case SDLK_s: note = 2; break;
+        case SDLK_x: note = 3; break;
+        case SDLK_d: note = 4; break;
+        case SDLK_c: note = 5; break;
+        case SDLK_v: note = 6; break;
+        case SDLK_g: note = 7; break;
+        case SDLK_b: note = 8; break;
+        case SDLK_h: note = 9; break;
+        case SDLK_n: note = 10; break;
+        case SDLK_j: note = 11; break;
+        case SDLK_m: note = 12; break;
+
+        case SDLK_q: note = 13; break;
+        case SDLK_2: note = 14; break;
+        case SDLK_w: note = 15; break;
+        case SDLK_3: note = 16; break;
+        case SDLK_e: note = 17; break;
+        case SDLK_r: note = 18; break;
+        case SDLK_5: note = 19; break;
+        case SDLK_t: note = 20; break;
+        case SDLK_6: note = 21; break;
+        case SDLK_y: note = 22; break;
+        case SDLK_7: note = 23; break;
+        case SDLK_u: note = 24; break;
+        case SDLK_i: note = 25; break;
+        case SDLK_9: note = 26; break;
+        case SDLK_o: note = 27; break;
+        case SDLK_0: note = 28; break;
+        case SDLK_p: note = 29; break;
+
+        default: return -1;
+    }
+
+    note += 12 * octave;
+    return (note < 1) ? 1 : (note > 120 ? 120 : note);
+}
+
+
+void au_adjust_value(int *val, int min, int max, int delta)
+{
+    *val += delta;
+    if (*val < min) *val = min;
+    else if (*val > max) *val = max;
+}
+
+
+int au_read_history(const char *filename, AUEditBuf **history, int maxHistory, int *histMax)
+{
+    char tmpStr[1024];
+    int i;
+
+    FILE *f = fopen(filename, "r");
+    if (f == NULL)
+    {
+        dmError("Could not open input file '%s'.\n", filename);
+        return -1;
+    }
+
+    while (fgets(tmpStr, sizeof(tmpStr), f) != NULL)
+    {
+        /* Strip the string end from linefeeds etc. */
+        for (i = 0; tmpStr[i] != 0 && i < 512; i++);
+        while (--i >= 0 && (tmpStr[i] == '\n' || tmpStr[i] == '\r' || isspace(tmpStr[i])))
+            tmpStr[i] = 0;
+        
+        /* Add to history only if it's not an empty line */
+        if (tmpStr[0] != 0)
+        {
+            au_editbuf_free(history[maxHistory + 1]);
+            history[maxHistory + 1] = NULL;
+            memmove(&history[2], &history[1], maxHistory * sizeof(history[0]));
+
+            history[1] = au_editbuf_new_str(AUVAL_TMPBUF_SIZE, tmpStr);
+
+            if (*histMax < maxHistory)
+                (*histMax)++;
+        }
+    }
+
+    fclose(f);
+    return 0;
+}
+
+
+int au_save_history(const char *filename, AUEditBuf **history, int histMax)
+{
+    int i;
+
+    FILE *f = fopen(filename, "w");
+    if (f == NULL)
+    {
+        dmError("Could not create output file '%s'.\n", filename);
+        return -1;
+    }
+
+    for (i = histMax; i >= 0; i--)
+    {
+        AUEditBuf *buf = history[i];
+        if (buf != NULL)
+        {
+            buf->data[buf->len] = 0;
+            fprintf(f, "%s\n", buf->data);
+        }
+    }
+
+    fclose(f);
+    return 0;
+}
+
+
+/* SDL audio callback. We do the actual rendering here, by setting
+ * Lua variables and calling Lua evaluation function to generate samples.
+ *
+ * Mutex locking is used to ensure that the variables are not improperly
+ * accessed.
+ */
+void au_sdl_audio_fill(void *udata, Uint8 *buf, int len)
+{
+    AUAudioData *data = (AUAudioData *) udata;
+
+    SDL_LockMutex(data->mutex);
+    
+    data->avail = 0;
+
+    if (data->err == 0)
+    {
+        while (data->avail < len && data->avail < data->bufsize)
+        {
+            double value;
+
+            data->varKeyTimeRev = 1.0f - data->varKeyTime;
+
+            if (dm_eval_exec(data->engine, data->expr, &value) != 0)
+                break;
+
+            if (optScale)
+            {
+                if (optClipping)
+                    value = ((value < -1.0f) ? -1.0f : ((value > 1.0f) ? 1.0f : value));
+                
+                value = 128 + (value * 126);
+            }
+            else
+            {
+                if (optClipping)
+                    value = ((value < 0) ? 0 : ((value > 255) ? 255 : value));
+            }
+
+            data->buf[data->avail++] = value;
+
+            data->varTime += data->varFreq;
+            data->varUnit += data->varKeyFreq;
+
+            data->varKeyTime += data->varKeyFreq;
+            if (data->varKeyTime > 1.0f)
+                data->varKeyTime = 1.0f;
+        }
+        
+        if (data->avail >= len && data->err == 0)
+        {
+            memcpy(buf, data->buf, len);
+            data->pos += len;
+        }
+        else
+            memset(buf, 0, len);
+    }
+        
+    SDL_UnlockMutex(data->mutex);
+}
+
+
+int main(int argc, char *argv[])
+{
+    AUEditBuf *editBuf = au_editbuf_new(AUVAL_TMPBUF_SIZE);
+    AUEditBuf **histBuf = NULL;
+    int histPos = 0, histMax = 0, viewMode;
+    SDL_Surface *screen = NULL, *bmp = NULL;
+    TTF_Font *font = NULL;
+    SDL_Color fontcol={255,255,255, 0};
+    SDL_Event event;
+    SDL_AudioSpec fmt;
+    AUAudioData audata;
+    int needRedraw;
+    BOOL initSDL = FALSE, initTTF = FALSE, exitFlag,
+         insertMode, audioPlaying = FALSE, jazzMode;
+
+    memset(&audata, 0, sizeof(audata));
+
+    /* Parse arguments */
+    if (!dmArgsProcess(argc, argv, optList, optListN,
+        argHandleOpt, NULL, FALSE))
+        exit(1);
+
+
+    /* Allocate edit history buffers */
+    histBuf = dmCalloc(sizeof(histBuf[0]), optHistoryLen + 2);
+    if (histBuf == NULL)
+    {
+        dmError("Could not allocate memory for edit history buffer!\n");
+        goto error_exit;
+    }
+
+
+    /* Read in history file, if any exists */
+    au_read_history(AUVAL_HISTORY_FILE, histBuf, optHistoryLen, &histMax);
+
+
+    /* Initialize evaluator engine */
+    audata.engine = dm_eval_new();
+    audata.expr = NULL;
+    
+    dm_eval_add_var(audata.engine, "t", &audata.varTime);
+    dm_eval_add_var(audata.engine, "k", &audata.varKeyTime);
+    dm_eval_add_var(audata.engine, "q", &audata.varKeyTimeRev);
+    dm_eval_add_var(audata.engine, "f", &audata.varUnit);
+
+
+    /* Initialize SDL */
+    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) != 0)
+    {
+        dmError("Could not initialize SDL: %s\n", SDL_GetError());
+        goto error_exit;
+    }
+    initSDL = TRUE;
+
+    if (TTF_Init() < 0)
+    {
+        dmError("Could not initialize FreeType/TTF: %s\n", SDL_GetError());
+        goto error_exit;
+    }
+    initTTF = TRUE;
+
+    font = TTF_OpenFont(optFontFile, optFontSize);
+    if (font == NULL)
+    {
+        dmError("Could not load TTF font '%s' (%d): %s\n",
+            optFontFile, optFontSize, SDL_GetError());
+        goto error_exit;
+    }
+    TTF_SetFontStyle(font, TTF_STYLE_NORMAL);
+    
+    if (!au_init_video(&screen))
+        goto error_exit;
+
+    SDL_WM_SetCaption(AUVAL_NAME " v" AUVAL_VERSION, AUVAL_NAME);
+
+    SDL_EnableUNICODE(1);
+    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
+
+
+    /* Initialize audio */
+    fmt.freq = optAudioFreq;
+    fmt.format = AUDIO_U8;
+    fmt.channels = 1;
+    fmt.samples = 512;
+    fmt.callback = au_sdl_audio_fill;
+    fmt.userdata = &audata;
+
+    if (SDL_OpenAudio(&fmt, NULL) < 0)
+    {
+        dmError("Could not initialize SDL audio.\n");
+        goto error_exit;
+    }
+
+    audata.bufsize = optAudioFreq;
+    audata.buf = dmMalloc(audata.bufsize * sizeof(*audata.buf));
+    audata.mutex = SDL_CreateMutex();
+    audata.varTime = 0;
+    audata.varKeyFreq = 1.0f / (double) optAudioFreq;
+
+    SDL_PauseAudio(0);
+    SDL_PauseAudio(1);
+    
+
+    /* Create visualizer bitmap surface */
+    bmp = SDL_CreateRGBSurface(SDL_SWSURFACE, optBMPSize, optBMPSize, 8, 0, 0, 0, 0);
+    SDL_Color pal[SDL_NCOLORS];
+    int n;
+    for (n = 0; n < SDL_NCOLORS; n++)
+    {
+        pal[n].r = n;
+        pal[n].g = n;
+        pal[n].b = n;
+    }
+    SDL_SetColors(bmp, pal, 0, SDL_NCOLORS);
+
+    /* Misc inits */
+    au_editbuf_clear(editBuf);
+    needRedraw = REDRAW_ALL;
+    exitFlag = FALSE;
+    insertMode = TRUE;
+    jazzMode = FALSE;
+    viewMode = 0;
+
+    /* Enter mainloop */
+    while (!exitFlag)
+    {
+        /* Handle events */
+        while (SDL_PollEvent(&event))
+        switch (event.type)
+        {
+            case SDL_KEYDOWN:
+                {
+                /* Get key event modifiers into handy booleans */
+                BOOL modCtrl  = event.key.keysym.mod & KMOD_CTRL,
+                     modShift = event.key.keysym.mod & KMOD_SHIFT;
+
+                switch (event.key.keysym.sym)
+                {
+                    case SDLK_F1:
+                        audioPlaying = !audioPlaying;
+                        SDL_PauseAudio(!audioPlaying);
+                        needRedraw |= REDRAW_ALL;
+                        break;
+
+                    case SDLK_F2:
+                        audata.varTime = 0;
+                        break;
+
+                    case SDLK_F4:
+                        optClipping = !optClipping;
+                        needRedraw |= REDRAW_VISUALIZER;
+                        break;
+
+                    case SDLK_F5:
+                        /* Toggle audio scaling / range between [0.0, 1.0] and [0, 255] */
+                        optScale = !optScale;
+                        needRedraw |= REDRAW_VISUALIZER;
+                        break;
+
+                    
+                    case SDLK_F6:
+                        viewMode = (viewMode + 1) % 2;
+                        needRedraw |= REDRAW_VISUALIZER;
+                        break;
+
+                    case SDLK_F7:
+                        /* Toggle "auto return" for Lua mode */
+                        optAutoReturn = !optAutoReturn;
+                        needRedraw |= REDRAW_VISUALIZER;
+                        break;
+
+                    case SDLK_F8:
+                        /* Toggle keyboard jazz mode */
+                        jazzMode = !jazzMode;
+                        needRedraw |= REDRAW_VISUALIZER;
+                        break;
+
+                    case SDLK_F9:
+                        /* Save history to file */
+                        au_save_history(AUVAL_HISTORY_USER, histBuf, histMax);
+                        break;
+
+                    case SDLK_ESCAPE:
+                        exitFlag = TRUE;
+                        break;
+                    
+                    case SDLK_RETURN:
+                        /* Add to history buffer */
+                        if (!jazzMode && editBuf->len > 0)
+                        {
+                            if (histMax > 0)
+                            {
+                                au_editbuf_free(histBuf[optHistoryLen+1]);
+                                histBuf[optHistoryLen+1] = NULL;
+                                memmove(&histBuf[2], &histBuf[1], histMax * sizeof(histBuf[0]));
+                            }
+                            
+                            histPos = 0;
+                            histBuf[1] = au_editbuf_copy(editBuf);
+                            if (histMax < optHistoryLen) histMax++;
+                            
+                            au_editbuf_insert(editBuf, editBuf->len, 0);
+
+                            /* You could do something here with the data ... */
+                            //result = handleUserInput(conn, editBuf->data, editBuf->len);
+                            
+                            au_editbuf_clear(editBuf);
+                        }
+                        break;
+
+                    case SDLK_UP: /* Backwards in input history */
+                        if (jazzMode)
+                            break;
+
+                        if (histPos == 0)
+                        {
+                            au_editbuf_free(histBuf[0]);
+                            histBuf[0] = au_editbuf_copy(editBuf);
+                        }
+                        if (histPos < histMax)
+                        {
+                            histPos++;
+                            au_editbuf_free(editBuf);
+                            editBuf = au_editbuf_copy(histBuf[histPos]);
+                        }
+                        break;
+                        
+                    case SDLK_DOWN: /* Forwards in input history */
+                        if (jazzMode)
+                            break;
+
+                        if (histPos > 0)
+                        {
+                            histPos--;
+                            au_editbuf_free(editBuf);
+                            editBuf = au_editbuf_copy(histBuf[histPos]);
+                        }
+                        break;
+                        
+                    case SDLK_LEFT:
+                        if (jazzMode)
+                            break;
+
+                        /* ctrl+left arrow = Skip words left */
+                        if (modCtrl)
+                        {
+                            while (editBuf->pos > 0 && isspace((int) editBuf->data[editBuf->pos - 1]))
+                                editBuf->pos--;
+                            while (editBuf->pos > 0 && !isspace((int) editBuf->data[editBuf->pos - 1]))
+                                editBuf->pos--;
+                        }
+                        else
+                        {
+                            au_editbuf_setpos(editBuf, editBuf->pos - 1);
+                        }
+                        break;
+                    
+                    case SDLK_RIGHT:
+                        if (jazzMode)
+                            break;
+
+                        /* ctrl+right arrow = Skip words right */
+                        if (modCtrl)
+                        {
+                            while (editBuf->pos < editBuf->len && isspace((int) editBuf->data[editBuf->pos]))
+                                editBuf->pos++;
+                            while (editBuf->pos < editBuf->len && !isspace((int) editBuf->data[editBuf->pos]))
+                                editBuf->pos++;
+                        }
+                        else
+                        {
+                            au_editbuf_setpos(editBuf, editBuf->pos + 1);
+                        }
+                        break;
+
+                    case SDLK_HOME:
+                        if (jazzMode)
+                            break;
+
+                        au_editbuf_setpos(editBuf, 0);
+                        break;
+
+                    case SDLK_END:
+                        if (jazzMode)
+                            break;
+
+                        au_editbuf_setpos(editBuf, editBuf->len);
+                        break;
+                    
+                    case SDLK_BACKSPACE:
+                        if (jazzMode)
+                            break;
+
+                        au_editbuf_delete(editBuf, editBuf->pos - 1);
+                        au_editbuf_setpos(editBuf, editBuf->pos - 1);
+                        break;
+                    
+                    case SDLK_DELETE: /* Delete character */
+                        if (jazzMode)
+                            break;
+
+                        au_editbuf_delete(editBuf, editBuf->pos);
+                        break;
+                        
+                    case SDLK_INSERT: /* Ins = Toggle insert / overwrite mode */
+                        if (jazzMode)
+                            break;
+
+                        insertMode = !insertMode;
+                        break;
+                    
+                    default:
+                        if (jazzMode)
+                        {
+                            int key = event.key.keysym.unicode;
+                            /* In keyboard jazz-mode, space stops playing */
+                            if (key == SDLK_SPACE)
+                            {
+                                SDL_LockMutex(audata.mutex);
+                                audata.varFreq = 1.0f;
+                                audata.pos = 0;
+                                audata.varTime = 0;
+                                audata.varKeyTime = 0;
+                                audata.varUnit = 0;
+                                SDL_UnlockMutex(audata.mutex);
+
+                                audioPlaying = FALSE;
+                                SDL_PauseAudio(!audioPlaying);
+                                break;
+                            }
+                            else
+                            {
+                                /* Calculate note based on key */
+                                int period, note = au_get_note_from_key(&event.key.keysym, 4);
+                                if (note > 0)
+                                {
+                                    period = 7680 - (note * 64) - (/* finetune */ 128 / 2);
+                                    if (period < 1) period = 1;
+                                
+                                    SDL_LockMutex(audata.mutex);
+                                    audata.varFreq = 8363.0f * pow(2.0f, (4608.0f - (double) period) / 768.0f) / optAudioFreq ;
+                                    audata.pos = 0;
+                                    audata.varTime = 0;
+                                    audata.varKeyTime = 0;
+                                    audata.varUnit = 0;
+                                    SDL_UnlockMutex(audata.mutex);
+                                
+                                    audioPlaying = TRUE;
+                                    SDL_PauseAudio(!audioPlaying);
+                                }
+                            }
+
+                            needRedraw |= REDRAW_VISUALIZER;
+                        }
+                        else
+                        {
+                            int key = event.key.keysym.unicode;
+                            if (isprint(key))
+                            {
+                                if (insertMode)
+                                    au_editbuf_insert(editBuf, editBuf->pos, key);
+                                else
+                                    au_editbuf_write(editBuf, editBuf->pos, key);
+                                au_editbuf_setpos(editBuf, editBuf->pos + 1);
+                            }
+                        }
+                        break;
+                }
+                }
+                needRedraw |= REDRAW_EDITOR | REDRAW_INFO;
+                break;
+            
+            case SDL_VIDEORESIZE:
+                /* Window resized, reinit video etc */
+                optScrWidth = event.resize.w;
+                optScrHeight = event.resize.h;
+
+                if (!au_init_video(&screen))
+                    goto error_exit;
+
+                needRedraw = REDRAW_ALL;
+                break;
+            
+            case SDL_VIDEOEXPOSE:
+                /* Window exposed, redraw everything just to be sure */
+                needRedraw = REDRAW_ALL;
+                break;
+
+            case SDL_QUIT:
+                goto error_exit;
+        }
+
+
+        /* If formula has changed, update evaluation variables and load the
+         * expression into Lua. Check for errors. The actual expression
+         * evaluation is done in the audio rendering callback.
+         */
+        if (!jazzMode && editBuf->len > 0 && editBuf->dirty)
+        {
+            editBuf->dirty = FALSE;
+            editBuf->data[editBuf->len] = 0;
+            
+            SDL_LockMutex(audata.mutex);
+            
+            DMEvalNode *tmp = NULL;
+            dm_eval_free(audata.expr);
+            audata.expr = NULL;
+            
+            audata.err = dm_eval_parse_expr(audata.engine, editBuf->data, &tmp);
+            if (audata.err == 0)
+                audata.err = dm_eval_reorder(audata.engine, tmp, &audata.expr);
+
+            dm_eval_free(tmp);
+
+            audata.pos = 0;
+            audata.varTime = 0;
+            audata.varFreq = 1.0f;
+            audata.varKeyTime = 0;
+            audata.varUnit = 0;
+
+            SDL_UnlockMutex(audata.mutex);
+        }
+        
+        /* Check if visualizer needs redrawing
+         */
+        if (audata.pos != audata.oldpos)
+        {
+            audata.oldpos = audata.pos;
+            needRedraw |= REDRAW_VISUALIZER;
+        }
+
+        /* Redraw screen, if needed */
+        if (needRedraw)
+        {
+            if (SDL_MUSTLOCK(screen) != 0 && SDL_LockSurface(screen) != 0)
+            {
+                dmError("Can't lock surface");
+                goto error_exit;
+            }
+
+            /* Clear the surface, draw copyright etc */
+            int fh = TTF_FontHeight(font);
+            int eh = screen->h - fh * 4;
+            
+            if (needRedraw & REDRAW_INFO)
+            {
+                dmFillBox3D(screen, 0, 0, screen->w - 1, fh + 5, dmMapRGB(screen, 50, 50, 150),
+                    dmMapRGB(screen, 150,150,250), dmMapRGB(screen, 25,25,50));
+
+                dmDrawTTFTextConst(screen, font, fontcol, 5, 2,
+                    AUVAL_NAME " v" AUVAL_VERSION " (C) Copyright 2013 ccr/TNSP");
+
+
+                dmFillBox3D(screen, 0, eh - 25 - fh, screen->w - 1, eh - 16, dmMapRGB(screen, 50, 50, 150),
+                    dmMapRGB(screen, 150,150,250), dmMapRGB(screen, 25,25,50));
+
+                dmDrawTTFText(screen, font, fontcol, 5, eh - fh - 20, "%s | [%-8s] | %s | %s",
+                    optClipping ? "CLIPPED" : "WRAPPED",
+                    optScale ? "0.0-1.0" : "0 - 255",
+                    optAutoReturn ? "RETURN" : "no ret",
+                    jazzMode ? "jazz" : "edit");
+            }
+
+            /* Draw the visualizer, based on the current mode*/
+            if (needRedraw & REDRAW_VISUALIZER)
+            {
+                char *vms;
+                dmFillRect(screen, 0, fh + 6, screen->w - 1, eh - fh - 27, dmMapRGB(screen, 15, 15, 15));
+
+                switch (viewMode)
+                {
+                    case 0:
+                        au_vis_wave(screen, 5, fh * 2 + 15, screen->w - 5, eh - 20, &audata);
+                        vms = "scope";
+                        break;
+
+                    case 1:
+                        au_vis_image(bmp, &audata);
+                        dmScaledBlitSurfaceAny(bmp, 5, fh * 2 + 15, screen->w - 10, eh - fh * 2 - 30, screen, DMD_NONE);
+                        vms = "bitmap";
+                        break;
+                    
+                    default:
+                        vms = "?";
+                }
+
+                dmDrawTTFText(screen, font, fontcol, 5, fh + 10, "%s | %s | f=%dHz | t=%-8.1f",
+                    audioPlaying ? "PLAYING" : "STOPPED",
+                    vms, optAudioFreq, audata.varTime
+                    );
+            }
+
+            /* Draw the function editor box */                
+            if (needRedraw & REDRAW_EDITOR)
+            {
+                SDL_LockMutex(audata.mutex);
+
+                dmFillBox3D(screen, 0, eh - 15, screen->w - 1, screen->h - 1,
+                    audata.err ? dmMapRGB(screen, 255, 0, 0) : dmMapRGB(screen, 0, 128, 64),
+                    dmMapRGB(screen, 200, 255, 200), 100);
+
+                au_draw_editbuf(screen, font, fontcol, 5, eh - 10, screen->w - 10, eh - 15, editBuf,
+                    dmMapRGB(screen, 0, 0, 150));
+                
+                if (audata.errStr != NULL)
+                    dmDrawTTFTextConst(screen, font, fontcol, 5, screen->h - fh, audata.errStr);
+
+                SDL_UnlockMutex(audata.mutex);
+            }
+
+            if (SDL_MUSTLOCK(screen) != 0)
+                SDL_UnlockSurface(screen);
+
+            SDL_Flip(screen);
+            needRedraw = 0;
+        }
+        
+        SDL_Delay(50);
+    }
+
+    /* Save history */
+    au_save_history(AUVAL_HISTORY_FILE, histBuf, histMax);
+
+error_exit:
+    if (screen)
+        SDL_FreeSurface(screen);
+
+    if (font)
+        TTF_CloseFont(font);
+
+    if (audioPlaying)
+        SDL_PauseAudio(1);
+
+    SDL_DestroyMutex(audata.mutex);
+
+    if (initSDL)
+        SDL_Quit();
+
+    if (initTTF)
+        TTF_Quit();
+
+    return 0;
+}