<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Icseon]]></title><description><![CDATA[Exploring and sharing my passions with the world. Learn more about my current projects, areas of focus, and personal stories.]]></description><link>https://icseon.com/</link><image><url>https://icseon.com/favicon.png</url><title>Icseon</title><link>https://icseon.com/</link></image><generator>Ghost 5.65</generator><lastBuildDate>Mon, 22 Jun 2026 16:44:27 GMT</lastBuildDate><atom:link href="https://icseon.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Building an LLM-based text translator]]></title><description><![CDATA[Let's use LLMs to translate some text for us!]]></description><link>https://icseon.com/building-an-ai-based-text-translator/</link><guid isPermaLink="false">657af806443ed8051a978217</guid><dc:creator><![CDATA[Icseon]]></dc:creator><pubDate>Thu, 14 Dec 2023 13:58:34 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-text">Contrary to popular belief, AI does not exist. They&apos;re LLMs. I&apos;ve corrected the inaccuracy.</div></div><p>These days, we hear all about AI/LLMs and how useful they can be. There&apos;s one thing that LLMs do exceptionally well: translating text!</p><p>I wanted to create my own translation service because I did not want to rely on any external service for providing translations. This can be really costly depending on the volume of requests I generate.</p><p>So that&apos;s when my experiment began. Initially, I was planning to make an automatic message translator for Matokai, but decided I must start simple and decided to create a Google Translator-like service instead, for the time being.</p><h2 id="planning-out-what-i-have-to-build">Planning out what I have to build</h2><p>For the server portion, I am aiming to use LLAMA which provides <a href="https://github.com/abetlen/llama-cpp-python?ref=icseon.com">bindings</a> for Python combined with <a href="https://bottlepy.org/docs/dev/?ref=icseon.com">Bottle</a> for the web-server, so I can easily create requests from the client to the server.<br><br>As for the client, I will be using Vue alongside TypeScript to build a basic interface where the user can provide inputs for the translation endpoint.</p><h3 id="why-bottle">Why Bottle?</h3><p>Bottle is extremely light weight and is perfect for this service that will really only have one single endpoint. I like using Bottle for integrations such as rendering 3D models and connecting AI with the web like I am doing today in this post.</p><p>Since it&apos;s so light weight, it&apos;s very quick and easy to get started with it. But honestly, Bottle can also be used for large scale APIs using other integrations and ORMs to manage data, but that is out of the scope of this post.</p><p>Bottom line: It&apos;s simple, small and nice.</p><h2 id="building-the-front-end">Building the front end</h2><p>Now that we have a general idea of what we want to build, let&apos;s start by creating a simple Vue component where the user is greeted with a simple form where they can</p><ol><li>Provide what language they wish to translate to - this can be any language, so I am opting to make this a text field instead, as the AI will understand what they mean in most cases.</li><li>Type text in any language they desire, as the AI will attempt to automatically detect what language their input is.</li><li>See a readonly text area where they can see the result of the translation.</li></ol><p>So after quickly designing a logo in Figma, coming up with a very fitting name and writing some text, this is what I came up with:</p><figure class="kg-card kg-image-card"><img src="https://icseon.com/content/images/2023/12/image-1.png" class="kg-image" alt loading="lazy" width="678" height="560" srcset="https://icseon.com/content/images/size/w600/2023/12/image-1.png 600w, https://icseon.com/content/images/2023/12/image-1.png 678w"></figure><p>It is an extremely simple form where the user can specify all the required information as outlined earlier. Perfect for our use case.</p><p>But let&apos;s make it a little nicer. Because we&apos;re using Vue, we can use its reactivity system to dynamically render elements based on a condition. So let&apos;s make the text areas and translate button not render at all until the user has given an input.</p><p>To do this, we must declare a variable within our component using <code>Refs</code>. But we must also do this to know the language in the first place, so let&apos;s go ahead and declare everything we need within an interface:</p><pre><code class="language-ts">/**
 * Represents the properties expected by the AppComponent.
 */
interface IAppComponent {

    /**
     * A reactive reference to indicate whether the network is currently processing.
     * We use this to give the user feedback through the user interface.
     */
    isNetworkProcessing: Ref&lt;boolean&gt;,

    /**
     * A reactive reference to indicate whether the network request has failed due to an error.
     */
    hasErrorOccurred: Ref&lt;boolean&gt;,

    /**
     * A reactive reference to store the target language for translation.
     * This is entered by the user through the user interface.
     */
    targetLanguage: Ref&lt;string&gt;,

    /**
     * A reactive reference to store the text the user wishes to translate.
     * This is also entered by the user through the user interface.
     */
    contentText: Ref&lt;string&gt;,

    /**
     * A reactive reference to store the translated text returned from the server.
     */
    responseContent: Ref&lt;string&gt;,

    /**
     * Performs translation by requesting a translation from the server.
     * @returns A Promise that resolves when the translation is complete.
     */
    translate: () =&gt; Promise&lt;void&gt;;
}</code></pre><p>Let&apos;s break this down:</p><ol><li><code>isNetworkProcessing</code> - This is a simple boolean I will be using to dictate whether the translation request is pending so I can update the user interface accordingly to give the user feedback and to show a loading spinner.</li><li><code>hasErrorOccurred</code> - This is yet another boolean that I will be using when the network request has failed for any reason. This could range from the API being unavailable to the user not having an internet connection. It will be used to show a basic error.</li><li><code>targetLanguage</code> - This is the language we will be translating into and this is the user input provided by our user. We will send this to the server and we will be using this to hide the rest of the form if the length of this string is 0.</li><li><code>contentText</code> - This is the content provided by our user that we will be passing to the AI to recognize (it will detect what language it is) and translate.</li><li><code>responseContent</code> - Once the server has translated the content provided by our user, it will have to be stored somewhere - this is it.</li><li><code>translate</code> - This method is the function that will send the request to the API and fetch its response - the entire logic of the application relies on this one function.</li></ol><p>Now that we have all the variables we needf for the application to function, let&apos;s start making sure the form is not rendered at all if the string length of <code>targetLanguage</code> is <code>0</code> (empty).</p><p>We can easily achieve this by using the <code>v-if</code> directive that Vue provides:</p><pre><code class="language-html">&lt;!-- Input template - only rendered when the user has given us a target language !--&gt;
&lt;template v-if=&quot;targetLanguage.length &gt; 0&quot;&gt;
	...
