DhrBaksteen / ArduinoOPL2

Arduino library for use with the OPL2 board (YM3812) and OPL3Duo (YMF262)
MIT License
198 stars 39 forks source link

Explain how PlayDRO works #1

Closed ghost closed 7 years ago

ghost commented 7 years ago

The PlayDRO example sketch is neat, but it isn't very well documented. Could you explain how everything works?

DhrBaksteen commented 7 years ago

The DRO format is documented pretty well on this site: http://www.shikadi.net/moddingwiki/DRO_Format.

The main gist of it is that a registerMap is read from the file. This array contains the OPL2 registers that the data is sent to. The playDroSong function reads the code (index into the register map) and data byte to send to the OPL. If the code is a registerMap entry it retrieves the OPL register from the registerMap, sends the data byte and returns a 0ms delay. If the code is a short or a long delay the return value is the number of milliseconds to delay until the next data pair is sent to the OPL2.

The main loop keeps sending data until all song data is sent. for a 0ms delay the next pair will be sent immediately, otherwise the Arduino delays for the returned amount of milliseconds.

The routine assumes version 2.0 DRO files made with DosBox 0.73 or later

Hope that explains it...

ghost commented 7 years ago

Thanks for clearing that up. I think a script that runs on the PC that reads IMF and DRO files and sends the data to the Arduino in real-time via serial while the Arduino sends the data to the OPL2, rather than the Arduino reading the file from an SD card would be useful for the example folder in the OPL2 library. If such a script existed, what would the script and Arduino sketch look like?

DhrBaksteen commented 7 years ago

The Arduino sketch would be very simple. Assuming you do all your decoding on the PC side you would just send register - data pairs over the line. In the main loop it would just wait for Serial.available() > 0, read the incoming byte and as soon as 2 bytes are received it can write a register of the OPL2. You may need a special byte pair though to give the OPL2 a hard reset between songs.

ghost commented 7 years ago

If you find time, I'd be grateful if you were willing to create a sketch like that. The main reason I'd like to see a streaming program for this project is because patches could be made for programs like DosBox which would allow it to interface with the OPL2 board rather than relying on emulation.

ghost commented 7 years ago

I've made somewhat of a thing now. Here's the Arduino sketch for the streaming program:

include

include "OPL2.h"

OPL2 opl2;

void setup() { opl2.init(); Serial.begin(9600); }

void loop() { while (Serial.available() < 2) { byte reg = Serial.read(); byte data = Serial.read(); opl2.write(reg, data); } }

DhrBaksteen commented 7 years ago

That would almost work :). If you change the condition of the while to while (Serial.available() > 1) { it should work.

ghost commented 7 years ago

Okay, I've changed it. I'm now working on the Python script that is to decode an IMF music file and send the registers to the Arduino, but I'm having trouble getting it to work. If you can see any problems with it, tell me. Here's the code:

!/usr/bin/env python

import serial import struct from time import sleep

file = open("k5t06.imf", "rb") (songLength,) = struct.unpack('<h', file.read(2)) imfSpeed = 560.0 arduino = serial.Serial('/dev/ttyACM0',9600)

if songLength == 0: songLength = 65535 file.seek(4, 0)

def playImfSong(songLength): (reg,) = struct.unpack('B', file.read(1)) (data,) = struct.unpack('B', file.read(1)) (wait,) = struct.unpack('f', file.read(4)) if reg != 0: arduino.write(reg) arduino.write(data) songLength -= 3 return round(wait * (1000 / imfSpeed));

while True: while songLength > 0: wait = playImfSong(songLength) if wait > 0: sleep(wait / 1000) songLength -= 1

Currently, when the program is ran, it throws an error:

Traceback (most recent call last): File "Stream.py", line 27, in wait = playImfSong(songLength) File "Stream.py", line 20, in playImfSong arduino.write(reg) File "/usr/lib/python2.7/dist-packages/serial/serialposix.py", line 518, in write d = to_bytes(data) File "/usr/lib/python2.7/dist-packages/serial/serialutil.py", line 61, in to_bytes for item in seq: TypeError: 'int' object is not iterable

DhrBaksteen commented 7 years ago

Sorry I'm not a Python expert, but my best guess from looking at that error is that the write function expects something like a byte array...

ghost commented 7 years ago

Perhaps a C++ version of this program can be made then. I'm no Python expert either, and I'm still scratching my head as to why the code doesn't run.