top of page
Search
Writer's pictureLeonardo

Making the Synthesizers.com modules

A few months ago, I received an intriguing message from Mike Graham who works at Synthesizers.com. He asked if I could port some of their modules to VCV Rack, which immediately piqued my interest.


I first learned about Synthesizers.com through the documentary I Dream of Wires, which also inspired me to start designing my own modular synthesizer. Synthesizers.com (often called DotCom for short) produces modular systems in the 5U format, similar to the original Moog modular. These modules are significantly taller than the Eurorack 3U format. One aspect I found particularly appealing about the DotCom modules is their vintage aesthetic. The black panels resemble lab equipment, giving them a classic, retro look. Another major attraction is that most of their modules are analog.


Before accepting the offer, I needed to clarify their expectations regarding the level of detail required for the models. Accurate simulation models are challenging to create. In the past, I’ve analyzed and created simulation models for other circuits, such as the VCV Drums. I detailed my process in a blog post, here, where I break down and simplify the 808 bass drum. Starting from a schematic, the process of capturing the circuit, understanding it, refining the model, and then simplifying it is time-consuming.


Modeling a large set of DotCom modules would have taken me many months, if not years, as I only work on Vult projects during my free time. Thankfully, they told me it was fine if the models were "close enough" and didn’t have to be a perfect emulation. The intention was to give the users a good approximation of what can be done with a real DotCom system.


This was a relief because I could use alternative modeling techniques, such as behavioral modeling, to achieve good approximations of the original circuits rather than needing to model each component individually.


In this blog post, I’ll share some of the techniques I used to model the DotCom modules. But before diving into that, I’d like to start by discussing my experience with the DotCom modular system.


My impressions about the Synthesizers.com system


The team at DotCom sent me a system to work with. Since I’m used to Eurorack sizes, it was hard to get a proper sense of the size of DotCom modules just from pictures. The 5U format feels enormous compared to Eurorack. It’s like moving from a studio apartment to a two-story house. You can really see the size difference when comparing a DotCom filter to the Freak.




Eurorack is filled with modules that are jam-packed with knobs, jacks, and buttons. Some of these controls are hard to access, especially with my medium-sized hands. In fact, some panels are so crowded that there’s barely enough room for proper labels. On the one hand, Eurorack is great because you can create a compact, fully featured system. On the other hand, these systems can become quite complex to use if not well planned, due to the physical limitations of control size, positioning, and the inevitable tangle of wires.


DotCom modules, on the other hand, have large knobs and plenty of space, making them easy and comfortable to work with. The design encourages you to interact with the modules. Even my kids, who are surrounded by Eurorack modules, were instantly drawn to the DotCom system when it was set up. They couldn’t resist playing with and tweaking the big knobs.




Another feature I appreciate about DotCom modules, and something I strive for in my own designs, is that they are mostly self-explanatory. You can look at the panel and quickly figure out how the module works without needing to read the manual. Additionally, each module has a well-defined purpose and excels at it. For example, their filters are great filters, and their oscillators are great oscillators. In contrast, some of my Eurorack modules are multifunctional, capable of serving as a VCO, VCF, LFO, and envelope generator all at once. While versatile and useful, these multifunctional modules don’t perform any single task as well as a dedicated module does.


Overall, DotCom modules offer excellent sound, a consistent design, and a beautiful aesthetic. I would consider them truly vintage, as they are built with through-hole components and hand-wired, giving them an artisanal feel. Just take a look at the back of one of these modules to see the craftsmanship.





Like any synthesizer, DotCom modules have certain characteristics that may or may not be disadvantages for you.


Size


The size of the system, while one of its biggest advantages, could also be seen as a disadvantage. As mentioned earlier, building a Eurorack system with similar functionality would result in a more compact setup. It’s a trade-off between a large, comfortable interface or a more compact and tight one.


Module availability


In Eurorack, according to Modulargrid.net, there are 15,982 modules available, compared to 1,164 in the MU format. Of course, this doesn’t mean that all 15,982 Eurorack modules are unique. There is a lot of overlapping functionality, with many modules performing the same tasks, like VCOs, VCFs, etc. However, it’s possible that your favorite Eurorack module doesn’t have an MU counterpart. Fortunately, you can use Eurorack modules in an MU system by using an adapter. I’ve used an adapter to combine DotCom modules with my own, like Freak and Caudal.





