Skip to content

Commit 2382c24

Browse files
authored
Merge pull request #65 from DhrBaksteen/OPL2_MIDI_Example
OPL2 MIDI Synth example
2 parents 8c63523 + b35f6ca commit 2382c24

File tree

4 files changed

+236
-4
lines changed

4 files changed

+236
-4
lines changed

build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ echo "\033[1;34m \\/ \\/ \\/ \\/ \033[0m"
2020
echo "For the \033[1;36mOPL2 Audio Board\033[0m and \033[1;36mOPL3 Duo!\033[0m synthesizers"
2121
echo ""
2222
echo "Installation script for Raspberry Pi and compatibles"
23-
echo "Library version 2.0.2, 14th of November 2020"
23+
echo "Library version 2.0.3, 23rd of November 2020"
2424
echo "Copyright (c) 2016-2020 Maarten Janssen, Cheerful"
2525
echo ""
2626

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/**
2+
* This is an example sketch for the OPL2 Audio Board that shows how MIDI controls can be used to change the properties
3+
* of an operator. In this example we assume that serial MIDI data is received from a MIDI shield connected to the Rx(1)
4+
* pin of the Arduino.
5+
*
6+
* The carriers of all OPL2 channels will be set to some preset values and with you MIDI controller or keyboard you can
7+
* play notes. The controls of the MIDI controller can be used to change ADRS, waveform and multiplier ot the carriers.
8+
*
9+
* In this example we assume that a MIDI controller is used that has at least 6 rotary encoders and that each rotary
10+
* encoder controls a different channel. The channel of the control determines what property of the carrier is changed.
11+
* If your MIDI controller is layed out differently you can adjust the changeControl function according to your setup.
12+
*
13+
* Code by Maarten Janssen (maarten@cheerful.nl) 2020-11-23
14+
* Most recent version of the library can be found at my GitHub: https://github.com/DhrBaksteen/ArduinoOPL2
15+
* WWW.CHEERFUL.NL
16+
*/
17+
18+
#include <OPL2.h>
19+
#include <midi_instruments.h>
20+
21+
// MIDI statuses
22+
#define MIDI_NOTE_OFF 0x80
23+
#define MIDI_NOTE_ON 0x90
24+
#define MIDI_POLY_AFTERTOUCH 0xA0
25+
#define MIDI_CONTROL_CHANGE 0xB0
26+
#define MIDI_PROGRAM_CHANGE 0xC0
27+
#define MIDI_CHANNEL_AFTERTOUCH 0xD0
28+
#define MIDI_SYSEX_START 0xF0
29+
#define MIDI_SYSEX_END 0xF7
30+
31+
#define NUM_OPL2_CHANNELS 9
32+
#define NO_NOTE 255
33+
34+
byte midiCommand = 0x00;
35+
byte midiChannel = 0;
36+
byte midiData[2] = { 0x00, 0x00 };
37+
byte midiDataBytes = 0;
38+
byte midiDataIndex = 0;
39+
40+
byte oplChannel = 0;
41+
byte oplNotes[NUM_OPL2_CHANNELS] = {
42+
NO_NOTE, NO_NOTE, NO_NOTE,
43+
NO_NOTE, NO_NOTE, NO_NOTE,
44+
NO_NOTE, NO_NOTE, NO_NOTE
45+
};
46+
47+
OPL2 opl2;
48+
Instrument instrument;
49+
50+
51+
void setup() {
52+
Serial.begin(31250);
53+
opl2.begin();
54+
55+
// Set properties for all channels.
56+
opl2.setWaveFormSelect(true);
57+
for (byte i = 0; i < opl2.getNumChannels(); i ++) {
58+
opl2.setTremolo (i, CARRIER, true);
59+
opl2.setVibrato (i, CARRIER, true);
60+
opl2.setMultiplier(i, CARRIER, 0x04);
61+
opl2.setAttack (i, CARRIER, 0x0A);
62+
opl2.setDecay (i, CARRIER, 0x04);
63+
opl2.setSustain (i, CARRIER, 0x08);
64+
opl2.setRelease (i, CARRIER, 0x04);
65+
opl2.setVolume (i, CARRIER, 0x00);
66+
opl2.setWaveForm (i, CARRIER, 0x00);
67+
opl2.setMaintainSustain(i, CARRIER, false);
68+
}
69+
}
70+
71+
72+
void loop() {
73+
if (Serial.available() > 0 ) {
74+
byte data = Serial.read();
75+
76+
// Is this a MIDI status?
77+
if (data & 0x80) {
78+
79+
// Handle system exclusive data.
80+
if (data == MIDI_SYSEX_START) {
81+
handleSysExEvent();
82+
83+
// For other MIDI statuses extract command, channel and perapre to receive data.
84+
} else {
85+
midiCommand = data & 0xF0;
86+
midiChannel = data & 0x0F;
87+
midiDataIndex = 0;
88+
midiDataBytes = 2;
89+
if (midiCommand == MIDI_PROGRAM_CHANGE || midiCommand == MIDI_CHANNEL_AFTERTOUCH) {
90+
midiDataBytes = 1;
91+
}
92+
}
93+
94+
// Receive MIDI data.
95+
} else {
96+
midiData[midiDataIndex] = (data & 0x7F);
97+
midiDataIndex ++;
98+
99+
// If all data for the MIDI command is received then handle it!
100+
if (midiDataIndex == midiDataBytes) {
101+
handleMidiEvent();
102+
midiDataIndex = 0;
103+
}
104+
}
105+
}
106+
}
107+
108+
109+
/**
110+
* Handle the current MIDI status.
111+
*/
112+
void handleMidiEvent() {
113+
switch (midiCommand) {
114+
case MIDI_NOTE_ON:
115+
playNote();
116+
break;
117+
case MIDI_NOTE_OFF:
118+
stopNote();
119+
break;
120+
case MIDI_POLY_AFTERTOUCH:
121+
break;
122+
case MIDI_CONTROL_CHANGE:
123+
changeControl();
124+
break;
125+
case MIDI_PROGRAM_CHANGE:
126+
break;
127+
case MIDI_CHANNEL_AFTERTOUCH:
128+
break;
129+
default:
130+
break;
131+
}
132+
}
133+
134+
135+
/**
136+
* Do not handle any system exclusive events. Just wait until the end of the system exclusive structure.
137+
*/
138+
void handleSysExEvent() {
139+
byte data = 0x00;
140+
while (data != MIDI_SYSEX_END) {
141+
if (Serial.available()) {
142+
data = Serial.read();
143+
}
144+
}
145+
}
146+
147+
148+
/**
149+
* Play a note on the next available OPL2 channel.
150+
*/
151+
void playNote() {
152+
byte note = midiData[0];
153+
byte velocity = midiData[1];
154+
155+
// Register which note is playing on which channel.
156+
oplNotes[oplChannel] = note;
157+
158+
// Adjust note to valid range and extract octave.
159+
note = max(24, min(note, 119));
160+
byte octave = 1 + (note - 24) / 12;
161+
note = note % 12;
162+
opl2.playNote(oplChannel, octave, note);
163+
164+
// Set OPL2 channel for the next note.
165+
oplChannel = (oplChannel + 1) % NUM_OPL2_CHANNELS;
166+
}
167+
168+
169+
/**
170+
* Stop playing a note by looking up its OPL2 channel and releasing the key.
171+
*/
172+
void stopNote() {
173+
byte note = midiData[0];
174+
byte velocity = midiData[1];
175+
176+
for (byte i = 0; i < NUM_OPL2_CHANNELS; i ++) {
177+
if (oplNotes[i] == note) {
178+
oplNotes[i] = NO_NOTE;
179+
opl2.setKeyOn(1, false);
180+
}
181+
}
182+
}
183+
184+
185+
/**
186+
* Change some of the carrier properties on control changes. Here the control's channel is used to pick the property to
187+
* change. If it's more convenient to use the actual control numbers from your MIDI controller then use midiData[0] to
188+
* get the control number.
189+
*/
190+
void changeControl() {
191+
byte control = midiChannel;
192+
// byte control = midiData[0];
193+
byte value = midiData[1];
194+
stopAll();
195+
196+
for (byte i = 0; i < NUM_OPL2_CHANNELS; i ++) {
197+
switch (control) {
198+
case 0:
199+
opl2.setAttack(i, CARRIER, value);
200+
break;
201+
case 1:
202+
opl2.setDecay(i, CARRIER, value);
203+
break;
204+
case 2:
205+
opl2.setSustain(i, CARRIER, value);
206+
break;
207+
case 3:
208+
opl2.setRelease(i, CARRIER, value);
209+
break;
210+
case 4:
211+
opl2.setWaveForm(i, CARRIER, value);
212+
break;
213+
case 5:
214+
opl2.setMultiplier(i, CARRIER, value);
215+
break;
216+
default:
217+
break;
218+
}
219+
}
220+
}
221+
222+
223+
/**
224+
* Immediately stop playing notes on all OPL2 channels when a control is changed.
225+
*/
226+
void stopAll() {
227+
for (byte i = 0; i < NUM_OPL2_CHANNELS; i ++) {
228+
opl2.setFNumber(i, 0);
229+
opl2.setKeyOn(i, false);
230+
oplNotes[i] = NO_NOTE;
231+
}
232+
}

