uxn tutorial: day 7, more devices

en español:

tutorial de uxn día 7

this is the seventh and last section of the uxn tutorial! here we talk about the devices in the varvara computer that we haven't covered yet: file, datetime and audio.

uxn tutorial

this should be a light and calm end of our journey, as it has to do less with programming logic and more with the input and output conventions in these devices.

let's begin!

the file devices

the file devices in the varvara computer allow us to read and write to external files.

there are two of them and they work in exactly the same way.

device ports

their ports are normally defined as follows:

a read operation is started when the read short is written to, and a write operation is started when the write short is written to.

these might seem like a lot of fields to handle, but we'll see that they are not too much of a problem!

reading a file

the following discussion will focus in only one file device. don't forget that for more advanced uses you can take advantage of both!

in order to read a file, we need to know the following:

and that's it!

we can use a structure like the following, where the filename and reserved memory are under a label, and the load-file subroutine under another one:

note that for the filename we are using the raw string rune (") that allows us to write several characters in program memory until a whitespace is found.

in this example we are writing a character to the console according to the success short being zero or not, but we could decide to take any action that we consider appropriate.

also, in this example we are not really concerned with how many bytes were actually read: keep in mind that this information is stored in File/success until another read or write happens!

it's important to remember that, as always in this context, we are dealing with raw bytes.

not only we can choose to treat these bytes as text characters, but also we can choose to use them as sprites, coordinates, dimensions, colors, etc!

writing a file

in order to write a file, we need:

keep in mind that the file will be completely overwritten unless you set append to 01!

the following program will write "hello" and a newline (0a) into a file called "test.txt":

note how similar it is to the load-file subroutine!

the only differences, beside the use of File/write instead of File/read, are the file length and the comparison for the success short: in this case we know for sure how many bytes should have been written.

a brief case study: the theme file

programs for the varvara computer written by 100r tend to have the ability to read a "theme" file that contains six bytes corresponding to the three shorts for the system colors.

these six bytes are in order: the first two are for the red channel, the next two for the green channel, and the last two for the blue channel.

this file has the name ".theme" and is written to a local directory from nasu whenever a spritesheet is saved.

uxn themes

reading the theme file

we could adapt our previous subroutine in order to load the theme file and apply its data as system colors:

note how the &data and &r labels are pointing to the same location: it's not a problem! :)

writing the theme file

and for doing the opposite operation, we can read the system colors into our reserved space in memory, and then write them into the file:

i invite you to compare these subroutines with the ones present in the 100r programs like nasu!

nasu source code

the datetime device

the datetime device can be useful for low precision timing and/or for visualizations of time.

it has several fields that we can read, all of them based on the current system time and timezone:

based on this, it should be straightforward for you to use them! e.g. in order to read the hour of the day into the stack, we'd do:

some time-based possibilities

i invite you to develop a creative visualization of time!

maybe you can use these values as coordinates for some sprites, or maybe you can use them as sizes or limits for shapes created with loops.

or what about conditionally drawing sprites, and/or changing the system colors depending on the time? :)

you can also use the values of date and time as seeds to generate some pseudo-randomness!

lastly, remember that for timing events with more precision than seconds, you can count the times that the screen vector has been fired.

the audio device

at last, the audio device! or i should say, the audio devices!

varvara has four identical stereo devices (or "channels"), that get mixed before going into the speakers/headphones:

similar to how in the screen device we can draw by pointing to addresses with sprite data, in the audio devices we will be able to play sounds by pointing to addresses with audio data ("samples").

stretching the analogy: similar to how we can draw sprites in different positions on the screen, we can play our samples at different rates, volume, and envelopes.

we'll assume that you might not be familiar with these concepts, so we'll briefly discuss them.

samples

as we mentioned above, we can think of the sample data as the equivalent of sprite data.

they have to be in program memory, they have a length that we have to know, and we can refer to them by labels.

the piano.tal example in the uxn repository, has several of them, all of them 256 bytes long:

piano.tal source code

and what do these numbers mean?

in the context of varvara, we can understand them as multiple unsigned bytes (u8) that correspond to amplitudes of the sound wave that compose the sample.

a "playhead" visits each of these numbers during a specific time, and uses them to set the amplitude of the sound wave.

the following images show the waveform of each one of these samples.

when we loop these waveforms, we get a tone based on their shape!

piano-pcm:

piano sample waveform

violin-pcm:

violin sample waveform

sin-pcm:

sin sample waveform

tri-pcm:

tri sample waveform

saw-pcm:

saw sample waveform

similar to how we have dealt with sprites, and similar to the file device discussed above, in order to set a sample in the audio device we just have to write its address and its length:

the frequency at which this sample is played (i.e. at which the wave amplitude takes the value from the next byte) is determined by the pitch byte.

