2
0
Fork 0

Add support for percussion mode.

This commit is contained in:
Bruce Sutherland 2015-01-01 20:55:03 +09:00
parent 03cada5102
commit ecfc83df47
7 changed files with 191 additions and 83 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="wUKQiT" name="JuceOPLVSTi" projectType="audioplug" version="0.10.1"
<JUCERPROJECT id="wUKQiT" name="JuceOPLVSTi" projectType="audioplug" version="0.11.1"
bundleIdentifier="com.plainweave.JuceOPLVSTi" buildVST="1" buildAU="1"
pluginName="JuceOPLVSTi" pluginDesc="JuceOPLVSTi" pluginManufacturer="Plainweave Software"
pluginManufacturerCode="Pwve" pluginCode="JOPL" pluginChannelConfigs="{0, 1}"

View file

@ -105,6 +105,8 @@ void PluginGui::updateFromParameters()
algorithmComboBox->setSelectedItemIndex(processor->getEnumParameter("Algorithm"), true);
percussionComboBox->setSelectedItemIndex(processor->getEnumParameter("Percussion Mode"), true);
tooltipWindow.setColour(tooltipWindow.backgroundColourId, Colour(0x0));
tooltipWindow.setColour(tooltipWindow.textColourId, Colour(COLOUR_MID));
}
@ -601,7 +603,7 @@ PluginGui::PluginGui (JuceOplvstiAudioProcessor* ownerFilter)
addAndMakeVisible (dbLabel5 = new Label ("db label",
TRANS("dB")));
dbLabel5->setFont (Font (15.00f, Font::plain));
dbLabel5->setJustificationType (Justification::centred);
dbLabel5->setJustificationType (Justification::centredLeft);
dbLabel5->setEditable (false, false, false);
dbLabel5->setColour (Label::textColourId, Colour (0xff007f00));
dbLabel5->setColour (Label::outlineColourId, Colour (0x00000000));
@ -633,7 +635,7 @@ PluginGui::PluginGui (JuceOplvstiAudioProcessor* ownerFilter)
TRANS("cents\n")));
dbLabel6->setTooltip (TRANS("A unit of pitch; 100 cents per semitone"));
dbLabel6->setFont (Font (15.00f, Font::plain));
dbLabel6->setJustificationType (Justification::centred);
dbLabel6->setJustificationType (Justification::centredLeft);
dbLabel6->setEditable (false, false, false);
dbLabel6->setColour (Label::textColourId, Colour (0xff007f00));
dbLabel6->setColour (Label::outlineColourId, Colour (0x00000000));
@ -873,6 +875,29 @@ PluginGui::PluginGui (JuceOplvstiAudioProcessor* ownerFilter)
recordButton->addListener (this);
recordButton->setColour (ToggleButton::textColourId, Colour (0xff007f00));
addAndMakeVisible (percussionComboBox = new ComboBox ("percussion combo box"));
percussionComboBox->setEditableText (false);
percussionComboBox->setJustificationType (Justification::centredLeft);
percussionComboBox->setTextWhenNothingSelected (String::empty);
percussionComboBox->setTextWhenNoChoicesAvailable (TRANS("(no choices)"));
percussionComboBox->addItem (TRANS("Off"), 1);
percussionComboBox->addItem (TRANS("Bass drum"), 2);
percussionComboBox->addItem (TRANS("Snare"), 3);
percussionComboBox->addItem (TRANS("Tom"), 4);
percussionComboBox->addItem (TRANS("Cymbal"), 5);
percussionComboBox->addItem (TRANS("Hi-hat"), 6);
percussionComboBox->addListener (this);
addAndMakeVisible (percussionLabel = new Label ("percussion label",
TRANS("Percussion mode")));
percussionLabel->setTooltip (TRANS("Enable percussion instruments instead of oscillators"));
percussionLabel->setFont (Font (15.00f, Font::plain));
percussionLabel->setJustificationType (Justification::centredLeft);
percussionLabel->setEditable (false, false, false);
percussionLabel->setColour (Label::textColourId, Colour (0xff007f00));
percussionLabel->setColour (TextEditor::textColourId, Colours::black);
percussionLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000));
//[UserPreSize]
frequencyComboBox->setColour (ComboBox::textColourId, Colour (COLOUR_MID));
@ -942,6 +967,12 @@ PluginGui::PluginGui (JuceOplvstiAudioProcessor* ownerFilter)
keyscaleAttenuationComboBox2->setColour (ComboBox::buttonColourId, Colours::black);
keyscaleAttenuationComboBox2->setColour (ComboBox::backgroundColourId, Colours::black);
percussionComboBox->setColour(ComboBox::textColourId, Colour(COLOUR_MID));
percussionComboBox->setColour(ComboBox::outlineColourId, Colour(COLOUR_MID));
percussionComboBox->setColour(ComboBox::arrowColourId, Colour(COLOUR_MID));
percussionComboBox->setColour(ComboBox::buttonColourId, Colours::black);
percussionComboBox->setColour(ComboBox::backgroundColourId, Colours::black);
sineImageButton->setClickingTogglesState(true);
sineImageButton->setRepaintsOnMouseActivity(false);
abssineImageButton->setClickingTogglesState(true);
@ -1002,7 +1033,7 @@ PluginGui::PluginGui (JuceOplvstiAudioProcessor* ownerFilter)
}
//[/UserPreSize]
setSize (860, 516);
setSize (860, 550);
//[Constructor] You can add your own custom stuff here..
@ -1097,6 +1128,8 @@ PluginGui::~PluginGui()
emulatorLabel = nullptr;
emulatorLabel2 = nullptr;
recordButton = nullptr;
percussionComboBox = nullptr;
percussionLabel = nullptr;
//[Destructor]. You can add your own custom destruction code here..
@ -1169,13 +1202,13 @@ void PluginGui::resized()
sustainButton2->setBounds (464, 384, 96, 24);
keyscaleEnvButton2->setBounds (552, 384, 184, 24);
frequencyLabel4->setBounds (672, 184, 152, 24);
groupComponent3->setBounds (16, 432, 832, 64);
tremoloSlider->setBounds (344, 456, 112, 24);
frequencyLabel5->setBounds (224, 456, 152, 24);
dbLabel5->setBounds (456, 448, 40, 40);
vibratoSlider->setBounds (672, 456, 112, 24);
frequencyLabel6->setBounds (560, 456, 152, 24);
dbLabel6->setBounds (784, 448, 47, 40);
groupComponent3->setBounds (16, 432, 832, 96);
tremoloSlider->setBounds (632, 456, 112, 24);
frequencyLabel5->setBounds (472, 456, 152, 24);
dbLabel5->setBounds (752, 448, 40, 40);
vibratoSlider->setBounds (632, 488, 112, 24);
frequencyLabel6->setBounds (472, 488, 152, 24);
dbLabel6->setBounds (752, 480, 47, 40);
feedbackSlider->setBounds (112, 232, 136, 24);
frequencyLabel7->setBounds (32, 232, 80, 24);
velocityComboBox->setBounds (336, 232, 72, 24);
@ -1201,6 +1234,8 @@ void PluginGui::resized()
emulatorLabel->setBounds (112, 32, 72, 24);
emulatorLabel2->setBounds (248, 32, 72, 24);
recordButton->setBounds (40, 456, 128, 24);
percussionComboBox->setBounds (256, 488, 112, 24);
percussionLabel->setBounds (40, 488, 163, 24);
//[UserResized] Add your own custom resize handling here..
for (unsigned int i = 0; i < channels.size(); ++i)
channels[i]->setBounds(456+44*i+4, 36, 16, 16);
@ -1264,6 +1299,13 @@ void PluginGui::comboBoxChanged (ComboBox* comboBoxThatHasChanged)
processor->setEnumParameter("Modulator Keyscale Level", id);
//[/UserComboBoxCode_keyscaleAttenuationComboBox]
}
else if (comboBoxThatHasChanged == percussionComboBox)
{
//[UserComboBoxCode_percussionComboBox] -- add your combo box handling code here..
int id = comboBoxThatHasChanged->getSelectedId() - 1;
processor->setEnumParameter("Percussion Mode", id);
//[/UserComboBoxCode_percussionComboBox]
}
//[UsercomboBoxChanged_Post]
//[/UsercomboBoxChanged_Post]
@ -1600,7 +1642,7 @@ BEGIN_JUCER_METADATA
parentClasses="public AudioProcessorEditor, public FileDragAndDropTarget, public DragAndDropContainer, public Timer"
constructorParams="JuceOplvstiAudioProcessor* ownerFilter" variableInitialisers=" AudioProcessorEditor (ownerFilter)"
snapPixels="8" snapActive="1" snapShown="1" overlayOpacity="0.330"
fixedSize="0" initialWidth="860" initialHeight="516">
fixedSize="0" initialWidth="860" initialHeight="550">
<BACKGROUND backgroundColour="ff000000"/>
<GROUPCOMPONENT name="new group" id="d2c7c07bf2d78c30" memberName="groupComponent"
virtualName="" explicitFocusOrder="0" pos="16 80 408 344" outlinecol="ff007f00"
@ -1850,41 +1892,41 @@ BEGIN_JUCER_METADATA
editableSingleClick="0" editableDoubleClick="0" focusDiscardsChanges="0"
fontname="Default font" fontsize="15" bold="0" italic="0" justification="36"/>
<GROUPCOMPONENT name="new group" id="7392f7d1c8cf6e74" memberName="groupComponent3"
virtualName="" explicitFocusOrder="0" pos="16 432 832 64" outlinecol="ff007f00"
virtualName="" explicitFocusOrder="0" pos="16 432 832 96" outlinecol="ff007f00"
textcol="ff007f00" title="Common" textpos="33"/>
<SLIDER name="tremolo slider" id="ab64abee7ac8874b" memberName="tremoloSlider"
virtualName="" explicitFocusOrder="0" pos="344 456 112 24" thumbcol="ff00af00"
virtualName="" explicitFocusOrder="0" pos="632 456 112 24" thumbcol="ff00af00"
trackcol="7f007f00" textboxtext="ff007f00" textboxbkgd="ff000000"
textboxhighlight="ff00af00" min="1" max="4.7999999999999998"
int="3.7999999999999998" style="LinearHorizontal" textBoxPos="TextBoxLeft"
textBoxEditable="1" textBoxWidth="44" textBoxHeight="20" skewFactor="1"/>
<LABEL name="frequency label" id="134ce8f87da62b88" memberName="frequencyLabel5"
virtualName="" explicitFocusOrder="0" pos="224 456 152 24" tooltip="OPL global tremolo depth"
virtualName="" explicitFocusOrder="0" pos="472 456 152 24" tooltip="OPL global tremolo depth"
textCol="ff007f00" edTextCol="ff000000" edBkgCol="0" labelText="Tremolo Depth&#10;"
editableSingleClick="0" editableDoubleClick="0" focusDiscardsChanges="0"
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/>
<LABEL name="db label" id="720df8e7c502dd91" memberName="dbLabel5" virtualName=""
explicitFocusOrder="0" pos="456 448 40 40" textCol="ff007f00"
explicitFocusOrder="0" pos="752 448 40 40" textCol="ff007f00"
outlineCol="0" edTextCol="ff000000" edBkgCol="0" labelText="dB"
editableSingleClick="0" editableDoubleClick="0" focusDiscardsChanges="0"
fontname="Default font" fontsize="15" bold="0" italic="0" justification="36"/>
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/>
<SLIDER name="vibrato slider" id="b45a1f20f22cf5ca" memberName="vibratoSlider"
virtualName="" explicitFocusOrder="0" pos="672 456 112 24" thumbcol="ff00af00"
virtualName="" explicitFocusOrder="0" pos="632 488 112 24" thumbcol="ff00af00"
trackcol="7f007f00" textboxtext="ff007f00" textboxbkgd="ff000000"
textboxhighlight="ff00af00" min="7" max="14" int="7" style="LinearHorizontal"
textBoxPos="TextBoxLeft" textBoxEditable="1" textBoxWidth="44"
textBoxHeight="20" skewFactor="1"/>
<LABEL name="frequency label" id="1412b9d14e37bcbe" memberName="frequencyLabel6"
virtualName="" explicitFocusOrder="0" pos="560 456 152 24" tooltip="OPL global vibrato depth"
virtualName="" explicitFocusOrder="0" pos="472 488 152 24" tooltip="OPL global vibrato depth"
textCol="ff007f00" edTextCol="ff000000" edBkgCol="0" labelText="Vibrato Depth"
editableSingleClick="0" editableDoubleClick="0" focusDiscardsChanges="0"
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/>
<LABEL name="db label" id="e13e0aff8b974a36" memberName="dbLabel6" virtualName=""
explicitFocusOrder="0" pos="784 448 47 40" tooltip="A unit of pitch; 100 cents per semitone"
explicitFocusOrder="0" pos="752 480 47 40" tooltip="A unit of pitch; 100 cents per semitone"
textCol="ff007f00" outlineCol="0" edTextCol="ff000000" edBkgCol="0"
labelText="cents&#10;" editableSingleClick="0" editableDoubleClick="0"
focusDiscardsChanges="0" fontname="Default font" fontsize="15"
bold="0" italic="0" justification="36"/>
bold="0" italic="0" justification="33"/>
<SLIDER name="feedback slider" id="f9d22e12f5e417e4" memberName="feedbackSlider"
virtualName="" explicitFocusOrder="0" pos="112 232 136 24" thumbcol="ff00af00"
trackcol="7f007f00" textboxtext="ff007f00" textboxbkgd="ff000000"
@ -2009,6 +2051,15 @@ BEGIN_JUCER_METADATA
virtualName="" explicitFocusOrder="0" pos="40 456 128 24" tooltip="Start recording all register writes to a DRO file - an OPL recording file format defined by DOSBox"
txtcol="ff007f00" buttonText="Record to DRO" connectedEdges="0"
needsCallback="1" radioGroupId="0" state="0"/>
<COMBOBOX name="percussion combo box" id="75a838b61782e17b" memberName="percussionComboBox"
virtualName="" explicitFocusOrder="0" pos="256 488 112 24" editable="0"
layout="33" items="Off&#10;Bass drum&#10;Snare&#10;Tom&#10;Cymbal&#10;Hi-hat"
textWhenNonSelected="" textWhenNoItems="(no choices)"/>
<LABEL name="percussion label" id="a3400e2e5e8e7900" memberName="percussionLabel"
virtualName="" explicitFocusOrder="0" pos="40 488 163 24" tooltip="Enable percussion instruments instead of oscillators"
textCol="ff007f00" edTextCol="ff000000" edBkgCol="0" labelText="Percussion mode"
editableSingleClick="0" editableDoubleClick="0" focusDiscardsChanges="0"
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/>
</JUCER_COMPONENT>
END_JUCER_METADATA

