Manu escribió:radastan, el problema del zumbido ya está arreglado. Como ya he dicho en un mensaje anterior, era un tema del buffer. Había valores a cero al final, y provocaban el zumbido a 50Hz
![Smile :)](./images/smilies/icon_smile.gif)
Por otro lado zx81, lo que indicas acerca de los timings más o menos lo tengo en cuenta. Lo que sí es cierto es que a pesar de que la emulación del Z80 es bastante completa (con los flags ocultos, el MEMPTR, y todas las operaciones no documentadas funcionando), la del Spectrum como máquina es muy básica (no hay contienda, y el frame lo construyo al final en vez de instrucción a instrucción). De hecho, es una arquitectura que monté para poder ver los tests del Z80, que casi todos eran para Spectrum
![Cool 8)](./images/smilies/icon_cool.gif)
Buf, te has dejado entonces una parte importante de la emulación del Spectrum, la memoria en contienda y que, lamentablemente, tiene un impacto bastante fuerte en el core Z80. Necesitas una emulación básica de esa peculiaridad del Spectrum para que funcionen algunos juegos y para que funcione todo (incluso las demos más puñeteras) no basta con que las instrucciones tarden N estados, tienen que estar ejecutados en un cierto orden que emula a los ciclos de máquina de la CPU. Para colmo, la I/O al puerto #FE siempre es en contienda, así que de entrada tienes todos los timings mal, no esperes que suene bien del todo. Por cierto, la pantalla tampoco la puedes actualizar al final de cada frame o te vas a encontrar con muchas sorpresas.
Manu escribió:Para que te hagas una idea, la clase que emula el sonido de 1 bit es esta:
Código: Seleccionar todo
package com.emubee.sound;
public class SingleBitAudio {
protected double microseconds;
protected final static double MICROSECONDS_PER_CYCLE = 1000000.0 / 44100.0;
protected boolean state;
protected byte[] buffer;
protected int pos;
public SingleBitAudio(int samples) {
buffer = new byte[samples * 4]; // 16 bits, stereo
pos = 0;
}
public void reset() {
pos = 0;
}
public void update(double time) {
microseconds += time;
if (microseconds >= 0) {
buffer[pos++] = (byte) (state ? 32 : -32);
buffer[pos++] = 0;
buffer[pos++] = (byte) (state ? 32 : -32);
buffer[pos++] = 0;
microseconds -= MICROSECONDS_PER_CYCLE;
}
}
public void setState(boolean state) {
this.state = state;
}
public byte[] getBuffer() {
byte[] res = new byte[pos];
System.arraycopy(buffer, 0, res, 0, pos);
return res;
}
}
Y el método que "renderiza" un frame en el Spectrum es este:
Código: Seleccionar todo
public void renderFrame() {
boolean ready = false;
audio.reset();
while (!ready) {
int c = cpu.execute();
ready = ula.update(c);
double microseconds = c * MICROSECONDS_PER_CYCLE;
cas.update(microseconds);
audio.update(microseconds);
}
frameConverted = false;
}
Tanto el gestor de cassete como el del audio trabajan en microsegundos y no en ciclos del Spectrum, porque los uso en otras máquinas. De hecho, la carga de cintas TZX se hace exactamente igual en el Spectrum que en el Amstrad CPC... y funciona
![Smile :)](./images/smilies/icon_smile.gif)
En cualquier caso, me temo que voy a tener que pelearme bastante con el sonido, ya que nunca había programado nada "multimedia" en mi vida...
El problema de ese método es que el out ejecutará un audio.setState y para cuando quieras llegar al Audio.update ya has metido un error porque la instrucción out tiene una contención al principio de la instrucción y otra al final y el cambio físico del bit se produce en medio, puedes verlo en la implementación del método Spectrum.outPort de mi emulador. Haciéndolo así te vas a equivocar en al menos 3 t-states por cada instrucción out y, aunque eso no parezca mucho, programas como Arkanoid o Fairlight cambian el bit del beeper cada 40 t-states o menos. La acumulación de errores generará mucho ruido, efectos de aliasing y otras cosas divertidas.
Tampoco es que sea muy eficiente ese bucle ejecutado instrucción a instrucción. La mayor parte del tiempo, no se ejecuta ningún out que afecte al beeper. En el caso de que no haya ningún cambio, ejecutas el Audio.update tantas veces como ejecutes el bucle while cuando en una sola llamada podrías crearlo igualmente al finalizar el frame. En cualquier caso, es imposible que te suene bien mientras no emules al menos la contención en la I/O, por eso te faltan samples al final del frame.
En fin, solo son ideas que te doy que a mi me hubieran evitado muchísimas horas de romperme el coco...
![Very Happy :D](./images/smilies/icon_biggrin.gif)
Edito para añadir que el error que se introduce de esa manera es incluso peor de lo que pensaba porque el error fundamental es que cuando llegas al Audio.update "parece" que todo el tiempo de ejecución de la instrucción out el bit de estado mantenía el neuvo estado y no el viejo, que es lo que sucede en realidad la mayor parte del tiempo. Para que me entiendas: si la instrucción dura 11 t-states (sin contar la contención), el cambio efectivo del bit se produce casi al final de la instrucción (digamos en el t-state 8 de 11) y cuando llegas al update actualizas el tiempo de los 11 t-states como si fueran todo uno, cuando solo 3 de los 11 pertenecen al tiempo de nuevo estado del bit del altavoz. 8 t-states de error por out son muchos (y realmente son más).
P.D.: Yo tampoco tenía ni idea de multimedia cuando me puse (y sigo sin tenerla). Pero lo que me aterraba de verdad no era el beeper, que me parecía relativamente sencillo comparado con emular a un AY-3-8912.