Cost


From what I’ve seen, the cost of MU modules is quite comparable to Eurorack. Basic or utility modules are around $150, while more complex modules, such as filters and oscillators, are roughly $250. There are also larger, more advanced modules, like the 24-stage sequencer or the advanced oscillator, which are more expensive. In Eurorack, you can find very cheap modules, but after purchasing some of them, I’ve noticed a significant difference in the quality. DotCom modules have extremely sturdy knobs, potentiometers, switches, and jacks. Comparing them to cheap Eurorack modules wouldn’t be fair. However, if you’re on a tight budget and aren’t too concerned about quality, Eurorack does offer inexpensive options to get started.


For both MU and Eurorack, cases are an important expense to consider. I’ve never had a decent Eurorack case—I’ve used cardboard boxes, 3D-printed frames, and glued-together pieces of wood. Because of this, I was blown away when I received the DotCom system. DotCom offers different case options, but the most flexible is probably the Box 11. It’s essentially a modular metal box with 11 spaces. These boxes can be used individually or combined to create a larger system. You can even add real wood sides for a classic look. Since the boxes can be stacked with special attachments, the Box 11 is a great option for starting small and expanding as needed.


I also tested the portable cabinet. These cabinets are made of wood, so they’re a bit heavier than the Box 11. However, they come with a lid, making them very secure for transportation. Both options look professional and are of high quality. It’s a completely different experience compared to my cheap, barely functional Eurorack cases.


Modeling process


As I mentioned earlier, my goal was not to model the modules component by component. Simulating each individual component, like resistors, capacitors, and transistors, typically leads to larger systems of equations that need to be solved. This would result in higher CPU usage for only a slight increase in model accuracy. (You can read more about this in my blog post on modeling the 808.)


In the world of modeling, there are different approaches to replicating the characteristics of a system. One method is black-box modeling, where a system—such as a module—is modeled based solely on its observable behavior at the outputs, given certain inputs. Imagine I take an oscillator and observe the waves it generates as I adjust the knobs and change the input signals. Based on these observations, I can propose a model that mimics the behavior.


The downside to black-box modeling is that you can only model behaviors that are observable. It can be challenging to figure out what’s happening inside a system and to replicate its behavior just by observing the outputs. In the case of an electrical circuit composed of multiple functional blocks, it’s often easier to model if you can access and observe some of the internal signals. For instance, by connecting an oscilloscope to different components, you can observe the behavior of a sub-circuit.


To know where to connect the oscilloscope and observe internal signals, you need a basic understanding of how the module works. This is where gray-box modeling comes in—it’s a method where you create a model based on some knowledge of how the system operates. Thanks to the previous work I’ve done modeling other synthesizer modules, I have a good understanding of the types of circuits commonly used in analog synthesizers. If you’re interested in learning more about these circuits, I offer a free course on the Wolfram U page: Introduction to Electric Circuits.


On the opposite end of the spectrum, we have white-box modeling. In this approach, we have complete knowledge of the system being modeled. This would be similar to having the schematic of each module and creating a simulation based on individual components.


All the models I created are gray-box behavioral models. I didn’t refer to the schematics; instead, I made educated guesses about the circuits being used and created functional blocks that behave like those circuits.


Let’s start with the first example: the Q105 Slew Limiter.


Modeling the Q105 Slew Limiter


Slew limiters work by controlling the rate at which a signal changes. There are various ways to implement them, but the most common types are exponential and linear slew limiters. In this case, we will focus on the exponential slew limiter.


A simple circuit that functions as an exponential slew limiter is the basic RC circuit, which consists of two main components: a resistor and a capacitor. This same circuit also acts as a simple low-pass filter. In my electric circuits course, we cover the details of this circuit in depth. For now, we’ll skip the model derivation and directly use the equations that describe it. Below is the Modelica code used to implement a simple RC filter/slew limiter in System Modeler.


model RC
  Modelica.Blocks.Interfaces.RealInput u;
  Modelica.Blocks.Interfaces.RealOutput y;
  parameter Real k = 1;
equation
  der(y) = (u - y) * k;
end RC;