View file

@ -179,6 +179,8 @@ private:
ScopedPointer<Label> emulatorLabel;
ScopedPointer<Label> emulatorLabel2;
ScopedPointer<ToggleButton> recordButton;
ScopedPointer<ComboBox> percussionComboBox;
ScopedPointer<Label> percussionLabel;
//==============================================================================

View file

@ -116,6 +116,11 @@ JuceOplvstiAudioProcessor::JuceOplvstiAudioProcessor()
StringArray(emulators, sizeof(emulators)/sizeof(String)))
);
const String percussion[] = { "Off", "Bass drum", "Snare", "Tom", "Cymbal", "Hi-hat" };
params.push_back(new EnumFloatParameter("Percussion Mode",
StringArray(percussion, sizeof(percussion) / sizeof(String)))
);
for(unsigned int i = 0; i < params.size(); i++) {
paramIdxByName[params[i]->getName()] = i;
}
@ -171,6 +176,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
0.5f, 0.3f, 0.1f, 0.6f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_0 (i_params_0, i_params_0 + sizeof(i_params_0) / sizeof(float));
programs["Mercenary Bass"] = std::vector<float>(v_i_params_0);
@ -189,6 +195,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
1.0f, 0.5f, 0.2f, 0.3f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_19189 (i_params_19189, i_params_19189 + sizeof(i_params_19189) / sizeof(float));
programs["Patrol Bass"] = std::vector<float>(v_i_params_19189);
@ -207,6 +214,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
1.0f, 0.1f, 0.9f, 1.0f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_38377 (i_params_38377, i_params_38377 + sizeof(i_params_38377) / sizeof(float));
programs["Subdue Bass"] = std::vector<float>(v_i_params_38377);
@ -225,6 +233,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
0.1f, 0.9f, 0.1f, 0.1f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_38392 (i_params_38392, i_params_38392 + sizeof(i_params_38392) / sizeof(float));
programs["Dark Future Sweep"] = std::vector<float>(v_i_params_38392);
@ -243,6 +252,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
1.0f, 0.7f, 0.0f, 0.4f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_39687 (i_params_39687, i_params_39687 + sizeof(i_params_39687) / sizeof(float));
programs["Sinister Bass"] = std::vector<float>(v_i_params_39687);
@ -261,6 +271,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
1.0f, 0.4f, 0.5f, 0.3f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_76784 (i_params_76784, i_params_76784 + sizeof(i_params_76784) / sizeof(float));
programs["Buzcut Bass"] = std::vector<float>(v_i_params_76784);
@ -279,6 +290,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
0.6f, 0.7f, 0.1f, 0.1f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_97283 (i_params_97283, i_params_97283 + sizeof(i_params_97283) / sizeof(float));
programs["Death Toll Bell"] = std::vector<float>(v_i_params_97283);
@ -298,6 +310,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
1.0f, 0.4f, 0.2f, 0.3f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_3136 (i_params_3136, i_params_3136 + sizeof(i_params_3136) / sizeof(float));
programs["Westwood Chime"] = std::vector<float>(v_i_params_3136);
@ -316,6 +329,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
0.2f, 0.1f, 0.1f, 0.0f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_7254 (i_params_7254, i_params_7254 + sizeof(i_params_7254) / sizeof(float));
programs["Desert Pipe"] = std::vector<float>(v_i_params_7254);
@ -334,6 +348,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
0.1f, 0.1f, 0.1f, 0.1f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_20108 (i_params_20108, i_params_20108 + sizeof(i_params_20108) / sizeof(float));
programs["Y2180 Strings"] = std::vector<float>(v_i_params_20108);
@ -352,6 +367,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
0.9f, 0.1f, 0.0f, 1.0f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_27550 (i_params_27550, i_params_27550 + sizeof(i_params_27550) / sizeof(float));
programs["Emperor Chord"] = std::vector<float>(v_i_params_27550);
@ -370,6 +386,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
0.85f, 0.3f, 0.1f, 0.6f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_harpsi (i_params_harpsi, i_params_harpsi + sizeof(i_params_harpsi) / sizeof(float));
programs["Harpsi"] = std::vector<float>(v_i_params_harpsi);
@ -388,6 +405,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
0.45f, 0.45f, 0.1f, 0.6f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_tromba (i_params_tromba, i_params_tromba + sizeof(i_params_tromba) / sizeof(float));
programs["Tromba"] = std::vector<float>(v_i_params_tromba);
@ -406,6 +424,7 @@ void JuceOplvstiAudioProcessor::initPrograms()
1.00f, 0.75f, 0.5f, 0.5f, // adsr
0.0f, 0.0f, // velocity sensitivity
0.0f, // emulator
0.0f, // percussion mode
};
std::vector<float> v_i_params_bassdrum (i_params_bassdrum, i_params_bassdrum + sizeof(i_params_bassdrum) / sizeof(float));
programs["bassdrum"] = std::vector<float>(v_i_params_bassdrum);
@ -475,6 +494,7 @@ int JuceOplvstiAudioProcessor::getEnumParameter (String name)
return p->getParameterIndex();
}
// Parameters which apply directly to the OPL
void JuceOplvstiAudioProcessor::setParameter (int index, float newValue)
{
FloatParameter* p = params[index];
@ -518,6 +538,8 @@ void JuceOplvstiAudioProcessor::setParameter (int index, float newValue)
Opl->VibratoDepth(((EnumFloatParameter*)p)->getParameterIndex() > 0);
} else if (name.startsWith("Emulator")) {
Opl->SetEmulator((Emulator)((EnumFloatParameter*)p)->getParameterIndex());
} else if (name.startsWith("Percussion")) {
Opl->SetPercussionMode(((EnumFloatParameter*)p)->getParameterIndex() > 0);
}
}
@ -532,6 +554,7 @@ void JuceOplvstiAudioProcessor::loadInstrumentFromFile(String filename)
updateGuiIfPresent();
}
// Used to configure parameters from .SBI instrument file
void JuceOplvstiAudioProcessor::setParametersByRegister(int register_base, int op, uint8 value)
{
const String operators[] = {"Modulator", "Carrier"};
@ -561,7 +584,6 @@ void JuceOplvstiAudioProcessor::setParametersByRegister(int register_base, int o
setEnumParameter("Algorithm", value & 0x1);
break;
case 0xE0:
printf("Setting wave to %d", value & 0x7);
setEnumParameter(operators[op] + " Wave", value & 0x7);
break;
default:
@ -688,6 +710,7 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
MidiMessage midi_message;
int sample_number;
int perc = getEnumParameter("Percussion Mode");
while (midi_buffer_iterator.getNextEvent(midi_message,sample_number)) {
if (midi_message.isNoteOn()) {
//note on at sample_number samples after
@ -696,21 +719,28 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
float noteHz = (float)MidiMessage::getMidiNoteInHertz(n);
int ch;
if (!available_channels.empty())
{
ch = available_channels.front();
available_channels.pop_front();
}
else
{
ch = used_channels.back(); // steal earliest/longest running active channel if out of free channels
used_channels.pop_back();
Opl->KeyOff(ch);
}
if (perc > 0) {
static const Drum drumIndex[] = { BDRUM, SNARE, TOM, CYMBAL, HIHAT };
for (int i = 1; i <= Hiopl::CHANNELS; i++) {
Opl->SetFrequency(i, noteHz, false);
}
Opl->HitPercussion(drumIndex[perc - 1]);
} else {
if (!available_channels.empty())
{
ch = available_channels.front();
available_channels.pop_front();
}
else
{
ch = used_channels.back(); // steal earliest/longest running active channel if out of free channels
used_channels.pop_back();
Opl->KeyOff(ch);
}
used_channels.push_front(ch);
used_channels.push_front(ch);
switch (getEnumParameter("Carrier Velocity Sensitivity")) {
switch (getEnumParameter("Carrier Velocity Sensitivity")) {
case 0:
Opl->SetAttenuation(ch, 2, getEnumParameter("Carrier Attenuation"));
break;
@ -720,8 +750,8 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
case 2:
Opl->SetAttenuation(ch, 2, 32 - (midi_message.getVelocity() / 4));
break;
}
switch (getEnumParameter("Modulator Velocity Sensitivity")) {
}
switch (getEnumParameter("Modulator Velocity Sensitivity")) {
case 0:
Opl->SetAttenuation(ch, 1, getEnumParameter("Modulator Attenuation"));
break;
@ -731,32 +761,39 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
case 2:
Opl->SetAttenuation(ch, 1, 63 - (midi_message.getVelocity() / 2));
break;
}
Opl->KeyOn(ch, noteHz);
active_notes[ch] = n;
applyPitchBend();
}
Opl->KeyOn(ch, noteHz);
active_notes[ch] = n;
applyPitchBend();
}
else if (midi_message.isNoteOff()) {
int n = midi_message.getNoteNumber();
int ch = 1;
while (ch <= Hiopl::CHANNELS && n != active_notes[ch]) {
ch += 1;
if (perc > 0) {
Opl->ReleasePercussion();
}
if (ch <= Hiopl::CHANNELS)
{
for (auto i = used_channels.begin(); i != used_channels.end(); ++i)
{
if (*i == ch)
{
used_channels.erase(i);
available_channels.push_back(ch);
break;
}
else {
int n = midi_message.getNoteNumber();
int ch = 1;
while (ch <= Hiopl::CHANNELS && n != active_notes[ch]) {
ch += 1;
}
if (ch <= Hiopl::CHANNELS)
{
for (auto i = used_channels.begin(); i != used_channels.end(); ++i)
{
if (*i == ch)
{
used_channels.erase(i);
available_channels.push_back(ch);
Opl->KeyOff(ch);
active_notes[ch]=NO_NOTE;
break;
}
}
Opl->KeyOff(ch);
active_notes[ch] = NO_NOTE;
}
}
}
else if (midi_message.isPitchWheel()) {
@ -833,7 +870,7 @@ void JuceOplvstiAudioProcessor::setStateInformation (const void* data, int sizeI
i_program = program;
}
for (int i=0; i<getNumParameters(); ++i)
for (int i=0; i < getNumParameters(); ++i)
{
var param = v[stringToIdentifier(getParameterName(i))];

View file

@ -177,6 +177,19 @@ void Hiopl::SetModulatorFeedback(int ch, int level) {
_WriteReg(0xc0+offset, (Bit8u)level, 0x0e);
}
void Hiopl::SetPercussionMode(bool enable) {
_WriteReg(0xbd, enable ? 0x20 : 0x0, 0x20);
}
void Hiopl::HitPercussion(Drum drum) {
Bit8u mask = (Bit8u)drum;
_WriteReg(0xbd, mask, mask);
}
void Hiopl::ReleasePercussion() {
_WriteReg(0xbd, 0x0, 0x1f);
}
void Hiopl::KeyOn(int ch, float frqHz) {
Hiopl::SetFrequency(ch, frqHz, true);
}

View file

@ -19,6 +19,11 @@ enum Emulator
DOSBOX=0, ZDOOM=1
};
enum Drum
{
BDRUM=0x10, SNARE=0x8, TOM=0x4, CYMBAL=0x2, HIHAT=0x1
};
class Hiopl {
public:
@ -26,6 +31,9 @@ class Hiopl {
static const int OSCILLATORS = 2;
Hiopl(int buflen, Emulator emulator=ZDOOM);
void SetEmulator(Emulator emulator);
void SetPercussionMode(bool enable);
void HitPercussion(Drum drum);
void ReleasePercussion();
void Generate(int length, short* buffer);
void Generate(int length, float* buffer);

View file

@ -1,35 +1,22 @@
# OPL2 VST plugin #
# OPL VST plugin #
This VST instrument emulates the OPL2 sound chip.
This VST instrument provides an emulated OPL sound chip. It provides all features of the OPL2, and some features of the OPL3.
See here for Windows binaries, screenshots etc: http://bsutherland.github.io/JuceOPLVSTi/
## What's an OPL2? ##
## What's an OPL? ##
The OPL2 is a digital sound synthesis chip developed by Yamaha in the mid 1980s. Among other products, it was used in sound cards for PC, including the Adlib card, and later the Sound Blaster Pro, which had two OPL2 chips for stereo output.
The OPL is a digital sound synthesis chip developed by Yamaha in the mid 1980s. Among other products, it was used in sound cards for PC, including the Adlib card.
At a technical level: the chip can produce 9 channels of sound, each channel having 2 oscillators. Each pair of oscillators is usually combined via phase modulation (basically frequency modulation). Each oscillator can produce one of four variations of a sine wave (sine, half sine, absolute sine and quarter sine), and has an ADSR envelope controlling its amplitude. The unusual waveforms give it a characteristic sound.
The chip is programmed through an 8-bit write-only register interface in a 256 byte address space.
At a technical level: the emulator has channels comprised of 2 oscillators each. Each pair of oscillators is usually combined via phase modulation (basically frequency modulation). Each oscillator can produce one of eight waveforms (sine, half sine, absolute sine, quarter sine, alternating sine, camel sine, square, logarithmic sawtooth), and has an ADSR envelope controlling its amplitude. The unusual waveforms give it a characteristic sound.
## Caveats and Limitations ##
Before I wrote this, I didn't know much about VST or the OPL at a technical level. This is the first VST plugin I've written.
Some limitations:
- Sample rate is locked at 44.1 kHz
- I'm a bit unsure as to whether the keyscale attenuation values are correct. I had two documents with conflicting information on that.
- There may be some subtle "out by one" type issues lingering around.
- I haven't added support for the built-in percussion. It's very poorly documented in the data sheet and seems like a lot of people never used it anyway.
In hindsight I would have implemented things a bit differently, but it all basically works.
Juce takes care of some of the tedious bits, but I was hoping it would take care of some more of the repetitive details, like converting to and from int/enum/float etc into the normalized floating point values used by VST. If it doesn't exist already, I think that I'd definitely write another layer of abstraction on top of Juce next time.
Before I wrote this, I didn't know much about VST or the OPL at a technical level. This is the first VST plugin I've written. The sample rate is locked at 44.1 kHz. In hindsight I would have implemented things a bit differently, but it all basically works, and is now reasonably well tested.
## How do I use it? ##
Each instance of the plugin emulates an entire OPL2 chip, but polyphony is implemented by using a channel per note, with parameter changes applied to all channels. With this plugin, essentially you are just working with two operators.
Each instance of the plugin emulates an entire OPL chip, but polyphony is implemented by using a channel per note. Parameter changes applied to all channels. With this plugin, essentially you are just working with two operators.
Some documentation which may be useful:
@ -38,6 +25,16 @@ Some documentation which may be useful:
- [AdLib programming guide](http://www.shipbrook.net/jeff/sb.html) Dates back to 1991!
- [Another programming guide](http://www.ugcs.caltech.edu/~john/computer/opledit/tech/opl3.txt) This one is for the OPL3, but most of the information still applies.
### Percussion
Percussion mode is now supported! This mode is not very well documented, even in the original Yamaha documentation. It works with the DOSBox emulator, but doesn't seem to work too well in the ZDoom emulator. Here are some tips on using it based on experimentation and looking at the DOSBox source code.
- Bass drum: Uses both operators. Essentially just doubles output amplitude?
- Snare: Uses carrier settings. Abs-sine waveform recommended.
- Tom: Uses modulator settings. Sine waveform recommended.
- Cymbal: Uses carrier settings. Half-sine recommended.
- Hi-hat: Uses modulator settings. Half-sine recommended.
## How did you create the instrument programs? ##
To figure out the parameters used by the original games, I just added a printf to the DOSBox OPL emulator, compiled DOSBox, ran the games, and captured their output as raw register writes with timestamps.
@ -46,11 +43,11 @@ I hacked together a Python script which parses the raw output, identifying uniqu
## How did you do this? ##
The emulation (ie, the hard part!) is taken straight from the excellent DOSBox emulator. The current DOSBox OPL emulator was rewritten a couple of years ago to use only integer math. The previous DOSBox OPL emulator, using floating point math, was derived from the MAME OPL emulator, which in turn was derived from ADLIBEMU, a library by Ken Silverman (who wrote the engine used in Duke Nukem 3D). I also used a function from libgamemusic by Adam Nielsen for converting frequencies from Hertz into the "FNUM" values used by the OPL2.
The emulation (ie, the hard part!) is taken straight from the excellent DOSBox and ZDoom projects. I also used a function from libgamemusic by Adam Nielsen for converting frequencies from Hertz into the "FNUM" values used by the OPL.
The VST was written using Juce, a cross-platform C++ library inspired by the Java JDK. Among other things, Juce provides a GUI for generating boilerplate for audio plugins.
The code I wrote is basically conversion glue between the DOSBox OPL emulator, the VST interface, and human friendly values.
The code I wrote is essentially a device driver for the emulated OPL, implementing the VST interface, and providing a UI.
## Building ##
@ -60,10 +57,10 @@ So far I've only built under Windows. Thanks to Juce, it should be possible to b
1. Download Juce (http://www.juce.com/)
2. Download the VST SDK (http://www.steinberg.net/en/company/developer.html)
3. Build and run "The Introjucer"
3. Run "The Introjucer" executable included in Juce.
4. Open JuceOPLVSTi.jucer
- Make any changes to the GUI layout and components here (PluginEditor.cpp).
- Save PluginEditor.cpp if modified
5. Hit "Save Project and Open in Visual Studio". I used Visual Studio Express 2012.
6. (For Windows XP compatibility) In the project's properties, set platform toolset to v110_xp (Configuration Properties > General).
5. Hit "Save Project and Open in Visual Studio". I use Visual Studio Express 2013.
6. (For Windows XP compatibility) In the project's properties, set platform toolset to Windows XP (Configuration Properties > General).
7. Build!