29 января 2012

Emacs workspaces.

Когда мне вконец надоело, что каждый emacs модуль открывает свои окна, как захочет, я нашел решение этой проблемы.

Скачайте workspace.el

(load-library "workspaces.el")
(global-set-key "\C-xg" 'workspace-goto)

Теперь переключитесь на первую рабочую область так:

С-x g 1 

Настройте расположение окон с помощью С-x 0|1|2|3|b .

Теперь переключитесь на вторую рабочую область:

С-x g 2 

И так далее, всего рабочих областей от 1 до 9.

23 января 2012

Embeddable Maxima. Советы и хитрости.

Updated: 23.01.12

Почему максиму сложно интегрировать в веб

Веб по-умолчанию многопользовательский. Максима по-умолчанию однопользовательская. Сделать максиму многопользовательской можно, но нужно желание этих пользователей пользоватся ею одновременно на одном компьютере.

Посмотреть справку

(pushnew "/path/to/embeddable-maxima" asdf:*central-registry*)
(ql:quickload :embeddable-maxima)
(in-package :maxima)

Для просмотра справки необходимо вызвать функцию describe("function-name"). Данная функция в окружении лиспа определена, как $describe. В лиспе вызывать ее следует с помощью mfuncall (maxima function caller). Например, просмотр справки для функции ratsimp.

MAXIMA> (mfuncall '$describe "ratsimp")

 -- Function: ratsimp ()
 -- Function: ratsimp (, , ..., )
     Simplifies the expression  and all of its subexpressions,
     including the arguments to non-rational functions.  The result is
     returned as the quotient of two polynomials in a recursive form,
     that is, the coefficients of the main variable are polynomials in
     the other variables.  Variables may include non-rational functions
     (e.g., `sin (x^2 + 1)') and the arguments to any such functions
     are also rationally simplified.

     `ratsimp (, , ..., )' enables rational
     simplification with the specification of variable ordering as in
     `ratvars'.

     When `ratsimpexpons' is `true', `ratsimp' is applied to the
     exponents of expressions during simplification.

     See also `ratexpand'.  Note that `ratsimp' is affected by some of
     the flags which affect `ratexpand'.

     Examples:

          (%i1) sin (x/(x^2 + x)) = exp ((log(x) + 1)^2 - log(x)^2);
                                                   2      2
                             x         (log(x) + 1)  - log (x)
          (%o1)        sin(------) = %e
                            2
                           x  + x
          (%i2) ratsimp (%);
                                       1          2
          (%o2)                  sin(-----) = %e x
                                     x + 1
          (%i3) ((x - 1)^(3/2) - (x + 1)*sqrt(x - 1))/sqrt((x - 1)*(x + 1));
                                 3/2
                          (x - 1)    - sqrt(x - 1) (x + 1)
          (%o3)           --------------------------------
                               sqrt((x - 1) (x + 1))
          (%i4) ratsimp (%);
                                     2 sqrt(x - 1)
          (%o4)                    - -------------
                                           2
                                     sqrt(x  - 1)
          (%i5) x^(a + 1/a), ratsimpexpons: true;
                                         2
                                        a  + 1
                                        ------
                                          a
          (%o5)                        x


  There are also some inexact matches for `ratsimp'.
  Try `?? ratsimp' to see them.

Выполнить строку

(pushnew "/path/to/embeddable-maxima" asdf:*central-registry*)
(ql:quickload :embeddable-maxima)
(in-package :maxima)

Делай ast с помощью специальной функции macsyma-read-string, раз:

Важно не забыть один из терминальных символов ";" или "$".

MAXIMA> (macsyma-read-string "ratsimp( (2*x^2 + 3*x + 1) - (x^2 + x^2 - 2*x + 4*x + 1) );")

(($RATSIMP)
 ((MPLUS) ((MPLUS) ((MTIMES) 2 ((MEXPT) $X 2)) ((MTIMES) 3 $X) 1)
  ((MMINUS)
   ((MPLUS) ((MEXPT) $X 2) ((MEXPT) $X 2) ((MTIMES) ((MMINUS) 2) $X)
    ((MTIMES) 4 $X) 1))))

Выполняй ast с помощью вызова максимы-функции ev(expression,arg_1,arg_2,...,arg_n), два:

MAXIMA> (mfuncall '$ev *)

$X

Отображай полученное ast в строчку, три:

MAXIMA> (displa *)

x
NIL

В данном примере мы упростили выражение ((2*x^2 + 3*x + 1) - (x^2 + x^2 - 2*x + 4*x + 1)) с помощью функции ratsimp.

Простой json-rpc для maxima

Осторожно, американский стиль повествования! Верите ли вы, что я сделаю это за 42 строчки? А вот да, сделаю. Конечно это лисп, и здесь в рамках одной строки вмещается раза в два больше информации, чем в алгольных языках. При этом я еще и поиспользую кучу библиотек, хотя считается, что под cl их нет. Ладно, если не считать пустых строк, то их всего 32. Ну что поверили? А вот я опять вас обманул. Действительно полезных строк 27. Итак json-rpc сервис для maxima, для того, чтобы вы из любого языка, который умеет http и json могли решить дифференциальное уравнение второго порядка. Только здесь и только сейчас.

Загружаем три библиотеки, 1-ая строка:

(mapcar #'ql:quickload '(:embeddable-maxima :restas :cl-json))

Обозначаем restas модуль для обработки http запросов, 4-ая строка:

(restas:define-module :maxima-json-rpc
  (:use #:cl #:restas #:json #:json-rpc))
(in-package :maxima-json-rpc)

Создаем функцию для проверки: заканчивается ли переданная максима команда с помощью терминальных символов $ или ;, 11-ая строка:

(defun ensure-valid-maxima-input (input)
  "Returns string with appended ';', if input string does not have maxima command terminator at the end."
  (let* ((input-trimmed (string-trim '(#\Space #\Newline #\Tab) input))
        (last-char (char input-trimmed (1- (length input-trimmed)))))
    (if (and (not (char= #\; last-char)) (not (char= #\$ last-char)))
        (concatenate 'string input-trimmed ";")
        input-trimmed)))

Создаем функцию, которая преобразовывает строковое выражение в АСТ для максимы, 13-ая строка:

(defun maxima-ast-from-string (input)
  (maxima::macsyma-read-string (ensure-valid-maxima-input input)))

Макрос, который заставляет максиму, выводить результат в "компьютерном" синтаксисе (дроби через /, степени - ^, и т.д.), 16-ая строка:

(defmacro with-2d-output (&body body)
  `(let ((maxima::$display2d nil))
     ,@body))

Экспортируемая функция доступная из удаленных источников. Создается с помощью json-rpc:defun-json-rpc, 24-ая строка:

(defun-json-rpc evaluate :explicit (text)
  "Evaluate maxima expression"
  (let ((result (make-array '(0) :element-type 'base-char :fill-pointer 0 :adjustable t)))
    (with-output-to-string (*standard-output* result)
      (with-2d-output
        (maxima::displa
         (maxima::mfuncall 'maxima::$ev (maxima-ast-from-string text)))))
    result))

Обработчик http маршрута, например, http://127.0.0.01/jsonrpc. 31-ая строка:

(define-route jsonrpc ("jsonrpc"
                            :method :post 
                            ;;:content-type "application/json"
                            )
  "json rpc route"
  (let ((*json-rpc-version* +json-rpc-2.0+))
    (invoke-rpc (hunchentoot:raw-post-data :force-text t))))

Ну а здесь мы предоставляет простую страничку, которая умеет с помощью jquery, ajax, json-rpc решать те самые диффуры:

(define-route example ("example")
   (merge-pathnames "examples/js-maxima-rpc-client.html" (asdf:component-pathname (asdf:find-system :maxima-json-rpc))))

Последняя строка для запуска:

(restas:start '#:maxima-json-rpc :port 8080)

Да, это все оформлено и в репозитарии лежит. https://github.com/filonenko-mikhail/maxima-json-rpc

Но это еще не всё. В дополнение вы получаете простой пример на php для того, чтобы моментально находить ответы на непростые математические вопросы. Внимание, данный пример требует наличия JSON-RPC PHP!

<?php
  require_once 'jsonRPCClient.php';
  $maxima = new jsonRPCClient('http://127.0.0.1:8080/jsonrpc');
  print "Maxima evaluator\n";
  print "Evaluate ratsimp(x^2 + 2*x + 1 - (x + 1)^2)\n";
  print $maxima->evaluate("ratsimp(x^2 + 2*x + 1 - (x + 1)^2)");

  print "Evaluate x^2 + 2*x + 1 + (x + 1)^2 in environment x=2\n";
  print $maxima->evaluate("ev(x^2 + 2*x + 1 + (x + 1)^2, x=2)");
?>

И напоследок, sh и решение диффуров. Диффуры такие:

(%i1) 'diff(f,x,2) = sin(x) + 'diff(g,x);

                                2
                               d f            dg
(%o1)                          --- = sin(x) + --
                                 2            dx
                               dx
(%i2) 'diff(f,x) + x^2 - f = 2*'diff(g,x,2);

                                               2
                               2   df         d g
(%o2)                         x  + -- - f = 2 ---
                                   dx           2
                                              dx

sh файлик такой:

#!/bin/sh
curl -i -X POST -d "{\"jsonrpc\": \"2.0\", \"method\": \"evaluate\", \"params\": [\"desolve(['diff(f(x),x,2) = 'diff(g(x),x,1)+sin(x), 'diff(f(x),x,1)-f(x)+x^2 = 2*'diff(g(x),x,2)], [f(x),g(x)]);\"], \"id\": 1}" http://linkfly.ru:8181/jsonrpc

А теперь, кто готов все повторить, но уже на пайтоне?

P.S. А Firemax, что расширение для Firefox, очень даже неплохо справляется с задачей эмуляции emacs под броузером.

16 января 2012

Common Lisp. Работа со звуком. CL-PortAudio.

Сразу код: караоке.

Установите PortAudio.
Включите музыку (желательно, с вокалом), и выполните следующий код:

sudo pacman -S portaudio
git clone --depth 1 https://github.com/filonenko-mikhail/cl-portaudio.git
emacs
M+x slime
  (pushnew "path/to/cl-portaudio/" asdf:*central-registry*)
  (ql:quickload :cl-portaudio)
  (ql:quickload :cl-portaudio-tests)
  (portaudio-tests:test-read-write-echo)

Теперь у вас есть 15 секунд, чтобы подпевать. Да, можно было бы подпевать и без таких сложных манипуляци, как лисп, emacs, но в результате небольшой задержки при записи/воспроизведении собственный голос звучит из колонок, как бэквокал, и я, например, понял, почему многие просят меня не подпевать радио.

CL-PortAudio

CL-PortAudio cffi-биндинги к PortAudio. PortAudio небольшая кроссплатформенная библиотека для работы со звуком. Она позволяет:

  • записывать звук;
  • воспроизводить звук.

CL-PortAudio соответственно позволяет делать эти вещи из лиспа.

Документация скопирована и переработана.

API

PortAudio предоставляет очень простое API. Для передачи данных в обоих направлениях можно использовать функцию обратного вызова (callback) или блокирующие функции чтения/записи. Блокирующие функции, надо сказать, можно "разблокировать", с помощью вызова *available функций, которые возвращают возможность чтения/записи данных.

CL-PortAudio использует только блокирующий ввод/вывод, так как функция обратного вызова исполняется в "критическом" контексте, и в ней нельзя даже память выделить, не говоря уже об интерпретаторе лиспа.

Common Lisp позволил уменьшить код основной работы со звуком до 4 функций/макросов. Вот код караоке, которое было вначале.

 (use-package :portaudio)

 (defconstant +frames-per-buffer+ 1024)
 (defconstant +sample-rate+ 44100d0)
 (defconstant +seconds+ 15)
 (defconstant +sample-format+ :float)
 (defconstant +num-channels+ 2)


 (defun test-read-write-converted-echo ()
  "Record input into an array; Separate array to channels; Merge channels into array; Play last array." 
  (with-audio
    (format t "~%=== Wire on. Will run ~D seconds . ===~%" +seconds+) 
    (with-default-audio-stream (astream +num-channels+ +num-channels+ :sample-format +sample-format+ :sample-rate +sample-rate+ :frames-per-buffer +frames-per-buffer+) 
      (dotimes (i (round (/ (* +seconds+ +sample-rate+) +frames-per-buffer+)))
        (ignore-errors (write-stream astream
                                    (merge-channels-into-array astream
                                                               (separate-array-to-channels astream
                                                                                           (read-stream astream)))))))))

  • with-audio (&body body)

    Макрос инициализирует в начале и деинициализирует в конце body библиотеку portaudio.

  • with-default-audio-stream ((var num-input num-output &key (sample-format :float) (sample-rate 44100.0d0) (frames-per-buffer 1024)) &body body)

    Макрос открывает в начале и закрывает в конце поток для ввода/вывода с заданным количеством каналов, форматом данных (только :float), частотой и количеством данных за один вызов записи/чтения. Макрос также запускает и останавливает открытый поток.

  • read-stream (pa-stream)

    Читает данные из потока запущенного ранее. Возвращает одномерные массив данных типа sample-format (только 'single-float) размером (* frames-per-buffer num-input).

  • write-stream (pa-stream buffer)

    Записывает данные в поток запущенный ранее. Массив должен быть одномерным типа sample-format (только 'single-float) размером (* frames-per-buffer num-output).

  • separate-array-to-channels (pa-stream array)

    Преобразует одномерный массив в (кол-во_каналов)-мерный массив.

  • merge-channels-into-array (pa-stream channels)

    Преобразует (кол-во_каналов)-мерный массив в одномерный массив.

Бинарные сборки библиотеки PortAudio можно скачать вот здесь http://planet.plt-scheme.org/package-source/clements/portaudio.plt/2/3/lib/. Очень большое спасибо ракетчикам.

Протестировано:

  • SBCL 1.0.54, archlinux x86_64
  • SBCL 1.0.53.74.mswinmt.1092-207a13d, windows 7 x86_64 (vbox).
  • SBCL 1.0.55 mt, Mac OS X 10.6.8 x86 (thanks Žiga Lenarčič).

03 января 2012

Common Lisp. 3d plot renderer.

Завлекушная картинка.

Захотелось тут написать простой интерактивный просмотрщик дву- и трехмерных графиков.

Я начал с библиотеки clx. Данная библиотека реализует протокол общения с графическим X сервером и не содержит внешних зависимостей. X сервер содержит расширение glx позволяющее использовать opengl. Я думал, что расширение glx заведется. Не завелось. Хотя под windows+cygwin/x+clx успех был, но это не моя конфигурация по-умолчанию.

На канале #lisp подсказали библиотеку glop, которую можно назвать урезанным clx'ом. Данная библиотека не реализует протокол самостоятельно, а просто является оберткой над сишными клиентами, а под windows и macos использует родные api.

Сегодня пойдет речь о том, как создать рендерер графиков на коммон лиспе с помощью библиотеки glop. По сути, это будет движок для приближения/отдаления точки и вращения вокруг нее.

Сразу же код:

(defpackage :3dplot
  (:use #:cl )
  (:export #:draw-plot #:while))

(in-package #:3dplot)

(defvar *min-zoom* 0.1)
(defvar *zoom-step* 0.1)
(defvar *rotate-multiplicator* 0.3)
(defvar *viewport-multiplicator* 10)

(defclass plotwindow (glop:window)
  ((1st-pressed :initform nil :accessor 1st-pressed)
   (zoom :initform 1 :accessor zoom)
   (xangle :initform 0 :accessor xangle)
   (yangle :initform 0 :accessor yangle)))

(defmethod glop:on-event ((window plotwindow) (event glop:key-event))
  (when (eq (glop:keysym event) :escape)
    (glop:push-close-event window))
  (when (and (glop:pressed event) (eq (glop:keysym event) :f))
    (glop:toggle-fullscreen window))
  (when (and (glop:pressed event) (eq (glop:keysym event) :g))
    (glop:set-fullscreen window)))

(defmethod glop:on-event ((window plotwindow) (event glop:button-event))
  (case (glop:button event)
    (1 ;; main button
     (setf (1st-pressed window) (glop:pressed event)))
    (4 ;; scroll up
     (when (> (zoom window) *min-zoom*)
       (decf (zoom window) *zoom-step*)
       (glop:push-event window (make-instance 'glop:resize-event :height (glop:window-height window)
                                                                 :width (glop:window-width window))))) 
    (5 ;; scroll down
     (incf (zoom window) *zoom-step*)
     (glop:push-event window (make-instance 'glop:resize-event :height (glop:window-height window)
                                                               :width (glop:window-width window))))))

(defmethod glop:on-event ((window plotwindow) (event glop:mouse-motion-event))
  (when (1st-pressed window)
    (incf (xangle window) (* *rotate-multiplicator* (glop:dx event)))
    (incf (yangle window) (* *rotate-multiplicator* (glop:dy event)))))

(defmethod glop:on-event ((window plotwindow) (event glop:resize-event))
  (let* ((width (glop:width event))
         (height (glop:height event))
         (aspect-ratio (/ height width))
         (zoom (zoom window)))
    (gl:viewport 0 0 width height)
    (gl:matrix-mode :projection)
    (gl:load-identity)
    (gl:ortho
     (* zoom (- *viewport-multiplicator*))
     (* zoom *viewport-multiplicator*)
     (* zoom (- (* *viewport-multiplicator* aspect-ratio)))
     (* zoom (* *viewport-multiplicator* aspect-ratio))
     (* zoom (- *viewport-multiplicator*))
     (* zoom *viewport-multiplicator*))))

(defmacro while (condition &body body)
  (let ((var (gensym)))
    `(do ((,var nil (progn ,@body)))
         ((not ,condition) ,var))))

(defun draw-3d-axes ()
  "Draw opengl 3d axes"
  (gl:color 0 1 0)
  (gl:with-primitives :lines
    (gl:vertex -10.0 0.0 0.0)
    (gl:vertex 10.0 0.0 0.0)
    ;; arrow
    (gl:vertex 10.0 0.0 0.0)
    (gl:vertex 9.5 0.5 0.0)
    (gl:vertex 10.0 0.0 0.0)
    (gl:vertex 9.5 -0.5 0.0)

    (gl:vertex 1.0 -0.2 0.0)
    (gl:vertex 1.0 0.2 0.0))
  (gl:color 1 1 0)
  (gl:with-primitives :lines
    (gl:vertex 0.0 -10.0 0.0)
    (gl:vertex 0.0 10.0 0.0)
    ;; arrow
    (gl:vertex 0.0 10.0 0.0)
    (gl:vertex -0.5 9.5 0.0)
    (gl:vertex 0.0 10.0 0.0)
    (gl:vertex 0.5 9.5 0.0)
    ;;unit
    (gl:vertex -0.2 1.0 0.0)
    (gl:vertex 0.2 1.0 0.0))
  (gl:color 0.4 0.5 1)
  (gl:with-primitives :lines
    (gl:vertex 0.0 0.0 -10.0)
    (gl:vertex 0.0 0.0 10.0)
    ;; arrow
    (gl:vertex 0.0 0.0 10.0)
    (gl:vertex -0.5 0.0 9.5)
    (gl:vertex 0.0 0.0 10.0)
    (gl:vertex 0.5 0.0 9.5)
    ;; unit
    (gl:vertex -0.2 0 1.0)
    (gl:vertex 0.2 0 1.0)))

(defun draw-plot-points (fn x-start x-end x-step y-start y-end y-step)
  "Draw plot of given function"
  (gl:color 1 1 1)
  (do ((x x-start (+ x x-step)))
      ((< x-end x) nil)
    (gl:with-primitives :line-strip
      (do ((y y-start (+ y y-step)))
          ((< y-end y) nil)
        (gl:vertex x y (funcall fn x y)))))
  (do ((y y-start (+ y y-step)))
      ((< y-end y) nil)
    (gl:with-primitives :line-strip
      (do ((x x-start (+ x x-step)))
          ((< x-end x) nil)
        (gl:vertex x y (funcall fn x y))))))


(defun draw-plot (fn x-start x-end x-step y-start y-end y-step)
  (glop:with-window (win "Interactive 3d plot" 800 600 :win-class 'plotwindow)
    ;; GL init
    (gl:clear-color 0 0 0 0)
    ;; idle loop, we draw here anyway
    (let ((frames 0)
          (last-time (get-universal-time)))
      (while (glop:dispatch-events win :blocking nil :on-foo nil)
        ;; rendering
        (gl:matrix-mode :modelview)
        (gl:load-identity)
        (gl:scale 1 1 -1)
        (gl:clear :color-buffer)
        ;; transform view
        (gl:with-pushed-matrix 
          (gl:rotate (xangle win) 0.0 1.0 0.0)
          (gl:rotate (yangle win) 1.0 0.0 0.0)
          (gl:color 1 1 1)
          (draw-3d-axes)
          (draw-plot-points fn x-start x-end x-step y-start y-end y-step))
        (gl:flush)
        (glop:swap-buffers win)
        (incf frames)
        (when (< 1 (- (get-universal-time) last-time))
          (format *standard-output* "fps ~a~%" (/ frames  (- (get-universal-time) last-time)))
          (setf last-time (get-universal-time))
          (setf frames 0))))))

Объявляем пакет 3dplot, экспортируем из него символ draw-plot.

draw-plot fn x-start x-end x-step y-start y-end y-step

Функция принимает:

  • fn функция от двух аргументов x и y, должна вернуть числовое значение
  • x-start x-end x-step начальное, конечное и приращение аргумента x
  • y-start y-end y-step начальное, конечное и приращение аргумента y

Пример, сетчатого графика для функции z = sin(x) + cos(x), где X э [-5,5] и Y э [-5,5], шаг 0.1 (э должна быть перевернута:), можете покрутить колесиком мыши или зажав левую клавишу подвигать ею.

(ql:quickload :glop)
(ql:quickload :cl-opengl)

(3dplot:draw-plot (lambda (x y) (+ (sin x) (cos y))) -5 5 0.1 -5 5 0.1)

Почему так много скобок.

Повторюсь: то, что получилось, представляет собой небольшой 3d движок. Можно даже сказать совсем небольшой. Что в нем реализовано:

  • Фокусирование на точке (0, 0, 0)
  • Приближение к данной точке
  • Отдаление от данной точки
  • Вращение вокруг данной точки

(defvar *min-zoom* 0.1)
(defvar *zoom-step* 0.1)
(defvar *rotate-multiplicator* 0.3)
(defvar *viewport-multiplicator* 10)

Это регуляторы для нашего движка.

  • *min-zoom* минимальное расстояние, меньше которого приближаться нельзя
  • *zoom-step* скорость приближения
  • *rotate-multiplicator* скорость поворота
  • *viewport-multiplicator* глобальное увеличение

Далее класс нашего окна для рисований. Наследуем его от glop:window.

(defclass plotwindow (glop:window)
  ...

Объект такого класса будет хранить следующие параметры.

  • 1st-pressed зажата ли левая клавиша мыши
  • zoom текущее увеличение
  • xangle текущий поворот относительно оси x
  • yangle текущий поворот относительно оси y

В этот же класс можно было бы добавить глобальные константы, но это в следующий раз.

Теперь реализуем обработчики событий от нашего окна. Первый специфируемый параметер: окно, второй - событие.

(defmethod glop:on-event ((window plotwindow) (event glop:key-event)
  ....

Обрабатываем нажатые клавиши мыши:

  • ESC - выход из программы. Осуществляется отсылкой сообщения glop:close-event нашему окну.
  • f - перключение полноэкранного режима
  • g - включение полноэкранного режима

(defmethod glop:on-event ((window plotwindow) (event glop:button-event)
  ....

Обрабатываем события от мыши. Если нажата левая клавиша: устанавливаем флаг в слоте окна 1st-pressed. Если нажата scroll up, приближем сцену, если scroll down - отдаляем. Для применения приближения или отдаления отсылаем сообщение об изменениях размеров. В обработчике того события происходит настройка сцены.

(defmethod glop:on-event ((window plotwindow) (event glop:mouse-motion-event))
  ....

Обработчик события передвижения мыши. Если зажата левая (главная клавиша) увеличиваем углы поворота сцены на "пробег" мыши.

(defmethod glop:on-event ((window plotwindow) (event glop:resize-event))
  ....

Здесь обрабатываем изменение размеров окна. Кроме того, данное событие "наступает", когда пользователь воспользовался функцией приближения/отдаления. Я не буду объяснять данный код, так как в книжках по opengl (например, redbook) это сделано гораздо лучше.

(defmacro while (condition &body body)
  ....

Вспомогательный макрос.

(defun draw-3d-axes ()
  ....

Отрисовываем оси координат, единичные отрезки и даже стрелочки. Делаем это разным цветом.

  • Ось X - зеленая
  • Ось Y - желтая
  • Ось Z - синяя

(defun draw-plot-points (fn x-start x-end x-step y-start y-end y-step)
  ....

Примитивная отрисовка графика.

(defun draw-plot (fn x-start x-end x-step y-start y-end y-step)
  ....

Главная функция. Создаем окно, настраиваем цвет фона. И в цикле обработки сообщений отрисовываем нашу сцену.