Modelica is a language that allows us to create complex models of physical systems. In this case, we're using it to simulate a simple differential equation. The model has one input, u, which represents the signal we want to limit, and the output is represented by the variable y. The model also includes a parameter, k, which defines the speed at which the slew limiter operates. The value of k is calculated as k = 1/(R*C), where R and C are the values of the resistor and capacitor, respectively.


The code contains an equation that describes the relationship between the variables: the derivative of y (der(y)) is equal to (u - y) * k. This formula involves the input u, the current output y, and the parameter k. We’ll explore what this formula means later, but for now, let's focus on the term der(y). If you recall from calculus, the derivative of a variable represents the rate at which that variable changes.

Take a look at the following simple model and the simulation results.


model IntegratorTest
  output Real y1;
  output Real y2;
  output Real y3;
equation
  der(y1) = 2;
  der(y2) = 1;
  der(y3) = -1;
end IntegratorTest;


The model includes three variables: y1, y2, and y3. Each variable has its own equation for the derivative, and each derivative takes on a different value. When comparing the simulation results, you can see that y1 increases faster than y2. This is because a larger derivative causes the variable to rise more quickly. On the other hand, the derivative of y3 is negative, so instead of increasing, it decreases. What we're observing here is the behavior of an integrator—the input signal is accumulated over time and reflected in the output.


Now, if we return to the original RC model, we can see that the derivative is not constant. It depends on the values of the output y, the input u, and the parameter k. The following plot shows the simulation results for this model when the input is a constant value of 1. The plot also displays the changing derivative over time.


At the start of the simulation, the output y is zero. Therefore, the derivative is calculated as (u - 0) * k. Since both u and k are positive numbers, the value of y will begin to increase. The rate of this change depends on the value of the parameter k. As we saw in the previous experiment, a larger derivative leads to a faster rate of change, so a higher value of k will cause y to change more quickly.


As y increases, the derivative becomes smaller. This happens because the difference between the input u and the output y—which is u - y—gets smaller. If you look closely at the simulation results, you’ll notice that the output y approaches the input value exponentially. This is the behavior of the exponential slew limiter that we're aiming for.


In the following simulation, you can see how changing the parameter k affects the results. Specifically, observe how the output signal takes a different time to approach the input signal.


If you haven't studied calculus before, all of this might seem a bit overwhelming. The key points to remember are:


  • The equation der(y) = ... represents an integrator.

  • A derivative value like u - y describes an exponential transition.

  • The speed of the integrator can be controlled by adjusting the parameter k.

  • A large value of k makes the transition so fast that it's almost as if there’s no slew limiting at all.


Since the Q105 is an exponential slew rate limiter, we can model it with the differential equation of the RC circuit. However the Q105 has three different operation modes:


  • Limiting on the “Up” direction (when the signal is rising)

  • Limiting on the “Down” direction (when the signal is decreasing)

  • Limiting on “Both” directions.


Using the same equation, we can model three different behaviors by simply adjusting the rate k. The next model demonstrates the implementation of the "Up" mode (limiting when increasing). We achieve this by changing the value of k. If the input is greater than the output (meaning the output needs to increase), the rate is limited to a specific value (10 in this case). If the input is less than the current output, the rate k becomes a much larger number (in this example, I chose 1000). This causes the output to quickly match the input signal, essentially behaving as if there’s no limiting.


model LimiterUp
  Modelica.Blocks.Interfaces.RealInput u;
  Modelica.Blocks.Interfaces.RealOutput y;
  Real actual_k;
  parameter Real k = 10;
equation
  actual_k = if u > y then k else 1000;
  der(y) = (u - y) * actual_k;
end LimiterUp;


The "Down" mode (limiting when decreasing) is implemented similarly, with the only difference being how we select the value of k. We have three slightly different models, and the correct one is chosen depending on the position of the mode switch. This forms our gray-box model. Now, we need to determine the actual parameters to match the behavior of the real Q105 module. To do this, I measured the output response at different positions of the Amount (rate control) on the actual hardware.


The Amount control is a potentiometer, which varies the resistance. Changing the resistance alters the value of k based on the formula k = 1 / (R * C). The following plot shows the values of R * C needed to match the slew limiter's response at various knob positions.



The data has an unusual shape because the potentiometer controlling the rate is logarithmic. In practice, logarithmic potentiometers are made using two linear potentiometers with different slopes or values. From the data, we can see that the points can be approximated by two lines. Using the Wolfram Language, we can easily fit the data to linear equations and derive an approximate model. The plot below shows the resulting lines. We can determine the exact point where the two lines intersect by solving the equations for their intersection. This point marks where we switch from using the blue line to the orange one. This behavior is implemented in the Vult language as follows:


fun calcRate(x) {
  if (x < 6.26703)
    return 0.0005 + 0.0315833 * x;
  else
    return -1.525 + 0.275 * x;
}

Here’s part of the Vult code implementing the slew limiter.

// determine the rate base on the log pot position 
val rc = calcRate(pos); 
// calculate the derivative
val der_y = (u - y) / rc;
// perform integration
y = Util.t(der_y, h);     
return y;

In the code, using the approximation we derived earlier, we calculate the rc value based on the knob position. Next, we calculate the derivative using the RC circuit formula. That value is passed to the integrator which returns the output value.


Since the behavior of the virtual Q105 is fitted to match the measurements of the real module, the results are very close. For most practical purposes they are identical.


Modeling the Q109 envelope generator


In the previous section, we briefly explored integrators and the first-order differential equation derived from the RC circuit. This differential equation is fundamental, as variations of the same RC circuit can be used to create a wide range of modules. As a second example, let’s examine the Q109 module.


The Q109 is an exponential envelope generator, and we can model it using the same RC equations we applied to the slew limiter.


The envelope operates by switching between three different behaviors to produce the four segments of the envelope:


  • Attack: The output increases exponentially toward 5V at a rate determined by the Attack potentiometer.

  • Decay/Sustain: The output decreases to the Sustain voltage at a rate set by the Decay potentiometer.

  • Release: The output decreases toward 0V at a rate controlled by the Release potentiometer.


The three behaviors can be captured using a single differential equation:

der(y) = (target - y) * rate;

The values of the target and rate change depending on the state.


State

Target

Rate

Attack

5V

Attack Pot.

Decay

Sustain Pot.

Decay Pot.

Release

0V

Release Pot.

There’s a logic that needs to be implemented to switch between the different states of the envelope. Initially, the envelope starts in the Release state and is off. When a gate signal is received, the envelope switches to the Attack state, causing the output voltage to rise. Once the voltage gets close to 5V, the envelope transitions to the Decay state. In the Decay state, the output decreases until it reaches the Sustain level, where it remains until the gate is released. When the gate is released, the envelope switches to the Release state, and the output decreases toward zero. Implementing this state machine is straightforward.


Similar to the Q105 modeling, I measured the transitions and calculated the R * C values needed to match the real module at various knob positions. The results are quite similar to what we observed earlier. Since the control potentiometers are logarithmic, fitting the data requires using a piecewise function with two lines.


As expected, the virtual and the real Q109 match very closely.


Modeling the Q106 Oscillator


A key element of a synthesizer's sound is the quality of its oscillators. However, modeling oscillators using circuit-based simulations can be tricky. The main issue is that if we simulate the electrical behavior of the circuit, we often encounter aliasing problems due to the high-frequency content that real analog oscillators can produce. To make a digital oscillator sound good, we need to generate band-limited waveforms that don’t exceed the Nyquist frequency. There are several techniques for producing analog-like waveforms without aliasing. For example, in my Bleak oscillator, I used band-limited impulse trains, a method that creates waveforms with no aliasing.


However, in this case, I wanted the oscillator to sound as close as possible to the real analog version. The best approach for achieving that is to sample the oscillator and create wave tables.


When we think of "sampling," we usually imagine recording audio using an audio interface and playing it back. That approach may not be sufficient here because audio interfaces have a limited sample rate, and some harmonics of analog waves can exceed the Nyquist frequency of the commercial sound cards. In my case, I sampled a wide range of waveforms using an oscilloscope capable of recording at up to 10 MHz of sampling rate. I captured waveforms from C0 to C9. Once I had the data, I performed post-processing to create the wave tables. I applied digital filtering to remove high-frequency content beyond the audible range. After filtering the waves, I resampled them and created floating-point wave tables with 16,384 points. For that, I used the table creation feature of the Vult language.


The following plot shows the wave tables for the Saw oscillator.


The oscillator uses 2D interpolation across the wave tables to synthesize the wave based on the frequency and its position in the table. As a result, the sound is very close to the original, since we are essentially playing back high-quality recordings of the original waveform.


