Pixel Perfect Text in Unity

In this article we will take a look at the solution of the pixel perfect font problem in Unity as I implemented it for my currently developed game SODHARA. Unity, given that it is a modern tool geared towards high res output, is not particularly friendly to the retro-inspired style of the yesteryear – the script presented in this article is one of the ways in which this hurdle can be overcome.

1 Overview

In SODHARA I use TextMeshPro text components for everything text related. I use pixel perfect bitmap fonts in which all the glyphs’ sizes should be integer values, and the resolution is low enough so that each “pixel” (or, rather, a constituent square) of each glyph should correspond to a single pixel on screen.

The truth is however that these fonts are not perfect, and the problem is mostly with padding. This means that for the left (or right) aligned text the first glyph will probably be set properly but then the next one will note be an integer value pixels away, which will accumulate after a while and present rather unpleasant artifacts. Much bigger problem lies in centered text, since usually not even the first glyph is properly set in such a case.

This can be somewhat circumvented by carefully placing the text objects on screen so that, even though their position lies between pixels, the text itself is rendered properly. This only works if the resolution of the game is 1:1, i.e. if one pixel of the graphics corresponds to one pixel on screen. However, this approach yields diminishing results as one introduces features such as localization, template strings, and any process which tampers with the strings in runtime, thus making it impossible to place the objects properly in advance.

To get my point across, please consider images 1-4, whereas the odd ones are taken from the editor and the even ones are in-game screenshots.

Image 1. Centered text (font Uni 05_x by Craig Kroeger) in Editor.
Image 2. Centered text (font Uni 05_x) in game.
Image 3. Centered text (font 5MikroPix by Winter Design Studios) in Editor.
Image 4. Centered text (font 5MikroPix) in game.

Whenever a glyph pixel founds itself in a sub-pixel position it will be “rounded” to the closest integer value, which, in case of being around the center of the pixel, means it will appear in two places at once, yielding “chunky” and irregular glyphs (both in case of letters and spaces) all over the text. What’s worse, in case of dynamic text, the glyphs will change width and shape based on their (changing) position, which I find truly horrendous.

The solutions I found online usually boil down to either moving the entire text object to such a sub-pixel position that will “trick” the rounding process and make the text snap into place, or using custom scripts that snap the text to grid. As I stated previously, the first solution is problematic when one has to deal with varying text, i.e. when localization is involved, and it is near impossible to achieve with centered text. The second solution seemed more plausible, but none of the users that mentioned it shared the scripts they use. Thus, I decided to write my own.

2 Pixel Perfect Text Enforcer

Retro-styled games usually use a small number fonts – SODHARA in particular uses three, so the first step was to describe all the fonts used in order to have relevant info on each of them in one place. The class I used is shown in listing 1.

public class CharSpacingInfo
{
    public bool IsFontMonospaced => _fixedCharWidth > 0;
    public int SuggestedWidthFor(float width) => 
        IsFontMonospaced ? _fixedCharWidth : (int)(width / _pixelSize);

    public int _pixelSize;
    public int _fontSize;
    public int _fixedCharWidth;
    public int _interCharSpaceWidth;
    public int _whitespaceWidth;

    public CharSpacingInfo(
        int pixelSize, int fontSize, int fixedCharWidth, 
        int interCharSpaceWidth, int whitespaceWidth)
    {
        _pixelSize = pixelSize;
        _fontSize = fontSize;
        _fixedCharWidth = fixedCharWidth;
        _interCharSpaceWidth = interCharSpaceWidth;
        _whitespaceWidth = whitespaceWidth;
    }
}

Listing 1. The CharSpacingInfo class keeps tracks of the various font measurements.

The fields of the CharSpacingInfo class represent the following font qualities:

  • pixelSize: size of the “font pixel”, or the “constituent square” as I dubbed it previously,
  • fontSize: size of the font as set in the TextMeshPro script within the inspector,
  • fixedCharWidth: width of the glyphs in monospaced fonts (set to -1 in case the font is not monospaced),
  • interCharSpaceWidth: width of the space between individual glyphs, and finally
  • whitespaceWidth: width of the whitespace, or spacebar character.

The data given for each of the three fonts are used to force the glyphs to snap to pixel grid by means of wrapping the chars and spaces with <mspace={size}px>...</mspace> tags, and also followed up by <space={size}px> tag when a non-whitespace character is followed by another non-whitespace char. The size are taken from the CharSpacingInfo instances, namely the _fixedCharWidth and  _interCharSpaceWidth fields.