&lt;/template&gt;</code></pre><p>Great! Now the rest of the form will not render until we know the language we are going to translate into, so this is what we are left with:</p><figure class="kg-card kg-image-card"><img src="https://icseon.com/content/images/2023/12/image-2.png" class="kg-image" alt loading="lazy" width="645" height="264" srcset="https://icseon.com/content/images/size/w600/2023/12/image-2.png 600w, https://icseon.com/content/images/2023/12/image-2.png 645w"></figure><p>Nice! Now the user has to type in a language before they can start giving the text they wish to translate. This one simple thing will guide the user through the steps without having to explain anything to them.</p><h3 id="sending-a-request-to-the-server">Sending a request to the server</h3><p>Now that we have built a form and added some nice-to-have functionality to it, it is time to start building the payload that will be received by the back-end server. Before we do this, let&apos;s collect what we need to perform a translation:</p><ol><li><code>contentText</code> - This is the content provided by the user and what we are going to be translating.</li><li><code>targetLanguage</code> - This is the language we will be translating into.</li></ol><p>That&apos;s all we need - we don&apos;t need to know the language we are translating from because the AI will do its best to know that.</p><p>So with that in mind, let&apos;s establish that our payload is going to be formatted as such:</p><pre><code class="language-json">{
	&quot;content&quot;: &lt;contentText&gt;,
	&quot;to&quot;: &lt;targetLanguage&gt;
}</code></pre><p>Remember the <code>translate</code> method I mentioned earlier? We&apos;ll be implementing this logic in there, using the native <code>fetch</code> API that all major browsers currently support.</p><pre><code class="language-ts">/**
     * Performs translation by requesting a translation from the server.
     */
    const translate = async(): Promise&lt;void&gt; =&gt; {

        /* Set network processing state and error state. */
        isNetworkProcessing.value = true;
        hasErrorOccurred.value = false;

        await fetch(&quot;http://localhost:8081&quot;, {
            method: &quot;POST&quot;,
            headers: {
                &quot;Content-Type&quot;: &quot;application/json&quot;
            },
            body: JSON.stringify({
                content: contentText.value,
                to: targetLanguage.value
            })
        }).then(async(response) =&gt; {

            /* Retrieve json from the response */
            const json = await response.json();

            /* Set content */
            responseContent.value = json.content;

        }).catch(() =&gt; {

            /* Set error state */
            hasErrorOccurred.value = true;

        }).finally(() =&gt; {

            /* Update network processing state again */
            isNetworkProcessing.value = false;

        });

    }</code></pre><p>Let&apos;s break down what happens in here.</p><ol><li><code>isNetworkProcessing</code> is set to <code>true</code> to indicate that a request is currently pending for the purpose of updating the user interface to reflect this.</li><li><code>hasErrorOccurred</code> is set to <code>false</code> in case it was set to <code>true</code> as a result of a previous request, we&apos;re trying again, so nothing has gone wrong just yet.</li><li>A <code>fetch</code> call is made to <code>http://localhost:8001</code> where I intend to host my Bottle server that will handle the translation request for us with the body in the format mentioned earlier.</li><li>Once we receive a response, we retrieve the JSON content of it and set <code>responseContent</code> to be the response&apos;s <code>content</code> value.</li><li>If an error occurrs, we update <code>hasErrorOccurred</code> to be true for the purpose of telling the user something went wrong.</li><li>In any case, regardless of state, we update <code>isNetworkProcessing</code> to be <code>false</code> again as the request has finished.</li></ol><p>We are now sending a request to a server that does not exist yet. So, let&apos;s go ahead and build the server now!</p><h2 id="building-the-server">Building the server</h2><p>We have completed our client for the purpose of translating text, we just have to go ahead and process this now using a server. Like I have mentioned before, I will be using Bottle because of its simplicity. So let&apos;s create a basic Bottle server.</p><pre><code class="language-python">from bottle import run

if __name__ == &quot;__main__&quot;:
	run(host=&quot;localhost&quot;, port=8081, debug=True)</code></pre><p>We now have a basic Bottle server that returns 404 for all routes - because we haven&apos;t created any yet! That&apos;s how simple Bottle is.</p><h3 id="allowing-origins-cors">Allowing origins (CORS)</h3><p>If you have ever developed an API like this before, you will know that all requests imposed from the client we have developed earlier will fail due to CORS. That&apos;s okay, easy fix. We&apos;ll be using the <code>bottle_cors_plugin</code> Python package to deal with this for us.</p><p>Our code now becomes this:</p><pre><code class="language-python">from bottle import app, run
from bottle_cors_plugin import cors_plugin


# Configure Bottle server.
app = app()
app.install(cors_plugin(&quot;*&quot;))  # CORS - allow all origins.

if __name__ == &quot;__main__&quot;:
	run(host=&quot;localhost&quot;, port=8081, debug=True)</code></pre><p>We are now allowing all origins. <em>This is incredibly insecure</em> - if you wish to deploy this to production, you must have an environment variable using something like <code>dotenv</code> to control this value, but for the purposes of making a fun project that will not be deployed and will only run on our local machine - this is fine.</p><p>To give an example of a secure implementation:</p><pre><code class="language-python">app.install(cors_plugin(&quot;translator.icseon.com&quot;))  # CORS - allow requests to only come from translator.icseon.com</code></pre><h3 id="processing-json">Processing JSON</h3><p>As established earlier, our payload is in the JSON format. However, our server does not understand this yet and will not be able to read <code>request.json</code> just yet. Adding support for this is relatively simple, using a hook.</p><p>Let&apos;s create this hook. Our code now looks like this:</p><pre><code class="language-python">from bottle import app, run, hook, response
from bottle_cors_plugin import cors_plugin


@hook(&quot;before_request&quot;)
def set_default_content_type() -&gt; None:

	&quot;&quot;&quot;
	Sets the default content type, so we can read JSON bodies from clients.
    :return None:
    &quot;&quot;&quot;
    response.content_type = &quot;application/json&quot;


# Configure Bottle server.
app = app()
app.install(cors_plugin(&quot;*&quot;))  # CORS - allow all origins.

if __name__ == &quot;__main__&quot;:
	run(host=&quot;localhost&quot;, port=8081, debug=True)</code></pre><p>Congratulations, we can now work with JSON using our Bottle server.</p><h3 id="creating-a-route-for-translation-and-implementing-the-ai">Creating a route for translation and implementing the AI</h3><p>Now that we have a basic server setup and working, we will proceed to create a new controller to handle the translation logic. Let&apos;s start by creating a new file called <code>translate_controller.py</code> and creating a method called <code>translate</code>, which will look something like this:</p><pre><code class="language-python">from bottle import request


def translate(body: request) -&gt; str:
	return &quot;This is a response from the translate controller&quot;</code></pre><p>Let&apos;s go back to the main file and create a route for this controller and import the controller.</p><pre><code class="language-python">from bottle import app, run, hook, response, route
from bottle_cors_plugin import cors_plugin
from translate_controller import translate


@hook(&quot;before_request&quot;)
def set_default_content_type() -&gt; None:

	&quot;&quot;&quot;
	Sets the default content type, so we can read JSON bodies from clients.
    :return None:
    &quot;&quot;&quot;
    response.content_type = &quot;application/json&quot;
    