library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=Arduino OPL2
2-
version=2.0.2
2+
version=2.0.3
33
author=Maarten Janssen <maarten@cheerful.nl>
44
maintainer=Maarten Janssen <maarten@cheerful.nl>
55
sentence=Use this library to control the OPL2 Audio Board or OPL3 Duo!

src/OPL2.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* \____|__ /__| \____ |____/|__|___| /\____/ \_____\ \ |____| |__|
1717
* \/ \/ \/ \/
1818
*
19-
* YM3812 OPL2 Audio Library for Arduino, Raspberry Pi and Orange Pi v2.0.2
19+
* YM3812 OPL2 Audio Library for Arduino, Raspberry Pi and Orange Pi v2.0.3
2020
* Code by Maarten Janssen (maarten@cheerful.nl) 2016-12-18
2121
* WWW.CHEERFUL.NL
2222
*
@@ -36,7 +36,7 @@
3636
* IMPORTANT: Make sure you set the correct BOARD_TYPE in OPL2.h. Default is set to Arduino.
3737
*
3838
*
39-
* Last updated 2020-10-31
39+
* Last updated 2020-11-23
4040
* Most recent version of the library can be found at my GitHub: https://github.com/DhrBaksteen/ArduinoOPL2
4141
* Details about the YM3812 and YMF262 chips can be found at http://www.shikadi.net/moddingwiki/OPL_chip
4242
*

0 commit comments

Comments
 (0)