nukeykt / Nuked-OPN2

Cycle-accurate Yamaha YM3438(YM2612) emulator
GNU Lesser General Public License v2.1
214 stars 15 forks source link

Add more friendly way to send data to registers or provide a short example #4

Closed Wohlstand closed 5 years ago

Wohlstand commented 6 years ago

Hello!

I am trying to figure out how to work with the new emulator to use it in my libOPNMIDI and in the OPN2 Bank Editor where I used emulator I got from GME project. It has void write0( int addr, int data );, void write1( int addr, int data ); functions to write register data (like in case of OPL3 emulators) and the void run( int pair_count, sample_t* out ); to generate an audio output chunk of specified length based on current state of data in registers. In case of new emulator it provides API which is similar to the real chip without having of a good manual to learn how to deal with it.

Can you implement those functions or provide a manual to let everyone easier control the chip?

nukeykt commented 6 years ago

Interfacing with the core is really not simple due to nature of the project. It requires 'delays' between write commands. I've already wrote wrapper code for VGMPlay which simplifies interfacing with the core. Look here: https://github.com/vgmrips/vgmplay/blob/master/VGMPlay/chips/ym3438.c https://github.com/vgmrips/vgmplay/blob/master/VGMPlay/chips/ym3438.h

Your interface functions will be look something like

write0(a,d)
{
OPN2_WriteBuffered(chip,0,a);
OPN2_WriteBuffered(chip,1,d);
}
write1(a,d)
{
OPN2_WriteBuffered(chip,2,a);
OPN2_WriteBuffered(chip,3,d);
}
run(count,out)
{
OPN2_GenerateStream(chip,out,count);
}

You'll probably need to change OPN2_GenerateStream to match your sample_t data type.

Wohlstand commented 6 years ago

Thanks, I'll check out this at evening when I'll come to home from my job place

Wohlstand commented 6 years ago

Okay, I have got it working at me, but I got next issues:

Maybe I need to initialize it a bit differently? (I gave same values as to GME's emulator I using now)

cardsOP2[i] = new ym3438_t;
std::memset(cardsOP2[i], 0, sizeof(ym3438_t));
OPN2_Reset(cardsOP2[i], (Bit32u)_parent->PCM_RATE, (Bit32u)(7153353.0 * 2.0));

Where PCM_RATE is an output sample rate (I usually set it as 44100 Hz)

I have posted my current stuff into the separated branch https://github.com/Wohlstand/libOPNMIDI/tree/nuked-opn2 In the src/opnmidi_opn2.cpp you can find the usage of the chip emulators

nukeykt commented 6 years ago

1) Yes. Frequency write order MUST BE reversed. 2) Hmm. x86 build of GPGX with my core loads CPU up to 14% on i5-3210M and x64 loads up to 4% on i7-4790 desktop. GPGX obviosly runs only one core. 3) Actually Genesis runs YM3438 at 7,6704545 MHz rate(7,600489 MHz on PAL Mega Drive).

Wohlstand commented 6 years ago
  1. I did x64 debug build and it eats too much even with one chip (lag still be). Not tested in release build
  2. I did some calibration of this on old emulator to get ability use full MIDI notes range, and yeah, LFO vibrato frequency is same with no matter on clock. I think here I will need for some different to calibrate...

P.S. Your emulator has much better volume model (I don't know why, but on old emulator native logarythmic volume was too strong and linealizing formula for OPL3 wasnt completely worked and volume still be logarythmic, but here I see volume is linealizyng fine ;-)

nukeykt commented 6 years ago

Did you remove 2.0 factor for master clock?

Wohlstand commented 6 years ago

Yes, I have tried to do that, and then I got lower sample output which then I tried to fix by division of PCM_RATE and lag is still same, even without rare division.

You can try out my libOPNMIDI in action (I did thematic branch with experimental code) by using test pro file for Qt Creator which builds simple player that will receive path to WOPN bank file and then path to MIDI file to play. It needs for SDL2.

P.S. For comparison what I mean LFO iterating faster, a short fragments dumped from both GME's and Nuked emulators:

Wohlstand commented 6 years ago

Okay, after your pull request LFO and CPU overload troubles are gone! Anyway, I see your OPN2 emulator requires much more power than same OPL3 emulator you did before, therefore I can't process more than 3 chips on same CPU core without getting of choppy playback.

Wohlstand commented 6 years ago

P.S. Is presented DC offset and noises is normal or it's a bug of the resampler?

nukeykt commented 6 years ago

Noisy output is normal as YM2612/3438's output is 9 bit wide. In case of YM2612, output is even more noise due to 'ladder' effect glitch, which was partially fixed in discrete YM3438 and completely fixed in ASIC YM3438. I have no idea what causes DC offset. Which chip type do you use(YM2612 or discrete/asic YM3438)? In case of the YM2612 it is likely due to 'ladder effect' emulation.

Wohlstand commented 6 years ago

I did used discrete YM3438 mode, I'll try out ASIC mode...

EDIT: Yeah, I have used OPN2_SetOptions(ym3438_type_discrete); which was totally incorrect (because of logic, noisy YM2612 was in use), when I have replaced that with OPN2_SetChipType(ym3438_type_asic);, noice is gone, and DC offcet gone too!

Wohlstand commented 6 years ago

Okay, why this difference in attack/decays angle between of emulators?

I have tried to play around of instrument's register write order (to do same that was in specification I used to learn the chip, where frequency byte order is also reversed), but I got same result with no matter to write order - attack/decay is longer on Nuked emulator than on GENS's (I found which emulator was originally until it came into GME, and I think, it is outdated and needs for update).

nukeykt commented 6 years ago

Gens' YM2612 core is quite outdated. I recommend to switch MAME core instead since it models internal work of YM2612 much more accurately.

Wohlstand commented 6 years ago

Okay, I'll try to use it when I'll come to my home. (I'll also replace emulator in the GME's core too) Also, with Nuked I see that even with three chips I getting clipping on a wide polyphony, so, I would to make volume reducer on output of each chip to avoid possible clipping with using of multiple chips (and multi-threading because Nuked OPN2 is much more powerful than even Nuked OPL3. Also, which of those chips you like more yourself? Proc of OPL is 8 various source waveforms and yeah, float32 sound output while OPN has sine only and 9-bit sound output as you have told, but proc of OPN is more built-in complex algorithms to combine operators, and yeah, porting of instruments between of those chips is not a simple task to repeat almost same output if that possible)

Wohlstand commented 5 years ago

Okay, keeping this issue be opened is no more needed. I'll close it.

jpcima commented 4 years ago

Hi @nukeykt

I think there is some trouble with the VGMPlay wrapper WriteBuffered, in case the buffer would be overflowed. I noticed how some register writes don't seem to pass through, and this routine is under high suspicion.

https://github.com/vgmrips/vgmplay/blob/c086bd2334d0efa02fc08815294c2674276eb367/VGMPlay/chips/ym3438.c#L1439-L1451

As I observe, this part of the routine must dequeue the buffered messages in case of a full buffer. If my understanding is correct, this has some problems.

Help will be very appreciated. I hope that my remarks above are making sense.

It's related to the effort of building a feature-limited OPNA on top of Nuked-OPN2. https://github.com/rerrahkr/BambooTracker/pull/94#issuecomment-584504828

jpcima commented 4 years ago

Sorry don't bother, I couldn't reproduce it on this repo's latest code, I'll ask again if I need.