@route(&quot;/&quot;, method=&quot;POST&quot;)
def translate_route() -&gt; str:


	&quot;&quot;&quot;
    Performs the translation by executing the translation controller.
    :return str:
    &quot;&quot;&quot;
    return translate(body=request)
    
  
# Configure Bottle server.
app = app()
app.install(cors_plugin(&quot;*&quot;))  # CORS - allow all origins.

if __name__ == &quot;__main__&quot;:
	run(host=&quot;localhost&quot;, port=8081, debug=True)</code></pre><p>We have now successfully defined a route for translation requests on path <code>/</code> and we will only accept <code>POST</code> requests through - other methods will be given a <code>405</code> response.</p><p>Let&apos;s go back to the <code>translate_controller.py</code> file and start implementing the AI. As mentioned earlier, I will be using the Llama Python bindings for this.</p><pre><code class="language-python">from bottle import request
from llama_cpp import Llama


# Intialize the LLM.
llm = Llama(
	model_path=&quot;./models/&lt;your model&gt;.gguf&quot;,
    n_ctx=4096,
    chat_format=&quot;&lt;your chat format&gt;&quot;
)


def translate(body: request) -&gt; str:

	&quot;&quot;&quot;
    Performs the translation by prompting LLM to translate for us.
    :param body:
    :return str:
    &quot;&quot;&quot;
    
    print(llm)
    
    return &quot;This is a response from the translate controller&quot;</code></pre><p>We have now initialized the large language model properly. However, you may notice I am unable to provide the <code>model_path</code> and <code>chat_format</code> to you - that&apos;s because you must have your own model and each model may have a different <code>chat_format</code>.</p><p>Now that that&apos;s out of the way, we must give a prompt to the AI. From some experience, I know that doing it manually will be ugly, so let&apos;s create a <code>PromptBuilder</code> class to deal with this for us. Start by creating a new file called <code>prompt_builder.py</code> (for example).</p><pre><code class="language-python">import json


class PromptBuilder:

	def __init__(self, to: str, content: str) -&gt; None:
    
    	self.to = to
        self.content = content
        
        # Build request dictionary.
        self.request = self.__dict__
        
    def build(self) -&gt; str:
    
    	&quot;&quot;&quot;
        Builds the prompt that we will be passing to the LLM.
        :return str:
        &quot;&quot;&quot;
        # Give an example for the AI to work with.
        response_example = {
        	&quot;to&quot;: None,
            &quot;from&quot;: &quot;&lt;language you translated&gt;&quot;,
            &quot;content&quot;: &quot;&lt;translation&gt;&quot;
        }
        
        # Build, format and return the built prompt.
        return &quot;You are a translation assistant that translates messages and responds with JSON like: {0}&quot; \
               &quot;You are translating the JSON string to {1} - ensure the response &apos;to&apos; key is also that.&quot; \
               &quot;Request: {2}&quot;.format(json.dumps(response_example), self.to, json.dumps(self.request))
</code></pre><p>What this will do is format promps that look like the following:</p><p><code>You are a translation assistant that translates messages and responds with JSON like: {&quot;to&quot;: null, &quot;from&quot;: &quot;&quot;, &quot;content&quot;: &quot;&quot;}You are translating the JSON string to nl - ensure the response &apos;to&apos; key is also that.Request: {&quot;to&quot;: &quot;nl&quot;, &quot;content&quot;: &quot;Hello, I am bored.&quot;}</code>&#x2003;</p><p>This informs the AI of its purpose and instructs it on what to do with the request given to it.</p><p>Now that we have a prompt, let&apos;s complete the implementation by importing the newly created <code>PromptBuilder</code> class into the translation controller.</p><pre><code class="language-python">from bottle import request
from llama_cpp import Llama
from prompt_builder import PromptBuilder
import json


# Intialize the LLM.
llm = Llama(
	model_path=&quot;./models/&lt;your model&gt;.gguf&quot;,
    n_ctx=4096,
    chat_format=&quot;&lt;your chat format&gt;&quot;
)