By using behavioral models, I was able to accurately replicate both linear and exponential FM (frequency modulation) effects found in the original module. To achieve this, I manually matched the harmonic content of the waves using a spectrogram.


The real Q106 oscillator has a Sync feature, and to implement this, I used the MinBlep technique to reduce aliasing. However, even with this method, there is a small amount of aliasing, which makes the oscillator sound slightly noisier than the real analog version. It’s not a major issue, but the analog version has a smoother sound.


I also added a bit of simulated drift to the virtual oscillator. This means the virtual version will slightly change pitch over time to simulate the analog feel. Without this drift, connecting multiple oscillators results in a very sterile sound, as real analog oscillators are nearly impossible to tune with such precision. If desired, the drift function can be turned off.


Overall, I think the virtual Q106 sounds excellent and provides a strong representation of what the real analog Q106 can do.


Modeling the Q107 state variable filter


Filters are the modules I've spent the most time modeling, so when it came to modeling the Q107, the process was relatively straightforward for me. The Q107 is a state-variable filter built around the classic CA3080 operational transconductance amplifier (OTA). Fortunately, I had a few of these chips on hand, which allowed me to create an accurate behavioral model of the IC. In the past, I have created models of other OTAs, like the ones found in the SSI2140 and LM13700. In the plot you can see how my model matches reasonably well the measured values when used with different control currents.




In my Electric Circuits course, I demonstrate how a state-variable filter can be built using ideal OTAs. For this project, it was just a matter of integrating the CA3080 model into that circuit and manually adjusting some of the parameters to closely match the actual behavior of the Q107 filter.


The Q107 filter uses the latest version of my Vult Super Solver, which ensures not only good sound quality but also highly efficient CPU usage.


One of the key challenges when modeling analog filters digitally is achieving the same warmth and character found in the real hardware, especially when using components like the CA3080, which have a non-linearities that define the sound. The behavioral models I created seem to capture all the important aspects of the sound.


Modeling the Q115 Spring Reverb


The Q115 is a reverb module that uses a real spring tank. When it came to modeling it, I explored various techniques, but the first one I tried gave excellent results: I implemented it as a convolution reverb. To do this, I needed to obtain an impulse response (IR) of the spring reverb tank. The process involved generating a chirp signal—a sine wave that gradually increases in frequency—and passing it through the reverb. From the output, I performed a deconvolution process to extract the impulse response of the tank. In the image you can see the impulse response of the spring.


Because the impulse response is quite large (240k samples), performing a basic convolution would require a significant number of operations, which could be computationally expensive. To address this, I used FFT (Fast Fourier Transform) multiplication. This method involves multiplying the FFT of both the input signal and the impulse response, then applying the inverse FFT to return the signal to the time domain. One drawback of this approach is that it introduces a slight delay in the output signal. When comparing the real reverb to the modeled version side by side, they sound almost identical, though if you listen closely, you might notice the small delay in the virtual module. However, this delay is not significant and doesn’t affect the overall quality of the reverb.


There were also other small behaviors I needed to model. For instance, the hardware's Drive control can slightly saturate the spring tank, creating a soft distortion. I was able to replicate this effect by using a Hammerstein model, which combines a nonlinear element (to simulate the saturation) with a linear element (the impulse response). This allowed me to approximate the output more accurately and capture the subtle distortion present in the real module.



Modeling the Q119A sequencer, Q174 MIDI interface and Q173 gate math


These modules are among the few that utilize a microcontroller. I had two options: either adapt the original source code to run on the virtual module, or recreate the behavior using my own code. Since I don’t know what language the original code is written in, I couldn't estimate how difficult the adaptation would be. If it's written in assembly, I would have to write a simple emulator for the microcontroller to execute the binary code. While it would be fun to create the emulator, I decided to save time by recreating the code in the Vult language.


The Q119A sequencer relies heavily on analog components. It appears that all voltage-related operations, like routing voltages with analog switches and summing them with operational amplifiers, are handled in the analog domain. Only the logic part, such as internal counters, is digital. Emulating designs like this is enjoyable because the code can be divided into functional components. For example, I can create a Vult function or component that behaves like an analog switch. Then, I can break the design into functional blocks and recreate the module accordingly.