The actual wrapping is done by the methods shown in listing 2.

    public static string CharWithTags(char c, int size) => 
        $"<mspace={size}px>{c}</mspace>";

    public static string WrapStringInTags
        (TMP_FontAsset fontAsset, string text)
    {
        if (string.IsNullOrEmpty(text))
            return text;

        var wrappedText = "";

        for (var i = 0; i < text.Length; i++)
            wrappedText += WrapCharInTags(
                fontAsset,
                text[i],
                i < text.Length - 1 ? text[i + 1] : null);

        return wrappedText;
    }

    private static string WrapCharInTags
        (TMP_FontAsset fontAsset, char curr, char? next)
    {
        var result = CharWithTags(
            curr, 
            GetCharPixelWidth(
                fontAsset, 
                CHAR_SPACING_INFO[fontAsset.name], 
                curr));

        if (curr != ' ' && next.HasValue && next.Value != ' ')
            result += 
                $"<space={CHAR_SPACING_INFO[fontAsset.name]._interCharSpaceWidth}px>";

        return result;
    }

    private static int GetCharPixelWidth
        (TMP_FontAsset fontAsset, CharSpacingInfo spacingInfo, char curr)
    {
        if (curr == ' ')
            return spacingInfo._whitespaceWidth;

        if (fontAsset.characterLookupTable.TryGetValue
            (curr, out TMP_Character tmpCharacter))
            return spacingInfo.SuggestedWidthFor
                (tmpCharacter.glyph.metrics.width);

        Debug.LogWarning($"Glyph for character '{curr}' not found in the font asset. Using 0 width.");
        return 0;
    }

Listing 2. The WrapStringInTags and WrapCharInTags methods of the PixelPerfectTextEnforcerStatic class.

The WrapStringInTags method handles the string processing obviously. Each character (alongside the following one) is fed into the WrapCharInTags method which then wraps the input character into the required mspace tags and adds the space tag if necessary. Since the character glyphs themselves are pixel perfect, in this way we are only forcing the proper spacing between the characters and the proper whitespace character width as well.

There is however another case one should account for, and that is the situation in which the string of pixel perfect text is supposed to be centered, but is of odd length in pixels – such a string can never snap perfectly to the pixel grid since all of its pixels will smack in the middle of the grid fields the entire time. For that purpose the methods shown in listing 3 are employed to adjust the TextMeshPro object position in such a way that pixel perfect text is assured even in such a case.

    private static int GetStringPixelWidth
        (TMP_FontAsset fontAsset, string text)
    {
        var totalPixelLength = 0;

        for (int i = 0; i < text.Length; i++)
        {
            totalPixelLength += GetCharPixelWidth(
                fontAsset, 
                CHAR_SPACING_INFO[fontAsset.name], 
                text[i]);

            totalPixelLength += 
                i < text.Length - 1 && text[i] != ' ' && text[i + 1] != ' ' ? 
                    CHAR_SPACING_INFO[fontAsset.name]._interCharSpaceWidth : 
                    0;
        }

        if (fontAsset.name == "HalfEighties") // special adjustment for the "HalfEighties" font.
            totalPixelLength += 1;

        return totalPixelLength;
    }

    public static float GetPosXAdjustment
        (TMP_Text tmpText, string currText)
    {
        if (tmpText.horizontalAlignment != HorizontalAlignmentOptions.Center)
            throw new System.ArgumentException
                ("Adjusting horizontal position for non-centered text is pointless. Aborting...");

        var lengthInPixels = GetStringPixelWidth(tmpText.font, currText);
        var pos = tmpText.GetComponent<RectTransform>().anchoredPosition;

        var isTextPosXInt = pos.x % 1 == 0;

        var shouldNudge = isTextPosXInt && lengthInPixels % 2 != 0; // text has not yet been nudged but it should be
        var shouldRevert = !isTextPosXInt && lengthInPixels % 2 == 0; // text has been nudged but should now be pulled back

        return shouldNudge ? -.5f : (shouldRevert ? +.5f : 0f);
    }

Listing 3. The GetStringPixelWidth and GetPosXAdjustment methods of the PixelPerfectTextEnforcerStatic class.

The result is responsive pixel perfect text that is suitable for all alignment settings, and for both static and dynamic text alike. How it looks like in the end can be viewed in image 5.

Image 5. Pixel perfect text enforcer.

There is a short interval which the text needs to snap into place. This float value is exposed by the script and can be adjusted in the inspector. One more important thing to note is that the TextMeshPro object’s RectTransform component has to be set to integer coordinates for the PixelPerfectTextEnforcer script to work properly.

The entire script may be viewed on GitHub and freely used. It is tested and works well with all kinds of text – static, dynamic, localized or not.

3 Conclusion

Nasty artifacts can really spoil the illusion of playing a game released a few decades ago, which breaks the immersion and ruins the style which the developer worked so hard to achieve. This simple script handles the TextMeshPro font problems neatly and effectively, and lets developers concentrate on things more important then nudging the text objects by sub-pixel values around.

Developing a “Slot-Based” Save System in Unity

In this article we will take a look at how to implement a “slot-based” save system in a Unity project in the way I did it for my currently developed game SODHARA. A “slot-based” system has been around for a long time and is employed in many games, with Super Metroid perhaps being the first that comes to mind.

1 Overview

The basic idea is to have a (finite or infinite, in the case of this example it is three) number of slots of which the player selects one at the start of game, after which the game is saved automatically into that particular slot, usually upon quitting the current game. This is the variant covered by this article, although a few variations will be briefly touched upon at the very end.

