The NES audio is so simple (only a couple simple channels, no fancy filters or anything), that a couple cool features could be included, like stereo output(the NES sound chip actually is capable of stereo, because for some reason some channels are on a separate output pin. I think both the squre waves are on one, triangle, noise and those samples noone ever uses are on the other. Although it sounds better if they are slightly mixed together. blablabla nerdgasm... anyways, it sounds really cool (the semi-stereo starts at: 8:36 or so) or even a built-in playroutine (for a commented, lightweight one for NES that's somewhat compatible with existing trackers look here: https://shiru.untergrund.net/code.shtml) Oh, and changing the waveform for the triangle channel Gameboy/Konami SSC style would be kindof cool,
That's some info there... I have very specific interests...
@Ahle2
Thanks for the source code. I see that most of the things I have discovered are correct, and few things changed compared to the source I have (the one with the Nyancat music, unfortunately it is now incompatible and not only because the updated source uses 16 bit pointers).
Still some things are not very clear to me. For example the meaning of the ASD bits, I know the concept of Attack/Decay/Sustain but I can't understand how it is applied in that case. The ASD value seems to be just added to the volume, I guess that a positive value means attack (volume is increased to maximum) and a negative means decay (decreased to sustain value) but experimenting with different values don't produce the expected results, and the usage in the sample music doesn't make much sense to me. I guess the code does something else with it.
The same for the modulation bits, so I'm looking forward for any additional documentation.
Hmm, about the "all channels on the left" thing, might this be the version for mono-boards?
All voices are sent to both left and right channels. The change to send a voice to a specific channel looks simple, see the block starting at line 219 (waveform shaping), all voices are added to outL variable, then outL is set to both FRQA and FRQB at line 266. There is already a outR variable so simple change outL to outR for the voices you want on the right channel and set FRQB to outR, also change the initialization few lines below to assign val31bit to outR.
The NES actually had five channels. Two square, one triangle, one noise and one PCM for samples. Japanese versions of some games (Famicom) added extra sound channels in the cartridges. So the sky's the limit there.
However, NTSC versions of NES could not physically use the extra channels because there was no way to route the extra audio out through the cartridge to the audio out. Which was sad once you hear the difference between the Japanese and American versions of Castlevania.
Also, the PCM channel was used in games like SMB3...mainly the steel drum sound.
But I agree that the NES audio system was a far cry from the SID. Although, it DID produce some great music. A tribute to the talented musicians that knew how to make it sing.
Technically, it isn't even as advanced as the AY but it makes up for this by having more channels.
@Ahle2
Thanks for the source code. I see that most of the things I have discovered are correct, and few things changed compared to the source I have (the one with the Nyancat music, unfortunately it is now incompatible and not only because the updated source uses 16 bit pointers).
Still some things are not very clear to me. For example the meaning of the ASD bits, I know the concept of Attack/Decay/Sustain but I can't understand how it is applied in that case. The ASD value seems to be just added to the volume, I guess that a positive value means attack (volume is increased to maximum) and a negative means decay (decreased to sustain value) but experimenting with different values don't produce the expected results, and the usage in the sample music doesn't make much sense to me. I guess the code does something else with it.
The same for the modulation bits, so I'm looking forward for any additional documentation.
Hmm, about the "all channels on the left" thing, might this be the version for mono-boards?
All voices are sent to both left and right channels. The change to send a voice to a specific channel looks simple, see the block starting at line 219 (waveform shaping), all voices are added to outL variable, then outL is set to both FRQA and FRQB at line 266. There is already a outR variable so simple change outL to outR for the voices you want on the right channel and set FRQB to outR, also change the initialization few lines below to assign val31bit to outR.
Macca, You are correct on almost everything!
But, the 9 LSBs of the modulation value can't be set if you want modulation. They are used to set a fixed value. If any of the 9 LSBs are set, the modulation register will be "9 LSBs << 23"
This way you could for an instance set pulse width to 12.5% with a value of "$200" and then starting modulating from that point with any value that have the 9 LSBs cleared like for an example "$123456000".
Retronitus uses this scheme and a lot of other "tricks" in order to eat as little CPU cycles per sample as possible so that the sample rate would be "good enough". (according to my standards)
test c1_mod, #511 wz ' Test if any of the 9 LSBs are set
if_nz movi c1_modulation, c1_mod ' If true, set modulation value to: "the 9 LSBs" << 23
if_z add c1_modulation, c1_mod ' Else, Modulate
There are other quirks as well. But I will come back with more information as soon as I find time.
Retronitus is basically an 1980´s programmable sound generator (AY38910, MOS6581 etc) combined with a music replay routine and 8 instruction units/IU (one per sound channel) in a single package. The way sound generation worked back then on old school machines involved the CPU, a VBL interrupt routine and some more or less complex music and sound software routines changing the PSG registers many times per second. Some PSG's had envelopes and other fancy things to make more in hardware, but still even the MOS6581 needs to be banged with data at high rates to produce interesting results. Today you could just let a PC/console play a sample of whatever sound you want and wait until it's finished. Using Retronitus is much more like playing samples, just point to the SFX in memory and let the sound just play; No need to make your own software routines or handle interrupts or anything complicated, it's just data (like a sample). The same goes for music, as that is built in as well. You can reserve channels to be dedicated for SFX by just not making your music using those channels. And even if you are using all channels for music, you could still trigger SFX in any channel; With the restriction that the note playing will be cut off and then when a new note is triggered by the music routine your SFX will get cut off.
How does Retronitus handle SFX/instruments you may ask? Basically a SFX is a very simple program that only operates on one channels internal registers (Retronitus doesn't expose its registers at all). There are just 4 instructions (SET, MOD, JMP, REP) and 4 registers (frequency, amplitude change, max amplitude, waveform modulation) that those instructions can modify. That's all! And that's all you have to have to basically do everything from the simplest square beep to almost any SFX heard on NES, C64, Amstrad CPC. Retronitus has got 8 sound channels divided into groups of 3 square, 3 saw, 1 triangle and 1 noise channel. Any SFX can be played in any of the 8 channels, even though each channel are hard coded to only generate one kind of waveform. This can result in that some sound effects will sound useless or even silent if played in the wrong type of channel. This is not just an disadvantage though. Let me explain! You could get different sounding/useful sound effects using the same set of data and that's great when dealing with the limited amount of memory found in the Propeller. The 3 square channels and the 3 saw channels can be used to play the same SFX and the result will not be too different since they both produce a tonal waveform and the modulators works similarly. The noise channel and the triangle channel, however, works differently as explained later. But you are, as stated before, free to play any SFX made for the square/saw channels in the noise or triangle or vice versa, it may/may not be useful sounding though! Note that all sound channels, regardless of type, has got the same 4 registers but the frequency and modulation register works a little bit different on the noise and triangle channels. More on this further down.
Instructions
SET: Sets a register to a value
MOD: Modifies a register using the formula "new value = old value + modify value"
JMP: Relative jump to some place in memory
REP: Repeats the instruction above X number of times (can be used for pauses/delays and smooth register changes)
(Note that the instruction rate of the 8 IU's are 7800 instructions per second)
Registers
frequency : Sets frequency using the formula "value = ((2^32) / 78000) * frequencyInHz"
max amplitude : Sets the amplitude max level using the formula "value = ((2^32) / 100) * percentageOfMaxAmplitude"
amplitude change : Sets how fast the amplitude should increase/decrease using the formula "value = ((2^32) / 78000) / secondsFromMaxToMin"
The 9 LSB's are used for amplitude modulation and will be described in more details some time.
For now, just set the 9 LSB's to 000000001 and ignore this feature
waveform modulation : Set the modulation rate using the formula "value = ((2^32) / 78000) * frequencyInHz"
The 9 LSB's are used to set a fixed duty value (0 - 511) and will disable modulation if not cleared.
This way you could start modulation from a known duty value by first setting the 9 LSB's and then setting the other bits.
Of course you could also just set duty to something and not use the modulation feature at all.
Waveforms
Square (cha 1-3) : Just a simple square wave with pulse width modulation
Saw (cha 4-6) : Just a simple saw wave with some kind of modulation yet unnamed (look at the scope, this is some funky stuff)
Triangle (cha 7) : Just a simple triangle wave that gets its bits masked by the modulation value (by masking only the 4 MSB's you get NES triangle)
Noise (cha 8) : Sample and hold noise that get its randomness from arithmetic feedback using the modulation value
Example SFX 1: 500 Hz square beep with fixed amplitude and fixed 50:50 duty (pseudo assembler code)
' Set the 4 registers to a known state to produce the wanted sound
SET SUSTAIN_REG , $F000_0000 ' Sets the sustain amplitude level to maximum
SET AMPLITUDE_CHANGE_REG , $0112_0100 ' Sets the amplitude change register to increase the amplitude "very fast" to sustain level
SET WAVEFORM_MODULATION_REG, $0000_0100 ' Sets the pulse width to a fixed 50:50 duty and no modulation
SET FREQUENCY_REG , $01A4_1A41 ' Sets the frequency to 500 hz
' Loop forever
LOOP:
JMP #LOOP
Example SFX 2: 100 Hz square beep with fixed amplitude and 1 Hz pulse width modulation
' Set the 4 registers to a known state to produce the wanted sound
SET SUSTAIN_REG , $F000_0000 ' Sets the sustain amplitude level to maximum
SET AMPLITUDE_CHANGE_REG , $0112_0100 ' Sets the amplitude change register to increase the amplitude "very fast" to sustain level
SET WAVEFORM_MODULATION_REG, $0000_D700 ' Sets pulse width modulation to 1 Hz (9 LSB's cleared to disable fixed duty)
SET FREQUENCY_REG , $0054_0540 ' Sets the frequency to 100 hz
' Loop forever
LOOP:
JMP #LOOP
Here is an example of using Retronitus to emulate NES sounds. (A gift from me to cbmeeks ) Since there was some discussion about it here.
Look at the SFX spin object to see the actual Retronitus SFX code in all its glory. I think it's almost readable the way I used constants for everything. Almost like ASM.
Note 1: The triangle SFX uses very fast attack and decay to simulate "instant" on/off like the triangle on NES.
Note 2: The NES can only do the 3 fixed pulse widths in my example
Note 3: By using the three saw channels and extra square channel in Retronitus , you could almost have the full VRC6 experience!
I played a bit with the instruments/effects commands and I must say that it is very difficult to obtain the desired results even with the documentation and the examples above, so I started to write a program aimed at simplifying the process.
The program allows to enter the register values by their meaning (frequency in Hz, duration in milliseconds, amplitude levels in percentage, etc.), do the calculations and produces a chart with the visual representation of the effects on the output. For example, frequency can be entered either as Hz or as the note text representation C-4, F#3, etc. The buttons on the right side allows to move the command up and down, delete and add entries. The Get Spin Data button allows to see the actual raw values ready to be copy/pasted into a spin source.
It is a Java program, unpack the archive and run the jar file:
java -jar retronitus-editor.jar
On Windows you should be able to double-click on the jar to start it.
This is just an initial attempt, it doesn't allow to save or load existing effects and there aren't much checks. I would like to get your opinions if this could be the right approach and suggestions on how to make it better.
This is the way Retronitus handles music...
First you need to understand some basic things, and then we can move on to the music format itself. First of all a Retronitus instrument is exactly the same as a sound effect, the only difference is that for tonal instruments you CAN NOT set the frequency because that is handled internally in the music routine. Percussion instruments can set the frequency though. Notes are in a 5 bit delta format and not in an absolute format as most routines uses. In each "note byte" there is also a 3 bit "steps to next note". The reason for this is that the music data will be very compact and more music can be stuffed in the relatively small amount of memory found in the Propeller. Another way Retronitus saves memory is the way patterns works. More on that further down.
Have a look in the file "music.spin". As you can see, the music format has got 4 types of data sections, namely "Music header", "Pattern sequency data", "Pattern data " and "Instrument data". I will describe them one at a time.
Music header
The first word is always zero, it will be set to !zero by the init music routine to indicate that it is already initialized.
Then comes a null-terminated word sized array of pointers to instruments used by the music.
Lastly comes a word sized array of pointers to where the music data is in in memory for every channel. If a channel is not in use, it will contain a zero. Note that this array is ALWAYS 8 words.
As you can see there are some pointer arithmetic going on all over the place in "music.spin". This calculates the addresses relative to the beginning of the music data rather than the absolute address. The reason for this is that the music can be reallocated anywhere in the memory. This simplifies loading music from SD card or over RS232 to anywhere in memory. The init routine will then go through the data and calculate the absolute address before handing it over to the music routine.
Pattern sequency data
Each channel in a particular piece of music has got its own "pattern sequency data" that is pointed to in the music header. This is an array structure that holds pointers to the actual note data (delta note patterns), instrument number, the octave and note to start from. It can also hold tempo information. This way you could play the exact same sequence of notes with different instruments, different tempo, different transpose etc by just pointing to the same note data but with a different setting in the pattern sequence data.
Two values, "END" and "RESTART", has special meaning in the sequence. END means that the music data simply ends here. RESTART means that the tune should loop from beginning. It's enough if just one channel has got the RESTART value, all the other restarts as well.
Pattern data
Each note resides in one byte. Notes values are calculated relative to the what is set as the "base note" in the sequency data. Each note in the data set is dependant on the delta values before it. Think of it like a coordinate system where X is the time and Y is the note. Every byte describes how many "note steps" and "time steps" that the next note is on compared to the notes before it. A null byte is interpreted as "end of pattern data". Delta values above 25 have special meaning. They will not be interpreted as delta notes, instead they trigger the selected instrument with an offset. This way you can add effects like vibrato, tremolo, echo, volume changes and whatever; By having a jump table in offset 0 to 5! Percussions are also handled this way, by having each instance in the jump table jump to a comletely new percussion instrument.
Just have a look below and I think you will understand how it works.
--------------------------------
Bit 7 - 3 | Bit 2 - 0
---------------------------------
5 bit delta note| 3 bit step wait
---------------------------------
5 bit delta note value interpretation
Value | Function
------------------------------------------
0 - 25 | Trigger delta note -12 to +12
26 | Do nothing
27 | Trigger instrument address + 1
28 | Trigger instrument address + 2
29 | Trigger instrument address + 3
30 | Trigger instrument address + 4
31 | Trigger instrument address + 5
3 bit step wait value interpretation
Value | Function
------------------------------------------
0 - 7 | Step wait 1 to 8
Thank you Johannes for the music data format, now the hard part is to write the editor... :-)
One question about the BPM value:
BPM = 9_691_208 ' (2^32 / 78_000 / 11) * 16
Are you sure that the formula is correct ? Because unless I'm doing a big mistake it should be 80080 or 80092 depending on the roundings. Indeed it works.
In the meantime, I have updated the editor, now it can save and reload the instrument settings to an xml file and allows to view and edit the actual register values.
Sources are available as a separate download.
For all you lazy guys that thinks it's such a cumbersome task to actually connect a resistor, a capacitor and some some wires to your Propeller and try out my latest example for yourself!
@Ahle2
Your work is absolutely amazing
now the only major task seems to make easier to develop music on it, perhaps a MIDI converter of some sort would do the trick?
Are you sure that the formula is correct ? Because unless I'm doing a big mistake it should be 80080 or 80092 depending on the roundings. Indeed it works.
What happens when SFX is played on a channel where music is playing? Does the music get muted while the effect is playing, or does the music override the sound effect when it's trying to play a note?
I can't test, my propeller stuff is still in the planning phase.
Regardless, it would be useful to have 2 noise channels. From briefly looking over the code, changing waveforms should be relativly simple, except that the sample rate might need some lowering, as the noise code is a couple cycles longer than the code for the other channels... but even that seems easy. Also there is a unique noiseValue variable, but there are 37 free longs, so it could should fit.
Anyways, this makes me want to get me my pcb made... first have to find an sd card slot that you can actually buy (in single digit quantities) AND also find a fitting eagle part for. These two properties seem to be mutually exclusive. :crazy:
@Ahle2
Your work is absolutely amazing
now the only major task seems to make easier to develop music on it, perhaps a MIDI converter of some sort would do the trick?
Thanks!
It should be "quite" easy to do a mod or midi to Retronitus converter. I did a basic midi to Retronitus converter as a proof o concept. It needs the user to set some additional stuff like, "how the midi instruments get translated to Retronitus instruments", "how to multiplex midi data into just 8 channels", "What's the base length of a pattern", "How to optimize the music footprint using pattern and pattern sequencies optimally", "How to handle delta >+12 or <-12 "... etc. All of this is "easy" when you are doing all the data input manually, but can be tricky when converting.
Too bad my editor went into the garbage; I rather do a complete rewrite than trying to resolve the issues I had with it.
What happens when SFX is played on a channel where music is playing? Does the music get muted while the effect is playing, or does the music override the sound effect when it's trying to play a note?
I can't test, my propeller stuff is still in the planning phase.
Regardless, it would be useful to have 2 noise channels. From briefly looking over the code, changing waveforms should be relativly simple, except that the sample rate might need some lowering, as the noise code is a couple cycles longer than the code for the other channels... but even that seems easy. Also there is a unique noiseValue variable, but there are 37 free longs, so it could should fit.
Anyways, this makes me want to get me my pcb made... first have to find an sd card slot that you can actually buy (in single digit quantities) AND also find a fitting eagle part for. These two properties seem to be mutually exclusive. :crazy:
The last one that triggers a SFX/Instrument (.playSFX() or the music routine) gets played and mutes any ongoing sound.
Two noise channels may not be needed depending of what kind of noise you would like to have. All channels can play noise using "modify" on the frequency value. That's how the triangle channel play snare drums in my latest example.
Of course you are right about all your assumptions regarding cycles and that it would be easy to exchange one channel for a second noise.
Do you sometimes get the feeling of wanting to be screeched at with ridiculous volume? NO?!?! Well, too bad!
I conjured up the worst demons to forge a virus that torments the unknowing computer user with it's hellish noises and disguised it as a Retronitus emulator! MUAHAHAHAHAHA!
Serioulsy, don't wear headphones, please. Source is inside the jar. Start from terminal, your ears will thank you later.
The Retronitus source had to be mangled with, as otherwise it would hang in "PRI initMusic" (cog 0 would eventually stop). Also it currently runs at 1/10th of the real samplerate. tl;dr; it sucks.
To switch on singlestepping, go to Popeller.java, line 22 and change false to true. (and compile, of course)
To switch to the original Retronitus code (without haxx), go to Top.java, line 9 and change "test3.eeprom" to "source.eeprom"
I will try to make it actually work.
EDIT: Ooops, exporting to jar broke it, hang tight...
Comments
Works here.
Did you try:
http://www.fnarfbargle.com/bst/snapshots/
Reuploaded version working great many thanks
Kamil
Go bananas!
Your audio chops on the Propeller are second to none. Great work!
Have you ever thought about writing an "official" NESCog? Probably take you 10 minutes. :-D
Maybe 15 minutes if you implement the extra sound channels the Famicom could support. hehehe
The NES audio is so simple (only a couple simple channels, no fancy filters or anything), that a couple cool features could be included, like stereo output(the NES sound chip actually is capable of stereo, because for some reason some channels are on a separate output pin. I think both the squre waves are on one, triangle, noise and those samples noone ever uses are on the other. Although it sounds better if they are slightly mixed together. blablabla nerdgasm... anyways, it sounds really cool (the semi-stereo starts at: 8:36 or so) or even a built-in playroutine (for a commented, lightweight one for NES that's somewhat compatible with existing trackers look here: https://shiru.untergrund.net/code.shtml) Oh, and changing the waveform for the triangle channel Gameboy/Konami SSC style would be kindof cool,
That's some info there... I have very specific interests...
Thanks for the source code. I see that most of the things I have discovered are correct, and few things changed compared to the source I have (the one with the Nyancat music, unfortunately it is now incompatible and not only because the updated source uses 16 bit pointers).
Still some things are not very clear to me. For example the meaning of the ASD bits, I know the concept of Attack/Decay/Sustain but I can't understand how it is applied in that case. The ASD value seems to be just added to the volume, I guess that a positive value means attack (volume is increased to maximum) and a negative means decay (decreased to sustain value) but experimenting with different values don't produce the expected results, and the usage in the sample music doesn't make much sense to me. I guess the code does something else with it.
The same for the modulation bits, so I'm looking forward for any additional documentation.
All voices are sent to both left and right channels. The change to send a voice to a specific channel looks simple, see the block starting at line 219 (waveform shaping), all voices are added to outL variable, then outL is set to both FRQA and FRQB at line 266. There is already a outR variable so simple change outL to outR for the voices you want on the right channel and set FRQB to outR, also change the initialization few lines below to assign val31bit to outR.
The NES actually had five channels. Two square, one triangle, one noise and one PCM for samples. Japanese versions of some games (Famicom) added extra sound channels in the cartridges. So the sky's the limit there.
However, NTSC versions of NES could not physically use the extra channels because there was no way to route the extra audio out through the cartridge to the audio out. Which was sad once you hear the difference between the Japanese and American versions of Castlevania.
Also, the PCM channel was used in games like SMB3...mainly the steel drum sound.
But I agree that the NES audio system was a far cry from the SID. Although, it DID produce some great music. A tribute to the talented musicians that knew how to make it sing.
Technically, it isn't even as advanced as the AY but it makes up for this by having more channels.
I think I found some evidence for the "all channels on the left" thing. It appears that CTRA is used for the "left" pin and CTRB for the "right" pin.
But look at that: The left channel is sent to both pins! Blasphemy! Infact, outR is never ever mentioned again! That's definitly not
Although, looking at the way this is written, it is likely that stereo support was quickly "hacked-out"...
Macca, You are correct on almost everything!
But, the 9 LSBs of the modulation value can't be set if you want modulation. They are used to set a fixed value. If any of the 9 LSBs are set, the modulation register will be "9 LSBs << 23"
This way you could for an instance set pulse width to 12.5% with a value of "$200" and then starting modulating from that point with any value that have the 9 LSBs cleared like for an example "$123456000".
Retronitus uses this scheme and a lot of other "tricks" in order to eat as little CPU cycles per sample as possible so that the sample rate would be "good enough". (according to my standards)
There are other quirks as well. But I will come back with more information as soon as I find time.
/Johannes
Retronitus is basically an 1980´s programmable sound generator (AY38910, MOS6581 etc) combined with a music replay routine and 8 instruction units/IU (one per sound channel) in a single package. The way sound generation worked back then on old school machines involved the CPU, a VBL interrupt routine and some more or less complex music and sound software routines changing the PSG registers many times per second. Some PSG's had envelopes and other fancy things to make more in hardware, but still even the MOS6581 needs to be banged with data at high rates to produce interesting results. Today you could just let a PC/console play a sample of whatever sound you want and wait until it's finished. Using Retronitus is much more like playing samples, just point to the SFX in memory and let the sound just play; No need to make your own software routines or handle interrupts or anything complicated, it's just data (like a sample). The same goes for music, as that is built in as well. You can reserve channels to be dedicated for SFX by just not making your music using those channels. And even if you are using all channels for music, you could still trigger SFX in any channel; With the restriction that the note playing will be cut off and then when a new note is triggered by the music routine your SFX will get cut off.
How does Retronitus handle SFX/instruments you may ask? Basically a SFX is a very simple program that only operates on one channels internal registers (Retronitus doesn't expose its registers at all). There are just 4 instructions (SET, MOD, JMP, REP) and 4 registers (frequency, amplitude change, max amplitude, waveform modulation) that those instructions can modify. That's all! And that's all you have to have to basically do everything from the simplest square beep to almost any SFX heard on NES, C64, Amstrad CPC. Retronitus has got 8 sound channels divided into groups of 3 square, 3 saw, 1 triangle and 1 noise channel. Any SFX can be played in any of the 8 channels, even though each channel are hard coded to only generate one kind of waveform. This can result in that some sound effects will sound useless or even silent if played in the wrong type of channel. This is not just an disadvantage though. Let me explain! You could get different sounding/useful sound effects using the same set of data and that's great when dealing with the limited amount of memory found in the Propeller. The 3 square channels and the 3 saw channels can be used to play the same SFX and the result will not be too different since they both produce a tonal waveform and the modulators works similarly. The noise channel and the triangle channel, however, works differently as explained later. But you are, as stated before, free to play any SFX made for the square/saw channels in the noise or triangle or vice versa, it may/may not be useful sounding though! Note that all sound channels, regardless of type, has got the same 4 registers but the frequency and modulation register works a little bit different on the noise and triangle channels. More on this further down.
Instructions
Registers
Waveforms
Example SFX 1: 500 Hz square beep with fixed amplitude and fixed 50:50 duty (pseudo assembler code)
Example SFX 2: 100 Hz square beep with fixed amplitude and 1 Hz pulse width modulation
More will follow on SFX (and of course music)
/Johannes
Here is an example of using Retronitus to emulate NES sounds. (A gift from me to cbmeeks ) Since there was some discussion about it here.
Look at the SFX spin object to see the actual Retronitus SFX code in all its glory. I think it's almost readable the way I used constants for everything. Almost like ASM.
Note 1: The triangle SFX uses very fast attack and decay to simulate "instant" on/off like the triangle on NES.
Note 2: The NES can only do the 3 fixed pulse widths in my example
Note 3: By using the three saw channels and extra square channel in Retronitus , you could almost have the full VRC6 experience!
/Johannes
The program allows to enter the register values by their meaning (frequency in Hz, duration in milliseconds, amplitude levels in percentage, etc.), do the calculations and produces a chart with the visual representation of the effects on the output. For example, frequency can be entered either as Hz or as the note text representation C-4, F#3, etc. The buttons on the right side allows to move the command up and down, delete and add entries. The Get Spin Data button allows to see the actual raw values ready to be copy/pasted into a spin source.
The program can be downloaded from here:
https://dev.maccasoft.com/propgame/downloads/retronitus-editor-linux64-160921-1020.tar.gz (Linux x86/64)
https://dev.maccasoft.com/propgame/downloads/retronitus-editor-windows-160921-1020.zip (Windows x86)
https://dev.maccasoft.com/propgame/downloads/retronitus-editor-src-160921-1020.zip (Sources)
It is a Java program, unpack the archive and run the jar file:
On Windows you should be able to double-click on the jar to start it.
This is just an initial attempt, it doesn't allow to save or load existing effects and there aren't much checks. I would like to get your opinions if this could be the right approach and suggestions on how to make it better.
Hope you enjoy!
Great first step on a SFX/Instrument editor. It looks really nice!
I will try it out soon and maybe do some hacking on it as well, if you don't mind?!
Btw, I will soon upload a new Retronitus tune and explain how the music format works!
/Johannnes
No problems, however I haven't included the source code in this release, I'll add in the next.
Here comes a new music example for Retronitus!
This is the way Retronitus handles music...
First you need to understand some basic things, and then we can move on to the music format itself. First of all a Retronitus instrument is exactly the same as a sound effect, the only difference is that for tonal instruments you CAN NOT set the frequency because that is handled internally in the music routine. Percussion instruments can set the frequency though. Notes are in a 5 bit delta format and not in an absolute format as most routines uses. In each "note byte" there is also a 3 bit "steps to next note". The reason for this is that the music data will be very compact and more music can be stuffed in the relatively small amount of memory found in the Propeller. Another way Retronitus saves memory is the way patterns works. More on that further down.
Have a look in the file "music.spin". As you can see, the music format has got 4 types of data sections, namely "Music header", "Pattern sequency data", "Pattern data " and "Instrument data". I will describe them one at a time.
Music header
The first word is always zero, it will be set to !zero by the init music routine to indicate that it is already initialized.
Then comes a null-terminated word sized array of pointers to instruments used by the music.
Lastly comes a word sized array of pointers to where the music data is in in memory for every channel. If a channel is not in use, it will contain a zero. Note that this array is ALWAYS 8 words.
As you can see there are some pointer arithmetic going on all over the place in "music.spin". This calculates the addresses relative to the beginning of the music data rather than the absolute address. The reason for this is that the music can be reallocated anywhere in the memory. This simplifies loading music from SD card or over RS232 to anywhere in memory. The init routine will then go through the data and calculate the absolute address before handing it over to the music routine.
Pattern sequency data
Each channel in a particular piece of music has got its own "pattern sequency data" that is pointed to in the music header. This is an array structure that holds pointers to the actual note data (delta note patterns), instrument number, the octave and note to start from. It can also hold tempo information. This way you could play the exact same sequence of notes with different instruments, different tempo, different transpose etc by just pointing to the same note data but with a different setting in the pattern sequence data.
Two values, "END" and "RESTART", has special meaning in the sequence. END means that the music data simply ends here. RESTART means that the tune should loop from beginning. It's enough if just one channel has got the RESTART value, all the other restarts as well.
Pattern data
Each note resides in one byte. Notes values are calculated relative to the what is set as the "base note" in the sequency data. Each note in the data set is dependant on the delta values before it. Think of it like a coordinate system where X is the time and Y is the note. Every byte describes how many "note steps" and "time steps" that the next note is on compared to the notes before it. A null byte is interpreted as "end of pattern data". Delta values above 25 have special meaning. They will not be interpreted as delta notes, instead they trigger the selected instrument with an offset. This way you can add effects like vibrato, tremolo, echo, volume changes and whatever; By having a jump table in offset 0 to 5! Percussions are also handled this way, by having each instance in the jump table jump to a comletely new percussion instrument.
Just have a look below and I think you will understand how it works. Instrument data
Exactly the same as SFX!
/Johannes
One question about the BPM value:
Are you sure that the formula is correct ? Because unless I'm doing a big mistake it should be 80080 or 80092 depending on the roundings. Indeed it works.
In the meantime, I have updated the editor, now it can save and reload the instrument settings to an xml file and allows to view and edit the actual register values.
Sources are available as a separate download.
The program can be downloaded from here:
https://dev.maccasoft.com/propgame/downloads/retronitus-editor-linux64-160921-1020.tar.gz (Linux x86/64)
https://dev.maccasoft.com/propgame/downloads/retronitus-editor-windows-160921-1020.zip (Windows x86)
https://dev.maccasoft.com/propgame/downloads/retronitus-editor-src-160921-1020.zip (Sources)
Here comes another example tune to learn from. This time a conversion from a famous Atari ST / Amga game. Can you tell wich one?
Some info about the tune:
Channels: 7 out of the 8
Size: 1.8 kB
Duration: 2:40
/Johannes
Your work is absolutely amazing
now the only major task seems to make easier to develop music on it, perhaps a MIDI converter of some sort would do the trick?
((2^32 / 78_000) / 11) * 16 == 80_092
(2^32 / (78_000 / 11)) * 16 == 9_691_208
The formula is probably just missing some parentheses.
I can't test, my propeller stuff is still in the planning phase.
Regardless, it would be useful to have 2 noise channels. From briefly looking over the code, changing waveforms should be relativly simple, except that the sample rate might need some lowering, as the noise code is a couple cycles longer than the code for the other channels... but even that seems easy. Also there is a unique noiseValue variable, but there are 37 free longs, so it could should fit.
Anyways, this makes me want to get me my pcb made... first have to find an sd card slot that you can actually buy (in single digit quantities) AND also find a fitting eagle part for. These two properties seem to be mutually exclusive. :crazy:
Thanks!
It should be "quite" easy to do a mod or midi to Retronitus converter. I did a basic midi to Retronitus converter as a proof o concept. It needs the user to set some additional stuff like, "how the midi instruments get translated to Retronitus instruments", "how to multiplex midi data into just 8 channels", "What's the base length of a pattern", "How to optimize the music footprint using pattern and pattern sequencies optimally", "How to handle delta >+12 or <-12 "... etc. All of this is "easy" when you are doing all the data input manually, but can be tricky when converting.
Too bad my editor went into the garbage; I rather do a complete rewrite than trying to resolve the issues I had with it.
The last one that triggers a SFX/Instrument (.playSFX() or the music routine) gets played and mutes any ongoing sound.
Two noise channels may not be needed depending of what kind of noise you would like to have. All channels can play noise using "modify" on the frequency value. That's how the triangle channel play snare drums in my latest example.
Of course you are right about all your assumptions regarding cycles and that it would be easy to exchange one channel for a second noise.
Thanks!
I have thought about it many times and I almost started to implement it once. It somehow fell between chairs.
/Johannes
I conjured up the worst demons to forge a virus that torments the unknowing computer user with it's hellish noises and disguised it as a Retronitus emulator! MUAHAHAHAHAHA!
Serioulsy, don't wear headphones, please. Source is inside the jar. Start from terminal, your ears will thank you later.
The Retronitus source had to be mangled with, as otherwise it would hang in "PRI initMusic" (cog 0 would eventually stop). Also it currently runs at 1/10th of the real samplerate. tl;dr; it sucks.
To switch on singlestepping, go to Popeller.java, line 22 and change false to true. (and compile, of course)
To switch to the original Retronitus code (without haxx), go to Top.java, line 9 and change "test3.eeprom" to "source.eeprom"
I will try to make it actually work.
EDIT: Ooops, exporting to jar broke it, hang tight...