Playing patches from RAM
Playing patches from RAM
I started making a realtime sound patch editor. An updated patchtool, really. I wrote pretty much all the code except the part where you push start and the patch plays.
Right now it has:
- Similar interface to patchtool
- Saving to EEPROM
- Loading from EEPROM
- External tool to convert the eeprom file into an inc file ready to be used
- Ability to load a patch directly from an inc file, so one less hacky convertion step when loading existing patches
It might not have been the best decision I made, I didn't think it that last step would be that hard to do, since it has been done before. But once I tried, i realized that none of the ram patch support is present in the kernel so it's going to be a lot of work to get that working. I tried finding the patch, but couldn't (ram and patch are common keywords). Could anyone point me to that or suggest something?
Right now it has:
- Similar interface to patchtool
- Saving to EEPROM
- Loading from EEPROM
- External tool to convert the eeprom file into an inc file ready to be used
- Ability to load a patch directly from an inc file, so one less hacky convertion step when loading existing patches
It might not have been the best decision I made, I didn't think it that last step would be that hard to do, since it has been done before. But once I tried, i realized that none of the ram patch support is present in the kernel so it's going to be a lot of work to get that working. I tried finding the patch, but couldn't (ram and patch are common keywords). Could anyone point me to that or suggest something?
Re: Playing patches from RAM
Best I could find is this:
I remember the code for my early patchtool was really bad when I last looked and I don't even have it anymore as I figured it would be best to totally rewrite it. I am not even sure what the state of MIDI_IN is anymore, or if it has fallen off due to extra sound channels, UART, inline mixer all being added since then. In that case, you would need an older version of the kernel. If it was possible, though it would probably be much more complex to do, the ability to play 1 patch simultaneously with the patch you are editing would make it the complete solution. It would be great without that as well, and the EEPROM output is a great idea. At the time, I didn't realize it was so easy to arbitrarily write over EEPROM.uze6666 wrote: Hehe, not much work, my friend, there's already some 'RAM patch' code for just that, creating patches. Just need somebody to write that tool . I was thinking we could save the patches in the EEPROM for easy transfer to the PC. So to make that tool you need 2 things:
1.to have the #define MIDI_IN == 1
2.declare the ram patch buffer in your code with
Code: Select all
extern unsigned char ramPatch[128];
Put the command stream as defined previously. When trigging notes or FX use only TriggerNote() patch #127 (you much specify a channel for the ram patch to work:chan 0,1,2 for wavetable, chan 3 for noise fxs).
That should be it!
Re: Playing patches from RAM
Unfortunately the ram patch feature was removed some time ago because I thought nobody used it and the MIDI interface was getting the boot. Though I now realize it is essential for making tools on the Uzebox (which I'm fond of ), like a patch tool or even a tracker. Let me think about it. I think I could add it back without too much effort, perhaps even support more than one ram patch. It would be activated with a compile switch of course.
Re: Playing patches from RAM
I'll try to add that once I have a working version.D3thAdd3r wrote:Best I could find is this:
If it was possible, though it would probably be much more complex to do, the ability to play 1 patch simultaneously with the patch you are editing would make it the complete solution. It would be great without that as well, and the EEPROM output is a great idea. At the time, I didn't realize it was so easy to arbitrarily write over EEPROM.
The only problem I had when arbitrarily writing over the EEPROM is the kernel kept wanting to format it, so I just incorporated the EEPROM signature into my own to fool it . It's made to only be used with uzem, anyway (for now, at least).
That would be awesome.uze6666 wrote:Unfortunately the ram patch feature was removed some time ago because I thought nobody used it and the MIDI interface was getting the boot. Though I now realize it is essential for making tools on the Uzebox (which I'm fond of ), like a patch tool or even a tracker. Let me think about it. I think I could add it back without too much effort, perhaps even support more than one ram patch. It would be activated with a compile switch of course.
I was hoping I could use the tool to generate patches for my UCC entry. I'll just keep polishing the logic and graphics for now and delay the audio creation, there's always something to improve .
Re: Playing patches from RAM
I decided to follow another path and make a desktop program, it should make everything easier really.
So I started out with basic stuff, like parsing the C source (or just the inc file), extracting the patches and saving them to a nicer format (one int per line, for easy scanfing). This part is working better than I expected actually, it didn't brake with any of the files I tested it on.
The second step is to generate a wav file given a patch, so I started writing a tool for this. I've been stuck for a couple of days, getting awful sounds (see the attached kling.wav, it's supposed to be the logo sound) and I could use some guidance. I'm going to explain what I did and show some code, hoping someone will notice something that does not properly emulate the uzebox's sound generation.
Regarding kling.wav: Tremolo is not implemented yet, but the first note sounds completely wrong and it's issued before the tremolo effect.
I basically output all the headers set to this:
RIFF (little-endian) data, WAVE audio, Microsoft PCM, 8 bit, mono 15734 Hz
I have the following defines and wave tables:
I took all the waves and the step table from the kernel code.
Here's how I'm mixing. vector data is the pcm data that goes straight into the wav file
I believe the problem is probably in the top part (before the switch statement). The mixing itself. I've been trying to replicate the kernel's code, but it's a bit hard to understand as this part is actually all over the place there.
Specially this line: data.push_back((v>>1) & 0xff)
I do this because that's what I read in the wiki, but if I read the kernel's assembly code right (which I'm really bad at), it does not divide it by 2. Anyway, it tried both approaches and the sound is still wrong.
A detailed explanation of the command stream treatment and mixing would help me infinitely .
Attachments:
kling.wav http://uzebox.nebososo.com/kling.wav
So I started out with basic stuff, like parsing the C source (or just the inc file), extracting the patches and saving them to a nicer format (one int per line, for easy scanfing). This part is working better than I expected actually, it didn't brake with any of the files I tested it on.
The second step is to generate a wav file given a patch, so I started writing a tool for this. I've been stuck for a couple of days, getting awful sounds (see the attached kling.wav, it's supposed to be the logo sound) and I could use some guidance. I'm going to explain what I did and show some code, hoping someone will notice something that does not properly emulate the uzebox's sound generation.
Regarding kling.wav: Tremolo is not implemented yet, but the first note sounds completely wrong and it's issued before the tremolo effect.
I basically output all the headers set to this:
RIFF (little-endian) data, WAVE audio, Microsoft PCM, 8 bit, mono 15734 Hz
I have the following defines and wave tables:
Code: Select all
#define SAMPLE_RATE 15734
#define SAMPLES_PER_FRAME ((SAMPLE_RATE)/60)
#define DEFAULT_VOLUME 0xc0
#define NUM_WAVES 10
const int *waves[] = {
sine_wave,
up_sawtooth_wave,
triangle_wave,
square_25_wave,
square_50_wave,
square_75_wave,
sine_disto1_wave,
sine_disto2_wave,
sine_disto3_wave,
filtered_50_square_wave,
};
Here's how I'm mixing. vector data is the pcm data that goes straight into the wav file
Code: Select all
std::vector<uint8_t> data;
char note = 80;
uint16_t next_sample = 0;
int track_volume = DEFAULT_VOLUME;
int note_volume = track_volume;
int envelope_volume = 0xff;
int env_speed = 0;
int wave = 0;
char loop_count = 0;
for (size_t i = 0; i < patch.size(); i += 3) {
for (int delay = patch[i]; delay; delay--) {
envelope_volume += env_speed;
envelope_volume = envelope_volume > 0xff? 0xff
: (envelope_volume < 0? 0 : envelope_volume);
if (note_volume && envelope_volume && track_volume) {
note_volume *= track_volume;
note_volume += 0x100;
note_volume >>= 8;
note_volume *= envelope_volume;
note_volume += 0x100;
note_volume >>= 8;
/* Assumes the master volume is 0xff, no calculation needed */
/* TODO: Tremolo */
}
else
note_volume = 0;
for (int j = 0; j < SAMPLES_PER_FRAME; j++) {
if (note >= 0 && wave >= 0 && wave < NUM_WAVES) {
next_sample += step_table[(int) note];
int v = waves[wave][next_sample>>8] * note_volume;
data.push_back((v>>1) & 0xff);
}
else
data.push_back(data.size()? data.back() : 0);
}
}
if (patch[i+1] == PATCH_END || patch[i+1] == PC_NOTE_CUT)
break;
switch (patch[i+1]) {
case PC_ENV_SPEED:
env_speed = patch[i+2];
break;
/* TODO */
case PC_NOISE_PARAMS:
break;
case PC_WAVE:
wave = patch[i+2];
break;
case PC_NOTE_UP:
note += patch[i+2];
break;
case PC_NOTE_DOWN:
note -= patch[i+2];
break;
/* TODO */
case PC_NOTE_HOLD:
break;
case PC_ENV_VOL:
envelope_volume = patch[i+2];
break;
case PC_PITCH:
note = patch[i+2];
break;
/* TODO */
case PC_TREMOLO_LEVEL:
case PC_TREMOLO_RATE:
case PC_SLIDE:
case PC_SLIDE_SPEED:
break;
case PC_LOOP_END:
if (loop_count <= 0)
break;
else if (patch[i+2] > 0) {
i -= 3*(i+1);
}
else {
do {
i -= 3;
} while(i >= 4 && patch[i+1] != PC_LOOP_START);
}
break;
case PC_LOOP_START:
default:
break;
}
Specially this line: data.push_back((v>>1) & 0xff)
I do this because that's what I read in the wiki, but if I read the kernel's assembly code right (which I'm really bad at), it does not divide it by 2. Anyway, it tried both approaches and the sound is still wrong.
A detailed explanation of the command stream treatment and mixing would help me infinitely .
Attachments:
kling.wav http://uzebox.nebososo.com/kling.wav
Re: Playing patches from RAM
I cannot see what would be causing this, but the waveform is definitely weird. Maybe it will help someone else to diagnose the problem:
Half of it never dips above the middle, so maybe the signs are off? Re: Playing patches from RAM
Aha, this actually helped me a lot. By looking at the wave, I was able to tell where exactly it was messing up and experimented with a couple of things.D3thAdd3r wrote:I cannot see what would be causing this, but the waveform is definitely weird. Maybe it will help someone else to diagnose the problem:Half of it never dips above the middle, so maybe the signs are off?
This is how you actually calculate the final value:
Code: Select all
int v = (waves[wave][(next_sample>>8)&0xff] * note_volume) + 0x100;
data.push_back((v>>8) & 0xff);
The kling is still very bland, because there's no tremolo yet, but it sounds right .
http://uzebox.nebososo.com/kling2.wav
Re: Playing patches from RAM
It's really powerful to have the sound engine in a PC program, now so many things you could do here. Good work on this so far!
Re: Playing patches from RAM
I present you kling3.wav with the tremolo effect. It sounds fine, but when compared to the intro on uzem, it feels more metallic and I'm too lazy to fetch my avcore in my car, setup it up and wait for it to warm up right now just to listen to half a second of sound. I'll add that to the todo list. But if someone could comment on how accurate the sound is on uzem, it might help me out a bit .
I also added the slide effect, I actually have no idea what it does, I just blindly adapted the code from the kernel.
And I couldn't figure out what NOTE_HOLD is supposed to do, so I'll leave that out for now.
The noise channel is still not supported yet.
I'm going to start learning wxwidgets now to make a usable cross-platform gui for this and add the current functionality to it. Not as easy as a bunch of Prints and SetTiles, but I believe it's going to be worth it .
http://uzebox.nebososo.com/kling3.wav
I also added the slide effect, I actually have no idea what it does, I just blindly adapted the code from the kernel.
And I couldn't figure out what NOTE_HOLD is supposed to do, so I'll leave that out for now.
The noise channel is still not supported yet.
I'm going to start learning wxwidgets now to make a usable cross-platform gui for this and add the current functionality to it. Not as easy as a bunch of Prints and SetTiles, but I believe it's going to be worth it .
http://uzebox.nebososo.com/kling3.wav
Re: Playing patches from RAM
It's a lot better and recognizable as the same sound for sure. Here is a recording from Uzem for comparison: http://uzebox.net/uzebox_kling.wav , if Apache decides it wants to work for more than 4 hours without a restart.
Here is the waveforms with Uzem on top and the latest kling version on the bottom. This one is a large zoom in on the wave forms at the very beginning of the sound: I am not sure what the master volume was set to on the rom I ran in Uzem but it was not 255 so the max value on the top is less than that of your output at 255. Also I probably didn't trim exactly the initial silence off the start, but still you can see the peaks of the waves are nearly lined up, just imagine them shifted a bit to align. The little tick marks show the actual sample bytes of both files, the wave shape itself is an imaginary extrapolation on those data points(well the audio hardware will do something like that with it to make it analog). It looks like at the high values it is converting right, but then it does not continue to follow the waveform...whatever that means, I don't see which part to even suspect in the code.
The slide commands are for sliding(automatically each frame, like an envelope for pitch instead of volume) to the target pitch and it takes the amount of frames specified by the speed command. It does it in a way that is in between the half steps in the step table. It is actually 1/8 steps so a little complicated and off hand I don't recall how that is implemented. Check out Patch Commands Defined too if you haven't already. The NOTE_HOLD relies on on there being a note off command in the MIDI stream(which by default midiconv will actually strip out unless you tell it not to), so it does not execute any of the following commands until that happens though any envelopes specified before still happen; I think.
Here is the waveforms with Uzem on top and the latest kling version on the bottom. This one is a large zoom in on the wave forms at the very beginning of the sound: I am not sure what the master volume was set to on the rom I ran in Uzem but it was not 255 so the max value on the top is less than that of your output at 255. Also I probably didn't trim exactly the initial silence off the start, but still you can see the peaks of the waves are nearly lined up, just imagine them shifted a bit to align. The little tick marks show the actual sample bytes of both files, the wave shape itself is an imaginary extrapolation on those data points(well the audio hardware will do something like that with it to make it analog). It looks like at the high values it is converting right, but then it does not continue to follow the waveform...whatever that means, I don't see which part to even suspect in the code.
The slide commands are for sliding(automatically each frame, like an envelope for pitch instead of volume) to the target pitch and it takes the amount of frames specified by the speed command. It does it in a way that is in between the half steps in the step table. It is actually 1/8 steps so a little complicated and off hand I don't recall how that is implemented. Check out Patch Commands Defined too if you haven't already. The NOTE_HOLD relies on on there being a note off command in the MIDI stream(which by default midiconv will actually strip out unless you tell it not to), so it does not execute any of the following commands until that happens though any envelopes specified before still happen; I think.