<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text">Gianluca Guida's personal page.</title>
  <id>http://tlbflush.org/atom.xml</id>
  <updated>2025-02-17T00:00:00Z</updated>
  <link href="http://tlbflush.org" />
  <link href="http://tlbflush.org/atom.xml" rel="self" />
  <author>
    <name>Gianluca Guida</name>
    
    <email>glguida@gmail.com</email>
    
  </author>
  
  
  <entry>
    <title type="text">A Short Guide to the GGUF Format</title>
    <id>http://tlbflush.org/post/2025_02_17_gguf_weekend/</id>
    <updated>2025-02-17T00:00:00Z</updated>
    <published>2025-02-17T00:00:00Z</published>
    <link href="http://tlbflush.org/post/2025_02_17_gguf_weekend/" />
    <author>
      <name>Gianluca Guida</name>
      
      <email>glguida@gmail.com</email>
      
    </author>
    <content type="html"><![CDATA[<p>As my recent <a href="https://fosdem.org/2025/schedule/event/fosdem-2025-5975-porting-ggml-to-the-nux-kernel-development-framework-/">FOSDEM
talk</a>
suggests, I find the <a href="https://github.com/ggml-org">GGML</a> ecosystem
an effective way to introduce system programmers such as myself to the
fancy new fashion of AI &ndash; meaning transformers and their uses.</p>
<p>For my FOSDEM <a href="https://github.com/glguida/blasbare">demo</a>, which ported
GGML to my <a href="https://nux.tlbflush.org">kernel library</a>, I wrote a crude
GPT-2 implementation based on GGML&#x27;s official
<a href="https://github.com/ggml-org/ggml/tree/master/examples/gpt-2">example</a>.</p>
<p>This demo led me to focus on how real models &mdash; like those running on
<code>llama.cpp</code> &mdash; are actually distributed.  The answer is GGUF, GGML&#x27;s
attempt at creating a universal format for distributing models.</p>
<p>I spent a weekend hacking away at GGUF, and here’s what I figured out.</p>
<h2 id="gguf-as-a-universal-format">GGUF as a universal format</h2>
<p><a href="https://github.com/ggml-org/ggml/blob/master/docs/gguf.md">GGUF</a> is
the latest format understood by <code>llama.cpp</code> to load and run models.</p>
<p>Its
<a href="https://github.com/ggml-org/ggml/blob/master/docs/gguf.md#historical-state-of-affairs">history</a>
points to an organic evolution &mdash; GGML, GGMF, GGJT, and now GGUF &mdash; and
it is now at version three of the format.</p>
<p>The first thing to notice about GGUF is that it is meant to be
<code>mmap</code>&#x27;d into memory and thus data on disk appear in the same order
as they do in memory.</p>
<p>What about <strong>endianness</strong>? Well, it is <strong>implicit</strong>. Version three, the
current version of the GGUF format, lets data be stored in
<em>big-endian</em>, but there is no flag whatsoever to signal this.</p>
<p><em>Interesting</em> choice, but it does make sense, little-endian is
expected in 2025: in my recent experience, big-endian machines are
either embedded routers or <em>hypotheses</em>.</p>
<p>Having cleared the encoding on disk, let&#x27;s have a look at what a GGUF
file is.</p>
<h2 id="gguf--an-overview">GGUF: An overview</h2>
<p>In broad strokes, a GGUF file is composed of:</p>
<ol>
<li>A fixed-size header</li>
<li>A key-value store</li>
<li>A list of typed, named tensors</li>
</ol>
<hr />
<p><img src="/img/gguf.svg" alt="GGUF structure"></p>
<hr />
<p>Let&#x27;s go down section by section and see how they&#x27;re actually represented on disk.</p>
<h2 id="the-gguf-header">The GGUF header</h2>
<p>The GGUF header is a fixed-size data structure, and there are no surprises there.</p>
<pre><code>struct gguf_header_t {
     char     magic[4];
     uint32_t version;
     uint64_t tensor_count;
     uint64_t metadata_kv_count;
};</code></pre>
<p>The first four bytes contain the ASCII characters ‘G’, ‘G’, ‘U’, ‘F’
to identify the file as GGUF.</p>
<p>A 32-bit unsigned integer follows to indicate the version. Currently,
the latest version is <code>3</code>.</p>
<p>Next, there are two important fields:</p>
<ul>
<li><code>tensor_count</code>: how many tensors this model includes.</li>
<li><code>metadata_kv_count</code>: how many <em>key-value</em> elements the metadata has.</li>
</ul>
<p>How to find tensors and metadata is the core of GGUF parsing, and will
be the main topic of this post.</p>
<p>Let&#x27;s start with the <em>metadata</em>, because they are placed right after
the header.</p>
<h2 id="the-key-value-store---metadata--">The Key-Value Store (<em>Metadata</em>)</h2>
<p>Following the header is a sequence of key-value data. The format is:</p>
<pre><code>struct {
    struct gguf_string  key;
    uint32_t value_type;
    &#x2F;* Value appended here. *&#x2F;
};</code></pre>
<p>The metadata is named by the string <code>key</code>, and has type <code>value_type</code>.</p>
<p>A GGUF string has this format:</p>
<pre><code>struct gguf_string {
    uint64_t len;
    char str[0];
}</code></pre>
<p>Where <em>len</em> is the number of bytes composing the string, and <em>str</em> is
a non-NULL terminated string that follows the <em>len</em> field.</p>
<p>The type is stored in a 32-bit unsigned integer, which currently can be one of the following:</p>
<pre><code>enum gguf_metadata_value_type {
    GGUF_MVT_UINT8 = 0,
    GGUF_MVT_INT8 = 1,
    GGUF_MVT_UINT16 = 2,
    GGUF_MVT_INT16 = 3,
    GGUF_MVT_UINT32 = 4,
    GGUF_MVT_INT32 = 5,
    GGUF_MVT_FLOAT32 = 6,
    GGUF_MVT_BOOL = 7,
    GGUF_MVT_STRING = 8,
    GGUF_MVT_ARRAY = 9,
    GGUF_MVT_UINT64 = 10,
    GGUF_MVT_INT64 = 11,
    GGUF_MVT_FLOAT64 = 12,
};</code></pre>
<p>Most of the fields should be self-descriptive, but there are two
types that need a bit more explanation: <em>bool</em> and <em>array</em>.</p>
<p><strong>bool</strong> is a one-byte value, where <em>zero</em> is <code>false</code>.</p>
<p><strong>array</strong> is what makes metadata parsing complicated.</p>
<p>When an element is described as an array, the following structure is appended:</p>
<pre><code>struct gguf_array {
    uint32_t type;
    uint64_t len;
}
&#x2F;* &#x27;len&#x27; elements of type &#x27;type&#x27; follow *&#x2F; </code></pre>
<p><code>type</code> is, once again, described by the <code>enum gguf_metadata_value_type</code> above.</p>
<p>What is complicated about <em>this</em>, you ask? Well, the type of an array
can be <code>GGUF_MVT_ARRAY</code>, so you can have multi-dimensional arrays
described in the metadata.</p>
<p>Powerful, but requires care during parsing.</p>
<p>The number of metadata elements in the file is specified by the GGUF header field <code>metadata_kv_count</code>.</p>
<p>After this, the <em>tensor store</em> begins.</p>
<h2 id="the-tensor-store">The Tensor Store</h2>
<p>Tensors are stored in two separate structures: the <strong>Tensor Info Array</strong> and the <strong>Tensor Data</strong>.</p>
<p>The <em>Tensor Info Array</em> starts right at the end of the metadata, and is a sequence of these fields:</p>
<ul>
<li><code>struct gguf_string name</code>: a GGUF string naming the tensor</li>
<li><code>uint32_t ndims</code>: A 32-bit integer indicating the <em>number of dimensions</em> of the tensor:</li>
<li><code>uint64_t dims[ndims]</code>: The size of each dimension follows as a sequence of <code>ndims</code> 64-bit integers.</li>
<li><code>uint32_t ggmltype</code>: The type of data stored in the tensor. This is defined as <code>enum
 ggml_type</code>, and contains data type natively supported by GGML.
 The <code>enum</code> is too long to list here, but supports from common floating
 point data to quantization formats.</li>
<li><code>uint64_t offset</code>: A 64-bit <em>offset</em>. This offset is counted from the start of the <strong>tensor data</strong>, which is the region following the tensor info array.</li>
</ul>
<p>And here lies the real <em>surprise</em> of the format: the  <strong>tensor data alignment</strong>.</p>
<h2 id="the-tensor-data">The Tensor Data</h2>
<p>The tensor data do not start immediately after the tensor info. Tensor data
can be aligned to a specific boundary. This is important, I believe,
because aligned data do speed up certain instructions &ndash; AVX
for example &ndash; and in some cases it might even be required. This allows data
to maintain alignment when the file is <code>mmap</code>d.</p>
<p>There&#x27;s a caveat though. The value of the alignment is stored in the
<strong>metadata</strong>. More specifically, the alignment value is a 32-bit
integer under the key <code>general.alignment</code>. If such metadata is not
present, the default alignment value is <code>32</code>.</p>
<p>Tensor data starts at the <strong>next alignment boundary</strong> from the end of
the tensor info array.</p>
<p>After this simple calculation, the rest is easy. The tensor info
includes an offset, and this offset &ndash; which <em>must</em> be aligned &ndash; is
added to the start of the tensor data, to retrieve the actual tensor.</p>
<p>We have now described how to scan the header, read the metadata, and
retrieve the tensors. This is all that there is in a GGUF file.</p>
<h2 id="conclusions">Conclusions</h2>
<p>The GGUF format was in a way a pleasant surprise. It&#x27;s a simple,
binary format that is almost self-explanatory.</p>
<p>There are things I would have done differently. Here are the things
that left me a bit puzzled about it:</p>
<p> - <strong>Tensor data alignment value in metadata</strong>. I think that inserting
a file&#x27;s structural information inside a high-level data structure &ndash;
<code>general.alignment</code> &ndash;  rather than in a quickly accessible field is
a <em>suboptimal</em> choice. But once again, I don&#x27;t know how this file
format evolved, and, as everything in software engineering, certain
choices make sense only when seen through the lens of historical
evolution.</p>
<p> - <strong>Everything is serial</strong>. In order to find the tensor info array,
or the tensor data start, we have to scan everything before
it. Including parsing the metadata.</p>
<p>The picture of the perfect GGUF variant in my head is something
with this header format:</p>
<pre><code>struct gguf_header_t {
     char     magic[4];
     uint32_t version;
     uint64_t tensor_offset;      &#x2F;* NEW *&#x2F;
     uint64_t tensor_count;
     uint64_t metadata_kv_offset; &#x2F;* NEW *&#x2F;
     uint64_t metadata_kv_count;
     uint64_t tensor_data_offset; &#x2F;* NEW *&#x2F;
};</code></pre>
<p>I.e., adding three offsets to the header, indicating the start of each
section: <em>tensor info</em>, <em>tensor data</em> and <em>metadata</em>.</p>
<p>This would have allowed for arbitrary alignment to be implicitly
supported without the need for special metadata keys. It would also
allow any scanner to quickly find the section it is searching for.</p>
<p>Moreover, this would allow a more flexible layout regarding the ordering of
the sections, without complicating the creation and saving of the
file.</p>
<p>In any case, nothing is perfect in this world, and all in all I am
pleasantly surprised by the simplicity and expandability of this
format.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="text">NUX and GGML: Bringing AI to Kernel Space.</title>
    <id>http://tlbflush.org/post/2025_01_15_nux_ggml/</id>
    <updated>2025-01-15T00:00:00Z</updated>
    <published>2025-01-15T00:00:00Z</published>
    <link href="http://tlbflush.org/post/2025_01_15_nux_ggml/" />
    <author>
      <name>Gianluca Guida</name>
      
      <email>glguida@gmail.com</email>
      
    </author>
    <content type="html"><![CDATA[<p>In the past few months, mostly pushed by friends more knowledgeable
than me in this field, I started &ndash; something not exactly <em>original</em> &ndash;
to divert my <em>attention</em> to the recent improvements in machine learning
and AI.</p>
<p>I had an alternating fascination with the field over the years. The
first time I had real interest as an adult engineer was in 2010,
after I watched the <a href="https://www.ted.com/talks/jeff_hawkins_how_brain_science_will_change_computing">Jeff Hawkins&#x27; 2002 TED
talk</a>. If
you haven&#x27;t watched it, watch it now; it&#x27;s a brilliant talk!</p>
<p>I was living in Amsterdam at the time, and I remember spending every
possible hour outside work tinkering with the idea of prediction. I
remember downloading the first Numenta&#x27;s whitepaper about their
Cortical Learning Algorithm and I did what I <em>usually</em> do when I want
to understand something: I <a href="https://github.com/glguida/libhacktm-old">reimplemented
it</a>. <a href="https://github.com/glguida/hacktm/">Twice</a>.</p>
<p>Speaking of Numenta, they&#x27;re definitely up to something. Their
recent
<a href="https://www.numenta.com/resources/research-publications/papers/">papers</a>,
although I have read them only lightly, look extremely promising and
super-interesting. If you haven&#x27;t already, check their <a href="https://thousandbrains.org">Thousand
Brains Project</a>. Seems like a place to
spend a lifetime of fun.</p>
<p>But of course, today all the discourse is about everything that
happened since <a href="https://en.wikipedia.org/wiki/Attention_Is_All_You_Need">this
paper</a>.
And I couldn&#x27;t ignore it.</p>
<h2 id="ggml-to-the-rescue">GGML to the rescue</h2>
<p>Personal taste here, but in order for me to experiment with things, I
<em>need</em> to find a way to experiment without resorting to <em>Python</em>.</p>
<p>I have been briefly exposed to <em>PyTorch</em> at work, and that was enough
experience for me.</p>
<p>I thought for some time that this meant that the whole AI thing would
be out of touch for me, but then a friend pointed me to GGML.</p>
<p><a href="https://github.com/ggerganov/ggml">GGML</a> is a tensor library used by
projects such as <a href="https://github.com/ggerganov/llama.cpp">Llama.cpp</a>. In the repository
<a href="https://github.com/ggerganov/ggml/tree/master/examples">examples</a>,
you can find even some simple but effective GPTs.</p>
<p>It was originally meant to support CPUs only (and aarch64 Macs in
particular), but now has backends for BLAS, OpenMP and hardware
platforms.</p>
<p>The code &ndash; that has all the obvious signs of a fast growing project
&ndash; is a mix of C and minimal C++. On a cursory glance, it seems to
be architected in this way:</p>
<ul>
<li>A set of tools to open, save and load models.</li>
<li>Functions that create a computational graph from the models.</li>
<li>A VM that executes the computational graphs in a thread pool.</li>
</ul>
<p>What I liked about GGML is that the architecture makes sense and it&#x27;s
easily hackable, if you can stomach <em>cmake</em>.</p>
<h2 id="ggml-in-kernel-space-">GGML in kernel space.</h2>
<p>The goal of my <a href="https://nux.tlbflush.org">NUX prototyping kernel
framework</a> is to be able to quickly create
custom kernels. It has its own libc &ndash; <em>libec</em>, based on the NetBSD
libc &ndash; and a powerful memory management and this means that as long
as file I&#x2F;O is not required, you should be able to port any C program
to run in kernel mode.</p>
<p>Another thing that NUX offers is the ability to completely control
what the hardware is doing. If I run some code in kernel space in a
CPU, I can make sure that nothing will ever interrupt it.</p>
<p>NUX supports IPIs, so we can use that (or simple SMP barriers) to
syncronize among them.</p>
<p>I realised quickly that this works really well with GGML
architecture. You could for example, boot a machine and assigns all
their secondary CPUs to the GGML threadpool, while using the
<em>bootstrap</em> CPU for system control and drivers.</p>
<p>Of course, I decided to implement that. And make a <a href="https://fosdem.org/2025/schedule/event/fosdem-2025-5975-porting-ggml-to-the-nux-kernel-development-framework-/">FOSDEM talk</a> about
the effort!</p>
<h2 id="an-early-prototype">An early prototype</h2>
<p>Today, I published a github something that has been living <em>dangerously unbacked up</em>
in my machine for the past few months:
<a href="https://github.com/glguida/blasbare/"><em>blasbare</em></a>.</p>
<p>It has been my workspace for running various experiment of porting
various computing architectures to NUX.</p>
<p>As it stands, there&#x27;s a simple
<a href="https://github.com/glguida/blasbare/blob/main/kern/main.c">kernel</a>
that runs the <code>simple-ctx</code> GGML example.</p>
<p>Despite the simplicity of it, it compiles the full GGML library with
the CPU backend.</p>
<p>Works still needs to be done, and the documentation is lacking, but
it&#x27;s the early days.</p>
<p>This project will be discussed more in detail at <a href="https://fosdem.org/2025/schedule/event/fosdem-2025-5975-porting-ggml-to-the-nux-kernel-development-framework-/">FOSDEM
2025</a>
in Brussels later this month. Hope to see you there!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="text">Why would one rewrite Mach?</title>
    <id>http://tlbflush.org/post/2025_01_06_machina/</id>
    <updated>2025-01-06T00:00:00Z</updated>
    <published>2025-01-06T00:00:00Z</published>
    <link href="http://tlbflush.org/post/2025_01_06_machina/" />
    <author>
      <name>Gianluca Guida</name>
      
      <email>glguida@gmail.com</email>
      
    </author>
    <content type="html"><![CDATA[<p>Over the past few months, I have been writing a microkernel aiming at
being effectively a <a href="https://github.com/glguida/machina">Mach
rewrite</a>.</p>
<p>The response to this, when I shared my plans with friends who share
my interests, was a simple, almost desperate question:
<em>Why, Gianluca, why would you do that?</em>.</p>
<p>Definitely an interesting question, so I thought this would be a good
time to write about it.</p>
<h2 id="the-many-reasons-behind-a-personal-project-">The many reasons behind a personal project.</h2>
<p>In the spring of 2024, I picked up an old project of mine,
<a href="https://nux.tlbflush.org"><em>NUX</em></a>, and brought it to a state where I
could easily build portable kernels that would run on real, modern
hardware.</p>
<p>The first test I made was to port <a href="https://mhsys.org">Murgia Hack</a> to
NUX. The port, which allowed MH to run on two new architectures, RISCV64
and AMD64, was straightforward. But the architecture of NUX is heavily
influenced by the design choices of MH, so it was not meant to be
difficult.</p>
<p>What I needed was a challenging kernel. I briefly considered a
classical UNIX system, but then it occurred to me that there&#x27;s always
been a microkernel-shaped hole in my life: <em>Mach</em>.</p>
<h2 id="mach-and-me--a-personal-story-">Mach and me, a personal story.</h2>
<p>I remember the first time I downloaded the <a href="https://www.gnu.org/software/hurd/hurd.html">GNU
Hurd</a> sources. It was the
mid-to-late 1990s. The first thing I&#x27;ve heard about the Hurd was this
famous <em>Linus Torvalds</em> quote:</p>
<blockquote><p><em>In short: just say NO TO DRUGS, and maybe you won&#x27;t end up like the Hurd people.</em></p>
</blockquote>
<p>Truth is, the promise of GNU Hurd was <em>exciting</em>. It was considered
the next big thing. It was an architecture that allowed to play with
the fundamentals of a UNIX system by providing <em>translators</em>.</p>
<p>It took me days to finally build the toolchain and the sources, and
being able to boot <em>GNU Mach</em> on my 486.</p>
<p>At that time &ndash; I was a high school student &ndash; I was able to
understand some parts of the Linux Kernel code, and so I decided to
study Mach. And so, with the usual youth optimism and fearlessness, I
printed the <a href="http://www.shakthimaan.com/downloads/hurd/kernel_principles.pdf">&quot;Mach 3 Kernel
Principles&quot;</a>
on my super-slow early <em>ink jet</em> printer, and spent nights reading
it. Then I started looking at the code.</p>
<h3 id="me-and-the-mach-source-code-">Me and the Mach source code </h3>
<p>If you have ever looked at Mach&#x27;s source code, you <em>know</em> where this
story is going. I was unprepared for that code. I finally understood
something my mediocre Italian Literature teacher was failing to
explain at the time: <em>Dante</em>&#x27;s trip to the <em>Inferno</em>.</p>
<p>There was something off about the purity, clarity of the Mach
principles and architecture, as explained in the documentation, and its
code.</p>
<p>I was always interested in memory management, and the VM code seemed
to me, at the time, designed to bring the worst doubts about one&#x27;s own
capacity to understand things.</p>
<p>As a reaction, I went on and decided to start writing my own
kernel. But this is another story.</p>
<p>I stayed around the Hurd community for quite some time. Despite what
<em>Linus</em> said about them &ndash; <em>us?</em> &ndash;, there were some really nice people in
the project. Many of them I still consider my friends.</p>
<h3 id="sto-mach">Sto-Mach</h3>
<p>With the years, I started being a bit more at ease with the Mach
source code, and in 2005, as a late computer engineering student at
university, I presented <em>Sto-Mach</em> at the Hurd Meeting in
Madrid. <em>Sto-Mach</em> was my reaction to a project called
<code>oskit-mach</code>. Based on
<a href="https://www-old.cs.utah.edu/flux/oskit/">oskit</a>, <code>oskit-mach</code> removed
much of the Mach core and substituted it with oskit
<em>components</em>. <em>Sto-Mach</em> did the opposite. Kept the core of Mach
intact, removed the Linux 2.0 driver glue code, and used oskit
components as drivers.</p>
<p>The result was <em>slightly</em> better. Now we had Linux <em>2.2</em> drivers, but
more importantly a
<a href="https://en.wikipedia.org/wiki/Component_Object_Model">COM-based</a>
interface &ndash; yes, oskit packaged other operating system source code in
this <em>Microsoft standard</em> &ndash; to write drivers.</p>
<p>I have recently found the source code of Sto-Mach in my archives. I
will be writing a blog post about it. For now, only slides of that
presentation are available on my <a href="https://tlbflush.org/talks">talks</a>
page.</p>
<p>Shortly after that, I left university, and started my career as a
hypervisor engineer. And I forgot many details about Mach.</p>
<h2 id="machina--a-nux-based-mach-clone-">MACHINA: a NUX-based Mach clone.</h2>
<p>Now, should be clearer why, when faced with the choice of a challenging
kernel architecture to implement in NUX, I <em>chose</em> Mach.</p>
<p>By coincidence, right when I was thinking about doing it, I found,
while unpacking the boxes after the move to a new flat, my old printed
version of <em>Mach 3 Kernel Principles</em> I spent so many nights reading,
decades ago.</p>
<p>On re-reading it, I re-discovered my fascination for that architecture,
and decided that I wanted to rewrite Mach, to answer questions that
now, as a seasoned system engineer, I can finally face:</p>
<ol>
<li>Does Mach <em>have to</em> be this complex to achieve this functionality?</li>
<li>What would a <em>modern</em> Mach implementation look like?</li>
<li>What architectural decisions of Mach would not be made today?</li>
</ol>
<p>Is Mach easy to rewrite? No, it is not. Is this going to be a
replacement for Mach? No, it will not. But sometimes, the best way to
understand a system is reimplementing it.</p>
<p>I will introduce MACHINA at <a href="https://www.fosdem.org/2025/schedule/event/fosdem-2025-5490-machina-lessons-and-insights-from-reimplementing-the-mach-microkernel/">FOSDEM 2025</a>. See you there!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="text">Introducing NUX, a kernel framework</title>
    <id>http://tlbflush.org/post/2024_12_24_notes_nux/</id>
    <updated>2024-12-24T00:00:00Z</updated>
    <published>2024-12-24T00:00:00Z</published>
    <link href="http://tlbflush.org/post/2024_12_24_notes_nux/" />
    <author>
      <name>Gianluca Guida</name>
      
      <email>glguida@gmail.com</email>
      
    </author>
    <content type="html"><![CDATA[<h2 id="history-and-motivation">History and motivation</h2>
<p>Circa 2018, I decided that the <a href="https://mhsys.org">Murgia Hack System</a>
needed a fresh start to support newer architectures.</p>
<p>MH&#x27;s kernel is quite clean and simple, but suffers from an aging low
level support. Incredibly, some of that i386 code can be traced back to
my early experiments (in 1999!) and code that I wrote for my first SMP
machine &ndash; a dual Pentium III bought in Akihabara in the early 2000s!</p>
<p>Unfortunately, emotional attachment to code doesn&#x27;t create great
engineering, and I had to start from scratch.</p>
<p>The driving principle behind this effort &ndash; that later became NUX &ndash;
was to rationalise my kernel development.</p>
<p>At its core, a kernel is an <em>executable</em>, running in privileged
mode. It&#x27;s special because it handles exceptions, IRQs and syscalls,
essentially <em>events</em>, so it can be seen as an <em>event-based
program</em>. And it runs on multiple CPUs concurrently, we can even
draw similarities with <em>multi-threading</em>.</p>
<p>The very annoying and often project specific part of a kernel is the
<em>bootstrap</em>. A kernel usually starts in a mode that it&#x27;s either very
limited (think x86 legacy boot) or very different in terms of runtime
(think EFI).</p>
<p>A kernel is thus required to set up its own data structures (and
virtual memory), and then jump in it (through magic pieces of
assembler called <em>trampolines</em>).</p>
<p><em>In a nutshell, NUX can be seen as an attempt to solve all the
abovementioned problems that differentiate a kernel from a normal
executable.</em></p>
<h2 id="solving-the-bootstrapping-problem-">Solving the bootstrapping problem.</h2>
<p>To solve the setup of the kernel executable data structures, NUX
introduces <strong>APXH, an ELF bootloader</strong>. APXH &ndash; (upper case of αρχη),
greek for beginning &ndash; is a portable bootloader whose goal is to load
an ELF executable, create the page tables based on the ELF&#x27;s Program
Header, and jump to the entry point. It attempts to be the closest
thing to an <code>exec()</code> you can possibly have at boot.</p>
<p>APXH also supports special program header entries, &ndash; such as Frame
Buffer, 1:1 Physical Map, Boot Information page &ndash; that allows the
kernel to immediately use system features discoverable at boot,
further reducing low level initialisation.</p>
<p>APXH is extremely portable, and currently works on i386, AMD64 and
RISCV64, and also supports booting from multiple environemnts,
currently EFI, GRUB&#x27;s multiboot and OpenSBI.</p>
<h2 id="creating-an-embedded-executable--the-need-for-a-small-libc-">Creating an embedded executable: the need for a small libc.</h2>
<p>In order to create an executable in C, you&#x27;ll have to create against a
<em>C Runtime</em> (<code>crt</code>) and a <em>C Library</em>.</p>
<p>This is why NUX introduces <strong>libec</strong>, an embedded <em>quasi-standard</em>
libc.</p>
<p><em>libec</em> is based on the NetBSD libc, guaranteeing extreme portability
and simplicity. It is meant to be used as a small, <em>embedded libc</em>.</p>
<p>Every binary built by NUX &ndash; whether APXH, a NUX kernel, or the example
kernel&#x27;s userspace program &ndash; are all compiled against libec.</p>
<h2 id="a-kernel-as-a-c-executable-">A kernel as a C executable.</h2>
<p>As for any C-program, the kernel will have to define a <code>main</code>
function, that is called after the C-runtime has initialised. The
<code>libec</code> is complex enough to support constructors, so that you can,
define initialisation functions that run before main.</p>
<p>A special function of NUX, that diverts from normal C-programs, is
<code>main_ap</code>. This is a main funciton, that is called on secondary
processors, that is other processors that are not the bootstrapping
CPU.</p>
<h2 id="kernel-entries-as--events--">Kernel entries as <em>events</em>.</h2>
<p>As mentioned above, a kernel has to deal with requests from userspace
and hardware events. In NUX, this is done by defining entry functions
for these events.</p>
<p>The whole state of the running kernel can be defined by the
actions of these entry functions.</p>
<p>A kernel entry has a <em>uctxt</em> passed as a parameters and returns a
<em>uctxt</em>. <em>uctxt</em> is a <em>User Context</em>, the state of the userspace
program. The kernel can modify the User Context passed as an argument
and return the same one, or can return a completely new one.</p>
<p>The former is how system calls return a value, the latter is how you
implement threads and process switches.</p>
<h2 id="the-nux-library-interface">The NUX library interface</h2>
<p>Finally, NUX provides three libraries:</p>
<ol>
<li><em><code>libnux</code></em>: a machine-independent library that provides the higher level
funcitonalities you need to develop a fully functional OS kernel.
The &#x27;libnux&#x27; interface is
<a href="https://github.com/glguida/nux/blob/main/include/nux/nux.h">here</a>.</li>
</ol>
<ol>
<li><em><code>libhal</code></em>: This is a machine-dependent layer. Exports a common
interface to handle low level CPU functionalities.  The HAL
interface is
<a href="https://github.com/glguida/nux/blob/main/include/nux/hal.h">here</a>.</li>
</ol>
<ol>
<li><em><code>libplt</code></em>: This is a machine-dependent layer. Exports a common
interface to handle low level <em>Platform</em> functionalities, such as
device discovery, interrupt controller configuration and timer
handling. The Platform Driver interface is
<a href="https://github.com/glguida/nux/blob/main/include/nux/plt.h">here</a>.</li>
</ol>
<p>The separation between <code>hal</code> and <code>plt</code> is possibly a unique choice of
NUX, and allows, as many other design choices of NUX, for a gradual
and quick porting to new architectures.</p>
<p>For example, when the AMD64 support was added, the ACPI platform
library needed no changes, as the CPU mode was different but the
platform was exactly the same.</p>
<p>Similarly, an upcoming support for ACPI support for Risc-V consists
mostly on expanding the ACPI libplt to support Risc-V specific tables
and the different interrupt controllers.</p>
<h2 id="a-useful-tool-for-kernel-prototyping-">A useful tool for kernel prototyping.</h2>
<p>NUX goal is to remove the burden of bootstrapping a kernel. And be
portable.</p>
<p>The hope is that NUX will be useful to others the same way it has been
useful to me: experimenting with kernel and OS architectures, while
skipping the hard part of low level initialisation and handling.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="text">Controlling MRG LFOs frequency range</title>
    <id>http://tlbflush.org/post/lfofreq/</id>
    <updated>2020-12-09T00:00:00Z</updated>
    <published>2020-12-09T00:00:00Z</published>
    <link href="http://tlbflush.org/post/lfofreq/" />
    <author>
      <name>Gianluca Guida</name>
      
      <email>glguida@gmail.com</email>
      
    </author>
    <content type="html"><![CDATA[<p>The MRG LFOs contains two different oscillator cores, each capable of oscillating from slightly more than a second to about 600Hz.</p>
<p>Deciding the frequency range of an LFO is a matter of components selection, arbitrary decisions, physical limits and personal taste.</p>
<p>There are three components that control the timing of each LFO in the circuit:</p>
<ol>
<li>The resistance at the 1M potentiometer (Rp) in the front PCB.</li>
<li>The resistor (Rt).</li>
<li>The timing capacitor (Ct)</li>
</ol>
<hr />
<p><img src="/lfofreq_0.png" alt="lfo pcb"></p>
<p>Location of the components controlling the frequency range on a MRG LFOs PCB. <a href="/lfofreq_0.png">Click Here</a> for full picture.</p>
<hr />
<p>The mathematical relationship that regulates each LFO frequency is:</p>
<p><img src="/lfofreq_1.png" alt="lfo freq"></p>
<p>The highest frequency is realised when the potentiometer is at its minimum, and can be approximated by removing <em>Rp</em> from the equation:</p>
<p><img src="/lfofreq_3.png" alt="lfo freq"></p>
<p>and the lowest when the potentiometer is at its maximum, approximated by exchanging <em>Rp</em> with the value of the potentiometer:</p>
<p><img src="/lfofreq_2.png" alt="lfo freq"></p>
<p>The MRG LFOs as shipped and designed uses 1K for Rt and 1uF for Ct, creating the following theoretical frequency ranges:</p>
<p><img src="/lfofreq_4.png" alt="lfo freq">
<img src="/lfofreq_5.png" alt="lfo freq"></p>
<p>In general, these <em>default</em> frequency ranges can be changed by
modifying the elements involved. The suggested way of doing that is
changing the timing capacitor Ct. A bigger capacitance will make the
oscillator slower, a smaller one faster.</p>
<p>As long as the timing capacitor Ct is a non-polarised capacitor with a
5mm pitch, it should be fine. Of course, being a timing capacitor, the
suggestion is to use a film capacitor, although 5mm pitch film
capacitors at the micro-farad scale are unfortunately rare.</p>
<p>Using a Ct of 4.7 uF as a timing capacitor leads to the
following minimum and maximum frequencies:</p>
<p><img src="/lfofreq_6.png" alt="lfo freq">
<img src="/lfofreq_7.png" alt="lfo freq"></p>
<p>Of course, as everything analog these are approximations, and real
life measurement will be subject to some degree of error.</p>
<p>As an example, the picture below shows a MRG LFOs modified with a 4.7
uF capacitor in one of the oscillators (I am using the <a href="https://uk.rs-online.com/web/p/polyester-film-capacitors/1719241/">KEMET
R82CC4470Z330K</a>). The
measured frequency range was from .126 Hz (about 8 seconds) to 124Hz.</p>
<p><img src="/lfofreq_8.jpg" alt="lfo mod"></p>
]]></content>
  </entry>
  
  <entry>
    <title type="text">With apologies to Paganini</title>
    <id>http://tlbflush.org/post/mrgpaganini/</id>
    <updated>2020-09-27T00:00:00Z</updated>
    <published>2020-09-27T00:00:00Z</published>
    <link href="http://tlbflush.org/post/mrgpaganini/" />
    <author>
      <name>Gianluca Guida</name>
      
      <email>glguida@gmail.com</email>
      
    </author>
    <content type="html"><![CDATA[<p>This is what happens when you feed a MIDI file of Paganini&#x27;s Capriccio no. 24 to a monosynth built with <a href="https://mrgsynth.it/4HP">MRG modules</a>:</p>
<p>
<div class="video">
<iframe height="100%" width="100%" src="https://www.youtube.com/embed/33nMMmW6pTs" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<p>
<p>I find that violin, viola and cello compositions are perfect to test a simple monosynth, as they are mostly monophonic and they range across many octaves.</p>
<p>The Capriccio n.24 is perfect in this, and it actually overstretches the octaves over the limits of my 2HP MIDI module: unfortunately all sounds above C7 are flattened to C7. But it&#x27;s still a good demo of the MRG 3340 VCO&#x27;s excellent 1V&#x2F;O tracking.</p>
<p>I played a lot with the envelope generated by the MRG 3310 ADSR module. The envelope is actually fed to the 24db MRG LPF, that acts as a low pass gate.
You can see me tweaking the filter as well. The output of the filter is then passed to the MRG VCA, that amplifies the signal controlled by the MIDI velocity, and then to the output.</p>
<p>I use two MRG 3340 VCOs here, both sawtooth waves. They start being tuned at the same frequency, but around 2:09 I detune the second oscillator to an octave lower.</p>
<p>I feel extremely dirty for having destroyed one of classical music&#x27;s masterpieces, and I am probably in danger for having offended a composer who liked to spread the rumor of having sold his soul to the devil, but I admit that recording this was a lot of fun!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="text">The Yamaha DX7 Envelope Generator, part four.</title>
    <id>http://tlbflush.org/post/dx7eg4/</id>
    <updated>2020-05-12T00:00:00Z</updated>
    <published>2020-05-12T00:00:00Z</published>
    <link href="http://tlbflush.org/post/dx7eg4/" />
    <author>
      <name>Gianluca Guida</name>
      
      <email>glguida@gmail.com</email>
      
    </author>
    <content type="html"><![CDATA[<p><em>This is part four of a qualitative analysis of the DX7 Envelope Generator. The series begins <a href="/post/dx7eg1">here</a>.</em></p>
<p><em>Part three is <a href="/post/dx7eg3">here</a></em></p>
<h2 id="rising-segments-">Rising segments.</h2>
<p>As we&#x27;ve seen, the rising segment of an envelope in a DX7 is not a
pure exponential.</p>
<p>A really useful (and hackish) experiment is to divide the signal by a
linear function, to poke with linear components.</p>
<hr />
<p><img src="/img/dx7_envtest0_lindiv.png" alt="Signal divided by linear function"></p>
<p>The envelope divided by <em>(x - 86250)</em></p>
<hr />
<p>What emerge is that, once divided by <em>x</em>, the segment is markedly
linear. This is really good, as it seems to suggest that the segment
is <em>quadratic</em>.</p>
<p>Let&#x27;s go back at <code>raw2plot.c</code> and add a new column, that calculates
the square root of the envelope. Patch is <a href="https://github.com/glguida/dx7revtools/commit/3b08cc36296fdc1cd4adcea5ab040f881911b298">here</a>.</p>
<hr />
<p><img src="/img/dx7_envtest0_sqrt.png" alt="Square root of the envelope"></p>
<p>Square root of the envelope.</p>
<hr />
<p>The linear behaviour of the square root is even more evident here.</p>
<p>Let&#x27;s calculate the curve by finding the start and end samples of the segment. The maximum (1.0) is reached at sample 87427, while the signal appears to start (hard to tell given noise) at around 86275.</p>
<p>We have then that:</p>
<pre><code>s = (1.0 - 0.0) &#x2F; (87427 - 86275) = .000868</code></pre>
<p>and</p>
<pre><code>env(t) = V * s^2 * x^2</code></pre>
<p>Let&#x27;s try this against the signal:</p>
<pre><code>$ gnuplot
gnuplot&gt;  plot [86000:88000][-.1:1] &#x27;envtest0.plt&#x27; \
using 1:2 with lines, \
.8*.000868*.000868 *(x - 86275)*(x-86275)</code></pre>
<hr />
<p><img src="/img/dx7_envtest0_parabola.png" alt="Fitting of rising segment with a parabola"></p>
<p>Fitting of rising segment with a parabola.</p>
<hr />
<p>It is a good fitting, apart from noise and some offset at the start.</p>
<p>As our goal is not a precise reproduction of the wave generated by the DX7 &ndash; this will likely require an analysis at the hardware level &ndash; but a characterisation of the envelope, this fits well enough.</p>
<h2 id="putting-it-all-together">Putting it all together</h2>
<p>We now have enough data to reconstruct the envelope of <code>ENV TEST#0</code>.</p>
<p><a href="https://github.com/glguida/dx7revtools/blob/f92ad42b588b7a8ab573dcacee0c8c409e7147a2/envtest0.c"><code>envtest0.c</code></a> is a simple program that simulates the DX70 envelope at
rate 50 using the equations we just found and recreates the original
recording. Its output can be printed by gnuplot.</p>
<hr />
<p><img src="/img/dx7_envtest0_match1.png" alt="Match1">
<img src="/img/dx7_envtest0_match2.png" alt="Match2"></p>
<p>Comparison of <code>envtest0.c</code> output (&quot;syntehtic&quot;) versus envelope of the
recorded sound.</p>
<hr />
<p>As you can see, the result is incredibly close. There&#x27;s a lag of about
1000 samples (or 22usec) between on the start of segment three, which
is probably due to an incorrect calculation of level 50.</p>
<h2 id="conclusion">Conclusion</h2>
<p>We finally found out more about the DX7 envelope, and we were able to
recreate an envelope based on our measuring.</p>
<p>This is not all is needed to simulate completely the Envelope Generator of a DX7. Information lacking is:</p>
<ol>
<li>How the parameters <em>s</em> and <em>d</em> change when rate changes.</li>
<li>What&#x27;s the output value for all the levels.</li>
</ol>
]]></content>
  </entry>
  
  <entry>
    <title type="text">The Yamaha DX7 Envelope Generator, part three.</title>
    <id>http://tlbflush.org/post/dx7eg3/</id>
    <updated>2020-05-10T00:00:00Z</updated>
    <published>2020-05-10T00:00:00Z</published>
    <link href="http://tlbflush.org/post/dx7eg3/" />
    <author>
      <name>Gianluca Guida</name>
      
      <email>glguida@gmail.com</email>
      
    </author>
    <content type="html"><![CDATA[<p><em>Part two is <a href="/post/dx7eg2">here</a></em></p>
<h2 id="extracting-the-envelope-">Extracting the envelope.</h2>
<p>We concluded <a href="/post/dx7eg2">part two</a>
finding out that the DX7 envelope generator&#x27;s curves were:</p>
<ol>
<li>non linear, apparently exponential.</li>
<li>rising faster than they were decreasing.</li>
</ol>
<p>Let&#x27;s try to find out if the curves are indeed exponential.</p>
<p>Before we proceed, let&#x27;s modify our tool to plot only what really
interests us: the <em>envelope</em>. <a href="https://github.com/glguida/dx7revtools/commit/d4641782063542817c8775bc6bf81d8718db07ff">This
patch</a>
to <code>raw2plot.c</code> adds a crude envelope detector: it tracks the delta
between two samples to detect a local maximum when the derivative of
the signal changes sign from positive to negative.</p>
<p>The output will now contain two columns: the first one is the signal,
the second is the envelope.</p>
<pre><code>$ .&#x2F;raw2plot &lt; envtest0.raw &gt; envtest0.plt
$ gnuplot
gnuplot&gt; plot &#x27;envtest0.plt&#x27; using [1:3] with \
lines</code></pre>
<p>Now that we have the envelope extracted, we can finally try to
understand more about it without being distracted by the wave&#x27;s
oscillations.</p>
<hr />
<p><img src="/img/dx7_envtest0_env.png" alt="Plot of recording's envelope"></p>
<p><img src="/img/dx7_envtest0_env_zoom2.png" alt="Plot of the first two segments envelopel"></p>
<p>Our recording and the first two segments of our signal after being processed by the envelope detector.</p>
<hr />
<h2 id="normalising-the-envelope">Normalising the envelope</h2>
<p>The record we&#x27;re using has maximum volume at <em>0.794</em>. In order to make
our calculations more generic, let&#x27;s normalise this by setting the
maximum to <em>1.0</em>.</p>
<p>[This patch] adds support for an passing an optional normalisation
factor to <code>raw2plot</code>.</p>
<p>This command:</p>
<pre><code>$ .&#x2F;raw2plot .794 &lt; envtest0.raw &gt; envtest0.plt</code></pre>
<p>will produce the same graph as above, scaled so that value <em>0.794</em>
will become <em>1.0</em>.</p>
<p>For the rest of this article we&#x27;ll be using the normalised envelope.</p>
<h2 id="searching-for-exponentials-">Searching for exponentials.</h2>
<p>A quick and immediate way to check for exponential curves of the form
<em>exp(x * T)</em> in the envelope is to plot in logscale. this is easy in
<code>gnuplot</code>, using <code>set logscale y</code>.</p>
<pre><code>$ gnuplot
gnuplot&gt; set logscale y
gnuplot&gt; plot &#x27;envtest0.plt&#x27; using [1:3] with \
lines</code></pre>
<p>This will produce a <a href="https://en.wikipedia.org/wiki/Semi-log_plot"><em>log-linear</em></a> plot of the envelope: <em>abscissa</em> unmodified but <em>ordinate</em> in <em>log10</em>.</p>
<hr />
<p><img src="/img/dx7_envtest0_logscale.png" alt="Plot of recording's logscale evnelope"></p>
<p><img src="/img/dx7_envtest0_logscale_edges.png" alt="plot of recordings's logscale envelope edges"></p>
<p>Logscale (base 10) plot of the envelope, and details about segments.</p>
<hr />
<p>It emerges clearly that the decreasing edges are linear in log scale:
this means they&#x27;re pure exponential. The increasing segments are more
complicated.</p>
<h2 id="decreasing-segments">Decreasing segments</h2>
<p>We have finally collected enough data to find the equation of one
element of the DX7 envelope generator: the decreasing segments.</p>
<p>The first segments reaches the maximum (1.0) at sample 87427, and
decrease linearly until sample 91410. Near the zero the graph is
polluted by recording noise, so we take another earlier sample point
and find the <em>decay</em> <em>constant</em>. Let&#x27;s note that at sample 89640,
output is circa .1.</p>
<p>The slope of the segment in the log-linear plot is:</p>
<pre><code>d = -log(.1 &#x2F; 1) &#x2F; (89640 - 87427) = 0.00104</code></pre>
<p><code>d</code> is the decay constant we&#x27;re searching for, and a decreasing
envelope in DX7 at rate 50, with a sampling rate of 44100 sample&#x2F;secs,
will behave as:</p>
<pre><code>env(t) = V * exp(-d * t)</code></pre>
<p>where V is the maximum level reached by the envelope.</p>
<p>The time constant for rate 50 of a decreasing envelope is:</p>
<pre><code>T = 1&#x2F;d = 961.1 samples</code></pre>
<p>Let&#x27;s prove our theory comparing or wave with the exponential <em>0.8 * exp( -d * (x - 87427))</em>:</p>
<pre><code>$ gnuplot
gnuplot&gt;  plot [-1:1] &#x27;envtest0.plt&#x27; using 1:2 \
with lines, .8*exp(-.00104 * (x - 87427))</code></pre>
<hr />
<p><img src="/img/dx7_envtest0_fitted.png" alt="Fitted exponential"></p>
<hr />
<p>Our curve matches perfectly the decreasing envelope!</p>
<h2 id="conclusions">Conclusions</h2>
<p>What we have so far is a characterization of the decreasing segment of
an envelope in a DX7, with precise measurement for rate 50 (the one
recorded).</p>
<p>In part four, we&#x27;ll focus on the rising segments.</p>
<p>Updated sourcecode for <code>rawp2plot.c</code> is in the &#x27;part3&#x27; branch of <a href="https://github.com/glguida/dx7revtools">this
GitHub project</a>.</p>
<p><em>Part four is <a href="/post/dx7eg4">here</a></em></p>
]]></content>
  </entry>
  
  <entry>
    <title type="text">The Yamaha DX7 Envelope Generator, part two.</title>
    <id>http://tlbflush.org/post/dx7eg2/</id>
    <updated>2020-05-08T00:00:00Z</updated>
    <published>2020-05-08T00:00:00Z</published>
    <link href="http://tlbflush.org/post/dx7eg2/" />
    <author>
      <name>Gianluca Guida</name>
      
      <email>glguida@gmail.com</email>
      
    </author>
    <content type="html"><![CDATA[<p><em>Part one is <a href="/post/dx7eg1">here</a></em></p>
<h2 id="a-closer-look-at-an-operator-envelope-">A closer look at an operator envelope.</h2>
<p>Let&#x27;s begin our understanding of the envelope generators by trying to
answer the easier question: what kind of shape the envelope segments
have.</p>
<p>As already anticipated, we&#x27;ll have to look at the DX7 output, and in
order to have a rational analysis we&#x27;ll have to control it somehow, to
isolate the part we want to study.</p>
<p>Let&#x27;s do this by creating a simple voice and uploading it to the
synthesizer.  The voice (<code>ENV TEST#0</code>) will have one operator active,
with a simple envelope.  The envelope will go from 0 to 99, 99 to 50,
50 to 80, 80 to 0. On a constant, slow rate. Fast enough that can be
recorded quickly, slow enough that the curve can be seen.</p>
<p>The rest of the voice should be configured as flat as possible, with
no fancy settings to avoid complicating our analysis.</p>
<h2 id="creating-a-voice-">Creating a voice.</h2>
<p>In order to create a voice we need to create a MIDI System Exclusive
message that contains the voice in the Packed 32 Voice format. It is a
format that stores all 32 voices of a DX7 in 4096 bytes.</p>
<p>The structure of the SYSEX message and the bulk voice format can
be both found in the <em>DX7s</em> <em>Owner&#x27;s</em> <em>Manual</em>.</p>
<p><a href="https://github.com/glguida/dx7revtools/blob/part2/genrom.c"><code>genrom.c</code></a> is a simple application that
write to standard output a packed 32 voices bulk SYSEX message for the
Yamaha DX7. The first voice in the set is <code>ENV TEST#0</code>, all others are
empty).</p>
<p>The basic characteristic of the voice can be seen in this code excerpt:</p>
<pre><code>v.ops[0].eg_rate[0] = 50;
v.ops[0].eg_rate[1] = 50;
v.ops[0].eg_rate[2] = 50;
v.ops[0].eg_rate[3] = 50;</code></pre>
<pre><code>v.ops[0].eg_level[0] = 99;
v.ops[0].eg_level[1] = 50;
v.ops[0].eg_level[2] = 80;
v.ops[0].eg_level[3] = 0;</code></pre>
<p>Note that we&#x27;re only setting to non-zero the first operator. All other
operators are essentially turned off. The <em>algorithm</em> used (i.e., the
connection map of the operators) is 32, i.e., all operators go
directly to output, no one modulates any other operator.</p>
<p>After compiling the above program, we can finally create a file
to send to the synthesizer:</p>
<pre><code>$ .&#x2F;genrom &gt; envtest.syx
$ file envtest.syx 
envtest.syx: SysEx File - Yamaha</code></pre>
<p>We now have a file slightly bigger that 4k that contains the set of
voices just created. The next step is sending it to the synthesizer.</p>
<h2 id="uploading-a-voice-">Uploading a voice.</h2>
<p>Uploading the set of voices to the synthesizer is also
straightforward.</p>
<p>All we need to do is turn the synthesizer on and setting <em>memory</em>
<em>protection</em> to <em>off</em> (the synthesizer manual will explain how to do
that). Once this is done, you can send <code>envtest.syx</code> from your
computer with the <code>amidi</code> ALSA utility:</p>
<pre><code>$ amidi --send envtest.syx -p &lt;port&gt;</code></pre>
<p><code>&lt;port&gt;</code> should be substituted with your midi device ALSA port. If
you don&#x27;t know it you can find it with <code>amidi -l</code>. Should be in the
form <code>hw:[0-9]+,[0-9]+,[0-9]+</code>.</p>
<p>If this is successful, you should see that the voice names in your
DX7 have changed. They&#x27;ll be all empty except from the first one,
which will be <code>ENV TEST#0</code>.</p>
<hr />
<p><img src="/img/dx7_envtest0.jpg" alt="ENV TEST#0 loaded"></p>
<p><code>ENV TEST#0</code> loaded on a Yamaha TX7, a desktop version of the DX7.</p>
<hr />
<p>We now have our test voice loaded in the synthesizer. All we need to do is playing a note and recording the sound.</p>
<h2 id="recording-a-voice-">Recording a voice.</h2>
<p>This step is also very basic. All that is required now is to connect the output of the DX7 to the microphone input of the computer, and record the output of a single note in raw format:</p>
<pre><code>$ arecord -f FLOAT_LE -t raw -c 1 -r 44100 envtest0.raw</code></pre>
<p><code>envtest0</code> contains a sequence of 32-bit floats (on a little-endian machine), one per each sample, <code>-c 1</code> specifies mono recording.</p>
<p>It can be played back using <code>aplay</code>:</p>
<pre><code>$ aplay -f FLOAT_LE -t raw -c 1 -r 44100 envtest0.raw</code></pre>
<p>Now that we have a recording of the sound, we can finally start analysing it.</p>
<h2 id="plotting-the-voice-">Plotting the voice.</h2>
<p>Let&#x27;s write a basic tool that we&#x27;ll hack as we go: <a href="https://github.com/glguida/dx7revtools/blob/part2/raw2plot.c"><code>raw2plot.c</code></a> is a simple C program that takes a raw file and converts it into a format understood by <code>gnuplot</code>.</p>
<p>Once it is compiled, we can generate a <code>gnuplot</code> output and plot it.</p>
<pre><code>$ .&#x2F;raw2plot &lt; envtest0.raw &gt; envtest0.plt
$ gnuplot
gnuplot&gt; plot &#x27;envtest0.plt&#x27; with lines</code></pre>
<p>And you should see a window appear with your wave plotted.</p>
<hr />
<p><img src="/img/dx7_envtest0_plt.png" alt="Plot of recording"></p>
<p><img src="/img/dx7_envtest0_zoom.png" alt="Plot of the signal"></p>
<p><img src="/img/dx7_envtest0_2seg.png" alt="plot of the first two segments"></p>
<p><code>gnuplot</code> output at three different zoom levels: full recording, the signal recording, the first two segments of the envelope.</p>
<hr />
<p>As you can see, something quite strange is going on at the start of the recording, most likely at the sound card level. After giving it some time for settling, a key is pressed and the sounds is recorded. As expected, the envelope goes to level 99, then to 50, and then to 80, and finally back to zero.</p>
<p>Even with a single plot we can derive the following conclusions:</p>
<ol>
<li>The envelope is not linear. Appears exponential, and at parity of rate the rising of the envelope is faster than the decay.</li>
<li>Levels are not linear: level 50 is very close to zero, and level 80 is less than half of level 99.</li>
</ol>
<p>In the next part we&#x27;ll attempt at characterising this plot.</p>
<h2 id="source-code-and-samples">Source code and samples</h2>
<p>All source code and samples used in this post are in the &#x27;part2&#x27; branch of <a href="https://github.com/glguida/dx7revtools">this GitHub project</a>.</p>
<p><em>Part three is <a href="/post/dx7eg3">here</a></em></p>
]]></content>
  </entry>
  
  <entry>
    <title type="text">The Yamaha DX7 Envelope Generator, part one.</title>
    <id>http://tlbflush.org/post/dx7eg1/</id>
    <updated>2020-05-07T00:00:00Z</updated>
    <published>2020-05-07T00:00:00Z</published>
    <link href="http://tlbflush.org/post/dx7eg1/" />
    <author>
      <name>Gianluca Guida</name>
      
      <email>glguida@gmail.com</email>
      
    </author>
    <content type="html"><![CDATA[<h2 id="silence--sine-waves-and-trigonometry-">Silence, sine waves and trigonometry.</h2>
<p>The Yamaha DX7, an FM synthesizer released in 1983, should need little
or no introduction. Its sound can be heard throghout most of
mid-to-late 1980s music.</p>
<p>If you&#x27;re  new to FM  synthesis, I&#x27;d suggest reading  Chowning&#x27;s paper
that introduced the concept: it&#x27;s a fascinating and inspiring read.</p>
<p>The DX7  has six operators.  Each operator is an oscillator  with two
inputs (amplitude and  pitch), with the output sine wave modulated by
a per-operator <em>envelope</em>. The  fundamental operation  of FM  synthesis consist  in
using the output  of an operator to modulate the  pitch of another (or
itself, referred to as <em>feedback</em>).
An additional global envelope modulates the pitch of the note being played.</p>
<hr />
<p><img src="/img/2op_fm.svg" alt="Two operators scheme"></p>
<p>The basic operation of FM synthesis: one operator (<em>Modulator</em>)
modulates the pitch of a second operator (<em>Carrier</em>) that produces
sound. (<a href="https://en.wikipedia.org/wiki/Frequency_modulation_synthesis#/media/File:2op_FM.svg">Source</a>)</p>
<hr />
<p>A DX7 <em>voice</em>, or instrument, specifies each operator&#x27;s (relative or
absolute) pitch and amplitude, and defines how they&#x27;re connected to
each other, and which operator&#x27;s output becomes sound.  There are
other parameters, but the <em>fundamental</em> characteristic of a voice is
defined by the parameters above.</p>
<p>The Yamaha DX7 can hold 32 voices. It is possible to create and use
new ones &ndash; it&#x27;s a <em>programmable</em> synthesizer after all &ndash; and the
never disappointing Internet has many new voices available for
download.</p>
<p>The preset voices (that range from Piano, to Marimba, to Bass) show how
flexible this synthesizer is. It is in the nature of curious people,
then, to look at each voice and see how they&#x27;re made. These are not
<em>samples</em> after all, the operators <em>synthesize</em> these complex sounds from
<em>silence</em>, <em>sine</em> <em>waves</em> and <em>trigonometry</em>!</p>
<p>This is precisely the beauty of FM synthesis: its theory and basic
implementation are <em>simple</em>, and few lines of code produce <em>powerful</em>
and unexpected results.</p>
<h2 id="analysing-a-dx7-voice-">Analysing a DX7 voice.</h2>
<p>In order to look at voices, we need to fetch them and look at their internals.</p>
<p>Both operations are quite easy. You can instruct the synthesizer to
dump all 32 voices in bulk over MIDI and in Linux you can easily save
them using <code>amidi --receive</code>. The format is quite simple, and well
specified in the DX7 manuals.</p>
<p>Understanding how those parameters translate in physical
characteristic is a bit more complicated.</p>
<p>The DX7 operator configuration is mostly in settings that go from 0 to
99, and understanding how these translate into sound production
(pitch, amplitude) requires some fair amount of reverse engineering.</p>
<p>The operators connections (<em>algorithms</em>) and the frequency at which they
operate are well known and easy to understand.</p>
<p>What&#x27;s hard, and yet fundamental for characterising a FM sound, is
understanding the parameters of the envelope generators.</p>
<h2 id="understanding-envelopes-">Understanding envelopes.</h2>
<p>As already said, there are seven envelope generators in a DX7
voice. Six for each operators, modulating amplitude, and one global,
modulating the pitch.</p>
<p>An envelope in a DX7 has four segments. Three for attack (start of
note to sustain) and one for the decay (end of sustain to end of
sound). Each of these segments are configured by two paramenters,
level and rate. Level (0-99) is the amplitude reached by the segment,
rate (also 0-99) specifies how fast it will get there.</p>
<hr />
<p><img src="/img/dx7env.jpg" alt="DX7 Envelopes"></p>
<p>A DX7 envelope scheme as printed on the synthesizer itself. (<a href="http://www.muzines.co.uk/articles/yamaha-dx7/4733">Source</a>)</p>
<hr />
<p>At this point a lot of question arise, some make sense, some might not.</p>
<ol>
<li>What kind of curve has each segment? (linear, exponential, other)</li>
<li>How level progress from zero to maximum? (linear, exponential, other)</li>
<li>How rate progress from zero to maximum? (linear, exponential, other)</li>
<li>How level (amplitude) is interpreted when used to modulate frequency of another operator? I.e., what amplitude is required to modulate the operator frequency by 1 Hz?</li>
</ol>
<p>In order to get these answers, we&#x27;ll have to look at <em>waves</em>.</p>
<p><em>Part two is <a href="/post/dx7eg2">here</a></em></p>
]]></content>
  </entry>
  
</feed>