In order to make this work, the following components are needed:

  1. a save game system, which will take care of the actual file writing and reading,
  2. a slot selection menu, which displays the current state of available slots and allows any of them to be selected or cleared, and finally
  3. an in-game pause menu, which will trigger the save game method upon quitting to main menu.

Let us inspect each of the moving parts individually.

2 Save System Controller

The save system controller is a component needed by both the UI system as well as the game proper. For that reason, I prefer to create an object which will persist (note that the term persistence here is used in a context different to the one related to databases) through scene transitions, and attach the save system controller script to that object, which precisely how I did it in SODHARA. In addition, it is logical to make that object a singleton, which yields the Awake() method for the SaveSystemCtrl script as given in Listing 1.

public static SaveSystemCtrl instance;

private void Awake()
{
    if (instance == null)
    {
        instance = this;
        DontDestroyOnLoad(gameObject);
    }
    else
    {
        Destroy(gameObject);
    }

    activeSlotIdx = -1;
}

Listing 1. The Awake() method of the SaveSystemCtrl persistent singleton class.

In order to justify its name the SaveSystemCtrl script the methods with which to save, load and clear game information. In addition, it needs the actual save data and for that I recommend writing a dedicated data class. The reason is purely organisational, as it allows one to keep track of what is being saved much more easily and efficiently. An example of one such class is given in Listing 2.

using UnityEngine;

[System.Serializable]
public class SaveData
{
    public int slotIdx;
    public bool isSlotEmpty;

    [Header("PLAYER STATE")]
    public int playerHealth;
    public int amountOfCrystals;

    [Header("PLAYER POSITION")]
    public int sceneIdx;
    public string sceneName;
    public Vector3 playerPosition;

    public SaveData()
    {
        slotIdx = 0;
        isSlotEmpty = true;

        playerHealth = 0;
        amountOfCrystals = 0;

        sceneIdx = 0;
        sceneName = "";
        playerPosition = Vector3.zero;
    }
}

Listing 2. The SaveData class keeps track of player’s health, amount of crystals and position.

The class is as standard as they come, the only part of which that requires a comment being the [System.Serializable] attribute which enables the display of the data class in Unity’s inspector. Constructor in this case is a matter of convenience and is not in any way mandatory.

Back to SaveSystemCtrl class – the first method we will work on is CreateSaveData() which has all the introduction it needs in its name alone and is shown in Listing 3.

private SaveData CreateSaveData()
{
    SaveData data = new SaveData();

    data.slotIdx = activeSlotIdx;
    data.isSlotEmpty = false;

    data.playerHealth = PlayerHealthController.instance.currentHealth;
    data.amountOfCrystals = DestructableTracker.instance.amountOfCrystals;

    data.sceneIdx = SceneManager.GetActiveScene().buildIndex;
    data.sceneName = SceneManager.GetActiveScene().name;
    data.playerPosition = PlayerController.instance.transform.position;

    return data;
}

Listing 3. The SaveSystemCtrl.CreateSaveData() method.

What this method does is access the relevant player information and use it to fill up an instance of the SaveData class and then pass it onward for other methods of the SaveSystemCtrl class to use. In fact, this data is only used by a single method, that one being Save() as given in the Listing 4.

public void Save()
{
    if (PlayerController.instance == null)
    {
        Debug.LogWarning("Player not found");
        return;
    }

    string dataPath = $"{Application.persistentDataPath}/slot{activeSlotIdx}.save";

    Debug.Log($"Saving to ${dataPath}...");

    var serializer = new XmlSerializer(typeof(SaveData));
    var stream = new FileStream(dataPath, FileMode.Create);
    serializer.Serialize(stream, CreateSaveData());
    stream.Close();
}

Listing 4. The SaveSystemCtrl.Save() method.

The method first checks whether the player exists at all. Next, the save file path is built using the Application.persistentDataPath value which contains the path to a persistent data directory. The data we are saving will then be serialised into an XML format and then fed into a file stream in order to write it into a file, and for that reason we are instancing the XmlSerializer and FileStream classes. After saving the data the stream is closed.

In order to reveal the actual folder to which the Application.persistantDataPath value points to, one can use the command given in the Listing 5.

EditorUtility.RevealInFinder(Application.persistentDataPath);

Listing 5. Revealing the Application.persistantDataPath folder in Finder or Explorer.

We will now take a look at the ReadSaveData(int slotIdx) method displayed in the Listing 6.

public SaveData ReadSaveData(int slotIdx)
{
    SaveData data = new SaveData();

    string dataPath = $"{Application.persistentDataPath}/slot{slotIdx}.save";

    if (!File.Exists(dataPath))
        return data;

    var serializer = new XmlSerializer(typeof(SaveData));
    var stream = new FileStream(dataPath, FileMode.Open);
    data = serializer.Deserialize(stream) as SaveData;

    data.isSlotEmpty = false;

    stream.Close();

    return data;
}

Listing 6. The SaveSystemCtrl.ReadSaveData() method.

There isn’t much to comment on in this case, as it is basically the same procedure as saving the game except for the “reverse” direction. Once the save file is read, the data is returned to the caller of the method.

