Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Matthew D. Miller
Matthew D. Miller

Posted on • Edited on • Originally published atblog.matthewdmiller.net

     

Learn LambdaNative by Example: Desktop GUI

This tutorial was updated on January 10, 2021 to generate a tone using LambdaNative instead of merely wrapping a Linux-only command-line program called beep that controls the PC speaker. This should now make it possible to follow this tutorial on any OS supported by LambdaNative. This tutorial can also be found in theGitHub repo with the example code. Pull requests for improving the example and tutorial are welcome.

LambdaNative is a cross-platform framework for developing desktop and mobile apps using Scheme (built atop Gambit). Resources on LambdaNative seem to be extremely scarce. I couldn't find a single step-by-step tutorial for using LambdaNative, so hopefully my small contribution will benefit others trying to get started with LambdaNative.

LambdaNative already includes a calculator demo, and frankly, I find making a millionth calculator example to be a little dull. Instead I'll be building a GUI for generating a tone. We're only going to be building a desktop GUI, so we aren't going to be tapping into the full power of LambdaNative. Where it seems LambdaNative would really be useful is enabling you to write cross-platform mobile apps with Scheme! Unfortunately, that is outside the scope of this tutorial. Maybe in the future I'll revisit LambdaNative and use it to create a mobile app.

Screenshot

Installing LambdaNative

The LambdaNative wiki gives alist of dependencies that need to be installed with your distro's package manager before installing LambdaNative. They give anapt command you can copy and paste to your terminal to install all the needed dependencies on Ubuntu.

  • Download the most recentrelease.

  • Unzip the release to a system-wide location such as/opt or/usr/local.

sudounzip lambdanative-*.zip-d /opt
Enter fullscreen modeExit fullscreen mode
  • Rename unzipped directory.
cd /optsudo mvlambdanative* lambdanative
Enter fullscreen modeExit fullscreen mode
  • Create the filesSETUP andPROFILE. If you were developing a mobile app, you would need to configure these files for the respective SDKs. Since that is outside the scope of this tutorial, that is left as an exercise for the reader.
cdlambdanativesudo cpSETUP.template SETUPsudo cpPROFILE.template PROFILE
Enter fullscreen modeExit fullscreen mode
  • Editscripts/lambdanative and populate theLAMBDANATIVE variable with/opt/lambdanative.
LAMBDANATIVE=/opt/lambdanative
Enter fullscreen modeExit fullscreen mode
  • Place the LambdaNative initialization script in the system path.
sudo ln-s /opt/lambdanative/scripts/lambdanative /usr/bin/lambdanative
Enter fullscreen modeExit fullscreen mode
  • Create and initialize a LambdaNative build directory.
mkdir ~/lambdanativecd ~/lambdanativelambdanative init
Enter fullscreen modeExit fullscreen mode

Creating a New GUI App

Your freshly initiated build directory will look like this:

apps/modules/configureMakefile
Enter fullscreen modeExit fullscreen mode

Your app will go in its own subdirectory inapps. To create a new app:

lambdanative create <appname> <apptype>
Enter fullscreen modeExit fullscreen mode

The options for<apptype> areconsole,gui, andeventloop. I created a new GUI app called bleep:

lambdanative create bleep gui
Enter fullscreen modeExit fullscreen mode

This will create a directory inapps calledbleep with several files in it.

Compiling the App

LambdaNative utilizes the GNU Build System.

./configure bleepmakemakeinstall
Enter fullscreen modeExit fullscreen mode

By default, the build will target the local host. The first time you do this will take awhile, because it is downloading and compiling prerequisites. These prerequisites are cached to speed up subsequent compiles. If you get any errors during the initial build, you probably missed installing a dependency. Install it with your distro's package manager and then try compiling again.

You can also configure in debug mode. You will want to clean the cache withmake scrub so everything is rebuilt. It will take awhile since everything is being rebuilt.

./configure bleep debugmake scrubmakemakeinstall
Enter fullscreen modeExit fullscreen mode

In my experience, debug mode didn't help much. Runtime errors are logged to~/Desktop/log/*.txt.

