So begins the Middle Layer adventure. As discussed in the Middle Layer main thread, I just wanted to get started and see how far I could get with what’s been offered up for examples. Thought I would try to build the round robin style polyphonic sample player that I made a video about in this thread as a stretch goal. But this time, using the middle layer. Thought Revolver made a nice code name.
I like to begin with the end in mind, and so started by trying to add the controls I want to see available on the unit. I was able to add a V/oct control, and a trigger control. Basically by just copying and pasting bits and pieces of code out of the sine oscillator and S&H built in units into the onLoadGraph and onLoadView functions.
It doesn’t do anything right now. Nothing is connected - i assume it’s basically a signal blocker or a null oscillator, however you’d care to describe it. But I do have the basic view controls that I want. And I only had 2 system crashes trying to get the code right (crash.log is pretty helpful for some clues).
For the next move, I wanted to add some things to the header menu. A way to select, slice, and edit a sample. The idea I had is that you’d select a single sample and it would be used in all four player objects.
It looks like I probably want to be grabbing some code from BasePlayer.lua. But it’s pretty lengthy and I’m not sure what parts I need yet. Also decided this would be a good point to stop and just do a checkpoint. Not sure the best way to share the code. I created a public github repo.
Thanks for doing this @Joe. I like the way you are showing your thinking as you approach the middle layer for the first time, rather than just documenting a completed project retrospectively.
I sort of have to do it this way. I don’t know what I’m doing enough to complete it without some (probably a lot of) coaching from the one guy on the planet who does. I have a fair amount of other development experience, so I know enough to be able to start hacking at this and will try to move it forward on my own. But I wouldn’t assume my approach at any given point is correct, the best way, or even functional.
Hopefully we’ll all learn a lot. I’m just volunteering to be the public guinea pig.
I am cross-quoting from the other thread here. Been looking at this a bit and I see how you are using this, for example, in the NativeSpeedPlayerUnit and PlayerUnit code. Their LUA files are extremely short and very similar (the difference of args.enableVariableSpeed and the unit names).
So I assume if I did this in my code, I would inherit all of the header menu items, as well as the view controls? Ultimately I think this will contain 4 sample players so I don’t really want 4x duplicates of all of them exposed. If I explicitly include the methods for onLoadViews and onLoadMenus will that overload what it is inherited from the BasePlayer so that I can be specific about what I want?
Thinking that calling on the builtins is not going to be the way to go here. A little experimentation proved unsuccessful. However, I did manage to add the sample selection to the header dialogue, and am able to select a sample from the card and get back to the unit view without crashing. This by copiously stealing some code from the Sample Scanner unit and making slight modifications. For now I have just commented out the lines where it actually does anything with the sample, like assign it. The unit still does nothing.
Detach does not cause a crash. Have not tested slice or edit.
After examining BasePlayer.lua for a while, I decided it was quite complicated and that I would take a short instant gratification detour and see if I could make this unit make some sound. So there is a new branch in the repo called “makesound”. I simply added sine oscillator (code copied from the FMOperator example) with a V/Oct and f0 control (The V/Oct control is the original added above in the master branch).
Questions:
I really tried not to add the f0 control, but rather just do without it and hard code the base frequency f0 of sinosc to a “C” note:
sinosc:hardSet(“Fundamental”,32.7)
This didn’t work. There was no output until I added the GainBias control and connected the it up to the sine oscillators fundamental:
local f0 = self:createObject(“GainBias”,“f0”)
connect(f0,“Out”,sinosc,“Fundamental”)
Why, I wonder…
Also, nearly every createObject I have added to the project has necessitated that I add a “require” library up at the top, if it wasn’t already there. The SineOscillator did not. Where does it live?
Edit: Regarding eliminating the f0 control also tried this, which causes a system crash.
sinosc:getOption(“Fundamental”):set(32.7)
And then this was just a total SWAG. Also caused a system crash.
Oops. I totally forgot to say anything about the difference between ports (Inlets and Outlets) and Parameters and Options. Ports come in two flavors, Inlets and Outlets. Signal travels from an Outlet port to any number of Inlet ports at the system sampling rate (48kHz or 96kHz). Parameters are values that can be assigned (tied) to other Parameters or controlled by UI controls. Parameters are updated at the system frame rate (375Hz or 750Hz). Options are like Parameters except they take their values from a given list of valid discrete values (like yes/no or left/both/right).
sinosc:hardSet(“Fundamental”,32.7) -- won't work
So in your case you were trying to set the value of an Inlet (SineOscillator’s Fundamental inlet) as if it was a Parameter. There are a number of ways to go about doing this. The simplest is to use a Constant object like this:
local freq = self:createObject("Constant","freq")
freq:hardSet("Value",32.7)
connect(freq,"Out",sinosc,"Fundamental")
The Constant object will output the value of the Value parameter at audio rate which is what the SineOscillator’s Fundamental inlet needs.
Parameters can be hardSet or softSet. The former sets the value immediately, while the latter will linearly tween towards the target value over time.
(P.S. Documentation is forthcoming but I’m in the middle of some manufacturing tasks at the moment. )
I am catching errors in the initial execution of the unit modules (when your unit class is defined) but it looks like I have neglected to catch errors during the instantation (when onLoadGraph and so on are called). Was never necessary until now I’ll fix that.
Does softSet accept a second optional param for the tween time? Or is it fixed? I see it used in some of the delay units, and it looks like it is used a if a preset was made prior to v0.3.04 when the feedback was made modulatable it converts it to the new schema. Not really sure why it was needed there - I guess to cause an abrupt jump from zero when it’s loaded?
Also, where does app.log write to? A file on the card, or somewhere on the display?
I have encountered the error.log in the libs\appfolder too. So I guess the goal would be for all debugging information relevant to the app (should we call these middle layer patches apps? ) would end up in this file (and also not force a reboot)?
The crash.log seems to append to itself, and assume error.log will too. Is it safe to delete these files and they will just get recreated?
Yay! This works great and the f0 control was successfully removed in commit 67ce03d of branch makesound.
No. It’s a fixed length tween at the moment. It’s just there to prevent unseemly noises. You wouldn’t use it to do to automation or modulation. Also, it is not necessary in the onLoadGraph function because the unit hasn’t been inserted into the DSP scheduler yet. You would typically only use it to a make change to a parameter while the unit is running and generating signal.
app.log(…) writes to the UART which I monitor in real-time via one of these.
Now that I have another folder in ER-301/libs, and another init.lua inside that folder, I no longer see the FMOperator or Countdown units from ER-301/libs/testlib. I only see my “Revolver” unit when I go to insert a unit. Can there only be a single init.lua in the directory structure?