Category: projects

Scott,

in contrast to what you say the Apple audio stack (CoreAudio) is far less streamlined that it might appear on first sight. The different APIs that make up the Apple audio stack are far more redundant than you might think. Also, they are different in programming style, and you can list at least as many seperate components for different areas of audio with different API/naming styles as you just did for the Linux audio stack.

Listing two components of the Linux audio stack that are considered obsolete these days, and listing one item twice doesn't really help making your post unassailable.

Having said that, yes, our Linux audio stack is still chaotic, redundant, badly documented and incomplete. You are very welcome to help fixing this. But just doing a bit PR and sticking a single name on the sum of it all doesn't even touch the real problems we have with the audio APIs on Linux.

Free software development is in its very essence distributed. The fact that our APIs sometimes appear a bit higgledy-piggledy is probably just an inevitable consequence of this.


String Pools

In part 2.4.3 of Ulrich Drepper's excellent How To Write Shared Libraries (which unfortunately is a bit out-of-date these days) Ulrich suggests replacing arrays of constant strings by a single concatenated string plus an index lookup table, to avoid unnecessary relocations during startup of ELF programs. Maintaining this string pool is however troublesome, it is hard to read and difficult to edit. In appendix B Ulrich lists an example C excerpt which contains some code for simplifying the maintaining of such strings pools, after an idea from Bruno Haible. In my opinion however that suggestion is not that much simpler, and requires splitting off the actual strings into a seperate source file. Ugly!

Some Free Software uses string pools to speed up relocation, e.g. GTK+. Some development tools like gperf contain support for string pools.

All solutions for string pool maintaining I could find on the Internet were not exactly beautiful. Either they were completely manual, manual plus a validity checking tool, or very very cumbersome. Googling around I was unable to find a satisfactory tool for this purpose[1].

After Diego Petteno complained about my heavy use of arrays of constant strings in libatasmart I sat down to change the situation, and wrote strpool.c, a simple parser for a very, very minimal subset of C, written in plain ANSI C. It looks for two special comment markers /* %STRINGPOOLSTART% */ and /* %STRINGPOOLSTOP% */, moves all immediate strings between those markers into a common string pool and rewrites the input with the strings replaced by indexes. Code accessing those strings must use the special _P() macro. With these minimal changes to a source file, passing it through strpool.c will automatically rewrite it to a string-poolized version. The nice thing about this is that the necessary changes in the source are minimal, and the code stays compilable with and without passing it through the strpool.c preprocessor.

Here's an example. First the original non-string-poolized version:

static const char* const table[] = {
	"waldo",
	"uxknurz",
	"foobar",
	"fubar"
};

static int main(int argc, char* argv[]) {
	printf("%s\n", table[2]);
	return 1;
}

For later use with strpool.c we change this like this:

#ifndef STRPOOL
#define _P(x) x
#endif

/* %STRINGPOOLSTART% */
static const char* const table[] = {
	"waldo",
	"uxknurz",
	"foobar",
	"fubar"
};
/* %STRINGPOOLSTOP% */

static int main(int argc, char* argv[]) {
	printf("%s\n", _P(table[2]));
	return 1;
}

When passed through strpool.c this will be rewritten as:

/* Saved 3 relocations, saved 0 strings (0 b) due to suffix compression. */
static const char _strpool_[] =
	"waldo\0"
	"uxknurz\0"
	"foobar\0"
	"fubar\0";
#ifndef STRPOOL
#define STRPOOL
#endif
#ifndef _P
#define _P(x) (_strpool_ + ((x) - (const char*) 1))
#endif

#ifndef STRPOOL
#define _P(x) x
#endif

/* %STRINGPOOLSTART% */
static const char* const table[] = {
	((const char*) 1),
	((const char*) 7),
	((const char*) 15),
	((const char*) 22)
};
/* %STRINGPOOLSTOP% */

static int main(int argc, char* argv[]) {
	printf("%s\n", _P(table[2]));
	return 1;
}

All three versions can be compiled directly with gcc. However, the version that was passed through strpool.c compresses the number of relocations for the table array from 4 to 1. Which isn't much of a difference, but the larger your tables are the more relevant the difference in the number of necessary relocations gets.

A more realistic example is atasmart.c which after being preprocessed with strpool.c looks like this. In this specific example the number of necessary startup relocations goes down from > 100 to 9.

I am note sure if the parser is 100% correct, but it works fine with all sources I tried. It even does suffix compression like gcc does for normal strings too.

Footnotes

[1] Or maybe I just suck in googling? Anyone has a suggestion for such a tool?


PulseAudio 0.9.11 released

I just relased PulseAudio 0.9.11.

It's an awesome release. To learn more about why, read the linked email, and this and maybe this blog story.

PulseAudio logo


Linux Plumbers Conference CFP Extended!

The Call for Papers for the Linux Plumbers Conference in September in Portland, Oregon has been extended until July 31st 2008. It's a conference about the core infrastructure of Linux systems: the part of the system where userspace and the kernel interface. It's the first conference where the focus is specifically on getting together the kernel people who work on the userspace interfaces and the userspace people who have to deal with kernel interfaces. It's supposed to be a place where all the people doing infrastructure work sit down and talk, so that each other understands better what the requirements and needs of the other are, and where we can work towards fixing the major problems we currently have with our lower-level APIs.

I am running the Audio microconf of the Plumbers Conference. Audio infrastructure on Linux is still heavily fragmented. Pro, desktop and embedded worlds are almost completely seperate worlds. While we have quite good driver support the user experience is far from perfect, mostly due because our infrastructure is so balkanized. Join us at the Plumbers Conference and help to fix this! If you are doing audio infrastructure work on Linux, make sure to attend and submit a paper!

Sign up soon! Send in your paper early! The conference is expected to sell out pretty quickly!

Plumbers Logo

See you in Portland!


PulseAudio FUD

Jeffrey Stedfast

Jeffrey Stedfast seems to have made it his new hobby to bash PulseAudio. In a series of very negative blog postings he flamed my software and hence me in best NotZed-like fashion. Particularly interesting in this case is the fact that he apologized to me privately on IRC for this behaviour shortly after his first posting when he was critizised on #gnome-hackers -- only to continue flaming and bashing in more blog posts shortly after. Flaming is very much part of the Free Software community I guess. A lot of people do it from time to time (including me). But maybe there are better places for this than Planet Gnome. And maybe doing it for days is not particularly nice. And maybe flaming sucks in the first place anyway.

Regardless what I think about Jeffrey and his behaviour on Planet Gnome, let's have a look on his trophies, the five "bugs" he posted:

  1. Not directly related to PulseAudio itself. Also, finding errors in code that is related to esd is not exactly the most difficult thing in the world.
  2. The same theme.
  3. Fixed 3 months ago. It is certainly not my fault that this isn't available in Jeffrey's distro.
  4. A real, valid bug report. Fixed in git a while back, but not available in any released version. May only be triggered under heavy load or with a bad high-latency scheduler.
  5. A valid bug, but not really in PulseAudio. Mostly caused because the ALSA API and PA API don't really match 100%.

OK, Jeffrey found a real bug, but I wouldn't say this is really enough to make all the fuss about. Or is it?

Why PulseAudio?

Jeffrey wrote something about 'solution looking for a problem' when speaking of PulseAudio. While that was certainly not a nice thing to say it however tells me one thing: I apparently didn't manage to communicate well enough why I am doing PulseAudio in the first place. So, why am I doing it then?

  • There's so much more a good audio system needs to provide than just the most basic mixing functionality. Per-application volumes, moving streams between devices during playback, positional event sounds (i.e. click on the left side of the screen, have the sound event come out through the left speakers), secure session-switching support, monitoring of sound playback levels, rescuing playback streams to other audio devices on hot unplug, automatic hotplug configuration, automatic up/downmixing stereo/surround, high-quality resampling, network transparency, sound effects, simultaneous output to multiple sound devices are all features PA provides right now, and what you don't get without it. It also provides the infrastructure for upcoming features like volume-follows-focus, automatic attenuation of music on signal on VoIP stream, UPnP media renderer support, Apple RAOP support, mixing/volume adjustments with dynamic range compression, adaptive volume of event sounds based on the volume of music streams, jack sensing, switching between stereo/surround/spdif during runtime, ...
  • And even for the most basic mixing functionality plain ALSA/dmix is not really everlasting happiness. Due to the way it works all clients are forced to use the same buffering metrics all the time, that means all clients are limited in their wakeup/latency settings. You will burn more CPU than necessary this way, keep the risk of drop-outs unnecessarily high and still not be able to make clients with low-latency requirements happy. 'Glitch-Free' PulseAudio fixes all this. Quite frankly I believe that 'glitch-free' PulseAudio is the single most important killer feature that should be enough to convince everyone why PulseAudio is the right thing to do. Maybe people actually don't know that they want this. But they absolutely do, especially the embedded people -- if used properly it is a must for power-saving during audio playback. It's a pity that how awesome this feature is you cannot directly see from the user interface.[1]
  • PulseAudio provides compatibility with a lot of sound systems/APIs that bare ALSA or bare OSS don't provide.
  • And last but not least, I love breaking Jeffrey's audio. It's just soo much fun, you really have to try it! ;-)

If you want to know more about why I think that PulseAudio is an important part of the modern Linux desktop audio stack, please read my slides from FOSS.in 2007.

Misconceptions

Many people (like Jeffrey) wonder why have software mixing at all if you have hardware mixing? The thing is, hardware mixing is a thing of the past, modern soundcards don't do it anymore. Precisely for doing things like mixing in software SIMD CPU extensions like SSE have been invented. Modern sound cards these days are kind of "dumbed" down, high-quality DACs. They don't do mixing anymore, many modern chips don't even do volume control anymore. Remember the days where having a Wavetable chip was a killer feature of a sound card? Those days are gone, today wavetable synthesizing is done almost exlcusively in software -- and that's exactly what happened to hardware mixing too. And it is good that way. In software mixing is is much easier to do fancier stuff like DRC which will increase quality of mixing. And modern CPUs provide all the necessary SIMD command sets to implement this efficiently.

Other people believe that JACK would be a better solution for the problem. This is nonsense. JACK has been designed for a very different purpose. It is optimized for low latency inter-application communication. It requires floating point samples, it knows nothing about channel mappings, it depends on every client to behave correctly. And so on, and so on. It is a sound server for audio production. For desktop applications it is however not well suited. For a desktop saving power is very important, one application misbehaving shouldn't have an effect on other application's playback; converting from/to FP all the time is not going to help battery life either. Please understand that for the purpose of pro audio you can make completely different compromises than you can do on the desktop. For example, while having 'glitch-free' is great for embedded and desktop use, it makes no sense at all for pro audio, and would only have a drawback on performance. So, please stop bringing up JACK again and again. It's just not the right tool for desktop audio, and this opinion is shared by the JACK developers themselves.

Jeffrey thinks that audio mixing is nothing for userspace. Which is basically what OSS4 tries to do: mixing in kernel space. However, the future of PCM audio is floating points. Mixing them in kernel space is problematic because (at least on Linux) FP in kernel space is a no-no. Also, the kernel people made clear more than once that maths/decoding/encoding like this should happen in userspace. Quite honestly, doing the mixing in kernel space is probably one of the primary reasons why I think that OSS4 is a bad idea. The fancier your mixing gets (i.e. including resampling, upmixing, downmixing, DRC, ...) the more difficulties you will have to move such a complex, time-intensive code into the kernel.

Not everytime your audio breaks it is alone PulseAudio's fault. For example, the original flame of Jeffrey's was about the low volume that he experienced when running PA. This is mostly due to the suckish way we initialize the default volumes of ALSA sound cards. Most distributions have simple scripts that initialize ALSA sound card volumes to fixed values like 75% of the available range, without understanding what the range or the controls actually mean. This is actually a very bad thing to do. Integrated USB speakers for example tend export the full amplification range via the mixer controls. 75% for them is incredibly loud. For other hardware (like apparently Jeffrey's) it is too low in volume. How to fix this has been discussed on the ALSA mailing list, but no final solution has been presented yet. Nonetheless, the fact that the volume was too low, is completely unrelated to PulseAudio.

PulseAudio interfaces with lower-level technologies like ALSA on one hand, and with high-level applications on the other hand. Those systems are not perfect. Especially closed-source applications tend to do very evil things with the audio APIs (Flash!) that are very hard to support on virtualized sound systems such as PulseAudio [2]. However, things are getting better. My list of issues I found in ALSA is getting shorter. Many applications have already been fixed.

The reflex "my audio is broken it must be PulseAudio's fault" is certainly easy to come up with, but it certainly is not always right.

Also note that -- like many areas in Free Software -- development of the desktop audio stack on Linux is a bit understaffed. AFAIK there are only two people working on ALSA full-time and only me working on PulseAudio and other userspace audio infrastructure, assisted by a few others who supply code and patches from time to time, some more and some less.

More Breakage to Come

I now tried to explain why the audio experience on systems with PulseAudio might not be as good as some people hoped, but what about the future? To be frank: the next version of PulseAudio (0.9.11) will break even more things. The 'glitch-free' stuff mentioned above uses quite a few features of the underlying ALSA infrastructure that apparently noone has been using before -- and which just don't work properly yet on all drivers. And there are quite a few drivers around, and I only have a very limited set of hardware to test with. Already I know that the some of the most popular drivers (USB and HDA) do not work entirely correctly with 'glitch-free'.

So you ask why I plan to release this code knowing that it will break things? Well, it works on some hardware/drivers properly, and for the others I know work-arounds to get things to work. And 0.9.11 has been delayed for too long already. Also I need testing from a bigger audience. And it is not so much 0.9.11 that is buggy, it is the code it is based on. 'Glitch-free' PA 0.9.11 is going to part of Fedora 10. Fedora has always been more bleeding edge than other other distributions. Picking 0.9.11 just like that for an 'LTS' release might however be a not a good idea.

So, please bear with me when I release 0.9.11. Snapshots have already been available in Rawhide for a while, and hell didn't freeze over.

The Distributions' Role in the Game

Some distributions did a better job adopting PulseAudio than others. On the good side I certainly have to list Mandriva, Debian[3], and Fedora[4]. OTOH Ubuntu didn't exactly do a stellar job. They didn't do their homework. Adopting PA in a distribution is a fair amount of work, given that it interfaces with so many different things at so many different places. The integration with other systems is crucial. The information was all out there, communicated on the wiki, the mailing lists and on the PA IRC channel. But if you join and hang around on neither, then you won't get the memo. To my surprise when Ubuntu adopted PulseAudio they moved into one of their 'LTS' releases rightaway [5]. Which I guess can be called gutsy -- on the background that I work for Red Hat and PulseAudio is not part of RHEL at this time. I get a lot of flak from Ubuntu users, and I am pretty sure the vast amount of it is undeserving and not my fault.

Why Jeffrey's distro of choice (SUSE?) didn't package pavucontrol 0.9.6 although it has been released months ago I don't know. But there's certainly no reason to whine about that to me and bash me for it.

Having said all this -- it's easy to point to other software's faults or other people's failures. So, admitting this, PulseAudio is certainly not bug-free, far from that. It's a relatively complex piece of software (threading, real-time, lock-free, sensitive to timing, ...), and every software has its bugs. In some workloads they might be easier to find than it others. And I am working on fixing those which are found. I won't forget any bug report, but the order and priority I work on them is still mostly up to me I guess, right? There's still a lot of work to do in desktop audio, it will take some time to get things completely right and complete.

Calls for "audio should just work (tm)" are often heard. But if you don't want to stick with a sound system that was state of the art in the 90's for all times, then I fear things *will have* to break from time to time. And Jeffrey, I have no idea what you are actually hacking on. Some people mentioned something with Evolution. If that's true, then quite honestly, "email should just work", too, shouldn't it? Evolution is not exactly famous for it's legendary bug-freeness and stability, or did I miss something? Maybe you should be the one to start with making things "just work", especially since Evolution has been around for much longer already.

Back to Work

Now that I responded to Jeffrey's FUD I think we all can go back to work and end this flamefest! I wish people would actually try to understand things before writing an insulting rant -- without the slightest clue -- but with words like "clusterfuck". I'd like to thank all the people who commented on Jeffrey's blog and basically already said what I wrote here now.

So, and now I am off hacking a bit on PulseAudio a bit more -- or should I say in Jeffrey's words: on my clusterfuck that is an epic fail and that no desktop user needs?

Footnotes

[1] BTW 'glitch-free' is nothing I invented, other OS have been doing something like this for quite a while (Vista, Mac OS). On Linux however, PulseAudio is the first and only implementation (at least to my knowledge).

[2] In fact, Flash 9 can not be made fully working on PulseAudio. This is because the way Flash destructs it's driver backends is racy. Unfixably racy, from external code. Jeffrey complained about Flash instability in his second post. This is unfair to PulseAudio, because I cannot fix this. This is like complaining that X crashes when you use binary-only fglrx.

[3] To Debian's standards at least. Since development of Debian is very distributed the integration of such a system as PulseAudio is much more difficult since in touches so many different packages in the system that are kind of private property by a lot of different maintainers with different views on things.

[4] I maintain the Fedora stuff myself, so I might be a bit biased on this one... ;-)

[5] I guess Ubuntu sees that this was a bit too much too early, too. At least that's how I understood my invitation to UDS in Prague. Since that summit I haven't heard anything from them anymore, though.


Conferences and Laptop Bags

Conference organizers! Be attentive to the signs of times! There's a trend towards smaller laptops. Don't hand out laptop bags where those newer laptops (such as the X60) fits in two or more times! Lighter is better!


Being Smart

Last weekend I set myself the task to write an ATA S.M.A.R.T. (i.e. hard disk health monitoring) reader and parser. After spending some time reading all kinds of T13 and T10 docs and a bit of hacking I now present you the following new software:

  • libatasmart: a lean, small and clean implementation of an ATA S.M.A.R.T. reading and parsing library. It's fairly comprehensive, however I only support a subset of the full S.M.A.R.T. set of functions: those parts which made sense to me, not the esoteric stuff. Here's the API and here's the README.
  • skdump: a little tool that produces a similar output to smartctl -a, but uses libatasmart.
  • sktest: a little tool for starting/aborting S.M.A.R.T. self-tests, based on libatasmart
  • gnome-disk-health-service: a little wrapper around libatasmart that exports its entire functionality via D-Bus, so that unpriviliged processes can introspect a drive's health records, including temperature, number of bad sectors and suchlike. This is written in Vala, which BTW is awesome for doing D-Bus services. Actually after having done this once now I really hope I will never have to write a D-Bus server without Vala again. I also wrote a Vala .vapi file for libatasmart which is shipped in its tarball.
  • gnome-disk-health: a little tool that reads the S.M.A.R.T. data from g-d-h-s and presents it in a pretty dialog. Includes support for viewing attributes and starting self-tests and stuff. Also written with Vala.

Why? You might ask what the point of all this stuff is where smartmontools already exists. What I'd like to see on future GNOME desktops is that as soon as a disk starts to fail a notification bubble pops up warning the user about this fact, and suggesting that he makes backups and replaces the disk. For a tight integration into the desktop, a S.M.A.R.T. implementation that is small, and not C++, and a library (i.e. embeddable into other software with a sane interface) is highly preferable. Also, stuff like distribution installers should link against libatasmart to warn the user about old, and defective disks before he even starts the installation on them. (Hey, anaconda developers! That means you! It's a tiny library, and all you need to do is a single call: int sk_disk_smart_status(SkDisk *d, SkBool *good);)

Please note that I certainly don't plan to replace smartmontools. libatasmart will always implement only a subset of S.M.A.R.T. If you want the full set of functionality then please refer to smartmontools.

Where's this going? I plan to fully maintain libatasmart (including skdump and sktest) for the future. However g-d-h and g-d-h-s will probably just bitrot in my repository -- unless someone else wants to pick this up and maintain it. The reason my further interest in those tools is rather limited is that for the long run we will hopefully will see davidz's DeviceKit-disks (screenhot) changed to use this library for health monitoring. Then DK-d will export the S.M.A.R.T. info on the bus, and a separate daemon would not be necessary anymore. DK-d provides a single interface for all kinds of health parameters for storage, including RAID health and suchlike. I thus think this is the way forward and not g-d-h-s. (That should, of course, not hinder anyone to step up and take up maintainership of g-d-h/g-d-h-s if he wants to. There might be good reasons for doing so. Maybe because you need something to do, or because you want a S.M.A.R.T. solution for the desktop now, and not wait until DeviceKit gets pushed into all the distros).

So, here's where you can get this stuff:

git://git.0pointer.de/libatasmart.git

git://git.0pointer.de/gnome-disk-health.git

Browse the GIT repos.

I will roll a 0.1 tarball of libatasmart soon. I'd be thankful if people could run skdump on their disks and check if its output is basically the same as smartctl -a's. Especially people with BE machines.

Of course the most important part of a software announcement is always the screenshot:

Smart-Ass!

return -ETOOMANYDOTS;


On Version Control Systems

Here's what I have to say about today's state of version control systems in Free Software:

We shouldn't forget that a VC system is just a development tool. Preferring one over the other is nothing that has any direct influence on code quality, it doesn't make your algorithms perform any better, or your applications look prettier. It's just a tool. As such it should just do its job and get out of the way. A programmer should have religious arguments about code quality, about algorithms or about UIs, but what he certainly should not have is religious arguments over the feature set of specific VCSes[1].

Does this mean it doesn't matter at all which VCS to choose? No, of course it does matter a lot. The step from traditional VCSes to DVCS is a major one, an important one. Starting a fresh new Free Software project today and choosing CVS or SVN is anachronistic at best.

Which leaves of course the question, which DVCS to pick. If you take the "get out of the way" requirement seriously than there can only be one answer to the question: GIT. Why? It certainly (still) has a steep learning curve, and a steeper one than most other VC systems. But what is even harder to learn than GIT is learning all of GIT, Mercurial, Monotone, Bizarre^H^H^H^H^H^H^HBazaar, Darcs, Arch, SVK at the same time. If every project picked a different VCS system, and you'd want to contribute to more than just a single project, then you'd have to learn them all. And learning them all means learning them all not very well. And needing to learn them all means scaring people away who don't want to learn yet another VCS just to check out your code. Fragmentation in use of VCSes for Free Software projects hinders development.

Which brings me to the main point I want to raise with this blog story:

It is much more important to make contributing to Free Software projects easy by choosing a VCS everyone knows well -- than it is to make it easy by choosing a VCS that everyone could learn easily.

So, and which VCS is it that has a chance of qualifying as "everyone knows well" and is a DVCS? I would say there is only one answer to the question: GIT. Sure, there are some high-profile projects using HG (Mozilla, Java, Solaris), but my impression is that the vast majority of projects that are central to free desktops do use GIT.

Certainly, some DVCSes might be nicer than others, there might be areas where GIT is lacking in comparison to others, but those differences are tiny. What matters more is not scaring contributors away by making it hard for them to contribute by requiring them to learn yet another VCS.

Yes, with CVS, SVN and GIT I think I have learned enough VC systems for now. My hunger for learning further ones is exactly zero. Let me just code, and don't make it hard for me by asking me to learn your favourite one, please.

Or in other, frank words, if you start a new Open Source project today, and you don't choose GIT as VCS then you basically ask potential contributors to go away.

ALSA recently switched from Mercurial to GIT. That was a good move.

So, please stop discussing which DVCS is the best one. It doesn't matter. Picking one that everyone knows is far more important.

That's all I have to say.

Footnotes

[1] Of course, unless he himself develops a VC system.


FOMS 2009 CFP

And here's a another conference CFP, this time for Foundations of Open Media Software 2009 (FOMS). It's simply the best conference about multimedia on free systems. Period.

It's the third iteration now, and the first two were plain awesome, so don't miss this one. It happens in Hobart, Tasmania, next to linux.conf.au 2009.

FOMS Logo

Send in your paper! Attend! Spread the word!


Linux Plumbers Conference CFP

The Call for Papers for the Linux Plumbers Conference in September in Portland is out now. It's a conference about the core infrastructure of Linux systems: the part of the system where userspace and the kernel interface. It's the first conference where the focus is specifically on getting together the kernel people who work on the userspace interfaces and the userspace people who have to deal with kernel interfaces. It's supposed to be a place where all the people doing infrastructure work sit down and talk, so that each other understands better what the requirements and needs of the other are, and where we can work towards fixing the major problems we currently have with our lower-level APIs.

I am running the Audio microconf of the Plumbers Conference. Audio infrastructure on Linux is still heavily fragmented. Pro, desktop and embedded worlds are almost completely seperate worlds. While we have quite good driver support the user experience is far from perfect, mostly due because our infrastructure is so balkanized. Join us at the Plumbers Conference and help to fix this! If you are doing audio infrastructure work on Linux, make sure to attend or -- even better -- submit a paper!

Sign up soon! Send in your paper early! The conference is expected to sell out pretty quickly!

Plumbers Logo

See you in Portland!

© Lennart Poettering. Built using Pelican. Theme by Giulio Fidente on github. .