[SYSTEM] 2019-03-16 00:59:21: Application bleep built 2019-03-16 00:59:14[SYSTEM] 2019-03-16 00:59:21: Git hash[ERROR] 2019-03-16 00:59:22: primordial: (assoc 49 #f): (Argument 2) LIST expected[ERROR] 2019-03-16 00:59:22: HALT
Enter fullscreen modeExit fullscreen mode

Not very helpful, is it? So I enabled debug mode.

[SYSTEM] 2019-03-16 01:16:44: Application bleep built 2019-03-16 01:16:34[SYSTEM] 2019-03-16 01:16:44: Git hash[ERROR] 2019-03-16 01:16:44: primordial: (assoc 49 #f): (Argument 2) LIST expected[ERROR] 2019-03-16 01:16:44: trace: /opt/lambdanative/modules/ln_glgui/primitives.scm line=230 col=21[ERROR] 2019-03-16 01:16:44: trace: /opt/lambdanative/modules/ln_glgui/primitives.scm line=273 col=21[ERROR] 2019-03-16 01:16:44: trace: /opt/lambdanative/modules/ln_glgui/slider.scm line=80 col=8[ERROR] 2019-03-16 01:16:44: trace: /opt/lambdanative/modules/ln_glgui/glgui.scm line=151 col=36[ERROR] 2019-03-16 01:16:44: trace: /opt/lambdanative/modules/ln_glgui/glgui.scm line=145 col=11[ERROR] 2019-03-16 01:16:44: trace: /opt/lambdanative/modules/ln_glgui/glgui.scm line=183 col=3[ERROR] 2019-03-16 01:16:44: trace: /opt/lambdanative/modules/eventloop/eventloop.scm line=151 col=9[ERROR] 2019-03-16 01:16:44: HALT
Enter fullscreen modeExit fullscreen mode

It's definitely longer but still not very helpful. It actually includes line numbers, which appears promising, but they are all line numbers in LambdaNative modules. It doesn't actually trace the error all the way to the line in my app causing the error. I spent a lot more time reading the source of the LambdaNative modules than I would have liked.

And that's when the log file even contained an error. Sometimes the app failed with a segmentation fault and there was no error in the log at all. I often peppered my source with(log-status "reached here") andtail -f the log file to debug and isolate errors.

Errors caught at compile time, on the other hand, were much nicer. If an error occurred while compiling, the error message included the line number from my app.

The development workflow is more akin to traditional compiled languages like C. I missed the quick feedback I'm used to while developing Scheme on a REPL. After the initial compile, subsequent compiles are much quicker.

$timemakereal  0m3.877suser  0m2.652ssys   0m0.605s
Enter fullscreen modeExit fullscreen mode

Four seconds can seem like a long time when you are making a series of small changes or chasing down a bug. There is amodule for REPL editing with Emacs. Since I don't use Emacs, I didn't give it a spin, but if I was developing a larger app, I might give it a try.

Theinstall step will move the executable to~/Desktop/bleep/bleep and launch it. This will launch a rectangular window.

Screenshot

This window looks awful lonely. Let's add some widgets!

Coding the App

If your interface will use text (such as labels on buttons), you must include aFONTS file in your application subdirectory. I just copied theFONTS file from one of the demos included with LambdaNative.

cdapps/bleepcp /opt/lambdanative/apps/LineDrop/FONTS.
Enter fullscreen modeExit fullscreen mode

This is what the file looks like:

DejaVuSans.ttf 8 18,25 ascii
Enter fullscreen modeExit fullscreen mode

For a description of the file format, see the documentation of the file on theLambdaNative wiki.

Your application subdirectory will already contain amain.scm. This file contains the basic skeleton for a GUI app (the black rectangle above):

;; LambdaNative gui template(definegui#f)(main;; initialization(lambda(wh)(make-window320480)(glgui-orientation-set!GUI_PORTRAIT)(set!gui(make-glgui));; initialize gui here);; events(lambda(txy)(if(=tEVENT_KEYPRESS)(begin(if(=xEVENT_KEYESCAPE)(terminate))))(glgui-eventguitxy));; termination(lambda()#t);; suspend(lambda()(glgui-suspend)(terminate));; resume(lambda()(glgui-resume)));; eof
Enter fullscreen modeExit fullscreen mode

I started by changing the comment at the top to

;; bleep - GUI for generating a tone made with LambdaNative
Enter fullscreen modeExit fullscreen mode

The bulk of the skeleton consists of theevent loop. The(main p1 p2 p3 p4 p5) loop takes five functions as arguments:

ParameterDescription
p1Function to be run before the main loop is initialized. This is where you setup the GUI.
p2Main loop function, which is called constantly throughout the application's life. This is where you listen for events like key presses. Since most widgets take a callback, you shouldn't need to do much in this area.
p3Function to be run when the application is to be terminated.
p4Function, which is called when the application is suspended.
p5Function, which is called when the application is resumed.

The functions supplied in the skeleton for p3, p4, and p5 should be sufficient for most applications. We won't need to touch them.

(make-window540360)(glgui-orientation-set!GUI_LANDSCAPE)
Enter fullscreen modeExit fullscreen mode

I started by changing the dimensions and orientation of the window. Now let's add some widgets to that initializationlambda. I copy and pasted the example from the bottom of theslider documentation page.

(set!sl(glgui-slidergui20202806015#fWhiteWhiteOrangeBlacknum_25.fntnum_20.fnt#fWhite))(glgui-widget-set!guisl'showvalue#t)
Enter fullscreen modeExit fullscreen mode

If you are familiar with Scheme, thatset! probably made you pause. Too many exclamation marks in my Scheme code always make me nervous. I immediately start wondering if there is a better way to write the code. As it should. Side effects should be avoided when possible. I tried changing theset! todefine and got the following error when recompiling:

*** ERROR IN "/home/matthew/lambdanative/apps/bleep/main.scm"@97.5 -- Ill-placed 'define'
Enter fullscreen modeExit fullscreen mode

Gambit (the underlying Scheme implementation used by LambdaNative) only allowsdefines at the beginning of alambda body. This actually conforms with the R[5-7]RS specs, but I'm used to Scheme implementations (such as Racket, Chicken, and MIT/GNU Scheme) that allowdefine anywhere in alambda body. All the LambdaNative examples and demos useset!, so I used it as well.

We must specify the color for several elements of the slider. LambdaNative doesn't use native widgets but draws its own widgets with OpenGL. I googled "color schemes" for inspiration and specified a few colors at the top of the script above the(main) loop that I could reference throughout the program.

;; UI color palette(define*background-color*(color-rgb262629))(define*foreground-color*(color-rgb195763))(define*accent-color*(color-rgb1113450))(define*text-color*(color-rgb255255255));; Scale used by slider(define*min-position*0)(define*max-position*2000);; Range of frequencies(define*min-frequency*20)(define*max-frequency*20000)
Enter fullscreen modeExit fullscreen mode

The variable name*background-color* is just a Lisp naming convention for global parameters. LambdaNative provides(color-rgb r g b) for creating colors. I also defined variables for the scale used by the slider and the range of frequencies accepted by beep.

We also need to specify the position and size of the slider. Both are specified in pixels. There aren't percentages or other scalable units you may be familiar with from CSS. There are functions to get the width and height of the window, so you could code the math to make a widget 80% the width of the window. Since we're dealing with a simple example with hard-coded window dimensions, I just hard-coded the position and size as well. Note that you specify the position along the y-axis as pixels from the bottom of the window. This seemed counter intuitive to me, and I continually caught myself trying to specify pixels from the top of the window.

;; Background color(let((w(glgui-width-get))(h(glgui-height-get)))(glgui-boxgui00wh*background-color*));; Frequency slider(set!slider(glgui-slidergui2028050060*min-position**max-position*#fWhite*foreground-color**accent-color*#fascii_18.fntascii_18.fnt#fWhite))(glgui-widget-set!guislider'showlabels#f)
Enter fullscreen modeExit fullscreen mode

I set a background color for the entire window. The only way I could find to do this was to create aglgui-box the size of the entire window and set the color of the box. I also renamed the variable fromsl toslider. LambdaNative has the tendency to use short, non-descriptive variable names throughout its examples and documentation. I prefer to use more descriptive variable names. Replace the fonts in the example slider code with the fonts we specified in theFONTS file. I also disabled the slider labels.

The range of frequencies audible by humans is typically between 20 Hz and 20 KHz (we lose the ability to hear some of those higher frequencies as we age). Themusical note A above middle C is 440 Hz. Since A4 serves as a general tuning standard, it seems like a sensible default.

The scale of 20 to 20,000 is so large that 440 wouldn't appear to move the slider at all. Ideally, 440 would fall about the middle of the slider. To achieve this, let's use a logarithmic scale.

I found aStack Overflow answer on how to map a slider to a logarithmic scale. The code given in the answer is JavaScript, but it was easy enough to port to Scheme.

;; Logarithmic scale for frequency (so middle A [440] falls about in the middle);; Adapted from https://stackoverflow.com/questions/846221/logarithmic-slider(definemin-freq(log*min-frequency*))(definemax-freq(log*max-frequency*))(definefrequency-scale(/(-max-freqmin-freq)(-*max-position**min-position*)));; Convert slider position to frequency(define(position->frequencyposition)(inexact->exact(round(exp(+min-freq(*frequency-scale(-position*min-position*)))))));; Convert frequency to slider position(define(frequency->positionfreq)(/(-(logfreq)min-freq)(+frequency-scale*min-position*)))
Enter fullscreen modeExit fullscreen mode

I created two functions: one that takes the position on the slider and returns the frequency (position->frequency) and another that takes a frequency and returns the position on the slider (frequency-position). Now let's set the initial position of our slider with thefrequency->position function.

(glgui-widget-set!guislider'value(frequency->position440))
Enter fullscreen modeExit fullscreen mode

Underneath the slider is a text field showing the current frequency, buttons to increase/decrease the frequency by one octave, and a play button.

;; Frequency display(set!frequency-field(glgui-inputlabelgui2102308030"440"ascii_18.fnt*text-color**foreground-color*))(glgui-widget-set!guifrequency-field'alignGUI_ALIGNCENTER)(set!frequency-label(glgui-labelgui2902304030"Hz"ascii_18.fnt*foreground-color**accent-color*))(glgui-widget-set!guifrequency-label'alignGUI_ALIGNCENTER);; Octave buttons(set!lower-button(glgui-button-stringgui1402305030"<"ascii_18.fnt(lambda(gwtxy)#t)))(set!higher-button(glgui-button-stringgui3502305030">"ascii_18.fnt(lambda(gwtxy)#t)));; Play button(set!play-button(glgui-button-stringgui2301258050"Play"ascii_25.fnt(lambda(gwtxy)#t)))
Enter fullscreen modeExit fullscreen mode

That last argument toglgui-button-string is a callback function. This is a function that is called when the button is pressed. I'm just trying to get the widgets layed out right now. I don't yet care about the function of the button, so I used anonymous functions (lambdas) that don't do anything for now.

The buttons do come with some default styling, but you'll probably want to tweak the look to fit your color scheme and UI design. We can useglgui-widget-set! to set parameters of a widget. Buttons have various parameters that can be set such as'button-normal-color and'button-selected-color.

(glgui-widget-set!guiplay-button'button-normal-color*foreground-color*)(glgui-widget-set!guiplay-button'button-selected-color*accent-color*)(glgui-widget-set!guiplay-button'solid-color#t)(glgui-widget-set!guiplay-button'rounded#f)
Enter fullscreen modeExit fullscreen mode

That seems like a lot to type (or copy and paste) for each button. With CSS I'm able to define a style for all buttons or apply a class to buttons. I used afor-each loop to loop through all the buttons and apply the above styling:

;; Style buttons(for-each(lambda(button)(glgui-widget-set!guibutton'button-normal-color*foreground-color*)(glgui-widget-set!guibutton'button-selected-color*accent-color*)(glgui-widget-set!guibutton'solid-color#t)(glgui-widget-set!guibutton'rounded#f))(listlower-buttonhigher-buttonplay-button))
Enter fullscreen modeExit fullscreen mode

At this point, we are starting to have a nice looking interface, but it doesn't do anything. If you click the buttons or slide the slider, nothing happens. While the buttons take a callback function parameter, I couldn't find a way to wire up the slider to a function. I read theglgui-slider documentation page several times searching for clues.

Finally, I resorted to looking at the source code forglgui-slider. Each of the widget documentation pages link directly to their implementation in the LambdaNative GitHub repo. I already mentioned that I ended up reading the LambdaNative source more than I would have liked for debugging. Documentation is one area where LambdaNative really could stand to improve. I scannedslider.scm and discovered it had a'callback parameter. I created a function that would set the frequency displayed in theglgui-inputlabel to the one that corresponded to the position of theglgui-slider.

;; Link slider to text field display of frequency(define(adjust-frequency)(glgui-widget-set!guifrequency-field'label(number->string(position->frequency(glgui-widget-getguislider'value)))))
Enter fullscreen modeExit fullscreen mode

and wired it up to the slider:

(glgui-widget-set!guislider'callback(lambda(parentwidgeteventxy)(adjust-frequency)))
Enter fullscreen modeExit fullscreen mode

A callback function takes five arguments. In the code examples in the LambdaNative documentation, these always appeared as(lambda (g w t x y)). These one-letter variables aren't very descriptive, and the arguments of the callback functions don't appear to be documented. Through experimentation and reading the source code and examples, I worked out the following:

ParameterDescription
gThe [G]UI the widget belongs to. I used the nameparent for this variable in my callback functions.
wThe [w]idget that triggered the callback function. I used the namewidget for this variable in my callback functions.
tThe [t]ype of event. I used the nameevent for this variable in my callback functions.
xFirst argument of event (x coordinate in pixels, keyboard character, etc.)
ySecond argument of event (y coordinate in pixels, modifier flags, etc.)

The callback function is only called once the user releases the slider handle. I want the user to get feedback as they drag the slider. You can write your own event handling code in thelambda that forms the second parameter of(main). The generated skeleton already includes code to terminate the application when theEsc key is pressed. I added some code to calladjust-frequency when the slider handle is being dragged:

;; events(lambda(txy)(if(=tEVENT_KEYPRESS)(begin(if(=xEVENT_KEYESCAPE)(terminate))));; Also update frequency when dragging slider (callback is only on release)(if(and(glgui-widget-getguislider'downval)(=tEVENT_MOTION))(adjust-frequency))(glgui-eventguitxy))
Enter fullscreen modeExit fullscreen mode

By looking at the implementation ofglgui-slider inslider.scm, I noticed that LambdaNative was setting a'downval parameter whenever the user was holding down the mouse button on the slider handle. Whenever that parameter is true, I listen for anEVENT_MOTION event to calladjust-frequency.

I replaced the anonymous lambdas in the octave button declarations with callback functions calleddecrease-octave andincrease-octave. Anoctave is "the interval between one musical pitch and another with double its frequency."

;; Set frequency slider and display(define(set-frequencyfreq)(glgui-widget-set!guislider'value(frequency->positionfreq))(glgui-widget-set!guifrequency-field'label(number->stringfreq)));; Buttons increase and decrease frequency by one octave(define(adjust-octavemodifier)(let((new-freq(*(string->number(glgui-widget-getguifrequency-field'label))modifier)))(if(and(>=new-freq*min-frequency*)(<=new-freq*max-frequency*))(set-frequencynew-freq))))(define(decrease-octaveparentwidgeteventxy)(adjust-octave0.5))(define(increase-octaveparentwidgeteventxy)(adjust-octave2))
Enter fullscreen modeExit fullscreen mode

The'aftercharcb callback ofglgui-inputlabel is called after each character is typed or deleted. We can use this to update the slider as a user enters a frequency. What if a user (and you know they will) enters a number higher than 20,000 or a letter? We need a function that will only allow numbers within a given range.

;; Only allow numbers within range of min-value and max-value(define(num-onlymin-valuemax-valueold-value)(lambda(parentwidget)(let*((current-value(glgui-widget-getparentwidget'label))(current-numified(string->numbercurrent-value)))(if(or(=(string-lengthcurrent-value)0); Allow field to be empty(andcurrent-numified(>=current-numifiedmin-value)(<=current-numifiedmax-value)))(set!old-valuecurrent-value)(glgui-widget-set!parentwidget'labelold-value)))))
Enter fullscreen modeExit fullscreen mode

If the user types a character that makes the value invalid, we want to revert to the last known good value. To accomplish this, I used a closure to remember the last known value. Many programming languages today have closures, but Scheme practically invented them. A closure enables variables to be associated with a function that persist through all the calls of the function.

Now we can wire theglgui-inputlabel callback up to these functions.

(set!frequency-range(num-only*min-frequency**max-frequency*(glgui-widget-getguifrequency-field'label)))(glgui-widget-set!guifrequency-field'aftercharcb(lambda(parentwidgeteventxy)(frequency-rangeparentwidget)(let((freq(string->number(glgui-widget-getparentwidget'label))))(iffreq(glgui-widget-set!parentslider'value(frequency->positionfreq))))))
Enter fullscreen modeExit fullscreen mode

We call thenum-only closure specifying the allowed range and initial value which returns a new function that can be used in the callback. After we make sure there are no high jinks going on with the value using the function created by the closure (frequency-range), we update the position of the slider using the current value of the text field.

We can use thenum-only closure again to create a field to specify the duration of the beep in milliseconds:

;; General Controls(glgui-labelgui20408030"Duration"ascii_18.fnt*foreground-color*)(set!duration-field(glgui-inputlabelgui110408030"200"ascii_18.fnt*text-color**foreground-color*))(glgui-widget-set!guiduration-field'alignGUI_ALIGNCENTER)(set!duration-range(num-only1600000(glgui-widget-getguiduration-field'label)))(glgui-widget-set!guiduration-field'aftercharcb(lambda(parentwidgeteventxy)(duration-rangeparentwidget)))(glgui-labelgui195404030"ms"ascii_18.fnt*foreground-color*)
Enter fullscreen modeExit fullscreen mode

Frequency is rather abstract. Let's also give the user the ability to select a musical note. We can store the corresponding frequencies for A4-G4 in a table.

;; Notes -> frequency (middle A-G [A4-G4]);; http://pages.mtu.edu/~suits/notefreqs.html(definenotes(list->table'((0.440.00); A(1.493.88); B(2.261.63); C(3.293.66); D(4.329.63); E(5.349.23); F(6.292.00)))); G
Enter fullscreen modeExit fullscreen mode

We'll give the user a drop-down menu. Whenever a note is selected from the drop-down menu, we'll look up the frequency in the table and set it using theset-frequency helper function we created for the octave buttons.

(glgui-labelgui410406030"Note"ascii_18.fnt*foreground-color*)(set!note(glgui-dropdownboxgui470405030(map(lambda(str)(lambda(lglwxywhs)(ifs(glgui:draw-boxxywh*foreground-color*))(glgui:draw-text-left(+x5)y(-w10)hstrascii_18.fnt*text-color*)))(list"A""B""C""D""E""F""G"))*accent-color**foreground-color**accent-color*))(glgui-widget-set!guinote'scrollcolor*accent-color*)(glgui-widget-set!guinote'callback(lambda(parentwidgeteventxy)(set-frequency(table-refnotes(glgui-widget-getparentwidget'current)))))
Enter fullscreen modeExit fullscreen mode

Now, let's make some noise. LambdaNative has a rtaudio module. We'll use that to generate a tone with a sine wave. Edit theMODULES file in your applications subdirectory and add rtaudio to the list. The Scheme API of the rtaudio module consists of essentially just two functions:rtaudio-start andrtaudio-stop. You must first register four real-time hooks (an initialization hook, input hook, output hook, and close hook) in a chunk of C code embedded within your Scheme code. I wish the rtaudio module had an API that allowed implementing these hooks in pure Scheme. Thankfully theDemoRTAudio app included with LambdaNative implements a sine wave, and I was able to copy and paste most of what I needed from there without spending a lot of time trying to figure out how to write a sine wave in C myself.

;; Register C-side real-time audio hooks(c-declare#<<end-of-c-declare#include<math.h>voidrtaudio_register(void(*)(int),void(*)(float),void(*)(float*,float*),void(*)(void));doublef;doublesrate=0;floatbuffer;voidmy_realtime_init(intsamplerate){srate=(double)samplerate; buffer=0; }voidmy_realtime_input(floatv){}voidmy_realtime_output(float*v1,float*v2){staticdoublet=0;buffer=0.95*sin(2*M_PI*f*t);*v1=*v2=(float)buffer;t+=1/srate;}voidmy_realtime_close(){buffer=0; }end-of-c-declare)(c-initialize"rtaudio_register(my_realtime_init,my_realtime_input,my_realtime_output,my_realtime_close);")
Enter fullscreen modeExit fullscreen mode

Thebasic formula for a sine wave is A sin(2πft) whereA is amplitude,f is frequency, andt is time. We need a way to pass the frequency from our slider in the Scheme to the output hook in the C. Gambit scheme has ac-lambda special form that makes it possible to create a Scheme function that is a representative of a C function or code sequence.

(definertaudio-frequency(c-lambda(double)void"f=___arg1;"))
Enter fullscreen modeExit fullscreen mode

This creates a Scheme function that sets the f variable in our C chunk. Now let's create a Schem function that will set the frequency and start and stop the real-time audio subsystem.

;; Generate a tone using the rtaudio module(define(generate-toneparentwidgeteventxy); Make sure neither frequency or duration were left blank(if(=(string-length(glgui-widget-getparentfrequency-field'label))0)(set-frequency1))(if(=(string-length(glgui-widget-getparentduration-field'label))0)(glgui-widget-set!parentduration-field'label"1"))(rtaudio-frequency(exact->inexact(string->number(glgui-widget-getparentfrequency-field'label))))(rtaudio-start441000.5)(thread-sleep!(/(string->number(glgui-widget-getparentduration-field'label))1000))(rtaudio-stop))
Enter fullscreen modeExit fullscreen mode

When playing a note such as B4 (493.88 Hz) that has a decimal point, the type passed from Scheme to C lines up with the C typefloat, but when passing an integer (such as 440), it will cause an error. Theexact->inexact conversion forces Scheme to pass the value along as afloat. Wire this up to the play button, and you're ready to make some noise.

(set!play-button(glgui-button-stringgui2301258050"Play"ascii_25.fntgenerate-tone))
Enter fullscreen modeExit fullscreen mode

LambdaNative has a lot of rough edges, not least of which is the documentation (or lack thereof). Looking at the source code for a widget seems to be the only way to determine all the parameters available for that widget. If you're like me, being able to write mobile apps in Lisp is a dream come true! LambdaNative may not be the smoothest development experience right now, but I hope to revisit it again in the future. It is being actively developed (and has the backing of a university research team), so my hopes are high for the future of LambdaNative.

You can check out the entire example onGitHub. This started as a personal learning project to explore the state of GUI programming in Lisp and has become a series of tutorials on building GUIs with various dialects of Lisp.

Top comments(1)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
chrismatheson profile image
Chris Matheson
  • Joined

You implemented this project in LN and Racket. Could you maybe expand on your findings between the two? I'm just starting out with a GUI project in Racket. But I'm concerned that going beyond the "basic" styling of the core components will be a nightmare.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I'm a DBA by day and a youth pastor by night. I love Perl.
  • Location
    Oklahoma
  • Work
    Database Administrator at Oklahoma Baptist University
  • Joined

More fromMatthew D. Miller

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp