Skip to content

Commit

Permalink
Merge pull request #54 from DhrBaksteen/Instrument_functions
Browse files Browse the repository at this point in the history
New instrument object and functions
  • Loading branch information
DhrBaksteen authored Nov 10, 2019
2 parents caea4c1 + 539e12a commit fd5459b
Show file tree
Hide file tree
Showing 11 changed files with 442 additions and 122 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This repository contains the OPL2 Audio Board library for Arduino, Teensy, Raspb
* Emulation with DosBox; you can use the board to output MIDI music (Teensy++ 2.0 and later)
* Use the board directly as a synthesizer by using the [OPL3BankEditor](https://github.com/Wohlstand/OPL3BankEditor) software by Wohlstand

Current library version is 1.4.7
Current library version is 1.5.0

To obtain your own OPL2 Audio Board visit the [Tindie store](https://www.tindie.com/products/DhrBaksteen/opl2-audio-board/).

Expand Down
2 changes: 1 addition & 1 deletion build
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ echo "\033[1;36m / | \\ | / /_/ | | ( /_/ ) | | ( /_/ ) __ \\|
echo "\033[1;34m \\____|__ /____/\\____ | |__|\\___/ |______ /\\___(____ /__| \\____ | "
echo "\033[1;34m \\/ \\/ \\/ \\/ \\/ \033[0m"
echo "Installation script for Raspberry Pi and compatibles"
echo "Library version 1.4.7, 3rd of September 2019"
echo "Library version 1.5.0, 10th of November 2019"
echo "Copyright (c) 2016-2019 Maarten Janssen, Cheerful"
echo ""

Expand Down
13 changes: 6 additions & 7 deletions examples/DemoTune/DemoTune.ino
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,11 @@ void setup() {
opl2.init();

// Setup channels 0, 1 and 2.
opl2.setInstrument(0, INSTRUMENT_PIANO1);
opl2.setBlock (0, 5);
opl2.setInstrument(1, INSTRUMENT_PIANO1);
opl2.setBlock (1, 4);
opl2.setInstrument(2, INSTRUMENT_PIANO1);
opl2.setBlock (2, 4);
Instrument piano = opl2.loadInstrument(INSTRUMENT_PIANO1);
for (int i = 0; i < 3; i ++) {
opl2.setInstrument(i, piano);
opl2.setBlock(i, 4);
}
}


Expand Down Expand Up @@ -180,7 +179,7 @@ float parseDuration(struct Tune *tune) {
} else {
tune->index --;
}

// Calculate note duration in ms.
duration = (60.0f / tempo) * duration * 1000;
return duration;
Expand Down
18 changes: 12 additions & 6 deletions examples/Drums/Drums.ino
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,18 @@ void setup() {
opl2.init();

// Set percussion mode and load instruments.
Instrument bass = opl2.loadInstrument(INSTRUMENT_BDRUM1);
Instrument snare = opl2.loadInstrument(INSTRUMENT_RKSNARE1);
Instrument tom = opl2.loadInstrument(INSTRUMENT_TOM2);
Instrument cymbal = opl2.loadInstrument(INSTRUMENT_CYMBAL1);
Instrument hihat = opl2.loadInstrument(INSTRUMENT_HIHAT2);

opl2.setPercussion(true);
opl2.setInstrument(0, INSTRUMENT_BDRUM1);
opl2.setInstrument(0, INSTRUMENT_RKSNARE1);
opl2.setInstrument(0, INSTRUMENT_TOM2);
opl2.setInstrument(0, INSTRUMENT_CYMBAL1);
opl2.setInstrument(0, INSTRUMENT_HIHAT2);
opl2.setDrumInstrument(bass);
opl2.setDrumInstrument(snare);
opl2.setDrumInstrument(tom);
opl2.setDrumInstrument(cymbal);
opl2.setDrumInstrument(hihat);

// Set octave and frequency for bass drum.
opl2.setBlock(6, 4);
Expand All @@ -56,7 +62,7 @@ void setup() {

void loop() {
bool bass = i % 4 == 0; // Bass drum every 1st tick
bool snare = (i + 2) % 4 == 0; // Snare drum every 3rd tick
bool snare = i % 4 == 2; // Snare drum every 3rd tick
bool tom = false; // No tom tom
bool cymbal = i % 32 == 0; // Cymbal every 32nd tick
bool hiHat = true; // Hi-hat every tick
Expand Down
161 changes: 93 additions & 68 deletions examples/Teensy/TeensyMidi/TeensyMidi.ino
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* This example can be used together with a Teensy 2.0 or later to use the OPL2 Audio Board as a MIDI device. To
* configure the Teensy as a MIDI device set USB Type to MIDI in the IDE using Tools > USB Type > MIDI. Once connected
* the board should appear in the device list as 'OPL2 AUdio Board MIDI'. You can now use test the board with, for
* example, MIDI-OX, your favorite music creation software or DosBox!
* example, MIDI-OX, your favorite music creation software or DosBox!
*
* OPL2 board is connected as follows:
* Pin 8 - Reset
Expand All @@ -28,21 +28,26 @@
#define CONTROL_RESET_ALL 121
#define CONTROL_ALL_NOTES_OFF 123

// Channel mapping to keep track of MIDI to OPL2 channel mapping.
struct ChannelMapping {

// OPL2 channel properties.
struct Opl2Channel {
byte midiChannel;
byte midiNote;
float midiVelocity;
float op1Level;
float op2Level;
float noteVelocity;
};

// Midi channel properties.
struct MidiChannel {
Instrument instrument;
unsigned char *instrumentDataPtr;
byte program;
float volume;
};

OPL2 opl2;
ChannelMapping channelMap[OPL2_NUM_CHANNELS];
Opl2Channel opl2ChannelMap[OPL2_NUM_CHANNELS];
MidiChannel midiChannelMap[MIDI_NUM_CHANNELS];
byte oldestChannel[OPL2_NUM_CHANNELS];
byte programMap[MIDI_NUM_CHANNELS];
float channelVolumes[MIDI_NUM_CHANNELS];


/**
Expand Down Expand Up @@ -72,7 +77,7 @@ void loop() {
/**
* Get a free OPL2 channel to play a note. If all channels are occupied then recycle the oldes one.
*/
byte getFreeChannel(byte midiChannel) {
byte getFreeChannel(byte midiChannel) {
byte opl2Channel = 255;
byte oldestIndex = 0;

Expand All @@ -90,7 +95,7 @@ byte getFreeChannel(byte midiChannel) {
if (opl2Channel == 255) {
opl2Channel = oldestChannel[0];
for (byte i = 0; i < OPL2_NUM_CHANNELS; i ++) {
if (channelMap[oldestChannel[i]].midiChannel == MIDI_DRUM_CHANNEL) {
if (opl2ChannelMap[oldestChannel[i]].midiChannel == MIDI_DRUM_CHANNEL) {
opl2Channel = oldestChannel[i];
oldestIndex = i;
break;
Expand All @@ -114,9 +119,12 @@ byte getFreeChannel(byte midiChannel) {
* Set the volume of operators 1 and 2 of the given OPL2 channel according to the settings of the given MIDI channel.
*/
void setOpl2ChannelVolume(byte opl2Channel, byte midiChannel) {
float volume = channelMap[opl2Channel].midiVelocity * channelVolumes[midiChannel];
byte volumeOp1 = round(channelMap[opl2Channel].op1Level * volume * 63.0);
byte volumeOp2 = round(channelMap[opl2Channel].op2Level * volume * 63.0);
Instrument instrument = midiChannelMap[midiChannel].instrument;
float volume = opl2ChannelMap[opl2Channel].noteVelocity * midiChannelMap[midiChannel].volume;
float op1Level = (float)(63 - instrument.operators[OPERATOR1].outputLevel) / 63.0;
float op2Level = (float)(63 - instrument.operators[OPERATOR2].outputLevel) / 63.0;
byte volumeOp1 = round(op1Level * volume * 63.0);
byte volumeOp2 = round(op2Level * volume * 63.0);
opl2.setVolume(opl2Channel, OPERATOR1, 63 - volumeOp1);
opl2.setVolume(opl2Channel, OPERATOR2, 63 - volumeOp2);
}
Expand All @@ -125,59 +133,62 @@ void setOpl2ChannelVolume(byte opl2Channel, byte midiChannel) {
/**
* Handle a note on MIDI event to play a note.
*/
void onNoteOn(byte channel, byte note, byte velocity) {
channel = channel % 16;
void onNoteOn(byte midiChannel, byte note, byte velocity) {
midiChannel = midiChannel % 16;

// Treat notes with a velocity of 0 as note off.
if (velocity == 0) {
onNoteOff(channel, note, velocity);
onNoteOff(midiChannel, note, velocity);
return;
}

// Get an available OPL2 channel and setup instrument parameters.
byte opl2Channel = getFreeChannel(channel);
if (channel != MIDI_DRUM_CHANNEL) {
opl2.setInstrument(opl2Channel, midiInstruments[programMap[channel]]);
} else {
if (note >= DRUM_NOTE_BASE && note < DRUM_NOTE_BASE + NUM_MIDI_DRUMS) {
opl2.setInstrument(opl2Channel, midiDrums[note - DRUM_NOTE_BASE]);
} else {
return;
}
// Fetch pointer to the instrument.
unsigned char *instrumentDataPtr = NULL;
if (midiChannel != MIDI_DRUM_CHANNEL) {
instrumentDataPtr = midiInstruments[midiChannelMap[midiChannel].program];
} else if (note >= DRUM_NOTE_BASE && note < DRUM_NOTE_BASE + NUM_MIDI_DRUMS) {
instrumentDataPtr = midiDrums[note - DRUM_NOTE_BASE];
}

// Register channel mapping.
channelMap[opl2Channel].midiChannel = channel;
channelMap[opl2Channel].midiNote = note;
channelMap[opl2Channel].midiVelocity = log(min((float)velocity, 127.0)) / log(127.0);
channelMap[opl2Channel].op1Level = (float)(63 - opl2.getVolume(opl2Channel, OPERATOR1)) / 63.0;
channelMap[opl2Channel].op2Level = (float)(63 - opl2.getVolume(opl2Channel, OPERATOR2)) / 63.0;

// Set operator output levels based on note velocity.
setOpl2ChannelVolume(opl2Channel, channel);

// Calculate octave and note number and play note!
byte opl2Octave = 4;
byte opl2Note = NOTE_C;
if (channel != MIDI_DRUM_CHANNEL) {
note = max(24, min(note, 119));
opl2Octave = 1 + (note - 24) / 12;
opl2Note = note % 12;
if (instrumentDataPtr != NULL) {
// Load new instrument if needed.
if (instrumentDataPtr != midiChannelMap[midiChannel].instrumentDataPtr) {
midiChannelMap[midiChannel].instrument = opl2.loadInstrument(instrumentDataPtr);
}

// Register channel mapping.
byte opl2Channel = getFreeChannel(midiChannel);
opl2ChannelMap[opl2Channel].midiChannel = midiChannel;
opl2ChannelMap[opl2Channel].midiNote = note;
opl2ChannelMap[opl2Channel].noteVelocity = log(min((float)velocity, 127.0)) / log(127.0);

// Calculate octave and note number.
byte opl2Octave = 4;
byte opl2Note = NOTE_C;
if (midiChannel != MIDI_DRUM_CHANNEL) {
note = max(24, min(note, 119));
opl2Octave = 1 + (note - 24) / 12;
opl2Note = note % 12;
}

// Set instrument registers and play note.
opl2.setInstrument(opl2Channel, midiChannelMap[midiChannel].instrument, opl2ChannelMap[opl2Channel].noteVelocity);
opl2.playNote(opl2Channel, opl2Octave, opl2Note);
}
opl2.playNote(opl2Channel, opl2Octave, opl2Note);
}


/**
* Handle a note off MIDI event to stop playing a note.
*/
void onNoteOff(byte channel, byte note, byte velocity) {
channel = channel % 16;
void onNoteOff(byte midiChannel, byte note, byte velocity) {
midiChannel = midiChannel % 16;

for (byte i = 0; i < OPL2_NUM_CHANNELS; i ++) {
if (channelMap[i].midiChannel == channel && channelMap[i].midiNote == note) {
if (opl2ChannelMap[i].midiChannel == midiChannel && opl2ChannelMap[i].midiNote == note) {
opl2.setKeyOn(i, false);
channelMap[i].midiChannel = 0xFF;
channelMap[i].midiNote = 0x00;
opl2ChannelMap[i].midiChannel = 0xFF;
opl2ChannelMap[i].midiNote = 0x00;

// Move channel to the back of recently used channels list to prevent it from
// being reused immediately and the release portion of the note being clubbored.
Expand All @@ -202,25 +213,25 @@ void onNoteOff(byte channel, byte note, byte velocity) {
/**
* Handle instrument change on the given MIDI channel.
*/
void onProgramChange(byte channel, byte program) {
programMap[channel % 16] = min(program, 127);
void onProgramChange(byte midiChannel, byte program) {
midiChannelMap[midiChannel % 16].program = min(program, 127);
}


/**
* Handle MIDI control changes on the given channel.
*/
void onControlChange(byte channel, byte control, byte value) {
channel = channel % 16;
void onControlChange(byte midiChannel, byte control, byte value) {
midiChannel = midiChannel % 16;

switch (control) {

// Change volume of a MIDI channel.
case CONTROL_VOLUME: {
channelVolumes[channel] = log(min((float)value, 127.0)) / log(127.0);
midiChannelMap[midiChannel].volume = log(min((float)value, 127.0)) / log(127.0);
for (byte i = 0; i < OPL2_NUM_CHANNELS; i ++) {
if (channelMap[i].midiChannel == channel && opl2.getKeyOn(i)) {
setOpl2ChannelVolume(i, channel);
if (opl2ChannelMap[i].midiChannel == midiChannel && opl2.getKeyOn(i)) {
setOpl2ChannelVolume(i, midiChannel);
}
}
break;
Expand All @@ -229,7 +240,13 @@ void onControlChange(byte channel, byte control, byte value) {
// Reset all controller values.
case CONTROL_RESET_ALL:
for (byte i = 0; i < MIDI_NUM_CHANNELS; i ++) {
channelVolumes[channel] = 0.8;
midiChannelMap[i].volume = log(127.0 * 0.8) / log(127.0);
}

for (byte i = 0; i < OPL2_NUM_CHANNELS; i ++) {
if (opl2.getKeyOn(i)) {
setOpl2ChannelVolume(i, opl2ChannelMap[i].midiChannel);
}
}
break;

Expand All @@ -244,8 +261,8 @@ void onControlChange(byte channel, byte control, byte value) {
// Silence all MIDI channels.
case CONTROL_ALL_NOTES_OFF: {
for (byte i = 0; i < OPL2_NUM_CHANNELS; i ++) {
if (channelMap[i].midiChannel == channel) {
onNoteOff(channelMap[i].midiChannel, channelMap[i].midiNote, 0);
if (opl2ChannelMap[i].midiChannel == midiChannel) {
onNoteOff(opl2ChannelMap[i].midiChannel, opl2ChannelMap[i].midiNote, 0);
}
}
break;
Expand All @@ -264,16 +281,24 @@ void onControlChange(byte channel, byte control, byte value) {
void onSystemReset() {
opl2.init();

// Silence all channels and set default instrument.
for (byte i = 0; i < OPL2_NUM_CHANNELS; i ++) {
opl2.setKeyOn(i, false);
opl2.setInstrument(i, midiInstruments[0]);
oldestChannel[i] = i;
}
// Default channel volume to 80%
float defaultVolume = log(127.0 * 0.8) / log(127.0);
unsigned char *defaultInstrumentPtr = midiInstruments[0];
Instrument defaultInstrument = opl2.loadInstrument(defaultInstrumentPtr);

// Reset default MIDI player parameters.
for (byte i = 0; i < MIDI_NUM_CHANNELS; i ++) {
programMap[i] = 0;
channelVolumes[i] = 0.8;
midiChannelMap[i].program = 0;
midiChannelMap[i].volume = defaultVolume;
midiChannelMap[i].instrumentDataPtr = defaultInstrumentPtr;
midiChannelMap[i].instrument = defaultInstrument;
}

// Silence all channels and set default instrument.
for (byte i = 0; i < OPL2_NUM_CHANNELS; i ++) {
oldestChannel[i] = i;
opl2ChannelMap[i].midiChannel = 0;
opl2ChannelMap[i].midiNote = 0;
opl2ChannelMap[i].noteVelocity = 0.0;
}
}
7 changes: 4 additions & 3 deletions examples_pi/demotune/demotune.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,10 @@ int main(int argc, char **argv) {
opl2.init();

// Setup channels 0, 1 and 2 instruments.
opl2.setInstrument(0, INSTRUMENT_PIANO1);
opl2.setInstrument(1, INSTRUMENT_PIANO1);
opl2.setInstrument(2, INSTRUMENT_PIANO1);
Instrument piano = opl2.loadInstrument(INSTRUMENT_PIANO1);
for (int i = 0; i < 3; i ++) {
opl2.setInstrument(i, piano);
}

int hasData = 1;
while(hasData) {
Expand Down
18 changes: 12 additions & 6 deletions examples_pi/drums/drums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,18 @@ int main(int argc, char **argv) {
opl2.init();

// Set percussion mode and load instruments.
Instrument bass = opl2.loadInstrument(INSTRUMENT_BDRUM1);
Instrument snare = opl2.loadInstrument(INSTRUMENT_RKSNARE1);
Instrument tom = opl2.loadInstrument(INSTRUMENT_TOM2);
Instrument cymbal = opl2.loadInstrument(INSTRUMENT_CYMBAL1);
Instrument hihat = opl2.loadInstrument(INSTRUMENT_HIHAT2);

opl2.setPercussion(true);
opl2.setInstrument(0, INSTRUMENT_BDRUM1);
opl2.setInstrument(0, INSTRUMENT_RKSNARE1);
opl2.setInstrument(0, INSTRUMENT_TOM2);
opl2.setInstrument(0, INSTRUMENT_CYMBAL1);
opl2.setInstrument(0, INSTRUMENT_HIHAT2);
opl2.setDrumInstrument(bass);
opl2.setDrumInstrument(snare);
opl2.setDrumInstrument(tom);
opl2.setDrumInstrument(cymbal);
opl2.setDrumInstrument(hihat);

// Set octave and frequency for bass drum.
opl2.setBlock(6, 4);
Expand All @@ -55,7 +61,7 @@ int main(int argc, char **argv) {
// Play drum loop
while (true) {
bool bass = i % 4 == 0; // Bass drum every 1st tick
bool snare = (i + 2) % 4 == 0; // Snare drum every 3rd tick
bool snare = i % 4 == 2; // Snare drum every 3rd tick
bool tom = false; // No tom tom
bool cymbal = i % 32 == 0; // Cymbal every 32nd tick
bool hiHat = true; // Hi-hat every tick
Expand Down
Loading

0 comments on commit fd5459b

Please sign in to comment.