def translate(body: request) -&gt; str:

	&quot;&quot;&quot;
    Performs the translation by prompting LLM to translate for us.
    :param body:
    :return str:
    &quot;&quot;&quot;
    # Initialize the prompt builder.
    prompt_builder = PromptBuilder(
		to=body.json.get(&quot;to&quot;),
        content=body.json.get(&quot;content&quot;)
    )
    
    # Ask the LLm for an answer.
    answer = llm.create_chat_completion(
		messages=[
        	{
				&quot;role&quot;: &quot;user&quot;,
                &quot;content&quot;: prompt_builder.build()
            }
        ]
    )
    
    # The AI may respond with &quot;Result: &lt;json&gt;&quot;, so let&apos;s account for that.
    response = answer[&quot;choices&quot;][0][&quot;message&quot;][&quot;content&quot;]
    result = &quot;{&quot; + response.split(&quot;{&quot;, 1)[1] if &quot;{&quot; in response else response
    return result</code></pre><p>Awesome. We have now fully implemented the LLM and created an AI to expose it to the web.</p><h2 id="the-result">The result</h2><p>Now that we have built both the client and server, let&apos;s look at what we&apos;ve built and see it in action...</p><figure class="kg-card kg-image-card"><img src="https://icseon.com/content/images/2023/12/ezgif.com-video-to-gif-converted.gif" class="kg-image" alt loading="lazy" width="600" height="325" srcset="https://icseon.com/content/images/2023/12/ezgif.com-video-to-gif-converted.gif 600w"></figure><p>Pretty cool!</p><h2 id="thanks-for-reading">Thanks for reading.</h2><p>You have now read how I utilized AI to power a text translation service for me in as much detail as possible.</p><p>I hope you have learnt something or otherwise found entertainment in reading about this.</p><p>&#x2014; Icseon</p>]]></content:encoded></item><item><title><![CDATA[Voice Activity Detection with WebRTC]]></title><description><![CDATA[Implementing VAD with WebRTC using the non-standard RTCAudioSink component]]></description><link>https://icseon.com/voice-activity-detection-with-webrtc/</link><guid isPermaLink="false">650edb73d4b59f2c4adcedfc</guid><dc:creator><![CDATA[Icseon]]></dc:creator><pubDate>Mon, 17 Apr 2023 12:35:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-text"><strong>Note</strong>: I wrote this back when one of my core principles was to do as much as possible on the serverside. I&apos;ve since altered my way of thinking and believe it would make more sense to send VAD detections from the client to the server, which then broadcasts that. <br><br>In theory this would let clients send fake states, but they&apos;re also the ones sending us audio in the first place. This is a small thing I&apos;m willing to let happen, since VAD detection is still pretty expensive to do on scale.</div></div><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://icseon.com/content/images/2023/09/matokai-call.gif" class="kg-image" alt loading="lazy" width="800" height="180" srcset="https://icseon.com/content/images/size/w600/2023/09/matokai-call.gif 600w, https://icseon.com/content/images/2023/09/matokai-call.gif 800w"><figcaption>Demonstrating voice activity detection</figcaption></figure><p>While implementing voice calls for Matokai, I needed a way to know which user is speaking. WebRTC does not handle this by default so it was time to get creative again.</p><h2 id="what-i-needed-to-achieve">What I needed to achieve</h2><p>To implement voice activity detection properly, I needed to have a way to:</p><ol><li>send data other than video and audio over RTC</li><li>access an audio track&#x2019;s PCM data that we receive from a peer</li><li>identify whether a PCM frame contains speech</li><li>broadcast a packet to all connected peers to let them know who is speaking</li></ol><h2 id="sending-packets-using-an-unorderedunreliable-data-channel">Sending packets using an unordered/unreliable data channel</h2><p>In WebRTC, there are two types of data channels; an ordered and an unordered one. While RTC works entirely on UDP, an ordered data channel ensures that data is received like TCP would do at the cost of latency and performance.</p><p>For our use case, we&#x2019;ll need an unordered data channel because it&#x2019;s okay if some packets never arrive, and we can use the performance, so we can rest assured that the activity packet is relatively lined up with audio data.</p><h3 id="creating-an-unordered-data-channel">Creating an unordered data channel</h3><p>Start by creating an unordered data channel on the client side like so:</p><pre><code class="language-js">/* Build an unordered data channel */
const unorderedDataChannel = this.peerConnection.createDataChannel(&apos;channel&apos;, {
    ordered: false
});</code></pre><h3 id="receiving-data-channels-from-clients">Receiving data channels from clients</h3><p>On the other end, we will receive this data channel during SDP negotiation. We can retrieve it as such:</p><pre><code class="language-js">/**
 * @author Icseon
 * @description This callback is invoked once a peer sends a data channel to us
 * @param channel
 */
peerConnection.ondatachannel = async({ dataChannel }) =&gt; {
  
  /* Tell ourselves about the fact we have received a data channel from a client */
  debug(`received a data channel labeled: ${dataChannel.label}`);
    
  /* If this is the data channel we expect, register it for our peer somehow. That&apos;s up to you. */
  if (dataChannel.label === &apos;channel&apos;)
  {
    
      /* Add the dataChannel to our peerConnection somehow for later access */
      peerConnection.someClass.dataChannel = channel;
        
  }
  
};</code></pre><h3 id="listening-for-packets-from-the-server">Listening for packets from the server</h3><p>Now we have a one-sided communication channel between the client and the server (server to client only), but we are not yet handling any packets on the client. Listen to packets from the server like this:</p><pre><code class="language-js">/* Listen for data from the server. */
unorderedDataChannel.onmessage = (data) =&gt; {
    
    /* We are going to do something with this data later on. For now, let&apos;s do a simple console.log */
    console.log(data);
    
});</code></pre><p>We can now send data to the client that is not audio or video which means we can send voice activity packets later!</p><h2 id="accessing-the-audio-tracks">Accessing the audio tracks</h2><p>I am assuming that you have sent your media streams over RTC before creating your connection. If not, go back and implement that first.</p><h3 id="receiving-media-tracks-from-clients">Receiving media tracks from clients</h3><p>Let&#x2019;s start simple and create a way to echo back audio data to our client. Make sure to check if we are dealing with an audio track because clients can also send video tracks. We are going to expand on this very soon:</p><pre><code class="language-js">/**
 * @author Icseon
 * @description This callback is invoked once a peer sends a track to us
 * @param channel
 */
peerConnection.addEventListener(&apos;track&apos;, async({ track, streams }) =&gt; {

    /* Let&apos;s know what we have received */
    debug(`got track of kind: ${track.kind}`);
    
    /* Check to see if we got an audio track */
    if (track.kind === &apos;audio&apos;)
    {
    
        /* Add a transceiver to our peer connection which will transmit audio data back to our client */
        peerConnection.addTransceiver(track, {
            direction: &apos;sendonly&apos;,
            streams
        });
    
    }

});</code></pre><p>WebRTC is now sending back your own audio. However, you can&#x2019;t hear yourself yet. We&#x2019;ll need to handle tracks on the client side as well and playback the media stream on the client side after receiving.</p><h3 id="receiving-media-tracks-from-the-server">Receiving media tracks from the server</h3><p>That is done by listening for an audio track, exactly the same way we have just done on the server side - except we also add a new <code>Audio</code> element.</p><pre><code class="language-js">/**
 * @author Icseon
 * @description Process incoming tracks
 * @param RTCTrackEvent
 */
this.peerConnection.ontrack = (RTCTrackEvent) =&gt; {

    /* Are we dealing with an audio track? */
    if (RTCTrackEvent.track.kind === &apos;audio&apos;)
    {
    
        /* Create a new audio element and begin playing the media stream */
        const audioElement = document.createElement(&apos;audio&apos;);
        audioElement.srcObject = RTCTrackEvent.streams[0]; /* A track may contain many streams - we only care about the first one */
        audioElement.play();
    
    }

}</code></pre><p>After handling the <code>ontrack</code> event on the client side, we should be able to hear ourselves! We are not listening to our own microphone directly, rather, we are listening to our microphone through WebRTC.</p><h3 id="reading-pcm-audio-data-using-rtcaudiosink">Reading PCM audio data using RTCAudioSink</h3><p>We are now handling audio data from client peers on the server side. However, as it stands, we do not have a way to access PCM data yet.</p><p>To begin receiving PCM audio data from a remote audio track, we are going to be using the non-standard <code>RTCAudioSink</code> component WebRTC provides. This component will allow us to very easily access raw PCM data from any audio track.</p><pre><code class="language-js">/* Construct a new RTCAudioSink using the audio track we have received */
const audioSink = new RTCAudioSink(track);

/* Handle audio data */
audioSink.ondata = (data) =&gt; {

    /* Read PCM data from the samples */
    const pcm = data.samples;
    
    /* This will spam your console every 10ms with raw PCM data. We now have access to PCM audio data! */
    console.log(pcm);

}</code></pre><p>At this point, we have successfully implemented a way to receive raw PCM audio data from an RTC peer and can start to use this data to see if there is speech in it.</p><h2 id="using-vad-to-detect-speech">Using VAD to detect speech<a href="https://icseon.com/posts/2023-04-17-voice-activity-detection-with-webrtc/#using-vad-to-detect-speech">#</a></h2><h3 id="installing-initializing-vad">Installing &amp; Initializing VAD</h3><p>We now have the ability to access raw PCM audio frames and can use this alongside VAD to detect if the audio frame contains speech. For this, we can use the <code>@ozymandiasthegreat/vad</code> npm package. Let&#x2019;s start by constructing VAD:</p><pre><code class="language-js">/* Retrieve VAD through the VadBuilder */
const VAD = await VADBuilder();
const vad = new VAD(VADMode.VERY_AGGRESSIVE, 48000); /* WebRTC has a sample rate of 48000 */</code></pre><h3 id="using-vad-to-detect-voice-activity">Using VAD to detect voice activity</h3><p>Right now, we have access to VAD and can start using it to detect speech in audio frames. We can do this by using the <code>processFrame</code> method VAD provides. Let&#x2019;s go back to our RTCAudioSink and add the logic required for identifying speech.</p><pre><code class="language-js">/* Construct a new RTCAudioSink using the audio track we have received */
const audioSink = new RTCAudioSink(track);

/* Handle audio data */
audioSink.ondata = (data) =&gt; {

    /* Read PCM data from the samples */
    const pcm = data.samples;
    
    /* Determine if the PCM data contains speech */
    const vadResult = vad.processFrame(pcm);
    
    /* If the vadResult indicates we have speech, log a message to the console indicating such */
    if (vadResult === VADEvent.VOICE)
    {
        console.log(&apos;speech detected!&apos;);
    }

}</code></pre><p>Awesome. We now have a way to detect speech from audio. We&#x2019;re almost there, we only need to notify all peers that somebody is speaking.</p><h2 id="sending-packets-to-all-peers-to-notify-them-of-voice-activity">Sending packets to all peers to notify them of voice activity</h2><p><strong>Note</strong>: To not overcomplicate this too much, we are going to be using a simple array of <code>WebRTCPeerConnection</code> instances. I&#x2019;ll assume this array is named <code>peers</code>.</p><h3 id="defining-the-voice-activity-packet">Defining the voice activity packet</h3><p>A clean approach of building a packet in my personal opinion is abstracting the structure away in a class. Let&#x2019;s start by building the <code>VoiceActivityPacket</code> class which we are going to be sending to all peers.</p><pre><code class="language-js">export default class VoiceActivityPacket {

    /**
    * @author Icseon
    * @description VoiceActivityPacket constructor
    * @param username
    */
    constructor(username)
    {
        
        /* For easy packet identification, I am choosing to add the packet type in the constructor */
        this.packetId = &apos;VoiceActivity&apos;;
        
        /* We really just need to know who is speaking. That&apos;s all. */
        this.username = username;
        
    }

}</code></pre><h3 id="broadcasting-the-voice-activity-packet">Broadcasting the voice activity packet</h3><p>Now that we have defined the voice activity packet, we can start sending it to all peers and handle it. Let&#x2019;s start by sending it to everyone:</p><pre><code class="language-js">/* Construct a new RTCAudioSink using the audio track we have received */
const audioSink = new RTCAudioSink(track);

/* Handle audio data */
audioSink.ondata = (data) =&gt; {

    /* Read PCM data from the samples */
    const pcm = data.samples;
    
    /* Determine if the PCM data contains speech */
    const vadResult = vad.processFrame(pcm);
    
    /* If the vadResult indicates we have speech, log a message to the console indicating such */
    if (vadResult === VADEvent.VOICE)
    {
    
        /* Build the voice activity packet */
        const packet = new VoiceActivityPacket(peerConnection.someClass.username); /* You need to deal with authentication somehow, I&apos;ll assume the username is accessible like this. */
        
        /* Loop through every peer in the peers array */
        peers.forEach((peer) =&gt; {
        
            /* We can only send arrayBuffers, blobs and strings. That&apos;s why JSON.stringify() is required */
            peer.someClass.dataChannel.send(JSON.stringify(packet));
        
        });
    }

}</code></pre><p>We are now sending the voice activity packet to all peers. Obviously, this is a very primitive way of broadcasting packets but for demonstration purposes it should suffice. All that&#x2019;s left to be done is handle the packet on the client side.</p><h3 id="handling-the-voice-activity-packet">Handling the voice activity packet</h3><p>The voice activity packet is now being sent and received to clients. It&#x2019;s time to start handling it.</p><pre><code class="language-js">/* Listen for data from the server. */
unorderedDataChannel.onmessage = (data) =&gt; {

    /* Parse JSON */
    data = JSON.parse(data);
    
    /* Determine what packet we have received */
    switch(data.packetId)
    {
        
        /* Handle voice activity packets */
        case &apos;VoiceActivity&apos;:
            
            /* You can handle this in any way you&apos;d like. In this post, we are just going to log who is speaking. */
            console.log(`${data.username} is speaking!`);
            break;
    }
    
});</code></pre><p>In this snippet, we are checking the packetId of the data we receive and handling the <code>VoiceActivityPacket</code> by logging the username of the speaker. You may do anything with this information like invoking a UI transition to clarify that a participant is speaking.</p><h2 id="that%E2%80%99s-a-wrap">That&#x2019;s a wrap!</h2><p>You have now read how I deal with voice activity detection with WebRTC. I left out a lot of implementation specific details because I do not know your use case - if you&#x2019;re going to use this knowledge then you should apply it in the scope of your project.</p><p>Keep in mind that SDP negotiation is required after adding a new track/transceiver to peers and that it needs to be handled through your signaling server(s) accordingly.</p><p>Thank you for reading my post, and I hope that this helps someone out. I&#x2019;ll be writing more technical posts like this one in the near future as I have more to write about.</p><p>&#x2014; Icseon</p>]]></content:encoded></item><item><title><![CDATA[Some thoughts]]></title><description><![CDATA[Some architectural considerations I've been making lately]]></description><link>https://icseon.com/some-thoughts/</link><guid isPermaLink="false">650ede38d4b59f2c4adcee23</guid><dc:creator><![CDATA[Icseon]]></dc:creator><pubDate>Fri, 21 Oct 2022 12:48:00 GMT</pubDate><content:encoded><![CDATA[<p>Up until this point, I&#x2019;ve been using the traditional means of creating an application on the web.</p><p>That being the utilization of the HTTP protocol and using either Ajax or standard means to transmit data to the server.</p><p><br>I have been considering an alternative which would enable me to:</p><ol><li>Transmit data in realtime, with little to no latency between your peer and the server</li><li>Have the ability to transmit data from client to client, with a relay sitting in between for secret messaging without exposing IP addresses to the peers and without exposing any data to us, except encrypted and rather useless data.</li><li>Reduce loading times by at least a hundred times from what I currently have</li><li>Follow a more traditional means of application development</li></ol><p>Naturally, my eyes went to WebSockets. It would follow a similar strategy as the one used for T-Bot Rewritten. The thing is, I&#x2019;d have to implement these things too:</p><ol><li>A room system, allowing me to only emit packets to those within scope</li><li>A session handler, purely just for connections</li><li>A ping-pong system to ensure that we are not talking to a dead link for too long - connections will die</li></ol><p>Why would I need to bother with any of that, if all of this has already been solved? Enter: socket.io</p><h2 id="socketio">socket.io</h2><p>All the things that I had mentioned above are already solved here. Its usage would be similar as using Express and almost everything that I do in Express can be done with socket.io as well.</p><p>In all, this is my next choice. This is the step I am taking for all my future personal projects. It would allow me to develop things that were once either incredibly hard or impossible to implement without adding another networking layer on top of what I already had, which I am naturally against. It&#x2019;s either one, but never extend one to two. You don&#x2019;t want to repeat yourself.</p><h3 id="handling-of-sessions">Handling of sessions</h3><p>Historically, I&#x2019;ve always been writing my own session handlers for full control. Really, I don&#x2019;t have to. I can just utilise <code>express-session</code> along with <code>connect-redis</code> to have the exact same functionality, but overwrite the method that is used to generate session IDs so that all sessions can easily be retrieved for users, voiding the need to even have a session store at all. This sounds like the right approach.</p><p>You might think that I&#x2019;d need to use Express to use the <code>express-session</code> component. I understand you might think so, but that&#x2019;s totally and completely wrong. I&#x2019;d just have to slightly modify it so that it no longer uses cookies and that&#x2019;ll be that!</p><h3 id="goodbye-cookies">Goodbye cookies</h3><p>Since sessions will no longer be using cookies, I will no longer have to have any cookies in the next thing I build. This will void the need for a lot of hassle on the legal end and best of all, we would not need a cookie banner telling users that we have cookies like 99% of other platforms on planet Earth which is in my opinion, a tad annoying.</p><p>Session tokens will then be stored in LocalStorage, ready to be transmitted over the link once a connection is created to the remote endpoint. A perfect solution, I&#x2019;d say.</p><h3 id="handling-denial-of-service-attacks">Handling denial of service attacks</h3><p>If you create something, there will always be one to try to destroy it through any means possible. That is true for almost every single online project in existence, and I am certain that whatever I create will not be exempt from this rule. Traditionally, I&#x2019;d only need to bother with <code>limit_req</code> to throttle the number of requests that can be made, but since that all data will now be sent over the socket, this no longer becomes possible.</p><p>The solution fortunately, is rather simple. Instead of relying on <code>limit_req</code>, implement a rate limit for actions such as creating an account, sending a message, etc - on the application level. Sure, you&#x2019;d still need to open a connection, but we can easily limit that through nginx with little to no overhead.</p><h3 id="querying-the-data-source">Querying the data source</h3><p>Up until now I&#x2019;ve been manually writing SQL queries to query for data from the database. I wonder what&#x2019;s been going through my head all this time. I&#x2019;m going to be using an ORM that will make my life a lot easier going forward.</p><h2 id="closing-words">Closing words</h2><p>These are only a few of my ideas that I&#x2019;ll be using moving forward. I am posting this online to get feedback on these ideas and maybe create a better strategy although I do not expect much to change.</p><p>Thanks for reading!</p>]]></content:encoded></item><item><title><![CDATA[Keeping players in sync]]></title><description><![CDATA[Read how I approach synchronizing player transforms]]></description><link>https://icseon.com/keeping-players-in-sync/</link><guid isPermaLink="false">650ee077d4b59f2c4adcee44</guid><dc:creator><![CDATA[Icseon]]></dc:creator><pubDate>Sun, 15 May 2022 12:57:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-text"><strong>Note</strong>: This post is quite old. While a large part of this post is still true, I would approach things differently now.</div></div><p>The goal of multiplayer games more often than not is to synchronize state between all peers where, preferably, the server is authoritative</p><h3 id="a-small-introduction">A small introduction</h3><p>As some of you may know, I am the developer of Cubash. Cubash was an online game where users could interact with each-other and customise their character to their likings and make new friends.</p><p>Cubash had a game client in development but unfortunately, never came to see the light of day.</p><p>However, as of recent I became interested to play around with the client, and I started rebuilding the client from the ground up.</p><p>One of the subjects I find fascinating throughout all the projects I have ever worked on is networking and that&#x2019;s exactly what we&#x2019;re going to be talking about today.</p><p><strong>This post will aim to cover the bare basics and will leave out a lot and focus only on the networking side of things.</strong></p><h2 id="listening-for-client-connections">Listening for client connections</h2><p>For this project, I have settled with using the Godot Engine which uses the ENet networking library that offers a high level interface for using the UDP and TCP protocols.</p><p>Before we can start sending RPCs to peers, we need a server for clients to connect to:</p><pre><code class="language-gdscript">extends Node