When porting hardware modules to VCV Rack, one challenge is that some functions require performing two actions simultaneously, like pressing two buttons at once. VCV Rack doesn’t support this directly, as it would require multi-touch screens, and I’m not even sure if Rack supports them. To address this, I made those multi-action functions accessible through context menus.



Similarly, I wrote code for the Q174 MIDI interface. Although VCV Rack offers a fully featured MIDI-to-CV interface, the DotCom version has a few unique features not available in VCV, such as the three different monophonic behaviors and re-trigger modes. However, I left out the polyphonic outputs because including them would contradict the monophonic features. For users interested in polyphony, I recommend using the VCV module.


The Q173 Gate Math is the most recent module I've been working on, and I’m still developing it as I write this. This module offers more than 40 different ways to generate rhythms, with each of the four channels allowing for a different mode. In the virtual version, I made it easier to configure by allowing users to select programs via context menus, simplifying its use in VCV Rack.


All of these modules are functionally the same as their hardware counterparts, but there may be small differences in behavior that I haven't detected. If you own the hardware versions and notice any discrepancies, please let me know, and I'll work on improving the code.


A few notes on other modules


Some modules, like the Q127 Filter Bank and Q110 Noise, use fixed filters. For these, I used a network analyzer to obtain a Bode (frequency response) plot and, based on that data, created equivalent analog filters, which I then digitized.


The Q117 Sample and Hold module has a jack that can switch between input and output depending on the position of a switch. In this case, we decided to keep the jack as an input because VCV Rack does not allow the direction of jacks to be changed dynamically.


Both the Q962 Sequential Switch and the Q128 Switch provide bidirectional switching. In the hardware version, you can use the jacks as either inputs or outputs. However, since VCV Rack only supports one-way signal flow, we selected the most common use case for these modules.


The most challenging module to port was the Q167 LFO++. This module combines an LFO with an envelope, and since the LFO is analog, it has subtle characteristics that are tricky to replicate. The module has an internal envelope that I needed to track in order to measure directly, which required disassembling the module and tapping some wires. Another challenge was characterizing the VCA (Voltage-Controlled Amplifier). I had to consult with the DotCom team for assistance in improving my gray-box model, as I couldn’t figure out how all the components interacted. After several weeks of work, I finally created a model that is close enough.


Closing remarks


I’ve ported 22 modules from the DotCom line to VCV Rack. While they offer many more hardware modules, this set provides all the core functionality needed to build a complex and fun synthesizer.


Although I put a lot of effort into making these virtual modules as close to the originals as possible, the sound and look are only part of the experience. The software is not a replacement of the hardware. The real magic of the hardware comes from the physical interaction—tweaking the controls creates a more immersive and satisfying connection with the machine.


Making music with a physical interface is a unique experience. Similar to playing a guitar or piano, the ideas that emerge are different from those that come from using a computer alone. The limitations of a physical setup actually inspire creativity—having a finite number of modules and patch cables pushes me to explore new directions that I might not consider when working with an infinite modular system like VCV Rack.



2,876 views9 comments

Recent Posts

See All

9 Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
Lore Suto
Lore Suto
13 minutes ago

I had a question about the Q171 Quantizer bank's Gate OUT... In the manual it says "Often this is unused or patched to a Q109 Envelope Generator." I can see in the scope that it does output a super short gate, but when I connect it to the Q109 EG, it doesn't do anything. Is that how the hardware functions? If it is, I'm not sure what you use it for, and why they would state that in the manual. This might be more of a question for Dotcom, but I wanted to check that the hardware function like the VCV module (I figure it probably does!) Thanks!

Like

Lore Suto
Lore Suto
Nov 05

I really like these, great work! I'm curious why you did the SVF instead of the Q150 Ladder filter, since that's the classic Moog sound?

Like
Lore Suto
Lore Suto
Nov 08
Replying to

Great to hear you're planning on doing more! (I wanted to ask, but didn't want to look greedy!

Edited
Like

Michael
Michael
Oct 27

Would be nice, when the modules also find their way into the Cardinal Freeware

Like
Gear Watcher
Gear Watcher
Oct 29
Replying to

Cardinal only accepts open source modules, which these are, from what I understand, not..

Like

Lars Bjerregaard
Lars Bjerregaard
Oct 25

Outstanding work Leonardo! Thank you.

Like
Leonardo
Leonardo
Oct 27
Replying to

Thank you!

Like
bottom of page