HISE Logo Forum
    • Categories
    • Register
    • Login
    1. HISE
    2. Orvillain
    O
    • Profile
    • Following 1
    • Followers 0
    • Topics 51
    • Posts 375
    • Groups 0

    Orvillain

    @Orvillain

    76
    Reputation
    50
    Profile views
    375
    Posts
    0
    Followers
    1
    Following
    Joined
    Last Online

    Orvillain Unfollow Follow

    Best posts made by Orvillain

    • RE: Need filmstrip animations

      @d-healey I really like that UI. Very simple, accessible, and smooth looking - for lack of a better word!

      posted in General Questions
      O
      Orvillain
    • RE: Can We PLEASE Just Get This Feature DONE

      Free mankini with every commercial license???

      posted in Feature Requests
      O
      Orvillain
    • RE: Orv's ScriptNode+SNEX Journey

      Lesson 5 - SNEX code in a bit more detail.

      So I'm by no means an expert in C or C++ - in fact I only just recently started learning it. But here's what I've sussed out in regards to the HISE template.... and template is exactly the right word, because the first line is:

      template <int NV> struct audio_loader
      

      Somewhere under the hood, HISE must be setup to send in an integer into any SNEX node, that integer corresponding to a voice. NV = new voice perhaps, or number of voices ????

      The line above declares a template that takes this NV integer in, and creates a struct called audio_loader for each instance of NV. Indeed we can prove this by running the following code:

      template <int NV> struct audio_loader
      {
      	SNEX_NODE(audio_loader);
      	
      	ExternalData data;
      	double note = 0.0;
      	
      	// Initialise the processing specs here
      	void prepare(PrepareSpecs ps)
      	{
      
      	}
      	
      	// Reset the processing pipeline here
      	void reset()
      	{
      		
      	}
      	
      	// Process the signal here
      	template <typename ProcessDataType> void process(ProcessDataType& data)
      	{
      
      	}
      	
      	// Process the signal as frame here
      	template <int C> void processFrame(span<float, C>& data)
      	{
      
      	}
      	
      	// Process the MIDI events here
      	void handleHiseEvent(HiseEvent& e)
      	{
      		double note = e.getNoteNumber();
      		Console.print(note);
      
      	}
      	
      	// Use this function to setup the external data
      	void setExternalData(const ExternalData& d, int index)
      	{
      		data = d;
      	}
      	
      	// Set the parameters here
      	template <int P> void setParameter(double v)
      	{
      		
      	}
      };
      
      

      There are only three things happening here:

      1. We set the ExternalData as in a previous post.
      2. We establish a variable with the datatype of double called 'note' and we initialise it as 0.0. But this value will never hold because....
      3. In the handleHiseEvent() method, we use e.getNoteNumber() and we assign this to the note variable. We then print the note variable out inside of the handleHiseEvent() method.

      Now when we run this script, any time we play a midi note, the console will show us the note number that we pressed. This is even true if you play chords, or in a scenario where no note off events occur.

      That's a long winded way of saying that a SNEX node is run for each active voice; at least when it is within a ScriptNode Synthesiser dsp network.

      The next line in the script after the template is established is:

      SNEX_NODE(audio_loader);
      

      This is pretty straight forward. The text you pass here has to match the name of the script loaded inside your SNEX node - not the name of the SNEX node itself.

      2aa2dbe0-4ea2-40b8-8a45-49d96ada26ec-image.png

      Here you can see my SNEX node is just called: snex_node.

      But the script loaded into it is called audio_loader, and so the reference to SNEX_NODE inside the script has to also reference audio_loader.

      posted in ScriptNode
      O
      Orvillain
    • RE: scriptAudioWaveForm and updating contents

      @d-healey said in scriptAudioWaveForm and updating contents:

      @Orvillain Did you try, AudioWaveform.set("processorId", value); ?

      Yeah I did, and it does update it based on a follow up AudioWaveform.get('processorId') call - but the UI component doesn't seem to update, and still shows data from the previous processorId. When I compile the script, then the UI updates one time... but not on subsequent calls to the set method.

      I figured I needed to call some kind of update() function after setting the processorId, but no such luck so far.

      posted in General Questions
      O
      Orvillain
    • RE: UI Design - AI?

      They're all crap quite honestly.

      posted in Presets / Scripts / Ideas
      O
      Orvillain
    • RE: Getting debug output to the compiler console..

      Hey apologies for the bump. But I got this to work by putting:
      JUCE_LOG_ASSERTIONS=1

      into the preprocessor definitions and then rebuilding. I get data from my custom c++ node printing to the visual studio log:
      947b7f47-31e0-48be-ad66-7de6be45afe0-image.png

      It wasn't working until I did that.

      posted in C++ Development
      O
      Orvillain
    • RE: Orv's ScriptNode+SNEX Journey

      @Orvillain

      Lesson 4 - SNEX Node layout.

      I'm still wrapping my head around how the SNEX node works.

      The first thing to note is, SNEX code does not support strings. The documentation for HISE does make this clear, but if you haven't seen it yet... then I've told you again! Here's the docs link:
      https://docs.hise.audio/scriptnode/manual/snex.html#getting-started

      As the docs say:
      The Scriptnode Expression Language (SNEX ) is a simplified subset of the C language family and is used throughout scriptnode for customization behaviour.

      Which means that most of the syntax you're used to when writing interface scripts, is just not going to be the same. There are some overlaps however - Console.print() is still used in SNEX scripts. However, print messages only get logged to the console when you put the SNEX node into debug mode. Which you can do by clicking this button:
      13b51aa6-bc19-42f4-a155-79e6a22b5edd-image.png

      From what I can tell, by default we have the following methods:

      • prepare
      • reset
      • process
      • processFrame
      • handleHiseEvent
      • setExternalData
      • setParameter

      Each one of these methods has a purpose. I'm still experimenting to figure out what those are, but here's what I've come up with so far:

      • prepare
        This is called when you compile or initialise your SNEX node, and it seems to run for each audio channel. I would guess this is meant to setup global parameters like sample rate and block size. Things that do not change from voice to voice.
      • reset
        This is called when you trigger a voice, in my case from midi. When using a ScriptNode Synthesiser, the midi passes into the node automatically. This is where you would initialise variables that can hold different values from voice to voice, but that must start out with the same default value each time.
      • process
        Haven't quite figured this one out yet.
      • processFrame
        Haven't quite figured this one out yet.
      • handleHiseEvent
        This is called when you trigger a HiseEvent - typically a midi event. This is where you would parse out your midi notes, velocities, controllers, and program changes; any midi data really.
      • setExternalData
        This is called whenever there is a change to the external data. In our case, that would be the AudioFile we added in previous steps. So for example if you went to the complex data editor for the External AudioFile Slot (in the node editor) and loaded a new file, this method would get called. This is where you would resize any arrays that you're using to store the sample data, for example.
      • setParameter
        This is called whenever a parameter inside the SNEX node is adjusted. You can parse the parameters out by using if statements and checking P against 0, 1, 2, 3, etc, depending on how many parameters you actually have.

      SNEX has some hard-coded variable names, most of which I don't know yet. But a valuable one is "ExternalData". Consider this code:

      template <int NV> struct audio_loader
      {
      	SNEX_NODE(audio_loader);
      	
      	ExternalData data;
      	// Initialise the processing specs here
      	void prepare(PrepareSpecs ps)
      	{
      
      	}
      	
      	// Reset the processing pipeline here
      	void reset()
      	{
      	
      
      	}
      	
      	// Process the signal here
      	template <typename ProcessDataType> void process(ProcessDataType& data)
      	{
      
      	}
      	
      	// Process the signal as frame here
      	template <int C> void processFrame(span<float, C>& data)
      	{
      
      	}
      	
      	// Process the MIDI events here
      	void handleHiseEvent(HiseEvent& e)
      	{
      		
      	}
      	
      	// Use this function to setup the external data
      	void setExternalData(const ExternalData& d, int index)
      	{
      		data = d;
      	}
      	
      	// Set the parameters here
      	template <int P> void setParameter(double v)
      	{
      		
      	}
      };
      

      Most of it doesn't do anything. But we have established that ExternalData is linked to a variable called data. We can also see this in the data table view:

      3432f74b-5161-4a02-bcb4-06589ff748d5-image.png

      Notice how ExternalData is a Data Type, and it is named data. Also notice how it has a variety of sub attributes - dataType, numSamples, numChannels, etc.

      Let's swap out the file loaded in the AudioFile editor:
      ae436812-4ef9-4d74-b7c7-1b01d056c4d5-image.png

      Notice how numSamples has updated, and also numChannels.

      Back to the code:

      // Use this function to setup the external data
      	void setExternalData(const ExternalData& d, int index)
      	{
      		data = d;
      	}
      	
      	// Set the parameters here
      	template <int P> void setParameter(double v)
      	{
      		
      	}
      

      The data variable we established as ExternalData at the top of the script, is now actually having the data pushed into it by the setExternalData method - which has two inputs; "d" and "index".

      This shows the very very basics of getting sample data into a SNEX script. But we're still not doing anything with it yet.

      posted in ScriptNode
      O
      Orvillain
    • RE: Orv's ScriptNode+SNEX Journey

      @d-healey I don't mean to be rude, but please don't distract from the purpose of this thread. Beautiful code and efficient code isn't the point here.

      The point is to demonstrate how the API works, and for there to be a resource for people who come along in the future looking to do sample loading from their scripts, and looking to do advanced things in ScriptNode or SNEX.

      I know full well that in a real world scenario, you wouldn't specify a bunch of files each as an individual const, and you'd put them in a key value pair inside of an array, or perhaps a function acting as a meta-object.

      posted in ScriptNode
      O
      Orvillain
    • RE: Orv's ScriptNode+SNEX Journey

      @Orvillain

      Lesson 3 - using the SNEX node with sample content.

      This one is something I'm still getting my head around. @Christoph-Hart kindly provided a one shot SNEX node demo, which you can find by going to the example snippet browser in the Help menu:

      9dc656c3-1c19-4519-a65b-23a8b0f68951-image.png

      This will open a whole new window where you can experiment with snippets. Maybe I'll go over the specific snippet in another post, but for this one... we're starting fresh, and we're going to just do the basics.

      So.... here we have a basic interface script that gives us some file const references, an AudioSampleProcessor retreiving the AudioSampleProcessor from a Scriptnode Synthesiser in our module tree. That synthesiser has a DspNetwork assigned to it:

      694c0445-52bf-4463-9e87-26e544831daf-image.png

      Right now, if we run the code... it will fail to find the AudioSampleProcessor as explained above. Let's add a SNEX node:
      79d066a0-73ee-4e1a-95db-b38bf740fc4b-image.png

      When you do this, it will be blank. You will need to click the three dot menu icon and choose "create new file" - strangely enough, you have to do this even when creating an embedded network. But fine. Let's do it:
      bb7e6551-caff-4400-a6df-a48dc78ca076-image.png

      We need to give it a name:
      40eba1f6-f540-44cf-a267-eec804013f1e-image.png

      At this point, the node becomes active, indicated by the green highlight text:
      644f74ac-0bb3-4dff-b3c8-aa7a1857925e-image.png

      Now if you open the same menu, you get more options:
      335c2077-daf7-41c7-a48e-d0a8bf3b4faa-image.png

      We're going to select 'Add AudioFile':
      bdc1ef26-6564-497a-88cf-c936e11b18fe-image.png

      You can see that now there is an extra icon in the SNEX node, which opens the AudioFile "Complex Data Editor" panel.

      We can add an External AudioFile Slot using the icon on the right hand side:
      1f59716d-91f4-46c7-9acf-06dc11862eee-image.png

      And now you can see that the data editor will display whatever sample you assign to that AudioSampleProcessor from your script:
      fbdb8b3a-79c1-4f4c-89f8-0b16ad744232-image.png

      So here we see that file_r has been loaded into the buffer, and if we wanted to do file_f instead we could change the code to do that:
      8a440106-6b99-411d-92a1-9bb2cbdc1bf6-image.png

      Note - you will not be able to playback the audio at this stage, as your SNEX code will be completely empty. If you click the icon on the right side, it will open the code editor for this SNEX node:
      74b46993-1131-4f9e-905d-5606bc69f521-image.png

      So whilst the data is loaded, our code isn't doing anything with it.

      posted in ScriptNode
      O
      Orvillain
    • RE: Orv's ScriptNode+SNEX Journey

      @Orvillain

      Lesson 2: loading samples.

      Loading samples into an AudioSampleProcessor can be done by running a .setFile() call on the retrieved AudioSampleProcessor object.

      However, on Windows, it is very easy to make this crash.

      3e9a7976-b21d-43eb-bb54-81a1e310ae4a-image.png

      Consider the above image. The backslashes in files a-r, will cause HISE to crash to the desktop if you try to load any of those files into the AudioSampleProcessor.

      file_s has double backslashes, and this does not seem to crash.

      Another way to ensure the file loads, is to just use forward slashes:
      dd73d3fc-85f7-467c-9db4-c7e995dbac7d-image.png

      posted in ScriptNode
      O
      Orvillain

    Latest posts made by Orvillain

    • RE: Getting debug output to the compiler console..

      Hey apologies for the bump. But I got this to work by putting:
      JUCE_LOG_ASSERTIONS=1

      into the preprocessor definitions and then rebuilding. I get data from my custom c++ node printing to the visual studio log:
      947b7f47-31e0-48be-ad66-7de6be45afe0-image.png

      It wasn't working until I did that.

      posted in C++ Development
      O
      Orvillain
    • RE: Pitch shifting when smoothing a delay

      @Christoph-Hart Right, gotcha!

      That was the one I tried to use initially, but it still glitched. I guess this is because it only has two buffers and can't keep up with the requested delay time shifts fast enough???

      I'm okay with the pitch shift. I was just curious if there was another container type that would handle that, but I guess not!

      posted in General Questions
      O
      Orvillain
    • Pitch shifting when smoothing a delay

      7c7293a5-4d15-4e69-b9d9-d658aabbbbe2-image.png

      When I have the smoothed_parameter and delay nodes outside of the frame2_block, I get lots of glitching in the audio. When it is inside the frame2_block, it is nice and smooth, but it also creates a pitch shift in the delay.

      Is there a solution to this that doesn't involve a custom SNEX or c++ delay?

      @griffinboy any ideas dude?

      posted in General Questions
      O
      Orvillain
    • RE: Simple ML neural network

      @Christoph-Hart @Dan-Korneff

      Link Preview Image
      HISE | Scripting | NeuralNetwork

      Access the neuralNetwork object

      favicon

      (docs.hise.dev)

      There is this call in the docs - does it work? How do you use it? Can you feed in the raw .NAM file as JSON and use it directly?

      posted in General Questions
      O
      Orvillain
    • RE: Spectral Gating

      Custom c++ node.

      You can get by quite easily by using the built in Juce fft:

      	// FFT object
      	static constexpr int fftSize = 128;
      	static constexpr int hopSize = 16;
      	juce::dsp::FFT fft { (int)std::log2(fftSize) };
      

      That would create the required object. Then you'd need to write a loop process in order to call something like:

      fft.performRealOnlyForwardTransform(fftBuffer.get());
      

      I just wrote a node recently to process an audio buffer with FFT's. It works really well.

      posted in General Questions
      O
      Orvillain
    • RE: Multiple Global Data Cables - only the first one gets a runtime target

      @griffinboy Hmmmm, that might be what I ran into. Can't remember now. But basically my c++ as an effect works well... and I'm able to update the UI any time I turn a parameter.

      But when I tried to convert it to a synth... it wasn't working.

      This actually makes sense, because my sendData function gets triggered from within processFrame, which gets triggered from within process()

      posted in Bug Reports
      O
      Orvillain
    • RE: Multiple Global Data Cables - only the first one gets a runtime target

      @griffinboy @Christoph-Hart

      Do you know if DataCables work in Scriptnode Synthesizers? Because I've just taken my node and put it into one, and now I no longer get the runtime target showing up, and the data callback no longer fires.

      Nothing has changed, and I only have a single instance of the node in the entire project.

      If I delete the scriptnode synthesizer and re-add the script FX, then it immediately begins to work.

      posted in Bug Reports
      O
      Orvillain
    • RE: Multiple Global Data Cables - only the first one gets a runtime target

      @griffinboy Yep - I can't even plead ignorance either. I knew about the hashing and just totally forgot! So I'm an idiot!

      posted in Bug Reports
      O
      Orvillain
    • RE: Multiple Global Data Cables - only the first one gets a runtime target

      @Christoph-Hart Dannngg!!! I knew I cocked something up!!!! baaaahhh!!

      Thanks Chris. No bug here. You're 100% right.

      posted in Bug Reports
      O
      Orvillain
    • Multiple Global Data Cables - only the first one gets a runtime target

      Custom c++ node:

      #pragma once
      #include <JuceHeader.h>
      
      namespace project
      {
      using namespace juce;
      using namespace hise;
      using namespace scriptnode;
      
      enum class GlobalCables
      {
      	dataCable = 0,
      	otherDataCable = 1
      };
      
      using cable_manager_t = routing::global_cable_cpp_manager<
      														SN_GLOBAL_CABLE(-389806413),
      														SN_GLOBAL_CABLE(-389806400)
      														>;
      
      template <int NV> struct custom_node: public data::base,
      								    public cable_manager_t
      {
      	SNEX_NODE(custom_node);
      	
      	struct MetadataClass
      	{
      		SN_NODE_ID("custom_node");
      	};
      	
      	static constexpr bool isModNode() { return false; };
      	static constexpr bool isPolyphonic() { return NV > 1; };
      	static constexpr bool hasTail() { return false; };
      	static constexpr bool isSuspendedOnSilence() { return false; };
      	static constexpr int getFixChannelAmount() { return 2; };
      	
      	static constexpr int NumTables = 0;
      	static constexpr int NumSliderPacks = 0;
      	static constexpr int NumAudioFiles = 1;
      	static constexpr int NumFilters = 0;
      	static constexpr int NumDisplayBuffers = 0;
      
      	custom_node()
      	{
      		// this doesn't do anything, but if you want the communication the other way around
      		// (from HISE to your C++ node, this is the way to register callbacks).
      		this->registerDataCallback<GlobalCables::dataCable>([](const var& funky)
      		{
      			jassertfalse;
      		});
      		
      		this->registerDataCallback<GlobalCables::otherDataCable>([](const var& funky)
      		{
      			jassertfalse;
      		});
      	}
      
      	// We don't need any of the DSP callbacks for this example so we can use these macros to
      	// define empty methods
      	SN_EMPTY_PREPARE;
      	SN_EMPTY_HANDLE_EVENT;
      	SN_EMPTY_PROCESS;
      	SN_EMPTY_PROCESS_FRAME;
      	SN_EMPTY_MOD;
      	SN_EMPTY_RESET;
      
      	void setExternalData(const ExternalData& data, int index)
      	{
      		if(data.isNotEmpty())
      		{
      			// convert the data to the juce::AudioSampleBuffer class
      			auto buffer = data.toAudioSampleBuffer();
      
      			// fetch a few properties from the buffer
      			// (this is the place where you could do your custom processing
      			auto magnitude = buffer.getMagnitude(0, 0, buffer.getNumSamples());
      			auto rms = buffer.getRMSLevel(0, 0, buffer.getNumSamples());
      			auto length = buffer.getNumSamples();
      			auto channels = buffer.getNumChannels();
      
      			hise::JSONObject obj;
      
      			// write the values into the JSON object
      			obj[String("magnitude")] = magnitude;
      			obj[String("RMS")] = rms;
      			obj[String("length")] = length;
      			obj[String("numChannels")] = channels;
      
      			// just attach the current knob position so you know it's working.
      			obj[String("knobValue")] = value;
      
      			// send the JSON object back to HISE
      			this->sendDataToGlobalCable<GlobalCables::dataCable>(obj);
      			this->sendDataToGlobalCable<GlobalCables::otherDataCable>(obj);
      		}
      	}
      	
      	template <int P> void setParameter(double v)
      	{
      		// just save the parameter to be send in the JSON object later.
      		value = v;
      	}
      
      	double value = 0.0;
      
      	void createParameters(ParameterDataList& data)
      	{
      		{
      			parameter::data p("MyParameter", { 0.0, 1.0 });
      			registerCallback<0>(p);
      			p.setDefaultValue(0.5);
      			data.add(std::move(p));
      		}
      	}
      };
      }
      
      

      Then the interface script:

      Content.makeFrontInterface(600, 600);
      
      reg rm = Engine.getGlobalRoutingManager();
      
      reg dataCable = rm.getCable("dataCable");
      reg otherDataCable = rm.getCable("otherDataCable");
      
      dataCable.registerDataCallback(function(data)
      {
      	Console.print(trace(data));
      });
      
      otherDataCable.registerDataCallback(function(data)
      {
      	Console.print("We could have other data here from the same node");
      });
      
      
      
      

      Here's the project snippet:

      HiseSnippet 1238.3ocuWs0SibCE1SfY2R5tRck19Z0HdJTQiRfvsspp.IDZTKPDgR2GPB43wCwEO1i73AHsZk5i8ma+GzdNyLIYlRX2UQsKHglyU+4i+NGa5azLdbr1Pbpdw3HNw4EtCFqriZOhJTjdcHNegaXhzJhjbFcnjGSNbbDMNl6SbbV5XzKmUVlj9ye88GRkTEiOSEgboVv3+jHTXmos+9+nPJ6R84WHBK3cq86wzp1ZoNAPzRtMHQT1sza3mRQ2p3R9AZ7HhyW6t0FaxZ4ui+Far0daynay2KfFDzbS+Vs1d28Zt4dzV6tMuwVDmmcjuvpMCrTK.dmkOT6OdvH88prE3RQr.1VnPSx.XkyT2UK8wsHpkzdjP52eRoJl.Yo+rB2RYEtW6dhvWLUewBHZvaVDEKfNUJCukJAulEgWiBvaNPxo.jVNCRuxc.yHhryrf34yc6orbS.ENmJBkLeIU9pJts0fGJa8P5s7tFPXZD01tQi08f+r12VspgeimIz667NRciPwqeC2drTOjJOWmXEpaNgpfyNSsI95Ssz1HIBBwDhtmJUa0oFVEbE8TaGwMcdJ2KaEio5zLTGBWDamXWJGBLnZAIJlUnU0P+Vq5uWcEXKFqA2iLBksl0f6sTiP1dGlwxKxhk1U+EtGSmH88FQuimsqRqBdvGbu.iNzCz4ECzaOk1Ocujt5UmjZOs5TskelpVZ9q9tpd+aSAAy0FdHZzRId.LGyXmm48EXMUR3PtYcu6nxD9TGA1UYJq6SSYK1Qwx3TEbTq5oD1yh3pmpOijSDgu94dXkG4445.+h3Fq.gfSG9cvTlLV+Jtc3w2Z0QvblG0R.MiZ+DI0VtCEmika.pAkZKPpuJVXGWbN2+YsserP7Ut8EV1n4iwJyAiPk5+CLlOr6ktGEDvY1Y.bY2tu8S2jsp4S1f0DW7WjK508sMm2LMm+zcFAOxvinF9E59R53ZPqGb014.FW2anTytcf323OtqHJCBGhdTiMhpTbY7hz77rO5pRy2+gxzYrVi3AnK7zjvAv0lLd6bzA5bpf8PYxMPYjWLfq7SE9a3mbiMQYmbiMmXrP62ob68ZysoGG4eSbddVsuSbzDUWxMwH4z44tMpC+RNPJ022VGFIxosvgRpt9Z43nQZkfgpx7XBzOHTm.qZN9gq7ufJjHOePRLLsv+L0.v4z2Yf7rSgAmPgsKkAE1w8o1QX+BNuAHmbSc1TRZIrNimfU3rWXTxALuSdKRVoCfYHVOvWh3rDZOqfjA.bEdoKKI1pCuFmlSJAouzEHQ+JzzTunKyCEkSxGBFXsSxeXx3wCR7E5tBYFzlJAsN8T97GvN0uoI4Hfc5CuvIMJm8cO9MW0AFnNT+vUGmHrTy4hff1ZSTR7U.rsPWmuHdmFWEl.71q5zyC+nVilqU+d5cjSDpKQh9DDdB8gox+waVAFgPMvVCt+LqgdhDXuPnHmoXntMA4rp5IimESA22hzgGPg2mVRGovcCkW5RVRGrfRvXyJYzidwWhVYT4jUAnfGp02FRSI+K10PeJZUCoLi9ZV1zGby8YoZ.9gJkSsh6InrWSxcYarYWTDBOO8ZFqbpdTfarnAt4hFXqEMvsVz.2dQCbmEMvc+vAh+2CGj.iCxFfBMW8OJqs04HE91zza5H+C7L.wuK
      

      You'll need to compile the custom node as "custom_node".

      So what I am seeing here, and I have no idea why .... the "otherDataCable" does not get a "Runtime Target" entry in the list of targets, so its callback never seems to run.

      This is using two cables with a single node.

      Is this not supported?

      posted in Bug Reports
      O
      Orvillain