Next up is the BackupAndClearSaveData(int slotIdx) method. As the save files in my project are fairly small, I decided to not simply clear them on deletion, but back them up in a dedicated folder. The code to do so is given in the Listing 7 and is fairly simple.

public void BackupAndClearSaveData(int slotIdx)
{
    string dataPath = $"{Application.persistentDataPath}/slot{slotIdx}.save";

    if (!File.Exists(dataPath))
    {
        Debug.LogWarning("File not found");
        return;
    }

    string targetDir = $"{Application.persistentDataPath}/deleted";

    if (!System.IO.Directory.Exists(targetDir))
        System.IO.Directory.CreateDirectory(targetDir);

    string targetDataPath = $"{Application.persistentDataPath}/deleted/{DateTime.Now.ToString("yyyyMMdd_HHmmss")} slot{slotIdx}.save";

    System.IO.File.Move(dataPath, targetDataPath);
}

Listing 7. The SaveSystemCtrl.BackupAndClearSaveData(int slotIdx) method.

After checking whether the save file exists we create the path to the backup directory, check if it exists and if not create it, and finally move the “deleted” save to the backup dir.

Finally, let’s have look at the ApplySavedDataAndClear() method as given in Listing 8.

public void ApplySavedDataAndClear()
{
    StartCoroutine(ApplySavedDataAndClearCoroutine());
}

private IEnumerator ApplySavedDataAndClearCoroutine()
{
    yield return new WaitForSeconds(.1f);

    PlayerHealthController.instance.currentHealth = dataToLoad.playerHealth;
    UIController.instance.UpdateHealth(PlayerHealthController.instance.currentHealth, PlayerHealthController.instance.maxHealth);
    DestructableTracker.instance.amountOfCrystals = dataToLoad.amountOfCrystals;

    PlayerController.instance.transform.position = dataToLoad.playerPosition;
    Camera.main.transform.position = new Vector3(
        dataToLoad.playerPosition.x, dataToLoad.playerPosition.y, Camera.main.transform.position.z);

    shouldLoadData = false;
    dataToLoad = null;

    GlobalSettingsCtrl.instance.fadeScreenCtrl.StartFadeFromBlack();
}

Listing 8. The SaveSystemCtrl.ApplySavedDataAndClear() method.

Just as ReadSaveData() is the opposite of Save(), so is ApplySavedDataAndClear() basically the opposite of CreateSaveData(). The dataToLoad attribute of the SaveSystemCtrl class is set in advance by one of the other scripts which handles save slot selection in the appropriate menu, which is done in order to persist saved data between scenes by saving it with a persistent object. Other than that there is nothing out of the ordinary, as the player’s health, crystal amount, position, as well as camera position are read from the saved data and applied. The saved game slot is then cleared and indicator boolean set to false. The final line activates a fade to black which is of course purely aesthetical and will not be further elaborated upon here.

3 Slot Selection Menu

Next up is the slot selection menu. The design is of course up to you, but as an idea you may inspect Image 1 and see how I did it for SODHARA.

Image 1. A slot selection menu design idea.

There are obviously two types of buttons in this menu, those being the Back button and the slot buttons, which also come in two states, i.e. empty (default) and populated. To the right of each slot button is the button used for clearing the slot and I consider these auxiliary elements part of the slot button in the populated state.

Much as it was the case with save data, it would be handy to have a data class to represent the stuff that this slot selection button should display. One such class is shown in Listing 9 and it is, unsurprisingly, quite similar to the SaveData class we inspected earlier.

public class SlotSelectionButton : MonoBehaviour
{
    [Header("FIELDS")]
    public GameObject dataEmpty;
    public GameObject dataPopulated;

    [Header("POPULATED DATA")]
    public TMP_Text captionHealth;
    public TMP_Text captionCrystals;
    public TMP_Text captionArea;
}

Listing 9. The SlotSelectionButton class contains the information displayed by a populated slot button.

As this class is not to be used in the Unity inspector there is no need to serialise it by adding the [System.Serializable] attribute as we did with SaveData earlier.

Listing 10 shows the class fields we are using in the SlotSelectionMenu class. The buttons are self-explanatory, the mainMenu points to the “upper-level” menu in relation to the slot selection one, and the saveData contain the data read from the three currently available save slots. 

[Header("BUTTONS")]
public GameObject btnSlot0;
public GameObject btnSlot1;
public GameObject btnSlot2;
public GameObject btnBack;

[Header("MENUS")]
public GameObject menuMain;

[Header("OTHER")]
public SaveData[] saveData = new SaveData[3];

Listing 10. The SlotSelectionMenu class fields.

The slot selection menu is stored as a game object which is interchangeably activated and deactivated as the player flips between it and the main menu. Therefore, the slot buttons become relevant only when this menu is active and they should be updated each time it is activated. Now, this presents some questions in regards to optimisation, as it is not really necessary to update the slots each time the menu is activated since it is only possible to save while in-game, but for simplicity purposes let’s do it this way, especially as it does not significantly hinder the performance. In order to track the activation of the object we need Unity’s OnEnable function as shown in Listing 11.