var peer:NetworkedMultiplayerENet

# Simple method to start a server using ENet
func _host(max_clients:int = 16, port:int = 22000, in_bandwidth:int = 2457600, out_bandwidth:int = 2457600):

    # We need the SceneTree singleton so we can register signals and register the network peer
    var tree = get_tree()
    
    # Register signals
    tree.connect(&quot;network_peer_connected&quot;, self, &quot;_network_peer_connected&quot;)
    tree.connect(&quot;network_peer_disconnected&quot;, self, &quot;_network_peer_disconnected&quot;)
    
    # Create a new instance of NetworkedMultiplayerENet so we can start listening for connections
    self.peer = NetworkedMultiplayerENet.new()
    self.peer.compression_mode = NetworkedMultiplayerENet.COMPRESS_ZLIB
    self.peer.create_server(port, max_clients, in_bandwidth, out_bandwidth)
    
    # Finally we attach the network peer to the SceneTree singleton
    tree.set_network_peer(self.peer)
    tree.set_meta(&quot;network_peer&quot;, self.peer)

# Method that is called once the script is ready
func _ready():

    # Start server
    _host()</code></pre><p>Once our script is ready, we invoke the <code>_host()</code> method that will initialize a NetworkedMultiplayerENet instance that we can use for hosting our game server.</p><p>Now we have a game server ready to happily accept connections from clients!</p><h2 id="connecting-to-the-server">Connecting to the server</h2><p>What would a server be without a client to serve? Let&#x2019;s make our client connect to our server. For that, we&#x2019;ll need to create a script that handles all the networking for the client.</p><p>It will look quite similar to the script we have just made for creating a server except now instead of creating a server, we connect to (hopefully) a listening server.</p><pre><code class="language-gdscript">extends Node