pitch

the pitch byte makes the sample start playing whenever we write to it, similar to how the sprite byte performs the drawing of the sprite when we write to it.

the first 7 bits (from right to left) of the byte correspond to a midi note, and therefore to the frequency at which the sample will be played.

the eighth bit is a flag: when it's 0 the sample will be looped, and when it's 1 the sample will be played only once.

normally we will want to loop the sample in order to generate a tone based on it. only when the sample is long enough it will make sense to not loop it and play it once.

regarding the bits for the midi note, it's a good idea to have a midi table around to see the hexadecimal values corresponding to different notes.

midi table

middle C (C4, or 3c in midi) is assumed to be the default pitch of the samples.

a "sample" program

in theory, it would appear that the following program should play our sample at that frequency, or not?

not really!

but almost there! in order to actually hear the sound, we need two more things: to set the volume of the device, and to set the ADSR envelope!

volume

the volume byte is divided in two nibbles: the high nibble corresponds to the volume of the left channel, and the low nibble corresponds to the volume of the right channel.

therefore, each channel has 16 possible levels: 0 is the minimum, and f the maximum.

the following would set the maximum volume in the device:

although the samples are mono, we can pan them with the volume byte in order to get stereo sound!

ADSR envelope

the last component we need in order to play audio is the ADSR envelope.

ADSR stands for attack, decay, sustain, and release. it is the name of a common "envelope" that modulates the amplitude of a sound from beginning to end.

in the varvara computer, the ADSR components work as follows:

each of these transitions are done linearly.

in the ADSR short of the audio device, there is one nibble for each of the components: therefore each one can have a duration from 0 to f.

the units for these durations are 15ths of a second.

as an example, if the duration of the attack component is 'f', then it will last one second (15/15 of a second, in decimal).

the following will set the maximum duration on each of the components, making the sound last 4 seconds in total:

ok, now we are ready to play the sound!

playing the sample

the following program has now the five components we need in order to play a sound: a sample address, its length, the adsr durations, the volume, and its pitch!

note (!) that it will play the sound only once, and it does it when the program starts.

some suggested experiments

i invite you to experiment modifying the ADSR values: how does the sound change when there's only one of them? or when all of them are small numbers? or with different combinations of durations?

also, try changing the pitch byte: does it correspond to your ears with the midi values as expected?

and how does the sound changes when you use a different sample? can you find or create different ones?

playing more than once

once we have set up our audio device with a sample, length, ADSR envelope and volume, we could play it again and again by (re)writing a pitch at a different moment; the other parameters can be left untouched.

for example, a macro like the following could allow us to play a note again according to the pitch given at the top of the stack:

when a specific event happened, you could call it:

keep in mind that every time you write a pitch, the playback of the sample and the shape of the envelope starts over regardless of where it was.

some ideas

what if you implement playing different pitches by pressing different keys on the keyboard? you could use our previous examples, but writing a pitch to the device instead of e.g. incrementing a coordinate :)

or what about complementing our pong program from uxn tutorial day 6 with sound effects, having the device playing a note whenever there's a bounce of the ball?

uxn tutorial day 6

or what if you use the screen vector to time the repetitive playing of a note? or what about you have it play a melody by following a sequence of notes? could this sequence come from a text file? :)

playback information

the audio device provides us with two ways of checking during runtime the state of the playback:

when we read the position short, we get the current position of the "playhead" in the sample, starting from 0 (i.e. the playhead is at the beginning of the sample) and ending at the sample length minus one.

the output byte allows us to read the amplitude of the envelope. it returns 0 when the sample is not playing, so it can be used as a way of knowing that the playback has ended.

polyphony

the idea of having four audio devices is that we can have all of them playing at once, and each one can have a different sample, ADSR envelope, volume, and pitch.

this gives us many more possibilities:

maybe in a game there could be a melody playing in the background along with incidental sounds related to the gameplay?

maybe you can build a sequencer where you can control the four devices as different tracks?

or maybe you create a livecoding platform to have a dialog with each of the four instruments?

in any case, don't hesitate to share what you create! :)

the end

hey! believe it or not, this is the end!

you made it to the end of the tutorial series! congratulations!

i hope you enjoyed it and i hope you see it as just the start of your uxn journey!

we'd love to see what you create! don't hesitate to share it in mastodon, the forum, or even via e-mail!

#uxn in merveilles

uxn in lines forum

contact

but before doing all this, don't forget to take a break! :)

see you around!

support

if you enjoyed this tutorial and found it helpful, consider sharing it and giving it your support :)

support

incoming links

uxn tutorial day 6

log

uxn tutorial

meta

compudanzas

contact

colophon

this work is dedicated to the public domain. CC0 1.0