private void OnEnable()
{
    PopulateSlotData();
}

private void PopulateSlotData()
{
    for (int i = 0; i < 3; i++)
        PopulateSlotDataByIdx(i);
}

private void PopulateSlotDataByIdx(int slotIdx)
{
    SlotSelectionButton currSlot = (slotIdx == 0 ? btnSlot0 : slotIdx == 1 ? btnSlot1 : btnSlot2).GetComponent<SlotSelectionButton>();

    SaveData data = SaveSystemCtrl.instance.ReadSaveData(slotIdx);
    saveData[slotIdx] = data;

    currSlot.dataEmpty.SetActive(data.isSlotEmpty);
    currSlot.dataPopulated.SetActive(!data.isSlotEmpty);

    if (data.isSlotEmpty) return;

    currSlot.captionHealth.text = $"HP {data.playerHealth}";
    currSlot.captionCrystals.text = $"CRYS {data.amountOfCrystals}";
    currSlot.captionArea.text = $"Area: {data.sceneName}";
}

Listing 11. Populating the save game slots upon activating the menu within SlotSelectionMenu class.

We’ll concentrate on the PopulateSlotDataByIdx(int slotIdx) method as the other two are self-explanatory. The relevant button is selected based on the slotIdx argument – the buttons could’ve been saved as an array in which case this would be slightly simpler, but I opted for this route in this particular case. Next, the saved data is read and stored in the saveData array, after which the button is set up for display. Button’s default state is empty so that one requires no setup. Based on the state of the isSlotEmpty field of the data variable the empty and populated state are activated or deactivated, and then the check to see if there is any data to be loaded is made – if not the method returns, otherwise the relevant info is fed into the TMP_Text elements of the button.

Shown in Listing 12 is the ClearSlot() method which does exactly what it says it does. Again, it is not necessary to reload all the slots after clearing one of them, but in this case it’s a simple and cheap solution so I opted for that route once again.

public void ClearSlot(int slotIdx)
{
    SaveSystemCtrl.instance.BackupAndClearSaveData(slotIdx);
    PopulateSlotDataByIdx(slotIdx);
}

Listing 12. The SlotSelectionMenu.ClearSlot() method.

The final element of the SlotSelectionMenu script is the StartGame(int slotIdx) method as shown in the Listing 13.

public void StartGame(int slotIdx)
{
    GlobalSettingsCtrl.instance.fadeScreenCtrl.StartFadeToBlack();
    StartCoroutine(StartGameCoroutine(GlobalSettingsCtrl.instance.fadeScreenCtrl.fadeSpeed, slotIdx));
}

private IEnumerator StartGameCoroutine(float timeToWait, int slotIdx)
{
    yield return new WaitForSeconds(timeToWait);

    SaveSystemCtrl.instance.activeSlotIdx = slotIdx;

    SaveSystemCtrl.instance.shouldLoadData = !saveData[slotIdx].isSlotEmpty;
    SaveSystemCtrl.instance.dataToLoad = saveData[slotIdx].isSlotEmpty ? null : saveData[slotIdx];

    SceneManager.LoadScene(saveData[slotIdx].isSlotEmpty ? menuMain.GetComponent<MainMenu>().newGameScene : saveData[slotIdx].sceneName);
}

Listing 13. The SlotSelectionMenu.StartGame() method.

The reason I use the coroutine is this case is solely in order to make my crossfade thingy, i.e. the fadeScreenCtrl component, work properly. What is happening basically is that I initiate a fade to black and only once that is complete will I call the StartGameCoroutine(), so that all the nasty transitions are happening behind the black curtain.

The activeSlotIdx of the SaveSystemCtrl class is the currently active slot, i.e. the one to which the game will be saved upon quitting the game via pause menu – obviously, it must persist through the scene transitions and is therefore kept in a persistent object. Fields shouldLoadData and dataToLoad are used for feeding the player the saved data after loading the required scene, which is done in the last line of the method, which loads the first scene in the game if the slot is empty, otherwise it loads the scene contained in the save file.

The last relevant snippet of code for loading the game can be seen in Listing 14.

private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
    if (SaveSystemCtrl.instance.shouldLoadData)
        SaveSystemCtrl.instance.ApplySavedDataAndClear();
    else
        GlobalSettingsCtrl.instance.fadeScreenCtrl.StartFadeFromBlack();
}

Listing 14. Applying the save data in case the data is present, otherwise activating the crossfade.

Unity’s OnSceneLoaded() function is activated once the current scene is completely loaded. At that point we are sure that all the necessary assets are in their place and so we can apply the saved data such as physical position of the player safely in case there is anything to apply. If not, the player is left in its default position and the fade from black is initiated.

4 In-Game Pause Menu

In SODHARA the game is saved when the player quits the game and returns to main menu. There isn’t much to discuss here actually, as the only two relevant lines are given in the Listing 15.

SaveSystemCtrl.instance.Save();
SaveSystemCtrl.instance.activeSlotIdx = -1;