var peer:NetworkedMultiplayerENet

func _connect(address:String = &quot;127.0.0.1&quot;, port:int = 22000):

    # Just like before, we need the SceneTree singleton for the exact same purpose
    var tree = get_tree()
    
    # Register the relevant signals
    tree.connect(&quot;connected_to_server&quot;, self, &quot;_connected_to_server&quot;)
    tree.connect(&quot;connection_failed&quot;, self, &quot;_connection_failed&quot;)
    tree.connect(&quot;server_disconnected&quot;, self, &quot;_server_disconnected&quot;)
    
    # Attempt to connect to the server. Similar to before, we create a new instance of NetworkedMultiplayerENet
    self.peer = NetworkedMultiplayerENet.new()
	self.peer.compression_mode = NetworkedMultiplayerENet.COMPRESS_ZLIB
	self.peer.create_client(address, port) # Note: create_client() instead of create_server()
	
	# Just like before, we do the attaching of the network peer to our SceneTree singleton
	tree.set_network_peer(self.peer)
    tree.set_meta(&quot;network_peer&quot;, self.peer)
    
    
func _ready():

    # Connect to the server once we&apos;re ready to do so
    _connect()</code></pre><p>A word on signals</p><p>Remember the signals we registered with <code>tree.connect()</code>?</p><p>They can help us make our network logic, but before we start doing that, let&#x2019;s explain what they mean:</p><h3 id="server-side-signals">Server side signals</h3><p><code>network_peer_connected</code><br>Invoked when a new client peer connects to our server.<br>We can use this signal to register network ownership of the Player node (or better known as their character, the only thing they should be in control terms of security).</p><p><code>network_peer_disconnected</code><br>Invoked when a client loses connection to our server or otherwise closes the socket. We should handle removal of client information once this is called.</p><h3 id="client-side-signals">Client side signals</h3><p><code>connected_to_server</code><br>Invoked when we have successfully connected to a game server.<br>We could use this signal to claim ownership of our local Player and to get the game going.</p><p><code>connection_failed</code><br>Invoked when we couldn&#x2019;t connect to a server after a time out occurs. We should let our user know that connection has failed.</p><p><code>server_disconnected</code><br>Invoked when the server disconnects you for any reason, like cheating or when the server closes. We should let the user know that the connection has closed down.</p><h2 id="making-network-code">Making network code</h2><p>Now that we know what every signal means and what it should do, let&#x2019;s start writing some basic network code.</p><h3 id="creating-a-new-player-on-the-server-side">Creating a new player on the server side</h3><p>Once our player has connected to our server, we need to create a new Player node for them. This will be their character that they can move around. Let&#x2019;s go back to the script we wrote to host a server and implement that logic:</p><pre><code class="language-gdscript">func _network_peer_connected(peer_id:int):
    
    # Create a new instance of the player Node
    var player = load(&quot;res://scenes/player.tscn&quot;).instance()
    
    # Set player name
    player.set_name(String(peer_id))
    
    # Give the client ownership of the player Node
    player.set_network_master(peer_id)
    
    # Insert the new Player Node to our game Node
    $&quot;/root/Game/Players&quot;.add_child(player)
    
    # Let the client(s) know about this. I&apos;ll explain this very soon!
    rpc(&quot;new_player&quot;, peer_id)</code></pre><p>Now, there&#x2019;s a new Player node reserved for the client that just connected to our game.</p><h3 id="creating-a-new-player-on-the-client-side">Creating a new player on the client side</h3><p>We have successfully created a new player for our client, but the client is not aware of this at all yet!</p><p>We need a way to tell <em>all</em> clients (including the one who just connected) about the creation of a new player. One way that I think is good is to have an RPC that sends over the client peer ID to all the peers, so that the clients can repeat what we just did.</p><p>Let&#x2019;s go back to the script we made to connect to a server and implement the <code>new_player</code> RPC:</p><pre><code class="language-gdscript">remotesync func new_player(peer_id:int):

    # Like on the server, we create a new instance of the player Node
    var player = load(&quot;res://scenes/player.tscn&quot;).instance()
    
    # Set player name
    player.set_name(String(peer_id))
    
    # Give network ownership to the peer we have received
    player.set_network_master(peer_id)
    
    # Finally, we add the player to the game Node
    $&quot;/root/Game/Players&quot;.add_child(player)</code></pre><p>That&#x2019;s it! Our clients are now automatically aware of any client that joins the server and creates a character for the player, including themselves.</p><h2 id="synchronizing-player-movement">Synchronizing player movement</h2><p>Inside the player Node movement script, we have to add additional logic, so we can send the Body velocity over the network and thus, let all other peers (including the server) be aware of where we are.</p><p>Why the server you ask? We want our movement to be secure, so we simulate the movement on the server side to check if the client is doing anything that&#x2019;s not supposed to happen.</p><h3 id="clientside">Clientside</h3><p>I assume you have already written your player movement logic script. If not, why are you adding networking to it now?</p><p>Moving to the player movement logic script, we add additional logic after we have calculated the velocity that we would be applying to our own player to make movement happen.</p><p>We also want to add a check to the Input handler that ensures that we only control our own player Node:</p><pre><code class="language-gdscript">onready var game = $&quot;/root&quot;.get_node(&quot;Game&quot;)

func _physics_process(delta:float = 0):

    # This variable contains the central force vector that will be applied to the Node
    var force = Vector3()
    
    # This variable contains the direction vector that is calculated by the Input handler
    var direction = Vector3()
    
    # Check if we are in control of this player.
    # Otherwise do no calculation here.
    if is_network_master():
    
        # Handle your input here using Input.is_action_pressed() - I leave that up to you.
        direction = Vector3()
        
    
        # This assumes you are using Vector3 and add_central_force to move your player around.
        # We populate this with the direction you calculated using the Input handler.
        # Again, this is up to you and heavily depends on the game you are making.
        force = Vector3()
        
    add_central_force(force)
    
    # Send our transform and direction over the network
    if is_network_master():
    
        # We use the script that&apos;s attached to the Game node to perform the RPC call
        game.rpc_unreliable_id(1, &quot;move&quot;, direction, get_transform())</code></pre><p>Now the direction and transform of our player Node is sent every physics process tick. By default, that&#x2019;s 60 times a second.</p><h3 id="serverside">Serverside</h3><p>The client is now sending us movement data. All we have to do is process the movement ourselves and send the result of that to all clients.</p><p>Let&#x2019;s look at the script we made to start a server again and add new logic:</p><pre><code class="language-gdscript">remote func move(direction, transform):
    
    # Obtain the RPC sender ID so we can find the relevant player
    var rpc_id = get_rpc_sender_id()
    
    # Find the player node
    var player = $&quot;/root/Game/Players&quot;.get_node(String(rpc_id))
    
    # Call movement function
    player.move(direction, transform)</code></pre><p>Let&#x2019;s also implement the <code>move</code> method that we are calling. In the player Node script:</p><pre><code class="language-gdscript">onready var game = $&quot;/root&quot;.get_node(&quot;Game&quot;)
onready var direction = Vector3()

func move(direction:Vector3, transform:Transform):
    
    # Update player state with the state received from the client
    direction = direction
    client_transform = transform # explicitly calling it client_transform here
    
func _physics_process(delta:float = 0):
    
    # Like the client, we have a force and a direction vector. We just run the same calculation here.
    # I still leave that up to you to do. It&apos;s your game and I do not know what you&apos;re making. :)
    var force = Vector3()
    
    add_central_force(force)
    
    # Send the transform that we, as server have calculated to all the clients
    game.rpc_unreliable(get_name(), &quot;move&quot;, get_transform())</code></pre><p>You may have noticed I am not using the <code>client_transform</code> variable here.</p><p>That&#x2019;s because you may choose to implement a way to allow slight differences between the client and server transform (there will be differences, no getting around that!) - but that is out of the scope of this post.</p><p>The server is now sending the transform of the player!</p><h3 id="processing-server-calculated-transforms">Processing server calculated transforms</h3><p>We are almost finished. Let&#x2019;s go back to the script we created to connect to servers and listen for the <code>move</code> RPC call:</p><pre><code class="language-gdscript">remote func move(peer_id:int, transform:Transform):
    
    # Get player node
    player = $&quot;/root/Game/Players&quot;.get_node(String(peer_id))
    player.move(transform)</code></pre><p>Lastly, we handle the transform data through the <code>move</code> method inside our <em>client</em> player Node script:</p><pre><code class="language-gdscript">func move(transform:Transform):

    # We can ignore the packet if we are the network owner of the player.
    # We already know our own state!
    if is_network_master():
        return
    
    # Set serversided calculated transform. You may want to use Tweening to make it look smoother.
    set_transform(transform)</code></pre><h2 id="you-made-it">You made it!</h2><p>In this post, we have successfully:</p><ol><li>Created a game server and game client</li><li>Handled adding of new players</li><li>Handled the movement synchronization of player Nodes</li></ol><p><strong>Thanks for reading</strong>. I hope you have learnt something useful.</p>]]></content:encoded></item><item><title><![CDATA[Why I moved on from PHP]]></title><description><![CDATA[To put it simply, I do not enjoy where PHP frameworks are headed]]></description><link>https://icseon.com/why-i-moved-on-from-php/</link><guid isPermaLink="false">650ee30cd4b59f2c4adcee82</guid><dc:creator><![CDATA[Icseon]]></dc:creator><pubDate>Tue, 01 Dec 2020 13:08:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-text"><strong>Note</strong>: At the time of writing this warning, this post is over 2 years old. I no longer use ExpressJS. I still stand by much of this post. Without expensive infrastructure, PHP is sub-optimal for any large scale web applications.</div></div><p>Do not get me wrong, I love PHP for the most part. I have been using it for years and I have to thank PHP for where I am right now. It has built opportunities for me that I otherwise possibly wouldn&#x2019;t have gotten.</p><p>Unfortunately, it has become clear that I need to make some changes. Up until now, I have been using the Phalcon framework to develop my web projects. However, starting PHP 8.0 they have announced that they were switching to &#x201C;native&#x201D; PHP which is just a fancy way of saying that they&#x2019;re going to be switching to being a framework based solely on PHP&#x2026;just like Laravel and Symfony. The only reason I was using Phalcon was because it&#x2019;s really an extension you can just install and have amazing performance just because of that. Plus, they have little to no implementations like authentication which allowed me to do that myself&#x2026; which is exactly what I was looking for!</p><p>I&#x2019;m afraid that will no longer be a thing. Also, the development for that framework in particular is really unpromising, their team is small. I do not want to take the risk of using Phalcon for all of my future projects, only to find I am using an unmaintained framework.</p><p>So I went looking for alternatives. There are no real alternatives, outside of just going ahead and writing my own PHP framework, which is what I did up until switching to Phalcon. The reason I&#x2019;m not using something like Laravel? Its performance is terrible (unless you cache everything, but does that really solve the problem?) and it&#x2019;s an extremely abstract framework. I want to implement things myself. I don&#x2019;t want things to &#x201C;just work&#x201D;, without knowing the underlying technology. I don&#x2019;t want to rely on someone else to provide security for my projects. That&#x2019;s something I want to have in my own hands.</p><p>Having realized at this point that most PHP frameworks just aren&#x2019;t for me, not because I can&#x2019;t use them, but because they don&#x2019;t allow much freedom, I considered writing my own PHP framework again. But do I really want to deal with that? It&#x2019;s almost like I am contradicting myself&#x2026; I want more control but when I have the option, I don&#x2019;t want to deal with it&#x2026; but hear me out.</p><p>Writing my own PHP framework would require me to implement routing and a controller &amp; view + model system myself and I would&#x2019;ve done that, had I not been introduced to the Phalcon framework that handled those things amazingly. I came to realize that I don&#x2019;t want to deal with <em>those</em> things at all.</p><p>So to be clear, yes, I want more control over how my application functions, but things like routing should be trivial and easy to go about. I shouldn&#x2019;t have to write my own framework that&#x2019;s maintained by a large group of people: me, I and myself. Furthermore, it wouldn&#x2019;t make much sense. My framework would be in &#x201C;native&#x201D; PHP, which is exactly the same route Phalcon is taking. Could as well just use Phalcon at that rate again.</p><p>So. I needed an alternative. An alternative to the thing we call PHP itself. I do not want to give up my control for abstraction and less performance. I do not want to rely on too much implementation by other people, as in, I want to implement some sensitive things myself to ensure its security and performance.</p><p>That&#x2019;s where it hit me. I remember using express.js at one point. Even its slogan is very promising. It is an unopinionated framework, just like Phalcon is. You do a lot of things yourself, and that&#x2019;s exactly what I am looking for! Plus, it has basic things like routing and MVC already implemented right out of the box! Plus, it&#x2019;s NodeJS. I&#x2019;ve wanted to move on for a while, and seeing how performant the vast majority of the applications based on Express are, I&#x2019;ve determined that it might be the right choice moving forward.</p><p>After tinkering around with it for a little bit, I have come to the conclusion that it is indeed what I am looking for. No more setbacks from PHP being PHP, and not a compiled language in itself unless you use OPCache which feels hacky, plus it&#x2019;s served through fastcgi, which cannot be good for performance whatsoever. I am aware that PHP8 ships with JIT and all those &#x201C;magical&#x201D; work-arounds&#x2026; but they&#x2019;re not really solving the issue itself, just patching it like you would with bandaid and toiletpaper. NodeJS is not compiled in ways things like C++ are, it is compiled once you run it once, unlike PHP that has the tendency to go through all of that again every single request which hurts performance terribly. Not to mention all of those frameworks that sit on top of that like Luigi&#x2019;s spaghetti&#x2026;</p><p>So ExpressJS it is! None of my future projects, if I have the choice, will be using PHP. It&#x2019;s time to move on.</p>]]></content:encoded></item></channel></rss>