Playing patches from RAM

Topics related to the API, programming discussions & questions, coding tips, bugs, etc. should go here.
User avatar
nebososo
Posts: 188
Joined: Sun Oct 04, 2009 10:33 pm

Playing patches from RAM

Post by nebososo »

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?
User avatar
D3thAdd3r
Posts: 3221
Joined: Wed Apr 29, 2009 10:00 am
Location: Minneapolis, United States

Re: Playing patches from RAM

Post by D3thAdd3r »

Best I could find is this:
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!
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.
User avatar
uze6666
Site Admin
Posts: 4801
Joined: Tue Aug 12, 2008 9:13 pm
Location: Montreal, Canada
Contact:

Re: Playing patches from RAM

Post by uze6666 »

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 :P ), 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.
User avatar
nebososo
Posts: 188
Joined: Sun Oct 04, 2009 10:33 pm

Re: Playing patches from RAM

Post by nebososo »

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.
I'll try to add that once I have a working version.
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 :lol: . It's made to only be used with uzem, anyway (for now, at least).
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 :P ), 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.
That would be awesome.
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 :lol: .
User avatar
nebososo
Posts: 188
Joined: Sun Oct 04, 2009 10:33 pm

Re: Playing patches from RAM

Post by nebososo »

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:

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,
};
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

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;
    }
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
User avatar
D3thAdd3r
Posts: 3221
Joined: Wed Apr 29, 2009 10:00 am
Location: Minneapolis, United States

Re: Playing patches from RAM

Post by D3thAdd3r »

I cannot see what would be causing this, but the waveform is definitely weird. Maybe it will help someone else to diagnose the problem:
waveform.jpg
waveform.jpg (54.31 KiB) Viewed 7143 times
Half of it never dips above the middle, so maybe the signs are off? :?
User avatar
nebososo
Posts: 188
Joined: Sun Oct 04, 2009 10:33 pm

Re: Playing patches from RAM

Post by nebososo »

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:
waveform.jpg
Half of it never dips above the middle, so maybe the signs are off? :?
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.
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);
I was also destroying note_volume every frame, which wasn't right, I fixed that too.

The kling is still very bland, because there's no tremolo yet, but it sounds right :D.

http://uzebox.nebososo.com/kling2.wav
User avatar
D3thAdd3r
Posts: 3221
Joined: Wed Apr 29, 2009 10:00 am
Location: Minneapolis, United States

Re: Playing patches from RAM

Post by D3thAdd3r »

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!
User avatar
nebososo
Posts: 188
Joined: Sun Oct 04, 2009 10:33 pm

Re: Playing patches from RAM

Post by nebososo »

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 :ugeek: .

http://uzebox.nebososo.com/kling3.wav
User avatar
D3thAdd3r
Posts: 3221
Joined: Wed Apr 29, 2009 10:00 am
Location: Minneapolis, United States

Re: Playing patches from RAM

Post by D3thAdd3r »

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.
wave_compare1.jpg
wave_compare1.jpg (48.29 KiB) Viewed 7096 times
This one is a large zoom in on the wave forms at the very beginning of the sound:
wave_compare2.jpg
wave_compare2.jpg (92.76 KiB) Viewed 7095 times
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.
Post Reply