Listing 15. Saving the game upon quitting to main menu.

Setting the activeSlotIdx to -1 is simply a precaution measure and is probably not necessary but, as always with coding, it’s better to be safe than sorry.

5 Conclusion

Once all the pieces are in place your project should now have a fully functional save system in place. Shown in the Image 2 is how it looks like in SODHARA v0.4.3.

Image 2. Slot-based save system as implemented in SODHARA.

The good thing about this system is that it is fairly simple and robust, yet it allows for variations and can thus be adapted to suit different needs. What immediately comes to mind is greater number of save slots, infinite number of slots even, manual as opposed to automatic save, and even saving to an arbitrary slot which in turn makes it a classical manual save system.

For any questions and comments feel free to contact me via the contact form of the website.

Consuming a REST API with ABAP

A RESTful (or REST for short) API is an architectural style for an application program interface (API) that uses HTTP requests to access and use data. That data can be used to GET, PUT, POST and DELETE data types, which refers to the reading, updating, creating and deleting of operations concerning resources (Gillis). To consume a REST API simply means to use any part of it from an application.

The simplest form of an HTTP request is certainly GET without parameters which is usually used to retrieve all entries offered via particular endpoint. In the following tutorial I will be looking at creating an ABAP program which consumes a REST API and displays all the entries retrieved as a HTML page.

Getting Started

Most of the coding will be done in Eclipse with ADT, but the entire process is as easy to follow when one is using SAP GUI tools as well. Select File / New / Other, and then type in “program” to filter out the results; select ABAP Program to create one. The result can be seen on Screenshot 1.

Screenshot 1 – A new ABAP program.

Define a selection screen with a single parameter of type string (Screenshot 2).

Screenshot 2 – Defining a selection screen.

In order to get the selection screen frame caption and the parameter label open the program with SAP GUI (Screenshot 3).

Screenshot 3 – Opening the program with SAP GUI.

Head over straight to the Text Elements page (Screenshot 4).

Screenshot 4 – Program opened with SAP GUI.

Add the entry 001 into the Text Symbols table with value “User Input” (can be anything really). The filled out table can be seen on Screenshot 5.

Screenshot 5 – Text Symbols table.

Now, head over to the Selection Texts tab and change the value of P_URL entry to “Rest API URL” (again, it can be anything). Check Screenshot 6 for comparison.

Screenshot 6 – Selection Texts table.

This concludes the initial setup, thus letting us move on to consuming the REST API itself.

Consuming the REST API

The basic idea, which is by no means exclusive to ABAP, comes down to following five steps:

  1. Create an HTTP Client Object
  2. Make a Request
  3. Ask for a Response
  4. Get the Data
  5. Display the Data

Create an HTTP Client Object

To create an HTTP client object in ABAP one should make use of cl_http_client class and its static method create_by_url (there are other options as well but this one suits us best in this case).

In order to make use of Eclipse’s advanced assistance features, start typing the name of the class and then hit CTRL+Space – an auto-complete list should pop up letting you choose one of the options available based on what you typed down already. Not only does this work with methods as well, but there is an additional feature of using SHIFT+Enter to select the method, which then types out the entire signature, i.e. reveals all the IMPORTING, EXPORTING, EXCEPTIONS and other types of parameters that the method works with (Screenshot 7).

Screenshot 7 – Creating an HTTP Client Object.

The method create_by_url takes in the URL of the REST API we wish to consume (fed in via parameter on the selection screen in this case) and returns the HTTP client object it creates (lo_http in our case). The exceptions stated in the signature can then be evaluated and properly handled, however that is out of the scope of this tutorial.

Make a Request

The IF sy-subrc = 0. check ensures that the program is terminated in case any errors occur. 

If, on the other hand, all went well we now have our HTTP client and we are in position to make a request. For this we utilise the send method (again, CTRL+Space and SHIFT+Enter make our lives easier in Eclipse) which only cares about a timeout value, which is here set to 15 but can be any other value that works as well. The result can be seen on Screenshot 8.

Screenshot 8 – Making a Request.

Ask for a Response

Now that the request has been made, we should ask for a response – this is achieved through method receive which takes no input and returns no output values (Screenshot 9).

Screenshot 9 – Asking for a Response.

Get the Data

Data is accessed via response, which is a field of the lo_http object. The get_cdata method used is the way of getting the data in character format, i.e. as a string. Since what we will be receiving from the API is a JSON object, this is exactly what we want (Screenshot 10).

Screenshot 10 – Getting the Data.

Display the Data

The data is displayed using the cl_abap_browser class and its method show_html. The method signature once fully displayed in Eclipse is pretty self-explanatory, however of its myriad of input parameters we will only be using two. This gives us perhaps not the fanciest, but certainly quite serviceable display of the JSON objects retrieved (Screenshot 11).

Screenshot 11 – Displaying the Data.

Example of REST APIs Ready for Consumption

Some of the URLs I have used are listed below:

Screenshot 12 – Data Displayed as an HTML Page.

The final result can be seen on Screenshot 12.

References

