|
|
|
02-07-2015, 01:41 PM
|
#41
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
Quote:
Originally Posted by Aries1985
Recently I have discovered Beyond. It works really good! However I am struggling to get Sysex data from media item through Beyond with Reaper.RPR_MIDI_GetTextSysexEvt() function.
|
When I tried your code, I encountered an exception in tkinter:
"Traceback (most recent call last):
File "C:\Program Files\Python\lib\tkinter\__init__.py", line 1487, in __call__
return self.func(*args)
File "M:\Program\Python\Examples Reaper Community\Aries1985\Sysex.py", line 24, in <lambda>
button = Button(self, text = "Test", anchor = W, command = lambda: self.access(index))
NameError: name 'index' is not defined"
I would recommend starting with the core functions, and verifying the progress with Say() at every step, and later build a UI around it.
Code:
import beyond.Reaper
import beyond.Reaper.Item
import beyond.Screen
@ProgramStart
class Main(Parallel):
def Start(o):
with Reaper as r:
for Item in r.ProjectSelected.ItemsSelected:
T = Item.TakeActive
Say(T.Name, T.Address)
Say(r.RPR_MIDI_GetTextSysexEvt(T.Address, 0, 0, 0, 0, 0, 0, 0))
Also check out the much simpler UI functions and examples.
|
|
|
02-08-2015, 12:00 AM
|
#42
|
Human being with feelings
Join Date: Jul 2011
Posts: 59
|
Quote:
Originally Posted by beyond
I have been quite busy lately, but I would like to let everyone know that this project has been very active and quite a work horse in our productions. I will be posting an update soon, along with responding to your messages.
|
That is great news!
Apologies for not-properly-debugged example and thank you for having a look.
The behavior should be: - look if supported SysEx message is under cursor, if so, notify in GUI
- when button in GUI is clicked message is: 1) removed if is on cursor position 2) added if it was not under cursor
I have EEL script that works for Sysex message detection in item REAPER (Python script is based on that). It is simple concept as well. When there is appropriate Sysex message under cursor, it writes message name in white. Following example shows that for one message:
Working EEL code:
Code:
sprintf(#sys_dr_on, "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", 0x42, 0x30, 0x00, 0x01, 0x05, 0x41, 0x00, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x01);
sprintf(#sys_dr_off, "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", 0x42, 0x30, 0x00, 0x01, 0x05, 0x41, 0x00, 0x00, 0x0B, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00);
dr_on_present = 0;
dr_off_present = 0;
PLUGIN_TITLE="Counter";
function dim() (
gfx_r = 0.7;
gfx_g = 0.7;
gfx_b = 0.7;
);
function lit() (
gfx_r = 1;
gfx_g = 1;
gfx_b = 1;
);
function checkWhatIsPresent() (
// reinitialize variables, cursor position might have changed
dr_on_present = 0;
dr_off_present = 0;
selectedTrack = GetSelectedTrack(0, 0);
cursor_pos = GetCursorPosition();
i = 0;
iterate_media = 1;
iterate_events = 1;
loop(CountTrackMediaItems(selectedTrack), iterate_media ? (
item_id = GetTrackMediaItem(selectedTrack, i);
pos = GetMediaItemInfo_Value(item_id, "D_POSITION");
length = GetMediaItemInfo_Value(item_id, "D_LENGTH");
end_pos = pos + length;
current_pos_ppq = MIDI_GetPPQPosFromProjTime(take, cursor_pos);
// printf("MediaItem %f %f %f\n", pos, end_pos, cursor_pos);
(cursor_pos >= pos + 0.001) && (cursor_pos <= end_pos + 0.001) ? (
// cursor is on item
take = GetActiveTake(item_id);
current_pos_ppq = MIDI_GetPPQPosFromProjTime(take, cursor_pos);
evt_index = 0;
loop(MIDI_CountEvts(take, NULL, NULL, num_sysex), iterate_events ?
(
MIDI_GetTextSysexEvt(take, evt_index, NULL, NULL, current_evt_pos_ppq, current_evt_type, current_ent_msg);
(current_evt_type == -1) ? (
// yes, this is a sysex message
(current_pos_ppq == current_evt_pos_ppq) ? (
// yes, it is on current cursor position
!strcmp(current_msg, #sys_dr_on) ? dr_on_present = 1;
!strcmp(current_msg, #sys_dr_off) ? dr_off_present = 1;
);
);
evt_index += 1;
)
);
iterate_media = 0;
);
i += 1;
)
);
);
function btn(title, variable, x_pos, y_pos) (
gfx_x = x_pos;
gfx_y = y_pos;
variable ? lit() : dim();
gfx_drawstr(title);
);
function run() (
checkWhatIsPresent();
btn("Dr On", dr_on_present, 10, 10);
btn("Dr Off", dr_off_present, 10, 26);
// check if "lmb down" and "mouse cursor is not in window" and "not currently moving slider"
mouse_cap >= 1 && (mouse_x <= 0 || mouse_x >= gfx_w || mouse_y < 2 || mouse_y >= gfx_h) && drag_started == 0 ? (
lmb_click_outside_window = 1;
) : mouse_cap == 0 ? (
lmb_click_outside_window = 0;
);
mouse_cap == 0 ? (
max_point_drag_started = 0; min_point_drag_started = 0; lmb_down = 0;
);
char = gfx_getchar();
// ctrl+lmb to Undo
// Esc to exit
char == 27 ? (
undo_block == 1 ? (
Undo_OnStateChange("Adjust track colors");
);
gfx_quit();
);
char >= 0 ? (
defer("run();");
);
gfx_update();
);
gfx_init("",420,200);
gfx_setfont(1, "Arial", 14);
lmb_click_outside_window = 1;
run();
Here is updated Python script with more reasonable debug messages written to console:
Code:
import array
import beyond.Reaper
import tkinter
from tkinter import *
# TODO - add loading support
sysex = [
("Drum Track On", bytes.fromhex("42 30 00 01 05 41 00 00 0B 00 0A 00 00 00 00 01")),
("Drum Track Off", bytes.fromhex("42 30 00 01 05 41 00 00 0B 00 0A 00 00 00 00 00"))
]
button_states = []
class my_app(Frame):
"""Basic Frame"""
last_pos = 0
buttons = []
def __init__(self, master):
"""Init the Frame"""
Frame.__init__(self,master)
self.grid()
for index in range(0, len(sysex)):
button_states.append(tkinter.IntVar())
button_state_variable = button_states[index]
button = Checkbutton(self, text = sysex[index][0], anchor = W, onvalue=1, offvalue=0, variable=button_state_variable , command = lambda index=index: self.access(index))
button.config(height = 3, width = 30)
button.grid(column = 0, row = index, sticky = NW)
self.buttons.append(button)
self.last_pos = Reaper.RPR_GetCursorPosition()
self.update_controls()
def update_controls(self):
selectedTrack = Reaper.RPR_GetSelectedTrack(0, 0);
# TODO add caching
cursor_pos = Reaper.RPR_GetCursorPosition()
for i in range(0, Reaper.RPR_CountTrackMediaItems(selectedTrack)):
item_id = Reaper.RPR_GetTrackMediaItem(selectedTrack, i)
take = Reaper.RPR_GetActiveTake(item_id);
pos = Reaper.RPR_GetMediaItemInfo_Value(item_id, "D_POSITION");
length = Reaper.RPR_GetMediaItemInfo_Value(item_id, "D_LENGTH");
end_pos = pos + length
current_pos_ppq = Reaper.RPR_MIDI_GetPPQPosFromProjTime(take, cursor_pos);
if (cursor_pos >= pos + 0.001) and (cursor_pos <= end_pos + 0.001):
# current_pos_ppq = Reaper.RPR_MIDI_GetPPQPosFromProjTime(take, cursor_pos);
# iterate over Sysex events, get status
num_sysex = Reaper.RPR_MIDI_CountEvts(take, 0, 0, 0)[0]
print("Num sysex: " + str(num_sysex))
for j in range(0, num_sysex):
# ta_ prefix for throw away
(ta_retval, ta_take, ta_textsyxevtidx, ta_selectedOutOptional, ta_mutedOutOptional, current_evt_pos_ppq, current_evt_type, current_msg, msgOptional_sz) = Reaper.RPR_MIDI_GetTextSysexEvt(take, j, 0, 0, 0, 0, 0, 0)
current_msg = Reaper.RPR_MIDI_GetTextSysexEvt(take, j, 0, 0, 0, 0, 0, 0)[7]
if current_evt_type == -1:
print("Sysex event number " + str(j))
# yes, this is a sysex message
if current_pos_ppq == current_evt_pos_ppq:
print("Sysex event is on cursor position")
print("Sysex event data: %s" % ''.join('{:02X}'.format(int(a)) for a in current_msg))
# yes, it is on current cursor position
# iterate over defined sysexes
print("Configuration iteration")
for conf_id in range(0, len(sysex)):
print("Current configuration data: %s" % ''.join('{:02X}'.format(int(a)) for a in sysex[conf_id][1]))
# print("Current configuration data: %s" % sysex[conf_id][1])
if current_msg == sysex[conf_id][1]:
print("Found sysex event for button switching")
self.buttons[conf_id].config(relief = "sunken")
break
def access(self, b_id):
self.b_id = b_id
clicked_button = self.buttons[b_id]
# get state, it is state after clicking
checked = button_states[b_id].get()
if checked:
# add event on cursor position
print("Would add sysex event on cursor position")
else:
# remove event under cursor
print("Would remove sysex event under cursor")
@ProgramStartDirect
def Main():
root = Tk()
root.title("SysEx helper")
root.geometry("500x500")
app = my_app(root)
root.mainloop()
Sample debug output looks following:
Code:
Num sysex: 2
Sysex event number 0
Sysex event number 1
Sysex event is on cursor position
Sysex event data: 00
Configuration iteration
Current configuration data: 42300001054100000B000A0000000001
Current configuration data: 42300001054100000B000A0000000000
Problem is seen in output Sysex event data: 00.
It should contain SysEx data returned from Reaper.
Calling len() on variable returns length of 1.
|
|
|
02-08-2015, 06:24 AM
|
#43
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
Yes, I see the problem. I could get your code to pick up the sysx message under the cursor, as well as my code, but Say(r.RPR_MIDI_GetTextSysexEvt(T.Address, 0, 0, 0, 0, 0, 0, 0)) returns:
(1, '(MediaItem_Take*)0x0000000005920FF0', 0, 1, 0, 15480.0, -1, '0', 0) (tuple)
Looks like RPR_MIDI_GetTextSysexEvt is returning things correctly, except the sysx message and its size are both 0. I doubt this is a bug in beyond.Reaper and its serialization, as the message size is a simple integer with a value of 0 confirming the empty message, and other integers have data.
I suspect there is a bug in Reaper's Python implementation of this function. Try creating a bare minimum Python program (without beyond.Reaper or any tkinter) to run directly inside Reaper to confirm the bug, and then report it to the developers. If you can get RPR_MIDI_GetTextSysexEvt() to work directly in Reaper with bare minimum Python code, then I can help you.
As another tip, try using "with Reaper as r:" and change all your "Reaper." calls to "r." This will greatly speed up your program, as all calls will be made in one network session.
|
|
|
02-08-2015, 10:17 PM
|
#44
|
Human being with feelings
Join Date: Jul 2011
Posts: 59
|
Quote:
Originally Posted by beyond
... Say(r.RPR_MIDI_GetTextSysexEvt(T.Address, 0, 0, 0, 0, 0, 0, 0)) returns:
(1, '(MediaItem_Take*)0x0000000005920FF0', 0, 1, 0, 15480.0, -1, '0', 0) (tuple)
Looks like RPR_MIDI_GetTextSysexEvt is returning things correctly, except the sysx message and its size are both 0. ...
...Try creating a bare minimum Python program (without beyond.Reaper or any tkinter) to run directly inside Reaper to confirm the bug, and then report it to the developers. ...
|
Thank you for help, beyond. Logging whole return value is great idea.
I was able to reproduce that behavior in minimum Python program and it behaves same. So it looks like REAPER bug.
I have posted it here http://forums.cockos.com/showthread....23#post1475923
Quote:
Originally Posted by beyond
As another tip, try using "with Reaper as r:" and change all your "Reaper." calls to "r." This will greatly speed up your program, as all calls will be made in one network session.
|
Wow, thanks! I did not realize it was connecting every time.
Many thanks for your help and library!
|
|
|
04-27-2015, 12:17 AM
|
#45
|
Human being with feelings
Join Date: Apr 2015
Posts: 3
|
GetPlayPosition() not upating
Hi! Firstly, thank you for making this! I'm trying to get the GetPlayPosition(), but it doesn't seem to update while RemoteControl is running. Maybe this is something on my end. I'm running a threaded socket to connect to use beyond (only one Reaper r instance ever exists though!).
My goal is to get the audio sample data from reaper, and I'm succeeding in that, but like I said GetPlayPosition() does not update passed the moment when RemoteControl is run. Is there an alternative to GetPlayPosition() in beyond or can you give me some advice on how to force an update?
Thanks!
~Jake
|
|
|
04-30-2015, 04:11 PM
|
#46
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
It looks like the Reaper API may not be updating GetPlayPosition() during the same script execution of RemoteControl.py. Try exiting and reentering the "with Reaper as r:" block as that will create another session of RemoteControl.py and may allow Reaper to update the API in between.
|
|
|
05-05-2015, 10:11 PM
|
#47
|
Human being with feelings
Join Date: Apr 2015
Posts: 3
|
EDIT: Just switched to a new computer and didn't check off 'Receive on Port' in the OSC Settings. Follow the instructions folks
###
Thanks beyond! That is definitely working but I'm running into one more issue.
Is the "with Reaper as r" block supposed to run RemoteControl.py automatically? For some reason, I'm having to run RemoteControl.py through the Reaper interface via the Run button in the Actions window every time I want to use beyond.
I'll run an external python script (or even the Test Installation script) and I will get no response until I manually run RemoteControl.py from Reaper. After that, I have to repeat the process for anything else, because RemoteControl.py seems to be closed after one communication is made.
If I try to reverse the order and run RemoteControl.py first, it will throw a
[WinError 10061] No connection could be made because the target machine actively refused it
I'm wondering if something weird happened. Has this ever happened to you or do you have any ideas?
Last edited by BobBobson108; 05-06-2015 at 06:30 AM.
|
|
|
05-15-2015, 08:04 PM
|
#48
|
Human being with feelings
Join Date: Oct 2013
Posts: 21
|
Accessing the mixer
Hi,
I'm a big Reaper fan and I love python.
Beyond makes this marriage even better !!
I was wondering hwo I can programmatically access the mixer controls ?
What I want to do, is set the heights of the mixer based on their nesting in groups...
Thanks !!
|
|
|
09-03-2015, 03:25 PM
|
#49
|
Human being with feelings
Join Date: Jun 2008
Posts: 4,923
|
unable to get going in most recent REAPER 5.01 on mac Python 3.4
... the command id for Rempote Control,py is a string beginning _50a8e452301445b2ad360c9054e753fe - is beyond.reaper expecting a number?
|
|
|
09-03-2015, 10:58 PM
|
#50
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
Looks like Reaper is now hiding the older Numeric Cmd IDs. Current installations still work, and you maybe able to try and find the number for RemoteControl.py around 53010 for a new installation. I have to update the OSC trigger to use the new String Command ID, and this should also be more permanent. There are other updates to beyond Reaper V27 as well, most importantly a faster and more complete Element parser for Reaper chunks and encoded binaries.
|
|
|
09-04-2015, 09:31 AM
|
#51
|
Human being with feelings
Join Date: Jun 2008
Posts: 4,923
|
thanks - I'll find the proper ID - looking forward to updates - cheers
|
|
|
09-04-2015, 09:56 AM
|
#52
|
Human being with feelings
Join Date: Jun 2008
Posts: 4,923
|
...ahhh but now getting "error connection refused" as well - tia
|
|
|
01-01-2016, 10:34 PM
|
#53
|
Human being with feelings
Join Date: Dec 2015
Posts: 13
|
Quote:
Originally Posted by beyond
Looks like Reaper is now hiding the older Numeric Cmd IDs. Current installations still work, and you maybe able to try and find the number for RemoteControl.py around 53010 for a new installation. I have to update the OSC trigger to use the new String Command ID, and this should also be more permanent. There are other updates to beyond Reaper V27 as well, most importantly a faster and more complete Element parser for Reaper chunks and encoded binaries.
|
cant await this. reaper crashing constantly the bigger my scripts get
|
|
|
02-26-2016, 03:58 PM
|
#54
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
V27 Update:- Support for the new and stable RemoteControl.py CommandID trigger (OSC s/action/str)
- Faster and more complete de/serialization of Reaper Elements/Chunks
- Updated Reaper 5.2 API calls
Test away guys, and contact me soon, while I am active on this project. :-)
|
|
|
03-28-2016, 04:13 AM
|
#55
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,690
|
Hi Beyond.
I am absolutely delighted about the BeyondPython project, as I am sure that in my work, there will be future projects that will include using a tightly remote-controlled Reaper installation. As those projects will have a rather complex additional business logic, (maybe) SQL Database work, and (maybe) sophisticated GUI, they will need to be done in a decent programming language, which might be Python (Or Pascal, or C++, or ....).
So I d/lded the V27 zip to read the documentation and unfortunately there is close to none.
As I did not use Python at all, yet, I can't easily understand or check out the many examples that are included.
That is why I would like to ask some simple questions to be able to decide if Beyond might be a way to be decently considered for those projects.
The questions include:
- I do understand that the external Python program can call any Reaper actions (including those that are provided as scripts within Reaper). Is it possible to transfer all parameters with that call, that are used by the appropriate action. (E.g. when starting a render action Reaper needs to know the destination file type and path, etc).
- Is it possible to dynamically retrieve states of the running Reaper installation such as "Recording" or "Busy doing a rendering action", "Name of the recorded WAV file of track 7" and similar.
- I understand that with Beyond, there is an internal Python program running as a script (provided by Beyond) in Reaper and an external Python program that can be completely done by the "user". Is it necessary / viable / common to enhance the internal Python script according to the project the user has in mind, or should same stay unmodified, being just a unified transfer module ?
Thanks for enlightenment,
-Michael
Last edited by mschnell; 03-28-2016 at 04:55 AM.
|
|
|
03-29-2016, 09:53 PM
|
#56
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,690
|
Can anybody more knowledgeable than me comment on my questions ?
Thanks,
-Michael
|
|
|
03-31-2016, 10:07 PM
|
#57
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
Quote:
Originally Posted by mschnell
Hi Beyond.
The questions include:
- I do understand that the external Python program can call any Reaper actions (including those that are provided as scripts within Reaper). Is it possible to transfer all parameters with that call, that are used by the appropriate action. (E.g. when starting a render action Reaper needs to know the destination file type and path, etc).
- Is it possible to dynamically retrieve states of the running Reaper installation such as "Recording" or "Busy doing a rendering action", "Name of the recorded WAV file of track 7" and similar.
- I understand that with Beyond, there is an internal Python program running as a script (provided by Beyond) in Reaper and an external Python program that can be completely done by the "user". Is it necessary / viable / common to enhance the internal Python script according to the project the user has in mind, or should same stay unmodified, being just a unified transfer module ?
Thanks for enlightenment,
-Michael
|
Yes, any function that is in the Reaper or an extension API can be called without any limits on the parameters, returns or exception handling. In fact, beyond.Reaper lifts many limits on Reaper's internal Python handling, such as the 4MB string limit (especially for Track FX state returns with hefty plugins) that can simply be extended with Reaper.StringLimit = 16 * 1024 ** 2 # for 16MB for example.
You can also use Reaper.Execute(""" Say(l[1], l[2]) """, 11, 22) # To send and execute entire mini scripts (with more complex parameters than 11 and 22, anything Python pickle-able), such as tight, fast loops inside Reaper. So, RemoteControl.py can handle anything.
You have to research the Reaper API and test to see if Reaper supports responding or reporting status when it is busy such as during a render. You may have to check the status of an output file, to see if it is openable, to determine that Reaper is done with it and responsive again. The TCP connection may timeout, if an API call takes too long to return, so you may have to send a mini script to return asynchronously, and wait for completion to reconnect again. beyond.Python makes it quite easy to to do Parallel operations, and there are an abundance of other Python libraries for almost any (non realtime) task.
The best way to learn programming is by getting at least a basic good editor such as Sublime Text, and reading the source code for examples, and running lots of small tests for which Python is ideal. I recommend an interactive approach to development, using Say() to output and explore your progress at every step of the program you are writing. Such approach is preferable to classical breakpoint debugging, since you can put Say() into loops outputting any significant variables and study a list of trends, rather than wasting time and your focus tracing things at every irrelevant step. Once you discover how things should work, then you can remove or comment out such feedback and proceed to the next challenge.
Python is a good language for automating and conducting research in pre-existing, high end tools from 3D renderers to AI systems. C++ is the best programming language without any limits, but it takes longer to pick up momentum learning it and building up your own high level methods before you build usable applications.
|
|
|
04-01-2016, 05:33 AM
|
#58
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,690
|
Sounds great !
Thanks a lot,
-Michael
|
|
|
04-30-2016, 12:56 AM
|
#59
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,690
|
Quote:
Originally Posted by beyond
You have to research the Reaper API and test to see if Reaper supports responding or reporting status when it is busy such as during a render.
|
We found that the call that orders the rendering in fact hangs until the rendering is done.
(At least for now) this is OK.
But we still don't seem to find out how any state can be reported. We would like to display the "STOP / PLAYing / RECORDing" state and the current time counter.
-Michael
Last edited by mschnell; 04-30-2016 at 01:04 PM.
|
|
|
12-13-2017, 01:09 PM
|
#60
|
Human being with feelings
Join Date: Feb 2009
Location: Reaper HAS send control via midi !!!
Posts: 4,031
|
Which are coolest examples created with beyond.Reaper?
|
|
|
03-24-2018, 12:36 PM
|
#61
|
Human being with feelings
Join Date: Jul 2017
Posts: 26
|
Is this project still alive? I am VERY much interested in the chord track mentioned. My goal is to replace Rapid Composer functionality natively into Reaper. This project sounds like a possible solution plus there is a ton of Python code available for music composition...
|
|
|
03-24-2018, 01:07 PM
|
#62
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,690
|
What do you mean by "Alive" ?
A friend of mine actively uses Beyond to remote-control Reaper from his Python program.
But I don't know how Beyond can access the data in media files. This might be possible, but this is not what he does.
-Michael
|
|
|
04-09-2018, 07:40 AM
|
#63
|
Human being with feelings
Join Date: Feb 2017
Posts: 54
|
Remote Control CommandID Changing
Hey Beyond!! I have to say, beyond reaper is amazing! It opens up so many possibilities to sound, especially in games that use middleware. I'm a huge fan, and thank you for everything you have done!
I came across an issue where the remote control commandID changed and it took me 30 mins or so to figure it out, haha. Once I changed it, it started working fine again. But, this seems like an issue that can easily be fixed.
I was wondering if you have thought about putting beyond.reaper as an extension so the commandID will never change? Or, if you want to be quick about it, adding it to ReaPack?
If you are busy, I can always add that .py script to ReaPack for you! Just let me know!
If that commandID stays the same, your stuff will NEVER break! And, in the installation process, it's one more thing you don't have to set or worry about. I hate that reaper doesn't have a more solid way to lock in their commandID's through script...
|
|
|
04-09-2018, 11:53 AM
|
#64
|
Human being with feelings
Join Date: May 2006
Posts: 1,030
|
|
|
|
09-14-2018, 02:44 AM
|
#65
|
Human being with feelings
Join Date: Nov 2015
Location: Novosibirsk, Russia
Posts: 40
|
UndoBlock
Can someone halp, if my problem is related to my connection with beyond, or with command themselves?
I'm making simple region namer for samle editing.
here is whole listing:
Code:
import os
import sys
from typing import Tuple
from reaper_python import *
from tkinter import *
from tkinter.ttk import *
from tkinter import filedialog
import beyond.Reaper
# import beyond.Screen
# from items import Item
# from misc import pr
# from serialize import Persist
try:
os.path.basename(sys.argv[0])
except AttributeError:
sys.argv = ('__main__')
except KeyError:
pass
# CONSTANTS
rr_amount = 4
dyn_names = ['pp', 'p', 'f', 'ff']
def get_project_dir():
path = str()
size = 100
with Reaper as r:
r.GetProjectPath(path, size)
return path
# def get_project_dir():
# return 'H:/contrabasses'
def load_func(ext_tuple: Tuple[str]):
name = filedialog.askopenfilename(
initialdir=get_project_dir(),
filetypes=(ext_tuple,
("All Files", "*.*")),
title="Choose a file."
)
return name
def save_func(ext_tuple: Tuple[str]):
name = filedialog.asksaveasfilename(
initialdir=get_project_dir(),
filetypes=(ext_tuple,
("All Files", "*.*")),
title="Choose a file.",
defaultextension=ext_tuple[1]
)
return name
class Mask:
selection = None
instances = list()
def __init__(self, parent: Tk, idx: int, name: str='name',
ext_tuple: str = ("Mask Files", "*.reg_mask")):
cls = self.__class__
if cls.selection is None:
cls.selection = IntVar()
cls.selection.set(0)
cls.instances.append(self)
self.idx = idx
self.ext_tuple = ext_tuple
self.frame = Frame(parent)
self.frame.grid_configure(column=idx, row=0)
self.textvar = StringVar(value=name)
self.text = Entry(self.frame, textvariable=self.textvar)
self.text.grid_configure(column=0, row=1)
self.button = Button(self.frame, text='create',
command=self.create)
self.button.grid_configure(column=0, row=2, sticky='nwes')
self.select = Radiobutton(self.frame, variable=self.selection,
value=idx)
self.select.grid_configure(row=0, sticky='nswe')
@property
def name(self):
return self.textvar.get()
@name.setter
def name(self, val):
self.textvar.set(val)
def create(self):
create(self.textvar.get())
@staticmethod
def save(masks: list):
names = list()
for obj in masks:
names.append(obj.name + '\n')
global save_func
file_n = save_func(("Mask Files", "*.reg_mask"))
with open(file_n, 'w') as f:
f.writelines(names)
@staticmethod
def load(masks: list):
names = list()
for obj in masks:
names.append(obj.name + '\n')
global save_func
file_n = load_func(("Mask Files", "*.reg_mask"))
with open(file_n, 'r') as f:
names = f.readlines()
for inst, name in zip(Mask.instances, names):
inst.name = name
class RR:
selection = None
instances = list()
def __init__(self, parent: Tk, idx: int, name: str='name',
ext_tuple: str = ("Mask Files", "*.reg_mask")):
cls = self.__class__
if cls.selection is None:
cls.selection = IntVar()
cls.selection.set(0)
cls.instances.append(self)
self.idx = idx
self.frame = Frame(parent)
self.frame.grid_configure(column=idx, row=0)
self.textvar = StringVar(value=name)
self.select = Radiobutton(self.frame, variable=self.selection,
value=idx, textvariable=self.textvar)
self.select.grid_configure(row=0, sticky='nswe')
@property
def name(self):
return self.textvar.get()
@name.setter
def name(self, val):
self.textvar.set(val)
class Dyn:
selection = None
instances = list()
def __init__(self, parent: Tk, idx: int, name: str='name',
ext_tuple: str = ("Mask Files", "*.reg_mask")):
cls = self.__class__
if cls.selection is None:
cls.selection = IntVar()
cls.selection.set(0)
cls.instances.append(self)
self.idx = idx
self.frame = Frame(parent)
self.frame.grid_configure(column=idx, row=0)
self.textvar = StringVar(value=name)
self.select = Radiobutton(self.frame, variable=self.selection,
value=idx, textvariable=self.textvar)
self.select.grid_configure(row=0, sticky='nswe')
@property
def name(self):
return self.textvar.get()
@name.setter
def name(self, val):
self.textvar.set(val)
def create(name: str):
with Reaper as r:
r.Undo_BeginBlock2(0)
sel_item = r.GetSelectedMediaItem(0, 0)
# take = r.GetActiveTake(sel_item)
start = r.GetMediaItemInfo_Value(sel_item, "D_POSITION")
end = start + r.GetMediaItemInfo_Value(sel_item, "D_LENGTH")
# start = item_obj._start
# end = start + item_obj._len
ret = r.AddProjectMarker(0, True, start, end, name, -1)
# r.Undo_EndBlock(f'add region with name {name}', -1)
r.Undo_EndBlock2(0, f'add region with name {name}', -1)
return ret
class Prefixes:
def __init__(self, parent, row: int):
self.frame = Frame(parent)
self.row = row
lb = Button(self.frame, text='load postfixes',
command=self.load)
lb.grid_configure(sticky='nswe')
self.vars = list()
self.frame.grid_configure(row=self.row,
sticky='nwse')
def load(self):
file_n = load_func(("postfixes", "*.reg_post"))
names = list()
columns = 15
with open(file_n, 'r') as f:
names = f.readlines()
for idx, name in enumerate(names):
b = Button(self.frame, text=name,
command=lambda name=name: self.create(name))
b.grid_configure(column=idx % columns,
row=idx // columns,
sticky='nwes')
self.frame.grid_configure(row=self.row,
sticky='nwse')
def create(self, name):
main = Mask.instances[Mask.selection.get()].name
rr = RR.instances[RR.selection.get()].name
if rr == 'Null':
rr = ''
else:
rr = f'_{rr}'
dyn = Dyn.instances[Dyn.selection.get()].name
create(f'{main}{rr}_{dyn}_{name}')
@ProgramStartDirect
def Setup():
mw = Tk(className='naming regions')
# mw.option_add('*tearOff', FALSE)
s = Style()
s.theme_use('clam')
names_frame = Frame(mw)
names_frame.grid_configure(row=0)
masks = list()
for idx in range(10):
masks.append(Mask(names_frame, idx))
ls_frame = Frame(mw)
load = Button(ls_frame, text='load names',
command=lambda: Mask.load(masks))
load.grid_configure(column=0, row=0, sticky='nwes')
save = Button(ls_frame, text='save names',
command=lambda: Mask.save(masks))
save.grid_configure(column=1, row=0, sticky='nwes')
ls_frame.grid_configure(column=0, row=1, sticky='nwes')
rrs_frame = Frame(mw)
rrs_frame.grid_configure(row=2)
rrs = list()
for idx in range(rr_amount + 1):
if idx == 0:
name = 'Null'
else:
name = f'RR{idx}'
rrs.append(RR(rrs_frame, idx, name=name))
dyn_frame = Frame(mw)
dyn_frame.grid_configure(row=3)
dyns = list()
for idx, name in enumerate(dyn_names):
dyns.append(Dyn(dyn_frame, idx, name=name))
postfixes = Prefixes(mw, row=4)
mw.call('wm', 'attributes', '.', '-topmost', '1')
mw.mainloop()
The problem is UndoPoints from this function
Code:
def create(name: str):
with Reaper as r:
r.Undo_BeginBlock2(0)
sel_item = r.GetSelectedMediaItem(0, 0)
# take = r.GetActiveTake(sel_item)
start = r.GetMediaItemInfo_Value(sel_item, "D_POSITION")
end = start + r.GetMediaItemInfo_Value(sel_item, "D_LENGTH")
# start = item_obj._start
# end = start + item_obj._len
ret = r.AddProjectMarker(0, True, start, end, name, -1)
# r.Undo_EndBlock(f'add region with name {name}', -1)
r.Undo_EndBlock2(0, f'add region with name {name}', -1)
return ret
works wrong within multiple undos. Instead of deleting region, this thing happens:
https://yadi.sk/i/Eojo5iSqCp_QSQ (image)
And within reloading of saved project all regions made within one script instance opened does the same with all added regions.
|
|
|
09-14-2018, 07:28 AM
|
#66
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
I have to activate my brain on this, but as I remember the r. context access already has Undo management. In the example code like "Modify Selected Tracks.py", there are lines like "Project = r.ProjectSelected" and "Project.UndoName = "Modify Selected Tracks Example"" which set up the automatic undo handling. If those test are running, and most of your code, I think you are mostly there. Otherwise, there may be some recent Reaper API changes to update with.
|
|
|
09-14-2018, 07:54 AM
|
#67
|
Human being with feelings
Join Date: Nov 2015
Location: Novosibirsk, Russia
Posts: 40
|
I found the issue
It was \n character within loaded patterns)
|
|
|
09-14-2018, 07:59 AM
|
#68
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
Quote:
Originally Posted by mschnell
What do you mean by "Alive" ?
A friend of mine actively uses Beyond to remote-control Reaper from his Python program.
|
:-)
I've taken a huge break from music for music videos and graphics programming. But, I hope to get back to music soon, and these tools are essential to me. :-)
|
|
|
09-14-2018, 08:06 AM
|
#69
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
Quote:
Originally Posted by JerContact
Hey Beyond!! I have to say, beyond reaper is amazing! It opens up so many possibilities to sound, especially in games that use middleware. I'm a huge fan, and thank you for everything you have done!
|
Thank you for the kind words and encouragement! Somehow I have missed these message notifications.
I have some unpublished updates to this project. I was also planning on starting an official repository on github. I would welcome your help and integrated distribution of these tools for the Reaper power users. Let me get back into it all (still busy with graphics coding), and we will chat again. :-)
|
|
|
09-14-2018, 08:20 AM
|
#70
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
Quote:
Originally Posted by InLight-Tone
Is this project still alive? I am VERY much interested in the chord track mentioned. My goal is to replace Rapid Composer functionality natively into Reaper. This project sounds like a possible solution plus there is a ton of Python code available for music composition...
|
I have this unpublished project "beyond patterns", and we have been using it internally. Basically, it allows you to create styles like on Tyros keyboards, even converting psr style files is quite easy, but you can make your own, record and edit them right inside Reaper like regular midi. There is a chord lead track, that recognizes chords, inversions, bass, scale, lead dynamics, and it can even be driven real time/live. And there are following, connected tracks that take the patterns as regular midi clips editable in reaper. They have special midi channels for different types of chord, bass and velocity mappings. You can make an entire orchestra follow your chords, or a super arp synth stack. It also has a special mapper for drums, and guitars. It is implemented in only JS with a simple GUI, but it works stably and very musically, just as I have dreamed of it for so long. I just have to see if I want to share my secret sauce! :-)
|
|
|
09-14-2018, 08:26 AM
|
#71
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,690
|
Quote:
Originally Posted by beyond
I hope to get back to music soon,
|
GREAT !!!
-Michael
|
|
|
09-14-2018, 08:27 AM
|
#72
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,690
|
Quote:
Originally Posted by beyond
I was also planning on starting an official repository on github.
|
Great again !
Hoping for seeing Beyond in ReaPack as a result of this ...
-Michael
|
|
|
09-14-2018, 09:15 AM
|
#73
|
Human being with feelings
Join Date: Jun 2007
Location: Palm Beach FL
Posts: 265
|
I've finally become very C++ focused in the recent years, and hope to convert many of my projects (from JS, Reaktor, Kontakt) into polished native versions.
Python is still my favorite dynamic, scripting language. It is clean, succinct, capable of modifying itself for seamless wrapping of other languages, apis or remoting. It is also becoming a powerful standard in the production industry for interconnecting powerful program, Blender, Nuke, Maya, Cinema 4D, AI and Science libraries, Reaper, Unreal Engine.
For the past 2 years, I have been busy with video processing, compositing, GPU algorithms, and Unreal Engine:
https://www.youtube.com/watch?v=oulEoN9Z15g
But it is all meant to come back to music. :-)
|
|
|
09-14-2018, 01:27 PM
|
#74
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,690
|
Quote:
Originally Posted by beyond
|
Unbelievable !
-Michael
|
|
|
11-15-2021, 03:34 AM
|
#75
|
Human being with feelings
Join Date: May 2017
Location: Cornwall, UK
Posts: 17
|
Quote:
Originally Posted by beyond
beyond.Reaper V26
beyond.Reaper is a small API that brings both simpler and unlimited
programming to Reaper Python developers.
|
Is this project still a thing or is the ReaScript API now capable of doing this ? Last update seems to be years ago and there must have been a lot of features added to the Reaper API since then. Cheers.
__________________
Never Fade Away !
|
|
|
11-15-2021, 11:38 AM
|
#76
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,690
|
A friend of mine extensively used Beyond in a project of his some years ago,
In Theory it should be possibel do add new Reaper API features to Beyond.
But seemingly ReaPy took over...
-Michael
|
|
|
11-16-2021, 04:23 AM
|
#77
|
Human being with feelings
Join Date: May 2017
Location: Cornwall, UK
Posts: 17
|
__________________
Never Fade Away !
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 04:53 PM.
|