Uzebox Patch Studio

Topics on software tools like TileStudio, comments on documentation and tutorials (or the lack of) should go here.
User avatar
nebososo
Posts: 188
Joined: Sun Oct 04, 2009 10:33 pm

Uzebox Patch Studio

Post by nebososo »

I started making this _months_ ago, I eventually ended up with no free time and it was sitting on my local git repository for a while.
All the intended functionality is present, I just wanted to test it more before releasing it, but it never happened. I encourage opening Github issues and/or sending pull requests.

Anyway, here's Uzebox Patch Studio, a wxWidgets application for creating Uzebox sound patches with nice features:
  • - Read and write directly from/to C source files (the .incs we are already used to)
    - Builtin sound emulator for you to test your patch as you write it
    - Loop patches
    - Play several patches at the same time
    - Supports Windows, Linux and MacOS, really anything that is supported by wxWidgets, SDL and SDL_mixer.
Building instruction are included in the README file in the repository.

Here's a screenshot playing with Megatris' patches:
Image

Repository/Source here:
https://github.com/nebososo/uzebox-patch-studio

Windows binaries:
https://drive.google.com/file/d/0B0Kl0x ... sp=sharing
Last edited by nebososo on Thu Sep 22, 2016 5:35 pm, edited 1 time in total.
User avatar
Jubatian
Posts: 1560
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Uzebox Patch Studio

Post by Jubatian »

Huh, this is sure something I wished to have! That is, once I get to do anything about the audio part, but eventually that will have to come :) I think I will give it a look though soon, if for nothing else, to understand better how the patches actually work (I mean it is hard to imagine their audible effect without actually hearing it), or how they are supposed to work in case I venture in messing around with audio code (it would be easier to test with this since it can also be used to build test cases).
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: Uzebox Patch Studio

Post by Artcfox »

This looks like it will be a very useful tool! Thank you for posting it.

I just tried it out on my patches.inc file, but under "Sound Patches" it's only showing me patch00, rather than all the patches listed in the PatchStruct.

Here is what my patches.inc file looks like:

Code: Select all

/*
 *  Uzebox Default Patches
 *  Copyright (C) 2008  Alec Bourque
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
	Patches are made of a command stream made of 3 bytes per command:
		1=delta time
		2=command
		3=command parameter

	Patches must start with 1 byte describing the sound type:
		0=wave channel (i.e.; channel 0,1 and 2)
		1=noise channel (channel 3)
		2=PCM (channel 3)
		->For type=2 two more byte follows in order: sample adress low byte, sample adress hi byte

	It must end with <0,PATCH_END> and this command takes
	only two bytes (no parameter).
*/

//FX: jump
const char patch00[] PROGMEM ={
0,PC_WAVE,0,
0,PC_ENV_SPEED,-8,
0,PC_PITCH,80,
1,PC_NOTE_UP,4,
1,PC_NOTE_CUT,0,
0,PATCH_END
};

//FX: kill monster
const char patch01[] PROGMEM ={
0,PC_WAVE,1,
0,PC_ENV_SPEED,-8,
1,PC_PITCH,84,
1,PC_PITCH,80,
1,PC_PITCH,81,
1,PC_PITCH,80,
1,PC_NOTE_CUT,0,
0,PATCH_END
};

//FX: cookie
const char patch02[] PROGMEM ={
0,PC_WAVE,1,
0,PC_ENV_SPEED,-8,
1,PC_PITCH,95,
1,PC_NOTE_UP,2,
1,PC_NOTE_UP,2,
1,PC_NOTE_CUT,0,
0,PATCH_END
};

//FX: player death
const char patch03[] PROGMEM ={
0,PC_WAVE,1,
0,PC_ENV_SPEED,-16,
1,PC_PITCH,54,
1,PC_NOTE_DOWN,2,
1,PC_NOTE_DOWN,3,
1,PC_NOTE_CUT,0,
0,PATCH_END
};

const struct PatchStruct patches[] PROGMEM = {
{0,NULL,patch00,0,0},
{0,NULL,patch01,0,0},
{0,NULL,patch02,0,0},
{0,NULL,patch03,0,0},
};
and here is what my sounds.inc file looks like:

Code: Select all