Gillis, Alexander S. “REST API (RESTful API)”. TechTarget, 11 Oct. 2022, https://www.techtarget.com/searchapparchitecture/definition/RESTful-API

Citation style is MLA (9th ed.).

Using Mylyn 3.23 with Eclipse ADT

Mylyn is a task management system that is integrated in the Eclipse tooling. As a tool it is highly recommended for the purpose of automatic organisation of Eclipse ADT workspace.

Install the plugin via Eclipse Marketplace: input “mylyn” as search term and then choose Mylyn 3.23 (currently latest version, it’s not the first result so scroll down a bit).

Screenshot 1 – Task List icon.

After restarting Eclipse access the Task List via Window / Show View / Task List, or via shortcut to the right (Screenshot 1).

Screenshot 2 – Create a new task.

Create a new task as either a local task or a task connected to one of the supported issue tracking tools such as JIRA – NB, I have only tested the local task functionality so far. Fill out the requested fields and save the task (Screenshot 2).

Screenshot 3 – Activate task.

Now, access the Task List again, right click on one of the tasks and select Activate (Screenshot 3). Activating the task for the first time will close the ABAP project in the Project Explorer to the left.

The purpose of Mylyn is automatically monitoring the files the developer accesses during work on a particular task so that once focus switches to that task again all unnecessary data stays hidden. Any files accessed while a task is active will be marked as “important” for the task and will thus be revealed any time the task is accessed.

Using the + sign button next to the project name in Project Explorer will reveal all the project files. Closing and opening the project again will once more reveal only the files Mylyn recognised as important for the active task.

Cakewalk SONAR MIDI Controllers ReWired to Max/MSP

In this tutorial I will show you my way of connecting Max/MSP to my DAW of choice – Cakewalk SONAR – and also an approach to handling multi-channel MIDI information coming to Max/MSP over from the DAW. My idea was to use Max/MSP for creating instruments which would then be controlled by the SONAR’s sophisticated MIDI sequencer to which I am more than used to. While it wasn’t particularly hard to achieve this goal, there are some quirks to the approach I would like to point out.

The Max/MSP patch I am building for this is fairly simple and uses only the basic objects Max has to offer. I would thus recommend even to the complete beginners in Max/MSP as it offers great results and is very easy to understand and build.

Complete patch

Please note that I am by no means an expert in Max/MSP and thus there may be much better ways to achieve the same or better result. This is simply my way of doing this. By all means please comment if you have better understanding of the subject and can offer more effective solutions.

CTLIN Versus NOTEIN

The two objects used to send MIDI data from the DAW to Max/MSP are notein and ctlin. Object notein receives MIDI events and can output the pitch, velocity and channel of the event, while ctlin receives MIDI controller data and outputs the value (0-127), controller number and its MIDI channel.

The reason why I use the latter and not the former is that it seems that ReWire in SONAR can only have a single MIDI channel:

Rewire options in SONAR Platinum.

This creates two problems in case you have more than one MIDI channel sending data as 1) you cannot separate incoming MIDI events by channels in Max/MSP and 2) the onset of a new event in any of the channels interrupts performance of the ongoing event in another channel (hence effectively disabling polyphony). Now, this may be different in other DAWs and I will in fact test this in the future, but for now it’s more convenient for me to use MIDI controllers as these do not face these obstacles when using Max/MSP ReWired to SONAR.

MIDI Controller Data

Different MIDI controllers are easily differentiated by their numbers as they are set in SONAR. CC01 is usually Modulation, CC02 is Breath Controller, and so on (these do not have any pre-defined meaning when sending them to Max/MSP as only their numbers count). An easy way of achieving polyphony thusly is by writing layer upon layer of different numbered CC data which are then routed to appropriate inlets upon their arrival to Max/MSP. The only rule I’ve noticed in numbering these – and it is only a rule of convenience, not mandatory! – is that the numbers should steadily increase (i. e. “1, 2, 3”, not “1, 2, 95”). Why this is so, we will see later.

Here’s a short loop I prepared using three controller lanes:

Short loop utilizing three controller lanes.

One important thing to note while writing such sequences – MIDI controllers do not have end times as data attached to their events, unlike regular MIDI events. It is thus necessary to write in end points to your values separately, which is what is happening in the third picture with each of the four notes having rests written after them.

All of these should be written into the MIDI track created upon inserting the Max/MSP ReWire instrument via Insert\ReWire Device\Uncategorized\Max 7 (actual path may vary but it’s certainly under Insert in Cakewalk SONAR).

Grabbing the Data in Max/MSP

Once Max/MSP ReWire device is created in SONAR and the MIDI CC data typed in, it’s time to start up Max/MSP and create a new patch. First of all, to make any of this work, you should bring up Options\Audio Status and select the ReWire device as your driver (in my case it’s “ad_rewire” but yours may be named differently) and make sure the audio engine is running (the big switch in the upper left corner).

Max/MSP audio engine settings.

Once that is done, we’re ready to create our patch.

The Max/MSP Patch

