|
|
|
12-02-2009, 11:05 AM
|
#121
|
Human being with feelings
Join Date: Aug 2009
Posts: 210
|
BCR2000
Thanks for the advice. I wasn't sure about action codes, but I see now how that works, many thanks.
I definitely have a call to IMPAPI for that OnPauseButtonEx function, but I'll go back and have another fiddle shortly. The control surface code examples I have all seem to use CSurf_OnStop which isn't really ideal. Actually, perhaps there's an action for pause. I'll check that, because if so, that'd be another way to do it.
Meanwhile I have all the FX parameter control stuff fully working, so now the surface can control every parameter of every FX insert on a track, full duplex.
This pretty much leaves the scrubbing stuff and the send stuff, and then I'm done and will start the unit testing phase, using some Reaper test projects, before I'm comfortable releasing it.
Meanwhile one quirk I did find is that some FX parameters - ReaComp's threshold, for example, report that their max value is 2.0 but in fact return a max value up to 2.5. Because Sonar normalises all parameters of everything to the range 0.... 1.0, the surface code is designed to work with that assumption and the external console UI, in particular, won't expect or cope with out-of-range values. So I am now normalising FX parameters on the way in and denormalising them on the way out. However, because Reacomp is telling fibs on me, the surface will not allow the threshold to be set all the way to the maximum possible value because it has scaled the values internally by Reacomp's claimed maximum value and will clip anything out of range to avoid internal failures. A minor quirk, though.
Summary of functionality now completely ported:
Control volume, pan, mute, solo for all tracks
Bank switching forward/backward by 8 tracks or 1 track at a time
Full transport control (subject to caveat regarding pause at this stage)
Select any track and/or make that track the leftmost controlled track on the surface
Control up to 8 FXs on any track, with up to 64 parameters per FX. Page switching on the control surface allows you to switch quickly between FXs and back to the main console view.
Coarse and fine parameter control for any param.
Full console UI displays a BCR2000 mimic console with scribble strips showing all control assignments, as well as the currently-selected FX, if applicable.
To be ported:
Control pan and gain for channel send 1, if present
Transport scrubbing
Alternate transport functions as discussed previously
|
|
|
12-04-2009, 10:40 AM
|
#122
|
Human being with feelings
Join Date: Aug 2009
Posts: 210
|
BCR2000 control surface
Ok, all functionality is now complete (even scrubbing, which turned out to be simpler than I had expected).
Once again, many many thanks for everyone's help. I understand the Reaper API much better now and it is great to have this working; I really missed being able to use my BCR2000 from Reaper and I hope others will find this plugin as useful as I have.
If time permits I will update some of the Wiki entries for the APIs with what I now know, so that others following in my footsteps will maybe not bug you guys so much with endless questions.
As the Sonar version is already hosted on Sourceforge, I'll be updating that project with a new version which will support both Sonar and Reaper from the one project tree, I think. I'll post back to the main forum when this has been done, probably tomorrow. I would like to rack up a little more testing, although at present it looks pretty rock solid. Also I need to create Reaper-specific install instructions, which will be just slightly different from Sonar.
HOWEVER
There are two bugs, I believe, with Reaper, that I uncovered and at present have no workaround for
One is minor, the other a little less so.
The first (minor) one is that programmatically changing the send volume or pan for a track works fine, and if you bring up the full send dialogue with the sliders, you can see them update correctly. However, if only the small send UI is displayed in the track, it does not update when the send volume or pan is changed programmatically. I could not see any obvious notification API that I should call, other than changing volume and pan as shown in the code snippet below (for volume). It looks like a screen redraw issue.
(fVal is a normalised parameter value as a float (0..1). The surface code manages all parameters in this way)
TRACK_VOL is "D_VOL"
// Reaper will return a NULL pointer if there is no send
// This is used as a guard to avoid attempting to update or retrieve non
// existent send information. I could not find any API to indicate
// the send count for a track, hence this approach.
pdVal = (double *) GetSetTrackSendInfo(tr, 0, 0, TRACK_VOL, NULL);
if (pdVal != NULL)
{
cVal = (unsigned char)(fVal * 127); //back to range 0-127
dVal=charToVol(cVal);
GetSetTrackSendInfo(tr, 0, 0, TRACK_VOL,&dVal);
}
break;
The second, more significant issue, is that the master track (track 0) doesn't behave consistently regarding getting/setting track properties. In particular, mute and solo can be programmatically set but the following code snippet, which works for all non-master tracks, to retrieve the mute status, always returns FALSE for both mute or solo (mute code shown)
(tr was retrieved for track 0, the master track, and is a valid reference)
bVal=*(bool *)GetSetMediaTrackInfo(tr,TRACK_MUTE,NULL);
*fVal = (float)bVal; // return
where TRACK_MUTE is "B_MUTE"
Therefore when mute or solo are toggled by clicking them in Reaper, the surface doesn't get the actual value back, although the same code works perfectly for tracks 1 onwards (regular tracks). However the code to SET mute and solo works correctly both for master and regular tracks.
In addition,programmatically attempting to set the master track's send volume and pan for the first send fails because
pdVal = (double *) GetSetTrackSendInfo(tr, 0, 0, TRACK_VOL, NULL);
will always return a NULL pointer, even though there is a send for the master track. This is inconsistent with the behaviour for the main tracks, where if a send exists, pdVal will be returned non-NULL and can be dereferenced to retrieve the send volume.
Note that tr is a valid MediaTrack reference at this point
Just thought I'd mention these in case anyone cares to have a look at them. They are obviously not show-stoppers and I will mention them as known issues in the release notes for the surface plugin, along with the issue of some plugin parameters misreporting the min and max values for the parameter, which causes the surface to be unable to control the full parameter range.
Otherwise Reaper has been rock-solid during testing, even with some fairly complex sample projects.
|
|
|
12-04-2009, 11:32 AM
|
#123
|
Human being with feelings
Join Date: Aug 2009
Posts: 210
|
BCR2000 control surface
I have managed to code around the master mute problem by using the callback SetSurfaceMute to keep the surface in sync, however, there is a second bug with master solo in this context. If you create two callbacks like this
void SetSurfaceMute(MediaTrack *trackid, bool mute)
{
int id;
id=CSurf_TrackToID(trackid,0);
if (id == 0)
{
// master track, for which we cannot otherwise retrieve status
m_bMasterMute = mute;
}
}
void SetSurfaceSolo(MediaTrack *trackid, bool solo)
{
int id;
id=CSurf_TrackToID(trackid,0);
if (id == 0)
{
// master track, for which we cannot otherwise retrieve status
m_bMasterSolo = solo;
}
}
then you will find that if you create a project with one track plus the master, then toggling master solo doesn't pass back the correct value to the SetSurfaceSolo callback, and furthermore, toggling track 1 solo will cause the callback to erroneously indicate that master solo was toggled (as well as track 1 solo).
I also noted that toggling either mute or solo causes both callbacks to be triggered, which is not what I would have expected, either.
Still, master solo is pretty much a useless function so I have simply disengaged the control surface from this entirely.
|
|
|
12-06-2009, 10:45 AM
|
#124
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
Would it be possible to implement the functionality implied by these made-up API calls below?
It wouldn't be done precisely like this I suspect, as I don't think we have a MIDINote object, so I'm asking for the functionality rather than these APIs.
Code:
int GetNumSelectedMidiNotes(MIDIEditor)
MIDINote* GetSelectedMidiNote(MIDIEditor,Index)
bool SetSelectedMidiNote(MIDIEditor, MIDINote* mn, param char, newvalue int)
"param" would be things like POS, PITCH, DURATION, VOLUME
__________________
Mike Lacey, Leicestershire, UK
|
|
|
12-15-2009, 02:55 AM
|
#125
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
Translate between .rpp and raw MIDI data
How do I translate between .rpp MIDI data and raw MIDI data?
__________________
Mike Lacey, Leicestershire, UK
|
|
|
12-15-2009, 07:01 AM
|
#127
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
Ooh look at that, can open and worms just everywhere.
Thanks Jeffos, it's not quite the single function call conversion I had in mind but I will look at it.
__________________
Mike Lacey, Leicestershire, UK
|
|
|
12-15-2009, 03:40 PM
|
#128
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
... about the MIDI_eventlist approach i obviously forget http://forum.cockos.com/showthread.php?t=45430, with a cool example posted by rome. MIDI looks like a brand new place for extensions.. but padre & rome seems in the starting block for that!
Quote:
Originally Posted by MikeLacey
Ooh look at that, can open and worms just everywhere.
|
What I meant above is that, if I was sure of what I'm doing I won't use the MIDI_eventlist appoach!
I've started digging into midi too.. but directly working with chunks. It's ok for READ and it can answer some your Q's above (see bellow). The few WRITE tests I did: removing cc123, empying, .. seemed also safe but I stopped there: it seemed "too easy to be true" (and schwa didn't talk about that in the thread above, where midi selection is also aksed)
So the few things I can answer you about your Q's in the previous posts are bellow. I only talk about my "C" point of view here, but HTH!
Inside a take's chunk (GetSetObjectState(MediaItem*)), the midi event list looks like:
Code:
e 4781 90 0e 01
e 240 80 0e 00
E 269 90 10 7f
E 240 80 10 00
E 240 b0 7b 00
here 5 events: 2* note on/off on channel 1 and the famous "all notes off" (cc123 on channel 1) that ends midi takes.
- 1st token: dunno yet if it can be different from "e/E". however, lower/upper case distinguish selected events (one of yours Qs)
- 2nd token: *offset* with the previous event or with the begining of the take for the 1st event. it's not really a timing (dunno the english wording..), in short the unit is 1/2560T, e.g. 240 = 1/16. note: working with offset can be nice (copy/paste,..)
- 3 following "bytes" = classical hexa midi coding (one of your Qs, if we're talking about the same "raw data")
beware! in the middle of this gentle midi event list you can face a wicked base64 sysex!
Code:
<X 286306 0
8AECAwQF9w==
>
note: does not work with external midi source file. any tips to complete/correct these info would be appreciated..
|
|
|
12-16-2009, 03:51 AM
|
#129
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
[EDIT July 2010] ignore this post, past issues: I was talking of chunk updates not taken into account (fixed in v3.22) and multi-threading issues (fixed in v3.31).
[/EDIT]
____
I have a last question for this "how do I" thread: I know I can update things at anytime (AWESOME!), but "how do I" am sure I can edit any chunk, anywhere ?
this is a bit scaring for the moment: for eg, I guess that if MIDI_eventlist exists, it's the recommended way for editing MIDI ? (or just here for frame offset processing ?) other example: I can remove the famous cc123 I was talking about in my previous post but the native ME forbidds it, there shoud be good reasons.. (edit) hmm.. i think i know them
so, the question is: can we edit any chunk, anywhere ? just a short "yes/no" reply would be much, much appreciated... if it's "no" a very short/rough list of unrecommended chunk updates would be much appreciated too. Right now, I feel a bit "limited" without the answers to these Q's.
[edit] removed, useless
Last edited by Jeffos; 07-03-2010 at 02:35 AM.
Reason: fixed issues
|
|
|
12-16-2009, 04:08 AM
|
#130
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
Thank you Jeff, much appreciated...
I'm starting to document "State Chunks" (a term I made up about an hour ago) in the Wiki, Track/Item/Envelope State Chunks, on the appropriate GetSet*State() page. Do feel free to correct my stupid and obvious mistakes.
__________________
Mike Lacey, Leicestershire, UK
|
|
|
12-16-2009, 06:42 AM
|
#131
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
Mike!! the wiki has grown a lot! awesome work you're doing about the chunks, really (this would have saved me many reverse engineerings a few time ago). There're little errors about midi decoding, I didn't detailed it 'cause I thought you were aware of that (there's not only notes in the chunks but also CC events, pitch events, etc..) => I'll update the wiki with the basics on how to decode the "3 'bytes' of classical hexa midi coding" i was talking about.
note: in case a knowledgeable people read that, please also have a look the Q raised in post 129.. last Q, I promise
|
|
|
12-16-2009, 12:41 PM
|
#132
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
Thx Jeff,
Would be excellent if you could add to/edit the Item State Chunk MIDI stuff...
Documenting stuff like this is the way I learn it. You can pretty much follow my current interest by what I'm writing about - as long as I get it right it works out well for everyone.
[fingers in ears and singing "Na na na. I'm Not Listening"]
[wicked base64 sysex
[/fingers in ears]
Too scary, I'll think about sysex in a bit...
About the 2nd token though "*offset* with the previous event ... in short the unit is 1/2560T, e.g. 240 = 1/16. note:"
I get that it's an offset from the previous thing but.. Eh? What units is it in? 1/2560T?
Mike
P.S. The knowledgable person thing - I doubt anyone's tried it that intensiveley. My (cunning) plan is to to update objects using State Chunks until they start to smoke from friction through the air - and see what happens.
__________________
Mike Lacey, Leicestershire, UK
|
|
|
12-16-2009, 01:39 PM
|
#133
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
Quote:
Originally Posted by MikeLacey
Thx Jeff,
Would be excellent if you could add to/edit the Item State Chunk MIDI stuff...
|
er.. yeah, ok, tomorrow!
Quote:
Originally Posted by MikeLacey
Documenting stuff like this is the way I learn it. You can pretty much follow my current interest by what I'm writing about - as long as I get it right it works out well for everyone.
|
yeah I see! With SWS/Xen's open source + that wiki (+ that forum), I think that people now have some serious entry points to get started
Quote:
Originally Posted by MikeLacey
[fingers in ears and singing "Na na na. I'm Not Listening"]
[wicked base64 sysex
[/fingers in ears]
Too scary, I'll think about sysex in a bit...
|
i hope i didn't made another english error that can be misinterpreted.. if so, not intended.
sysex is a kind of "custom" MIDI message that carries a variable number of bytes. this is mostly intended for midi hardware, not much for VST (e.g. bulk of settings, text for lcd, anything). you don't need to know much about it, execpt that you must be aware that you indeed have to ignore it! (eg parser). base64 is a codage that allows compacting data, sysex can be very, very big. there's a coder/decoder in SWS open source.
Quote:
Originally Posted by MikeLacey
About the 2nd token though "*offset* with the previous event ... in short the unit is 1/2560T, e.g. 240 = 1/16. note:"
I get that it's an offset from the previous thing but.. Eh? What units is it in? 1/2560T?
|
ok, this one gonna be hard for me in english but.. i try! this is a temporal information that is not related to time (yeah.. I know) this is related to beats: this is a kind of pourcentage of a beat.
reminder: what I've said might be wrong, incomplete, bla-bla.
P.S. The smoke from friction thing - ha ha! same here.. let me know if something smokes in england.. for the moment all seems ok, on this side.
|
|
|
12-16-2009, 01:51 PM
|
#134
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
Quote:
Originally Posted by Jeffos
.. hope i didn't made another english error that can be misinterpreted ..
|
No., not at all - I'm just putting off thinking about sysex messages until I get the other stuff nailed down.
The offset field, I get that it's a position relative to the last event rather than an absolute position in the timeline (and your english is fine, just by the way).
I'm sorry to be thick about this... If 1/240 = 1/16th note -- that means that 16 * 240 (that's 3840) equals one whole note?
Mike
__________________
Mike Lacey, Leicestershire, UK
|
|
|
12-16-2009, 02:00 PM
|
#135
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
1/16 -> 240. the full note is 2.6041666666666666666666666666667e-4 ("unit") * 240 * 16 = 1
|
|
|
12-16-2009, 02:03 PM
|
#136
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
Bloody hell...
Ok, gotcha
__________________
Mike Lacey, Leicestershire, UK
|
|
|
12-17-2009, 02:32 AM
|
#137
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
Mike, I was in a rush when I did my last post, so first, thanks for my english skills, 1 beat=3840 that's it, I put that formula to be sure we're talking about the same thing (in french: "une règle de trois"). Wiki update done: cosmetic (but needed) updates 'cause someone did the job when I was sleeping! I'm impressed. There're other chunks I know quite well, I'll have a look, but later
About the smoking REAPER: in other words, if all is OK, I wonder why there's not only -one- function in the API: GetSetObjectState (ok, I exaggerate a bit but you can get the idea..).
|
|
|
12-17-2009, 03:27 AM
|
#138
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
"une règle de trois" I like it, and yes.
Thanks for the Wiki updates, really helpful to my understanding, and kudos to you for picking your way through Wiki nested tables (not the nicest implementation I've ever seen)
Actually there was just the one GetSetObjectState function at one time and it was split into three (and GetSetObjectState hidden from ReaScript) after discussions between Xen and schwa - neither Xen or I could sucessfully call GetSetObjectState from a script so schwa helpfully got out his hammer and ajusted some bits here and there.
__________________
Mike Lacey, Leicestershire, UK
|
|
|
12-17-2009, 06:39 AM
|
#139
|
Human being with feelings
Join Date: Sep 2009
Posts: 107
|
Edit: nevermind, my bad.
|
|
|
12-17-2009, 08:48 AM
|
#140
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
You're welcome Mike! I hope i'll be able to participate a bit more...
Quote:
Originally Posted by MikeLacey
Actually there was just the one GetSetObjectState function at one time and it was split into three (and GetSetObjectState hidden from ReaScript) after discussions between Xen and schwa - neither Xen or I could sucessfully call GetSetObjectState from a script so schwa helpfully got out his hammer and ajusted some bits here and there.
|
no, that's not really why I meant: for scripts, GetSetObjectState was indeed splitted because of the pointer issue.
What I meant is that I think chuncks are the representation of Reaper's buisness model, ie the core stuff. GetSetObjectState -or its "children" for reaScript- can replace many functions of the API, for (random) example, we don't really need things like CountTrackMediaItems or CountTakes: we can do the job through GetSetObjectState, I hope these functions are not just wrappers themselves, that they don't process chunks themselves (if so, that would impact the way I code). Here, the more I swim in the API, the less functions I use..
|
|
|
12-17-2009, 02:20 PM
|
#141
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
Ah.. I see what you mean.
Yes, I agree with you, I like the chunks stuff, very flexible - Xen on the other hand much prefers the more precise and focussed API functions.
__________________
Mike Lacey, Leicestershire, UK
|
|
|
12-23-2009, 05:27 PM
|
#142
|
Human being with feelings
Join Date: Sep 2009
Posts: 107
|
Does anyone know how to make the Reaper API calls available to imported modules in python?
The reason I'm asking is I want to put all the API-specific functions in a separate module and then call them via another module (e.g. the main script that reaper runs). Is this possible?
|
|
|
12-24-2009, 12:11 AM
|
#143
|
Human being with feelings
Join Date: Dec 2009
Location: Wellington, NZ
Posts: 300
|
Quote:
Originally Posted by Malevol3nt
Does anyone know how to make the Reaper API calls available to imported modules in python?
The reason I'm asking is I want to put all the API-specific functions in a separate module and then call them via another module (e.g. the main script that reaper runs). Is this possible?
|
I don't think it is. I tried to wrap some of the api calls in some of python's OO goodness and store the wrapper class in a seperate module. Reaper couldn't see the function calls. I think it must parse the python file and look for RPR_ <function name> and then replace it with code to actually perform the api call.
One thing you could do is store your class which calls the api in a module and when you instantiate the class in your main script, you pass in a callback function to your class which resides in your main script. You then pass a function identifier and your arguments to the callback. That way you can still have your code nicely stored in modules for reuse with the trade off being a big callback function at the start of every script.
|
|
|
12-24-2009, 12:26 AM
|
#144
|
Human being with feelings
Join Date: Dec 2009
Location: Wellington, NZ
Posts: 300
|
An example:
MediaItem.py
Code:
class MediaItem:
ParamMute = "B_MUTE"
ParamLoopSource = "B_LOOPSRC"
ParamAllTakesPlay = "B_ALLTAKESPLAY"
ParamSelected = "B_UISEL"
ParamVolume = "D_VOL"
ParamPosition = "D_POSITION"
ParamSnapOffset = "D_SNAPOFFSET"
def __init__(self, hMediaItem, callback):
self.hMediaItem = hMediaItem
self.callback = callback
def __repr__(self):
str_repr = ""
str_repr += "Muted: " + str(self.Muted) + "\n"
str_repr += "Loop Source: " + str(self.LoopSource) + "\n"
str_repr += "All Takes Play: " + str(self.AllTakesPlay) + "\n"
str_repr += "Selected: " + str(self.Selected) + "\n"
str_repr += "Volume: " + str(self.Volume) + "\n"
str_repr += "Position: " + str(self.Position) + "\n"
str_repr += "SnapOffset: " + str(self.SnapOffset) + "\n"
return str_repr
def __get_api_call(self, param):
return self.callback("GetMediaItemInfo_Value", (self.hMediaItem, param))
def __set_api_call(self, param, new_value):
self.callback("SetMediaItemInfo_Value", (self.hMediaItem, param, new_value))
def __set_bool(self, param, bValue):
if bValue:
self.__set_api_call(param, 1.0)
else:
self.__set_api_call(param, 0.0)
@property
def Muted(self):
return self.__get_api_call(MediaItem.ParamMute) == 1.0
@Muted.setter
def Muted(self, value):
self.__set_bool(MediaItem.ParamMute, value)
@property
def LoopSource(self):
return self.__get_api_call(MediaItem.ParamLoopSource) == 1.0
@LoopSource.setter
def LoopSource(self, value):
self.__set_bool(MediaItem.ParamLoopSource, value)
@property
def AllTakesPlay(self):
return self.__get_api_call(MediaItem.ParamAllTakesPlay) == 1.0
@AllTakesPlay.setter
def AllTakesPlay(self, value):
self.__set_bool(MediaItem.ParamAllTakesPlay, value)
@property
def Selected(self):
return self.__get_api_call(MediaItem.ParamSelected) == 1.0
@Selected.setter
def Selected(self, value):
self.__set_bool(MediaItem.ParamSelected, value)
@property
def Volume(self):
return self.__get_api_call(MediaItem.ParamVolume)
@Volume.setter
def Volume(self, value):
return self.__set_api_call(MediaItem.ParamVolume, value)
@property
def Position(self):
return self.__get_api_call(MediaItem.ParamPosition)
@Position.setter
def Position(self, value):
return self.__set_api_call(MediaItem.ParamPosition, value)
@property
def SnapOffset(self):
return self.__get_api_call(MediaItem.ParamSnapOffset)
@SnapOffset.setter
def SnapOffset(self, value):
return self.__set_api_call(MediaItem.ParamSnapOffset, value)
MuteSelectedMediaItem.py
Code:
import sys
# add path to my scripts folder
sys.path.append("c:\\ReaperScripts")
from MediaItem import MediaItem
def ReaperCB(func_name, args):
if func_name == "GetMediaItemInfo_Value":
return RPR_GetMediaItemInfo_Value(args[0], args[1])
if func_name == "SetMediaItemInfo_Value":
RPR_SetMediaItemInfo_Value(args[0], args[1], args[2])
itemHandle = RPR_GetSelectedMediaItem(0,0)
myItem = MediaItem(itemHandle, ReaperCB)
myItem.Muted = True
|
|
|
12-24-2009, 03:30 AM
|
#145
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
Quote:
Originally Posted by Malevol3nt
Does anyone know how to make the Reaper API calls available to imported modules in python?
The reason I'm asking is I want to put all the API-specific functions in a separate module and then call them via another module (e.g. the main script that reaper runs). Is this possible?
|
Similar issue in Perl, you can't call RPR API functions in a Perl Module
__________________
Mike Lacey, Leicestershire, UK
|
|
|
12-24-2009, 07:28 AM
|
#146
|
Human being with feelings
Join Date: Sep 2009
Posts: 107
|
Quote:
Originally Posted by fingers
An example:
MediaItem.py
Code:
class MediaItem:
ParamMute = "B_MUTE"
ParamLoopSource = "B_LOOPSRC"
ParamAllTakesPlay = "B_ALLTAKESPLAY"
ParamSelected = "B_UISEL"
ParamVolume = "D_VOL"
ParamPosition = "D_POSITION"
ParamSnapOffset = "D_SNAPOFFSET"
def __init__(self, hMediaItem, callback):
self.hMediaItem = hMediaItem
self.callback = callback
def __repr__(self):
str_repr = ""
str_repr += "Muted: " + str(self.Muted) + "\n"
str_repr += "Loop Source: " + str(self.LoopSource) + "\n"
str_repr += "All Takes Play: " + str(self.AllTakesPlay) + "\n"
str_repr += "Selected: " + str(self.Selected) + "\n"
str_repr += "Volume: " + str(self.Volume) + "\n"
str_repr += "Position: " + str(self.Position) + "\n"
str_repr += "SnapOffset: " + str(self.SnapOffset) + "\n"
return str_repr
def __get_api_call(self, param):
return self.callback("GetMediaItemInfo_Value", (self.hMediaItem, param))
def __set_api_call(self, param, new_value):
self.callback("SetMediaItemInfo_Value", (self.hMediaItem, param, new_value))
def __set_bool(self, param, bValue):
if bValue:
self.__set_api_call(param, 1.0)
else:
self.__set_api_call(param, 0.0)
@property
def Muted(self):
return self.__get_api_call(MediaItem.ParamMute) == 1.0
@Muted.setter
def Muted(self, value):
self.__set_bool(MediaItem.ParamMute, value)
@property
def LoopSource(self):
return self.__get_api_call(MediaItem.ParamLoopSource) == 1.0
@LoopSource.setter
def LoopSource(self, value):
self.__set_bool(MediaItem.ParamLoopSource, value)
@property
def AllTakesPlay(self):
return self.__get_api_call(MediaItem.ParamAllTakesPlay) == 1.0
@AllTakesPlay.setter
def AllTakesPlay(self, value):
self.__set_bool(MediaItem.ParamAllTakesPlay, value)
@property
def Selected(self):
return self.__get_api_call(MediaItem.ParamSelected) == 1.0
@Selected.setter
def Selected(self, value):
self.__set_bool(MediaItem.ParamSelected, value)
@property
def Volume(self):
return self.__get_api_call(MediaItem.ParamVolume)
@Volume.setter
def Volume(self, value):
return self.__set_api_call(MediaItem.ParamVolume, value)
@property
def Position(self):
return self.__get_api_call(MediaItem.ParamPosition)
@Position.setter
def Position(self, value):
return self.__set_api_call(MediaItem.ParamPosition, value)
@property
def SnapOffset(self):
return self.__get_api_call(MediaItem.ParamSnapOffset)
@SnapOffset.setter
def SnapOffset(self, value):
return self.__set_api_call(MediaItem.ParamSnapOffset, value)
MuteSelectedMediaItem.py
Code:
import sys
# add path to my scripts folder
sys.path.append("c:\\ReaperScripts")
from MediaItem import MediaItem
def ReaperCB(func_name, args):
if func_name == "GetMediaItemInfo_Value":
return RPR_GetMediaItemInfo_Value(args[0], args[1])
if func_name == "SetMediaItemInfo_Value":
RPR_SetMediaItemInfo_Value(args[0], args[1], args[2])
itemHandle = RPR_GetSelectedMediaItem(0,0)
myItem = MediaItem(itemHandle, ReaperCB)
myItem.Muted = True
|
I see I am not the only one using "sys.path.append". Why is it that Python can't load modules located in directories that have empty spaces in them?
I mean, if I put a couple of my modules in a folder such as "C:\Scripts", importing one module from the other works fine. But if they're located in a folder with spaces, e.g. "C:\ Program Files\Reaper\Scripts" then Python simply won't be able to load any other modules in that same directory unless I specifically add "sys.path.append" in my main module.
Thanks for those pieces of code, I'll be studying them shortly. Cheers!
Edit: Btw instead of using a predefined folder location as you do, I use this:
Code:
import os
work_directory = (os.getcwd() + "\\Scripts\\")
import sys
sys.path.append(work_directory)
os.getcwd() returns the Reaper directory (since it's calling python in the first place). I think there's an API call for that as well, but I'm using this one.
Last edited by Malevol3nt; 12-25-2009 at 04:05 PM.
|
|
|
12-26-2009, 08:32 PM
|
#147
|
Human being with feelings
Join Date: Dec 2009
Location: Wellington, NZ
Posts: 300
|
Quote:
Originally Posted by Malevol3nt
I see I am not the only one using "sys.path.append". Why is it that Python can't load modules located in directories that have empty spaces in them?
|
Can't say I have come across this. I think python will look in the cwd for a module and then anything in your python path.
Quote:
Edit: Btw instead of using a predefined folder location as you do, I use this:
Code:
import os
work_directory = (os.getcwd() + "\\Scripts\\")
import sys
sys.path.append(work_directory)
os.getcwd() returns the Reaper directory (since it's calling python in the first place). I think there's an API call for that as well, but I'm using this one.
|
The current working directory is generally the directory you saved your reaper project in, or opened it from. I have all my scripts in c:\ReaperScripts, because it is easy to find my scripts this way, and less typing for sys.path.append :-). I should put them somewhere different like you have.
One thing you could do (as C:\Program Files\Reaper is in your path already) is have a __init__.py file in c:\Program files\Reaper\Scripts. That way you can do something like
Code:
from Scripts.somemoduleinscripts import something
where you'd have a file c:\Program files\Reaper\Scripts\somemoduleinscripts.py with a python object called something.
|
|
|
12-27-2009, 07:34 AM
|
#148
|
Human being with feelings
Join Date: Sep 2009
Posts: 107
|
Quote:
Originally Posted by fingers
The current working directory is generally the directory you saved your reaper project in, or opened it from.
|
I did not know that. Thanks!
Quote:
Originally Posted by fingers
One thing you could do (as C:\Program Files\Reaper is in your path already) is have a __init__.py file in c:\Program files\Reaper\Scripts. That way you can do something like
Code:
from Scripts.somemoduleinscripts import something
where you'd have a file c:\Program files\Reaper\Scripts\somemoduleinscripts.py with a python object called something.
|
Ahh, so I have to type in the folder name as well when I'm importing. I've already tried using __init__.py and importing a module, and it didn't work. But I never thought of adding the directory name before the module (e.g. import Scripts.modulename). It works fine now.
Thanks again!
|
|
|
12-27-2009, 02:42 PM
|
#149
|
Human being with feelings
Join Date: Apr 2009
Location: GWB
Posts: 76
|
ShowConsoleMsg()?
Hi-
Can I use ShowConsoleMsg() from a VST to log debug information? I know I don't want to do this from the DSP loop, but outside?
Thx, Charles
(Later...)
Hey, works fine! I had to cast the function name string for OSX:
Code:
*(long *)&ShowConsoleMsg = audioMaster(NULL,0xdeadbeef,0xdeadf00d,0,(void *)"ShowConsoleMsg",0.0);
Last edited by cturner; 12-27-2009 at 04:12 PM.
|
|
|
01-11-2010, 10:27 AM
|
#150
|
Human being with feelings
Join Date: Dec 2006
Location: UK
Posts: 789
|
How do I get at the tempo envelope using ReaScript?
My first thought was that it belonged to the MASTER track - but I can't see how to get a reference to the MASTER track.
I'm assuming I could do this by making the envelope visible, selecting it and then calling GetSelectedEnv() - but that's cheating.
__________________
Mike Lacey, Leicestershire, UK
|
|
|
05-10-2010, 09:29 AM
|
#151
|
Human being with feelings
Join Date: May 2010
Posts: 8
|
State in Mem Between Actions
I'm prototyping a set of Actions in Reascript using Python. It'd be useful for me to be able to allows these scripts to share some info in memory with each between invocations. (I could use files if there's no other choice, but prefer not to.)
Are there any places where a script can park some info in Repaer and another can get it? Globally or in the project would work. (Track if I had to.) I'd be happy w/ raw storage or could massage my state into an XML chunk or string.
|
|
|
05-11-2010, 12:32 AM
|
#152
|
Human being with feelings
Join Date: Nov 2009
Location: Perth, W.A.
Posts: 1,708
|
Quote:
Originally Posted by jweite@yahoo.com
I'm prototyping a set of Actions in Reascript using Python. It'd be useful for me to be able to allows these scripts to share some info in memory with each between invocations. (I could use files if there's no other choice, but prefer not to.)
Are there any places where a script can park some info in Repaer and another can get it? Globally or in the project would work. (Track if I had to.) I'd be happy w/ raw storage or could massage my state into an XML chunk or string.
|
sharedmem = mmap.mmap(0, 16384, "GlobalSharedMemory")
also this
http://docs.python.org/library/mmap.html
Hope that helps.
|
|
|
05-11-2010, 01:14 AM
|
#153
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by Amazed
|
This solution somehow smells wrong...The original poster isn't talking about sharing memory between scripts/processes but keeping state between runs of the same script. (What happens in Reaper currently is that nothing remains of the script's state once it has been run.)
I ended up experimenting simply with disk files to save/restore the state. It isn't such a bad solution in the end, especially if nothing more suitable exists...
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
05-11-2010, 01:33 AM
|
#154
|
Human being with feelings
Join Date: Nov 2009
Location: Perth, W.A.
Posts: 1,708
|
Quote:
Originally Posted by Xenakios
This solution somehow smells wrong...The original poster isn't talking about sharing memory between scripts/processes but keeping state between runs of the same script. (What happens in Reaper currently is that nothing remains of the script's state once it has been run.)
I ended up experimenting simply with disk files to save/restore the state. It isn't such a bad solution in the end, especially if nothing more suitable exists...
|
All entirely true besides which even if you had a handle to shared mem one would still need to persist the handle somehow. In which case a file becomes your only possible way at present. A simple extension to the api could of course make this quite easy by giving you access to, perhaps a named list of values so you could persist values between scripts that way.
|
|
|
05-12-2010, 03:39 AM
|
#155
|
Human being with feelings
Join Date: May 2010
Posts: 8
|
Quote:
Originally Posted by Amazed
All entirely true besides which even if you had a handle to shared mem one would still need to persist the handle somehow. In which case a file becomes your only possible way at present. A simple extension to the api could of course make this quite easy by giving you access to, perhaps a named list of values so you could persist values between scripts that way.
|
I've shied away from using files for this, because I need very fast response to these actions. The prototypes weren't awful, so I may end up going with that approach after all and seeing how it works out.
The shared memory approach is a good idea - I was thinking in the Reaper box instead of in the Python box. (I'm an old C++/Java guy learning Python on the fly). As pointed out, I'll still need to keep the handle somewhere unless I do memory mapped files, which I suspect take as long to open as plain-old-files (though may provide benefits if I'm doing random-access of the contents.)
I'm starting to wonder about "creative" ways to preserve the handle, like encoding it into a GUID and poking it into someplace that wants a GUID. Or are there any ways for me to add user-specific facts to the project config?
|
|
|
06-23-2010, 02:56 AM
|
#156
|
Mobile
Join Date: Jan 2006
Location: London & São Paulo. Hardcore commercial REAPERite
Posts: 1,669
|
Hi - 3.64pre2 has the long-awaited: REAPERAPI_DECL void (*SetCurrentBPM)(ReaProject* __proj, double bpm, bool wantUndo);
Unfortunately I'm getting "syntax error : found 'void (' at global scope (was a declaration intended?)" on this line in my .cpp:
void (*SetCurrentBPM)(ReaProject *__proj, double bpm, bool wantUndo);
What am I doing wrong? I have a feeling it may be about ReaProject.
Incidentally I've not done any of this for a while and am now using the reaper_plugin_functions.h that REAPER generates for the first time.
Two questions:
1) Would I not need to update reaper_plugin.h too? The SDK one appears to be from last year.
2) There's no declaration of ReaProject in the REAPER-made reaper_plugin_functions.h - So do the following points still stand?
Quote:
Originally Posted by Xenakios
Add to the top of reaper_plugin_functions.h :
class LICE_IBitmap;
class ReaProject;
|
Quote:
Originally Posted by schwa
Those declares should be at the top of the automatically generated reaper_plugin_functions.h. As Xenakios says, the one in the SDK is dated (we need to update the SDK page to say you should use the automatically generated header).
|
__________________
Proudly using REAPER exclusively for...
* Media and event music composition & production, sound design + auto-processing at Qsonics.com
* Broadcast branding, promos, education & training and narration voice-overs at DrewWhite.com
|
|
|
06-23-2010, 07:30 AM
|
#157
|
Human being with feelings
Join Date: Jun 2010
Posts: 1
|
Single FXs
Hi there,
is there a way via python to...
1) access a single FX on a track
2) set the bypass state of a single FX
3) set the offline state of a single FX
4) check if a single fx is a vst instrument
Thanks and regards,
trugschluss
Last edited by trugschluss; 06-23-2010 at 07:45 AM.
|
|
|
07-01-2010, 11:47 AM
|
#158
|
Human being with feelings
Join Date: Jul 2010
Posts: 66
|
discrete parameters
I see you have an extension that lets my plug-in tell you that it has discrete values. How does reaper know how may steps there are? This does not seem to be part of the API.
Also, how are these quantized? Is it like VST3 and RTAS where, for example, 0 to .5 maps to zero, and .5 to 1.0 maps to one?
|
|
|
07-01-2010, 03:29 PM
|
#159
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
hi & welcome!
trugschluss,
here are just some clues (I'm on the C/C++ side, most of the time what I propose can't be done with reaScript but.. better than nothing!?):
- check out RPR_GetSetTrackState() and some threads about "RPP chunk patching" over here
- in a track (or an item, for item's fx) chunk, look closely how the nth "BYPASS" line behaves when you bypass/offline the nth FX
- look the diff for "<JS [...]", "<VST [...], etc..
- the rest is a matter of string processing (possibly HUGE string processing!)
In C++, toggeling bypass or offline states can be eased like this (the SNM_ChunkParserPatcher class can be grabbed here):
Code:
{
SNM_ChunkParserPatcher p((MediaTrack*)tr);
bool updated = (p.ParsePatch(_mode, 2, "FXCHAIN", "BYPASS", 3, fxId, _token, (void*)_value) > 0);
} // auto commit
.. where "_token" 1=bypass, 2=offline
___
bruce,
Cockos VST Extensions also allows defining "custom" mix & max values - outside the normalized range. The quantization is deduced from there, I guess, it's linear.
Also, yes, for e.g. an on/off fx param will be "on" when the normalized value is >= 0.5
___
[EDIT] didn't see drew's post!
Hi drew!! I guess you've fixed your issue since that post but.. just in case.. just upgrade your SDK, the last update has been done a few days after the schwa's post you quoted, with the fix.
Last edited by Jeffos; 07-01-2010 at 03:59 PM.
Reason: drew!!!
|
|
|
07-01-2010, 04:03 PM
|
#160
|
Human being with feelings
Join Date: Jul 2010
Posts: 66
|
discrete parameters
so you are saying that if I want a control with five values, I must make its range be 0..4?
That is unfortunate. Many other plug-in APIs support discrete values naively - the ones I know of are RTAS and VST3.
Both of these use 0..1 for the range, but let the controls declare that they have a certain number of discrete values.
I would think that if I DID declare a control range of 0..4 that it would not work so well in other plug-in hosts, since the VST standard is 0..1
It seems that Reaper made an odd decision for this extension - if they had emulated VST3 or RTAS they would have a) been consistent with everyone else, and b) more compatible with other VST2 hosts.
How about a new version of the discrete extension that let's you specify a number of steps? That would address the flaws in today's API, but plug-ins that use today's API would still work.
|
|
|
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 05:27 AM.
|