revision day 7

This commit is contained in:
sejo 2022-01-07 20:17:13 -06:00
parent 073dd3057c
commit c66b28f92a
1 changed files with 61 additions and 29 deletions

View File

@ -13,28 +13,28 @@ the file device in the varvara computer allows us to read and write to external
its ports are normally defined as follows:
```
|a0 @File [ &vector $2 &success $2 &stat $2 &delete $1 &append $1 &name $2 &length $2 &load $2 &save $2 ]
|a0 @File [ &vector $2 &success $2 &stat $2 &delete $1 &append $1 &name $2 &length $2 &read $2 &write $2 ]
```
* the vector short is currently unused
* the success short stores the length of the data that was successfully read or written, or zero if there was an error
* the name short is for the memory address where the filename (null-terminated, i.e. with a 00) is stored
* the length short is the amount of bytes to read or write: don't forget that the program memory is ffff plus 1 bytes long, and that the program itself is stored there!
* the load short is for the starting memory address where the read data should be stored
* the save short is for the starting memory address where the data to be written is stored
* the read short is for the starting memory address where the read data should be stored
* the write short is for the starting memory address where the data to be written is stored
* the stat short is similar to load, but reads the directory entry for the filename
* the delete byte deletes the file when any value is written to it
* setting the append byte to 01 makes save append data to the end of the file, when it is the default, 00, save overwrites the contents from the start
* setting the append byte to 01 makes write append data to the end of the file. when the append byte has the the default value, 00, write overwrites the contents from the start
a read operation is started when the load short is written to, and a write operation is started when the save short is written to.
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, but we'll see that they are not too much of a problem!
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
in order to read a file, we need to know the following:
* the path of the file, written as a string of text in program memory: terminated by a 00, and with an appropriate label - this path would be relative to the location where uxnemu is ran.
* the path of the file, written as a labelled string of text in program memory and terminated by a 00 - this path would be relative to the location where uxnemu is ran.
* the amount of bytes that we want to read from the file: it's okay if this number is not equal to the size of the file; it can be smaller or even greater.
* the label for a reserved section of program memory where read data will be stored
@ -48,7 +48,7 @@ we can use a structure like the following, where the filename and reserved memor
#00ff .File/length DEO2 ( will attempt to read 255 bytes )
( set address for the data to read, and do read )
;file/data .File/load DEO2
;file/data .File/read DEO2
( check the success byte and jump accordingly )
.File/success DEI2 #0000 EQU2 ,&failed JCN
@ -68,7 +68,7 @@ RTN
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.
int 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.
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!
@ -80,7 +80,7 @@ not only we can choose to treat these bytes as text characters, but also we can
in order to write a file, we need:
* the path of the file
* the path of the file, written as a string in program memory as the case above
* the amount of bytes that we want to write into the file
* the label for the section of program memory that we will write into the file
@ -94,7 +94,7 @@ the following program will write "hello" and a newline (0a) into a file called "
#0006 .File/length DEO2 ( will attempt to write 6 bytes )
( set data starting address, and do write )
;file/data .File/save DEO2
;file/data .File/write DEO2
( read and evaluate success byte )
.File/success DEI2 #0006 NEQ2 ,&failed JCN
@ -114,13 +114,15 @@ RTN
note how similar it is to the load-file subroutine!
the only differences, beside the use of File/save instead of File/load, 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.
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.
this file has the name ".theme" and can be written to a local directory from nasu, using the ctrl+p shortcut.
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.
=> https://wiki.xxiivv.com/site/theme.html uxn themes
@ -155,6 +157,8 @@ RTN
&r $2 &g $2 &b $2
```
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:
@ -211,13 +215,17 @@ based on this, it should be straightforward for you to use them! e.g. in order t
.DateTime/hour DEI
```
## 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? :)
remember that for timing events with a little bit more precison, you can count the times that the screen vector has been fired!
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
@ -236,13 +244,13 @@ similar to how in the screen device we can draw by pointing to addresses with sp
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 briefly discuss these concepts in order to use them, assuming that you might not be familiar with them.
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 specific length, and we can refer to them by labels.
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:
@ -346,7 +354,9 @@ in the context of varvara, we can understand them as multiple unsigned bytes (u8
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 them we get a tone based on that shape!
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:
=> ./img/screenshot_uxn-waveform_piano.png piano sample waveform
@ -363,7 +373,7 @@ tri-pcm:
saw-pcm:
=> ./img/screenshot_uxn-waveform_saw.png saw sample waveform
similar to how we have dealt with sprites and with the file device above, in order to set a sample in the audio device, we just have to write its address and its length:
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:
```
;saw-pcm .Audio0/addr DEO2 ( set sample address )
@ -376,11 +386,13 @@ the frequency at which this sample is played (i.e. at which the wave amplitude t
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), and 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.
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 midi note bits, it's a good idea to have a midi table around to see the hexadecimal values corresponding to different notes.
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.
=> https://wiki.xxiivv.com/site/midi.html midi table
@ -388,7 +400,7 @@ middle C (C4, or 3c in midi) is assumed to be the default pitch of the samples.
### a "sample" program
in theory, the following program should play our sample at that frequency, or not?
in theory, it would appear that the following program should play our sample at that frequency, or not?
```
( hello-sound.tal )
@ -442,15 +454,15 @@ in the ADSR short of the audio device, there is one nibble for each of the compo
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 second, in decimal).
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:
the following will set the maximum duration on each of the components, making the sound last 4 seconds in total:
```
#ffff .Audio0/adsr
```
ok, so now we are ready to play the sound!
ok, now we are ready to play the sound!
## playing the sample
@ -475,9 +487,11 @@ BRK
note (!) that it will play the sound only once, and it does it when the program starts.
i invite you to experiment with 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?
### some suggested experiments
also, try changing the pitch byte: does it correspond with the midi values as expected?
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?
@ -485,7 +499,19 @@ and how does the sound changes when you use a different sample? can you find or
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.
keep in mind that every time you write a pitch, the playback of the sample and the shape of the envelope starts over.
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:
```
%PLAY-NOTE { .Audio0/pitch DEO } ( pitch -- )
```
when a specific event happened, you could call it:
```
#3c PLAY-NOTE ( play middle C )
```
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
@ -508,9 +534,15 @@ the output byte allows us to read the amplitude of the envelope. it returns 0 wh
## polyphony
the idea of having four audio devices is that we can have all of them play at once, and each one can have a different sample, ADSR envelope, volume, and pitch.
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?
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! :)