/*
 *  Uzebox Wavetable
 *  Copyright (C) 2008  Alec Bourque
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

;********************************************
; Sound Waves Definitions 
; Last Generated:30/04/2006 9:43:45 AM
;********************************************

	;*************************************************************
	;Generated Wave #3: square-25%
	;*************************************************************
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80

	;*************************************************************
	;Generated Wave #4: square-50%
	;*************************************************************
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
	.byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
I'm using a custom sounds.inc in order to save on flash ram (only including the wave tables that I use), but I don't think that should confuse it, other than it isn't necessarily going to use the correct wave table with the correct patch, since mine are renumbered, so the sounds will be a little off.

Edit: If I click on "patches" under "Patch Structs" it shows me all of my patches, but only patch00 is green, the rest are red.

Edit 2: This also happens if I create a patch struct and multiple patches entirely from within Patch Studio, save it, quit, relaunch, and import the file I just saved.
User avatar
nebososo
Posts: 188
Joined: Sun Oct 04, 2009 10:33 pm

Re: Uzebox Patch Studio

Post by nebososo »

Artcfox wrote:This looks like it will be a very useful tool! Thank you for posting it.

I just tried it out on my patches.inc file, but under "Sound Patches" it's only showing me patch00, rather than all the patches listed in the PatchStruct.

Here is what my patches.inc file looks like:

and here is what my sounds.inc file looks like:

I'm using a custom sounds.inc in order to save on flash ram (only including the wave tables that I use), but I don't think that should confuse it, other than it isn't necessarily going to use the correct wave table with the correct patch, since mine are renumbered, so the sounds will be a little off.

Edit: If I click on "patches" under "Patch Structs" it shows me all of my patches, but only patch00 is green, the rest are red.

Edit 2: This also happens if I create a patch struct and multiple patches entirely from within Patch Studio, save it, quit, relaunch, and import the file I just saved.
Thanks for the bug report.

I just opened your file and it worked for me, on Linux with wxGtk 3. I'm using wx's data structures and classes to parse the file so it might be related to that. What OS and wx implementation are you using?

Support for different wave tables is something I wanted to add but ended up not doing, the way it is now, it's not going to crash or anything, it's just going to sound off, like you said.
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: Uzebox Patch Studio

Post by Artcfox »

nebososo wrote:Thanks for the bug report.

I just opened your file and it worked for me, on Linux with wxGtk 3. I'm using wx's data structures and classes to parse the file so it might be related to that. What OS and wx implementation are you using?

Support for different wave tables is something I wanted to add but ended up not doing, the way it is now, it's not going to crash or anything, it's just going to sound off, like you said.
I was using a Debian 8 LiveUSB, wxGtk3.

I made a debug build, ran it through GDB, and realized the problem is in this function:

Code: Select all

bool FileReader::read_patches(const std::string &clean_src,
    std::multimap<wxString, wxVector<long>> &data) {
  std::smatch match;
  auto search_start = clean_src.cbegin();

  data.clear();

  while (std::regex_search(search_start, clean_src.cend(),
        match, patch_declaration)) {
    search_start += match.position() + match.length();

    wxVector<long> vals;
    std::string varea(search_start, clean_src.cend());
    if (!read_patch_vals(varea, vals))
      return false;
    data.emplace(match[1].str(), vals);
  }

  return true;
}
It only ever makes it through that while loop once.


Furthermore, I made a test case wtf.cpp:

Code: Select all

#include <iostream>
#include <string>
#include <regex>

int main()
{
    // repeated search (see also std::regex_iterator)
    std::string log(
		    "	Speed:	366"
		    "	Mass:	35"
		    "	Speed:	378"
		    "	Mass:	32"
		    "	Speed:	400"
		    "	Mass:	30"
		    );

    std::regex r("(Speed:\\t\\d*)");

    std::smatch sm;

    while(std::regex_search(log, sm, r))
    {
        std::cout << sm.str() << '\n';
        log = sm.suffix();
    }

    std::string log2(
		     "const char patch00[] PROGMEM ={ 0,PC_WAVE,0, 0,PC_ENV_SPEED,-8, 0,PC_PITCH,80, 1,PC_NOTE_UP,4, 1,PC_NOTE_CUT,0, 0,PATCH_END };"
		     "const char patch01[] PROGMEM ={ 0,PC_WAVE,1, 0,PC_ENV_SPEED,-8, 1,PC_PITCH,84, 1,PC_PITCH,80, 1,PC_PITCH,81, 1,PC_PITCH,80, 1,PC_NOTE_CUT,0, 0,PATCH_END };"
		     "const char patch02[] PROGMEM ={ 0,PC_WAVE,1, 0,PC_ENV_SPEED,-8, 1,PC_PITCH,95, 1,PC_NOTE_UP,2, 1,PC_NOTE_UP,2, 1,PC_NOTE_CUT,0, 0,PATCH_END };"
		     "const char patch03[] PROGMEM ={ 0,PC_WAVE,1, 0,PC_ENV_SPEED,-16, 1,PC_PITCH,54, 1,PC_NOTE_DOWN,2, 1,PC_NOTE_DOWN,3, 1,PC_NOTE_CUT,0, 0,PATCH_END };"
		     "const struct PatchStruct patches[] PROGMEM = { {0,NULL,patch00,0,0}, {0,NULL,patch01,0,0}, {0,NULL,patch02,0,0}, {0,NULL,patch03,0,0}, };"
		     );

    std::regex r2("const char ([a-zA-Z_][a-zA-Z_0-9]*)\\[\\] PROGMEM ?= ?");

    std::smatch sm2;

    while(std::regex_search(log2, sm2, r2))
    {
        std::cout << sm2.str() << '\n';
        log2 = sm2.suffix();
    }
}
Which when compiled with:

Code: Select all

g++ -std=c++14 -o wtf wtf.cpp
gives me:

Code: Select all

$ ./wtf
Speed:	366
Speed:	378
Speed:	400
const char patch00[] PROGMEM =
So… like… um… yeah.
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: Uzebox Patch Studio

Post by Artcfox »

Found the solution, and it doesn't make sense to me at all.

Just replace 0-9 in the regex with \\d and it works.

Code: Select all

diff --git a/filereader.cpp b/filereader.cpp
index 07dc765..d69bfbb 100644
--- a/filereader.cpp
+++ b/filereader.cpp
@@ -53,9 +53,9 @@ const std::regex FileReader::singleline_comments("//.*");
 const std::regex FileReader::white_space("[\t\n\r]");
 const std::regex FileReader::extra_space(" +");
 const std::regex FileReader::patch_declaration(
-    "const char ([a-zA-Z_][a-zA-Z_0-9]*)\\[\\] PROGMEM ?= ?");
+    "const char ([a-zA-Z_][a-zA-Z_\\d]*)\\[\\] PROGMEM ?= ?");
 const std::regex FileReader::struct_declaration(
-    "const struct PatchStruct ([a-zA-Z_][a-zA-Z_0-9]*)\\[\\] PROGMEM ?= ?");
+    "const struct PatchStruct ([a-zA-Z_][a-zA-Z_\\d]*)\\[\\] PROGMEM ?= ?");
 
 long FileReader::string_to_long(const wxString &str) {
   if (defines.find(str) != defines.end())
User avatar
nebososo
Posts: 188
Joined: Sun Oct 04, 2009 10:33 pm

Re: Uzebox Patch Studio

Post by nebososo »

Wow, it really does make zero sense. Anyway, great job finding it, I pushed your fix, thanks.
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: Uzebox Patch Studio

Post by Artcfox »

nebososo wrote:Wow, it really does make zero sense. Anyway, great job finding it, I pushed your fix, thanks.
No problem! I ended up learning more about gdb, so it was a good (although frustrating) learning experience for me.

Using gcc 5.2, or gcc 6.1 with your original code also works, but not gcc 4.9.
User avatar
Jubatian
Posts: 1560
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Uzebox Patch Studio

Post by Jubatian »

I cloned it to try it out a bit, successful with what I attempted :)

Seeing the readme I got a slight deja-vu:
Image
(Of course I mean the relation of steps necessary to get it running on Linux versus Windows)
User avatar
D3thAdd3r
Posts: 3175
Joined: Wed Apr 29, 2009 10:00 am
Location: Minneapolis, United States

Re: Uzebox Patch Studio

Post by D3thAdd3r »

I have wanted this forever, it is a really important tool. I will try building this tonight and check it out!
Post Reply