First of all we need the ctlin object which receives the CC data sent by the DAW. For monitoring purposes attach two integer boxes to its outputs #1 and #2 so that you can watch the numbers change as the playback within DAW cycles in a loop. Below is a pic of how it looks on my end.

Receiving end of the CC data sent by DAW.

The next step is to route all those CC inputs according to their numbers as we will certainly want different controllers to do different things. The way I do it is by using gate with as many outputs as there are controllers. This is why I said it’s best to use steadily increasing numbers as it is most practical thing to have on screen (why have 95 outputs if you will only use three). As is visible on the image below, output #2 of the ctlin object controls which gate is open, and the associated CC value is then passed along the appropriate path.

Gate object attached.

This is where this tutorial could end basically. The data is received and directed to different outputs based on their controller numbers. However, before passing this data onwards to the oscillators I decided to include a basic glissando engine which could be used either for long glides between pitches or simply for preventing nasty clicks and artifacts at points where CC values change. This is handled via sub-patch I called glissandoEngine and I will describe it more detail next.

The glissandoEngine Sub-Patch

The idea behind this is fairly simple – we want smooth glissandos between value changes in our controller data instead of sudden jumps which is how things are by default. The approach is not remarkable though, again, there are some quirks one should be careful with. Here is how I solved some of the problems I faced.

We start out with an inlet receiving the controller values from the main patch. This value is then sent to a trigger which first outputs that same value and then the bang which is used later – more on that when we get to that.

The integer then enters a select object which checks if it is equal to zero or not. This is one of the quirks I mentioned earlier – my first approach was simply to send integers to mtof objects, expecting them to return frequency zero in case of receiving zero integers. However, this was not the case as zero integers translate to some 8. Hz when passed through mtof, which effectively makes the rests not silent (especially audible when using phasor~). Thus I had to check the incoming integers and in case of zero (i. e. rests) I would send them directly to the float outputs without passing them through mtof. The non-zero integers would of course be run through mtof and then passed to float outputs. The result is visible in the float box below the mtof object.

In order to have a glissando ramp at all we need a line~ object driven by an appropriate message. This message has to be in such a format: “starting value”, “end value” “time ramp”. This translates to: “change value from starting to end within time ramp period”. The tricky part is that what is at the moment “end value” should immediately after become “start value” for the next glissando – this is achieved by using triggers and floats to store and send out values at appropriate time.

glissandoEngine sub-patch

First of all, after the float box showing the current frequency there is a trigger which sends out two floats one after the other. The right output is sent out first and it first enters a float number box which I use simply for control. Afterwards, the float goes to another trigger which sends out a float and a bang. Float enters input #3 of our pack object (more on this shortly!) and the bang is sent elsewhere (more on that shortly as well!). The left output of the earlier trigger is sent to a float’s RIGHT output and is thus simply stored and NOT fired out right away. What forces this value through the output is the bang we mentioned just a moment ago – what is important is that this happens only once all other values necessary for the pack object are in place as relasing this float completes the puzzle and also triggers the pack object into action.

Now, regarding the pack object… Its arguments are “0. , 0. 0.” – note that there are FOUR values, as comma also counts as an argument. This is the skeleton for the message we talked about earlier: “starting value”, “end value” “time ramp”. Let’s go from right to left, the Max/MSP way! Fourt (right-most) of all, we have time ramp value which is actually sent independently from the main patch and which controls how long glissandi last. Third, we have the end value which is the most recent float received from the main patch as CC integer and processed within the sub-patch. Second input is the one opened by our comma – no input goes here as we want our comma never to change. Finally, the first input is the starting value, i. e. the value previously received and the one that is still being output to the main patch (this is made possible by storing it within float object and only outputing it with a bang sent out by the most recent float arriving from the outside). Finally, our pack is flushed out into our message’s RIGHT output, as we don’t want this to trigger the message but instead set its contents.

The message itself is triggered by the bang routed from a trigger hit by the integer coming in from the main patch. What this achieves is that the whole right side of the sub-patch is first performed, and only then is the glissando initated by this one bang. The message then goes to the line object and then goes back out to the main patch.

Outputing the Glissandi

In this simple example I have used three CC lanes and thus have three instances of the glissandoEngine and associated controls. This data is sent to three oscillators and then to the output via gain~ object. As I said, these flows may be used for all kinds of purposes, but it’s fairly easy to take it from here once you know the basics and what exactly you want to achieve.

Conclusion

I hope I helped clarify some of the problems associated with ReWiring MIDI data from DAWs to Max/MSP. As I said before, there may be better approaches to this – this is simply how I prefer to do it at the moment. If you have any questions or comments, please feel free to write.

In case you would like to take a look at the Max/MSP 7.2.0 patch or SONAR Platinum 23.5.0 B32 project file, you can find them here: https://drive.google.com/open?id=1BMbpK2FlsS063CELmDRvODUbMVYOjQZ7

In case you want this whole tutorial as a PDF, you can have it here: https://drive.google.com/open?id=1jfIy7DHw1rKr1RwLyfZyKYbb1B-iU2Vm

Have fun with ReWire!