Typewriter Text Revisited Revisited

This ongoing adventure to create a typewriter text effect has had a lot of twists and turns over the years. Back in 2011 I used Pure Data to achieve this effect. Fast forward to 2019 and I experimented with Kdenlive and Natron before settling on Animation Nodes. In April 2020 update on this I detailed how I used Animation Nodes and attempted to use Aegisub to create this effect. Around the same time I had started experimenting with expressions in Natron to achieve the same effect.

The value of a parameter can be set by Python expressions. An expression is a line of code that can either reference the value of other parameters or apply mathematical functions to the current value.

The expression will be executed every times the value of the parameter is fetched from a call to getValue(dimension) or get().

In theory with Natron expressions I could created a counter that would increment on every frame and type words out character by character. Y’know, like a typewriter. I’m forever learning Python so after a lot of effort, and a lot of help from people on the Natron forum I came up with the following solution. In the Text node I entered the following expression:

originalText = original.text.get()
output = " "
ptr = 0
slowFac = 4
for i in range(frame/slowFac, len(originalText)+1):
	if frame/slowFac < len(originalText):
		ptr=frame/slowFac
	else:
		ptr=len(originalText)
ret = originalText[0:ptr]

A fellow Natron user greatly simplified the code and presented the following solution:

text = Text1.text.get()
ret = text[:frame-1]

Success! I used this in the last video for Design Yourself:

The typewriter text effect starts from 01:04. The same Natron user also posted an alternative solution.

I noticed a bug which meant that I couldn’t change the speed that the letters typed out at. One method of speeding up the text would be to use ret = text[:frame*2-1] or a different multiplier. However, I wanted something a little bit more precise, so I thought about using the Retime node. Unfortunately there was a bug which prevented this. The workaround of using a Constant node worked. In the end it got fixed, but not in time for making that Design Yourself video.

In June I was asked if I could make an intro video for Network music Festival. The organisers wanted around 10 slides of text to appear throughout the video. Some had only several words on them but some had large blocks of text.

I already decided that I wanted to use the typewriter text effect to make the text appear and then to hold that text for a couple of seconds. This presented an interesting problem. Without a Retime node the text appears one character per frame. With a large block of text 250 characters in length (including spaces) this would take, well, 240 frames to appear, which at 24 fps would be 10 seconds. The organisers wanted the video to be about a minute long, so having one slide take up 10 seconds would be far too long.

What I needed was a method for making an arbitrary amount of text to appear within a specific time/frame count. My final Natron expression (after a bit of bug fixing) looked like this.

text = Source.text.get()
letter= 0

# what frame to start triggering the write-on effect
trigger = 15

# how many frames it'll take to write the full text
length = 46

# map values. Taken from herehttps://stackoverflow.com/a/1969274
def translate(value, leftMin, leftMax, rightMin, rightMax):
    # Figure out how 'wide' each range is
    leftSpan = leftMax - leftMin
    rightSpan = rightMax - rightMin

    # Convert the left range into a 0-1 range (float)
    valueScaled = float(value - leftMin) / float(leftSpan)

    # Convert the 0-1 range into a value in the right range.
    return rightMin + (valueScaled * rightSpan)


if  frame >= trigger:
	letter = int(ceil(translate(frame-trigger, 1, length, 1, len(text))))
else:
	letter = 0

ret= text[:letter]

This expression does several things. It first allows a user to specify at which frame the text will appear (trigger). Then, no matter how much input text there is it will be mapped to the length value. Oddly Python doesn’t have a built in mapping function so I had to use the one from here. Unfortunately it doesn’t work as expected if your Text node has keyframed text changes. So, for that you’ll have to have multiple Text nodes. Here’s the finished Network Music Festival video.