Просмотр полной версии : Эмуляция AY/YM
Господа, поделитесь секретом, как съэмулировать сабж?
Я просто ламер в звуке, я больше по низкому уровню и БД.
Колупаю чисто для себя проектик, хочу приделать звук, но не понимаю, как из записей в регистры получить "волну", чтобы ее потом выкинуть в поток (хоть на звуковую, хоть в файл).
Колупаю чисто для себя проектик, хочу приделать звук, но не понимаю, как из записей в регистры получить "волну", чтобы ее потом выкинуть в поток (хоть на звуковую, хоть в файл).
Почитай про внутреннюю структуру чипа. Для начала сэмулируй один тоновый канал.
А можно ссылочку, что почитать? Пробовал разобраться в исходнике унрила, но слишком там одно за другое зацеплено, сходу не поймешь...
А можно ссылочку, что почитать? Пробовал разобраться в исходнике унрила, но слишком там одно за другое зацеплено, сходу не поймешь...
http://speccy.info/AY
Внизу ссылки, в том числе и на разные даташиты.
А можно ссылочку, что почитать? Пробовал разобраться в исходнике унрила, но слишком там одно за другое зацеплено, сходу не поймешь...
лучше разбираться в исходниках Unreal Speccy Portable, там код относящийся к эмуляции устройств более понятный и менее завязан на всё остальное.
Стал в ступор с тупой функцией вывода звука: слышу лишь "пуки". Вырезал из программы кусок, чтобы чисто - результат тот же. Никто не подскажет, где я ступил?
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
HWAVEOUT handle;
WAVEHDR whdr;
typedef unsigned long dword;
union SAMPLE
{
dword sample;
struct { short left, right; } ch;
};
SAMPLE buffer[16384];
int main()
{
int i;
WAVEFORMATEX wf;
wf.cbSize = sizeof(wf);
wf.wFormatTag = WAVE_FORMAT_PCM;
wf.nSamplesPerSec = 44100;
wf.wBitsPerSample = 16;
wf.nChannels = 2;
wf.nBlockAlign = 4;
wf.nAvgBytesPerSec = 176400;
MMRESULT r = waveOutOpen(&handle, WAVE_MAPPER, &wf, NULL, 0, CALLBACK_NULL);
if(r != MMSYSERR_NOERROR) printf("Аудио не запустилось!");
for (int i=0; i<16384; i++) {
buffer[i].sample=0x3FFF3FFF;
}
for (i=0; i<100; i++) {
whdr.dwBufferLength=882*4;
whdr.lpData=(char *)buffer;
whdr.dwFlags=0;
whdr.dwBytesRecorded = 0;
whdr.dwUser = 0;
whdr.dwLoops = 0;
whdr.lpNext = NULL;
whdr.reserved = 0;
MMRESULT r=waveOutPrepareHeader(handle,&whdr,sizeof(whdr));
if (r==MMSYSERR_NOERROR) waveOutWrite(handle,&whdr,sizeof(whdr));
while(!(whdr.dwFlags&WHDR_DONE)) Sleep(1);
waveOutUnprepareHeader(handle,&whdr,sizeof(whdr));
}
return 0;
}
В исходниках zxmak2 посмотри код AY8910, там все просто и завязано только на шину z80. Наружу торчит буфер с текущим фреймом (см интерфейс ISoundRenderer)
В исходниках zxmak2 посмотри код AY8910, там все просто и завязано только на шину z80. Наружу торчит буфер с текущим фреймом (см интерфейс ISoundRenderer)
Как ему это поможет пофиксить код, использующий waveOut API?:)
Alexander Makeev, к сожалению с шарпом слабо знаком, мне сложно понять, что да как...
После серии экспериментов появилось подозрение, что все дело в ожидании, пока проиграется текущий фрагмент. То есть если я правильно понял, то после проигрывания текущего фрагмента звук выключается на какое-то время, после которого идет пауза, формируемая винапи. Пока не придумал, как обойти...
То есть если я правильно понял, то после проигрывания текущего фрагмента звук выключается на какое-то время, после которого идет пауза, формируемая винапи. Пока не придумал, как обойти...
У тебя просто неправильно сделано ожидание окончания воспроизведения.
Надо как-то так:
HANDLE event = CreateEvent(0, FALSE, FALSE, 0);//создаем объект события
waveOutOpen(&Handle, WAVE_MAPPER, &wf, DWORD_PTR(event), 0, CALLBACK_EVENT | WAVE_FORMAT_DIRECT);//открываем устройство и говорим, что надо по окончании воспроизведения буфера выставлять событие
waveOutPrepareHeader(handle,&whdr,sizeof(whdr));//готовим буфер
whdr.dwFlags |= WHDR_DONE;//помечаем как свободный
waveOutWrite(...);//пишем
while (!(whdr.dwFlags & WHDR_DONE))
{
WaitForSingleObject(event, INFINITE);
}
//дождались окончания- закрываем все
waveOutUnprepare(...)
waveOutClose(...);
CloseHandle(event);
Ну, наверно вы не проверили свой код. Тот же результат.
Ну, наверно вы не проверили свой код. Тот же результат.
Ну свой код я выдернул из другого своего, работающего, кода.
А что ожидается от данного куска? Он должен проиграть два щелчка по идее- в начале воспроизведения и в конце.
Ну я подготовил фрейм, проиграл отрывок, пошел следующий готовить. Так вот при любом раскладе получается как в том отрывке, что я приводил - по сути бесконечный цикл с паузой только на проигрывание отрывка. Вот тут и получается, что каким бы методом я не ждал - вашим или своим (из MSDN), я получаю щелчок.
Вот тут и получается, что каким бы методом я не ждал - вашим или своим (из MSDN), я получаю щелчок.
Щелчок из-за того, что устройству нечего играть во время подготовки данных. Значит надо использовать двойную (в простейшем случае) буферизацию.
Я так и подозревал, но пока с синхронизацией не очень. По сути получается, что не дожидаясь окончания проигрывания первого куска ставить на проигрывание второй, и только на третьем ждать пока выпадет из очереди первый, после чего и третий ставить. Наверно так.
Я так и подозревал, но пока с синхронизацией не очень. По сути получается, что не дожидаясь окончания проигрывания первого куска ставить на проигрывание второй, и только на третьем ждать пока выпадет из очереди первый, после чего и третий ставить. Наверно так.
Алгоритм такой же, как и при работе с двумя спековскими экранами.
10 создать event
20 открыть устройство
30 создать и инициализировать буфер1
40 создать и инициализировать буфер2
50 курсор=буфер1
60 ждем пока освободится *курсор
70 пишем в *курсор
80 отправляем *курсор на воспроизведение
90 если курсор=буфер1, то курсор=буфер2, иначе курсор=буфер1
100 если не завершили переход на 60
110 закрыть устройство
120 освободить буфер1
130 освободить буфер2
140 освободить event
Кусочек из AY эмулятора на PureBasic отвечающий за вывод звука:
; ----------------------------------------------
; Public functions
; ----------------------------------------------
Declare.l Sound_Start()
Declare.l Sound_Stop()
Declare.l Sound_SetCallbacks(adressReset.l, adressSample.l, adressUpdate.l, adressTick.l)
Declare.l Sound_SetCallbackReset(adress.l)
Declare.l Sound_GetCallbackReset()
Declare.l Sound_SetCallbackSample(adress.l)
Declare.l Sound_GetCallbackSample()
Declare.l Sound_SetCallbackTick(adress.l)
Declare.l Sound_GetCallbackTick()
Declare.l Sound_SetCallbackUpdate(adress.l)
Declare.l Sound_GetCallbackUpdate()
; ----------------------------------------------
; Private functions
; ----------------------------------------------
Declare.l Sound_Software_Fill()
Declare.l Sound_Software_DoubleBufferThread(value.l)
Declare.l Sound_ClearBuffer()
; ----------------------------------------------
; Private vars
; ----------------------------------------------
;- Vars
#Sound_LATENCY = 20
#Sound_SLEEPTIME = 4
Global Sound_BufferSizeMs.l = 1000
Global Sound_Channels.l = 2
Global Sound_Bits.l = 16
Global Sound_Bytes.l = Sound_Bits / 8 * Sound_Channels
Global Sound_MixRate.l = 44100
Global Sound_BlockSize.l = ((Sound_MixRate * #Sound_LATENCY / 1000) + 3) & $FFFFFFFC ; // Number of *samples*
Global Sound_BufferSize.l = ( Sound_BlockSize << 2 ) << 1 ;( Sound_BlockSize * (Sound_BufferSizeMs / #Sound_LATENCY) ) << 1 ; // make it perfectly divisible by granularity - double buffer
Global Sound_TotalBlocks.l = Sound_BufferSize / Sound_BlockSize
Global Sound_BufferMemoryLength.l = Sound_BufferSize * Sound_Bytes ;// 16bits
Global Sound_BufferMemory.l = 0
Global Sound_SampleCount.l = 0
Global Sound_SampleMax.l = Sound_MixRate / 50
Global Sound_WaveHdr.WAVEHDR
Global Sound_WaveOutHandle.l = 0
Global Sound_Software_Exit.l = #False
Global Sound_Software_ThreadFinished.l = #False
Global Sound_Software_Thread.l = 0
Global Sound_Software_FillBlock.l = 0
Global Sound_Software_RealBlock.l = 0
Global Sound_Software_UpdateMutex.l = #False
Global Sound_CallbackReset.l = 0
Global Sound_CallbackSample.l = 0
Global Sound_CallbackTick.l = 0
Global Sound_CallbackUpdate.l = 0
Debug_("Sound_Bytes "+Str(Sound_Bytes))
Debug_("Sound_BlockSize "+Str(Sound_BlockSize))
Debug_("Sound_BufferSize "+Str(Sound_BufferSize))
Debug_("Sound_TotalBlocks "+Str(Sound_TotalBlocks))
Debug_("Sound_BufferMemoryLength "+Str(Sound_BufferMemoryLength))
Debug_("Sound_SampleMax "+Str(Sound_SampleMax))
Debug_("")
;- Procedures
Procedure.l Sound_Software_Fill()
Protected i.l
Protected mixpos.l
Protected mixBuffer.l
mixpos = Sound_Software_FillBlock * Sound_BlockSize
mixBuffer = Sound_BufferMemory + mixpos * Sound_Bytes
; Debug_( Str(Sound_Software_FillBlock)+" "+Str(mixBuffer) )
For i=0 To Sound_BlockSize-1
If Sound_CallbackSample<>0 : CallFunctionFast(Sound_CallbackSample,mixBuffer) : EndIf
Sound_SampleCount+1
If Sound_SampleCount = Sound_SampleMax
If Sound_CallbackTick<>0 : CallFunctionFast(Sound_CallbackTick) : EndIf
Sound_SampleCount=0
EndIf
mixBuffer + Sound_Bytes
Next
Sound_Software_FillBlock + 1
If Sound_Software_FillBlock >= Sound_TotalBlocks
Sound_Software_FillBlock = 0
EndIf
EndProcedure
Procedure.l Sound_CreateBuffer()
If Sound_BufferMemory=0
Sound_BufferMemory = AllocateMemory(Sound_BufferMemoryLength)
EndIf
EndProcedure
Procedure.l Sound_FreeBuffer()
If Sound_BufferMemory<>0
; FreeMemory(Sound_BufferMemory)
Sound_BufferMemory = 0
EndIf
EndProcedure
Procedure.l Sound_ClearBuffer()
For i=0 To Sound_BufferMemoryLength-1
PokeL (Sound_BufferMemory+i,0)
Next
EndProcedure
Procedure.l Sound_Start()
Protected i.l
Protected res.l
Protected pcmwf.WAVEFORMATEX
Debug_ ("Sound start")
If Sound_CallbackReset<>0 : CallFunctionFast(Sound_CallbackReset) : EndIf
Sound_CreateBuffer()
Sound_ClearBuffer()
Sound_WaveOutHandle = 0
pcmwf\wFormatTag = #WAVE_FORMAT_PCM
pcmwf\nChannels = Sound_Channels
pcmwf\wBitsPerSample = Sound_Bits
pcmwf\nBlockAlign = Sound_Bytes
pcmwf\nSamplesPerSec = Sound_MixRate
pcmwf\nAvgBytesPerSec = Sound_MixRate * Sound_Bytes
pcmwf\cbSize = 0
res.l = waveOutOpen_( @Sound_WaveOutHandle, #WAVE_MAPPER, @pcmwf, 0, 0, 0)
Debug_ ("waveOutOpen "+Str(res))
Debug_ ("waveOutHandle "+Str(Sound_WaveOutHandle))
If res<>0
ProcedureReturn 1
EndIf
Sound_WaveHdr\dwFlags = #WHDR_BEGINLOOP | #WHDR_ENDLOOP
Sound_WaveHdr\lpData = Sound_BufferMemory ;Sound_Memory_Calloc(length);
Sound_WaveHdr\dwBufferLength = Sound_BufferMemoryLength
Sound_WaveHdr\dwBytesRecorded = 0
Sound_WaveHdr\dwUser = 0
Sound_WaveHdr\dwLoops = -1
Sound_WaveHdr\lpNext = 0
res = waveOutPrepareHeader_(Sound_WaveOutHandle, @Sound_WaveHdr, SizeOf(WAVEHDR))
Debug_ ("waveOutPrepareHeader "+Str(res))
If res<>0
ProcedureReturn 2
EndIf
res = waveOutWrite_(Sound_WaveOutHandle, @Sound_WaveHdr, SizeOf(WAVEHDR))
Debug_ ("waveOutWrite "+Str(res))
If res<>0
ProcedureReturn 3
EndIf
Sound_Software_FillBlock = 0
Sound_Software_RealBlock = 0
Sound_Software_UpdateMutex = #False
Sound_SampleCount = 0
Sound_Software_Exit = #False
Debug_ ("Create thread")
timeBeginPeriod_(1)
Sound_Software_Thread = CreateThread(@Sound_Software_DoubleBufferThread(), 0)
ThreadPriority(Sound_Software_Thread, 32) ; THREAD_PRIORITY_TIME_CRITICAL)
Debug_ ("")
ProcedureReturn 0
EndProcedure
Procedure.l Sound_Stop()
Debug_("Sound stop")
; kill Thread
Sound_Software_Exit = #True
While Not Sound_Software_ThreadFinished
Debug_ ("Wait thread")
Sleep_(10)
Wend
Sound_Software_Thread = 0
Debug_("Kill thread")
res = waveOutReset_(Sound_WaveOutHandle)
Debug_ ("waveOutReset "+Str(res))
res = waveOutUnprepareHeader_(Sound_WaveOutHandle, @Sound_WaveHdr, SizeOf(WAVEHDR))
Debug_ ("waveOutUnprepareHeader "+Str(res))
Debug_ ("WHDR_DONE flag " + Str( Sound_WaveHdr\dwFlags & #WHDR_DONE ))
res = waveOutClose_(Sound_WaveOutHandle)
Debug_ ("waveOutClose "+Str(res))
Sound_FreeBuffer()
ProcedureReturn 0
EndProcedure
Procedure.l Sound_SetCallbackReset(adress.l)
If adress<>0
Sound_CallbackReset = adress
ProcedureReturn 1
Else
ProcedureReturn 0
EndIf
EndProcedure
Procedure.l Sound_GetCallbackReset()
ProcedureReturn Sound_CallbackReset
EndProcedure
Procedure.l Sound_SetCallbackSample(adress.l)
If adress<>0
Sound_CallbackSample = adress
ProcedureReturn 1
Else
ProcedureReturn 0
EndIf
EndProcedure
Procedure.l Sound_GetCallbackSample()
ProcedureReturn Sound_CallbackSample
EndProcedure
Procedure.l Sound_SetCallbackTick(adress.l)
If adress<>0
Sound_CallbackTick = adress
ProcedureReturn 1
Else
ProcedureReturn 0
EndIf
EndProcedure
Procedure.l Sound_GetCallbackTick()
ProcedureReturn Sound_CallbackTick
EndProcedure
Procedure.l Sound_SetCallbackUpdate(adress.l)
If adress<>0
Sound_CallbackUpdate = adress
ProcedureReturn 1
Else
ProcedureReturn 0
EndIf
EndProcedure
Procedure.l Sound_GetCallbackUpdate()
ProcedureReturn Sound_CallbackUpdate
EndProcedure
Procedure.l Sound_SetCallbacks(adressReset.l, adressSample.l, adressUpdate.l, adressTick.l)
Sound_SetCallbackReset (adressReset)
Sound_SetCallbackSample (adressSample)
Sound_SetCallbackTick (adressTick)
Sound_SetCallbackUpdate (adressUpdate)
EndProcedure
Procedure.l Sound_Software_DoubleBufferThread(value.l)
Protected mmt.MMTIME
Protected cursorpos.l
Protected cursorblock.l
Protected prevblock.l
;Debug_ ("Thread Started")
Sound_Software_ThreadFinished = #False
While Not Sound_Software_Exit
mmt\wType = #TIME_BYTES
waveOutGetPosition_(Sound_WaveOutHandle, @mmt, SizeOf(MMTIME))
cursorpos = mmt\u\cb >> 2
cursorpos % Sound_BufferSize
cursorblock = cursorpos / Sound_BlockSize
;Debug_(Str(cursorpos)+" "+Str(cursorblock)+" "+Str(Sound_Software_FillBlock))
prevblock = cursorblock - 1
If prevblock < 0
prevblock = Sound_TotalBlocks - 1
EndIf
While Sound_Software_FillBlock <> cursorblock
Sound_Software_UpdateMutex = #True
Sound_Software_Fill()
Sound_Software_RealBlock+1
If Sound_Software_RealBlock >= Sound_TotalBlocks
Sound_Software_RealBlock = 0
EndIf
Sound_Software_UpdateMutex = #False
Wend
If Sound_CallbackUpdate<>0 : CallFunctionFast(Sound_CallbackUpdate) : EndIf
Sleep_(#Sound_SLEEPTIME)
Wend
;Debug_ ("Thread Finished")
Sound_Software_ThreadFinished = #True
EndProcedure
Если у нас есть 71680 тактов на кадр, 882 куска звука, то получается нам надо рендерить каждые 81.26 тактов.
Ну с AY пока абы что, но это понятно, только понял как оно работает. Но вот спикер - девайс простейший. Ни волны, ни плавных уровней. Сразу же заработало. Диззик прекрасно пищит, прям как на реале. Чудесно.
Но вот дальше тупичок. Почему-то простой клавиатурный "пук" не слышен. Там нет никаких хитростей?
Ну я подготовил фрейм, проиграл отрывок, пошел следующий готовить. Так вот при любом раскладе получается как в том отрывке, что я приводил - по сути бесконечный цикл с паузой только на проигрывание отрывка. Вот тут и получается, что каким бы методом я не ждал - вашим или своим (из MSDN), я получаю щелчок.
пишешь данные в буфер постоянно, пока данных нету - пишешь нули, вот и все
Powered by vBulletin® Version 4.2.5 Copyright © 2025 vBulletin Solutions, Inc. All rights reserved. Перевод: zCarot